Error handling: Make |?| return |null| on unsuccessful match

Before this commit, the |?| operator returned an empty string upon
unsuccessful match. This commit changes the returned value to |null|. It
also updates the PEG.js grammar and the example grammars, which used the
value returned by |?| quite often.

Returning |null| is possible because it no longer indicates a match
failure.

I expect that this change will simplify many real-world grammars, as an
empty string is almost never desirable as a return value (except some
lexer-level rules) and it is often translated into |null| or some other
value in action code.

Implements part of #198.
redux
David Majda 11 years ago
parent 57e806383c
commit 86769a6c5c

@ -291,7 +291,7 @@ expression as many times as possible.
#### *expression* ?
Try to match the expression. If the match succeeds, return its match result,
otherwise return an empty string.
otherwise return `null`.
#### & *expression*

@ -33,7 +33,7 @@ stylesheet
return {
type: "stylesheet",
charset: charset !== "" ? charset[1] : null,
charset: charset !== null ? charset[1] : null,
imports: importsConverted,
rules: rulesConverted
};
@ -44,7 +44,7 @@ import
return {
type: "import_rule",
href: href,
media: media !== "" ? media : []
media: media !== null ? media : []
};
}
@ -75,16 +75,16 @@ page
declarationsHead:declaration?
declarationsTail:(";" S* declaration?)*
"}" S* {
var declarations = declarationsHead !== "" ? [declarationsHead] : [];
var declarations = declarationsHead !== null ? [declarationsHead] : [];
for (var i = 0; i < declarationsTail.length; i++) {
if (declarationsTail[i][2] !== "") {
if (declarationsTail[i][2] !== null) {
declarations.push(declarationsTail[i][2]);
}
}
return {
type: "page_rule",
qualifier: qualifier !== "" ? qualifier : null,
qualifier: qualifier,
declarations: declarations
};
}
@ -119,9 +119,9 @@ ruleset
selectors.push(selectorsTail[i][2]);
}
var declarations = declarationsHead !== "" ? [declarationsHead] : [];
var declarations = declarationsHead !== null ? [declarationsHead] : [];
for (i = 0; i < declarationsTail.length; i++) {
if (declarationsTail[i][2] !== "") {
if (declarationsTail[i][2] !== null) {
declarations.push(declarationsTail[i][2]);
}
}
@ -196,8 +196,8 @@ attrib
return {
type: "attribute_selector",
attribute: attribute,
operator: operatorAndValue !== "" ? operatorAndValue[0] : null,
value: operatorAndValue !== "" ? operatorAndValue[2] : null
operator: operatorAndValue !== null ? operatorAndValue[0] : null,
value: operatorAndValue !== null ? operatorAndValue[2] : null
};
}
@ -208,7 +208,7 @@ pseudo
return {
type: "function",
name: name,
params: params !== "" ? [params[0]] : []
params: params !== null ? [params[0]] : []
};
}
/ IDENT
@ -230,7 +230,7 @@ declaration
type: "declaration",
property: property,
expression: expression,
important: important !== "" ? true : false
important: important !== null ? true : false
};
}
@ -262,7 +262,12 @@ term
/ FREQ S*
/ PERCENTAGE S*
/ NUMBER S*
) { return { type: "value", value: operator + value[0] }; }
) {
return {
type: "value",
value: (operator !== null ? operator : "") + value[0]
};
}
/ value:URI S* { return { type: "uri", value: value }; }
/ function
/ hexcolor
@ -331,7 +336,7 @@ comment
ident
= dash:"-"? nmstart:nmstart nmchars:nmchar* {
return dash + nmstart + nmchars.join("");
return (dash !== null ? dash : "") + nmstart + nmchars.join("");
}
name

@ -478,25 +478,25 @@ ArrayLiteral
= "[" __ elision:(Elision __)? "]" {
return {
type: "ArrayLiteral",
elements: elision !== "" ? elision[0] : []
elements: elision !== null ? elision[0] : []
};
}
/ "[" __ elements:ElementList __ elision:("," __ (Elision __)?)? "]" {
return {
type: "ArrayLiteral",
elements: elements.concat(elision !== "" && elision[2] !== "" ? elision[2][0] : [])
elements: elements.concat(elision !== null && elision[2] !== null ? elision[2][0] : [])
};
}
ElementList
= head:(
elision:(Elision __)? element:AssignmentExpression {
return (elision !== "" ? elision[0] : []).concat(element);
return (elision !== null ? elision[0] : []).concat(element);
}
)
tail:(
__ "," __ elision:(Elision __)? element:AssignmentExpression {
return (elision !== "" ? elision[0] : []).concat(element);
return (elision !== null ? elision[0] : []).concat(element);
}
)* {
var result = head;
@ -519,7 +519,7 @@ ObjectLiteral
= "{" __ properties:(PropertyNameAndValueList __ ("," __)?)? "}" {
return {
type: "ObjectLiteral",
properties: properties !== "" ? properties[0] : []
properties: properties !== null ? properties[0] : []
};
}
@ -663,7 +663,7 @@ CallExpression
Arguments
= "(" __ args:ArgumentList? __ ")" {
return args !== "" ? args : [];
return args !== null ? args : [];
}
ArgumentList
@ -1143,7 +1143,7 @@ Block
= "{" __ statements:(StatementList __)? "}" {
return {
type: "Block",
statements: statements !== "" ? statements[0] : []
statements: statements !== null ? statements[0] : []
};
}
@ -1187,7 +1187,7 @@ VariableDeclaration
return {
type: "VariableDeclaration",
name: name,
value: value !== "" ? value[1] : null
value: value !== null ? value[1] : null
};
}
@ -1196,7 +1196,7 @@ VariableDeclarationNoIn
return {
type: "VariableDeclaration",
name: name,
value: value !== "" ? value[1] : null
value: value !== null ? value[1] : null
};
}
@ -1221,7 +1221,7 @@ IfStatement
type: "IfStatement",
condition: condition,
ifStatement: ifStatement,
elseStatement: elseStatement !== "" ? elseStatement[3] : null
elseStatement: elseStatement !== null ? elseStatement[3] : null
};
}
@ -1272,9 +1272,9 @@ ForStatement
{
return {
type: "ForStatement",
initializer: initializer !== "" ? initializer : null,
test: test !== "" ? test : null,
counter: counter !== "" ? counter : null,
initializer: initializer,
test: test,
counter: counter,
statement: statement
};
}
@ -1358,10 +1358,10 @@ CaseBlock
before:CaseClauses?
defaultAndAfter:(__ DefaultClause __ CaseClauses?)? __
"}" {
var before = before !== "" ? before : [];
if (defaultAndAfter !== "") {
var before = before !== null ? before : [];
if (defaultAndAfter !== null) {
var defaultClause = defaultAndAfter[1];
var clausesAfter = defaultAndAfter[3] !== ""
var clausesAfter = defaultAndAfter[3] !== null
? defaultAndAfter[3]
: [];
} else {
@ -1386,7 +1386,7 @@ CaseClause
return {
type: "CaseClause",
selector: selector,
statements: statements !== "" ? statements[1] : []
statements: statements !== null ? statements[1] : []
};
}
@ -1394,7 +1394,7 @@ DefaultClause
= DefaultToken __ ":" statements:(__ StatementList)? {
return {
type: "DefaultClause",
statements: statements !== "" ? statements[1] : []
statements: statements !== null ? statements[1] : []
};
}
@ -1470,7 +1470,7 @@ FunctionDeclaration
return {
type: "Function",
name: name,
params: params !== "" ? params : [],
params: params !== null ? params : [],
elements: elements
};
}
@ -1481,8 +1481,8 @@ FunctionExpression
"{" __ elements:FunctionBody __ "}" {
return {
type: "Function",
name: name !== "" ? name : null,
params: params !== "" ? params : [],
name: name,
params: params !== null ? params : [],
elements: elements
};
}
@ -1497,13 +1497,13 @@ FormalParameterList
}
FunctionBody
= elements:SourceElements? { return elements !== "" ? elements : []; }
= elements:SourceElements? { return elements !== null ? elements : []; }
Program
= elements:SourceElements? {
return {
type: "Program",
elements: elements !== "" ? elements : []
elements: elements !== null ? elements : []
};
}

@ -466,7 +466,7 @@ module.exports = function(ast, options) {
},
optional: function(node, context) {
var emptyStringIndex = addConst('""');
var nullIndex = addConst('null');
return buildSequence(
generate(node.expression, {
@ -476,7 +476,7 @@ module.exports = function(ast, options) {
}),
buildCondition(
[op.IF_ERROR],
buildSequence([op.POP], [op.PUSH, emptyStringIndex]),
buildSequence([op.POP], [op.PUSH, nullIndex]),
[]
)
);

File diff suppressed because it is too large Load Diff

@ -477,7 +477,7 @@ describe("compiler pass |generateBytecode|", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'""',
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));

@ -621,7 +621,7 @@ describe("generated parser", function() {
it("matches correctly", function() {
var parser = PEG.buildParser('start = "a"?', options);
expect(parser).toParse("", "");
expect(parser).toParse("", null);
expect(parser).toParse("a", "a");
});
});
@ -1112,8 +1112,8 @@ describe("generated parser", function() {
*/
var parser = PEG.buildParser([
'S = &(A "c") a:"a"+ B:B !("a" / "b" / "c") { return a.join("") + B; }',
'A = a:"a" A:A? b:"b" { return a + A + b; }',
'B = b:"b" B:B? c:"c" { return b + B + c; }'
'A = a:"a" A:A? b:"b" { return [a, A, b].join(""); }',
'B = b:"b" B:B? c:"c" { return [b, B, c].join(""); }'
].join("\n"), options);
expect(parser).toParse("abc", "abc");

@ -6,7 +6,7 @@ grammar
= __ initializer:initializer? rules:rule+ {
return {
type: "grammar",
initializer: initializer !== "" ? initializer : null,
initializer: initializer,
rules: rules
};
}
@ -24,7 +24,7 @@ rule
return {
type: "rule",
name: name,
expression: displayName !== ""
expression: displayName !== null
? {
type: "named",
name: displayName,
@ -249,10 +249,10 @@ class "character class"
= "[" inverted:"^"? parts:(classCharacterRange / classCharacter)* "]" flags:"i"? __ {
var partsConverted = utils.map(parts, function(part) { return part.data; });
var rawText = "["
+ inverted
+ (inverted !== null ? inverted : "")
+ utils.map(parts, function(part) { return part.rawText; }).join("")
+ "]"
+ flags;
+ (flags !== null ? flags : "");
return {
type: "class",

Loading…
Cancel
Save