code-the-jewels 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/PROMPTS/01-build-v0.1.md +10 -0
- package/README.md +186 -0
- package/dist/ast.d.ts +143 -0
- package/dist/ast.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +145 -0
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.js +16 -0
- package/dist/generator.d.ts +11 -0
- package/dist/generator.js +126 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +15 -0
- package/dist/lexer.d.ts +18 -0
- package/dist/lexer.js +210 -0
- package/dist/parser.d.ts +40 -0
- package/dist/parser.js +394 -0
- package/dist/repl.d.ts +1 -0
- package/dist/repl.js +132 -0
- package/dist/runtime/atl-data.d.ts +4 -0
- package/dist/runtime/atl-data.js +18 -0
- package/dist/runtime/atl-flow.d.ts +1 -0
- package/dist/runtime/atl-flow.js +5 -0
- package/dist/runtime/bk-parse.d.ts +3 -0
- package/dist/runtime/bk-parse.js +9 -0
- package/dist/runtime/bk-text.d.ts +5 -0
- package/dist/runtime/bk-text.js +13 -0
- package/dist/runtime/rtj-core.d.ts +1 -0
- package/dist/runtime/rtj-core.js +51 -0
- package/dist/semantic.d.ts +11 -0
- package/dist/semantic.js +153 -0
- package/dist/tests/basic.test.d.ts +1 -0
- package/dist/tests/basic.test.js +69 -0
- package/dist/token.d.ts +56 -0
- package/dist/token.js +77 -0
- package/examples/cities.rtj +11 -0
- package/examples/count-words.rtj +12 -0
- package/examples/duo.rtj +12 -0
- package/examples/hello.rtj +1 -0
- package/examples/pipes.rtj +6 -0
- package/package.json +22 -0
- package/public/_redirects +1 -0
- package/public/index.html +559 -0
- package/src/ast.ts +189 -0
- package/src/cli.ts +120 -0
- package/src/diagnostics.ts +15 -0
- package/src/generator.ts +129 -0
- package/src/index.ts +7 -0
- package/src/lexer.ts +208 -0
- package/src/parser.ts +461 -0
- package/src/repl.ts +105 -0
- package/src/runtime/atl-data.ts +11 -0
- package/src/runtime/atl-flow.ts +1 -0
- package/src/runtime/bk-parse.ts +3 -0
- package/src/runtime/bk-text.ts +5 -0
- package/src/runtime/rtj-core.ts +21 -0
- package/src/semantic.ts +144 -0
- package/src/tests/basic.test.ts +74 -0
- package/src/token.ts +85 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Prompt 01: Build Code The Jewels v0.1 (Full Implementation)
|
|
2
|
+
|
|
3
|
+
Source: Notion page 32f4cf98-04bf-81ac-a19d-c05270b747a6
|
|
4
|
+
|
|
5
|
+
Built the complete v0.1 transpiler from scratch:
|
|
6
|
+
- Lexer, Parser (recursive descent), AST, Semantic analysis, JS code generator
|
|
7
|
+
- Runtime standard library: bk:text, bk:parse, atl:data, atl:flow
|
|
8
|
+
- CLI with run/compile/check/repl commands
|
|
9
|
+
- Pipe operator (|>) and duo blocks (mike/el dual pipelines)
|
|
10
|
+
- 5 example programs, all passing acceptance criteria
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Code The Jewels
|
|
2
|
+
|
|
3
|
+
> :point_right:{:gem:}.rtj
|
|
4
|
+
|
|
5
|
+
A programming language built the way Run The Jewels makes music. Two cities. Two voices. One compiler.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g code-the-jewels
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or from source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/everettsteele/code-the-jewels.git
|
|
17
|
+
cd code-the-jewels
|
|
18
|
+
npm install
|
|
19
|
+
npm run build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
Create `hello.rtj`:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
talk "code the jewels"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run it:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
rtj run hello.rtj
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Output: `code the jewels`
|
|
37
|
+
|
|
38
|
+
A duo block:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
feature words from "bk:parse"
|
|
42
|
+
feature count from "atl:data"
|
|
43
|
+
|
|
44
|
+
jewel line = "walking in the snow"
|
|
45
|
+
|
|
46
|
+
jewel wordCount = duo(line) {
|
|
47
|
+
mike: words
|
|
48
|
+
el: count
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
talk wordCount
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Output: `4`
|
|
55
|
+
|
|
56
|
+
## Keywords
|
|
57
|
+
|
|
58
|
+
Every keyword traces back to RTJ.
|
|
59
|
+
|
|
60
|
+
| Keyword | Purpose | JS Equivalent | Origin |
|
|
61
|
+
|------------|--------------------------------|------------------------|---------------------------------------------|
|
|
62
|
+
| `jewel` | Declare a variable | `const` | The jewel. The core unit. |
|
|
63
|
+
| `verse` | Declare a function | `function` | A verse. A block of bars. |
|
|
64
|
+
| `send` | Return a value | `return` | Send it back. |
|
|
65
|
+
| `talk` | Print to console | `console.log` | "Talk to Me", RTJ3 |
|
|
66
|
+
| `ifwild` | Conditional | `if` | "if wild" check before you move |
|
|
67
|
+
| `elsewild` | Else branch | `else` | The other path |
|
|
68
|
+
| `run` | Loop over a collection | `for...of` | Run it. |
|
|
69
|
+
| `in` | Iterator keyword | `of` | Run x in y |
|
|
70
|
+
| `feature` | Import from standard library | destructured `require` | A feature. A guest appearance. |
|
|
71
|
+
| `from` | Import source | module path | Where the feature comes from |
|
|
72
|
+
| `yank` | Throw an error | `throw` | Yank the chain. Pull the plug. |
|
|
73
|
+
| `duo` | Dual pipeline block | (custom) | "yankee and the brave (ep. 4)", RTJ4 |
|
|
74
|
+
| `mike` | First pipeline in a duo block | (custom) | Killer Mike. Atlanta. |
|
|
75
|
+
| `el` | Second pipeline in a duo block | (custom) | El-P. Brooklyn. |
|
|
76
|
+
|
|
77
|
+
## Pipe Operator
|
|
78
|
+
|
|
79
|
+
The pipe operator `|>` passes the jewel from one function to the next. Left to right. No nesting.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
feature trim, upper from "bk:text"
|
|
83
|
+
|
|
84
|
+
jewel line = " code the jewels "
|
|
85
|
+
jewel clean = line |> trim |> upper
|
|
86
|
+
|
|
87
|
+
talk clean
|
|
88
|
+
// Output: CODE THE JEWELS
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`a |> f |> g` compiles to `g(f(a))`. Each step takes the previous result as its argument.
|
|
92
|
+
|
|
93
|
+
## duo()
|
|
94
|
+
|
|
95
|
+
Named after "yankee and the brave (ep. 4)" from RTJ4. A duo block splits processing into two named pipelines.
|
|
96
|
+
|
|
97
|
+
`mike` runs first (Atlanta). `el` responds (Brooklyn). Both branches are required. A duo without both is just a solo.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
feature trim from "bk:text"
|
|
101
|
+
feature words from "bk:parse"
|
|
102
|
+
feature count from "atl:data"
|
|
103
|
+
|
|
104
|
+
jewel bars = " rtj from atlanta to brooklyn "
|
|
105
|
+
|
|
106
|
+
jewel total = duo(bars) {
|
|
107
|
+
mike: trim |> words
|
|
108
|
+
el: count
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
talk total
|
|
112
|
+
// Output: 5
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Compiles to: `el(mike(input))`. The mike pipeline feeds into the el pipeline. Two passes, one result.
|
|
116
|
+
|
|
117
|
+
## Standard Library
|
|
118
|
+
|
|
119
|
+
The standard library is split by city. `bk:` is Brooklyn (El-P's borough). `atl:` is Atlanta (Killer Mike's city).
|
|
120
|
+
|
|
121
|
+
### bk:text
|
|
122
|
+
String operations: `trim`, `upper`, `lower`, `split`, `join`
|
|
123
|
+
|
|
124
|
+
### bk:parse
|
|
125
|
+
Text parsing: `words`, `lines`, `chars`
|
|
126
|
+
|
|
127
|
+
### atl:data
|
|
128
|
+
Data operations: `count`, `countBy`, `first`, `last`
|
|
129
|
+
|
|
130
|
+
### atl:flow
|
|
131
|
+
Control flow: `identity`
|
|
132
|
+
|
|
133
|
+
## Compiler Stages
|
|
134
|
+
|
|
135
|
+
The compiler runs in stages. Each one has a name.
|
|
136
|
+
|
|
137
|
+
1. **The Fist (El)** / Lexer. Breaks source into tokens.
|
|
138
|
+
2. **The Hands** / Parser. Shapes tokens into an AST.
|
|
139
|
+
3. **Mike Pass** / Semantic analysis. Walks the tree, checks scope, validates imports.
|
|
140
|
+
4. **El Pass** / Optimization. (Reserved for v0.2.)
|
|
141
|
+
5. **The Gun (Mike)** / Code generator. Fires the AST into JavaScript.
|
|
142
|
+
6. **Jewel Box** / Output. The final `.js` file, ready to run.
|
|
143
|
+
|
|
144
|
+
## CLI Commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Run a .rtj file
|
|
148
|
+
rtj run <file.rtj>
|
|
149
|
+
|
|
150
|
+
# Compile to JavaScript
|
|
151
|
+
rtj compile <file.rtj>
|
|
152
|
+
|
|
153
|
+
# Check for errors (parse + semantic only)
|
|
154
|
+
rtj check <file.rtj>
|
|
155
|
+
|
|
156
|
+
# Start interactive REPL
|
|
157
|
+
rtj repl
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Version Naming
|
|
161
|
+
|
|
162
|
+
Each major version is named after an RTJ album.
|
|
163
|
+
|
|
164
|
+
| Version | Codename | Album |
|
|
165
|
+
|---------|-----------------------------|-------|
|
|
166
|
+
| v0.1 | RTJ0 "The Self-Titled Era" | Run The Jewels (2013) |
|
|
167
|
+
| v0.2 | RTJ2 "The Meow Era" | Run The Jewels 2 (2014) |
|
|
168
|
+
| v0.3 | RTJ3 "Talk to Me" | Run The Jewels 3 (2016) |
|
|
169
|
+
| v0.4 | RTJ4 "The Brave Era" | RTJ4 (2020) |
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
See the `examples/` directory:
|
|
174
|
+
- `hello.rtj` - Hello world
|
|
175
|
+
- `pipes.rtj` - Pipe operator
|
|
176
|
+
- `duo.rtj` - Duo blocks
|
|
177
|
+
- `cities.rtj` - Functions and arrays
|
|
178
|
+
- `count-words.rtj` - Word frequency counting
|
|
179
|
+
|
|
180
|
+
## Links
|
|
181
|
+
|
|
182
|
+
[codethejewels.com](http://codethejewels.com)
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT. Free, like every RTJ album. Part of [Meridian](https://neverstill.llc).
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export interface Location {
|
|
2
|
+
line: number;
|
|
3
|
+
column: number;
|
|
4
|
+
}
|
|
5
|
+
export type Statement = VarDecl | FunctionDecl | ReturnStmt | TalkStmt | IfStmt | LoopStmt | ImportStmt | ThrowStmt | ExpressionStmt | BlockStmt;
|
|
6
|
+
export type Expression = Identifier | StringLiteral | NumberLiteral | BooleanLiteral | NullLiteral | ArrayLiteral | ObjectLiteral | BinaryExpr | UnaryExpr | CallExpr | MemberExpr | PipeExpr | DuoExpr;
|
|
7
|
+
export interface Program {
|
|
8
|
+
type: 'Program';
|
|
9
|
+
body: Statement[];
|
|
10
|
+
}
|
|
11
|
+
export interface VarDecl {
|
|
12
|
+
type: 'VarDecl';
|
|
13
|
+
name: string;
|
|
14
|
+
init: Expression;
|
|
15
|
+
loc: Location;
|
|
16
|
+
}
|
|
17
|
+
export interface FunctionDecl {
|
|
18
|
+
type: 'FunctionDecl';
|
|
19
|
+
name: string;
|
|
20
|
+
params: string[];
|
|
21
|
+
body: BlockStmt;
|
|
22
|
+
loc: Location;
|
|
23
|
+
}
|
|
24
|
+
export interface ReturnStmt {
|
|
25
|
+
type: 'ReturnStmt';
|
|
26
|
+
value?: Expression;
|
|
27
|
+
loc: Location;
|
|
28
|
+
}
|
|
29
|
+
export interface TalkStmt {
|
|
30
|
+
type: 'TalkStmt';
|
|
31
|
+
value: Expression;
|
|
32
|
+
loc: Location;
|
|
33
|
+
}
|
|
34
|
+
export interface IfStmt {
|
|
35
|
+
type: 'IfStmt';
|
|
36
|
+
condition: Expression;
|
|
37
|
+
consequent: BlockStmt;
|
|
38
|
+
alternate?: BlockStmt;
|
|
39
|
+
loc: Location;
|
|
40
|
+
}
|
|
41
|
+
export interface LoopStmt {
|
|
42
|
+
type: 'LoopStmt';
|
|
43
|
+
variable: string;
|
|
44
|
+
iterable: Expression;
|
|
45
|
+
body: BlockStmt;
|
|
46
|
+
loc: Location;
|
|
47
|
+
}
|
|
48
|
+
export interface ImportStmt {
|
|
49
|
+
type: 'ImportStmt';
|
|
50
|
+
names: string[];
|
|
51
|
+
source: string;
|
|
52
|
+
loc: Location;
|
|
53
|
+
}
|
|
54
|
+
export interface ThrowStmt {
|
|
55
|
+
type: 'ThrowStmt';
|
|
56
|
+
value: Expression;
|
|
57
|
+
loc: Location;
|
|
58
|
+
}
|
|
59
|
+
export interface ExpressionStmt {
|
|
60
|
+
type: 'ExpressionStmt';
|
|
61
|
+
expr: Expression;
|
|
62
|
+
loc: Location;
|
|
63
|
+
}
|
|
64
|
+
export interface BlockStmt {
|
|
65
|
+
type: 'BlockStmt';
|
|
66
|
+
body: Statement[];
|
|
67
|
+
loc: Location;
|
|
68
|
+
}
|
|
69
|
+
export interface Identifier {
|
|
70
|
+
type: 'Identifier';
|
|
71
|
+
name: string;
|
|
72
|
+
loc: Location;
|
|
73
|
+
}
|
|
74
|
+
export interface StringLiteral {
|
|
75
|
+
type: 'StringLiteral';
|
|
76
|
+
value: string;
|
|
77
|
+
loc: Location;
|
|
78
|
+
}
|
|
79
|
+
export interface NumberLiteral {
|
|
80
|
+
type: 'NumberLiteral';
|
|
81
|
+
value: number;
|
|
82
|
+
loc: Location;
|
|
83
|
+
}
|
|
84
|
+
export interface BooleanLiteral {
|
|
85
|
+
type: 'BooleanLiteral';
|
|
86
|
+
value: boolean;
|
|
87
|
+
loc: Location;
|
|
88
|
+
}
|
|
89
|
+
export interface NullLiteral {
|
|
90
|
+
type: 'NullLiteral';
|
|
91
|
+
loc: Location;
|
|
92
|
+
}
|
|
93
|
+
export interface ArrayLiteral {
|
|
94
|
+
type: 'ArrayLiteral';
|
|
95
|
+
elements: Expression[];
|
|
96
|
+
loc: Location;
|
|
97
|
+
}
|
|
98
|
+
export interface ObjectLiteral {
|
|
99
|
+
type: 'ObjectLiteral';
|
|
100
|
+
pairs: {
|
|
101
|
+
key: string;
|
|
102
|
+
value: Expression;
|
|
103
|
+
}[];
|
|
104
|
+
loc: Location;
|
|
105
|
+
}
|
|
106
|
+
export interface BinaryExpr {
|
|
107
|
+
type: 'BinaryExpr';
|
|
108
|
+
op: string;
|
|
109
|
+
left: Expression;
|
|
110
|
+
right: Expression;
|
|
111
|
+
loc: Location;
|
|
112
|
+
}
|
|
113
|
+
export interface UnaryExpr {
|
|
114
|
+
type: 'UnaryExpr';
|
|
115
|
+
op: string;
|
|
116
|
+
operand: Expression;
|
|
117
|
+
loc: Location;
|
|
118
|
+
}
|
|
119
|
+
export interface CallExpr {
|
|
120
|
+
type: 'CallExpr';
|
|
121
|
+
callee: Expression;
|
|
122
|
+
args: Expression[];
|
|
123
|
+
loc: Location;
|
|
124
|
+
}
|
|
125
|
+
export interface MemberExpr {
|
|
126
|
+
type: 'MemberExpr';
|
|
127
|
+
object: Expression;
|
|
128
|
+
property: Expression;
|
|
129
|
+
computed: boolean;
|
|
130
|
+
loc: Location;
|
|
131
|
+
}
|
|
132
|
+
export interface PipeExpr {
|
|
133
|
+
type: 'PipeExpr';
|
|
134
|
+
steps: Expression[];
|
|
135
|
+
loc: Location;
|
|
136
|
+
}
|
|
137
|
+
export interface DuoExpr {
|
|
138
|
+
type: 'DuoExpr';
|
|
139
|
+
input: Expression;
|
|
140
|
+
mikePipeline: Expression[];
|
|
141
|
+
elPipeline: Expression[];
|
|
142
|
+
loc: Location;
|
|
143
|
+
}
|
package/dist/ast.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const lexer_1 = require("./lexer");
|
|
41
|
+
const parser_1 = require("./parser");
|
|
42
|
+
const semantic_1 = require("./semantic");
|
|
43
|
+
const generator_1 = require("./generator");
|
|
44
|
+
const diagnostics_1 = require("./diagnostics");
|
|
45
|
+
const repl_1 = require("./repl");
|
|
46
|
+
function compile(source) {
|
|
47
|
+
const lexer = new lexer_1.Lexer(source);
|
|
48
|
+
const tokens = lexer.tokenize();
|
|
49
|
+
const parser = new parser_1.Parser(tokens);
|
|
50
|
+
const ast = parser.parse();
|
|
51
|
+
const semantic = new semantic_1.Semantic();
|
|
52
|
+
semantic.analyze(ast);
|
|
53
|
+
const gen = new generator_1.Generator();
|
|
54
|
+
return gen.generate(ast);
|
|
55
|
+
}
|
|
56
|
+
function resolveRuntimePath() {
|
|
57
|
+
// When running from dist/, runtime is at dist/runtime/rtj-core.js
|
|
58
|
+
// When running via ts-node, runtime is at src/runtime/rtj-core.ts
|
|
59
|
+
const distRuntime = path.join(__dirname, 'runtime', 'rtj-core');
|
|
60
|
+
return distRuntime;
|
|
61
|
+
}
|
|
62
|
+
function run(filePath) {
|
|
63
|
+
const absPath = path.resolve(filePath);
|
|
64
|
+
if (!fs.existsSync(absPath)) {
|
|
65
|
+
console.error(`File not found: ${absPath}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const source = fs.readFileSync(absPath, 'utf-8');
|
|
69
|
+
let js = compile(source);
|
|
70
|
+
// Replace the generic runtime require with the actual path
|
|
71
|
+
const runtimePath = resolveRuntimePath().replace(/\\/g, '/');
|
|
72
|
+
js = js.replace('const __rtj = require("./runtime/rtj-core");', `const __rtj = require("${runtimePath}");`);
|
|
73
|
+
// Write to temp file and execute
|
|
74
|
+
const tmpFile = path.join(os.tmpdir(), `rtj_${Date.now()}_${Math.random().toString(36).slice(2)}.js`);
|
|
75
|
+
fs.writeFileSync(tmpFile, js, 'utf-8');
|
|
76
|
+
try {
|
|
77
|
+
require(tmpFile);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
fs.unlinkSync(tmpFile);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function compileToFile(filePath) {
|
|
84
|
+
const absPath = path.resolve(filePath);
|
|
85
|
+
if (!fs.existsSync(absPath)) {
|
|
86
|
+
console.error(`File not found: ${absPath}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const source = fs.readFileSync(absPath, 'utf-8');
|
|
90
|
+
const js = compile(source);
|
|
91
|
+
const outPath = absPath.replace(/\.rtj$/, '.js');
|
|
92
|
+
fs.writeFileSync(outPath, js, 'utf-8');
|
|
93
|
+
}
|
|
94
|
+
function check(filePath) {
|
|
95
|
+
const absPath = path.resolve(filePath);
|
|
96
|
+
if (!fs.existsSync(absPath)) {
|
|
97
|
+
console.error(`File not found: ${absPath}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const source = fs.readFileSync(absPath, 'utf-8');
|
|
101
|
+
compile(source); // throws on error
|
|
102
|
+
}
|
|
103
|
+
const [, , command, ...args] = process.argv;
|
|
104
|
+
try {
|
|
105
|
+
switch (command) {
|
|
106
|
+
case 'run': {
|
|
107
|
+
if (!args[0]) {
|
|
108
|
+
console.error('Usage: rtj run <file.rtj>');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
run(args[0]);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'compile': {
|
|
115
|
+
if (!args[0]) {
|
|
116
|
+
console.error('Usage: rtj compile <file.rtj>');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
compileToFile(args[0]);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case 'check': {
|
|
123
|
+
if (!args[0]) {
|
|
124
|
+
console.error('Usage: rtj check <file.rtj>');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
check(args[0]);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case 'repl': {
|
|
131
|
+
(0, repl_1.startRepl)();
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
console.log('Usage: rtj <run|compile|check|repl> [file]');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (err instanceof diagnostics_1.RTJError) {
|
|
141
|
+
console.error(err.format());
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class RTJError extends Error {
|
|
2
|
+
kind: 'SyntaxError' | 'NameError' | 'ImportError' | 'DuoError' | 'RuntimeError';
|
|
3
|
+
line?: number | undefined;
|
|
4
|
+
column?: number | undefined;
|
|
5
|
+
constructor(kind: 'SyntaxError' | 'NameError' | 'ImportError' | 'DuoError' | 'RuntimeError', message: string, line?: number | undefined, column?: number | undefined);
|
|
6
|
+
format(): string;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RTJError = void 0;
|
|
4
|
+
class RTJError extends Error {
|
|
5
|
+
constructor(kind, message, line, column) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.kind = kind;
|
|
8
|
+
this.line = line;
|
|
9
|
+
this.column = column;
|
|
10
|
+
}
|
|
11
|
+
format() {
|
|
12
|
+
const loc = this.line ? ` near line ${this.line}` : '';
|
|
13
|
+
return `${this.kind}: ${this.message}${loc}`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.RTJError = RTJError;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Program } from './ast';
|
|
2
|
+
export declare class Generator {
|
|
3
|
+
private indent;
|
|
4
|
+
generate(program: Program): string;
|
|
5
|
+
private pad;
|
|
6
|
+
private genStatement;
|
|
7
|
+
private genBlock;
|
|
8
|
+
private genExpression;
|
|
9
|
+
private flattenPipe;
|
|
10
|
+
private flattenPipeFromStrings;
|
|
11
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Generator = void 0;
|
|
4
|
+
class Generator {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.indent = 0;
|
|
7
|
+
}
|
|
8
|
+
generate(program) {
|
|
9
|
+
const lines = [
|
|
10
|
+
'"use strict";',
|
|
11
|
+
'const __rtj = require("./runtime/rtj-core");',
|
|
12
|
+
'const __modules = __rtj.modules;',
|
|
13
|
+
'',
|
|
14
|
+
];
|
|
15
|
+
for (const stmt of program.body) {
|
|
16
|
+
lines.push(this.genStatement(stmt));
|
|
17
|
+
}
|
|
18
|
+
return lines.join('\n') + '\n';
|
|
19
|
+
}
|
|
20
|
+
pad() {
|
|
21
|
+
return ' '.repeat(this.indent);
|
|
22
|
+
}
|
|
23
|
+
genStatement(stmt) {
|
|
24
|
+
switch (stmt.type) {
|
|
25
|
+
case 'VarDecl':
|
|
26
|
+
return `${this.pad()}const ${stmt.name} = ${this.genExpression(stmt.init)};`;
|
|
27
|
+
case 'FunctionDecl': {
|
|
28
|
+
const params = stmt.params.join(', ');
|
|
29
|
+
const body = this.genBlock(stmt.body);
|
|
30
|
+
return `${this.pad()}function ${stmt.name}(${params}) ${body}`;
|
|
31
|
+
}
|
|
32
|
+
case 'ReturnStmt':
|
|
33
|
+
return stmt.value
|
|
34
|
+
? `${this.pad()}return ${this.genExpression(stmt.value)};`
|
|
35
|
+
: `${this.pad()}return;`;
|
|
36
|
+
case 'TalkStmt':
|
|
37
|
+
return `${this.pad()}__rtj.talk(${this.genExpression(stmt.value)});`;
|
|
38
|
+
case 'IfStmt': {
|
|
39
|
+
let result = `${this.pad()}if (${this.genExpression(stmt.condition)}) ${this.genBlock(stmt.consequent)}`;
|
|
40
|
+
if (stmt.alternate) {
|
|
41
|
+
result += ` else ${this.genBlock(stmt.alternate)}`;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
case 'LoopStmt':
|
|
46
|
+
return `${this.pad()}for (const ${stmt.variable} of ${this.genExpression(stmt.iterable)}) ${this.genBlock(stmt.body)}`;
|
|
47
|
+
case 'ImportStmt': {
|
|
48
|
+
const names = stmt.names.join(', ');
|
|
49
|
+
return `${this.pad()}const { ${names} } = __modules["${stmt.source}"];`;
|
|
50
|
+
}
|
|
51
|
+
case 'ThrowStmt':
|
|
52
|
+
return `${this.pad()}throw new Error(${this.genExpression(stmt.value)});`;
|
|
53
|
+
case 'ExpressionStmt':
|
|
54
|
+
return `${this.pad()}${this.genExpression(stmt.expr)};`;
|
|
55
|
+
case 'BlockStmt':
|
|
56
|
+
return this.genBlock(stmt);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
genBlock(block) {
|
|
60
|
+
const lines = ['{'];
|
|
61
|
+
this.indent++;
|
|
62
|
+
for (const stmt of block.body) {
|
|
63
|
+
lines.push(this.genStatement(stmt));
|
|
64
|
+
}
|
|
65
|
+
this.indent--;
|
|
66
|
+
lines.push(`${this.pad()}}`);
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
genExpression(expr) {
|
|
70
|
+
switch (expr.type) {
|
|
71
|
+
case 'Identifier':
|
|
72
|
+
return expr.name;
|
|
73
|
+
case 'StringLiteral':
|
|
74
|
+
return JSON.stringify(expr.value);
|
|
75
|
+
case 'NumberLiteral':
|
|
76
|
+
return String(expr.value);
|
|
77
|
+
case 'BooleanLiteral':
|
|
78
|
+
return String(expr.value);
|
|
79
|
+
case 'NullLiteral':
|
|
80
|
+
return 'null';
|
|
81
|
+
case 'ArrayLiteral':
|
|
82
|
+
return `[${expr.elements.map(e => this.genExpression(e)).join(', ')}]`;
|
|
83
|
+
case 'ObjectLiteral': {
|
|
84
|
+
const pairs = expr.pairs.map(p => `${JSON.stringify(p.key)}: ${this.genExpression(p.value)}`);
|
|
85
|
+
return `{ ${pairs.join(', ')} }`;
|
|
86
|
+
}
|
|
87
|
+
case 'BinaryExpr':
|
|
88
|
+
return `(${this.genExpression(expr.left)} ${expr.op} ${this.genExpression(expr.right)})`;
|
|
89
|
+
case 'UnaryExpr':
|
|
90
|
+
return `(${expr.op}${this.genExpression(expr.operand)})`;
|
|
91
|
+
case 'CallExpr': {
|
|
92
|
+
const callee = this.genExpression(expr.callee);
|
|
93
|
+
const args = expr.args.map(a => this.genExpression(a)).join(', ');
|
|
94
|
+
return `${callee}(${args})`;
|
|
95
|
+
}
|
|
96
|
+
case 'MemberExpr':
|
|
97
|
+
if (expr.computed) {
|
|
98
|
+
return `${this.genExpression(expr.object)}[${this.genExpression(expr.property)}]`;
|
|
99
|
+
}
|
|
100
|
+
return `${this.genExpression(expr.object)}.${this.genExpression(expr.property)}`;
|
|
101
|
+
case 'PipeExpr':
|
|
102
|
+
return this.flattenPipe(expr.steps);
|
|
103
|
+
case 'DuoExpr': {
|
|
104
|
+
const allSteps = [...expr.mikePipeline, ...expr.elPipeline];
|
|
105
|
+
const initial = this.genExpression(expr.input);
|
|
106
|
+
return this.flattenPipeFromStrings(initial, allSteps);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
flattenPipe(steps) {
|
|
111
|
+
if (steps.length === 0)
|
|
112
|
+
return '';
|
|
113
|
+
if (steps.length === 1)
|
|
114
|
+
return this.genExpression(steps[0]);
|
|
115
|
+
const initial = this.genExpression(steps[0]);
|
|
116
|
+
return this.flattenPipeFromStrings(initial, steps.slice(1));
|
|
117
|
+
}
|
|
118
|
+
flattenPipeFromStrings(initial, fns) {
|
|
119
|
+
let result = initial;
|
|
120
|
+
for (const fn of fns) {
|
|
121
|
+
result = `${this.genExpression(fn)}(${result})`;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.Generator = Generator;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Lexer } from './lexer';
|
|
2
|
+
export { Parser } from './parser';
|
|
3
|
+
export { Semantic } from './semantic';
|
|
4
|
+
export { Generator } from './generator';
|
|
5
|
+
export { RTJError } from './diagnostics';
|
|
6
|
+
export { TokenType } from './token';
|
|
7
|
+
export type { Token } from './token';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenType = exports.RTJError = exports.Generator = exports.Semantic = exports.Parser = exports.Lexer = void 0;
|
|
4
|
+
var lexer_1 = require("./lexer");
|
|
5
|
+
Object.defineProperty(exports, "Lexer", { enumerable: true, get: function () { return lexer_1.Lexer; } });
|
|
6
|
+
var parser_1 = require("./parser");
|
|
7
|
+
Object.defineProperty(exports, "Parser", { enumerable: true, get: function () { return parser_1.Parser; } });
|
|
8
|
+
var semantic_1 = require("./semantic");
|
|
9
|
+
Object.defineProperty(exports, "Semantic", { enumerable: true, get: function () { return semantic_1.Semantic; } });
|
|
10
|
+
var generator_1 = require("./generator");
|
|
11
|
+
Object.defineProperty(exports, "Generator", { enumerable: true, get: function () { return generator_1.Generator; } });
|
|
12
|
+
var diagnostics_1 = require("./diagnostics");
|
|
13
|
+
Object.defineProperty(exports, "RTJError", { enumerable: true, get: function () { return diagnostics_1.RTJError; } });
|
|
14
|
+
var token_1 = require("./token");
|
|
15
|
+
Object.defineProperty(exports, "TokenType", { enumerable: true, get: function () { return token_1.TokenType; } });
|