Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse arithmetic #14

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env node

var fs = require('fs')
var pegjs = require('pegjs')
var overrideAction = require('pegjs-override-action')
Expand All @@ -6,10 +8,12 @@ var input = __dirname + '/grammar.pegjs'
var output = __dirname + '/parser.js'
var overrides = __dirname + '/overrides.js'

var trace = false;
if (require.main === module) {
if (process.argv[2] == '-w') {
watch()
} else {
if (process.argv[2] == '-t') trace = true;
console.log(getSource())
}
}
Expand All @@ -19,6 +23,7 @@ function getSource () {
var grammar = fs.readFileSync(input, 'utf8')
var parserSource = pegjs.buildParser(grammar, {
output: "source",
trace: trace,
allowedStartRules: [
'script',
'command',
Expand Down
116 changes: 113 additions & 3 deletions grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ statementList
space* last:controlOperator? spaceNL*

statement
= statement:( subshell
= statement:(arithmeticStatement
/ subshell
/ bashExtensions
/ command
/ variableAssignment
Expand All @@ -20,6 +21,9 @@ statement
chainedStatement
= operator:('&&' / '||') spaceNL* statement:statement

arithmeticStatement "an arithmetic statement"
= "((" space* expression:arithmetic space* "))"

subshell "a subshell"
= "(" space* statements:statementList space* ")"

Expand Down Expand Up @@ -85,6 +89,7 @@ concatenation "concatenation of strings and/or variables"
/ bareword
/ environmentVariable
/ variableSubstitution
/ arithmeticSubstitution
/ commandSubstitution
/ singleQuote
/ doubleQuote
Expand Down Expand Up @@ -123,7 +128,8 @@ doubleQuoteMeta
= '"' / '$' / '`'

expandsInQuotes
= commandSubstitution
= arithmeticSubstitution
/ commandSubstitution
/ environmentVariable
/ variableSubstitution

Expand All @@ -140,6 +146,9 @@ commandSubstitution
parenCommandSubstitution
= '$(' commands:statementList ')'

arithmeticSubstitution
= '$((' expression:arithmetic '))'

backQuote
= '`' input:backQuoteChar+ '`'

Expand Down Expand Up @@ -200,8 +209,109 @@ keyword
)
( spaceNL+ / EOF )

// http://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
arithmetic "an arithmetic expression"
= expression:aComma { return expression }

aComma "a sequence of arithmetic expressions"
= head:aAssign tail:( spaceNL* "," spaceNL* expr:aAssign { return expr } )*

aAssign "an arithmetic assignment"
= left:aCond spaceNL* operator:( "=" !"=" / "*=" / "/=" / "%=" / "+=" / "-=" / "<<=" / ">>=" / "&=" / "^=" / "|=" ) spaceNL* right:aAssign
/ other:aCond

aCond "an arithmetic conditional expression"
= test:aLogicalOr spaceNL* "?" spaceNL* consequent:aAssign spaceNL* ":" spaceNL* alternate:aAssign
/ other:aLogicalOr

aLogicalOr "an arithmetic logical or"
= head:aLogicalAnd tail:(spaceNL* op:"||" spaceNL* node:aLogicalAnd { return {op: op, node: node} })*

aLogicalAnd "an arithmetic logical and"
= head:aBitwiseOr tail:(spaceNL* op:"&&" spaceNL* node:aBitwiseOr { return {op: op, node: node} })*

aBitwiseOr
= head:aBitwiseXor tail:(spaceNL* op:"|" ![|=] spaceNL* node:aBitwiseXor { return {op: op, node: node} })*

aBitwiseXor
= head:aBitwiseAnd tail:(spaceNL* op:"^" !"=" spaceNL* node:aBitwiseAnd { return {op: op, node: node} })*

aBitwiseAnd
= head:aEquality tail:(spaceNL* op:"&" ![&=] spaceNL* node:aEquality { return {op: op, node: node} })*

aEquality
= head:aComparison tail:(spaceNL* op:( "==" / "!=" ) spaceNL* node:aComparison { return {op: op, node: node} })*

aComparison
= head:aBitwiseShift tail:(spaceNL* op:( "<=" / ">=" / (v:"<" !"<" { return v }) / (v:">" !">" { return v }) ) spaceNL* node:aBitwiseShift { return {op: op, node: node} })*

aBitwiseShift
= head:aAddSubtract tail:(spaceNL* op:( "<<" / ">>" ) !"=" spaceNL* node:aAddSubtract { return {op: op, node: node} })*

aAddSubtract
= head:aMultDivModulo tail:(spaceNL* op:( "+" / "-" ) !"=" spaceNL* node:aMultDivModulo { return {op: op, node: node} })*

aMultDivModulo
= head:aExponent tail:(spaceNL* op:( (v:"*" !"*" { return v }) / "/" / "%") !"=" spaceNL* node:aExponent { return {op: op, node: node} })*

aExponent
= head:aNegation tail:(spaceNL* op:"**" spaceNL* node:aNegation { return {op: op, node: node} })*

aNegation
= operator:( "!" / "~" ) spaceNL* argument:aNegation
/ other:aUnary

aUnary
= operator:( (op:"+" ![+=]) { return op } / (op:"-" ![-=]) { return op } ) spaceNL* argument:aUnary
/ other:aPreIncDec

aPreIncDec
= operator:( "++" / "--" ) spaceNL* argument:aPreIncDec
/ other:aPostIncDec

aPostIncDec
= argument:aMemberExpr operators:(spaceNL* op:( "++" / "--" ) { return op })+
/ other:aMemberExpr


aMemberExpr
= head:aParenExpr tail:(spaceNL* "[" property:arithmetic "]" { return property })*

aParenExpr
= '(' spaceNL* value:arithmetic spaceNL* ')' { return value }
/ other:aLiteral { return other }

aBareword "arithmetic variable"
= !'#' name:aBarewordChar+

aBarewordChar
= '\\' chr:aBarewordMeta { return chr }
/ !aBarewordMeta chr:. { return chr }

aBarewordMeta = [$"';&<>\n()\[\]*?|`:+^\- ]

aConcatenation "concatenation of strings and/or variables"
= pieces:( aBareword
/ environmentVariable
/ variableSubstitution
/ arithmeticSubstitution
/ commandSubstitution
/ singleQuote
/ doubleQuote
)+

aLiteral
= val:aNumber { return val }
/ val:aConcatenation { return val }

aNumber
= "0" [xX] digits:[0-9a-fA-Z]+
/ base:[0-9]+ "#" digits:[0-9a-zA-Z]+
/ "0" digits:[0-7]+
/ digits:[0-9]+

continuationStart
= &( keyword / '"' / "'" / '`' / "$(" / "${" ) .*
= &( keyword / '"' / "'" / '`' / "$(" / "$((" / "${" ) .*

EOF
= !.
159 changes: 159 additions & 0 deletions overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ exports.initializer = [
type: 'concatenation',
pieces: result
}
},
// create a nested tree from a list of same-precedence operators
function buildTree(head, tail, createNode) {
var result = head;
for (var i = 0, l = tail.length; i < l; i++) {
result = createNode(result, tail[i]);
}
return result;
}
].join('\n')

Expand Down Expand Up @@ -97,6 +105,13 @@ rules.statementList = function (first, tail, last) {
}
}

rules.arithmeticStatement = function (expression) {
return {
type: 'arithmeticStatement',
expression: expression,
}
}

rules.subshell = function (statements) {
return {
type: 'subshell',
Expand Down Expand Up @@ -267,6 +282,13 @@ rules.parenCommandSubstitution = function (commands) {
}
}

rules.arithmeticSubstitution = function (expression) {
return {
type: 'arithmeticSubstitution',
expression: expression
};
}

rules.backQuote = function (input) {
return { type: 'commandSubstitution', commands: parse(input.join('')) }
}
Expand Down Expand Up @@ -304,3 +326,140 @@ rules.redirectFd = function (fd, op, filename) {
filename: filename
}
}

rules.aBareword = function (name) {
// TODO: check if array
return {type: 'variable', name: name.join('')}
}

rules.aConcatenation = function (pieces) {
// TODO: if it's an array, nest it
return flattenConcatenation(pieces)
}

rules.aNumber = [
function (digits) {
return {type: 'number', value: parseInt(digits.join(''), 16)}
},
function (base, digits) {
return {type: 'number', value: parseInt(digits.join(''), parseInt(base.join(''), 10))}
},
function (digits) {
return {type: 'number', value: parseInt(digits.join(''), 8)}
},
function (digits) {
return {type: 'number', value: parseInt(digits.join(''), 10)}
}
]

rules.aComma = function (head, tail) {
if (tail.length) {
return {
type: 'arithmeticSequence',
expressions: [head].concat(tail)
}
}
return head
}

function other(other) { return other }

rules.aCond = [
function (test, consequent, alternate) {
return {
type: 'arithmeticConditional',
test: test,
consequent: consequent,
alternate: alternate
}
}, other
]

rules.aAssign = [
function (left, operator, right) {
return {
type: 'arithmeticAssignment',
left: left,
operator: operator,
right: right
}
}, other
]

rules.aLogicalOr =
rules.aLogicalAnd =
function (head, tail) {
return buildTree(head, tail, function (child, current) {
return {
type: 'arithmeticLogical',
operator: current.op,
left: child,
right: current.node
}
});
}

rules.aBitwiseOr =
rules.aBitwiseXor =
rules.aBitwiseAnd =
rules.aEquality =
rules.aComparison =
rules.aBitwiseShift =
rules.aAddSubtract =
rules.aMultDivModulo =
rules.aExponent =
function (head, tail) {
return buildTree(head, tail, function (child, current) {
return {
type: 'arithmeticBinary',
operator: current.op,
left: child,
right: current.node
}
});
}

rules.aNegation = rules.aUnary =
[
function (operator, argument) {
return {
type: 'arithmeticUnary',
operator: operator,
argument: argument
}
}, other
]

rules.aPreIncDec = [
function (operator, argument) {
return {
type: 'arithmeticUpdate',
operator: operator,
argument: argument,
prefix: true
}
}, other
]

rules.aPostIncDec = [
function (argument, operators) {
return buildTree(argument, operators, function (node, operator) {
return {
type: 'arithmeticUpdate',
operator: operator,
argument: node,
prefix: false
}
});
}, other
]

rules.aMemberExpr = function (head, tail) {
return buildTree(head, tail, function (child, current) {
return {
type: 'arithmeticMemberExpression',
array: child,
property: current
}
});
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"scripts": {
"test": "tape tests/*.js",
"watch": "npm run prepublish && node build.js -w",
"watch": "npm run prepublish && echo watching... && node build.js -w",
"prepublish": "node build.js > parser.js"
},
"testling": {
Expand All @@ -35,10 +35,10 @@
}
},
"devDependencies": {
"markdown-code-blocks": "0.0.1",
"pegjs": "~0.9.0",
"pegjs-override-action": "0.2.3",
"tape": "^4.5.1",
"markdown-code-blocks": "0.0.1",
"xtend": "~2.1.1"
},
"repository": {
Expand Down
Loading