Remove Rockstar-related files
Rockstar got slightly tricky to implement, so for the sake of completing the project, Rockstar is being kept for later in a separate branch.
This commit is contained in:
parent
a7b7879fb3
commit
10221b0767
@ -1,7 +0,0 @@
|
||||
# Rockstar
|
||||
|
||||
## References
|
||||
|
||||
- Official docs: https://codewithrockstar.com/docs
|
||||
|
||||
## Implementation details
|
@ -1,400 +0,0 @@
|
||||
# The Rockstar Language Spec
|
||||
|
||||
This file contains an **unofficial** version of the Rockstar Language Spec. While the official docs are obviously the source of the ultimate truth, they are also a slight bit unorganized in terms of order of content. This file is just a more condensed version, used as reference while building the parser and interpreter.
|
||||
|
||||
## Comments
|
||||
|
||||
`(your comment)`
|
||||
|
||||
- Only single-line comments supported, can appear anywhere
|
||||
|
||||
## Variables
|
||||
|
||||
Three kinds of variables supported. There is no functional difference in the three whatsoever.
|
||||
All variable names are **case-insensitive** in usage (apart from proper variables below).
|
||||
|
||||
1. **Simple variables**: Single word, only letters, no lang keywords.
|
||||
|
||||
```
|
||||
Variable is 1
|
||||
Tommy is a rockstar
|
||||
X is 2
|
||||
Y is 3
|
||||
Put x plus y into result
|
||||
```
|
||||
|
||||
2. **Common variables**: Variable name consists of two parts:
|
||||
|
||||
1. Keyword: `a, an, the, my, your, our`. This is part of the variable identifier.
|
||||
2. Name: lowercase letters, no spaces.
|
||||
|
||||
```
|
||||
My variable is 5
|
||||
Your variable is 4
|
||||
Put my variable plus your variable into the total
|
||||
Shout the total
|
||||
```
|
||||
|
||||
3. **Proper variables**: Multi-word, each word starting with capital letter, no lang keywords.
|
||||
|
||||
- Proper variales are also case-insensitive except that each word MUST start with capital letter in every use.
|
||||
|
||||
`Doctor Feelgood, Mister Crowley, Tom Sawyer, Billie Jean, Distance In KM`
|
||||
|
||||
### Case-sensitivity in variable names
|
||||
|
||||
- Rockstar keywords and variable names are all case-insensitive...
|
||||
- ...except proper variables, in which each word MUST start with a capital letters
|
||||
|
||||
```
|
||||
TIME, time, tIMe, TIMe (Simple variables, equivalent)
|
||||
MY HEART, my heart, My Heart (Common variables, equivalent)
|
||||
Tom Sawyer, TOM SAWYER, TOm SAWyer (Proper variables, equivalent)
|
||||
(note that the "S" above must be capital)
|
||||
DOCTOR feelgood (NOT a valid variable name)
|
||||
```
|
||||
|
||||
### Scope rules
|
||||
|
||||
- All variables declared in global scope (outside of any function) are accessible and modifyable everywhere **below** their first initialization.
|
||||
- Variables defined inside functions are available until end of function.
|
||||
|
||||
### Referring to last named variable
|
||||
|
||||
`it, he, she, him, her, they, them, ze, hir, zie, zir, xe, xem, ve, ver`
|
||||
|
||||
Above keywords can be used to refer to the last named variable, **in parsing order**. This means the variable last used in the lines just above the line being parsed.
|
||||
|
||||
### Types
|
||||
|
||||
- **Undefined**: `mysterious`, assigned to variables that don't have a value yet. Falsy.
|
||||
- **Null**: `null, nothing, nowhere, nobody, gone`. Falsy.
|
||||
- **Boolean**:
|
||||
- True: `true, right, yes, ok`. Truthy.
|
||||
- False: `false, wrong, no, lies`. Falsy.
|
||||
- **Number**: IEEE 754 floating-point numbers. Falsy only if zero.
|
||||
- **String**: UTF-16 encoded strings. Empty string falsy, else truthy.
|
||||
- Aliases for empty string: `empty, silent, silence`
|
||||
|
||||
### Literals
|
||||
|
||||
- Strings use double quotes: `"Hello San Francisco"`
|
||||
- Numeric literals: `123`, `3.1415`
|
||||
|
||||
### Value Assignment
|
||||
|
||||
```
|
||||
Put <expression> into <variable>
|
||||
Put <expression> in <variable>
|
||||
Let <variable> be <expression>
|
||||
```
|
||||
|
||||
> Note on the use of single quotes:
|
||||
>
|
||||
> - `'s` and `'re` appearing at end of word are considered as `... is` and `... are`.
|
||||
> - All other uses of single quotes are completely ignored.
|
||||
|
||||
### Poetic literals
|
||||
|
||||
#### Poetic constant literals
|
||||
|
||||
- For assigning constant values like bools, null, undefined.
|
||||
- Assignment syntax: `<variable> is/are/was/were <value>`
|
||||
|
||||
#### Poetic string literals
|
||||
|
||||
- For assigning strings without using double quotes.
|
||||
- String literal continues all the way to `\n`, may contain keywords.
|
||||
- Assignment syntax: `<variables> say/says/said <string>`
|
||||
|
||||
#### Poetic number literals
|
||||
|
||||
- For assigning numbers using words.
|
||||
- Assignment syntax: `<variable> is/are/was/were <words>`
|
||||
- `<words>` continues all the way until `\n`.
|
||||
- Each word denotes a digit, which is `word-length % 10`.
|
||||
- `-` is counted as a letter. All other non-alphabetical chars are ignored.
|
||||
- `<words>` may contain one period in between, which acts as decimal point.
|
||||
- `<words>` MUST not start with a constant literal (`nothing`, etc).
|
||||
|
||||
```
|
||||
Tommy was a lovestruck ladykiller (100)
|
||||
My dreams were ice. A life unfulfilled (3.141)
|
||||
Tommy was without (7)
|
||||
Her fire was all-consuming (13)
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
### Arithmetic
|
||||
|
||||
- Usage: `<expression> <op> <expression>`
|
||||
- Addition: `plus, with`
|
||||
- Subtraction: `minus, without`
|
||||
- Multiplication: `times, of`
|
||||
- Division: `over, between`
|
||||
|
||||
### Compound assignment operators
|
||||
|
||||
- Usage: `Let <variable> be <op> <expression>`
|
||||
- Equivalent to `(variable) (op)= (expression)` (`x += 5`)
|
||||
|
||||
### Incrementing and decrementing
|
||||
|
||||
- Increment: `Build <variable> up`
|
||||
- Decrement: `Knock <variable> down`
|
||||
- Multiple `up`s or `down`s adjust step value
|
||||
|
||||
```
|
||||
Build my world up (my world += 1)
|
||||
Knock the walls down, down (the walls -= 2)
|
||||
```
|
||||
|
||||
### Rounding numbers
|
||||
|
||||
- `turn up`: round up, `turn down`: round down
|
||||
- `turn round/around`: round to the nearest integer
|
||||
- Syntax: `Turn <type> <variable>` (acts in-place)
|
||||
|
||||
### List arithmetic
|
||||
|
||||
- Operators support argument list on right side
|
||||
- Operator is then applied iteratively on each arg (like `reduce`)
|
||||
- Can also be used in compound assignment operators
|
||||
- Only allowed where result type supports further ops
|
||||
|
||||
```
|
||||
Let X be 1 with 2, 3, 4 (X = 1+2+3+4)
|
||||
Let X be "foo" with "bar", and "baz" (X = "foobarbaz")
|
||||
Let the wolf be without fear, fury (the wolf -= fear + fury)
|
||||
Let X be "foo" with 2, 2, 2 (X = "foo"*8 = "foofoo...")
|
||||
Let X be 2 times "foo", "bar" (unsupported op, X = mysterious)
|
||||
```
|
||||
|
||||
### Converting between string and number
|
||||
|
||||
Keyword: `cast, burn`
|
||||
|
||||
- This is a mutation op (described in Arrays section)
|
||||
- Parses strings into numbers (optional arg: base number (default 10))
|
||||
- Converts numbers into corresponding Unicode characters (no optional arg)
|
||||
|
||||
```
|
||||
Let X be "123.45"
|
||||
Cast X (X = numeric value 123.45)
|
||||
Let X be "ff"
|
||||
Cast X with 16 (X = 255 = OxFF)
|
||||
Cast "12345" into result (result = 12345)
|
||||
Cast "aa" into result with 16 (result = 170 = 0xAA)
|
||||
|
||||
Cast 65 into result (result = "A" - ASCII code 65)
|
||||
Cast 1046 into result (result = "Ж" - Unicode 1046)
|
||||
```
|
||||
|
||||
### Comparison
|
||||
|
||||
- Syntax: `<expr> <op> <expr>`
|
||||
- Above syntax only valid in comparison context (eg. `if` blocks)
|
||||
- Equality comparison: `is/are/was/were` (`If <expr> is <expr>`)
|
||||
- Not-equal comparison: `ain't, aren't, wasn't, weren't`
|
||||
- Comparison keywords:
|
||||
- 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 or equal: `is as low/little/small/weak as`
|
||||
|
||||
### Logical operators
|
||||
|
||||
- `A and/or/nor B`: AND, OR, NOT-OR
|
||||
- `not A`: negation (NOT)
|
||||
- All logical ops are **short-circuiting**.
|
||||
|
||||
### Input-output
|
||||
|
||||
- `Listen to <variable>`: Read one line of STDIN and store in variable.
|
||||
- `Say/Shout/Whisper/Scream <expression>`: Print expr to STDOUT.
|
||||
|
||||
### Operator precedence
|
||||
|
||||
1. Function calls (greedy arguments)
|
||||
2. Logical NOT (right-associative)
|
||||
3. Multiplication and Division (left-associative)
|
||||
4. Addition and Subtraction (left-associative)
|
||||
5. Comparison operators (left-associative)
|
||||
6. AND, OR, NOR (left-associative)
|
||||
|
||||
```
|
||||
A taking B times C plus not D times E and F
|
||||
= ((A(B) * C) + (!D * E)) && F
|
||||
```
|
||||
|
||||
### Implicit conversions
|
||||
|
||||
For comparison operators:
|
||||
|
||||
- `mysterious OP mysterious` => Equal
|
||||
- `<non-myst> OP mysterious` => Non-equal
|
||||
- `<str> OP <num>` => Convert str to num (base 10). If fail, non-equal.
|
||||
- `<str> OP <bool>` => Empty str is false, else str is true.
|
||||
- `<str> OP null` => Non-equal
|
||||
- `<num> OP <bool>` => Convert num to bool by truthiness.
|
||||
- `<num> OP null` => Convert null to 0.
|
||||
- `<bool> OP null` => Convert null to false.
|
||||
|
||||
For increment-decrement operators:
|
||||
|
||||
- `OP mysterious/<str>` => Error
|
||||
- `OP <bool>` => invert bool
|
||||
- `OP null` => coerce to zero, then apply op
|
||||
|
||||
For binary operators (non-mentioned cases are error):
|
||||
|
||||
- `<str> PLUS <num>`: convert num to base-10 str. Unnecessary zeros removed. Numbers with no whole part (eg 0.75) have one leading zero when serialized.
|
||||
- `<str> PLUS <bool>`: convert bool to "true" or "false"
|
||||
- `<str> PLUS null`: convert null to "null"
|
||||
- `<str> PLUS mysterious`: convert mysterious to "mysterious"
|
||||
- `<str> TIMES <num>`: str gets repeated num times
|
||||
|
||||
## Arrays
|
||||
|
||||
- Rockstar arrays support for numeric and non-numeric keys.
|
||||
- Numeric keys are zero-indexed.
|
||||
- Arrays are dynamically allocated when values are assigned.
|
||||
- Returning array as scalar returns array's length instead.
|
||||
|
||||
```
|
||||
Let my array at 255 be "some value"
|
||||
Let my array at "some_key" be "some_value"
|
||||
Shout my array at 255 (will print "some_value")
|
||||
Shout my array (will print the value 256)
|
||||
Shout my array at "some_key" (will print "some_value")
|
||||
```
|
||||
|
||||
Read (but not write) characters from a string with array syntax:
|
||||
|
||||
```
|
||||
Let my string be "abcdefg"
|
||||
Shout my string at 0 (will print "a")
|
||||
Shout my string at 1 (will print "b")
|
||||
Let the character be my string at 2
|
||||
```
|
||||
|
||||
### Array comparison
|
||||
|
||||
For two arrays to be equal,
|
||||
|
||||
- Must be of the same length
|
||||
- Corresponding elements must be equal
|
||||
|
||||
### Array operations
|
||||
|
||||
#### Queueing elements onto array
|
||||
|
||||
Keyword: `rock, push`
|
||||
|
||||
- Create new empty array: `Rock the array`
|
||||
- Queue value to end of array: `Rock the array with the element`
|
||||
- Queue poetic literals: `Rock the array like the poetic literal (= [367])`
|
||||
- List expressions:
|
||||
```
|
||||
Rock the array with 1, 2, 3
|
||||
Rock the array with the one, the two, the three
|
||||
Rock the array with the one, the two, and the three
|
||||
```
|
||||
|
||||
> NOTE: `with` has other uses too - it is also alias for the addition operator.
|
||||
> `Rock ints with 1, 2 with 3, 4, 5 (ints = [1, 5, 4, 5])`
|
||||
|
||||
#### Dequeue elements from the array
|
||||
|
||||
Keyword: `roll, pop`
|
||||
|
||||
- Remove first element from array (and optionally return it)
|
||||
- Special `roll x into y` syntax for assigning result to variable
|
||||
|
||||
```
|
||||
Rock ints with 1, 2, 3
|
||||
Roll ints (ints is now [ 2, 3 ])
|
||||
Let the two be roll ints (the two = 2, ints = [3])
|
||||
Roll ints into three (three = 3, ints = [])
|
||||
Roll ints (returns mysterious, ints = [])
|
||||
```
|
||||
|
||||
> Below two operations are **mutation operations**, which all share the following syntax:
|
||||
>
|
||||
> ```
|
||||
> Modify X (acts in place)
|
||||
> Modify X into Y (put result in Y, X is unmodified)
|
||||
> Modify X with Z (acts in place, Z is op parameter)
|
||||
> Modify X into Y with Z (put result in Y, X is unmodified, Z is op param)
|
||||
> ```
|
||||
|
||||
#### Splitting strings
|
||||
|
||||
Keyword: `cut, split, shatter`
|
||||
|
||||
- Split string into array of its characters
|
||||
- If separator specified, split on the separator
|
||||
|
||||
#### Joining arrays
|
||||
|
||||
Keyword: `join, unite`
|
||||
|
||||
- Join array of strings into single string
|
||||
- If separator specified, insert separator between each element
|
||||
|
||||
## Flow Control
|
||||
|
||||
### Block syntax
|
||||
|
||||
- Blocks start with an `If` or `Else` of function declaration...
|
||||
- ...and end with an empty line - one empty line ends the innermost scope only.
|
||||
- EOF ends all current scopes.
|
||||
|
||||
### If-Else
|
||||
|
||||
- Conditional, with optional `else` block.
|
||||
|
||||
```
|
||||
If <condition>
|
||||
...code...
|
||||
Else
|
||||
...code...
|
||||
(empty line)
|
||||
```
|
||||
|
||||
### Loops
|
||||
|
||||
- Only `while` loops are supported. Keywords: `while, until`
|
||||
- Inner code is executed as long as expression is truthy.
|
||||
- Breaking the loop: `break` or `Break it down`.
|
||||
- Continue to next iteration: `continue` or `Take it to the top`
|
||||
|
||||
### Functions
|
||||
|
||||
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, send`.
|
||||
- Return syntax: `<keyword> <var> [back]` (`back` is optional)
|
||||
|
||||
```
|
||||
(This function adds 9 to its input and returns the result)
|
||||
Polly wants a cracker
|
||||
Cheese is delicious
|
||||
Put a cracker with cheese into your mouth
|
||||
Give it back
|
||||
```
|
||||
|
||||
Calling functions:
|
||||
|
||||
- Functions are called with `taking` keyword and min 1 argument
|
||||
- Argument list is separated by `,`, `and`, `&`, `n`
|
||||
- Arguments can be expressions (literals, arithmetic ops, fn calls)
|
||||
|
||||
```
|
||||
Multiply taking 3, 5 (is an expression, returns 15)
|
||||
Search taking "hands", "my hands are"
|
||||
Put Multiply taking 3, 5, and 9 into Large (Large = 3*5*9)
|
||||
```
|
@ -1,116 +0,0 @@
|
||||
import { MonacoTokensProvider } from "../../types";
|
||||
|
||||
export const sampleProgram = [
|
||||
`Midnight takes your heart and your soul`,
|
||||
`While your heart is as high as your soul`,
|
||||
`Put your heart without your soul into your heart`,
|
||||
``,
|
||||
`Give back your heart`,
|
||||
``,
|
||||
`(♬ FizzBuzz riff plays ♬)`, // :)
|
||||
`Desire is a lovestruck ladykiller`,
|
||||
`My world is nothing `,
|
||||
`Fire is ice`,
|
||||
`Hate is water`,
|
||||
`Until my world is Desire,`,
|
||||
`Build my world up`,
|
||||
`If Midnight taking my world, Fire is nothing and Midnight taking my world, Hate is nothing`,
|
||||
`Shout "FizzBuzz!"`,
|
||||
`Take it to the top`,
|
||||
``,
|
||||
`If Midnight taking my world, Fire is nothing`,
|
||||
`Shout "Fizz!"`,
|
||||
`Take it to the top`,
|
||||
``,
|
||||
`If Midnight taking my world, Hate is nothing`,
|
||||
`Say "Buzz!"`,
|
||||
`Take it to the top`,
|
||||
``,
|
||||
`Whisper my world`,
|
||||
].join("\n");
|
||||
|
||||
// prettier-ignore
|
||||
const MAYBE_EQ_COMPARE_WORDS = ["high", "great", "big", "strong", "low", "little", "small", "weak"]
|
||||
const MAYBE_EQ_COMPARE_REGEX = MAYBE_EQ_COMPARE_WORDS.join("|");
|
||||
|
||||
// prettier-ignore
|
||||
const NON_EQ_COMPARE_WORDS = ["higher", "greater", "bigger", "stronger", "lower", "less", "smaller", "weaker"];
|
||||
const NON_EQ_COMPARE_REGEX = NON_EQ_COMPARE_WORDS.join("|");
|
||||
|
||||
/** Syntax highlighting */
|
||||
export const editorTokensProvider: MonacoTokensProvider = {
|
||||
ignoreCase: true,
|
||||
|
||||
// prettier-ignore
|
||||
varPronouns: ["it", "he", "she", "him", "her", "they", "them",
|
||||
"ze", "hir", "zie", "zir", "xe", "xem", "ve", "ver"],
|
||||
|
||||
// prettier-ignore
|
||||
operators: ["plus", "with", "minus", "without", "times", "of", "over", "between",
|
||||
"build", "knock", "up", "down", "is", "are", "was", "were", "ain't", "aren't",
|
||||
"wasn't", "weren't", "and", "or", "nor", "not", "like"],
|
||||
|
||||
// prettier-ignore
|
||||
inbuiltFns: ["say", "shout", "whisper", "scream", "cast", "burn", "rock", "push",
|
||||
"roll", "pop", "cut", "split", "shatter", "join", "unite"],
|
||||
|
||||
// prettier-ignore
|
||||
keywords: ["if", "else", "while", "until", "break", "continue", "put", "let", "be",
|
||||
"into", "give", "return", "back"],
|
||||
|
||||
// prettier-ignore
|
||||
constants: ["mysterious", "null", "nothing", "nowhere", "nobody", "gone", "true",
|
||||
"right", "yes", "ok", "false", "wrong", "no", "lies", "empty", "silent", "silence"],
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\(/, { token: "comment", next: "@comment" }],
|
||||
[/"/, { token: "string", next: "@string" }],
|
||||
[/,|\./, ""],
|
||||
// Function usage
|
||||
[
|
||||
// Allowing multi-word fn names here leads to too greedy rule,
|
||||
// so for syntax highlighting, we use only one-word functions
|
||||
/([a-zA-Z]+)( )(takes|wants|taking)( )/,
|
||||
["variable.function", "", "keyword", ""],
|
||||
],
|
||||
// Comparator clauses
|
||||
[
|
||||
/(is as )([a-zA-Z]+)( as)/,
|
||||
{ cases: { [`$2~${MAYBE_EQ_COMPARE_REGEX}`]: "operators" } },
|
||||
],
|
||||
[
|
||||
/(is )([a-zA-Z]+)( than)/,
|
||||
{ cases: { [`$2~${NON_EQ_COMPARE_REGEX}`]: "operators" } },
|
||||
],
|
||||
// Poetic string literals
|
||||
[/(says )(.+$)/, ["keyword", "string"]],
|
||||
// Multi-word keywords (can't be captured by keyword catchall)
|
||||
[/turn (up|down|round|around)\b/, "operators"],
|
||||
[/(break it down|take it to the top)/, "keyword"],
|
||||
[/listen to/, "constant.function"],
|
||||
// Catchall for keywords
|
||||
[
|
||||
/[^\s]+\b/,
|
||||
{
|
||||
cases: {
|
||||
"@constants": "constant",
|
||||
"@keywords": "keyword",
|
||||
"@varPronouns": "variable",
|
||||
"@operators": "operators",
|
||||
"@inbuiltFns": "constant.function",
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
comment: [
|
||||
[/\)/, { token: "comment", next: "@pop" }],
|
||||
[/[^\)]*/, "comment"],
|
||||
],
|
||||
string: [
|
||||
[/"/, { token: "string", next: "@pop" }],
|
||||
[/[^"]*/, "string"],
|
||||
],
|
||||
},
|
||||
defaultToken: "variable",
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
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;
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
import { setupWorker } from "../setup-worker";
|
||||
import RockstarLanguageEngine from "./runtime";
|
||||
|
||||
setupWorker(new RockstarLanguageEngine());
|
@ -1,12 +0,0 @@
|
||||
import { Renderer } from "./renderer";
|
||||
import { LanguageProvider } from "../types";
|
||||
import { RS } from "./common/types";
|
||||
import { sampleProgram, editorTokensProvider } from "./common/misc";
|
||||
|
||||
const provider: LanguageProvider<RS> = {
|
||||
Renderer,
|
||||
sampleProgram,
|
||||
editorTokensProvider,
|
||||
};
|
||||
|
||||
export default provider;
|
@ -1,83 +0,0 @@
|
||||
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;
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
// @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);
|
@ -1,245 +0,0 @@
|
||||
/** 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 };
|
@ -1,460 +0,0 @@
|
||||
/*
|
||||
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,29 +0,0 @@
|
||||
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
@ -1,6 +0,0 @@
|
||||
import { RendererProps } from "../types";
|
||||
import { RS } from "./common";
|
||||
|
||||
export const Renderer = ({ state }: RendererProps<RS>) => {
|
||||
return state == null ? null : <p>state.value</p>;
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import { LanguageEngine, StepExecutionResult } from "../../types";
|
||||
import { RS } from "../common/types";
|
||||
import { parseProgram } from "../parser";
|
||||
|
||||
export default class XYZLanguageEngine implements LanguageEngine<RS> {
|
||||
resetState() {
|
||||
// TODO: Unimplemented
|
||||
}
|
||||
|
||||
validateCode(code: string) {
|
||||
parseProgram(code);
|
||||
}
|
||||
|
||||
prepare(code: string, input: string) {
|
||||
parseProgram(code);
|
||||
// TODO: Unimplemented
|
||||
}
|
||||
|
||||
executeStep(): StepExecutionResult<RS> {
|
||||
// TODO: Unimplemented
|
||||
return { rendererState: { value: 0 }, nextStepLocation: { line: 0 } };
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import { Mainframe } from "../../ui/Mainframe";
|
||||
import LangProvider from "../../languages/rockstar";
|
||||
const LANG_ID = "rockstar";
|
||||
const LANG_NAME = "Rockstar";
|
||||
|
||||
const IDE: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{LANG_NAME} | Esolang Park</title>
|
||||
</Head>
|
||||
<Mainframe
|
||||
langId={LANG_ID}
|
||||
langName={LANG_NAME}
|
||||
provider={LangProvider}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IDE;
|
@ -14,9 +14,5 @@
|
||||
{
|
||||
"display": "Deadfish",
|
||||
"id": "deadfish"
|
||||
},
|
||||
{
|
||||
"display": "Rockstar",
|
||||
"id": "rockstar"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user