UNPKG

parser-transform

Version:

Streaming+Async lexer and parser

219 lines (163 loc) 6.57 kB
{LRParser,LRParsingTable,Grammar,CanonicalCollection,LRParserGenerator} = require 'syntax-cli' {EntryType} = LRParsingTable SpecialSymbols = require 'syntax-cli/dist/special-symbols.js' Stream = require 'stream' vm = require 'vm' class StreamingLRParserGenerator extends LRParserGenerator constructor: ({grammar,options = {}}) -> super {grammar,options} @context = yytext: '' yyleng: 0 __: null __handler: [] context = vm.createContext @context @vm = eval: (code) -> vm.runInContext code, context return This is in `traits`. buildSemanticAction: (production) -> # action is the text action = @getSemanticActionCode production return null unless action # args is the Array containing the argument names args = @getSemanticActionParams production # Save the action, they are injected later. # "__handler[1]", "__handler[2]", etc. index = production.getNumber() name = "__handler[#{index}]" handler = @vm.eval "__handler[#{index}] = async function (#{args}) {\n#{action}\n}" name generateModuleInclude: -> moduleInclude = @_grammar.getModuleInclude() if moduleInclude? @vm.eval moduleInclude return Replaces the "template" class StreamingLRParser constructor: (generator) -> @_generator = generator @_grammar = generator._grammar @_table = generator.getTable() @_stack = [] @_startingState = @_table._canonicalCollection .getStartingState() .getNumber() @_stack.push @_startingState @_shiftedToken = null return _peek: LRParser::_peek _shift: LRParser::_shift _reduce: (entry,token) -> productionNumber = entry.slice(1) production = this._grammar.getProduction(productionNumber) hasSemanticAction = production.hasSemanticAction() semanticValueArgs = if hasSemanticAction then [] else null locationArgs = if hasSemanticAction and this._grammar.shouldCaptureLocations() then [] else null # Pop 2x symbols from the stack (RHS + state number for each), # unless it's an ε-production for which nothing to pop. unless production.isEpsilon() rhsLengh = production.getRHS().length while rhsLengh-- # Pop state number this._stack.pop() # Pop production symbol. stackEntry = this._stack.pop() if (hasSemanticAction) semanticValueArgs.unshift(stackEntry.semanticValue) if (locationArgs) locationArgs.unshift(stackEntry.loc) previousState = @_peek() symbolToReduceWith = production.getLHS().getSymbol() reduceStackEntry = {symbol: symbolToReduceWith} if hasSemanticAction @_generator.context.yytext = if token then token.value else '' @_generator.context.yyleng = if token then token.value.length else 0 semanticActionArgs = if locationArgs isnt null semanticValueArgs.concat(locationArgs) else semanticValueArgs # Run corresponding semantic action, result is in $$ (__). handler = @_generator.context.__handler[productionNumber] await handler(semanticActionArgs...).catch console.error reduceStackEntry.semanticValue = @_generator.context.__ if (locationArgs) reduceStackEntry.loc = CodeUnit.getSandbox().$$loc # Then push LHS. this._stack.push(reduceStackEntry) nextState = this._table.get()[previousState][symbolToReduceWith] # And push the next state (goto) this._stack.push(nextState) return class ParserTransform extends Stream.Transform constructor: (grammar,options = {}) -> options.objectMode = true super options generator = new StreamingLRParserGenerator {grammar} generator.generateParserData() generator.context.emit = (chunk) => @push chunk @parser = new StreamingLRParser generator return _transform: (chunk,encoding,next) -> token = type: if chunk.eof then SpecialSymbols.EOF else chunk.token value: chunk.text line: chunk.line column: chunk.column unless @parser._stack.length >= 1 next new Error "Parser stack too small" return state = @parser._peek() column = token.type entry = @parser._table.get()[state][column] unless entry next new Error "Unexpected token #{column} in state #{state} at line #{token.line} column #{token.column}" return switch LRParsingTable.getEntryType(entry) when EntryType.SHIFT @parser._shift token, entry @parser._shiftedToken = token next() when EntryType.REDUCE do => try await @parser._reduce entry, @parser._shiftedToken # Don't advance tokens on reduce. @_transform chunk,encoding,next catch error next error return when EntryType.SR_CONFLICT next new Error "ConflictError: shift-reduce in #{state}, #{column}" when EntryType.RR_CONFLICT next new Error "ConflictError: reduce-reduce in #{state}, #{column}" when EntryType.ACCEPT # Pop starting production and its state number. @parser._stack.pop() parsed = @parser._stack.pop() unless @parser._stack.length is 1 and @parser._stack[0] is @parser._startingState and chunk.eof next new Error "Unexpected token #{token}" return result = {status: 'accept'} if parsed.hasOwnProperty 'semanticValue' result.value = parsed.semanticValue @emit 'success', result next() return _final: (next) -> do => error = null try while @parser._stack.length > 1 await new Promise (resolve) => @_transform eof:true, null, resolve catch error if error? next error else next() return return module.exports = ParserTransform