106 lines
2.7 KiB
Text
106 lines
2.7 KiB
Text
/*
|
|
* Canvas syntax parser
|
|
*
|
|
* You MUST use PegJS to generate a .js file to use this.
|
|
* Yes, technically you can load this and build the parser in real time, but this makes it annoying
|
|
* to share the grammar between the front end and back, so, you know, just generate the parser.
|
|
* You shouldn't be futzing around in the grammar very often anyway.
|
|
*/
|
|
|
|
{
|
|
function addMeta(node, text, { start: { offset: start }, end: { offset: end } }) {
|
|
if (!options.addMeta) return node;
|
|
return { node, text, start, end };
|
|
}
|
|
}
|
|
|
|
/* ----- Expressions ----- */
|
|
|
|
start
|
|
= expression
|
|
|
|
expression
|
|
= space? first:function rest:('|' space? fn:function { return fn; })* {
|
|
return addMeta({
|
|
type: 'expression',
|
|
chain: [first].concat(rest)
|
|
}, text(), location());
|
|
}
|
|
|
|
/* ----- Functions ----- */
|
|
|
|
function "function"
|
|
= name:identifier arg_list:arg_list {
|
|
return addMeta({
|
|
type: 'function',
|
|
function: name,
|
|
arguments: arg_list
|
|
}, text(), location());
|
|
}
|
|
|
|
/* ----- Arguments ----- */
|
|
|
|
argument_assignment
|
|
= name:identifier space? '=' space? value:argument {
|
|
return { name, value };
|
|
}
|
|
/ value:argument {
|
|
return { name: '_', value };
|
|
}
|
|
|
|
argument
|
|
= '$'? '{' expression:expression '}' { return expression; }
|
|
/ value:literal {
|
|
return addMeta(value, text(), location());
|
|
}
|
|
|
|
arg_list
|
|
= args:(space arg:argument_assignment { return arg; })* space? {
|
|
return args.reduce((accumulator, { name, value }) => ({
|
|
...accumulator,
|
|
[name]: (accumulator[name] || []).concat(value)
|
|
}), {});
|
|
}
|
|
|
|
/* ----- Core types ----- */
|
|
|
|
identifier
|
|
= name:[a-zA-Z0-9_-]+ {
|
|
return name.join('');
|
|
}
|
|
|
|
literal "literal"
|
|
= phrase
|
|
/ unquoted_string_or_number
|
|
|
|
phrase
|
|
= '"' chars:dq_char* '"' { return chars.join(''); } // double quoted string
|
|
/ "'" chars:sq_char* "'" { return chars.join(''); } // single quoted string
|
|
|
|
unquoted_string_or_number
|
|
// Make sure we're not matching the beginning of a search
|
|
= string:unquoted+ { // this also matches nulls, booleans, and numbers
|
|
var result = string.join('');
|
|
// Sort of hacky, but PEG doesn't have backtracking so
|
|
// a null/boolean/number rule is hard to read, and performs worse
|
|
if (result === 'null') return null;
|
|
if (result === 'true') return true;
|
|
if (result === 'false') return false;
|
|
if (isNaN(Number(result))) return result; // 5bears
|
|
return Number(result);
|
|
}
|
|
|
|
space
|
|
= [\ \t\r\n]+
|
|
|
|
unquoted
|
|
= "\\" sequence:([\"'(){}<>\[\]$`|=\ \t\n\r] / "\\") { return sequence; }
|
|
/ [^"'(){}<>\[\]$`|=\ \t\n\r]
|
|
|
|
dq_char
|
|
= "\\" sequence:('"' / "\\") { return sequence; }
|
|
/ [^"] // everything except "
|
|
|
|
sq_char
|
|
= "\\" sequence:("'" / "\\") { return sequence; }
|
|
/ [^'] // everything except '
|