punyexpr grammar

by Arnaud Buchholz

HTML

Simplified <a href="https://tc39.es/ecma262/#sec-expressions" target="_blank">grammar</a>

<pre>
  Literal :
    IdentifierLiteral
    NullLiteral
    BooleanLiteral
    NumbericLiteral
    StringLiteral

  PrimaryExpression :
    Literal
    <s><i>[</i>ArrayLiteral</s>
    <s><i>{</i>ObjectLiteral</s>
    <i>(</i> CoverParenthesizedExpressionAndArrowParameterList
  
  CoverParenthesizedExpressionAndArrowParameterList :
    <b>(</b> Expression (<b>,</b> Expression)* <b>)</b>
    
  ConditionalExpression :
    ShortCircuitExpression <b>?</b> AssignmentExpression <b>:</b>  AssignmentExpression

  ShortCircuitExpression :
    
    
  EqualityExpression :
    RelationalExpression
    EqualityExpression <b>==</b> RelationalExpression
    EqualityExpression <b>!=</b> RelationalExpression
    EqualityExpression <b>===</b> RelationalExpression
    EqualityExpression <b>!==</b> RelationalExpression

  RelationalExpression :
    AdditiveExpression
    RelationalExpression <b>&lt;</b> AdditiveExpression
    RelationalExpression <b>></b> AdditiveExpression
    RelationalExpression <b>&lt;=</b> AdditiveExpression
    RelationalExpression <b>>=</b> AdditiveExpression

  AdditiveExpression :
    MultiplicativeExpression
    AdditiveExpression <b>+</b> MultiplicativeExpression
    AdditiveExpression <b>-</b> MultiplicativeExpression

  MultiplicativeExpression :
    ExponentiationExpression
    MultiplicativeExpression <b>*</b> MemberExpression
    MultiplicativeExpression <b>/</b> MemberExpression
    MultiplicativeExpression <b>%</b> MemberExpression
    
  MemberExpression :
    PrimaryExpression
    MemberExpression <b>[</b> Expression <b>]</b>
    MemberExpression <b>.</b> IdentifierLiteral
    
    
</pre>

JavaScript

'use strict'

const log = text => {
  const div = document.createElement('div')
  if (typeof text !== 'string') {
    text = JSON.stringify(text)
  }
  div.appendChild(document.createTextNode(text))
  document.body.appendChild(div)
}

const parse = tokens => {
  const bind = (impl, ...params) => impl.bind(null, ...params)

  const addImpl = (...args) => {
    const context = args.pop()
    const first = args.shift()
    log({ addImpl: args, first: first(), context })
    return args.reduce((sum, arg) => sum + arg(context), first(context))
  }

  const subImpl = (...args) => {
    const context = args.pop()
    const first = args.shift()
    return args.reduce((sum, arg) => sum - arg(context), first(context))
  }
  
  const getImpl = (member, context) => {
    return context[member]
  }

  const iifImpl = (condition, trueValue, falseValue, context) => {
    if (condition(context)) {
      return trueValue(context)
    }
    return falseValue(context)
  }

  const current = () => tokens[0]
  const preview = (steps = 1) => tokens[steps]
  const move = (steps = 1) => { tokens = tokens.splice(steps) }
  
  const constantExpr = () => {
    const token = current()
    log({ constantExpr: token || 'EOT' })
    if (['number', 'string'].includes(token[0])) {
      move()
      return () => token[1]
    }
    throw new Error('Unexpected')
  }
  
  const addSubExpr = () => {
    const nextToken = preview()
    log({ addSubExpr: nextToken || 'EOT' })
    if (nextToken && nextToken[0] === 'symbol' && ['-', '+'].includes(nextToken[1])) {
      const first = constantExpr()
      move()
      const second = addSubExpr()
      log({ first: first, second: second })
      if (nextToken[1] === '+') {
        return bind(addImpl, first, second)
      } 
      return bind(subImpl, first, second)
    }
    return constantExpr()
  }
  
  const parsed = addSubExpr()
  return context => parsed(context)
}

const expr = parse([
  ['number', 1],
  ['symbol', '+'],
  ['number', 2]
])

log(expr())