merged from master

This commit is contained in:
MasonBone 2021-11-18 12:01:53 -06:00
commit acd7a0e442
13 changed files with 584 additions and 174 deletions

14
media/dep.svg Normal file
View 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

View File

@ -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
View 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);
}
}

View File

@ -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;

View File

@ -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() {}

View File

@ -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';

View File

@ -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();

View File

@ -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;
}
/**

View File

@ -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 [];
}
}

View File

@ -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"
}

View File

@ -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();

View File

@ -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);
});
}
});

View File

@ -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
};