[wip] Add grammar-level parser for Rockstar

This commit is contained in:
Nilay Majorwar 2022-02-13 19:21:32 +05:30
parent fb331e1194
commit a7b7879fb3
11 changed files with 7021 additions and 4 deletions

View File

@ -201,7 +201,7 @@ Cast 1046 into result (result = "Ж" - Unicode 1046)
- Greater than: `is higher/greater/bigger/stronger than`
- Less than: `is lower/less/smaller/weaker than`
- Greater or equal: `is as high/great/big/strong as`
- Less than: `is as low/little/small/weak as`
- Less or equal: `is as low/little/small/weak as`
### Logical operators
@ -376,7 +376,7 @@ Declaring functions:
- Function declaration syntax: `<fn-name> takes/wants <arg list>`
- Argument list is separated by `,`, `and`, `&`, `n`.
- Functions always have return value. Keyword: `return, give, give, send`.
- Functions always have return value. Keyword: `return, give, send`.
- Return syntax: `<keyword> <var> [back]` (`back` is optional)
```

View File

@ -1,4 +1,13 @@
import { DocumentRange } from "../../types";
import { Line } from "../parser/grammar-types";
/** Type of props passed to renderer */
export type RS = {
value: number;
};
/** A single step of the Rockstar AST */
export type ASTStep = {
op: Line;
range: DocumentRange;
};

View File

@ -0,0 +1,83 @@
import { ParseError } from "../../worker-errors";
import { ASTStep } from "../common/types";
import * as D from "./grammar-types";
/** Single item of the scope block stack */
type BlockStackItem =
| { type: "if"; line: number }
| { type: "loop"; line: number }
| { type: "function"; line: number };
/** Stack to track the current scopes */
type BlockStack = BlockStackItem[];
/**
* Check that the given line is valid to be the next line of
* an inline if-statement. The next line of an inline if-statement
* can only be an empty line or an else-clause.
*
* @param lineNum Line number of the line in code
* @param line The line in parsed form
*/
const validateInlineIfNextLine = (lineNum: number, line: D.Line) => {
if (line.type !== "blank" && line.type !== "else") {
throw new ParseError(
"Expected else clause or blank line after inline if-statement",
{ line: lineNum }
);
}
};
/**
* Build the executable AST from the parsed program, by filling in
* the jump addresses of control flow statements and validating blocks.
*
* @param program Program in parsed form
* @returns AST of the program
*/
export const buildAST = (program: D.Program): ASTStep[] => {
const lines = program.list;
const stack: BlockStack = [];
const ast: ASTStep[] = [];
for (let i = 0; i < lines.length; ++i) {
const line = lines[i];
// Function declaration or loop start: push block onto stack
if (line.type === "function_decl" || line.type === "loop") {
stack.push({ type: "function", line: i });
}
if (line.type === "if") {
stack.push({ type: "if", line: i });
// Check if the next line is valid to follow an inline if-statement
if (line.statement && i + 1 < lines.length)
validateInlineIfNextLine(i + 1, lines[i + 1]);
}
if (line.type === "else") {
// Pop if-item from block stack
const ifItem = stack.pop();
if (!ifItem || ifItem.type !== "if")
throw new ParseError("Unexpected else clause", { line: i });
// Fill in the jump address for the corresponding if-statement
const ifLine = ast[ifItem.line];
if (!ifLine || ifLine.op.type !== "if") throw new Error("Bad stack item");
ifLine.op.jump = i;
}
if (line.type === "blank") {
// Pop block from stack
const blockItem = stack.pop();
if (blockItem) {
// Fill in the jump address for the corresponding block
const blockLine = ast[blockItem.line];
if (!blockLine || blockLine.op.type !== blockItem.type)
throw new Error("Bad stack item");
// TODO: Check block type and act accordingly
}
}
}
return ast;
};

View File

@ -0,0 +1,16 @@
// @ts-check
const fs = require("fs");
const path = require("path");
const peggy = require("peggy");
const grammarPath = path.resolve(__dirname, "grammar.peg");
const grammar = fs.readFileSync(grammarPath).toString();
const parser = peggy.generate(grammar, {
output: "source",
format: "commonjs",
allowedStartRules: ["program"],
});
const outputPath = path.resolve(__dirname, "parser.out.js");
fs.writeFileSync(outputPath, parser);

View File

@ -0,0 +1,245 @@
/** AST of the entire program */
export type Program = { list: Line[] };
/** A single line of the program */
export type Line = Statement | { type: "blank" };
/** A single (non-blank-line) statement */
export type Statement =
| Break
| Continue
| FunctionDecl
| FunctionCall
| FunctionReturn
| Loop
| If
| Else
| Operation
| Expression;
/*********************************
FLOW CONTROL
*********************************/
/** If-statement, optionally containing inline statement */
export type If = {
type: "if";
condition: Expression;
statement?: Statement | null;
/** Filled by AST builder: address to block ender */
jump?: number;
};
/** Else-statement, optionally containing inline statement */
export type Else = {
type: "else";
statement: Statement | null;
};
/** Loop-starting statement */
export type Loop = {
type: "loop";
condition: Expression;
};
/** Loop-breaking statement */
export type Break = { type: "break" };
/** Loop-continuing statement */
export type Continue = { type: "continue" };
/** Function declaration statement */
export type FunctionDecl = {
type: "function_decl";
name: Variable;
args: VariableList;
};
/** Function return statement */
export type FunctionReturn = {
type: "return";
expression: Expression;
};
/*********************************
VARIABLES AND ASSIGNMENT
*********************************/
/** Pronoun used to refer to last assigned variable */
export type Pronoun = { pronoun: string };
/** Identifier for a variable */
export type Variable = string;
/** List of variable identifiers */
export type VariableList = Variable[];
/** Target for an assignment statement */
export type Assignable = {
variable: Variable;
index: Expression;
};
/** Assignment statement */
export type Assignment =
| { type: "assign"; target: Assignable; expression: Literal | PoeticNumber }
| { type: "assign"; target: Assignable; expression: PoeticString }
| { type: "assign"; target: Assignable; expression: Expression }
// Return types of branch 4 & 5 are a subset of branch 3
| { type: "enlist"; variable: Variable; expression: Expression }
| { type: "enlist"; variable: Variable; expression: Literal | PoeticNumber }
// Return type of branch 8 is subset of branch 6
| { type: "enlist"; variable: Variable }
| { type: "assign"; target: Assignable; expression: Delist };
/*********************************
EXPRESSION TREE
*********************************/
/** List of atomic expressions */
export type ExpressionList = SimpleExpression[];
/** Root of an expression tree */
export type Expression = Nor;
/** NOR expression clause */
export type Nor = { type: "binary"; op: "nor"; lhs: Or; rhs: Nor } | Or;
/** OR expression clause */
export type Or = { type: "binary"; op: "or"; lhs: And; rhs: Or } | And;
/** AND expression clause */
export type And =
| { type: "binary"; op: "and"; lhs: EqualityCheck; rhs: And }
| EqualityCheck;
/** Equality/inequality check clause */
export type EqualityCheck =
| {
type: "comparison";
comparator: "eq" | "ne";
lhs: Not;
rhs: EqualityCheck;
}
| Not;
/** NOT expression clause */
export type Not = { type: "not"; expression: Not } | Comparison;
/** Comparison clause */
export type Comparison = {
type: "comparison";
comparator: "gt" | "lt" | "ge" | "le";
lhs: Arithmetic;
rhs: Comparison;
};
/** Add/Subtract arithmetic clause */
export type Arithmetic =
| { type: "binary"; op: "+" | "-"; lhs: Arithmetic; rhs: Product }
| Product;
/** Utility type for main branch of "product" grammar rule */
type _Product = {
type: "binary";
op: "*" | "/";
lhs: _Product | SimpleExpression;
rhs: ExpressionList;
};
/** Multiply/Divide arithmetic clause */
export type Product = _Product | ExpressionList | SimpleExpression;
/** Leaf of an expression tree */
export type SimpleExpression =
| FunctionCall
| Constant
| Lookup
| Literal
| Pronoun;
/** Expression for a function call */
export type FunctionCall = {
type: "call";
name: Variable;
args: ExpressionList;
};
/** Constant literal */
export type Literal = Constant | Number | String;
/** Unit constant literal */
export type Constant = { constant: null | boolean | "" } | "__MYSTERIOUS__";
/** Constant numeric literal */
export type Number = { number: number };
/** Constant string literal */
export type String = { string: string };
/*********************************
OPERATION STATEMENTS
*********************************/
/** Single-operation statements */
export type Operation =
| Readline
| Output
| Crement
| Mutation
| Assignment
| Rounding;
/** STDIN statement */
export type Readline = { type: "stdin"; target?: Assignable };
/** STDOUT statement */
export type Output = { type: "stdout"; output: Expression };
/** Increment/decrement statements */
export type Crement = {
type: "increment" | "decrement";
variable: Variable;
multiple: number;
};
/** Types of mutation operations */
export type Mutator = "split" | "cast" | "join";
/** Mutation operation statement */
export type Mutation = {
type: "mutation";
target: Assignable;
expression: {
mutation: {
type: Mutator;
modifier: Expression;
source: Expression | { lookup: Assignable };
};
};
};
/** Rounding operation statement */
export type Rounding = {
type: "rounding";
direction: "up" | "down" | "nearest";
variable: Variable;
};
/*********************************
KITCHEN SINK
*********************************/
/** Clause representing dequeueing of an array */
export type Delist = { type: "delist"; variable: Variable };
/** Clause for variable lookup at leaf of expression tree */
export type Lookup =
| { type: "lookup"; variable: Variable; index?: Expression }
| Delist;
/** Poetic numeric literal */
export type PoeticNumber = { number: number };
/** Poetic string literal */
export type PoeticString = { string: string };

View File

@ -0,0 +1,460 @@
/*
This is a slightly modified version of the grammar defined in Satriani,
the official reference implementation of Rockstar. Modifications made:
- Add very short comment for each grammar rule
- Adapt grammar to parse the program line-by-line
- Organize the rules into broad categories
- Adjust action returns to be easier to use in TypeScript
Apart from line-by-line parsing, all changes are cosmetic and the
actual grammar should be identical to Satriani. The rule names should not
be changed too much, so that matching any upstream updates is easy.
Satriani grammar: https://github.com/RockstarLang/rockstar/blob/main/satriani/rockstar.peg
After updating, run `node generate-parser.js` in this directory.
*/
{
/* initialiser code - this is JS that runs before the parser is generated */
const keywords = new Set([
// common variable prefixes
'a', 'an', 'the', 'my', 'your', 'our',
// pronouns
'it', 'he', 'she', 'him', 'her', 'they', 'them', 'ze', 'hir', 'zie', 'zir', 'xe', 'xem', 've', 'ver',
// literal values
'mysterious',
'null', 'nothing', 'nowhere', 'nobody', 'gone',
'true', 'right', 'yes', 'ok',
'false', 'wrong', 'no', 'lies',
'maybe', 'definitely', // reserved for future use
'empty', 'silent', 'silence',
// assignment
'let', 'be', 'put', 'into', 'in', // expression
'is', 'are', 'was', 'were', 'say', 'says', 'said', // poetic
// operations
'at', 'rock', 'with', 'roll', 'into', 'push', 'pop', 'like', // arrays
'cut', 'split', 'shatter', 'join', 'unite', 'cast', 'burn', // strings
'build', 'up', 'knock', 'down', // increment/decrement
'plus', 'with', 'minus', 'without', 'times', 'of', 'over', 'between', // arithmetic
'and', // list arithmetic
'turn', 'up', 'down', 'round', 'around', // rounding
'and', 'or', 'nor', 'not', // logical
// comparison
'is', "isn't", 'isnt', "ain't", 'aint',
'arent', "aren't", 'wasnt', "wasn't", 'werent', "weren't",
'not',
'than',
'higher', 'greater', 'bigger', 'stronger',
'lower', 'less', 'smaller', 'weaker',
'as',
'high', 'great', 'big', 'strong',
'low', 'little', 'small', 'weak',
// input/output
'listen', 'to',
'say', 'shout', 'whisper', 'scream',
// control flow
'if', 'else',
'while', 'until',
'break', 'continue',
'break', 'it', 'down',
'take', 'it', 'to', 'the', 'top',
'take',
// functions
'takes', 'wants',
'give', 'return', 'send', 'back',
'taking',
])
function isKeyword(string) {
return keywords.has(string.toLowerCase());
}
}
/* Rule for the entire program */
program = p:line * { return { list: p } }
/* Rule for a single block of the program */
line = _* s:statement _* (EOL / EOF) { return s }
/ _* EOL { return { type: 'blank' } }
/ _+ EOF {return { type: 'blank' } }
/* Utility types for whitespace and comments */
whitespace = [ \t]
comment = '(' [^)]* ')'
_ = (whitespace / comment)+
noise = (_ / [;,?!&.])
EOL "end of line" = noise* '\r'? '\n'
EOF = !.
ignore_rest_of_line = (_[^\n]*)?
/* Rule for a single statement */
statement = _* s:(break / continue / function_decl / function_call
/ function_return / loop / if / else / operation / expression) { return s }
/*********************************
FLOW CONTROL
*********************************/
/* Rule for an if-statement */
// To run inline statements in a separate step, we need location information
inline_statement = s:statement { return {s: s, start: location().start.offset }}
if = 'if'i _ e:expression s:inline_statement?
{ return {
type: 'if',
condition: e,
statement: s && s.s,
split: s && s.start,
} }
/* Rule for an else-statement */
else = _* 'else'i _ a:statement { return { type: 'else', statement: a } }
/ _* 'else'i _* {return { type: 'else', statement: null } }
/* Rule for starting a while-loop */
loop_keyword = ('while'i / 'until'i)
loop = loop_keyword _ e:expression { return { type: 'loop', condition: e } }
/* Rule for the loop break statement */
break = 'break'i ignore_rest_of_line { return { type: 'break' } }
/* Rule for the loop continue statement */
continue = ('continue'i ignore_rest_of_line / 'take it to the top'i)
{ return { type: 'continue' } }
/* Rule for function declaration statement */
takes = ('takes'i / 'wants'i)
function_decl = name:variable _ takes _ args:variable_list
{ return { type: 'function_decl', name: name, args: args } }
/* Rule for function return statement */
return = 'return'i / 'give back'i / 'send'i / 'give'i
function_return = return _ e:expression (_ 'back'i)?
{ return { type: 'return', expression: e } }
/*********************************
VARIABLES AND ASSIGNMENT
*********************************/
/* Keywords used to refer to last assigned variable */
pronoun "pronoun" = pronoun:(
'they'i / 'them'i
/ 'she'i / 'him'i / 'her'i / 'hir'i / 'zie'i / 'zir'i / 'xem'i / 'ver'i
/ 'ze'i / 've'i / 'xe'i / 'it'i / 'he'i
) &(is / _ / EOL / EOF)
{ return { pronoun: pronoun.toLowerCase() } }
/* Prefix for defining common variables */
common_prefix "common variable prefix" = ( 'an'i / 'a'i / 'the'i / 'my'i / 'your'i / 'our'i)
/* Set of recognized uppercase letters */
uppercase_letter "uppercase letter" = [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĸĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽ]
/* Set of recognized lowercase letters */
lowercase_letter "lowercase letter" = [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷÿźżžʼnß]
/* Set of recognized letters */
letter "letter" = uppercase_letter / lowercase_letter
/* Rule for variable identifiers */
variable "variable" = common_variable / proper_variable / pronoun / simple_variable
/* Name of a common variable */
common_variable = prefix:common_prefix _ name:$(letter+)
{ return (prefix + '_' + name).toLowerCase() };
/* Rule for name of a simple variable */
simple_variable = name:$(letter letter*) !{ return isKeyword(name) } { return name.toLowerCase() }
/* Rule for a single word in proper variable */
proper_noun = noun:$(uppercase_letter letter*) !{ return isKeyword(noun) } { return noun }
/* Rule for name of a proper variable */
proper_variable = head:$(proper_noun (' ' $proper_noun)*)
{ return head.replace(/ /g, '_').toLowerCase() }
/* Rule for a list of variables */
variable_list_separator "separator" = expression_list_separator / _ 'and'i _
variable_list "variable list" = head:variable variable_list_separator tail:variable_list
{ return [head].concat(tail) }
/ arg:variable { return [arg] }
/* Rule for part of array access clause */
indexer = _ 'at'i _ i:expression { return i };
/* Rule for possible target of a value assignment */
assignable "assignable variable"
= v:variable i:indexer?
{ return { variable: v, index: i }; }
/* Operators allowed in compound assignment */
compoundable_operator "operator" = add / subtract / multiply / divide
/* Rule for assignment statements */
assignment "assignment statement" = target:assignable is _* e:(literal / poetic_number)
{ return { type: "assign", target: target, expression: e} }
/ target:assignable _ ('says 'i / 'say 'i / 'said 'i) e:poetic_string
{ return { type: "assign", target: target, expression: e} }
/ 'put'i _ e:expression into target:assignable
{ return { type: "assign", target: target, expression: e} }
/ 'let'i _ target:assignable _ 'be'i o:compoundable_operator e:expression {
return {
type: "assign",
target: target,
expression: { binary: { op: o, lhs: { lookup: target }, rhs: e } }
}
}
/ 'let'i _ t:assignable _ 'be'i _ e:expression
{ return { type: "assign", target: t, expression: e} }
/ push _ e:expression into v:variable
{ return { type: "enlist", variable: v, expression: e } }
/ push _ v:variable _ 'like'i _ e:(literal / poetic_number)
{ return { type: "enlist", variable: v, expression: e } }
/ push _ v:variable (_ 'with'i)? _ e:expression
{ return { type: "enlist", variable: v, expression: e } }
/ push _ v:variable
{ return { type: "enlist", variable: v } }
/ e:delist into target:assignable
{ return { type: "assign", target: target, expression: e } }
/*********************************
EXPRESSION TREE
*********************************/
/* Rule for a list of expressions */
expression_list_separator "separator" = (_? ', and'i _ / _?('&' / ',' / "'n'"i)_?)
expression_list "expression list" = head:simple_expression expression_list_separator tail:expression_list
{ return [head].concat(tail) }
/ arg:simple_expression { return [arg] }
/* Root rule for expression tree */
expression "expression" = boolean
/* Rule for a boolean operation clause */
boolean = nor
/* Rule for NOR operation */
nor = lhs:or _ 'nor'i _ rhs:nor {
return { type: "binary", op: 'nor', lhs: lhs, rhs: rhs } }
/ or
/* Rule for OR operation */
or = lhs:and _ 'or'i _ rhs:or {
return { type: "binary", op: 'or', lhs: lhs, rhs: rhs } }
/ and
/* Rule for AND operation */
and = lhs:equality_check _ 'and'i _ rhs:and {
return { type: "binary", op: 'and', lhs: lhs, rhs: rhs } }
/ equality_check
/* Keywords for equality/inequality check */
is = ("'s"i / "'re"i / _ ('=' / 'is'i / 'was'i / 'are'i / 'were'i)) _
isnt = _ (
'isnt'i / "isn't"i /
'aint'i / "ain't"i /
'arent'i / "aren't"i /
'wasnt'i / "wasn't"i /
'werent'i / "weren't"i
) _
/* Rule for equality/inequality check */
eq = isnt { return 'ne' } / is { return 'eq' }
equality_check = lhs:not c:eq rhs:equality_check
{ return { type: "comparison", comparator: c, lhs: lhs, rhs: rhs } }
/ not
/* Rule for NOT operation */
not = 'not'i _ e:not { return { type: "not", expression: e } }
/ comparison
/* Keywords for comparison operators */
greater = ('higher'i / 'greater'i / 'bigger'i / 'stronger'i)
smaller = ('lower'i / 'less'i / 'smaller'i / 'weaker'i)
great = ('high'i / 'great'i / 'big'i / 'strong'i)
small = ('low'i / 'little'i / 'small'i / 'weak'i)
comparator = is greater _ 'than'i _ { return 'gt' }
/ is smaller _ 'than'i _ { return 'lt' }
/ is 'as'i _ great _ 'as'i _ { return 'ge' }
/ is 'as'i _ small _ 'as'i _ { return 'le' }
/* Rule for comparison clause */
comparison = lhs:arithmetic c:comparator rhs:comparison
{ return { type: "comparison", comparator: c, lhs: lhs, rhs: rhs } }
/ arithmetic
/* Rule for plus/minus arithmetic clause */
arithmetic = first:product rest:((add / subtract) product)+
{ return rest.reduce(function(memo, curr) {
return { type: "binary", op: curr[0], lhs: memo, rhs: curr[1] }
}, first); }
/ product
/* Rule for multiply/divide arithmetic clause */
product = first:simple_expression rest:((multiply / divide) expression_list)+
{ return rest.reduce(function(memo, curr) {
return { binary: { op: curr[0], lhs: memo, rhs: curr[1] } };
}, first); }
/ expression_list
/ simple_expression
/* Rule for the leaf of an expression tree */
simple_expression = function_call / constant / lookup / literal / pronoun
/* Rule for function call expression */
function_call "function call" = name:variable _ 'taking'i _ args:expression_list
{ return { type: "call", name: name, args: Array.isArray(args) ? args : [args] } }
/* Rule for a constant literal */
literal "literal" = constant / number / string
/* Rule for keyword-based constant literals */
constant "constant" = null / true / false / empty_string / mysterious
null = ('null'i / 'nothing'i / 'nowhere'i / 'nobody'i / 'gone'i) { return { constant: null } }
true = ('true'i / 'ok'i / 'right'i / 'yes'i) !letter { return { constant: true } }
false = ('false'i / 'lies'i / 'wrong'i / 'no'i) !letter { return { constant: false } }
empty_string = ('empty'i / 'silent'i / 'silence'i) { return { constant: "" } }
mysterious = 'mysterious'i { return '__MYSTERIOUS__' }
/* Rule for a numeric literal */
number "number" = n:$('-'?[0-9]+ ('.' [0-9]+)?) '.'?
{ return {number: parseFloat(n)} }
/ n:$('.' [0-9]+)
{ return {number: parseFloat(n) } }
/* Rule for a string literal */
string "string" = '"' s:$[^"]* '"' { return {string: s }; }
/* 'TODO remove */
/*********************************
OPERATION STATEMENTS
*********************************/
/* Rule for single-operation statements */
operation "operation statement" = readline / output / crement / mutation / assignment / rounding
/* Rule for STDIN operation statement */
readline "stdin statement" = 'listen to'i _ target:assignable
{ return { type: "stdin", target: target } }
/ 'listen'i { return { type: "stdin" } }
/* Rule for STDOUT statement */
output "stdout statement" = ('say'i/'shout'i/'whisper'i/'scream'i) _ e:expression
{ return { type: "stdout", output: e } }
/* Rule for increment/decrement statements */
crement "increment/decrement statement" = increment / decrement
/* Rule for increment statement */
increment = 'build'i _ v:variable _ t:('up'i noise*)+
{ return { type: "increment", variable: v, multiple: t.length } }
/* Rule for decrement statement */
decrement = 'knock'i _ v:variable _ t:('down'i noise*)+
{ return { type: "decrement", variable: v, multiple: t.length } }
split = ('cut'i / 'split'i / 'shatter'i) { return 'split' }
cast = ('cast'i / 'burn'i) { return 'cast' }
join = ('join'i / 'unite'i) { return 'join' }
/* Rule for mutation operation statements */
mutator "mutation keyword" = split / cast / join
modifier = _ ('with'i / 'using'i) _ m:expression { return m }
mutation "mutation statement" = op:mutator _ s:expression into t:assignable m:modifier?
{ return { assign: { target: t, expression: { mutation: { type: op, source: s, modifier: m } } } } ; }
/ op:mutator _ s:assignable m:modifier?
{ return { assign: { target: s, expression: { mutation: { type: op, source: { lookup: s }, modifier: m } } } } ; }
/* Rule for rounding operation statements */
rounding "rounding statement" = floor / ceil / math_round
floor = 'turn'i _ v:variable _ 'down'i
{ return { type: "rounding", variable: v, direction: 'down' } }
/ 'turn'i _ 'down'i _ v:variable
{ return { type: "rounding", variable: v, direction: 'down' } }
ceil = 'turn'i _ v:variable _ 'up'i
{ return { type: "rounding", variable: v, direction: 'up' } }
/ 'turn'i _ 'up'i _ v:variable
{ return { type: "rounding", variable: v, direction: 'up' } }
math_round = 'turn'i _ v:variable _ ('round'i/'around'i)
{ return { type: "rounding", variable: v, direction: 'nearest' } }
/ 'turn'i _ ('round'i/'around'i) _ v:variable
{ return { type: "rounding", variable: v, direction: 'nearest' } }
/*********************************
KITCHEN SINK
*********************************/
/* Keywords for arithmetic operators */
// Note that operator aliases explicitly include a trailing space,
// otherwise 'with' is a prefix code for 'without' and confuses the parser.
add = _* ('+' / 'plus 'i / 'with 'i) _* { return '+' }
subtract = _* ('-' / 'minus 'i / 'without 'i) _* { return '-' }
multiply = _* ('*' / 'times 'i / 'of 'i) _* { return '*' }
divide = _* ('/' / 'over 'i / 'between 'i) _* { return '/' }
push = ('rock'i / 'push'i )
pop = ('roll'i / 'pop'i)
into = _ ('into'i / 'in'i) _
/* Rule representing array dequeue clause */
delist "array roll" = pop _ v:variable
{ return { type: "delist", variable: v } }
/* Rule for variable in expression tree leaf */
lookup "variable or array element" = d:delist { return d; }
/ v:variable _ 'at'i _ i:expression
{ return { type: "lookup", variable: v, index: i } }
/ v:variable
{ return { type: "lookup", variable: v } }
/* Rule for poetic string literal */
poetic_string "poetic string" = s:$[^\r\n]* { return { string: s } }
/* Rule for poetic number literal */
poetic_number "poetic number" = poetic_digit_separator* n:poetic_digits poetic_digit_separator* d:poetic_decimal? poetic_digit_separator*
{ return { number: parseFloat(d?n+'.'+d:n)}}
/* Rule for poetic decimal literal */
poetic_decimal = '.' poetic_decimal_digit_separator* d:poetic_decimal_digits poetic_decimal_digit_separator* {return d}
/ '.' poetic_decimal_digit_separator*
/* Separator used in poetic literals */
poetic_digit_separator = ( _ / [0-9\',;:?!+_/] )
poetic_digits = poetic_digit_separator* head:poetic_digit poetic_digit_separator+ tail:poetic_digits
{ return head + tail }
/ d: poetic_digit
{ return d }
poetic_decimal_digit_separator = ( _ / poetic_digit_separator / '.')
poetic_decimal_digits = poetic_decimal_digit_separator* head:poetic_digit poetic_decimal_digit_separator+ tail:poetic_decimal_digits
{ return head + tail }
/ d: poetic_digit
{ return d }
poetic_digit = t:[A-Za-z\-']+
{ return (t.filter(c => /[A-Za-z\-]/.test(c)).length%10).toString() }

View File

@ -1 +1,29 @@
export {};
import { ParseError } from "../../worker-errors";
import * as parser from "./parser.out.js";
import * as D from "./grammar-types";
import { ASTStep } from "../common/types";
import { buildAST } from "./ast-builder";
/** Run the program source code through the Peg parser */
export const pegParseProgram = (program: string): D.Program => {
try {
return parser.parse(program);
} catch (err) {
if (err instanceof parser.SyntaxError) {
const error = err as any;
const message = error.message;
const line = error.location.start.line - 1;
const charRange = {
start: error.location.start.offset,
end: error.location.end.offset,
};
throw new ParseError(message, { line, charRange });
} else throw err;
}
};
export const parseProgram = (program: string): ASTStep[] => {
const lines = pegParseProgram(program);
const ast = buildAST(lines);
return ast;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import { LanguageEngine, StepExecutionResult } from "../../types";
import { RS } from "../common/types";
import { parseProgram } from "../parser";
export default class XYZLanguageEngine implements LanguageEngine<RS> {
resetState() {
@ -7,10 +8,11 @@ export default class XYZLanguageEngine implements LanguageEngine<RS> {
}
validateCode(code: string) {
// TODO: Unimplemented
parseProgram(code);
}
prepare(code: string, input: string) {
parseProgram(code);
// TODO: Unimplemented
}

View File

@ -28,6 +28,7 @@
"eslint": "8.4.0",
"eslint-config-next": "12.0.7",
"jest": "^27.4.7",
"peggy": "^1.2.0",
"ts-loader": "^9.2.6",
"typescript": "4.5.2",
"webpack": "^5.65.0",

View File

@ -4409,6 +4409,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
peggy@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/peggy/-/peggy-1.2.0.tgz#657ba45900cbef1dc9f52356704bdbb193c2021c"
integrity sha512-PQ+NKpAobImfMprYQtc4Egmyi29bidRGEX0kKjCU5uuW09s0Cthwqhfy7mLkwcB4VcgacE5L/ZjruD/kOPCUUw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"