Skip to content

Commit 7f7e4ee

Browse files
committed
added funcDef node, fixed multiline instructions
1 parent 2114a8f commit 7f7e4ee

File tree

6 files changed

+115
-27
lines changed

6 files changed

+115
-27
lines changed

index.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@
2828
<body>
2929
<div class="container">
3030
<h4>JSPython development console</h4>
31-
<div id="editor">o.f1(22,23).x</div>
31+
<div id="editor">
32+
def add(x, y):
33+
z = 5
34+
x + z + y
35+
36+
add(3, 5)
37+
38+
</div>
3239
<button onclick="tokenize()">Tokenize</button>
3340
<button onclick="parse()">Parse</button>
3441
<button onclick="runInterpreter()">Run</button>

src/common/ast-types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export class FunctionCallNode extends AstNode {
4444
}
4545
}
4646

47+
export class FunctionDefNode extends AstNode {
48+
constructor(public name: string, public params: string[], public body: AstNode[] ) {
49+
super('funcDef');
50+
}
51+
}
52+
4753
export class GetSingleVarNode extends AstNode {
4854
name: string;
4955
nullCoelsing: boolean | undefined = undefined;
@@ -81,5 +87,6 @@ export class BinOpNode extends AstNode {
8187

8288
export interface Ast {
8389
name: string;
84-
body: AstNode[]
90+
funcs: FunctionDefNode[];
91+
body: AstNode[];
8592
}

src/common/token-types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export function getEndColumn(token: Token): number {
6666
export function splitTokens(tokens: Token[], separator: string): Token[][] {
6767
const result: Token[][] = [];
6868

69+
if (!tokens.length) { return []; }
70+
6971
const sepIndexes = findTokenValueIndexes(tokens, value => value === separator);
7072

7173
let start = 0;
@@ -79,6 +81,22 @@ export function splitTokens(tokens: Token[], separator: string): Token[][] {
7981
return result;
8082
}
8183

84+
export function findTokenValueIndex(tokens: Token[], predicate: (value: TokenValue) => boolean, start = 0): number {
85+
for (let i = start; i < tokens.length; i++) {
86+
if (getTokenValue(tokens[i]) === '(') {
87+
i = skipInnerBrackets(tokens, i, '(', ')');
88+
} else if (getTokenValue(tokens[i]) === '[') {
89+
i = skipInnerBrackets(tokens, i, '[', ']');
90+
} else if (getTokenValue(tokens[i]) === '{') {
91+
i = skipInnerBrackets(tokens, i, '{', '}');
92+
} else if (predicate(getTokenValue(tokens[i]))) {
93+
return i;
94+
}
95+
}
96+
97+
return -1;
98+
}
99+
82100
export function findTokenValueIndexes(tokens: Token[], predicate: (value: TokenValue) => boolean): number[] {
83101
const opIndexes: number[] = [];
84102

src/interpreter.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ describe('Interpreter', () => {
9191

9292
expect(await e.evaluate("o.add(2, 3)", obj)).toBe(5)
9393
expect(await e.evaluate("o.add(2 * 10, 3)", obj)).toBe(23)
94+
expect(await e.evaluate(`
95+
o.add(
96+
2 * 10,
97+
3
98+
)`, obj)).toBe(23)
99+
94100
});
95101

96102
it('Object call2', async () => {

src/parser/parser.ts

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
BinOpNode, ConstNode, Ast, Token, ParserOptions, AstNode, OperatorsMap, OperationTypes,
33
Operators, AssignNode, TokenTypes, SetSingleVarNode, GetSingleVarNode, FunctionCallNode,
44
getTokenType, getTokenValue, isTokenTypeLiteral, getStartLine, getStartColumn, getEndColumn,
5-
getEndLine, findOperators, splitTokens, DotObjectAccessNode, BracketObjectAccessNode
5+
getEndLine, findOperators, splitTokens, DotObjectAccessNode, BracketObjectAccessNode, findTokenValueIndexes, findTokenValueIndex, FunctionDefNode
66
} from '../common';
77

88
export class InstructionLine {
@@ -33,36 +33,65 @@ export class Parser {
3333
* @param options parsing options. By default it will exclude comments and include LOC (Line of code)
3434
*/
3535
parse(allTokens: Token[], options: ParserOptions = { includeComments: false, includeLoc: true }): Ast {
36+
37+
if (!allTokens || !allTokens.length) { return {} as Ast; }
38+
39+
// get all instruction lines starting at first line
40+
const instructions = this.getBlock(allTokens, 1);
41+
3642
const ast = {
3743
name: "undefined.jspy",
44+
funcs: [],
3845
body: []
3946
} as Ast;
4047

41-
if (!allTokens || !allTokens.length) { return ast; }
48+
this.instructionsToNodes(instructions, ast);
49+
return ast;
50+
}
4251

43-
// get all instruction lines starting at first line
44-
const instructions = this.getBlock(allTokens, 1);
52+
private instructionsToNodes(instructions: InstructionLine[], ast: Ast): void {
4553

4654
for (let instruction of instructions) {
47-
let node: AstNode | null = null;
4855

4956
if (!instruction.tokens.length) {
5057
continue;
5158
}
5259

5360
const assignTokens = splitTokens(instruction.tokens, '=');
5461

55-
if (assignTokens.length > 1) {
56-
const target = this.createNode(assignTokens[0]);
57-
const source = this.createNode(assignTokens[1]);
58-
node = new AssignNode(target, source);
62+
if (getTokenValue(instruction.tokens[0]) === 'def') {
63+
const funcName = getTokenValue(instruction.tokens[1]) as string;
64+
const paramsTokens = instruction.tokens.slice(
65+
instruction.tokens.findIndex(tkns => getTokenValue(tkns) === '(') + 1,
66+
instruction.tokens.findIndex(tkns => getTokenValue(tkns) === ')')
67+
);
68+
69+
const params = splitTokens(paramsTokens, ',').map(t => getTokenValue(t[0]) as string);
70+
71+
const endDefOfDef = findTokenValueIndex(instruction.tokens, v => v === ':');
72+
73+
if (endDefOfDef === -1) {
74+
throw (`Can't find : for def`)
75+
}
76+
77+
const instructionLines = this.getBlock(instruction.tokens, getStartLine(instruction.tokens[endDefOfDef + 1]));
78+
const funcAst = {
79+
body: [] as AstNode[],
80+
funcs: [] as AstNode[]
81+
} as Ast;
82+
this.instructionsToNodes(instructionLines, funcAst);
83+
84+
ast.funcs.push(new FunctionDefNode(funcName, params, funcAst.body))
85+
86+
} else if (assignTokens.length > 1) {
87+
const target = this.createExpressionNode(assignTokens[0]);
88+
const source = this.createExpressionNode(assignTokens[1]);
89+
ast.body.push(new AssignNode(target, source));
5990
} else {
60-
node = this.createNode(instruction.tokens)
91+
ast.body.push(this.createExpressionNode(instruction.tokens))
6192
}
6293

63-
ast.body.push(node)
6494
}
65-
return ast;
6695
}
6796

6897
private getBlock(tokens: Token[], startLine: number): InstructionLine[] {
@@ -76,20 +105,29 @@ export class Parser {
76105
const sLine = getStartLine(token);
77106
const sColumn = getStartColumn(token);
78107
if (sLine >= startLine) {
79-
// first line defines a minimum indent
80-
if (column === 0) { column = sColumn; }
81108

82-
if (sLine !== currentLine) {
109+
if (currentLine !== sLine) {
110+
currentLine = sLine;
111+
}
112+
113+
if (column === sColumn) {
83114
currentLine = sLine;
84115
lines.push(line);
85116
line = new InstructionLine();
86117
}
87118

88119
line.tokens.push(token);
89120

121+
// first line defines a minimum indent
122+
if (column === 0) {
123+
column = sColumn;
124+
}
125+
90126
// stop looping through if line has less indent
91127
// it means the corrent block finished
92-
if (sColumn < column) { break; }
128+
if (sColumn < column) {
129+
break;
130+
}
93131
}
94132
}
95133

@@ -100,11 +138,12 @@ export class Parser {
100138
return lines;
101139
}
102140

103-
private createNode(tokens: Token[], prevNode: AstNode | null = null): AstNode {
141+
private createExpressionNode(tokens: Token[], prevNode: AstNode | null = null): AstNode {
104142
if (tokens.length === 0) {
105143
throw new Error(`Token length can't be null.`)
106144
}
107145

146+
// const or variable
108147
if (tokens.length === 1
109148
|| (tokens.length === 2 && getTokenValue(tokens[1]) === '?')
110149
) {
@@ -150,8 +189,8 @@ export class Parser {
150189
const leftSlice2 = slice(tokens, opIndex + 1, nextOpIndex);
151190
const rightSlice2 = slice(tokens, nextOpIndex + 1, nextOpIndex2 || tokens.length);
152191

153-
const left2 = this.createNode(leftSlice2);
154-
const right2 = this.createNode(rightSlice2);
192+
const left2 = this.createExpressionNode(leftSlice2);
193+
const right2 = this.createExpressionNode(rightSlice2);
155194
rightNode = new BinOpNode(left2, nextOp, right2);
156195

157196
i++;
@@ -163,15 +202,15 @@ export class Parser {
163202
// add up result
164203
if (prevNode === null) {
165204
const leftSlice = slice(tokens, 0, opIndex);
166-
prevNode = this.createNode(leftSlice);
205+
prevNode = this.createExpressionNode(leftSlice);
167206
}
168207
prevNode = new BinOpNode(prevNode, op, rightNode)
169208

170209
} else {
171210
const leftSlice = prevNode ? [] : slice(tokens, 0, opIndex);
172211
const rightSlice = slice(tokens, opIndex + 1, nextOpIndex || tokens.length);
173-
const left = prevNode || this.createNode(leftSlice, prevNode);
174-
const right = this.createNode(rightSlice);
212+
const left = prevNode || this.createExpressionNode(leftSlice, prevNode);
213+
const right = this.createExpressionNode(rightSlice);
175214
prevNode = new BinOpNode(left, op, right);
176215
}
177216
}
@@ -186,15 +225,15 @@ export class Parser {
186225
// create DotObjectAccessNode
187226
const subObjects = splitTokens(tokens, '.');
188227
if (subObjects.length > 1) {
189-
return new DotObjectAccessNode(subObjects.map(tkns => this.createNode(tkns)));
228+
return new DotObjectAccessNode(subObjects.map(tkns => this.createExpressionNode(tkns)));
190229
}
191230

192231
// create function call node
193232
if (tokens.length > 2 && getTokenValue(tokens[1]) === '(') {
194233
const name = getTokenValue(tokens[0]) as string;
195234
const paramsTokensSlice = tokens.slice(2, tokens.length - 1);
196235
const paramsTokens = splitTokens(paramsTokensSlice, ',')
197-
const paramsNodes = paramsTokens.map(tkns => this.createNode(tkns));
236+
const paramsNodes = paramsTokens.map(tkns => this.createExpressionNode(tkns));
198237

199238
return new FunctionCallNode(name, paramsNodes);
200239
}
@@ -203,7 +242,7 @@ export class Parser {
203242
if (tokens.length > 2 && getTokenValue(tokens[1]) === '[') {
204243
const name = getTokenValue(tokens[0]) as string;
205244
const paramsTokensSlice = tokens.slice(2, tokens.length - 1);
206-
const paramsNodes = this.createNode(paramsTokensSlice);
245+
const paramsNodes = this.createExpressionNode(paramsTokensSlice);
207246
return new BracketObjectAccessNode(name, paramsNodes);
208247
}
209248

src/tokenizer/tokenizer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ export class Tokenizer {
6868

6969
const tokens: Token[] = [];
7070

71+
let first = true;
72+
// handle initial spaces
73+
while (script[this._cursor] === '\n') {
74+
this.incrementCursor();
75+
if (first) {
76+
this._currentLine++;
77+
first = false;
78+
}
79+
this._currentColumn = 1;
80+
}
81+
7182
do {
7283
const symbol = script[this._cursor];
7384

0 commit comments

Comments
 (0)