diff --git a/debugger/steps.coffee b/debugger/steps.coffee new file mode 100644 index 0000000..3141457 --- /dev/null +++ b/debugger/steps.coffee @@ -0,0 +1,34 @@ +{ instructionQueue } = require '../interpreter/vm-state' +Ast = require '../parser/ast' +{ getFunctionMap } = require '../interpreter/function' + +{ NODES, STATEMENTS, OPERATORS } = Ast + +@stepOut = -> + i = instructionQueue.length - 1 + while i >= 0 + break if instructionQueue[i].getType() is NODES.END_FUNC_BLOCK + --i + instructionQueue[--i].stop = yes + +@stepOver = -> + i = instructionQueue.length - 1 + while i >= 0 + if instructionQueue[i].getType() is NODES.FUNCALL + instructionQueue[i].stop = yes + break + if instructionQueue[i].getType() is NODES.END_FUNC_BLOCK and i > 0 + instructionQueue[--i].stop = yes + break + --i + +@stepInto = -> + instr = instructionQueue[instructionQueue.length - 1] + if instr.getType() is NODES.FUNCALL + { instructions } = getFunctionMap()[instr.getChild(0).getChild(0)] + console.log instructions + instructions.getChild(0).stop = yes + else + instr.stop = yes + +module.exports = @ diff --git a/gulpfile.js b/gulpfile.js index edf9f3e..64e9cd5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,7 +23,7 @@ gulp.task('dev', function() { .transform(coffeeify) .transform(jisonify) .bundle() - .pipe(source('index.js')) + .pipe(source('index.min.js')) .pipe(gulp.dest('./lib')) }); diff --git a/index.coffee b/index.coffee index 7ef9187..3dd32e7 100644 --- a/index.coffee +++ b/index.coffee @@ -2,6 +2,9 @@ { checkSemantics } = require './semantics/' interpreter = require './interpreter/' Ast = require './parser/ast.coffee' +io = require './interpreter/io' +Stack = require './interpreter/stack' +{ stepInto, stepOver, stepOut } = require './debugger/steps' parser.yy = { Ast } @@ -18,14 +21,25 @@ parser.yy = { Ast } loop { value, done } = iterator.next() - yield value break unless not done - result = value - yield result + yield value: value, stack: Stack.stack + yield 0 + +@hooks = { + setInput: (input) -> io.setInput(io.STDIN, input) + isInputBufferEmpty: -> io.isInputBufferEmpty(io.STDIN) + modifyVariable: (stackNumber, varName, value) -> Stack.stack[stackNumber].variables[varName].value = value +} @events = { onstdout: (cb) -> interpreter.onstdout cb } +@actions = { + stepOut: -> stepOut() + stepOver: -> stepOver() + stepInto: -> stepInto() +} + self.cmm = @ diff --git a/interpreter/function.coffee b/interpreter/function.coffee index 704a211..21e4456 100644 --- a/interpreter/function.coffee +++ b/interpreter/function.coffee @@ -4,14 +4,17 @@ Stack = require './stack' Ast = require '../parser/ast' Error = require '../error' Expression = require './expression' +{ instructionQueue } = require './vm-state' + { NODES, TYPES } = Ast module.exports = @ -# Of the form: { : { type: tipus, argIds: [id1, id2, ..., idn] } } +# Of the form: { : { type: tipus, argTypes: [ty1, ty2, ..., tyn] argIds: [id1, id2, ..., idn], instructions: instruccions } } functions = null +@getFunctionMap = -> functions @mapFunctions = (T) -> functions = {} @@ -19,9 +22,10 @@ functions = null for functionTree in T.getChildren() assert.strictEqual functionTree.getType(), TYPES.FUNCTION funcId = functionTree.getChild(1).getChild(0) + argTypes = (argAst.getChild(0) for argAst in functionTree.getChild(2).getChildren()) argIds = (argAst.getChild(1).getChild(0) for argAst in functionTree.getChild(2).getChildren()) type = functionTree.getChild(0) - functions[funcId] = { type, argIds, instructions: functionTree.getChild(3) } + functions[funcId] = { type, argTypes, argIds, instructions: functionTree.getChild(3) } return @initFunction = (T) -> @@ -34,23 +38,28 @@ functions = null assert func?, 'Function ' + funcId + ' not declared' - { type, argIds, instructions } = func + { type, argTypes, argIds, instructions } = func assert argIds.length is argValuesAst.getChildCount() argIdValuePairs = for id, i in argIds + type: argTypes[i] id: id value: Expression.evaluateExpression(argValuesAst.getChild(i)) result = null - Stack.pushActivationRecord() + Stack.pushActivationRecord(funcId, argIds) - for { id, value } in argIdValuePairs - Stack.defineVariable id, value + for { type, id, value } in argIdValuePairs + Stack.defineVariable id, type, value, param=yes - instructions + instructionQueue.push new Ast NODES.END_FUNC_BLOCK, [] + instructionQueue.push instructions @finalizeFunction = -> + loop + instr = instructionQueue.pop() + break if instr.getType() is NODES.END_FUNC_BLOCK Stack.popActivationRecord() diff --git a/interpreter/index.coffee b/interpreter/index.coffee index 7470c4b..92156c2 100644 --- a/interpreter/index.coffee +++ b/interpreter/index.coffee @@ -3,7 +3,7 @@ assert = require 'assert' Error = require '../error' Ast = require '../parser/ast' Stack = require './stack' -{ mapFunctions, initFunction, finalizeFunction } = require './function' +{ mapFunctions, initFunction } = require './function' { initRunner, executeInstruction } = require './runner' io = require './io' @@ -19,20 +19,14 @@ module.exports = @ io.reset() io.setInput(io.STDIN, input) - try - instructions = initFunction new Ast(NODES.FUNCALL, [new Ast(NODES.ID, ["main"]), new Ast(NODES.PARAM_LIST, [])]) - initRunner instructions - iterator = executeInstruction() - loop - { value, done } = iterator.next() - yield value - break unless not done - status = value - finalizeFunction() - catch error - console.error error.stack ? error.message ? error - io.output io.STDERR, error.message - status = error.code + initFunction new Ast(NODES.FUNCALL, [new Ast(NODES.ID, ["main"]), new Ast(NODES.PARAM_LIST, [])]) + initRunner() + iterator = executeInstruction() + loop + { value, done } = iterator.next() + break unless not done + yield value + status = value yield { status, stderr: io.getStream(io.STDERR) } diff --git a/interpreter/io.coffee b/interpreter/io.coffee index 4c09ee3..b87cadf 100644 --- a/interpreter/io.coffee +++ b/interpreter/io.coffee @@ -27,6 +27,7 @@ module.exports = class IO IO.streams[stream] += string IO.stdoutCB IO.streams[IO.STDOUT] + IO.streams[IO.STDOUT] = ''; @setInput: (stream, input) -> assert (typeof input is "string") @@ -48,3 +49,5 @@ module.exports = class IO @getStream: (stream) -> IO.streams[stream] @setStdoutCB: (cb) -> IO.stdoutCB = cb + + @isInputBufferEmpty: (stream) -> IO.streams[stream].length is 0 diff --git a/interpreter/runner.coffee b/interpreter/runner.coffee index 48afcd8..4724bac 100644 --- a/interpreter/runner.coffee +++ b/interpreter/runner.coffee @@ -13,9 +13,7 @@ io = require './io' module.exports = @ -@initRunner = (T) -> - @root = T - instructionQueue = [T] +@initRunner = -> @openScopes = {} @getNumberInstructions = -> @@ -35,14 +33,15 @@ executeInstructionHelper = (T) -> unwrapBlock T when NODES.DECLARATION declarations = T.getChild 1 + type = T.getChild 0 for declaration in declarations if declaration.getType() is OPERATORS.ASSIGN varName = declaration.child().child() value = evaluateExpression declaration.getChild 1 - Stack.defineVariable varName, value + Stack.defineVariable varName, type, value else if declaration.getType() is NODES.ID varName = declaration.child() - Stack.defineVariable varName + Stack.defineVariable varName, type when STATEMENTS.COUT for outputItem in T.getChildren() io.output io.STDOUT, evaluateExpression(outputItem) @@ -87,7 +86,7 @@ executeInstructionHelper = (T) -> when NODES.CLOSE_SCOPE Stack.closeScope() when NODES.FUNCALL - pushInstruction initFunction T + initFunction T when STATEMENTS.CIN allRead = yes for inputItem in T.getChildren() @@ -107,6 +106,8 @@ executeInstructionHelper = (T) -> Stack.setVariable id, null allRead = no cinStack.push allRead + when NODES.END_FUNC_BLOCK + (->)() else evaluateExpression T diff --git a/interpreter/stack.coffee b/interpreter/stack.coffee index 212eb3b..f01e657 100644 --- a/interpreter/stack.coffee +++ b/interpreter/stack.coffee @@ -5,8 +5,8 @@ module.exports = class Stack @stack: [] @currentAR: null - @pushActivationRecord: -> - @currentAR = { scopesStack: [], variables: {} } + @pushActivationRecord: (funcName, args) -> + @currentAR = { scopesStack: [], variables: {}, funcName: funcName, args: args } @stack.push @currentAR @popActivationRecord: -> @@ -17,28 +17,28 @@ module.exports = class Stack if @stack.length > 0 then @stack[@stack.length - 1] else null # Parameter value is optional, if ommited means variable has been declared but not yet assigned - @defineVariable: (name, value = null) -> + @defineVariable: (name, type, value = null) -> assert @currentAR? assert (typeof name is "string") - @currentAR.variables[name] = value + @currentAR.variables[name] = { type: type, value: value } @getVariable: (name) -> assert @currentAR? assert (typeof name is "string") - assert typeof @currentAR.variables[name] isnt "undefined" + assert typeof @currentAR.variables[name].value isnt "undefined" if @currentAR.variables[name] is null throw Error.GET_VARIABLE_NOT_ASSIGNED.complete('name', name) else - @currentAR.variables[name] + @currentAR.variables[name].value @setVariable: (name, value) -> assert @currentAR? assert (typeof name is "string") - assert typeof @currentAR.variables[name] isnt "undefined" + assert typeof @currentAR.variables[name].value isnt "undefined" - @currentAR.variables[name] = value + @currentAR.variables[name].value = value @openNewScope: -> assert @currentAR? diff --git a/parser/ast.coffee b/parser/ast.coffee index 382a9fa..c03f99d 100644 --- a/parser/ast.coffee +++ b/parser/ast.coffee @@ -72,6 +72,7 @@ module.exports = class Ast CLOSE_SCOPE: 'CLOSE_SCOPE' FUNC_VALUE: 'FUNC_VALUE' CIN_VALUE: 'CIN_VALUE' + END_FUNC_BLOCK: 'END_FUNC_BLOCK' }) @CASTS: Object.freeze({ @@ -101,6 +102,7 @@ module.exports = class Ast constructor: (@type, @children, @leaf = no) -> @instr = no + @instrNumber = -1 assert (typeof @type is "string") assert Array.isArray(@children) @@ -136,10 +138,14 @@ module.exports = class Ast setIsInstr: (@instr) -> + setInstrNumber: (@instrNumber) -> + setId: (@id) -> - addChild: (child, instr=no) -> - child.setIsInstr yes if instr + addChild: (child, instr=no, instrNumber=-1) -> + if instr + child.setIsInstr yes + child.setInstrNumber instrNumber @children.push child setChild: (i, value) -> @children[i] = value diff --git a/parser/grammar.jison b/parser/grammar.jison index 09cd13b..38e4d77 100644 --- a/parser/grammar.jison +++ b/parser/grammar.jison @@ -154,7 +154,7 @@ arg block_instr : block_instr instruction - {$$.addChild($2, instr=true);} + {$$.addChild($2, instr=true, instrNumber=@2.first_line);} | {$$ = new yy.Ast('BLOCK-INSTRUCTIONS', []);} ; @@ -206,7 +206,7 @@ if : IF '(' expr ')' instruction_body %prec THEN {$$ = new yy.Ast('IF-THEN', [$3, $5]);} | IF '(' expr ')' instruction_body else - {$$ = new yy.Ast('IF-THEN-ELSE', [$3, $5, $6]);} + {$6.setIsInstr(true); $6.setInstrNumber(@6.first_line); $$ = new yy.Ast('IF-THEN-ELSE', [$3, $5, $6]);} ; while @@ -256,7 +256,7 @@ block_cout instruction_body : instruction - {$1.setIsInstr(true); $$ = new yy.Ast('BLOCK-INSTRUCTIONS', [$1]);} + {$1.setIsInstr(true); $1.setInstrNumber(@1.first_line); $$ = new yy.Ast('BLOCK-INSTRUCTIONS', [$1]);} | '{' block_instr '}' {$$ = $2;} ; diff --git a/semantics/index.coffee b/semantics/index.coffee index 6cf2397..f6cbc37 100644 --- a/semantics/index.coffee +++ b/semantics/index.coffee @@ -415,6 +415,7 @@ checkAndPreprocess = (ast, definedVariables, functionId) -> return TYPES.VOID +# TODO: replace by jison enumerations enumerateInstructions = (T) -> for child in T.getChildren() child.setId ++NODE_INDEX if child.instr @@ -504,6 +505,7 @@ preprocessFunctionAndCinNodes = (T, currentBlockInstr, currentInstr) -> enumerateInstructions blockInstructionsAst preprocessFunctionAndCinNodes blockInstructionsAst, blockInstructionsAst, blockInstructionsAst.getChild(0) blockInstructionsAst.getChildren().push new Ast(STATEMENTS.RETURN, []) if returnType is TYPES.VOID + blockInstructionsAst.getChildren().push new Ast(STATEMENTS.RETURN, []) if functionId is "main" if definedVariables.main isnt TYPES.FUNCTION throw Error.MAIN_NOT_DEFINED