@iarna/parser

    1.0.0 • Public • Published

    @iarna/parser

    A recursive descent parser framework, as seen in @iarna/toml, etc

    EXAMPLE

    Once you create a grammar, you can use it with a provided helper function:

    const { parseString } = require('@iarna/parser')
    
    console.log(parseString(Grammar, '+ 1 2')) // 3
    
    console.log(parseString(Grammar, '+ 1 2 + 4 5')) // 9, the most recent result
    
    console.log(parseString.all(Grammar, '+ 1 2 + 4 5')) // [3, 9], all results

    Or directly:

    console.log(new Grammar().parse('+ 1 2').finish()) // 3
    console.log(new Grammar().parse('+ 1 2 + 4 5').finish()) // 9
    
    const gr = new Grammar()
    const results = []
    gr.on('result', _ => results.push(_))
    gr.parse('+ 1 2 + 4 5').finish()
    console.log(results) // [3, 9]

    Grammars are defined as subclasses of Parsers and always start with a parseStart method.

    const Parser = require('@iarna/parser')
    
    class RPN extends Parser {
      parseStart () {
        if (this.char === Parser.END) {
          return this.returnNow()
        } else {
          return this.callNow(this.parseValue, this.emitResult)
        }
      }
      emitResult (value) {
        this.result(value)
        return this.goto(this.parseNext)
      }
      parseNext () {
        if (this.char === Parser.END) {
          return this.returnNow()
        } else if (this.char === CHAR_SP) {
          return this.next(this.parseStart)
        } else {
          throw this.error(new Error('Expected space or end of expression'))
        }
      }
    
      parseValue () {
        if (this.char === CHAR_PLUS || this.char === CHAR_MINUS) {
          return this.goto(this.parseOp)
        } else if (this.char >= CHAR_0 || this.char <= CHAR_9) {
          return this.goto(this.parseNumber)
        } else {
          return this.returnNow()
        }
      }
    
      parseOp () {
        if (this.char === CHAR_PLUS) {
          return this.call(this.parsePair, this.parseComputeAdd)
        } else {
          throw this.error(new Error('Expected valid operator'))
        }
      }
      parseComputeAdd (values) {
        return this.returnNow(values.reduce((acc, _) => acc + _, 0))
      }
    
      parsePair () {
        if (this.char === CHAR_SP) {
          return this.call(this.parseValue, this.parsePairSep)
        } else {
          throw this.error(new Error('Expected valid separator'))
        }
      }
      parsePairSep (value) {
        if (this.char === CHAR_SP) {
          this.state.list.push(value)
          return this.call(this.parseValue, this.parsePairResult)
        } else {
          throw this.error(new Error('Expected space'))
        }
      }
      parsePairResult (value) {
        this.state.list.push(value)
        return this.returnNow(this.state.list)
      }
    
      parseNumber () {
        if (this.char >= CHAR_0 && this.char <= CHAR_9) {
          this.consume()
        } else {
          return this.returnNow(Number(this.buf()))
        }
      }
    }

    METHODS

    debug()

    parse(buf|str)

    finish()

    result(value)

    next(state)

    goto(state)

    call(state[, returnState])

    callNow(state[, returnState])

    return(value)

    returnNow(value)

    consume([char])

    error([err])

    buf()

    nextChar()

    haveBuffer()

    runOne()

    RESERVED STATES

    parseStart

    Overrideme!

    endParse(value)

    Stores value for returning by finish(). Throws if it ever is entered outside of the end.

    TODO

    • Obvously, finish docs. On the plus side, tests are 100%, which is keen.
    • Maybe create a grammar construction language, to make them more concise, easier to compose.

    Keywords

    none

    Install

    npm i @iarna/parser

    DownloadsWeekly Downloads

    2

    Version

    1.0.0

    License

    ISC

    Unpacked Size

    54.5 kB

    Total Files

    17

    Last publish

    Collaborators

    • iarna