Access Control Panel (ACS) Parser / Interpreter with PEG.js
Introduction
I have been working on the creation of A ccess C ontrol S tring (or S ystem) (ACS) line Parser / Interpreter with PEG.js. ACS strings are commonly used in bulletin systems (BBS) to check access rights to specific areas of the board. For example, see Revised ACS documentation .
Sample ACS strings
Below are some simplified lines and their English translations for illustration:
// Has GM123 OR NOT GM456
GM123|!GM456
// Has GM123 OR NOT (GM456 AND GM789) (note: AND is implied in this grammar if not specified)
GM123|!(GM456GM789)
// Has GM123 AND NOT GM456 OR has GM789
GM123!GM456|GM789
// Has GM1 OR (NOT GM2 OR GM3)
GM1|(!GM2|GM3)
What i am trying to achieve
What I would like to do here is parsing and interpreting (or "running") the ACS string and ultimately the final boolean.
Grammar so far
Below is the PEG.js grammar I am with so far. Note that the ACS strings themselves are a little more complex than the examples above (say GM ['abc', 'def'], for example), but I think it's pretty clear up to this point.
{
function checkAccessSingle(acsName, arg) {
return true;
}
function checkAccessMulti(acsName, args, anyMatch) {
return true;
}
function makeNot(not, x) {
return not ? !x : x;
}
}
start
= acsString
whitespaceChar
= ' '
ws
= whitespaceChar*
lineTerminatorChar
= [\r\n\u2028\u2029]
decimalDigit
= [0-9]
integer
= decimalDigit+ { return parseInt(text(), 10); }
asciiPrintableChar
= [ -~]
singleAsciiStringChar
= !("'") asciiPrintableChar { return text(); }
doubleAsciiStringChar
= !('"') asciiPrintableChar { return text(); }
nonEmptyStringLiteral
= "'" chars:singleAsciiStringChar+ "'" { return chars.join(''); }
/ '"' chars:doubleAsciiStringChar+ '"' { return chars.join(''); }
AND
= '&'
OR
= '|'
NOT
= '!'
acsName
= n:([A-Z][A-Z]) { return n.join(''); }
acsArg
= nonEmptyStringLiteral
/ integer
acsArgs
= first:acsArg rest:(ws ',' ws a:acsArg { return a; })* {
var args = [ first ];
for(var i = 0; i < rest.length; ++i) {
args.push(rest[i]);
}
return args;
}
singleAcsCheck
= not:NOT? n:acsName a:acsArg* {
return function() {
makeNot(not, checkAccessSingle(n, a));
}
}
/ not:NOT? n:acsName '[' a:acsArgs ']' {
return function() {
return makeNot(not, checkAccessMulti(n, a, false));
}
}
/ not:NOT? n:acsName '{' a:acsArgs '}' {
return function() {
return makeNot(not, checkAccessMulti(n, a, true));
}
}
multiAcsCheck
= singleAcsCheck+
acsString = multiAcsCheck
Where do I need help
The main issue I'm running into (if not others I haven't encountered yet!) Is handling priority with () and OR clauses. It might be something simple, but I have been working on it for a few days and have been a little behind. Again, what I'm ultimately trying to achieve here is feed into an ACS string and output the final boolean result. Various ACS commands (like "GM" in the above example) have to make method calls that do the dirty work.
source to share
Here's a quick demo that parses your example correctly and shows how you could evaluate expressions on the fly (which return a boolean value):
{
function check(name, value) {
// Dummy implementation: returns true when the name starts with 'A'
return name.charAt(0) == 'A';
}
}
start
= expr
expr
= or_expr
or_expr
= left:and_expr '|' right:expr { return left || right; }
/ and_expr
and_expr
= left:not_expr '&'? right:expr { return left && right; }
/ not_expr
not_expr
= '!' value:atom { return !value; }
/ atom
atom
= acs_check
/ '(' value:expr ')' { return value; }
acs_check
= n:name a:arg { return check(n, a); }
name
= c:([A-Z][A-Z]) { return c.join(''); }
arg
= c:[A-Z]+ { return c.join(''); }
/ d:[0-9]+ { return d.join(''); }
source to share