[wip] Add grammar-level parser for Rockstar
This commit is contained in:
parent
fb331e1194
commit
a7b7879fb3
@ -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)
|
||||
|
||||
```
|
||||
|
@ -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;
|
||||
};
|
||||
|
83
languages/rockstar/parser/ast-builder.ts
Normal file
83
languages/rockstar/parser/ast-builder.ts
Normal 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;
|
||||
};
|
16
languages/rockstar/parser/generate-parser.js
Normal file
16
languages/rockstar/parser/generate-parser.js
Normal 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);
|
245
languages/rockstar/parser/grammar-types.ts
Normal file
245
languages/rockstar/parser/grammar-types.ts
Normal 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 };
|
460
languages/rockstar/parser/grammar.peg
Normal file
460
languages/rockstar/parser/grammar.peg
Normal 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() }
|
@ -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;
|
||||
};
|
||||
|
6168
languages/rockstar/parser/parser.out.js
Normal file
6168
languages/rockstar/parser/parser.out.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user