@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 CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
1
+ # Oryn
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ast.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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;
@@ -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>;