Skip to content

Instantly share code, notes, and snippets.

@raisch
Last active January 26, 2017 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raisch/440c65d0c6c5d84bf794f831b6ebed60 to your computer and use it in GitHub Desktop.
Save raisch/440c65d0c6c5d84bf794f831b6ebed60 to your computer and use it in GitHub Desktop.
Example Of Using Interned Variables In Pegjs

Example Of Using Interned Variables In Pegjs

Author: Rob Raisch raisch@gmail.com

GOAL

Create a parser for "{float a =3;a*3+1;}" such that it will produce a result value of 10 per the StackOverflow question "The element of the two statements are mutually referenced with peg.js"

REQUIREMENTS

  • pegjs - see pegjs.org
  • util - used in grammar.js to format optional debugging messages
  • assert - used in testParser to check parsing result

INSTRUCTIONS

npm install pegjs
pegjs -o parser.js grammar.pegjs
node testParser

Files

  • README.md - this file
  • grammar.pegjs - parser grammar
  • testParser.js - simple test jig
{
/*
Filename: grammar.pegjs
Author: raisch@gmail.com
Uses a global vars:Object to handle variable declarations and substitutions.
*/
const util = require('util')
/**
* Storage of Declared Variables
* @type {Object}
*/
const vars = {}
/**
* If true, debugging messages will be emitted via debug
* @type {Boolean}
*/
const DEBUGGING = true
/**
* Emit debugging message via debug if DEBUGGING is true.
* @param {Array} args - arguments supplied to util.format
*/
const debug = (...args) => {
if (DEBUGGING) {
console.log('DEBUG: ' + util.format.apply(null, args))
}
}
}
// a variable declaration followed by a arithmetic operation producing a result
compound_statement = OPENCURLY SP* decl_statement SP* statement:statement SEMI CLOSECURLY {
debug(`found complex statement:"${text()}" = ${statement}`)
debug('result:'+JSON.stringify({
vars:vars,
value:statement
}, null, 2))
return statement
}
// ==== VARIABLE DECLARATION ====
decl_statement = spec:decl_specification SP+ id:symbol num:decl_assignment SP* SEMI {
debug(`found decl_statement:"${text()}"`)
const vardef = {
id: id,
type: spec,
value: 'float' === spec ? parseFloat(num) : parseInt(num)
}
debug(`INTERNING NEW VARIABLE "${id}": ${JSON.stringify(vardef)}`)
vars[id] = vardef
}
decl_specification
= 'int' {
debug(`found decl integer specification:"${text()}"`)
return text()
}
/ 'float' {
debug(`found decl float specification:"${text()}"`)
return text()
}
decl_assignment
= SP* ASSIGN SP* float:float {
debug(`found decl float assignment:"${text()}"`)
return float
}
/ SP* ASSIGN SP* int:integer {
debug(`found decl integer assignment:"${text()}"`)
return int
}
// ==== STATEMENTS ====
statement = expr:expression {
debug(`found expression_statement:"${text()}" = ${JSON.stringify(expr)}`)
return expr
}
/ primary:primary {
debug(`found primary_statement:"${text()}"`)
return primary
}
// ==== EXPRESSION ====
expression = head:term tail:( SP* addsub_op SP* term )* {
debug(`found expression:"${text()}"`)
return tail.reduce((res, elt) => {
const op = elt[1]
debug(`found ${op} operator`)
if(op === '+') {
return res + elt[3]
}
else if(op === '-') {
return res - elt[3]
}
}, head)
}
term = SP* head:factor tail:( SP* multdiv_op SP* factor)* {
debug(`found term:"${text()}"`)
return tail.reduce((res, elt) => {
const op = elt[1]
debug(`found ${op} operator`)
if(op === '*') {
return res * elt[3]
}
else if (op === '/') {
return res / elt[3]
}
}, head)
}
factor = OPENPAREN expr:expression CLOSEPAREN {
debug(`found paren factor:"${text()}"`)
return expr
}
/ primary:primary {
debug(`found primary factor:"${text()}"`)
return primary
}
primary
= float {
debug(`found float primary:"${text()}"`)
return parseFloat(text())
}
/ integer {
debug(`found integer primary:"${text()}"`)
return parseInt(text(), 10)
}
/ symbol:symbol { // deference symbol into cached value
debug(`found symbol primary:"${text()}"`)
const id = symbol
const v = vars[id] || {}
debug(`dereferencing: ${id}=${JSON.stringify(v)}`)
if(typeof v === 'object') {
if('float' === v.type) {
return parseFloat(v.value)
}
else if( 'integer' === v.type) {
return parseInt(v.value, 10)
}
else {
throw `attempt to dereference a bad object: ${JSON.stringify(v)}`
}
}
else {
throw 'attempt to reference undefined id:' + text()
}
}
integer = SIGN? intdigits {
debug(`found integer:"${text()}"`)
return text()
}
float = SIGN? intdigits float_frac? {
debug(`found float:"${text()}"`)
return text()
}
float_frac = POINT digits {
return text()
}
addsub_op = op:( ADD / SUB ) {
debug(`found addsub op:"${op}"`)
return op
}
multdiv_op = op:( MULT / DIV ) {
debug(`found multdiv op:"${op}"`)
return op
}
symbol = LETTER ( LETTER / DIGIT )* {
debug(`found symbol:"${text()}"`)
return text()
}
digits = DIGIT DIGIT* {
debug(`found digits:"${text()}"`)
return text()
}
intdigits = INTDIGIT DIGIT* {
debug(`found intdigits:"${text()}"`)
return text()
}
// ==== LEXICALS ====
OPENCURLY = '{'
CLOSECURLY = '}'
OPENPAREN = '('
CLOSEPAREN = ')'
LETTER = [a-z]
DIGIT = [0-9]
INTDIGIT = [1-9]
ASSIGN = '='
SEMI = ';'
SIGN = '+' / '-'
POINT = '.'
ADD = '+'
SUB = '-'
MULT = '*'
DIV = '/'
SP = [ \t]
/*
Filename: testParser.js
Author: raisch@gmail.com
Tester for parser created from grammar.pegjs
*/
const assert = require('assert')
const parser = require('./parser')
const target = '{float a =3;a*3+1;}'
let result
try {
result = parser.parse(target)
} catch (err) {
throw new Error('failed to parse: ' +JSON.stringify(err, null, 2))
}
assert(result === 10, `found unexpected result: ${result}`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment