mirror of
https://github.com/We-Dont-Byte/Mind_Reader.git
synced 2025-01-18 18:45:59 +00:00
merged from master
This commit is contained in:
commit
acd7a0e442
14
media/dep.svg
Normal file
14
media/dep.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="dep" transform="translate(4.000000, 6.000000)" fill="#ffffff">
|
||||
<g id="Page-1">
|
||||
<path d="M5.14285714,16.2778182 C5.14285714,16.9917576 4.89285714,17.5986061 4.39285714,18.0983636 C3.89285714,18.5981212 3.28571429,18.848 2.57142857,18.848 C1.85714286,18.848 1.25,18.5981212 0.75,18.0983636 C0.25,17.5986061 0,16.9917576 0,16.2778182 C0,15.5638788 0.25,14.9570303 0.75,14.4572727 C1.25,13.9575152 1.85714286,13.7076364 2.57142857,13.7076364 C3.28571429,13.7076364 3.89285714,13.9575152 4.39285714,14.4572727 C4.89285714,14.9570303 5.14285714,15.5638788 5.14285714,16.2778182 L5.14285714,16.2778182 L5.14285714,16.2778182 Z M5.14285714,9.424 C5.14285714,10.1379394 4.89285714,10.7447879 4.39285714,11.2445455 C3.89285714,11.744303 3.28571429,11.9941818 2.57142857,11.9941818 C1.85714286,11.9941818 1.25,11.744303 0.75,11.2445455 C0.25,10.7447879 0,10.1379394 0,9.424 C0,8.7100606 0.25,8.1032121 0.75,7.6034545 C1.25,7.103697 1.85714286,6.8538182 2.57142857,6.8538182 C3.28571429,6.8538182 3.89285714,7.103697 4.39285714,7.6034545 C4.89285714,8.1032121 5.14285714,8.7100606 5.14285714,9.424 L5.14285714,9.424 L5.14285714,9.424 Z M24,14.9927273 L24,17.5629091 C24,17.6789242 23.9575893,17.779322 23.8727679,17.8641023 C23.7879464,17.9488826 23.6875,17.9912727 23.5714286,17.9912727 L7.2857143,17.9912727 C7.1696429,17.9912727 7.0691964,17.9488826 6.984375,17.8641023 C6.8995536,17.779322 6.8571429,17.6789242 6.8571429,17.5629091 L6.8571429,14.9927273 C6.8571429,14.8767121 6.8995536,14.7763144 6.984375,14.6915341 C7.0691964,14.6067538 7.1696429,14.5643636 7.2857143,14.5643636 L23.5714286,14.5643636 C23.6875,14.5643636 23.7879464,14.6067538 23.8727679,14.6915341 C23.9575893,14.7763144 24,14.8767121 24,14.9927273 L24,14.9927273 L24,14.9927273 Z M5.14285714,2.57018182 C5.14285714,3.2841212 4.89285714,3.8909697 4.39285714,4.3907273 C3.89285714,4.8904848 3.28571429,5.1403636 2.57142857,5.1403636 C1.85714286,5.1403636 1.25,4.8904848 0.75,4.3907273 C0.25,3.8909697 0,3.2841212 0,2.57018182 C0,1.85624242 0.25,1.24939394 0.75,0.74963636 C1.25,0.24987879 1.85714286,0 2.57142857,0 C3.28571429,0 3.89285714,0.24987879 4.39285714,0.74963636 C4.89285714,1.24939394 5.14285714,1.85624242 5.14285714,2.57018182 L5.14285714,2.57018182 L5.14285714,2.57018182 Z M24,8.1389091 L24,10.7090909 C24,10.8251061 23.9575893,10.9255038 23.8727679,11.0102841 C23.7879464,11.0950644 23.6875,11.1374545 23.5714286,11.1374545 L7.2857143,11.1374545 C7.1696429,11.1374545 7.0691964,11.0950644 6.984375,11.0102841 C6.8995536,10.9255038 6.8571429,10.8251061 6.8571429,10.7090909 L6.8571429,8.1389091 C6.8571429,8.0228939 6.8995536,7.9224962 6.984375,7.8377159 C7.0691964,7.7529356 7.1696429,7.7105455 7.2857143,7.7105455 L23.5714286,7.7105455 C23.6875,7.7105455 23.7879464,7.7529356 23.8727679,7.8377159 C23.9575893,7.9224962 24,8.0228939 24,8.1389091 L24,8.1389091 L24,8.1389091 Z M24,1.28509091 L24,3.8552727 C24,3.9712879 23.9575893,4.0716856 23.8727679,4.1564659 C23.7879464,4.2412462 23.6875,4.2836364 23.5714286,4.2836364 L7.2857143,4.2836364 C7.1696429,4.2836364 7.0691964,4.2412462 6.984375,4.1564659 C6.8995536,4.0716856 6.8571429,3.9712879 6.8571429,3.8552727 L6.8571429,1.28509091 C6.8571429,1.16907576 6.8995536,1.06867803 6.984375,0.98389773 C7.0691964,0.89911742 7.1696429,0.85672727 7.2857143,0.85672727 L23.5714286,0.85672727 C23.6875,0.85672727 23.7879464,0.89911742 23.8727679,0.98389773 C23.9575893,1.06867803 24,1.16907576 24,1.28509091 L24,1.28509091 L24,1.28509091 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
127
package.json
127
package.json
@ -47,10 +47,27 @@
|
||||
"command": "mind-reader.selectTheme",
|
||||
"title": "Select Theme"
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
{
|
||||
"command": "mind-reader.openWebview",
|
||||
"title": "Mind Reader Webview"
|
||||
=======
|
||||
|
||||
{
|
||||
"command": "mind-reader.runLineContext",
|
||||
"title": "Run Line Context"
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.runCursorContext",
|
||||
"title": "Run Cursor Context"
|
||||
},
|
||||
{
|
||||
"command": "mind-reader.getIndent",
|
||||
"title": "Get Line Indentation"
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
@ -80,8 +97,86 @@
|
||||
"command": "mind-reader.resetEditorScale",
|
||||
"key": "shift+enter",
|
||||
"mac": ""
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.showAllSymbols",
|
||||
"key": "Ctrl+T",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.gotoLine",
|
||||
"key": "CTRL+G",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.quickOpen",
|
||||
"key": "CTRL+P",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.gotoSymbol",
|
||||
"key": "Ctrl+Shift+O",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.showProblems",
|
||||
"key": "Ctrl+Shift+M",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.nextInFiles",
|
||||
"key": "F8",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.prevInFiles",
|
||||
"key": "Shift+F8",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup",
|
||||
"key": "Ctrl+Tab",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.navigateBack",
|
||||
"key": "Ctrl+Alt+-",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.getuickInputBack",
|
||||
"key": "Ctrl+Alt+-",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.navigateForward",
|
||||
"key": "Ctrl+Shift+-",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.selectTheme",
|
||||
"key": "Ctrl+Shift+1",
|
||||
"mac": ""
|
||||
},
|
||||
|
||||
{
|
||||
"command": "mind-reader.getIndent",
|
||||
"key": "Shift+Tab",
|
||||
"mac": ""
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/context": [
|
||||
{
|
||||
@ -154,7 +249,7 @@
|
||||
"LEGO® Education SPIKE™ Prime Set (45678)"
|
||||
]
|
||||
},
|
||||
"mindreader.screenReader": {
|
||||
"mindreader.reader.screenReader": {
|
||||
"type": "string",
|
||||
"description": "Specifies which screen reader to optimize for.",
|
||||
"default": "NVDA",
|
||||
@ -169,6 +264,11 @@
|
||||
"Apple VoiceOver (macOS)"
|
||||
]
|
||||
},
|
||||
"mindreader.reader.contextWindow": {
|
||||
"type": "number",
|
||||
"description": "The number of words around the cursor to use when reading the cursor context",
|
||||
"default": 1
|
||||
},
|
||||
"mindreader.connection.connectAutomatically": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether to try to automatically detect and communicate with a connected Hub.",
|
||||
@ -179,7 +279,26 @@
|
||||
"markdownDescription": "Specifies the serial port path to use if `#mindreader.connectAutomatically#` is not set."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"accessActions": [
|
||||
{
|
||||
"id": "accessActions",
|
||||
"name": "Access Actions",
|
||||
"icon": "media/dep.svg",
|
||||
"contextualTitle": "Accessibility Menu Actions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "accessActions",
|
||||
"title": "Access Actions",
|
||||
"icon": "media/dep.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
|
47
src/accessNodeProvider.ts
Normal file
47
src/accessNodeProvider.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// list of all actions
|
||||
let actions: AccessAction[] = [];
|
||||
|
||||
class AccessAction extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
public readonly command: vscode.Command
|
||||
) {
|
||||
super(label, vscode.TreeItemCollapsibleState.None);
|
||||
}
|
||||
};
|
||||
|
||||
export default class AccessNodeProvider implements vscode.TreeDataProvider<AccessAction> {
|
||||
public getTreeItem(a: AccessAction): vscode.TreeItem {
|
||||
return a;
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<AccessAction[]> {
|
||||
if (actions.length === 0) {
|
||||
// fetch and cache mind-reader options
|
||||
let cmds: string[] = await vscode.commands.getCommands(true); // get non-builtin commands
|
||||
cmds = cmds.filter(x => x.startsWith('mind-reader')); // filter mind-reader commands
|
||||
|
||||
cmds.forEach(c => {
|
||||
let humanReadable = c.replace(/^mind-reader\./, ''); // strip extensions name
|
||||
|
||||
// Convert camelCaseText to Title Case Text
|
||||
humanReadable = humanReadable.replace(/([A-Z])/g, ' $1');
|
||||
humanReadable = humanReadable.charAt(0).toUpperCase() + humanReadable.slice(1);
|
||||
|
||||
// add item to actions
|
||||
actions.push(new AccessAction(
|
||||
humanReadable,
|
||||
{
|
||||
title: humanReadable,
|
||||
command: c,
|
||||
tooltip: humanReadable
|
||||
}
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(actions);
|
||||
}
|
||||
}
|
220
src/commands.ts
220
src/commands.ts
@ -1,4 +1,5 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as pl from './pylex';
|
||||
|
||||
/**
|
||||
* @type {Object} Command // Command to register with the VS Code Extension API
|
||||
@ -52,6 +53,79 @@ const commands: Command[] = [
|
||||
{
|
||||
name: 'mind-reader.openWebview',
|
||||
callback: openWebview,
|
||||
},
|
||||
|
||||
//Navigation Keys......
|
||||
{
|
||||
name: 'mind-reader.showAllSymbols',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.showAllSymbols'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.gotoLine',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.gotoLine'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.quickOpen',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.quickOpen'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.gotoSymbol',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.gotoSymbol'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.showProblems',
|
||||
callback: () => vscode.commands.executeCommand('workbench.actions.view.problems'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.nextInFiles',
|
||||
callback: () => vscode.commands.executeCommand('editor.action.marker.nextInFiles'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.prevInFiles',
|
||||
callback: () => vscode.commands.executeCommand('editor.action.marker.prevInFiles'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.showCommands',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.showCommands'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.navigateBack',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.navigateBack'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.getuickInputBack',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.quickInputBack'),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'mind-reader.navigateForward',
|
||||
callback: () => vscode.commands.executeCommand('workbench.action.navigateForward'),
|
||||
},
|
||||
{
|
||||
name: 'mind-reader.runLineContext',
|
||||
callback: runLineContext,
|
||||
},
|
||||
{
|
||||
name: 'mind-reader.runCursorContext',
|
||||
callback: runCursorContext
|
||||
},
|
||||
{
|
||||
name: 'mind-reader.getIndent',
|
||||
callback: getIndent
|
||||
}
|
||||
];
|
||||
|
||||
@ -122,4 +196,150 @@ function getWebviewContent() {
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function getIndent(): void {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if(editor)
|
||||
{
|
||||
let lineNum = editor.selection.active.line + 1;
|
||||
let textLine = editor.document.lineAt(lineNum - 1);
|
||||
if(textLine.isEmptyOrWhitespace)
|
||||
{
|
||||
vscode.window.showInformationMessage("Line number " + lineNum.toString() + " Is Empty");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Grab tab format from open document
|
||||
let tabFmt = {
|
||||
size: editor.options.tabSize as number,
|
||||
hard: !editor.options.insertSpaces
|
||||
};
|
||||
let i = pl.Lexer.getIndent(textLine.text, tabFmt);
|
||||
vscode.window.showInformationMessage("Line Number " + lineNum.toString() + " Indentation " + i.toString());
|
||||
}
|
||||
}
|
||||
else{
|
||||
vscode.window.showErrorMessage('No document currently active');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function runLineContext(): void {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (editor){
|
||||
// current text and line number
|
||||
let editorText = editor.document.getText();
|
||||
let line = editor.selection.active.line;
|
||||
|
||||
// get tab info settings
|
||||
let size = parseInt(editor.options.tabSize as string);
|
||||
let hard = !editor.options.insertSpaces;
|
||||
|
||||
// initialize parser
|
||||
let parser = new pl.Parser(editorText, {size, hard});
|
||||
parser.parse();
|
||||
|
||||
let context = parser.context(line);
|
||||
|
||||
// build text
|
||||
let contentString = createContextString(context, line);
|
||||
vscode.window.showInformationMessage(contentString);
|
||||
} else {
|
||||
vscode.window.showErrorMessage('No document currently active');
|
||||
}
|
||||
}
|
||||
|
||||
function createContextString(context: pl.LexNode[], line: number): string {
|
||||
if (context.length < 1) {
|
||||
throw new Error('Cannot create context string for empty context');
|
||||
}
|
||||
|
||||
let contextString = 'Line ' + (line+1); // 1 based
|
||||
if (context[0].token && context[0].token.attr) {
|
||||
contextString += ': ' + context[0].token.type.toString() + ' ' + context[0].token.attr.toString();
|
||||
}
|
||||
for (let i = 1; i < context.length; i++) {
|
||||
let node = context[i];
|
||||
if (node.label === 'root') {
|
||||
// root
|
||||
contextString += ' in the Document Root';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.token!.type !== pl.PylexSymbol.EMPTY &&
|
||||
node.token!.type !== pl.PylexSymbol.INDENT) {
|
||||
contextString += ' inside ' + node.token!.type.toString();
|
||||
if (node.token!.attr) {
|
||||
contextString += ' ' + node.token!.attr.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return contextString;
|
||||
}
|
||||
|
||||
// find up to `n` words around the cursor, where `n` is
|
||||
// the value of `#mindreader.reader.contextWindow`
|
||||
function runCursorContext(): void {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
vscode.window.showErrorMessage("RunCursorContext: No Active Editor");
|
||||
return;
|
||||
}
|
||||
|
||||
const cursorPos: vscode.Position = editor.selection.active;
|
||||
const text: string = editor.document.lineAt(cursorPos).text;
|
||||
const windowSize: number = vscode.workspace.getConfiguration('mindreader').get('reader.contextWindow')!;
|
||||
|
||||
let trimmedText = text.trimStart(); // trim leading whitespace
|
||||
let leadingWS = text.length - trimmedText.length; // # of characters of leading whitespace
|
||||
trimmedText = trimmedText.trimEnd(); // trim trailing whitespace
|
||||
let pos = leadingWS;
|
||||
let maxPos = text.length;
|
||||
|
||||
// clamp cursor start/end to new range
|
||||
let col = cursorPos.character; // effective column of the cursor position
|
||||
if (col < leadingWS) {
|
||||
// move effective start to first non-whitespace character in the line
|
||||
col = leadingWS;
|
||||
} else if (col > leadingWS + trimmedText.length - 1) {
|
||||
// move effective end to last non-whitespace character in the line
|
||||
col = leadingWS + trimmedText.length - 1;
|
||||
}
|
||||
|
||||
// generate list of space separate words with range data (start, end)
|
||||
// TODO: can find user position to be done in one pass
|
||||
let spaceWords: {word: string, start: number, end: number}[] = [];
|
||||
while (pos < maxPos && trimmedText.length > 0) {
|
||||
let word = trimmedText.replace(/ .*/, '');
|
||||
spaceWords.push({word, start: pos, end: pos+word.length});
|
||||
|
||||
// remove processed word from trimmed text
|
||||
const oldText = trimmedText;
|
||||
trimmedText = trimmedText.replace(/[^ ]+/, '').trimStart();
|
||||
|
||||
// update pos to start of next word
|
||||
pos += oldText.length - trimmedText.length;
|
||||
}
|
||||
|
||||
// find word the user is in
|
||||
let contextStart: number = -1, contextEnd: number = -1;
|
||||
for (let i = 0; i < spaceWords.length; i++) {
|
||||
if (col >= spaceWords[i].start && col <= spaceWords[i].end) {
|
||||
// found the word
|
||||
contextStart = Math.max(0, i - windowSize); // clamp start index
|
||||
contextEnd = Math.min(spaceWords.length, i + windowSize + 1); // clamp end index
|
||||
|
||||
// construct cursor context string
|
||||
let contextString = '';
|
||||
for (let i = contextStart; i < contextEnd; i++) {
|
||||
contextString += spaceWords[i].word + ' ';
|
||||
}
|
||||
|
||||
// output cursor context string
|
||||
vscode.window.showInformationMessage(contextString);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default commands;
|
||||
|
@ -3,6 +3,8 @@ import * as vscode from 'vscode';
|
||||
import * as pl from './pylex';
|
||||
import commands from './commands';
|
||||
|
||||
import AccessNodeProvider from './accessNodeProvider'
|
||||
|
||||
let parser: pl.Parser = new pl.Parser();
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
@ -20,6 +22,10 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(disposable);
|
||||
console.log(command.name);
|
||||
});
|
||||
|
||||
let provider = new AccessNodeProvider();
|
||||
vscode.window.registerTreeDataProvider('accessActions', provider);
|
||||
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
@ -4,4 +4,4 @@ export {default as LineToken} from './token';
|
||||
export {default as Lexer} from './lexer';
|
||||
export {default as LexNode} from './node';
|
||||
export {TabInfo as TabInfo} from './token';
|
||||
|
||||
export {Symbol as PylexSymbol} from './token';
|
||||
|
@ -159,14 +159,15 @@ export default class Lexer {
|
||||
}
|
||||
// No rules matched
|
||||
|
||||
// Skip this line if it is whitespace, comment, or empty
|
||||
// TODO: move to rules
|
||||
if (/^\s*(#.*)?$/.test(line)) {
|
||||
this.pos++;
|
||||
continue;
|
||||
// "empty" line
|
||||
token = new LineToken(Symbol.EMPTY, this.pos, 999999);
|
||||
} else {
|
||||
// This is an INDENT token
|
||||
token = new LineToken(Symbol.INDENT, this.pos, indent);
|
||||
}
|
||||
|
||||
// This is an INDENT token
|
||||
token = new LineToken(Symbol.INDENT, this.pos, indent);
|
||||
this._currToken = token;
|
||||
this.pos++;
|
||||
return this.currToken();
|
||||
|
@ -2,6 +2,8 @@ import * as vscode from 'vscode';
|
||||
|
||||
import LineToken from './token';
|
||||
|
||||
/* TODO: make accessing children and parent less tedious */
|
||||
/* TODO: 'root.children()![i])' */
|
||||
/**
|
||||
* A node in a Parse tree.
|
||||
*/
|
||||
@ -46,8 +48,9 @@ export default class LexNode extends vscode.TreeItem {
|
||||
* Adopt child nodes.
|
||||
*
|
||||
* @param `child` Array of nodes to adopt.
|
||||
* @returns an updated version of itself
|
||||
*/
|
||||
adopt(children: LexNode[]): void {
|
||||
adopt(children: LexNode[]): LexNode {
|
||||
let parentedChildren = children.map(c => new LexNode(
|
||||
c.label,
|
||||
c.collapsibleState,
|
||||
@ -64,6 +67,7 @@ export default class LexNode extends vscode.TreeItem {
|
||||
// No....
|
||||
this._children = parentedChildren;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@ import * as vscode from 'vscode';
|
||||
import { EOFTOKEN, Symbol, TabInfo } from './token';
|
||||
import Lexer from './lexer';
|
||||
import LexNode from './node';
|
||||
/* TODO: update design doc */
|
||||
|
||||
/**
|
||||
* A parse tree generator
|
||||
@ -71,8 +72,20 @@ export default class Parser {
|
||||
// go up 1 level of recursion at a time to unravel properly
|
||||
this.currIndent--;
|
||||
return children;
|
||||
} else if (this.lexer.currToken().type === Symbol.INDENT) {
|
||||
}
|
||||
|
||||
if (this.lexer.currToken().type === Symbol.INDENT ||
|
||||
this.lexer.currToken().type === Symbol.EMPTY) {
|
||||
const label = this.lexer.currToken().type;
|
||||
// regular code, advance and stay in same block
|
||||
children.push(new LexNode(
|
||||
label,
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
this.lexer.currToken(),
|
||||
null,
|
||||
parent)
|
||||
);
|
||||
|
||||
this.lexer.next();
|
||||
continue;
|
||||
} else {
|
||||
@ -105,29 +118,28 @@ export default class Parser {
|
||||
* @param `lineNumber` The line number to query context for.
|
||||
* @return An array of LexNodes for the root path containing `lineNumber`
|
||||
*/
|
||||
context(lineNumber: number): LexNode[] {
|
||||
if (!this.root.children()) {
|
||||
return [];
|
||||
context(lineNumber: number, root?: LexNode): LexNode[] {
|
||||
if (!root) {
|
||||
root = this.root;
|
||||
}
|
||||
|
||||
// Returns the LexNode that is the parent
|
||||
// of the queried line number
|
||||
let find = (root: LexNode): LexNode | undefined => {
|
||||
let prevChild: LexNode;
|
||||
for (var child of root.children()!) {
|
||||
if (lineNumber < child.token!.linenr) {
|
||||
if (prevChild!.children()) {
|
||||
return find(prevChild!);
|
||||
} else {
|
||||
return prevChild!;
|
||||
}
|
||||
} else {
|
||||
prevChild = child;
|
||||
// is this the target?
|
||||
if (root.token && root.token!.linenr === lineNumber) {
|
||||
// match
|
||||
return root.rootPath();
|
||||
}
|
||||
|
||||
if (root.children()) {
|
||||
// recursive call
|
||||
for (let i = 0; i < root.children()!.length; i++) {
|
||||
let ctx = this.context(lineNumber, root.children()![i]);
|
||||
if (ctx.length > 0) {
|
||||
// a rootpath was returned
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let target = find(this.root);
|
||||
return target!.rootPath();
|
||||
}
|
||||
// no matches
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ export enum Symbol {
|
||||
FINALLY = "finally",
|
||||
WITH = "with",
|
||||
INDENT = "INDENT", // Indent token, default if not EOF, only contains indent information
|
||||
EMPTY = "EMPTY", // empty line, used only to associate with the previous line
|
||||
EOF = "EOF"
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,18 @@ suite('Lexer Test Suite', () => {
|
||||
});
|
||||
|
||||
test('Undefined', () => {
|
||||
let l: Lexer = new Lexer('');
|
||||
let l: Lexer = new Lexer(undefined);
|
||||
assert.deepStrictEqual(l.currToken(), EOFTOKEN);
|
||||
});
|
||||
|
||||
test('Whitespace', () => {
|
||||
let l: Lexer = new Lexer(' \t\t'.repeat(4).repeat(4));
|
||||
assert.deepStrictEqual(l.currToken(), EOFTOKEN);
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.EMPTY, 0, 999999));
|
||||
});
|
||||
|
||||
test('Comment', () => {
|
||||
let l: Lexer = new Lexer('# ur mom eats toes');
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.EMPTY, 0, 999999));
|
||||
});
|
||||
|
||||
test('Non-Whitespace with no construct', () => {
|
||||
@ -120,98 +125,6 @@ suite('Lexer Test Suite', () => {
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.IF, 0, 0, 'is_old()'));
|
||||
});
|
||||
|
||||
test('next() ignores empty lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
'',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
l.next();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.INDENT, 2, 1));
|
||||
});
|
||||
|
||||
test('retract() ignores empty lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
'',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
l.next();
|
||||
|
||||
l.retract();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.IF, 0, 0, 'wurst_available()'));
|
||||
});
|
||||
|
||||
test('next() ignores whitespace lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
' \t \t ',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
l.next();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.INDENT, 2, 1));
|
||||
});
|
||||
|
||||
test('retract() ignores whitespace lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
' \t \t ',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
// Advance to end of input
|
||||
// Eliminates dependence on next()
|
||||
// skipping whitespace
|
||||
do {} while (l.next() !== EOFTOKEN);
|
||||
|
||||
l.retract(); // retract past EOFTOKEn
|
||||
l.retract();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.IF, 0, 0, 'wurst_available()'));
|
||||
});
|
||||
|
||||
test('next() ignores comment lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
' \t # I hate testing \t',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
l.next();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.INDENT, 2, 1));
|
||||
});
|
||||
|
||||
test('retract() ignores comment lines', () => {
|
||||
let lines: string[] = [
|
||||
'if wurst_available():',
|
||||
' \t # \t',
|
||||
' eat_wurst()'
|
||||
];
|
||||
let l: Lexer = new Lexer(lines.join('\n'));
|
||||
|
||||
// Advance to end of input
|
||||
// Eliminates dependence on next()
|
||||
// skipping comment
|
||||
do {} while (l.next() !== EOFTOKEN);
|
||||
|
||||
l.retract(); // retract past EOFTOKEn
|
||||
l.retract();
|
||||
|
||||
assert.deepStrictEqual(l.currToken(), new LineToken(Symbol.IF, 0, 0, 'wurst_available()'));
|
||||
});
|
||||
|
||||
test('next() out of range', () => {
|
||||
let l: Lexer = new Lexer('foo = zaboomafoo');
|
||||
l.next();
|
||||
|
@ -1,51 +1,61 @@
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { after } from 'mocha';
|
||||
import { deparent, root } from '../../util';
|
||||
|
||||
import Parser from '../../../pylex/parser';
|
||||
import LexNode from '../../../pylex/node';
|
||||
import LineToken from '../../../pylex/token';
|
||||
import { Symbol } from '../../../pylex/token';
|
||||
|
||||
import { root,indent,empty } from '../../util';
|
||||
|
||||
/**
|
||||
* Test Descriptor
|
||||
*/
|
||||
type ParserTest = {
|
||||
name: string,
|
||||
input: string[],
|
||||
output: LexNode,
|
||||
name: string, // short name for the test
|
||||
input: string[], // input lines for the test
|
||||
output: LexNode // expected output. outputs are compared for token equality *only*
|
||||
};
|
||||
|
||||
const tests: ParserTest[] = [
|
||||
{ name: 'No Input', input: [], output: root(null) },
|
||||
{ name: 'Single Empty Line', input: [''], output: root(null) },
|
||||
{
|
||||
name: 'No Input',
|
||||
input: [ ],
|
||||
output: root(null),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Single line without construct',
|
||||
input: [ 'foo = "Yellow M&Ms make me angry >:(' ],
|
||||
output: root(null),
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Single line with construct',
|
||||
input: [ 'for x of y:' ],
|
||||
name: 'Single Whitespace Only Line',
|
||||
input: [' '],
|
||||
output: root([
|
||||
new LexNode(
|
||||
'for x of y',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.FOR, 0, 0, 'x of y')
|
||||
)
|
||||
]),
|
||||
empty(0)
|
||||
])
|
||||
},
|
||||
{
|
||||
name: 'Single Comment Only Line',
|
||||
input: ['# ur mom likes peas'],
|
||||
output: root([
|
||||
empty(0)
|
||||
])
|
||||
},
|
||||
{
|
||||
name: 'Single Non-Control Line',
|
||||
input: ['my_age = 42'],
|
||||
output: root([
|
||||
indent(0, 0)
|
||||
])
|
||||
},
|
||||
{
|
||||
name: 'Single Control Line',
|
||||
input: ['while True:'],
|
||||
output: root([
|
||||
new LexNode('', 0, new LineToken(Symbol.WHILE, 0, 0, 'True'))
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Sequential lines, without construct',
|
||||
input: [
|
||||
'bar = "Blue M&Ms make me happy <:)"',
|
||||
'reba = "A hard working gal"'
|
||||
],
|
||||
output: root(null),
|
||||
output: root([indent(0,0), indent(1,0)]),
|
||||
},
|
||||
|
||||
{
|
||||
@ -58,7 +68,9 @@ const tests: ParserTest[] = [
|
||||
output: root([
|
||||
new LexNode('if radioshack',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 0, 0, 'radioshack'))
|
||||
new LineToken(Symbol.IF, 0, 0, 'radioshack'),
|
||||
[indent(1, 1)]),
|
||||
indent(2, 0)
|
||||
])
|
||||
},
|
||||
|
||||
@ -70,9 +82,11 @@ const tests: ParserTest[] = [
|
||||
' print radioshack.hours'
|
||||
],
|
||||
output: root([
|
||||
indent(0, 0),
|
||||
new LexNode('if radioshack',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 1, 0, 'radioshack'))
|
||||
new LineToken(Symbol.IF, 1, 0, 'radioshack'),
|
||||
[indent(2, 1)])
|
||||
])
|
||||
},
|
||||
|
||||
@ -89,13 +103,13 @@ const tests: ParserTest[] = [
|
||||
output: root([
|
||||
new LexNode('if yummy',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 0, 0, 'yummy')),
|
||||
new LineToken(Symbol.IF, 0, 0, 'yummy'), [indent(1, 1)]),
|
||||
new LexNode('elif just_ok',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELIF, 2, 0, 'just_ok')),
|
||||
new LineToken(Symbol.ELIF, 2, 0, 'just_ok'), [indent(3, 1)]),
|
||||
new LexNode('else',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELSE, 4, 0)),
|
||||
new LineToken(Symbol.ELSE, 4, 0), [indent(5,1)]),
|
||||
])
|
||||
},
|
||||
|
||||
@ -113,8 +127,9 @@ const tests: ParserTest[] = [
|
||||
[
|
||||
new LexNode('if in_my_tummy',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 1, 1, 'in_my_tummy'))
|
||||
]
|
||||
new LineToken(Symbol.IF, 1, 1, 'in_my_tummy'),
|
||||
[indent(2, 2)])
|
||||
],
|
||||
)
|
||||
])
|
||||
},
|
||||
@ -135,12 +150,14 @@ const tests: ParserTest[] = [
|
||||
[
|
||||
new LexNode('if in_my_tummy',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 1, 1, 'in_my_tummy'))
|
||||
new LineToken(Symbol.IF, 1, 1, 'in_my_tummy'),
|
||||
[indent(2, 2)])
|
||||
]
|
||||
),
|
||||
new LexNode('else',
|
||||
new LexNode('else',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELSE, 3, 0),
|
||||
[indent(4, 1)]
|
||||
)
|
||||
])
|
||||
},
|
||||
@ -166,7 +183,8 @@ const tests: ParserTest[] = [
|
||||
[
|
||||
new LexNode('if looks_like_a_mummy',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 2, 2, 'looks_like_a_mummy'))
|
||||
new LineToken(Symbol.IF, 2, 2, 'looks_like_a_mummy'),
|
||||
[indent(3, 3)])
|
||||
]
|
||||
)
|
||||
]
|
||||
@ -174,12 +192,13 @@ const tests: ParserTest[] = [
|
||||
new LexNode('else',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELSE, 4, 0),
|
||||
[indent(5, 1)]
|
||||
)
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Doubly Nested Block, with multiple indent resets',
|
||||
name: 'Doubly Nested Block, with multiple indent resets > 1',
|
||||
input: [
|
||||
'if yummy:',
|
||||
' if in_my_tummy:',
|
||||
@ -188,7 +207,7 @@ const tests: ParserTest[] = [
|
||||
' else:',
|
||||
' print("eek! a zombie!)',
|
||||
' elif in_my_mouth:',
|
||||
' print("ill be in my tummy soon!"',
|
||||
' print("itll be in my tummy soon!"',
|
||||
'else:',
|
||||
' print("Food is food...")'
|
||||
],
|
||||
@ -203,25 +222,70 @@ const tests: ParserTest[] = [
|
||||
[
|
||||
new LexNode('if looks_like_a_mummy',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.IF, 2, 2, 'looks_like_a_mummy')),
|
||||
new LineToken(Symbol.IF, 2, 2, 'looks_like_a_mummy'),
|
||||
[indent(3, 3)]),
|
||||
new LexNode('else',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELSE, 4, 2))
|
||||
new LineToken(Symbol.ELSE, 4, 2),
|
||||
[indent(5, 3)])
|
||||
]
|
||||
),
|
||||
new LexNode('elif in_my_mouth',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELIF, 6, 1, 'in_my_mouth'))
|
||||
new LineToken(Symbol.ELIF, 6, 1, 'in_my_mouth'),
|
||||
[indent(7, 2)]
|
||||
)
|
||||
]
|
||||
),
|
||||
new LexNode('else',
|
||||
vscode.TreeItemCollapsibleState.None,
|
||||
new LineToken(Symbol.ELSE, 8, 0)
|
||||
new LineToken(Symbol.ELSE, 8, 0),
|
||||
[indent(9, 1)]
|
||||
)
|
||||
])
|
||||
},
|
||||
{
|
||||
name: 'Multiline Block',
|
||||
input: [
|
||||
'if yummy:',
|
||||
' print("you have a spot on your tummy"',
|
||||
' print("eek! a zombie!)',
|
||||
' print("itll be in my tummy soon!"',
|
||||
'else:',
|
||||
' print("Food is food...")'
|
||||
],
|
||||
output: root([
|
||||
new LexNode('if yummy', 0, new LineToken(Symbol.IF, 0, 0, 'yummy'),
|
||||
[
|
||||
indent(1, 1),
|
||||
indent(2, 1),
|
||||
indent(3, 1),
|
||||
]
|
||||
),
|
||||
new LexNode('else', 0, new LineToken(Symbol.ELSE, 4, 0), [indent(5, 1)])
|
||||
])
|
||||
}
|
||||
];
|
||||
|
||||
/* Checks for strict equality between the tokens of a lex node tree */
|
||||
const checkEq = (reference: LexNode, subject: LexNode) => {
|
||||
if (!reference.children()) {
|
||||
// subject should also have no children
|
||||
assert.deepStrictEqual(subject.children(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
assert.notStrictEqual(subject.children(), null);
|
||||
assert.deepStrictEqual(reference.children()!.length, subject.children()!.length);
|
||||
for (let i = 0; i < subject.children()!.length; i++) {
|
||||
// compare top level nodes
|
||||
assert.deepStrictEqual(reference.children()![i].token, subject.children()![i].token);
|
||||
|
||||
// compare all children
|
||||
checkEq(reference.children()![i], subject.children()![i]);
|
||||
}
|
||||
};
|
||||
|
||||
suite('Parser Test Suite', () => {
|
||||
after(() => {
|
||||
vscode.window.showInformationMessage('All tests passed!');
|
||||
@ -230,10 +294,8 @@ suite('Parser Test Suite', () => {
|
||||
for (var t of tests) {
|
||||
let currTest = t; // without this, all test calls get the last test
|
||||
test(currTest.name, () => {
|
||||
let result: LexNode = deparent(new Parser(currTest.input.join('\n')).parse());
|
||||
process.stdout.write(Object.entries(result).toString());
|
||||
|
||||
assert.deepStrictEqual(result, currTest.output);
|
||||
let result = new Parser(currTest.input.join('\n')).parse();
|
||||
checkEq(currTest.output, result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import LexNode from '../pylex/node';
|
||||
import { LineToken, PylexSymbol, LexNode} from '../pylex';
|
||||
|
||||
/**
|
||||
* TODO: Eliminate need for me.
|
||||
@ -50,8 +49,20 @@ function root(nodes: LexNode[] | null): LexNode {
|
||||
);
|
||||
}
|
||||
|
||||
/* short hand for returning an indentation token for a certain line and indentation */
|
||||
function indent(linenr: number, indentLevel: number): LexNode {
|
||||
return new LexNode('INDENT', 0, new LineToken(PylexSymbol.INDENT, linenr, indentLevel));
|
||||
}
|
||||
|
||||
/* short hand for returning an empty token for a certain line*/
|
||||
function empty(linenr: number): LexNode {
|
||||
return new LexNode('EMPTY', 0, new LineToken(PylexSymbol.EMPTY, linenr, 999999));
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
deparent,
|
||||
root,
|
||||
indent,
|
||||
empty
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user