Replace ":" after a rule name with "="

I'll introduce labelled expressions shortly and I want to use ":" as a
label-expression separator. This change avoids conflict between the two
meanings of ":". (What would e.g. "foo: 'bar'" mean?  Rule "foo"
matching string "bar", or string "bar" labelled "foo"?)
redux
David Majda 14 years ago
parent 7fdf0492c7
commit 698564a3c2

@ -3,15 +3,15 @@
* "2*(3+4)". The parser generated from this grammar then computes their value.
*/
start : additive
start = additive
additive : multiplicative "+" additive { return $1 + $3; }
additive = multiplicative "+" additive { return $1 + $3; }
/ multiplicative
multiplicative : primary "*" multiplicative { return $1 * $3; }
multiplicative = primary "*" multiplicative { return $1 * $3; }
/ primary
primary : integer
primary = integer
/ "(" additive ")" { return $2; }
integer "integer" : [0-9]+ { return parseInt($1.join(""), 10); }
integer "integer" = [0-9]+ { return parseInt($1.join(""), 10); }

@ -14,9 +14,9 @@
/* ===== Syntactical Elements ===== */
start: stylesheet comment* { return $1; }
start = stylesheet comment* { return $1; }
stylesheet:
stylesheet =
(CHARSET_SYM STRING ";")? (S / CDO / CDC)*
(import (CDO S* / CDC S*)*)*
((ruleset / media / page) (CDO S* / CDC S*)*)*
@ -39,7 +39,7 @@ stylesheet:
};
}
import: IMPORT_SYM S* (STRING / URI) S* media_list? ";" S* {
import = IMPORT_SYM S* (STRING / URI) S* media_list? ";" S* {
return {
type: "import_rule",
href: $3,
@ -47,7 +47,7 @@ import: IMPORT_SYM S* (STRING / URI) S* media_list? ";" S* {
};
}
media: MEDIA_SYM S* media_list "{" S* ruleset* "}" S* {
media = MEDIA_SYM S* media_list "{" S* ruleset* "}" S* {
return {
type: "media_rule",
media: $3,
@ -55,7 +55,7 @@ media: MEDIA_SYM S* media_list "{" S* ruleset* "}" S* {
};
}
media_list: medium ("," S* medium)* {
media_list = medium ("," S* medium)* {
var result = [$1];
for (var i = 0; i < $2.length; i++) {
result.push($2[i][2]);
@ -63,9 +63,9 @@ media_list: medium ("," S* medium)* {
return result;
}
medium: IDENT S* { return $1; }
medium = IDENT S* { return $1; }
page: PAGE_SYM S* pseudo_page? "{" S* declaration? (";" S* declaration?)* "}" S* {
page = PAGE_SYM S* pseudo_page? "{" S* declaration? (";" S* declaration?)* "}" S* {
var declarations = $6 !== "" ? [$6] : [];
for (var i = 0; i < $7.length; i++) {
if ($7[i][2] !== "") {
@ -80,21 +80,21 @@ page: PAGE_SYM S* pseudo_page? "{" S* declaration? (";" S* declaration?)* "}" S*
};
}
pseudo_page: ":" IDENT S* { return $2; }
pseudo_page = ":" IDENT S* { return $2; }
operator
: "/" S* { return $1; }
= "/" S* { return $1; }
/ "," S* { return $1; }
combinator
: "+" S* { return $1; }
= "+" S* { return $1; }
/ ">" S* { return $1; }
unary_operator: "+" / "-"
unary_operator = "+" / "-"
property: IDENT S* { return $1; }
property = IDENT S* { return $1; }
ruleset: selector ("," S* selector)* "{" S* declaration? (";" S* declaration?)* "}" S* {
ruleset = selector ("," S* selector)* "{" S* declaration? (";" S* declaration?)* "}" S* {
var selectors = [$1];
for (var i = 0; i < $2.length; i++) {
selectors.push($2[i][2]);
@ -115,7 +115,7 @@ ruleset: selector ("," S* selector)* "{" S* declaration? (";" S* declaration?)*
}
selector
: simple_selector S* combinator selector {
= simple_selector S* combinator selector {
return {
type: "selector",
combinator: $3,
@ -134,7 +134,7 @@ selector
/ simple_selector S* { return $1; }
simple_selector
: element_name
= element_name
(
HASH { return { type: "ID selector", id: $1.substr(1) }; }
/ class
@ -160,11 +160,11 @@ simple_selector
};
}
class: "." IDENT { return { type: "class_selector", "class": $2 }; }
class = "." IDENT { return { type: "class_selector", "class": $2 }; }
element_name: IDENT / '*'
element_name = IDENT / '*'
attrib: "[" S* IDENT S* (('=' / INCLUDES / DASHMATCH) S* (IDENT / STRING) S*)? "]" {
attrib = "[" S* IDENT S* (('=' / INCLUDES / DASHMATCH) S* (IDENT / STRING) S*)? "]" {
return {
type: "attribute_selector",
attribute: $3,
@ -173,7 +173,7 @@ attrib: "[" S* IDENT S* (('=' / INCLUDES / DASHMATCH) S* (IDENT / STRING) S*)? "
};
}
pseudo:
pseudo =
":"
(
FUNCTION S* (IDENT S*)? ")" {
@ -197,7 +197,7 @@ pseudo:
};
}
declaration: property ":" S* expr prio? {
declaration = property ":" S* expr prio? {
return {
type: "declaration",
property: $1,
@ -206,9 +206,9 @@ declaration: property ":" S* expr prio? {
};
}
prio: IMPORTANT_SYM S*
prio = IMPORTANT_SYM S*
expr: term (operator? term)* {
expr = term (operator? term)* {
var result = $1;
for (var i = 0; i < $2.length; i++) {
result = {
@ -222,7 +222,7 @@ expr: term (operator? term)* {
}
term
: unary_operator?
= unary_operator?
(
EMS S*
/ EXS S*
@ -239,7 +239,7 @@ term
/ STRING S* { return { type: "string", value: $1 }; }
/ IDENT S* { return { type: "ident", value: $1 }; }
function: FUNCTION S* expr ")" S* {
function = FUNCTION S* expr ")" S* {
return {
type: "function",
name: $1,
@ -247,160 +247,160 @@ function: FUNCTION S* expr ")" S* {
};
}
hexcolor: HASH S* { return { type: "hexcolor", value: $1}; }
hexcolor = HASH S* { return { type: "hexcolor", value: $1}; }
/* ===== Lexical Elements ===== */
/* Macros */
h: [0-9a-fA-F]
nonascii: [\x80-\xFF]
unicode: "\\" h h? h? h? h? h? ("\r\n" / [ \t\r\n\f])? {
return String.fromCharCode(parseInt("0x" + $2 + $3 + $4 + $5 + $6 + $7));
}
escape: unicode / "\\" [^\r\n\f0-9a-fA-F] { return $2; }
nmstart: [_a-zA-Z] / nonascii / escape
nmchar: [_a-zA-Z0-9-] / nonascii / escape
integer: [0-9]+ { return parseInt($1.join("")); }
float: [0-9]* "." [0-9]+ { return parseFloat($1.join("") + $2 + $3.join("")); }
string1: '"' ([^\n\r\f\\"] / "\\" nl { return $2 } / escape)* '"' { return $2.join(""); }
string2: "'" ([^\n\r\f\\'] / "\\" nl { return $2 } / escape)* "'" { return $2.join(""); }
comment: "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/"
ident: "-"? nmstart nmchar* { return $1 + $2 + $3.join(""); }
name: nmchar+ { return $1.join(""); }
num: float / integer
string: string1 / string2
url: ([!#$%&*-~] / nonascii / escape)* { return $1.join(""); }
s: [ \t\r\n\f]+
w: s?
nl: "\n" / "\r\n" / "\r" / "\f"
h = [0-9a-fA-F]
nonascii = [\x80-\xFF]
unicode = "\\" h h? h? h? h? h? ("\r\n" / [ \t\r\n\f])? {
return String.fromCharCode(parseInt("0x" + $2 + $3 + $4 + $5 + $6 + $7));
}
escape = unicode / "\\" [^\r\n\f0-9a-fA-F] { return $2; }
nmstart = [_a-zA-Z] / nonascii / escape
nmchar = [_a-zA-Z0-9-] / nonascii / escape
integer = [0-9]+ { return parseInt($1.join("")); }
float = [0-9]* "." [0-9]+ { return parseFloat($1.join("") + $2 + $3.join("")); }
string1 = '"' ([^\n\r\f\\"] / "\\" nl { return $2 } / escape)* '"' { return $2.join(""); }
string2 = "'" ([^\n\r\f\\'] / "\\" nl { return $2 } / escape)* "'" { return $2.join(""); }
comment = "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/"
ident = "-"? nmstart nmchar* { return $1 + $2 + $3.join(""); }
name = nmchar+ { return $1.join(""); }
num = float / integer
string = string1 / string2
url = ([!#$%&*-~] / nonascii / escape)* { return $1.join(""); }
s = [ \t\r\n\f]+
w = s?
nl = "\n" / "\r\n" / "\r" / "\f"
A
: [aA]
= [aA]
/ "\\" "0"? "0"? "0"? "0"? "41" ("\r\n" / [ \t\r\n\f])? { return "A"; }
/ "\\" "0"? "0"? "0"? "0"? "61" ("\r\n" / [ \t\r\n\f])? { return "a"; }
C
: [cC]
= [cC]
/ "\\" "0"? "0"? "0"? "0"? "43" ("\r\n" / [ \t\r\n\f])? { return "C"; }
/ "\\" "0"? "0"? "0"? "0"? "63" ("\r\n" / [ \t\r\n\f])? { return "c"; }
D
: [dD]
= [dD]
/ "\\" "0"? "0"? "0"? "0"? "44" ("\r\n" / [ \t\r\n\f])? { return "D"; }
/ "\\" "0"? "0"? "0"? "0"? "64" ("\r\n" / [ \t\r\n\f])? { return "d"; }
E
: [eE]
= [eE]
/ "\\" "0"? "0"? "0"? "0"? "45" ("\r\n" / [ \t\r\n\f])? { return "E"; }
/ "\\" "0"? "0"? "0"? "0"? "65" ("\r\n" / [ \t\r\n\f])? { return "e"; }
G
: [gG]
= [gG]
/ "\\" "0"? "0"? "0"? "0"? "47" ("\r\n" / [ \t\r\n\f])? { return "G"; }
/ "\\" "0"? "0"? "0"? "0"? "67" ("\r\n" / [ \t\r\n\f])? { return "g"; }
/ "\\" [gG] { return $2; }
H
: [hH]
= [hH]
/ "\\" "0"? "0"? "0"? "0"? "48" ("\r\n" / [ \t\r\n\f])? { return "H"; }
/ "\\" "0"? "0"? "0"? "0"? "68" ("\r\n" / [ \t\r\n\f])? { return "h"; }
/ "\\" [hH] { return $2; }
I
: [iI]
= [iI]
/ "\\" "0"? "0"? "0"? "0"? "49" ("\r\n" / [ \t\r\n\f])? { return "I"; }
/ "\\" "0"? "0"? "0"? "0"? "69" ("\r\n" / [ \t\r\n\f])? { return "i"; }
/ "\\" [iI] { return $2; }
K
: [kK]
= [kK]
/ "\\" "0"? "0"? "0"? "0"? "4" [bB] ("\r\n" / [ \t\r\n\f])? { return "K"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [bB] ("\r\n" / [ \t\r\n\f])? { return "k"; }
/ "\\" [kK] { return $2; }
L
: [lL]
= [lL]
/ "\\" "0"? "0"? "0"? "0"? "4" [cC] ("\r\n" / [ \t\r\n\f])? { return "L"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [cC] ("\r\n" / [ \t\r\n\f])? { return "l"; }
/ "\\" [lL] { return $2; }
M
: [mM]
= [mM]
/ "\\" "0"? "0"? "0"? "0"? "4" [dD] ("\r\n" / [ \t\r\n\f])? { return "M"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [dD] ("\r\n" / [ \t\r\n\f])? { return "m"; }
/ "\\" [mM] { return $2; }
N
: [nN]
= [nN]
/ "\\" "0"? "0"? "0"? "0"? "4" [eE] ("\r\n" / [ \t\r\n\f])? { return "N"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [eE] ("\r\n" / [ \t\r\n\f])? { return "n"; }
/ "\\" [nN] { return $2; }
O
: [oO]
= [oO]
/ "\\" "0"? "0"? "0"? "0"? "4" [fF] ("\r\n" / [ \t\r\n\f])? { return "O"; }
/ "\\" "0"? "0"? "0"? "0"? "6" [fF] ("\r\n" / [ \t\r\n\f])? { return "o"; }
/ "\\" [oO] { return $2; }
P
: [pP]
= [pP]
/ "\\" "0"? "0"? "0"? "0"? "50" ("\r\n" / [ \t\r\n\f])? { return "P"; }
/ "\\" "0"? "0"? "0"? "0"? "70" ("\r\n" / [ \t\r\n\f])? { return "p"; }
/ "\\" [pP] { return $2; }
R
: [rR]
= [rR]
/ "\\" "0"? "0"? "0"? "0"? "52" ("\r\n" / [ \t\r\n\f])? { return "R"; }
/ "\\" "0"? "0"? "0"? "0"? "72" ("\r\n" / [ \t\r\n\f])? { return "r"; }
/ "\\" [rR] { return $2; }
S_
: [sS]
= [sS]
/ "\\" "0"? "0"? "0"? "0"? "53" ("\r\n" / [ \t\r\n\f])? { return "S"; }
/ "\\" "0"? "0"? "0"? "0"? "73" ("\r\n" / [ \t\r\n\f])? { return "s"; }
/ "\\" [sS] { return $2; }
T
: [tT]
= [tT]
/ "\\" "0"? "0"? "0"? "0"? "54" ("\r\n" / [ \t\r\n\f])? { return "T"; }
/ "\\" "0"? "0"? "0"? "0"? "74" ("\r\n" / [ \t\r\n\f])? { return "t"; }
/ "\\" [tT] { return $2; }
U
: [uU]
= [uU]
/ "\\" "0"? "0"? "0"? "0"? "55" ("\r\n" / [ \t\r\n\f])? { return "U"; }
/ "\\" "0"? "0"? "0"? "0"? "75" ("\r\n" / [ \t\r\n\f])? { return "u"; }
/ "\\" [uU] { return $2; }
X
: [xX]
= [xX]
/ "\\" "0"? "0"? "0"? "0"? "58" ("\r\n" / [ \t\r\n\f])? { return "X"; }
/ "\\" "0"? "0"? "0"? "0"? "78" ("\r\n" / [ \t\r\n\f])? { return "x"; }
/ "\\" [xX] { return $2; }
Z
: [zZ]
= [zZ]
/ "\\" "0"? "0"? "0"? "0"? "5" [aA] ("\r\n" / [ \t\r\n\f])? { return "Z"; }
/ "\\" "0"? "0"? "0"? "0"? "7" [aA] ("\r\n" / [ \t\r\n\f])? { return "z"; }
/ "\\" [zZ] { return $2; }
/* Tokens */
S "whitespace" : comment* s
S "whitespace" = comment* s
CDO "<!--" : comment* "<!--"
CDC "-->" : comment* "-->"
INCLUDES "~=" : comment* "~="
DASHMATCH "|=" : comment* "|="
CDO "<!--" = comment* "<!--"
CDC "-->" = comment* "-->"
INCLUDES "~=" = comment* "~="
DASHMATCH "|=" = comment* "|="
STRING "string" : comment* string { return $2; }
STRING "string" = comment* string { return $2; }
IDENT "identifier" : comment* ident { return $2; }
IDENT "identifier" = comment* ident { return $2; }
HASH "hash" : comment* "#" name { return $2 + $3; }
HASH "hash" = comment* "#" name { return $2 + $3; }
IMPORT_SYM "@import" : comment* "@" I M P O R T
PAGE_SYM "@page" : comment* "@" P A G E
MEDIA_SYM "@media" : comment* "@" M E D I A
CHARSET_SYM "@charset" : comment* "@charset "
IMPORT_SYM "@import" = comment* "@" I M P O R T
PAGE_SYM "@page" = comment* "@" P A G E
MEDIA_SYM "@media" = comment* "@" M E D I A
CHARSET_SYM "@charset" = comment* "@charset "
/* Note: We replace "w" with "s" here to avoid infinite recursion. */
IMPORTANT_SYM "!important" : comment* "!" (s / comment)* I M P O R T A N T { return "!important"; }
IMPORTANT_SYM "!important" = comment* "!" (s / comment)* I M P O R T A N T { return "!important"; }
EMS "length" : comment* num E M { return $2 + $3 + $4; }
EXS "length" : comment* num E X { return $2 + $3 + $4; }
LENGTH "length" : comment* num (P X / C M / M M / I N / P T / P C) { return $2 + $3.join(""); }
ANGLE "angle" : comment* num (D E G / R A D / G R A D) { return $2 + $3.join(""); }
TIME "time" : comment* num (M S_ { return $1 + $2; } / S_) { return $2 + $3; }
FREQ "frequency" : comment* num (H Z / K H Z) { return $2 + $3.join(""); }
DIMENSION "dimension" : comment* num ident { return $2 + $3; }
PERCENTAGE "percentage" : comment* num "%" { return $2 + $3; }
NUMBER "number" : comment* num { return $2; }
EMS "length" = comment* num E M { return $2 + $3 + $4; }
EXS "length" = comment* num E X { return $2 + $3 + $4; }
LENGTH "length" = comment* num (P X / C M / M M / I N / P T / P C) { return $2 + $3.join(""); }
ANGLE "angle" = comment* num (D E G / R A D / G R A D) { return $2 + $3.join(""); }
TIME "time" = comment* num (M S_ { return $1 + $2; } / S_) { return $2 + $3; }
FREQ "frequency" = comment* num (H Z / K H Z) { return $2 + $3.join(""); }
DIMENSION "dimension" = comment* num ident { return $2 + $3; }
PERCENTAGE "percentage" = comment* num "%" { return $2 + $3; }
NUMBER "number" = comment* num { return $2; }
URI "uri" : comment* U R L "(" w (string / url) w ")" { return $7; }
URI "uri" = comment* U R L "(" w (string / url) w ")" { return $7; }
FUNCTION "function" : comment* ident "(" { return $2; }
FUNCTION "function" = comment* ident "(" { return $2; }

File diff suppressed because one or more lines are too long

@ -2,13 +2,13 @@
/* ===== Syntactical Elements ===== */
start: _ object { return $2; }
start = _ object { return $2; }
object
: "{" _ "}" _ { return {}; }
= "{" _ "}" _ { return {}; }
/ "{" _ members "}" _ { return $3; }
members: pair ("," _ pair)* {
members = pair ("," _ pair)* {
var result = {};
result[$1[0]] = $1[1];
for (var i = 0; i < $2.length; i++) {
@ -17,13 +17,13 @@ members: pair ("," _ pair)* {
return result;
}
pair: string ":" _ value { return [$1, $4]; }
pair = string ":" _ value { return [$1, $4]; }
array
: "[" _ "]" _ { return []; }
= "[" _ "]" _ { return []; }
/ "[" _ elements "]" _ { return $3; }
elements: value ("," _ value)* {
elements = value ("," _ value)* {
var result = [$1];
for (var i = 0; i < $2.length; i++) {
result.push($2[i][2]);
@ -32,7 +32,7 @@ elements: value ("," _ value)* {
}
value
: string
= string
/ number
/ object
/ array
@ -44,14 +44,14 @@ value
/* ===== Lexical Elements ===== */
string "string"
: '"' '"' _ { return ""; }
= '"' '"' _ { return ""; }
/ '"' chars '"' _ { return $2; }
chars: char+ { return $1.join(""); }
chars = char+ { return $1.join(""); }
char
// In the original JSON grammar: "any-Unicode-character-except-"-or-\-or-control-character"
: [^"\\\0-\x1F\x7f]
= [^"\\\0-\x1F\x7f]
/ '\\"' { return '"'; }
/ "\\\\" { return "\\"; }
/ "\\/" { return "/"; }
@ -65,24 +65,24 @@ char
}
number "number"
: int frac exp _ { return parseFloat($1 + $2 + $3); }
= int frac exp _ { return parseFloat($1 + $2 + $3); }
/ int frac _ { return parseFloat($1 + $2); }
/ int exp _ { return parseFloat($1 + $2); }
/ int _ { return parseFloat($1); }
int
: digit19 digits { return $1 + $2; }
= digit19 digits { return $1 + $2; }
/ digit
/ "-" digit19 digits { return $1 + $2 + $3; }
/ "-" digit { return $1 + $2; }
frac: "." digits { return $1 + $2; }
frac = "." digits { return $1 + $2; }
exp: e digits { return $1 + $2; }
exp = e digits { return $1 + $2; }
digits: digit+ { return $1.join(""); }
digits = digit+ { return $1.join(""); }
e: [eE] [+-]? { return $1 + $2; }
e = [eE] [+-]? { return $1 + $2; }
/*
* The following rules are not present in the original JSON gramar, but they are
@ -91,16 +91,16 @@ e: [eE] [+-]? { return $1 + $2; }
* FIXME: Define them according to ECMA-262, 5th ed.
*/
digit: [0-9]
digit = [0-9]
digit19: [1-9]
digit19 = [1-9]
hexDigit: [0-9a-fA-F]
hexDigit = [0-9a-fA-F]
/* ===== Whitespace ===== */
_ "whitespace": whitespace*
_ "whitespace" = whitespace*
// Whitespace is undefined in the original JSON grammar, so I assume a simple
// conventional definition consistent with ECMA-262, 5th ed.
whitespace: [ \t\n\r]
whitespace = [ \t\n\r]

@ -135,7 +135,7 @@ PEG.grammarParser = (function(){
};
}
if (result8 !== null) {
var result9 = this._parse_colon(context);
var result9 = this._parse_equals(context);
if (result9 !== null) {
var result10 = this._parse_choice(context);
if (result10 !== null) {
@ -535,7 +535,7 @@ PEG.grammarParser = (function(){
};
}
if (result69 !== null) {
var result70 = this._parse_colon(context);
var result70 = this._parse_equals(context);
if (result70 !== null) {
var result68 = [result69, result70];
} else {
@ -819,8 +819,8 @@ PEG.grammarParser = (function(){
return result88;
},
_parse_colon: function(context) {
var cacheKey = "colon" + '@' + this._pos;
_parse_equals: function(context) {
var cacheKey = "equals" + '@' + this._pos;
var cachedResult = this._cache[cacheKey];
if (cachedResult) {
this._pos = cachedResult.nextPos;
@ -831,13 +831,13 @@ PEG.grammarParser = (function(){
var savedPos16 = this._pos;
if (this._input.substr(this._pos, 1) === ":") {
var result91 = ":";
if (this._input.substr(this._pos, 1) === "=") {
var result91 = "=";
this._pos += 1;
} else {
var result91 = null;
if (context.reportMatchFailures) {
this._matchFailed(this._quoteString(":"));
this._matchFailed(this._quoteString("="));
}
}
if (result91 !== null) {

@ -1,10 +1,10 @@
grammar: __ rule+ {
grammar = __ rule+ {
var result = {};
PEG.ArrayUtils.each($2, function(rule) { result[rule.name] = rule; });
return result;
}
rule: identifier (literal / "") colon expression {
rule = identifier (literal / "") equals expression {
return {
type: "rule",
name: $1,
@ -13,9 +13,9 @@ rule: identifier (literal / "") colon expression {
};
}
expression: choice
expression = choice
choice: sequence (slash sequence)* {
choice = sequence (slash sequence)* {
if ($2.length > 0) {
var alternatives = [$1].concat(PEG.ArrayUtils.map(
$2,
@ -31,7 +31,7 @@ choice: sequence (slash sequence)* {
}
sequence
: prefixed* action {
= prefixed* action {
var expression = $1.length != 1
? {
type: "sequence",
@ -54,43 +54,43 @@ sequence
}
prefixed
: and suffixed { return { type: "and_predicate", expression: $2 }; }
= and suffixed { return { type: "and_predicate", expression: $2 }; }
/ not suffixed { return { type: "not_predicate", expression: $2 }; }
/ suffixed
suffixed
: primary question { return { type: "optional", expression: $1}; }
= primary question { return { type: "optional", expression: $1}; }
/ primary star { return { type: "zero_or_more", expression: $1}; }
/ primary plus { return { type: "one_or_more", expression: $1}; }
/ primary
primary
: identifier !(( literal / "") colon) { return { type: "rule_ref", name: $1 }; }
/ literal { return { type: "literal", value: $1 }; }
/ dot { return { type: "any" }; }
= identifier !(( literal / "") equals) { return { type: "rule_ref", name: $1 }; }
/ literal { return { type: "literal", value: $1 }; }
/ dot { return { type: "any" }; }
/ class
/ lparen expression rparen { return $2; }
/ lparen expression rparen { return $2; }
/* "Lexical" elements */
action "action": braced __ { return $1.substr(1, $1.length - 2); }
action "action" = braced __ { return $1.substr(1, $1.length - 2); }
braced: "{" (braced / nonBraceCharacter)* "}" { return $1 + $2.join("") + $3; }
braced = "{" (braced / nonBraceCharacter)* "}" { return $1 + $2.join("") + $3; }
nonBraceCharacters: nonBraceCharacter+ { return $1.join(""); }
nonBraceCharacters = nonBraceCharacter+ { return $1.join(""); }
nonBraceCharacter: [^{}]
colon: ":" __ { return $1; }
slash: "/" __ { return $1; }
and: "&" __ { return $1; }
not: "!" __ { return $1; }
question: "?" __ { return $1; }
star: "*" __ { return $1; }
plus: "+" __ { return $1; }
lparen: "(" __ { return $1; }
rparen: ")" __ { return $1; }
dot: "." __ { return $1; }
equals = "=" __ { return $1; }
slash = "/" __ { return $1; }
and = "&" __ { return $1; }
not = "!" __ { return $1; }
question = "?" __ { return $1; }
star = "*" __ { return $1; }
plus = "+" __ { return $1; }
lparen = "(" __ { return $1; }
rparen = ")" __ { return $1; }
dot = "." __ { return $1; }
/*
* Modelled after ECMA-262, 5th ed., 7.6, but much simplified:
@ -107,7 +107,7 @@ dot: "." __ { return $1; }
* The simplifications were made just to make the implementation little bit
* easier, there is no "philosophical" reason behind them.
*/
identifier "identifier": (letter / "_" / "$") (letter / digit / "_" / "$")* __ {
identifier "identifier" = (letter / "_" / "$") (letter / digit / "_" / "$")* __ {
return $1 + $2.join("");
}
@ -115,33 +115,33 @@ identifier "identifier": (letter / "_" / "$") (letter / digit / "_" / "$")* __ {
* Modelled after ECMA-262, 5th ed., 7.8.4. (syntax & semantics, rules only
* vaguely).
*/
literal "literal": (doubleQuotedLiteral / singleQuotedLiteral) __ { return $1; }
literal "literal" = (doubleQuotedLiteral / singleQuotedLiteral) __ { return $1; }
doubleQuotedLiteral: '"' doubleQuotedCharacter* '"' { return $2.join(""); }
doubleQuotedLiteral = '"' doubleQuotedCharacter* '"' { return $2.join(""); }
doubleQuotedCharacter
: simpleDoubleQuotedCharacter
= simpleDoubleQuotedCharacter
/ simpleEscapeSequence
/ zeroEscapeSequence
/ hexEscapeSequence
/ unicodeEscapeSequence
/ eolEscapeSequence
simpleDoubleQuotedCharacter: !('"' / "\\" / eolChar) . { return $2; }
simpleDoubleQuotedCharacter = !('"' / "\\" / eolChar) . { return $2; }
singleQuotedLiteral: "'" singleQuotedCharacter* "'" { return $2.join(""); }
singleQuotedLiteral = "'" singleQuotedCharacter* "'" { return $2.join(""); }
singleQuotedCharacter
: simpleSingleQuotedCharacter
= simpleSingleQuotedCharacter
/ simpleEscapeSequence
/ zeroEscapeSequence
/ hexEscapeSequence
/ unicodeEscapeSequence
/ eolEscapeSequence
simpleSingleQuotedCharacter: !("'" / "\\" / eolChar) . { return $2; }
simpleSingleQuotedCharacter = !("'" / "\\" / eolChar) . { return $2; }
class "character class": "[" "^"? (classCharacterRange / classCharacter)* "]" __ {
class "character class" = "[" "^"? (classCharacterRange / classCharacter)* "]" __ {
parts = PEG.ArrayUtils.map($3, function(part) { return part.data; });
rawText = "["
+ $2
@ -157,7 +157,7 @@ class "character class": "[" "^"? (classCharacterRange / classCharacter)* "]" __
};
}
classCharacterRange: classCharacter "-" classCharacter {
classCharacterRange = classCharacter "-" classCharacter {
if ($1.data.charCodeAt(0) > $3.data.charCodeAt(0)) {
throw new this.SyntaxError(
"Invalid character range: " + $1.rawText + "-" + $3.rawText + "."
@ -171,7 +171,7 @@ classCharacterRange: classCharacter "-" classCharacter {
}
}
classCharacter: bracketDelimitedCharacter {
classCharacter = bracketDelimitedCharacter {
return {
data: $1,
// FIXME: Get the raw text from the input directly.
@ -180,16 +180,16 @@ classCharacter: bracketDelimitedCharacter {
}
bracketDelimitedCharacter
: simpleBracketDelimitedCharacter
= simpleBracketDelimitedCharacter
/ simpleEscapeSequence
/ zeroEscapeSequence
/ hexEscapeSequence
/ unicodeEscapeSequence
/ eolEscapeSequence
simpleBracketDelimitedCharacter: !("]" / "\\" / eolChar) . { return $2; }
simpleBracketDelimitedCharacter = !("]" / "\\" / eolChar) . { return $2; }
simpleEscapeSequence: "\\" !(digit / "x" / "u" / eolChar) . {
simpleEscapeSequence = "\\" !(digit / "x" / "u" / eolChar) . {
return $3
.replace("b", "\b")
.replace("f", "\f")
@ -199,41 +199,41 @@ simpleEscapeSequence: "\\" !(digit / "x" / "u" / eolChar) . {
.replace("v", "\x0B") // IE does not recognize "\v".
}
zeroEscapeSequence: "\\0" !digit { return "\0"; }
zeroEscapeSequence = "\\0" !digit { return "\0"; }
hexEscapeSequence: "\\x" hexDigit hexDigit {
hexEscapeSequence = "\\x" hexDigit hexDigit {
return String.fromCharCode(parseInt("0x" + $2 + $3));
}
unicodeEscapeSequence: "\\u" hexDigit hexDigit hexDigit hexDigit {
unicodeEscapeSequence = "\\u" hexDigit hexDigit hexDigit hexDigit {
return String.fromCharCode(parseInt("0x" + $2 + $3 + $4 + $5));
}
eolEscapeSequence: "\\" eol { return $2; }
eolEscapeSequence = "\\" eol { return $2; }
digit: [0-9]
digit = [0-9]
hexDigit: [0-9a-fA-F]
hexDigit = [0-9a-fA-F]
letter: lowerCaseLetter / upperCaseLetter
letter = lowerCaseLetter / upperCaseLetter
lowerCaseLetter: [a-z]
lowerCaseLetter = [a-z]
upperCaseLetter: [A-Z]
upperCaseLetter = [A-Z]
__: (whitespace / eol / comment)*
__ = (whitespace / eol / comment)*
/* Modelled after ECMA-262, 5th ed., 7.4. */
comment "comment": singleLineComment / multiLineComment
comment "comment" = singleLineComment / multiLineComment
singleLineComment: "//" (!eolChar .)*
singleLineComment = "//" (!eolChar .)*
multiLineComment: "/*" (!"*/" .)* "*/"
multiLineComment = "/*" (!"*/" .)* "*/"
/* Modelled after ECMA-262, 5th ed., 7.3. */
eol "end of line": "\n" / "\r\n" / "\r" / "\u2028" / "\u2029"
eol "end of line" = "\n" / "\r\n" / "\r" / "\u2028" / "\u2029"
eolChar: [\n\r\u2028\u2029]
eolChar = [\n\r\u2028\u2029]
/* Modelled after ECMA-262, 5th ed., 7.2. */
whitespace "whitespace": [ \t\v\f\u00A0\uFEFF\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
whitespace "whitespace" = [ \t\v\f\u00A0\uFEFF\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]

@ -193,7 +193,7 @@ test("buildParser reports syntax errors in the grammar", function() {
test("buildParser reports missing start rule", function() {
throws(
function() { PEG.buildParser('notStart: "abcd"'); },
function() { PEG.buildParser('notStart = "abcd"'); },
PEG.GrammarError,
{ message: "Missing \"start\" rule." }
);
@ -201,17 +201,17 @@ test("buildParser reports missing start rule", function() {
test("buildParser reports missing referenced rules", function() {
var grammars = [
'start: missing',
'start: missing / "a" / "b"',
'start: "a" / "b" / missing',
'start: missing "a" "b"',
'start: "a" "b" missing',
'start: &missing',
'start: !missing',
'start: missing?',
'start: missing*',
'start: missing+',
'start: missing { }'
'start = missing',
'start = missing / "a" / "b"',
'start = "a" / "b" / missing',
'start = missing "a" "b"',
'start = "a" "b" missing',
'start = &missing',
'start = !missing',
'start = missing?',
'start = missing*',
'start = missing+',
'start = missing { }'
];
PEG.ArrayUtils.each(grammars, function(grammar) {
@ -226,19 +226,19 @@ test("buildParser reports missing referenced rules", function() {
test("buildParser reports left recursion", function() {
var grammars = [
/* Direct */
'start: start',
'start: start / "a" / "b"',
'start: "a" / "b" / start',
'start: start "a" "b"',
'start: &start',
'start: !start',
'start: start?',
'start: start*',
'start: start+',
'start: start { }',
'start = start',
'start = start / "a" / "b"',
'start = "a" / "b" / start',
'start = start "a" "b"',
'start = &start',
'start = !start',
'start = start?',
'start = start*',
'start = start+',
'start = start { }',
/* Indirect */
'start: stop\nstop: start'
'start = stop\nstop = start'
];
PEG.ArrayUtils.each(grammars, function(grammar) {
@ -251,7 +251,7 @@ test("buildParser reports left recursion", function() {
});
test("buildParser allows custom start rule", function() {
var parser = PEG.buildParser('s: "abcd"', "s");
var parser = PEG.buildParser('s = "abcd"', "s");
parses(parser, "abcd", "abcd");
});
@ -260,7 +260,7 @@ test("buildParser allows custom start rule", function() {
module("Generated Parser");
test("choices", function() {
var parser = PEG.buildParser('start: "a" / "b" / "c"');
var parser = PEG.buildParser('start = "a" / "b" / "c"');
parses(parser, "a", "a");
parses(parser, "b", "b");
parses(parser, "c", "c");
@ -270,11 +270,11 @@ test("choices", function() {
});
test("sequences", function() {
var emptySequenceParser = PEG.buildParser('start: ');
var emptySequenceParser = PEG.buildParser('start = ');
parses(emptySequenceParser, "", []);
doesNotParse(emptySequenceParser, "abc");
var nonEmptySequenceParser = PEG.buildParser('start: "a" "b" "c"');
var nonEmptySequenceParser = PEG.buildParser('start = "a" "b" "c"');
parses(nonEmptySequenceParser, "abc", ["a", "b", "c"]);
doesNotParse(nonEmptySequenceParser, "");
doesNotParse(nonEmptySequenceParser, "ab");
@ -285,12 +285,12 @@ test("sequences", function() {
* Test that the parsing position returns after unsuccessful parsing of a
* sequence.
*/
var posTestParser = PEG.buildParser('start: ("a" "b") / "a"');
var posTestParser = PEG.buildParser('start = ("a" "b") / "a"');
parses(posTestParser, "a", "a");
});
test("and predicate", function() {
var parser = PEG.buildParser('start: "a" &"b" "b"');
var parser = PEG.buildParser('start = "a" &"b" "b"');
parses(parser, "ab", ["a", "", "b"]);
doesNotParse(parser, "ac");
@ -301,7 +301,7 @@ test("and predicate", function() {
});
test("not predicate", function() {
var parser = PEG.buildParser('start: "a" !"b"');
var parser = PEG.buildParser('start = "a" !"b"');
parses(parser, "a", ["a", ""]);
doesNotParse(parser, "ab");
@ -309,25 +309,25 @@ test("not predicate", function() {
* Test that the parsing position returns after successful parsing of a
* predicate.
*/
var posTestParser = PEG.buildParser('start: "a" !"b" "c"');
var posTestParser = PEG.buildParser('start = "a" !"b" "c"');
parses(posTestParser, "ac", ["a", "", "c"]);
});
test("optional expressions", function() {
var parser = PEG.buildParser('start: "a"?');
var parser = PEG.buildParser('start = "a"?');
parses(parser, "", "");
parses(parser, "a", "a");
});
test("zero or more expressions", function() {
var parser = PEG.buildParser('start: "a"*');
var parser = PEG.buildParser('start = "a"*');
parses(parser, "", []);
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("one or more expressions", function() {
var parser = PEG.buildParser('start: "a"+');
var parser = PEG.buildParser('start = "a"+');
doesNotParse(parser, "");
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
@ -335,46 +335,46 @@ test("one or more expressions", function() {
test("actions", function() {
var singleMatchParser = PEG.buildParser(
'start: "a" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
'start = "a" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
);
parses(singleMatchParser, "a", "A");
var multiMatchParser = PEG.buildParser(
'start: "a" "b" "c" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
'start = "a" "b" "c" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }'
);
parses(multiMatchParser, "abc", "ABC");
var innerMatchParser = PEG.buildParser(
'start: "a" ("b" "c" "d" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }) "e"'
'start = "a" ("b" "c" "d" { return Array.prototype.slice.call(arguments).join("").toUpperCase(); }) "e"'
);
parses(innerMatchParser, "abcde", ["a", "BCD", "e"]);
/* Test that the action is not called when its expression does not match. */
var notAMatchParser = PEG.buildParser(
'start: "a" { ok(false, "action got called when it should not be"); }'
'start = "a" { ok(false, "action got called when it should not be"); }'
);
doesNotParse(notAMatchParser, "b");
var variablesParser = PEG.buildParser([
'start: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" {',
' return [$1, $2, $3, $4, $5, $6, $7, $8, $9, $10].join("").toUpperCase();',
' }'
'start = "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" {',
' return [$1, $2, $3, $4, $5, $6, $7, $8, $9, $10].join("").toUpperCase();',
' }'
].join("\n"));
parses(variablesParser, "abcdefghij", "ABCDEFGHIJ");
});
test("rule references", function() {
var parser = PEG.buildParser([
'start: static / dynamic',
'static: "C" / "C++" / "Java" / "C#"',
'dynamic: "Ruby" / "Python" / "JavaScript"'
'start = static / dynamic',
'static = "C" / "C++" / "Java" / "C#"',
'dynamic = "Ruby" / "Python" / "JavaScript"'
].join("\n"));
parses(parser, "Java", "Java");
parses(parser, "Python", "Python");
});
test("literals", function() {
var parser = PEG.buildParser('start: "abcd"');
var parser = PEG.buildParser('start = "abcd"');
parses(parser, "abcd", "abcd");
doesNotParse(parser, "");
doesNotParse(parser, "abc");
@ -385,12 +385,12 @@ test("literals", function() {
* Test that the parsing position moves forward after successful parsing of
* a literal.
*/
var posTestParser = PEG.buildParser('start: "a" "b"');
var posTestParser = PEG.buildParser('start = "a" "b"');
parses(posTestParser, "ab", ["a", "b"]);
});
test("anys", function() {
var parser = PEG.buildParser('start: .');
var parser = PEG.buildParser('start = .');
parses(parser, "a", "a");
doesNotParse(parser, "");
doesNotParse(parser, "ab");
@ -399,17 +399,17 @@ test("anys", function() {
* Test that the parsing position moves forward after successful parsing of
* an any.
*/
var posTestParser = PEG.buildParser('start: . .');
var posTestParser = PEG.buildParser('start = . .');
parses(posTestParser, "ab", ["a", "b"]);
});
test("classes", function() {
var emptyClassParser = PEG.buildParser('start: []');
var emptyClassParser = PEG.buildParser('start = []');
doesNotParse(emptyClassParser, "");
doesNotParse(emptyClassParser, "a");
doesNotParse(emptyClassParser, "ab");
var nonEmptyClassParser = PEG.buildParser('start: [ab-d]');
var nonEmptyClassParser = PEG.buildParser('start = [ab-d]');
parses(nonEmptyClassParser, "a", "a");
parses(nonEmptyClassParser, "b", "b");
parses(nonEmptyClassParser, "c", "c");
@ -417,12 +417,12 @@ test("classes", function() {
doesNotParse(nonEmptyClassParser, "");
doesNotParse(nonEmptyClassParser, "ab");
var invertedEmptyClassParser = PEG.buildParser('start: [^]');
var invertedEmptyClassParser = PEG.buildParser('start = [^]');
doesNotParse(invertedEmptyClassParser, "");
parses(invertedEmptyClassParser, "a", "a");
doesNotParse(invertedEmptyClassParser, "ab");
var invertedNonEmptyClassParser = PEG.buildParser('start: [^ab-d]');
var invertedNonEmptyClassParser = PEG.buildParser('start = [^ab-d]');
doesNotParse(invertedNonEmptyClassParser, "a", "a");
doesNotParse(invertedNonEmptyClassParser, "b", "b");
doesNotParse(invertedNonEmptyClassParser, "c", "c");
@ -434,7 +434,7 @@ test("classes", function() {
* Test that the parsing position moves forward after successful parsing of
* a class.
*/
var posTestParser = PEG.buildParser('start: [ab-d] [ab-d]');
var posTestParser = PEG.buildParser('start = [ab-d] [ab-d]');
parses(posTestParser, "ab", ["a", "b"]);
});
@ -443,23 +443,23 @@ test("cache", function() {
* Should trigger a codepath where the cache is used (for the "a" rule).
*/
var parser = PEG.buildParser([
'start: (a b) / (a c)',
'a: "a"',
'b: "b"',
'c: "c"'
'start = (a b) / (a c)',
'a = "a"',
'b = "b"',
'c = "c"'
].join("\n"));
parses(parser, "ac", ["a", "c"]);
});
test("indempotence", function() {
var parser1 = PEG.buildParser('start: "abcd"');
var parser2 = PEG.buildParser('start: "abcd"');
var parser1 = PEG.buildParser('start = "abcd"');
var parser2 = PEG.buildParser('start = "abcd"');
strictEqual(parser1.toSource(), parser2.toSource());
});
test("error messages", function() {
var literalParser = PEG.buildParser('start: "abcd"');
var literalParser = PEG.buildParser('start = "abcd"');
doesNotParseWithMessage(
literalParser,
"",
@ -476,34 +476,34 @@ test("error messages", function() {
'Expected end of input but "e" found.'
);
var classParser = PEG.buildParser('start: [a-d]');
var classParser = PEG.buildParser('start = [a-d]');
doesNotParseWithMessage(
classParser,
"",
'Expected [a-d] but end of input found.'
);
var negativeClassParser = PEG.buildParser('start: [^a-d]');
var negativeClassParser = PEG.buildParser('start = [^a-d]');
doesNotParseWithMessage(
negativeClassParser,
"",
'Expected [^a-d] but end of input found.'
);
var anyParser = PEG.buildParser('start: .');
var anyParser = PEG.buildParser('start = .');
doesNotParseWithMessage(
anyParser,
"",
'Expected any character but end of input found.'
);
var namedRuleWithLiteralParser = PEG.buildParser('start "digit": [0-9]');
var namedRuleWithLiteralParser = PEG.buildParser('start "digit" = [0-9]');
doesNotParseWithMessage(
namedRuleWithLiteralParser,
"a",
'Expected digit but "a" found.'
);
var namedRuleWithAnyParser = PEG.buildParser('start "whatever": .');
var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .');
doesNotParseWithMessage(
namedRuleWithAnyParser,
"",
@ -511,8 +511,8 @@ test("error messages", function() {
);
var namedRuleWithNamedRuleParser = PEG.buildParser([
'start "digits": digit+',
'digit "digit": [0-9]'
'start "digits" = digit+',
'digit "digit" = [0-9]'
].join("\n"));
doesNotParseWithMessage(
namedRuleWithNamedRuleParser,
@ -520,49 +520,49 @@ test("error messages", function() {
'Expected digits but end of input found.'
);
var choiceParser1 = PEG.buildParser('start: "a" / "b" / "c"');
var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"');
doesNotParseWithMessage(
choiceParser1,
"def",
'Expected "a", "b" or "c" but "d" found.'
);
var choiceParser2 = PEG.buildParser('start: "a" "b" "c" / "a"');
var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"');
doesNotParseWithMessage(
choiceParser2,
"abd",
'Expected "c" but "d" found.'
);
var notPredicateParser = PEG.buildParser('start: !"a" "b"');
var notPredicateParser = PEG.buildParser('start = !"a" "b"');
doesNotParseWithMessage(
notPredicateParser,
"c",
'Expected "b" but "c" found.'
);
var andPredicateParser = PEG.buildParser('start: &"a" [a-b]');
var andPredicateParser = PEG.buildParser('start = &"a" [a-b]');
doesNotParseWithMessage(
andPredicateParser,
"c",
'Expected end of input but "c" found.'
);
var emptyParser = PEG.buildParser('start: ');
var emptyParser = PEG.buildParser('start = ');
doesNotParseWithMessage(
emptyParser,
"something",
'Expected end of input but "s" found.'
);
var duplicateErrorParser = PEG.buildParser('start: "a" / "a"');
var duplicateErrorParser = PEG.buildParser('start = "a" / "a"');
doesNotParseWithMessage(
duplicateErrorParser,
"",
'Expected "a" but end of input found.'
);
var unsortedErrorsParser = PEG.buildParser('start: "b" / "a"');
var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"');
doesNotParseWithMessage(
unsortedErrorsParser,
"",
@ -572,9 +572,9 @@ test("error messages", function() {
test("error positions", function() {
var parser = PEG.buildParser([
'start: line (("\\r" / "\\n" / "\\u2028" / "\\u2029")+ line)*',
'line: digit (" "+ digit)*',
'digit: [0-9]+ { return $1.join(""); }'
'start = line (("\\r" / "\\n" / "\\u2028" / "\\u2029")+ line)*',
'line = digit (" "+ digit)*',
'digit = [0-9]+ { return $1.join(""); }'
].join("\n"));
doesNotParseWithPos(parser, "a", 1, 1);
@ -603,9 +603,9 @@ test("arithmetics", function() {
* Expr Sum
*/
var parser = PEG.buildParser([
'Value : [0-9]+ { return parseInt($1.join("")); }',
'Value = [0-9]+ { return parseInt($1.join("")); }',
' / "(" Expr ")" { return $2; }',
'Product : Value (("*" / "/") Value)* {',
'Product = Value (("*" / "/") Value)* {',
' var result = $1;',
' for (var i = 0; i < $2.length; i++) {',
' if ($2[i][0] == "*") { result *= $2[i][1]; }',
@ -613,7 +613,7 @@ test("arithmetics", function() {
' }',
' return result;',
' }',
'Sum : Product (("+" / "-") Product)* {',
'Sum = Product (("+" / "-") Product)* {',
' var result = $1;',
' for (var i = 0; i < $2.length; i++) {',
' if ($2[i][0] == "+") { result += $2[i][1]; }',
@ -621,7 +621,7 @@ test("arithmetics", function() {
' }',
' return result;',
' }',
'Expr : Sum'
'Expr = Sum'
].join("\n"), "Expr");
/* Test "value" rule. */
@ -659,9 +659,9 @@ test("non-context-free language", function() {
* B b B? c
*/
var parser = PEG.buildParser([
'S: &(A "c") "a"+ B !("a" / "b" / "c") { return $2.join("") + $3; }',
'A: "a" A? "b" { return $1 + $2 + $3; }',
'B: "b" B? "c" { return $1 + $2 + $3; }',
'S = &(A "c") "a"+ B !("a" / "b" / "c") { return $2.join("") + $3; }',
'A = "a" A? "b" { return $1 + $2 + $3; }',
'B = "b" B? "c" { return $1 + $2 + $3; }',
].join("\n"), "S");
parses(parser, "abc", "abc");
@ -683,12 +683,12 @@ test("nested comments", function() {
* Z any single character
*/
var parser = PEG.buildParser([
'Begin : "(*"',
'End : "*)"',
'C : Begin N* End { return $1 + $2.join("") + $3; }',
'N : C',
'Begin = "(*"',
'End = "*)"',
'C = Begin N* End { return $1 + $2.join("") + $3; }',
'N = C',
' / (!Begin !End Z) { return $3; }',
'Z : .'
'Z = .'
].join("\n"), "C");
parses(parser, "(**)", "(**)");

@ -138,9 +138,9 @@ function actionGrammar(action) {
/* Canonical grammar is "a: \"abcd\";\nb: \"efgh\";\nc: \"ijkl\";". */
test("parses grammar", function() {
grammarParserParses('a: "abcd"', { a: rule("a", null, literalAbcd) });
grammarParserParses('a = "abcd"', { a: rule("a", null, literalAbcd) });
grammarParserParses(
'a: "abcd"\nb: "efgh"\nc: "ijkl"',
'a = "abcd"\nb = "efgh"\nc = "ijkl"',
{
a: rule("a", null, literalAbcd),
b: rule("b", null, literalEfgh),
@ -152,11 +152,11 @@ test("parses grammar", function() {
/* Canonical rule is "a: \"abcd\"". */
test("parses rule", function() {
grammarParserParses(
'start: "abcd" / "efgh" / "ijkl"',
'start = "abcd" / "efgh" / "ijkl"',
oneRuleGrammar(choiceLiterals)
);
grammarParserParses(
'start "start rule": "abcd" / "efgh" / "ijkl"',
'start "start rule" = "abcd" / "efgh" / "ijkl"',
{
start: rule("start", "start rule", choiceLiterals)
}
@ -166,7 +166,7 @@ test("parses rule", function() {
/* Canonical expression is "\"abcd\" / \"efgh\" / \"ijkl\"". */
test("parses expression", function() {
grammarParserParses(
'start: "abcd" / "efgh" / "ijkl"',
'start = "abcd" / "efgh" / "ijkl"',
oneRuleGrammar(choiceLiterals)
);
});
@ -174,11 +174,11 @@ test("parses expression", function() {
/* Canonical choice is "\"abcd\" / \"efgh\" / \"ijkl\"". */
test("parses choice", function() {
grammarParserParses(
'start: "abcd" "efgh" "ijkl"',
'start = "abcd" "efgh" "ijkl"',
oneRuleGrammar(sequenceLiterals)
);
grammarParserParses(
'start: "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl"',
'start = "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl" / "abcd" "efgh" "ijkl"',
oneRuleGrammar(choice([
sequenceLiterals,
sequenceLiterals,
@ -190,341 +190,342 @@ test("parses choice", function() {
/* Canonical sequence is "\"abcd\" \"efgh\" \"ijkl\"". */
test("parses sequence", function() {
grammarParserParses(
'start: { code }',
'start = { code }',
oneRuleGrammar(action(sequenceEmpty, " code "))
);
grammarParserParses(
'start: !"abcd" { code }',
'start = !"abcd" { code }',
oneRuleGrammar(action(notAbcd, " code "))
);
grammarParserParses(
'start: !"abcd" !"efgh" !"ijkl" { code }',
'start = !"abcd" !"efgh" !"ijkl" { code }',
oneRuleGrammar(action(sequenceNots, " code "))
);
grammarParserParses('start: ', oneRuleGrammar(sequenceEmpty));
grammarParserParses('start: !"abcd"', oneRuleGrammar(notAbcd));
grammarParserParses('start = ', oneRuleGrammar(sequenceEmpty));
grammarParserParses('start = !"abcd"', oneRuleGrammar(notAbcd));
grammarParserParses(
'start: !"abcd" !"efgh" !"ijkl"',
'start = !"abcd" !"efgh" !"ijkl"',
oneRuleGrammar(sequenceNots)
);
});
/* Canonical prefixed is "!\"abcd\"". */
test("parses prefixed", function() {
grammarParserParses('start: &"abcd"?', oneRuleGrammar(andPredicate(optionalLiteral)));
grammarParserParses('start: !"abcd"?', oneRuleGrammar(notPredicate(optionalLiteral)));
grammarParserParses('start: "abcd"?', oneRuleGrammar(optionalLiteral));
grammarParserParses('start = &"abcd"?', oneRuleGrammar(andPredicate(optionalLiteral)));
grammarParserParses('start = !"abcd"?', oneRuleGrammar(notPredicate(optionalLiteral)));
grammarParserParses('start = "abcd"?', oneRuleGrammar(optionalLiteral));
});
/* Canonical suffixed is "\"abcd\"?". */
test("parses suffixed", function() {
grammarParserParses('start: "abcd"?', oneRuleGrammar(optionalLiteral));
grammarParserParses('start: "abcd"*', oneRuleGrammar(zeroOrMore(literalAbcd)));
grammarParserParses('start: "abcd"+', oneRuleGrammar(oneOrMore(literalAbcd)));
grammarParserParses('start: "abcd"', literalGrammar("abcd"));
grammarParserParses('start = "abcd"?', oneRuleGrammar(optionalLiteral));
grammarParserParses('start = "abcd"*', oneRuleGrammar(zeroOrMore(literalAbcd)));
grammarParserParses('start = "abcd"+', oneRuleGrammar(oneOrMore(literalAbcd)));
grammarParserParses('start = "abcd"', literalGrammar("abcd"));
});
/* Canonical primary is "\"abcd\"". */
test("parses primary", function() {
grammarParserParses('start: a', identifierGrammar("a"));
grammarParserParses('start: "abcd"', literalGrammar("abcd"));
grammarParserParses('start: .', anyGrammar);
grammarParserParses('start: [a-d]', classGrammar(false, [["a", "d"]], "[a-d]"));
grammarParserParses('start: ("abcd")', literalGrammar("abcd"));
grammarParserParses('start = a', identifierGrammar("a"));
grammarParserParses('start = "abcd"', literalGrammar("abcd"));
grammarParserParses('start = .', anyGrammar);
grammarParserParses('start = [a-d]', classGrammar(false, [["a", "d"]], "[a-d]"));
grammarParserParses('start = ("abcd")', literalGrammar("abcd"));
});
/* Canonical action is "{ code }". */
test("parses action", function() {
grammarParserParses('start: "a" { code }', actionGrammar(" code "));
grammarParserParses('start = "a" { code }', actionGrammar(" code "));
});
/* Canonical braced is "{ code }". */
test("parses braced", function() {
grammarParserParses('start: "a" {}', actionGrammar(""));
grammarParserParses('start: "a" {a}', actionGrammar("a"));
grammarParserParses('start: "a" {{a}}', actionGrammar("{a}"));
grammarParserParses('start: "a" {aaa}', actionGrammar("aaa"));
grammarParserParses('start = "a" {}', actionGrammar(""));
grammarParserParses('start = "a" {a}', actionGrammar("a"));
grammarParserParses('start = "a" {{a}}', actionGrammar("{a}"));
grammarParserParses('start = "a" {aaa}', actionGrammar("aaa"));
});
/* Trivial character rules are not tested. */
/* Canonical identifier is "a". */
test("parses identifier", function() {
grammarParserParses('start: a', identifierGrammar("a"));
grammarParserParses('start: z', identifierGrammar("z"));
grammarParserParses('start: A', identifierGrammar("A"));
grammarParserParses('start: Z', identifierGrammar("Z"));
grammarParserParses('start: _', identifierGrammar("_"));
grammarParserParses('start: $', identifierGrammar("$"));
grammarParserParses('start: aa', identifierGrammar("aa"));
grammarParserParses('start: az', identifierGrammar("az"));
grammarParserParses('start: aA', identifierGrammar("aA"));
grammarParserParses('start: aZ', identifierGrammar("aZ"));
grammarParserParses('start: a0', identifierGrammar("a0"));
grammarParserParses('start: a9', identifierGrammar("a9"));
grammarParserParses('start: a_', identifierGrammar("a_"));
grammarParserParses('start: a$', identifierGrammar("a$"));
grammarParserParses('start: abcd', identifierGrammar("abcd"));
grammarParserParses('start: a\n', identifierGrammar("a"));
grammarParserParses('start = a', identifierGrammar("a"));
grammarParserParses('start = z', identifierGrammar("z"));
grammarParserParses('start = A', identifierGrammar("A"));
grammarParserParses('start = Z', identifierGrammar("Z"));
grammarParserParses('start = _', identifierGrammar("_"));