diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index c9cdc63b0..000000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index f67d43ffd..000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn eslint --fix && yarn format && yarn eslint \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index 9e5ab0cee..000000000 --- a/.husky/pre-push +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn jsdoc && yarn autocomplete && yarn format:ci && yarn eslint && yarn test diff --git a/src/go-slang/ece.ts b/src/go-slang/ece.ts index d7146952e..8d936a8be 100644 --- a/src/go-slang/ece.ts +++ b/src/go-slang/ece.ts @@ -3,13 +3,13 @@ import { Stack } from '../cse-machine/utils' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { Value } from '../types' import { FuncArityError, UndefinedError, UnknownInstructionError } from './error' +import { AstMap } from './lib/astMap' import { evaluateBinaryOp } from './lib/binaryOp' -import { Environment, createGlobalEnvironment } from './lib/env' +import { Environment } from './lib/env' import { Heap, HeapAddress } from './lib/heap' -import { PREDECLARED_FUNCTIONS } from './lib/predeclared' +import { PREDECLARED_FUNCTIONS, PREDECLARED_IDENTIFIERS } from './lib/predeclared' import { zip, isAny } from './lib/utils' import { - ApplyBuiltinOp, AssignOp, Assignment, BinaryExpression, @@ -34,8 +34,8 @@ import { IfStatement, Instruction, Literal, - Node, NodeType, + Operator, PopS, PopTillM, PopTillMOp, @@ -49,10 +49,9 @@ import { VariableDeclaration } from './types' -type Control = Stack +type Control = Stack type Stash = Stack type Builtins = Map any> -type AstMap = Map interface Context { C: Control @@ -63,23 +62,20 @@ interface Context { A: AstMap } -const CALL_MAIN: CallExpression = { - type: NodeType.CallExpression, - callee: { type: NodeType.Identifier, name: 'main' }, - args: [] -} - export function evaluate(program: SourceFile, slangContext: SlangContext): Value { - const C = new Stack() + const C = new Stack() const S = new Stack() - const E = createGlobalEnvironment() - const H = new Heap() - const A = new Map() + const E = new Environment({ ...PREDECLARED_IDENTIFIERS }) + + // `SourceFile` is the root node of the AST which has latest (monotonically increasing) uid of all AST nodes + // Therefore, the next uid to be used to track AST nodes is the uid of SourceFile + 1 + const A = new AstMap((program.uid as number) + 1) + const H = new Heap(A) // inject predeclared functions into the global environment const B = new Map any>() PREDECLARED_FUNCTIONS.forEach(({ name, func, op }, id) => { - E.declare(name, { ...op, id }) + E.declare(name, { ...op, id } as BuiltinOp) if (name === 'println') { // println is special case where we need to the `rawDisplay` slang builtin for // console capture, therefore we handle it differently from other predeclared functions @@ -93,10 +89,15 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value const Context = { C, S, E, B, H, A } // start the program by calling `main` - C.pushR(program, CALL_MAIN) + const CALL_MAIN: CallExpression = { + type: NodeType.CallExpression, + callee: { type: NodeType.Identifier, name: 'main' }, + args: [] + } + C.pushR(H.alloc(program), H.alloc(CALL_MAIN)) while (!C.isEmpty()) { - const inst = C.pop() as Instruction + const inst = H.resolve(C.pop()) as Instruction if (!interpreter.hasOwnProperty(inst.type)) { slangContext.errors.push(new UnknownInstructionError(inst.type)) @@ -116,7 +117,7 @@ export function evaluate(program: SourceFile, slangContext: SlangContext): Value const interpreter: { [key: string]: (inst: Instruction, context: Context) => RuntimeSourceError | void } = { - SourceFile: ({ topLevelDecls }: SourceFile, { C }) => C.pushR(...topLevelDecls), + SourceFile: ({ topLevelDecls }: SourceFile, { C, H }) => C.pushR(...H.allocM(topLevelDecls)), FunctionDeclaration: (funcDeclNode: FunctionDeclaration, { E, A }) => { const { id, uid: funcDeclNodeUid } = funcDeclNode @@ -124,24 +125,32 @@ const interpreter: { E.declare(id.name, { type: CommandType.ClosureOp, funcDeclNodeUid, envId: E.id() } as ClosureOp) }, - Block: ({ statements }: Block, { C, E }) => { - C.pushR(...statements, { type: CommandType.EnvOp, envId: E.id() }) + Block: ({ statements }: Block, { C, E, H }) => { + C.pushR(...H.allocM([...statements, { type: CommandType.EnvOp, envId: E.id() }])) E.extend({}) }, - ReturnStatement: ({ expression }: ReturnStatement, { C }) => - C.pushR(expression, PopTillM(RetMarker)), + ReturnStatement: ({ expression }: ReturnStatement, { C, H }) => + C.pushR(H.alloc(expression), H.alloc(PopTillM(RetMarker))), - IfStatement: ({ stmt, cond, cons, alt }: IfStatement, { C }) => { + IfStatement: ({ stmt, cond, cons, alt }: IfStatement, { C, H }) => { const branchOp: BranchOp = { type: CommandType.BranchOp, cons, alt } - stmt ? C.pushR(stmt, cond, branchOp) : C.pushR(cond, branchOp) + stmt ? C.pushR(...H.allocM([stmt, cond, branchOp])) : C.pushR(...H.allocM([cond, branchOp])) }, - ForStatement: (inst: ForStatement, { C }) => { + ForStatement: (inst: ForStatement, { C, H }) => { const { form, block: forBlock } = inst if (form === null || form.type === ForFormType.ForCondition) { const branch = { type: CommandType.BranchOp, cons: forBlock, alt: PopTillM(ForEndMarker) } - C.pushR(form ? form.expression : True, branch as BranchOp, ForStartMarker, inst, ForEndMarker) + C.pushR( + ...H.allocM([ + form ? form.expression : True, + branch as BranchOp, + ForStartMarker, + inst, + ForEndMarker + ]) + ) } else if (form.type === ForFormType.ForClause) { const { init, cond, post } = form const forCond = { @@ -155,27 +164,27 @@ const interpreter: { ] } } as ForStatement - C.push({ type: NodeType.Block, statements: [init ?? EmptyStmt, forCond] }) + C.push(H.alloc({ type: NodeType.Block, statements: [init ?? EmptyStmt, forCond] })) } }, - BreakStatement: (_inst, { C }) => C.push(PopTillM(ForEndMarker)), + BreakStatement: (_inst, { C, H }) => C.push(H.alloc(PopTillM(ForEndMarker))), - ContinueStatement: (_inst, { C }) => C.push(PopTillM(ForPostMarker, ForStartMarker)), + ContinueStatement: (_inst, { C, H }) => C.push(H.alloc(PopTillM(ForPostMarker, ForStartMarker))), - VariableDeclaration: ({ left, right }: VariableDeclaration, { C }) => { - const decls = left.map(({ name }) => ({ type: CommandType.VarDeclOp, name })) as Instruction[] + VariableDeclaration: ({ left, right }: VariableDeclaration, { C, H, A }) => { + const decls = A.trackM(left).map(({ uid }) => ({ type: CommandType.VarDeclOp, idNodeUid: uid })) return right.length === 0 ? // if there is no right side, we push zero value for each declaration - C.pushR(...decls.map(decl => ({ ...decl, zeroValue: true }))) + C.pushR(...H.allocM(decls.map(decl => ({ ...decl, zeroValue: true })))) : // assume: left.length === right.length - C.pushR(...right, ...decls.reverse()) + C.pushR(...H.allocM(right), ...H.allocM(decls.reverse())) }, - Assignment: ({ left, right }: Assignment, { C }) => { + Assignment: ({ left, right }: Assignment, { C, H, A }) => { const ids = left as Identifier[] // assume: left is always an array of identifiers - const asgmts = ids.map(({ name }) => ({ type: CommandType.AssignOp, name })) as Instruction[] - C.pushR(...right, ...asgmts.reverse()) + const asgmts = ids.map(id => ({ type: CommandType.AssignOp, idNodeUid: A.track(id).uid })) + C.pushR(...H.allocM(right), ...H.allocM(asgmts.reverse())) }, EmptyStatement: () => void {}, @@ -187,64 +196,70 @@ const interpreter: { return value === null ? new UndefinedError(name) : S.push(H.alloc(value)) }, - UnaryExpression: ({ argument, operator }: UnaryExpression, { C }) => - C.pushR(argument, { type: CommandType.UnaryOp, operator }), + UnaryExpression: ({ argument, operator: op }: UnaryExpression, { C, H, A }) => + C.pushR(...H.allocM([argument, { type: CommandType.UnaryOp, opNodeId: A.track(op).uid }])), - BinaryExpression: ({ left, right, operator }: BinaryExpression, { C }) => - C.pushR(left, right, { type: CommandType.BinaryOp, operator: operator }), + BinaryExpression: ({ left, right, operator: op }: BinaryExpression, { C, H, A }) => + C.pushR(...H.allocM([left, right, { type: CommandType.BinaryOp, opNodeId: A.track(op).uid }])), - CallExpression: ({ callee, args }: CallExpression, { C }) => - C.pushR(callee, ...args, { - type: CommandType.CallOp, - calleeName: callee.name, - arity: args.length - }), + CallExpression: ({ callee, args }: CallExpression, { C, H, A }) => + C.pushR( + ...H.allocM([ + callee, + ...args, + { type: CommandType.CallOp, calleeNodeId: A.track(callee).uid, arity: args.length } + ]) + ), - ExpressionStatement: ({ expression }: ExpressionStatement, { C }) => C.pushR(expression, PopS), + ExpressionStatement: ({ expression }: ExpressionStatement, { C, H }) => + C.pushR(...H.allocM([expression, PopS])), - VarDeclOp: ({ name, zeroValue }: VarDeclOp, { S, E, H }) => - zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())), + VarDeclOp: ({ idNodeUid, zeroValue }: VarDeclOp, { S, E, H, A }) => { + const name = A.get(idNodeUid).name + zeroValue ? E.declareZeroValue(name) : E.declare(name, H.resolve(S.pop())) + }, - AssignOp: ({ name }: AssignOp, { S, E, H }) => - !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {}, + AssignOp: ({ idNodeUid }: AssignOp, { S, E, H, A }) => { + const name = A.get(idNodeUid).name + !E.assign(name, H.resolve(S.pop())) ? new UndefinedError(name) : void {} + }, - UnaryOp: ({ operator }: UnaryOp, { S, H }) => { + UnaryOp: ({ opNodeId }: UnaryOp, { S, H, A }) => { const operand = H.resolve(S.pop()) - S.push(H.alloc(operator === '-' ? -operand : operand)) + S.push(H.alloc(A.get(opNodeId).op === '-' ? -operand : operand)) }, - BinaryOp: ({ operator }: BinaryOp, { S, H }) => { - const [left, right] = S.popNR(2).map(H.resolve.bind(H)) - S.push(H.alloc(evaluateBinaryOp(operator, left, right))) + BinaryOp: ({ opNodeId }: BinaryOp, { S, H, A }) => { + const [left, right] = H.resolveM(S.popNR(2)) + S.push(H.alloc(evaluateBinaryOp(A.get(opNodeId).op, left, right))) }, - CallOp: ({ calleeName, arity }: CallOp, { C, S, E, H, A }) => { - const values = S.popNR(arity).map(H.resolve.bind(H)) + CallOp: ({ calleeNodeId, arity }: CallOp, { C, S, E, B, H, A }) => { + const values = H.resolveM(S.popNR(arity)) const op = H.resolve(S.pop()) as ClosureOp | BuiltinOp + // handle BuiltinOp if (op.type === CommandType.BuiltinOp) { - return C.pushR({ type: CommandType.ApplyBuiltinOp, builtinOp: op, values }) + return S.push(H.alloc(B.get(op.id)!(...values))) } // handle ClosureOp const { funcDeclNodeUid, envId } = op - const { params, body } = A.get(funcDeclNodeUid) as FunctionDeclaration + const { params, body } = A.get(funcDeclNodeUid) const paramNames = params.map(({ name }) => name) if (paramNames.length !== values.length) { - return new FuncArityError(calleeName, values.length, params.length) + const calleeId = A.get(calleeNodeId) + return new FuncArityError(calleeId.name, values.length, params.length) } - C.pushR(body, RetMarker, { type: CommandType.EnvOp, envId: E.id() }) + C.pushR(...H.allocM([body, RetMarker, { type: CommandType.EnvOp, envId: E.id() }])) // set the environment to the closure's environment E.setId(envId).extend(Object.fromEntries(zip(paramNames, values))) }, BranchOp: ({ cons, alt }: BranchOp, { S, C, H }) => - void (H.resolve(S.pop()) ? C.pushR(cons) : alt && C.pushR(alt)), - - ApplyBuiltinOp: ({ builtinOp: { id }, values }: ApplyBuiltinOp, { S, B, H }) => - void S.push(H.alloc(B.get(id)!(...values))), + void (H.resolve(S.pop()) ? C.pushR(H.alloc(cons)) : alt && C.pushR(H.alloc(alt))), EnvOp: ({ envId }: EnvOp, { E }) => void E.setId(envId), diff --git a/src/go-slang/lib/astMap.ts b/src/go-slang/lib/astMap.ts new file mode 100644 index 000000000..93151a724 --- /dev/null +++ b/src/go-slang/lib/astMap.ts @@ -0,0 +1,33 @@ +import { Node } from '../types' + +export class AstMap extends Map { + private nextAstId: number + + constructor(nextAstId: number) { + super() + this.nextAstId = nextAstId + } + + /** + * Track an AST node + * + * If the node already has a unique identifier, it will be returned as is. + * Otherwise, a new unique identifier will be assigned to the node (in-place). + * + * @param node AST node to track + * @returns AST node with a unique identifier + */ + public track(node: Node): Node { + node.uid = node.uid ?? this.nextAstId++ + this.set(node.uid, node) + return node + } + + public trackM(nodes: Node[]): Node[] { + return nodes.map(this.track.bind(this)) + } + + public get(uid: number): T { + return super.get(uid) as T + } +} diff --git a/src/go-slang/lib/env.ts b/src/go-slang/lib/env.ts index bac0adba0..55b664cee 100644 --- a/src/go-slang/lib/env.ts +++ b/src/go-slang/lib/env.ts @@ -1,5 +1,3 @@ -import { PREDECLARED_IDENTIFIERS } from './predeclared' - type Maybe = T | null interface Frame { @@ -65,7 +63,3 @@ export class Environment { return this } } - -export function createGlobalEnvironment(): Environment { - return new Environment({ ...PREDECLARED_IDENTIFIERS }) -} diff --git a/src/go-slang/lib/heap/index.ts b/src/go-slang/lib/heap/index.ts index 0a597006c..a01e74730 100644 --- a/src/go-slang/lib/heap/index.ts +++ b/src/go-slang/lib/heap/index.ts @@ -1,4 +1,19 @@ -import { BuiltinOp, ClosureOp, CommandType, isCommand } from '../../types' +import { + AssignOp, + BinaryOp, + BuiltinOp, + CallOp, + ClosureOp, + CommandType, + EnvOp, + Node, + PopS, + UnaryOp, + VarDeclOp, + isCommand, + isNode +} from '../../types' +import { AstMap } from '../astMap' import { DEFAULT_HEAP_SIZE, WORD_SIZE } from './config' import { PointerTag } from './tags' @@ -8,7 +23,11 @@ export class Heap { private memory: DataView private free: number = 0 // n_words to the next free block - constructor(n_words?: number) { + // we need to keep track of the AstMap to be able to resolve AST nodes stored in the heap + private astMap: AstMap + + constructor(astMap: AstMap, n_words?: number) { + this.astMap = astMap this.memory = new DataView(new ArrayBuffer((n_words ?? DEFAULT_HEAP_SIZE) * WORD_SIZE)) // Allocate special values in the heap to avoid re-allocating them @@ -36,19 +55,42 @@ export class Heap { return this.allocateNumber(value) } + // AST nodes + if (isNode(value)) { + // we need to track the AST node to be able to resolve it later + return this.allocateAstNode(this.astMap.track(value)) + } + // ECE operations if (isCommand(value)) { switch (value.type) { + case CommandType.VarDeclOp: + return this.allocateVarDeclOp(value) + case CommandType.AssignOp: + return this.allocateAssignOp(value) + case CommandType.UnaryOp: + case CommandType.BinaryOp: + return this.allocateUnaryBinaryOp(value) + case CommandType.CallOp: + return this.allocateCallOp(value) case CommandType.BuiltinOp: return this.allocateBuiltinOp(value) case CommandType.ClosureOp: return this.allocateClosureOp(value) + case CommandType.EnvOp: + return this.allocateEnvOp(value) + case CommandType.PopSOp: + return this.allocateTaggedPtr(PointerTag.PopSOp) } } return value } + public allocM(values: any[]): HeapAddress[] { + return values.map(this.alloc.bind(this)) + } + /** * Resolve a heap address to its underlying data/value * @@ -69,6 +111,35 @@ export class Heap { return true case PointerTag.Number: return this.get(heap_addr + 1) + case PointerTag.AstNode: + return this.astMap.get(this.memory.getInt16(mem_addr + 1)) + case PointerTag.VarDeclOp: + return { + type: CommandType.VarDeclOp, + idNodeUid: this.memory.getInt16(mem_addr + 1), + zeroValue: this.memory.getInt8(mem_addr + 7) === 1 + } as VarDeclOp + case PointerTag.AssignOp: + return { + type: CommandType.AssignOp, + idNodeUid: this.memory.getInt16(mem_addr + 1) + } as AssignOp + case PointerTag.UnaryOp: + return { + type: CommandType.UnaryOp, + opNodeId: this.memory.getInt16(mem_addr + 1) + } as UnaryOp + case PointerTag.BinaryOp: + return { + type: CommandType.BinaryOp, + opNodeId: this.memory.getInt16(mem_addr + 1) + } as BinaryOp + case PointerTag.CallOp: + return { + type: CommandType.CallOp, + calleeNodeId: this.memory.getInt16(mem_addr + 1), + arity: this.memory.getInt16(mem_addr + 3) + } as CallOp case PointerTag.BuiltInOp: return { type: CommandType.BuiltinOp, @@ -81,9 +152,20 @@ export class Heap { funcDeclNodeUid: this.memory.getInt16(mem_addr + 1), envId: this.memory.getInt16(mem_addr + 3) } as ClosureOp + case PointerTag.EnvOp: + return { + type: CommandType.EnvOp, + envId: this.memory.getInt16(mem_addr + 1) + } as EnvOp + case PointerTag.PopSOp: + return PopS } } + public resolveM(heap_addrs: any[]): any[] { + return heap_addrs.map(this.resolve.bind(this)) + } + private allocateBoolean(value: boolean): HeapAddress { // booleans are represented as tagged pointers with no underlying data return this.allocateTaggedPtr(value ? PointerTag.True : PointerTag.False, 0) @@ -99,6 +181,62 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of an AST Node: [0:tag, 1-2:astId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateAstNode({ uid }: Node): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.AstNode) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, uid as number) + + return ptr_heap_addr + } + + /* Memory Layout of a Unary/Binary Op: [0:tag, 1-2:opNodeId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateUnaryBinaryOp({ type, opNodeId }: UnaryOp | BinaryOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr( + type === CommandType.UnaryOp ? PointerTag.UnaryOp : PointerTag.BinaryOp + ) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, opNodeId) + + return ptr_heap_addr + } + + /* Memory Layout of a VarDeclOp: [0:tag, 1-2:idNodeUid, 3-4:_unused_, 5-6:size, 7:isZeroValue] (1 word) */ + private allocateVarDeclOp({ zeroValue, idNodeUid }: VarDeclOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.VarDeclOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, idNodeUid) + this.memory.setInt8(ptr_mem_addr + 7, zeroValue ? 1 : 0) + + return ptr_heap_addr + } + + /* Memory Layout of an AssignOp: [0:tag, 1-2:idNodeUid, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateAssignOp({ idNodeUid }: AssignOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.AssignOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + this.memory.setInt16(ptr_mem_addr + 1, idNodeUid) + + return ptr_heap_addr + } + + /* Memory Layout of a CallOp: [0:tag, 1-2:calleeNodeId, 3-4:arity, 5-6:size, 7:_unused_] (1 word) */ + private allocateCallOp({ calleeNodeId, arity }: CallOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.CallOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + // NOTE: assume there will be no more than 2^16 AST nodes + this.memory.setInt16(ptr_mem_addr + 1, calleeNodeId) + // NOTE: assume there will be no more than 2^16 arguments + this.memory.setInt16(ptr_mem_addr + 3, arity) + + return ptr_heap_addr + } + /* Memory Layout of a BuiltinOp: [0:tag, 1-2: arity, 3-4:id, 5-6:size, 7:_unused_] (1 word) */ private allocateBuiltinOp({ arity, id }: BuiltinOp): HeapAddress { const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.BuiltInOp) @@ -125,6 +263,17 @@ export class Heap { return ptr_heap_addr } + /* Memory Layout of an EnvOp: [0:tag, 1-2:envId, 3-4:_unused_, 5-6:size, 7:_unused_] (1 word) */ + private allocateEnvOp({ envId }: EnvOp): HeapAddress { + const ptr_heap_addr = this.allocateTaggedPtr(PointerTag.EnvOp) + + const ptr_mem_addr = ptr_heap_addr * WORD_SIZE + // NOTE: assume there will be no more than 2^16 envs + this.memory.setInt16(ptr_mem_addr + 1, envId) + + return ptr_heap_addr + } + /** * Allocate a tagged pointer in the heap * diff --git a/src/go-slang/lib/heap/tags.ts b/src/go-slang/lib/heap/tags.ts index c18f26db6..4f6c26f26 100644 --- a/src/go-slang/lib/heap/tags.ts +++ b/src/go-slang/lib/heap/tags.ts @@ -2,6 +2,14 @@ export enum PointerTag { False = 0, True, Number, + AstNode, + VarDeclOp, + AssignOp, + UnaryOp, + BinaryOp, + CallOp, BuiltInOp, - ClosureOp + ClosureOp, + EnvOp, + PopSOp } diff --git a/src/go-slang/parser/go.js b/src/go-slang/parser/go.js index 84346db9d..c99d8e87f 100644 --- a/src/go-slang/parser/go.js +++ b/src/go-slang/parser/go.js @@ -373,8 +373,8 @@ function peg$parse(input, options) { var peg$f8 = function(digits) { return buildLiteral(buildInteger(digits, 16)) }; - var peg$f9 = function(operator, argument) { - return makeNode({type: "UnaryExpression", operator, argument}) + var peg$f9 = function(op, argument) { + return makeNode({type: "UnaryExpression", operator: makeOperator(op), argument}) }; var peg$f10 = function(head, tail) { return buildBinaryExpression(head, tail); }; var peg$f11 = function(head, tail) { return buildBinaryExpression(head, tail); }; @@ -5117,16 +5117,20 @@ function peg$parse(input, options) { function makeNode(node) { return { ...node, uid: uid++, loc: location() }; } + + function makeOperator(op) { + return makeNode({ type: "Operator", op }); + } function buildLiteral(value) { return makeNode({ type: "Literal", value: value}); } - + function buildBinaryExpression(head, tail) { return tail.reduce(function(result, element) { return makeNode({ type: "BinaryExpression", - operator: element[1], + operator: makeOperator(element[1]), left: result, right: element[3] }); diff --git a/src/go-slang/parser/go.pegjs b/src/go-slang/parser/go.pegjs index 773cbc9e6..b19370b4a 100644 --- a/src/go-slang/parser/go.pegjs +++ b/src/go-slang/parser/go.pegjs @@ -20,16 +20,20 @@ function makeNode(node) { return { ...node, uid: uid++, loc: location() }; } + + function makeOperator(op) { + return makeNode({ type: "Operator", op }); + } function buildLiteral(value) { return makeNode({ type: "Literal", value: value}); } - + function buildBinaryExpression(head, tail) { return tail.reduce(function(result, element) { return makeNode({ type: "BinaryExpression", - operator: element[1], + operator: makeOperator(element[1]), left: result, right: element[3] }); @@ -141,8 +145,8 @@ HexDigit UnaryExpression = CallExpression / PrimaryExpression - / operator:UnaryOperator argument:UnaryExpression { - return makeNode({type: "UnaryExpression", operator, argument}) + / op:UnaryOperator argument:UnaryExpression { + return makeNode({type: "UnaryExpression", operator: makeOperator(op), argument}) } UnaryOperator diff --git a/src/go-slang/types.ts b/src/go-slang/types.ts index e22b14e37..8816d1767 100644 --- a/src/go-slang/types.ts +++ b/src/go-slang/types.ts @@ -11,6 +11,7 @@ export enum NodeType { ExpressionStatement = 'ExpressionStatement', EmptyStatement = 'EmptyStatement', Assignment = 'Assignment', + Operator = 'Operator', UnaryExpression = 'UnaryExpression', BinaryExpression = 'BinaryExpression', Identifier = 'Identifier', @@ -54,6 +55,10 @@ export interface Node { loc?: NodeLocation } +export function isNode(v: any): boolean { + return v && v.type && NodeType[v.type] +} + export interface SourceFile extends Node { type: NodeType.SourceFile topLevelDecls: TopLevelDeclaration[] @@ -154,12 +159,6 @@ export const True: Literal = { type: NodeType.Literal, value: true } export type UnaryOperator = '+' | '-' -export interface UnaryExpression extends Node { - type: NodeType.UnaryExpression - operator: UnaryOperator - argument: Expression -} - export type BinaryOperator = | '+' | '-' @@ -175,9 +174,20 @@ export type BinaryOperator = | '>' | '>=' +export interface Operator extends Node { + type: NodeType.Operator + op: UnaryOperator | BinaryOperator +} + +export interface UnaryExpression extends Node { + type: NodeType.UnaryExpression + operator: Operator + argument: Expression +} + export interface BinaryExpression extends Node { type: NodeType.BinaryExpression - operator: BinaryOperator + operator: Operator left: Expression right: Expression } @@ -199,8 +209,7 @@ export enum CommandType { EnvOp = 'EnvOp', PopSOp = 'PopSOp', PopTillMOp = 'PopTillMOp', - BuiltinOp = 'BuiltinOp', - ApplyBuiltinOp = 'ApplyBuiltinOp' + BuiltinOp = 'BuiltinOp' } export interface Command { @@ -214,22 +223,22 @@ export function isCommand(v: any): boolean { export interface VarDeclOp extends Command { type: CommandType.VarDeclOp zeroValue: boolean - name: string + idNodeUid: number } export interface AssignOp extends Command { type: CommandType.AssignOp - name: string + idNodeUid: number } export interface UnaryOp extends Command { type: CommandType.UnaryOp - operator: UnaryOperator + opNodeId: number } export interface BinaryOp extends Command { type: CommandType.BinaryOp - operator: BinaryOperator + opNodeId: number } export interface ClosureOp extends Command { @@ -241,7 +250,7 @@ export interface ClosureOp extends Command { export interface CallOp extends Command { type: CommandType.CallOp arity: number - calleeName: string + calleeNodeId: number } export interface BranchOp extends Command { @@ -278,12 +287,6 @@ export interface BuiltinOp extends Command { arity?: number } -export interface ApplyBuiltinOp extends Command { - type: CommandType.ApplyBuiltinOp - builtinOp: BuiltinOp - values: any[] -} - export enum MarkerType { RetMarker = 'RetMarker', ForStartMarker = 'ForStartMarker', @@ -328,5 +331,4 @@ export type Instruction = | PopSOp | PopTillMOp | BuiltinOp - | ApplyBuiltinOp | Marker