@sheldev/oryn 0.0.0-stage → 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/README.md +73 -2
- package/SPEC.md +143 -0
- package/dist/ast.d.ts +118 -0
- package/dist/ast.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +68 -0
- package/dist/codegen.d.ts +22 -0
- package/dist/codegen.js +165 -0
- package/dist/compiler.d.ts +21 -0
- package/dist/compiler.js +89 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/lexer.d.ts +28 -0
- package/dist/lexer.js +305 -0
- package/dist/parser.d.ts +52 -0
- package/dist/parser.js +518 -0
- package/dist/token.d.ts +16 -0
- package/dist/token.js +9 -0
- package/package.json +35 -4
package/README.md
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Oryn
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Oryn is a small syntax layer over Odin. Oryn source files use `.ory` and are
|
|
4
|
+
transpiled to ordinary `.odin` source files, which are compiled by the native
|
|
5
|
+
Odin compiler.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g oryn
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js 20+ and the [Odin compiler](https://odin-lang.org/) on `PATH`
|
|
14
|
+
(only needed for `oryn build` / `oryn run`).
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
oryn tokenize hello.ory # dump token stream
|
|
20
|
+
oryn parse hello.ory # dump AST as JSON
|
|
21
|
+
oryn transpile hello.ory # Odin source to stdout
|
|
22
|
+
oryn transpile hello.ory -o hi.odin # Odin source to file
|
|
23
|
+
oryn transpile src/ -o out/ # transpile a whole project
|
|
24
|
+
oryn build hello.ory # transpile + invoke `odin build`
|
|
25
|
+
oryn run hello.ory # transpile + `odin run`
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Example input (`hello.ory`):
|
|
29
|
+
|
|
30
|
+
```ory
|
|
31
|
+
import "core:fmt"
|
|
32
|
+
|
|
33
|
+
proc main() {
|
|
34
|
+
var message: string = "Hello from Oryn!"
|
|
35
|
+
fmt.println(message)
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Output (`odin transpile hello.ory`):
|
|
40
|
+
|
|
41
|
+
```odin
|
|
42
|
+
package main
|
|
43
|
+
|
|
44
|
+
import "core:fmt"
|
|
45
|
+
|
|
46
|
+
main :: proc() {
|
|
47
|
+
message: string = "Hello from Oryn!"
|
|
48
|
+
fmt.println(message)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Status
|
|
53
|
+
|
|
54
|
+
Milestone 7: parser, codegen, and CLI. Supports procedures, variables
|
|
55
|
+
(`var` / `let` / `const`), structs, enums, multiple return values, pointers,
|
|
56
|
+
lambdas, `if` / `while` / `for`, and Odin's `or_return` / `or_else` / `or_break`
|
|
57
|
+
error-handling suffixes.
|
|
58
|
+
|
|
59
|
+
See [SPEC.md](SPEC.md) for the language surface and lowering rules.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm install
|
|
65
|
+
npm test
|
|
66
|
+
npm run dev -- transpile examples/hello.ory
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Design principles
|
|
70
|
+
|
|
71
|
+
- Oryn syntax must lower to valid Odin.
|
|
72
|
+
- Native Odin syntax remains accepted where it does not conflict with Oryn.
|
|
73
|
+
- No custom runtime or compiler backend.
|
|
74
|
+
- Newlines and semicolons both separate statements.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Oryn language specification — initial draft
|
|
2
|
+
|
|
3
|
+
**Version:** 0.1-draft
|
|
4
|
+
**Source extension:** `.ory`
|
|
5
|
+
**Target:** Odin source (`.odin`)
|
|
6
|
+
|
|
7
|
+
## Identity and goals
|
|
8
|
+
|
|
9
|
+
Oryn is a small source-to-source syntax layer over Odin. It reuses Odin's type
|
|
10
|
+
system, runtime, standard library, and native compiler. It does not introduce a
|
|
11
|
+
custom runtime or bytecode format.
|
|
12
|
+
|
|
13
|
+
Oryn source is transpiled to ordinary Odin source. Oryn-only syntax is not
|
|
14
|
+
expected to compile directly with the Odin compiler.
|
|
15
|
+
|
|
16
|
+
## Compatibility
|
|
17
|
+
|
|
18
|
+
- Native Odin syntax is accepted wherever it does not conflict with Oryn syntax.
|
|
19
|
+
- Oryn syntax is lowered to valid Odin syntax.
|
|
20
|
+
- Imports, comments, pointers, error-handling constructs, and multiple return
|
|
21
|
+
values retain Odin conventions unless explicitly stated otherwise.
|
|
22
|
+
|
|
23
|
+
## Declarations
|
|
24
|
+
|
|
25
|
+
### Procedures
|
|
26
|
+
|
|
27
|
+
```ory
|
|
28
|
+
proc add(a: int, b: int) -> int {
|
|
29
|
+
return a + b
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
proc greet(name: string) {
|
|
33
|
+
fmt.println("Hello", name)
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Lowering: `proc name(...)` declarations become Odin `name :: proc(...)`.
|
|
38
|
+
Return types may be omitted when the procedure returns no value or when Odin's
|
|
39
|
+
syntax permits inference only through the existing procedure rules. The parser
|
|
40
|
+
must preserve explicit multiple return types, e.g. `-> (int, int)`.
|
|
41
|
+
|
|
42
|
+
Anonymous procedures are supported. Expression lambdas use `=>`, for example
|
|
43
|
+
`(x: int) => x * 2`. Exact lambda lowering is an implementation milestone.
|
|
44
|
+
|
|
45
|
+
### Variables and constants
|
|
46
|
+
|
|
47
|
+
```ory
|
|
48
|
+
var score: int = 0
|
|
49
|
+
let username = "Sheldi"
|
|
50
|
+
const GRID: int = 32
|
|
51
|
+
const PROJECT = "Oryn"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `var` follows JavaScript-style function scoping.
|
|
55
|
+
- `let` follows JavaScript-style block scoping.
|
|
56
|
+
- Both `var` and `let` are mutable.
|
|
57
|
+
- `const` is block-scoped and cannot be reassigned.
|
|
58
|
+
- Type annotations are optional when the initializer provides enough information.
|
|
59
|
+
- Native Odin declarations such as `score := 0` remain accepted.
|
|
60
|
+
|
|
61
|
+
The transpiler must lower these declarations without silently changing their
|
|
62
|
+
observable scope or mutability. `const` lowers to Odin's compile-time constant
|
|
63
|
+
form when the initializer is a valid Odin constant expression.
|
|
64
|
+
|
|
65
|
+
### Structs and literals
|
|
66
|
+
|
|
67
|
+
```ory
|
|
68
|
+
struct Player {
|
|
69
|
+
name: string,
|
|
70
|
+
health: int
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var player: Player = {
|
|
74
|
+
name: "Sheldi",
|
|
75
|
+
health: 100
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A named struct declaration lowers to Odin's `Player :: struct { ... }` form.
|
|
80
|
+
A context-typed object literal lowers to an Odin compound literal, with field
|
|
81
|
+
initializers translated from `:` to `=`. Untyped object literals are not part
|
|
82
|
+
of the initial implementation unless their type can be unambiguously inferred.
|
|
83
|
+
|
|
84
|
+
### Enums and aliases
|
|
85
|
+
|
|
86
|
+
```ory
|
|
87
|
+
enum Direction {
|
|
88
|
+
North,
|
|
89
|
+
South,
|
|
90
|
+
East,
|
|
91
|
+
West
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
enum Status {
|
|
95
|
+
Pending = 0,
|
|
96
|
+
Running = 1,
|
|
97
|
+
Finished = 2
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type Health = int
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Both implicit and explicitly assigned enum members are supported. Type aliases
|
|
104
|
+
use `type Name = Existing_Type`.
|
|
105
|
+
|
|
106
|
+
## Expressions and operators
|
|
107
|
+
|
|
108
|
+
- Odin operators remain available.
|
|
109
|
+
- `not`, `and`, and `or` lower to `!`, `&&`, and `||` respectively.
|
|
110
|
+
- `is` retains Odin's existing type-related meaning; it is not redefined as
|
|
111
|
+
equality. Use `==` and `!=` for equality and inequality.
|
|
112
|
+
- Approximate comparison (`about` / `approx`) is reserved for a later design
|
|
113
|
+
decision because its tolerance and numeric semantics must be specified.
|
|
114
|
+
|
|
115
|
+
## Formatting and separators
|
|
116
|
+
|
|
117
|
+
- Newlines may separate statements.
|
|
118
|
+
- Semicolons may separate multiple statements on one line.
|
|
119
|
+
- Semicolons are optional where statement boundaries are otherwise unambiguous.
|
|
120
|
+
- Odin-style `//` and `/* ... */` comments are retained.
|
|
121
|
+
|
|
122
|
+
## Imports, pointers, and error handling
|
|
123
|
+
|
|
124
|
+
Use Odin's existing syntax, including:
|
|
125
|
+
|
|
126
|
+
```ory
|
|
127
|
+
import "core:fmt"
|
|
128
|
+
var player: ^Player
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Odin error-handling constructs such as `or_return`, `or_else`, and `or_break`
|
|
132
|
+
remain unchanged.
|
|
133
|
+
|
|
134
|
+
## Initial CLI direction
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
oryn tokenize file.ory
|
|
138
|
+
oryn transpile file.ory
|
|
139
|
+
oryn build file.ory
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The first implementation milestone is a lexer. Parsing and Odin code generation
|
|
143
|
+
will follow after tokenization and source-location behavior are tested.
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export type Node = Program | ImportDecl | ProcDecl | VarDecl | StructDecl | EnumDecl | AssignStmt | ReturnStmt | IfStmt | WhileStmt | ForStmt | BlockStmt | BinaryExpr | UnaryExpr | CallExpr | MemberExpr | IdentifierExpr | LiteralExpr | LambdaExpr | OrHandleExpr;
|
|
2
|
+
export interface Program {
|
|
3
|
+
kind: "Program";
|
|
4
|
+
imports: ImportDecl[];
|
|
5
|
+
body: (ProcDecl | VarDecl | StructDecl | EnumDecl)[];
|
|
6
|
+
}
|
|
7
|
+
export interface ImportDecl {
|
|
8
|
+
kind: "ImportDecl";
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
export interface StructField {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
}
|
|
15
|
+
export interface StructDecl {
|
|
16
|
+
kind: "StructDecl";
|
|
17
|
+
name: string;
|
|
18
|
+
fields: StructField[];
|
|
19
|
+
}
|
|
20
|
+
export interface EnumMember {
|
|
21
|
+
name: string;
|
|
22
|
+
value?: Node;
|
|
23
|
+
}
|
|
24
|
+
export interface EnumDecl {
|
|
25
|
+
kind: "EnumDecl";
|
|
26
|
+
name: string;
|
|
27
|
+
members: EnumMember[];
|
|
28
|
+
}
|
|
29
|
+
export interface ProcDecl {
|
|
30
|
+
kind: "ProcDecl";
|
|
31
|
+
name: string;
|
|
32
|
+
params: Param[];
|
|
33
|
+
returnTypes: string[];
|
|
34
|
+
body: BlockStmt;
|
|
35
|
+
}
|
|
36
|
+
export interface Param {
|
|
37
|
+
name: string;
|
|
38
|
+
type: string;
|
|
39
|
+
}
|
|
40
|
+
export interface VarDecl {
|
|
41
|
+
kind: "VarDecl";
|
|
42
|
+
name: string;
|
|
43
|
+
type?: string;
|
|
44
|
+
initializer?: Node;
|
|
45
|
+
mutable: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface AssignStmt {
|
|
48
|
+
kind: "AssignStmt";
|
|
49
|
+
target: Node;
|
|
50
|
+
operator: string;
|
|
51
|
+
value: Node;
|
|
52
|
+
}
|
|
53
|
+
export interface ReturnStmt {
|
|
54
|
+
kind: "ReturnStmt";
|
|
55
|
+
arguments: Node[];
|
|
56
|
+
}
|
|
57
|
+
export interface IfStmt {
|
|
58
|
+
kind: "IfStmt";
|
|
59
|
+
condition: Node;
|
|
60
|
+
consequent: BlockStmt;
|
|
61
|
+
alternate?: BlockStmt | IfStmt;
|
|
62
|
+
}
|
|
63
|
+
export interface WhileStmt {
|
|
64
|
+
kind: "WhileStmt";
|
|
65
|
+
condition: Node;
|
|
66
|
+
body: BlockStmt;
|
|
67
|
+
}
|
|
68
|
+
export interface ForStmt {
|
|
69
|
+
kind: "ForStmt";
|
|
70
|
+
condition: Node;
|
|
71
|
+
body: BlockStmt;
|
|
72
|
+
}
|
|
73
|
+
export interface BlockStmt {
|
|
74
|
+
kind: "BlockStmt";
|
|
75
|
+
statements: Node[];
|
|
76
|
+
}
|
|
77
|
+
export interface BinaryExpr {
|
|
78
|
+
kind: "BinaryExpr";
|
|
79
|
+
left: Node;
|
|
80
|
+
operator: string;
|
|
81
|
+
right: Node;
|
|
82
|
+
}
|
|
83
|
+
export interface UnaryExpr {
|
|
84
|
+
kind: "UnaryExpr";
|
|
85
|
+
operator: string;
|
|
86
|
+
argument: Node;
|
|
87
|
+
}
|
|
88
|
+
export interface CallExpr {
|
|
89
|
+
kind: "CallExpr";
|
|
90
|
+
callee: Node;
|
|
91
|
+
args: Node[];
|
|
92
|
+
}
|
|
93
|
+
export interface MemberExpr {
|
|
94
|
+
kind: "MemberExpr";
|
|
95
|
+
object: Node;
|
|
96
|
+
property: string;
|
|
97
|
+
}
|
|
98
|
+
export interface IdentifierExpr {
|
|
99
|
+
kind: "IdentifierExpr";
|
|
100
|
+
name: string;
|
|
101
|
+
}
|
|
102
|
+
export interface LiteralExpr {
|
|
103
|
+
kind: "LiteralExpr";
|
|
104
|
+
value: string | number | boolean;
|
|
105
|
+
raw: string;
|
|
106
|
+
type: "string" | "number" | "boolean";
|
|
107
|
+
}
|
|
108
|
+
export interface LambdaExpr {
|
|
109
|
+
kind: "LambdaExpr";
|
|
110
|
+
params: Param[];
|
|
111
|
+
body: Node;
|
|
112
|
+
}
|
|
113
|
+
export interface OrHandleExpr {
|
|
114
|
+
kind: "OrHandleExpr";
|
|
115
|
+
expression: Node;
|
|
116
|
+
operator: "or_return" | "or_else" | "or_break";
|
|
117
|
+
fallback?: Node;
|
|
118
|
+
}
|
package/dist/ast.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { parse } from "./parser.js";
|
|
5
|
+
import { transpileFile, buildWithOdin, transpileDirectory, } from "./compiler.js";
|
|
6
|
+
function usage() {
|
|
7
|
+
console.error("Usage:\n" +
|
|
8
|
+
" oryn tokenize <file.ory>\n" +
|
|
9
|
+
" oryn parse <file.ory>\n" +
|
|
10
|
+
" oryn transpile <file.ory|dir> [-o <out.odin|outdir>]\n" +
|
|
11
|
+
" oryn build <file.ory> [-o <out.bin>]\n" +
|
|
12
|
+
" oryn run <file.ory>");
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const command = args[0];
|
|
17
|
+
const input = args[1];
|
|
18
|
+
if (!command || !input)
|
|
19
|
+
usage();
|
|
20
|
+
const outIndex = args.indexOf("-o");
|
|
21
|
+
const outPath = outIndex !== -1 ? args[outIndex + 1] : undefined;
|
|
22
|
+
try {
|
|
23
|
+
if (command === "tokenize") {
|
|
24
|
+
const source = await readFile(input, "utf8");
|
|
25
|
+
const { tokenize } = await import("./lexer.js");
|
|
26
|
+
const tokens = tokenize(source);
|
|
27
|
+
for (const token of tokens) {
|
|
28
|
+
const location = `${token.start.line}:${token.start.column}`;
|
|
29
|
+
console.log(`${location.padEnd(8)} ${token.kind.padEnd(12)} ${JSON.stringify(token.value)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (command === "parse") {
|
|
33
|
+
const source = await readFile(input, "utf8");
|
|
34
|
+
const ast = parse(source);
|
|
35
|
+
console.log(JSON.stringify(ast, null, 2));
|
|
36
|
+
}
|
|
37
|
+
else if (command === "transpile") {
|
|
38
|
+
const info = await stat(input);
|
|
39
|
+
if (info.isDirectory()) {
|
|
40
|
+
const outDir = outPath ?? join(dirname(input), "odin");
|
|
41
|
+
const { count } = await transpileDirectory(input, outDir);
|
|
42
|
+
console.log(`Transpiled ${count} file(s) from ${input} -> ${outDir}`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const odinCode = await transpileFile(input, outPath);
|
|
46
|
+
if (!outPath) {
|
|
47
|
+
process.stdout.write(odinCode);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(`Transpiled ${input} -> ${outPath}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (command === "build") {
|
|
55
|
+
await buildWithOdin(input, { outputBinary: outPath, run: false });
|
|
56
|
+
}
|
|
57
|
+
else if (command === "run") {
|
|
58
|
+
await buildWithOdin(input, { run: true });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
usage();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as AST from "./ast.js";
|
|
2
|
+
export interface CodegenOptions {
|
|
3
|
+
packageName?: string;
|
|
4
|
+
includePackageHeader?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class CodeGenerator {
|
|
7
|
+
private indentLevel;
|
|
8
|
+
generate(program: AST.Program, options?: CodegenOptions): string;
|
|
9
|
+
private generateNode;
|
|
10
|
+
private generateOrHandleExpr;
|
|
11
|
+
private mapOperator;
|
|
12
|
+
private generateStructDecl;
|
|
13
|
+
private generateEnumDecl;
|
|
14
|
+
private generateProcDecl;
|
|
15
|
+
private generateLambdaExpr;
|
|
16
|
+
private generateVarDecl;
|
|
17
|
+
private generateIfStmt;
|
|
18
|
+
private generateWhileStmt;
|
|
19
|
+
private generateForStmt;
|
|
20
|
+
private generateBlockStmt;
|
|
21
|
+
}
|
|
22
|
+
export declare function transpile(program: AST.Program, options?: CodegenOptions): string;
|
package/dist/codegen.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export class CodeGenerator {
|
|
2
|
+
indentLevel = 0;
|
|
3
|
+
generate(program, options = {}) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
const includePackage = options.includePackageHeader ?? true;
|
|
6
|
+
const packageName = options.packageName ?? "main";
|
|
7
|
+
if (includePackage) {
|
|
8
|
+
lines.push(`package ${packageName}`);
|
|
9
|
+
lines.push("");
|
|
10
|
+
}
|
|
11
|
+
for (const imp of program.imports) {
|
|
12
|
+
lines.push(`import ${imp.path}`);
|
|
13
|
+
}
|
|
14
|
+
if (program.imports.length > 0 && program.body.length > 0) {
|
|
15
|
+
lines.push("");
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < program.body.length; i++) {
|
|
18
|
+
lines.push(this.generateNode(program.body[i]));
|
|
19
|
+
if (i < program.body.length - 1) {
|
|
20
|
+
lines.push("");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join("\n") + "\n";
|
|
24
|
+
}
|
|
25
|
+
generateNode(node) {
|
|
26
|
+
switch (node.kind) {
|
|
27
|
+
case "ProcDecl":
|
|
28
|
+
return this.generateProcDecl(node);
|
|
29
|
+
case "StructDecl":
|
|
30
|
+
return this.generateStructDecl(node);
|
|
31
|
+
case "EnumDecl":
|
|
32
|
+
return this.generateEnumDecl(node);
|
|
33
|
+
case "VarDecl":
|
|
34
|
+
return this.generateVarDecl(node);
|
|
35
|
+
case "AssignStmt":
|
|
36
|
+
return `${this.generateNode(node.target)} ${node.operator} ${this.generateNode(node.value)}`;
|
|
37
|
+
case "ReturnStmt":
|
|
38
|
+
if (node.arguments.length === 0)
|
|
39
|
+
return "return";
|
|
40
|
+
return `return ${node.arguments.map(arg => this.generateNode(arg)).join(", ")}`;
|
|
41
|
+
case "IfStmt":
|
|
42
|
+
return this.generateIfStmt(node);
|
|
43
|
+
case "WhileStmt":
|
|
44
|
+
return this.generateWhileStmt(node);
|
|
45
|
+
case "ForStmt":
|
|
46
|
+
return this.generateForStmt(node);
|
|
47
|
+
case "BlockStmt":
|
|
48
|
+
return this.generateBlockStmt(node);
|
|
49
|
+
case "BinaryExpr":
|
|
50
|
+
return `${this.generateNode(node.left)} ${this.mapOperator(node.operator)} ${this.generateNode(node.right)}`;
|
|
51
|
+
case "UnaryExpr":
|
|
52
|
+
return `${this.mapOperator(node.operator)}${this.generateNode(node.argument)}`;
|
|
53
|
+
case "CallExpr":
|
|
54
|
+
return `${this.generateNode(node.callee)}(${node.args.map(a => this.generateNode(a)).join(", ")})`;
|
|
55
|
+
case "MemberExpr":
|
|
56
|
+
return `${this.generateNode(node.object)}.${node.property}`;
|
|
57
|
+
case "IdentifierExpr":
|
|
58
|
+
return node.name;
|
|
59
|
+
case "LiteralExpr":
|
|
60
|
+
return node.raw;
|
|
61
|
+
case "LambdaExpr":
|
|
62
|
+
return this.generateLambdaExpr(node);
|
|
63
|
+
case "OrHandleExpr":
|
|
64
|
+
return this.generateOrHandleExpr(node);
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unknown AST node kind: ${node.kind}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
generateOrHandleExpr(node) {
|
|
70
|
+
const expr = this.generateNode(node.expression);
|
|
71
|
+
if (node.operator === "or_else" && node.fallback) {
|
|
72
|
+
return `${expr} ${node.operator} ${this.generateNode(node.fallback)}`;
|
|
73
|
+
}
|
|
74
|
+
return `${expr} ${node.operator}`;
|
|
75
|
+
}
|
|
76
|
+
mapOperator(op) {
|
|
77
|
+
if (op === "and")
|
|
78
|
+
return "&&";
|
|
79
|
+
if (op === "or")
|
|
80
|
+
return "||";
|
|
81
|
+
if (op === "not")
|
|
82
|
+
return "!";
|
|
83
|
+
return op;
|
|
84
|
+
}
|
|
85
|
+
generateStructDecl(node) {
|
|
86
|
+
const innerIndent = " ".repeat(this.indentLevel + 1);
|
|
87
|
+
const fields = node.fields.map(f => `${innerIndent}${f.name}: ${f.type},`);
|
|
88
|
+
return `${node.name} :: struct {\n${fields.join("\n")}\n}`;
|
|
89
|
+
}
|
|
90
|
+
generateEnumDecl(node) {
|
|
91
|
+
const innerIndent = " ".repeat(this.indentLevel + 1);
|
|
92
|
+
const members = node.members.map(m => m.value ? `${innerIndent}${m.name} = ${this.generateNode(m.value)},` : `${innerIndent}${m.name},`);
|
|
93
|
+
return `${node.name} :: enum {\n${members.join("\n")}\n}`;
|
|
94
|
+
}
|
|
95
|
+
generateProcDecl(node) {
|
|
96
|
+
const params = node.params.map(p => `${p.name}: ${p.type}`).join(", ");
|
|
97
|
+
let ret = "";
|
|
98
|
+
if (node.returnTypes.length === 1) {
|
|
99
|
+
ret = ` -> ${node.returnTypes[0]}`;
|
|
100
|
+
}
|
|
101
|
+
else if (node.returnTypes.length > 1) {
|
|
102
|
+
ret = ` -> (${node.returnTypes.join(", ")})`;
|
|
103
|
+
}
|
|
104
|
+
const body = this.generateBlockStmt(node.body);
|
|
105
|
+
return `${node.name} :: proc(${params})${ret} ${body}`;
|
|
106
|
+
}
|
|
107
|
+
generateLambdaExpr(node) {
|
|
108
|
+
const params = node.params.map(p => `${p.name}: ${p.type}`).join(", ");
|
|
109
|
+
const bodyStr = node.body.kind === "BlockStmt" ? this.generateBlockStmt(node.body) : `{ return ${this.generateNode(node.body)} }`;
|
|
110
|
+
return `proc(${params}) ${bodyStr}`;
|
|
111
|
+
}
|
|
112
|
+
generateVarDecl(node) {
|
|
113
|
+
if (!node.mutable && node.initializer) {
|
|
114
|
+
const typePart = node.type ? `: ${node.type} : ` : " :: ";
|
|
115
|
+
return `${node.name}${typePart}${this.generateNode(node.initializer)}`;
|
|
116
|
+
}
|
|
117
|
+
if (node.type) {
|
|
118
|
+
const initPart = node.initializer ? ` = ${this.generateNode(node.initializer)}` : "";
|
|
119
|
+
return `${node.name}: ${node.type}${initPart}`;
|
|
120
|
+
}
|
|
121
|
+
if (node.initializer) {
|
|
122
|
+
return `${node.name} := ${this.generateNode(node.initializer)}`;
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`Variable declaration '${node.name}' requires either a type or an initializer`);
|
|
125
|
+
}
|
|
126
|
+
generateIfStmt(node) {
|
|
127
|
+
const cond = this.generateNode(node.condition);
|
|
128
|
+
const consequent = this.generateBlockStmt(node.consequent);
|
|
129
|
+
let result = `if ${cond} ${consequent}`;
|
|
130
|
+
if (node.alternate) {
|
|
131
|
+
if (node.alternate.kind === "IfStmt") {
|
|
132
|
+
result += ` else ${this.generateIfStmt(node.alternate)}`;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result += ` else ${this.generateBlockStmt(node.alternate)}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
generateWhileStmt(node) {
|
|
141
|
+
const cond = this.generateNode(node.condition);
|
|
142
|
+
const body = this.generateBlockStmt(node.body);
|
|
143
|
+
return `for ${cond} ${body}`;
|
|
144
|
+
}
|
|
145
|
+
generateForStmt(node) {
|
|
146
|
+
const cond = this.generateNode(node.condition);
|
|
147
|
+
const body = this.generateBlockStmt(node.body);
|
|
148
|
+
return `for ${cond} ${body}`;
|
|
149
|
+
}
|
|
150
|
+
generateBlockStmt(node) {
|
|
151
|
+
const indent = " ".repeat(this.indentLevel);
|
|
152
|
+
const innerIndent = " ".repeat(this.indentLevel + 1);
|
|
153
|
+
this.indentLevel++;
|
|
154
|
+
const statements = node.statements.map(s => `${innerIndent}${this.generateNode(s)}`);
|
|
155
|
+
this.indentLevel--;
|
|
156
|
+
if (statements.length === 0) {
|
|
157
|
+
return "{}";
|
|
158
|
+
}
|
|
159
|
+
return `{\n${statements.join("\n")}\n${indent}}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function transpile(program, options) {
|
|
163
|
+
return new CodeGenerator().generate(program, options);
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=codegen.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CodegenOptions } from "./codegen.js";
|
|
2
|
+
export declare function compileSource(source: string, options?: CodegenOptions): string;
|
|
3
|
+
export declare function transpileFile(inputPath: string, outputPath?: string): Promise<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Recursively transpile every `.ory` file under `inputDir` into `outputDir`,
|
|
6
|
+
* preserving the relative directory layout and swapping the `.ory` extension
|
|
7
|
+
* for `.odin`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function transpileDirectory(inputDir: string, outputDir: string): Promise<{
|
|
10
|
+
count: number;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Transpile `inputPath` and hand the generated Odin source to the native
|
|
14
|
+
* `odin` compiler. `-file` tells Odin to treat a single file as a
|
|
15
|
+
* self-contained package, which is exactly what we want for a single Oryn
|
|
16
|
+
* source file.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildWithOdin(inputPath: string, options?: {
|
|
19
|
+
outputBinary?: string;
|
|
20
|
+
run?: boolean;
|
|
21
|
+
}): Promise<void>;
|