@user-synax/synax 0.0.1

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/synax.html ADDED
@@ -0,0 +1,281 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Synax — a small scripting language that compiles to JavaScript</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1115; --panel: #161a22; --border: #2a3040;
10
+ --text: #e6e9f0; --muted: #9aa3b5; --accent: #7aa2f7;
11
+ --error: #f7768e; --ok: #9ece6a; --mono: ui-monospace, "Cascadia Code", Consolas, monospace;
12
+ }
13
+ * { box-sizing: border-box; }
14
+ body { margin: 0; background: var(--bg); color: var(--text);
15
+ font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif; }
16
+ main { max-width: 980px; margin: 0 auto; padding: 24px 20px 80px; }
17
+ h1 { font-size: 2rem; margin: 0 0 4px; }
18
+ h1 .accent { color: var(--accent); }
19
+ .tagline { color: var(--muted); margin: 0 0 28px; }
20
+ h2 { font-size: 1.25rem; margin: 36px 0 12px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
21
+ code, pre { font-family: var(--mono); font-size: 0.92em; }
22
+ code { background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
23
+ pre { background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
24
+ padding: 12px 14px; overflow-x: auto; line-height: 1.5; }
25
+ table { border-collapse: collapse; width: 100%; font-size: 0.95em; }
26
+ th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border); }
27
+ th { color: var(--muted); font-weight: 600; }
28
+ td code { white-space: nowrap; }
29
+ .playground { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px; }
30
+ .pg-bar { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
31
+ .pg-bar label { color: var(--muted); font-size: 0.9em; }
32
+ select, button {
33
+ font: inherit; background: var(--bg); color: var(--text);
34
+ border: 1px solid var(--border); border-radius: 6px; padding: 6px 12px;
35
+ }
36
+ button { background: var(--accent); color: #0b0e14; font-weight: 600; cursor: pointer; border: none; }
37
+ .panes { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
38
+ @media (max-width: 760px) { .panes { grid-template-columns: 1fr; } }
39
+ .pane { display: flex; flex-direction: column; min-width: 0; }
40
+ .pane h3 { margin: 0 0 6px; font-size: 0.85rem; color: var(--muted);
41
+ text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; }
42
+ textarea, .output, .js, .diagnostic {
43
+ font-family: var(--mono); font-size: 13.5px; line-height: 1.5;
44
+ background: var(--bg); color: var(--text);
45
+ border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px;
46
+ white-space: pre; overflow: auto;
47
+ }
48
+ textarea { width: 100%; height: 300px; resize: vertical; }
49
+ .output, .js { min-height: 120px; max-height: 300px; }
50
+ .diagnostic { border-color: var(--error); color: var(--error); margin-top: 10px; display: none; }
51
+ .diagnostic.error { display: block; }
52
+ .footnote { color: var(--muted); font-size: 0.9em; margin-top: 8px; }
53
+ details summary { cursor: pointer; color: var(--accent); }
54
+ details pre { margin-top: 8px; }
55
+ </style>
56
+ </head>
57
+ <body>
58
+ <main>
59
+ <h1><span class="accent">synax</span> — no braces, no semicolons</h1>
60
+ <p class="tagline">A small scripting language that compiles to plain, readable JavaScript.
61
+ Reads closer to plain English than Python.</p>
62
+
63
+ <h2>Try it right here</h2>
64
+ <div class="playground">
65
+ <div class="pg-bar">
66
+ <label for="examples">Examples:</label>
67
+ <select id="examples">
68
+ <option>Hello World</option>
69
+ <option>Variables</option>
70
+ <option>If / else</option>
71
+ <option>Function</option>
72
+ <option>Loop</option>
73
+ <option>Fibonacci</option>
74
+ <option>FizzBuzz</option>
75
+ </select>
76
+ <button id="run" type="button">Run ▶</button>
77
+ <span class="footnote">The full compiler is inlined in this page — everything runs locally.</span>
78
+ </div>
79
+ <div class="panes">
80
+ <div class="pane">
81
+ <h3>program.snx</h3>
82
+ <textarea id="editor" spellcheck="false"></textarea>
83
+ </div>
84
+ <div class="pane">
85
+ <h3>JavaScript</h3>
86
+ <div id="js" class="js"></div>
87
+ </div>
88
+ </div>
89
+ <div id="diagnostic" class="diagnostic"></div>
90
+ <h3 style="margin-top:12px">Output</h3>
91
+ <div id="output" class="output">(press Run)</div>
92
+ </div>
93
+
94
+ <h2>Install &amp; run locally</h2>
95
+ <pre>npm install synax # or: bun add synax
96
+ synax program.snx # prints JavaScript to stdout
97
+ synax program.snx | node # compile and run it</pre>
98
+
99
+ <h2>The language in one table</h2>
100
+ <table>
101
+ <tr><th>Construct</th><th>Syntax</th><th>Compiles to</th></tr>
102
+ <tr><td>Declare / assign</td><td><code>set x = 5</code></td><td><code>let x = 5;</code> (later <code>set</code>s reassign)</td></tr>
103
+ <tr><td>Print</td><td><code>print e</code></td><td><code>console.log(e);</code></td></tr>
104
+ <tr><td>If / else</td><td><code>if c … else … end</code></td><td><code>if (c) { … } else { … }</code></td></tr>
105
+ <tr><td>Function</td><td><code>fn f(a, b) … end</code></td><td><code>function f(a, b) { … }</code></td></tr>
106
+ <tr><td>Loop</td><td><code>for i in 1..5 … end</code></td><td><code>for (let i = 1; i &lt;= 5; i++) { … }</code> — inclusive</td></tr>
107
+ <tr><td>Call</td><td><code>f(x, y)</code></td><td><code>f(x, y);</code></td></tr>
108
+ <tr><td>Comment</td><td><code># line &nbsp;/# block #/</code></td><td>stripped</td></tr>
109
+ <tr><td>Strings</td><td><code>"a\nb"</code></td><td><code>\n \t \" \\</code> escapes decoded</td></tr>
110
+ <tr><td>Operators</td><td><code>+ - * / == != &gt; &lt; &gt;= &lt;=</code></td><td>same precedence as JS; <code>+</code> coerces like JS</td></tr>
111
+ </table>
112
+
113
+ <h2>From source</h2>
114
+ <pre># clone the repo, then:
115
+ bun install
116
+ bun test # 158 tests
117
+ bun run src/cli.ts examples/fizzbuzz.snx
118
+ bun run scripts/build-docs.ts # regenerate this page</pre>
119
+
120
+ <p class="footnote">Spec: <code>SPEC.md</code> · License: MIT · v0.0.1</p>
121
+ </main>
122
+ <script>(()=>{var C=Object.defineProperty;var O=(F)=>F;function P(F,j){this[F]=O.bind(null,j)}var L=(F,j)=>{for(var z in j)C(F,z,{get:j[z],enumerable:!0,configurable:!0,set:P.bind(j,z)})};var V={};L(V,{generate:()=>w,formatDiagnostic:()=>I,ParserError:()=>f,Parser:()=>K,LexerError:()=>$,Lexer:()=>G});var R={"==":1,"!=":1,">":1,"<":1,">=":1,"<=":1,"+":2,"-":2,"*":3,"/":3};function w(F){return new x().program(F)}class x{indentLevel=0;scope=new Set;program(F){return F.body.map((j)=>this.statement(j)).join(`
123
+ `)}statement(F){switch(F.kind){case"VarDecl":return this.varDecl(F);case"PrintStmt":return this.printStmt(F);case"IfStmt":return this.ifStmt(F);case"FnDecl":return this.fnDecl(F);case"ForStmt":return this.forStmt(F);case"ExprStmt":return`${this.indent()}${this.expression(F.expression)};`}}varDecl(F){let j=this.expression(F.value);if(this.scope.has(F.name))return`${this.indent()}${F.name} = ${j};`;return this.scope.add(F.name),`${this.indent()}let ${F.name} = ${j};`}printStmt(F){return`${this.indent()}console.log(${this.expression(F.argument)});`}ifStmt(F){let z=`${this.indent()}if (${this.expression(F.condition)}) `+this.block(F.thenBranch);if(F.elseBranch!==null)z+=" else "+this.block(F.elseBranch);return z}fnDecl(F){let j=`${this.indent()}function ${F.name}(${F.params.join(", ")}) `,z=this.scope;this.scope=new Set(F.params);let J=this.block(F.body);return this.scope=z,j+J}forStmt(F){let{variable:j}=F,z=this.expression(F.start),J=this.expression(F.end);return`${this.indent()}for (let ${j} = ${z}; ${j} <= ${J}; ${j}++) `+this.block(F.body)}expression(F){switch(F.kind){case"NumberLiteral":return String(F.value);case"StringLiteral":return JSON.stringify(F.value);case"Identifier":return F.name;case"BinaryExpr":return this.binary(F,0,!1);case"FunctionCall":return`${F.callee}(${F.args.map((j)=>this.expression(j)).join(", ")})`}}binary(F,j,z){let J=R[F.operator],Q=this.operand(F.left,J,!1),X=this.operand(F.right,J,!0),Z=`${Q} ${F.operator} ${X}`;return J<j||J===j&&z?`(${Z})`:Z}operand(F,j,z){if(F.kind==="BinaryExpr")return this.binary(F,j,z);return this.expression(F)}block(F){if(F.length===0)return"{}";this.indentLevel++;let j=F.map((z)=>this.statement(z)).join(`
124
+ `);return this.indentLevel--,`{
125
+ ${j}
126
+ ${this.indent()}}`}indent(){return" ".repeat(this.indentLevel)}}function I(F,j){let{file:z,line:J,column:Q,length:X,reason:Z}=F,B=`${z}:${J}:${Q}: error: ${Z}`,M=j.split(/\r?\n/)[J-1];if(M===void 0||M.trim()==="")return B;let W=Math.max(0,Math.min(Q-1,M.length)),N=Math.max(1,Math.min(X,M.length-W)),S=String(J),_=" ".repeat(S.length),D=M.slice(0,W).replace(/[^\t]/g," "),b=` ${S} | ${M}`,v=` ${_} | ${D}${"^".repeat(N)}`;return`${B}
127
+ ${b}
128
+ ${v}`}var q={SET:"SET",PRINT:"PRINT",IF:"IF",ELSE:"ELSE",END:"END",FN:"FN",FOR:"FOR",IN:"IN",NUMBER:"NUMBER",STRING:"STRING",IDENT:"IDENT",EQUALS:"EQUALS",PLUS:"PLUS",MINUS:"MINUS",STAR:"STAR",SLASH:"SLASH",EQEQ:"EQEQ",BANGEQ:"BANGEQ",GT:"GT",LT:"LT",GTEQ:"GTEQ",LTEQ:"LTEQ",DOTDOT:"DOTDOT",LPAREN:"LPAREN",RPAREN:"RPAREN",COMMA:"COMMA",NEWLINE:"NEWLINE",EOF:"EOF"};class $ extends Error{line;column;length;reason;constructor(F,j,z,J=1){super(`${F} (line ${j})`);this.name="LexerError",this.reason=F,this.line=j,this.column=z,this.length=J}}var g=new Map([["set",q.SET],["print",q.PRINT],["if",q.IF],["else",q.ELSE],["end",q.END],["fn",q.FN],["for",q.FOR],["in",q.IN]]),u=new Map([["==",q.EQEQ],["!=",q.BANGEQ],[">=",q.GTEQ],["<=",q.LTEQ],["..",q.DOTDOT]]),E=new Map([["=",q.EQUALS],["+",q.PLUS],["-",q.MINUS],["*",q.STAR],["/",q.SLASH],[">",q.GT],["<",q.LT],["(",q.LPAREN],[")",q.RPAREN],[",",q.COMMA]]),m=new Map([["n",`
129
+ `],["t","\t"],['"','"'],["\\","\\"]]);function U(F){return F!==void 0&&F>="0"&&F<="9"}function l(F){return F!==void 0&&(F>="a"&&F<="z"||F>="A"&&F<="Z")}function A(F){return l(F)||F==="_"}function h(F){return A(F)||U(F)}class G{source;index=0;line=1;lineStart=0;constructor(F){this.source=F}get column(){return this.index-this.lineStart+1}tokenize(){this.index=0,this.line=1,this.lineStart=0;let F=[];while(!0){let j=this.source[this.index];if(j===void 0)break;if(j===" "||j==="\t"||j==="\r"){this.index++;continue}if(j===`
130
+ `){F.push({type:q.NEWLINE,lexeme:`
131
+ `,line:this.line,column:this.column}),this.index++,this.line++,this.lineStart=this.index;continue}if(j==="#"){this.skipLineComment();continue}if(j==="/"&&this.source[this.index+1]==="#"){this.skipBlockComment();continue}if(j==='"'){F.push(this.readString());continue}if(U(j)){F.push(this.readNumber());continue}if(A(j)){F.push(this.readIdentifier());continue}let z=this.readOperator();if(z!==void 0){F.push(z);continue}throw new $(`Unexpected character ${JSON.stringify(j)}`,this.line,this.column)}return F.push({type:q.EOF,lexeme:"",line:this.line,column:this.column}),F}skipLineComment(){this.index++;while(!0){let F=this.source[this.index];if(F===void 0||F===`
132
+ `)return;this.index++}}skipBlockComment(){let F=this.line,j=this.column;this.index+=2;while(!0){let z=this.source[this.index];if(z===void 0)throw new $("Unterminated multi-line comment",F,j,2);if(z==="#"&&this.source[this.index+1]==="/"){this.index+=2;return}if(z===`
133
+ `){this.line++,this.index++,this.lineStart=this.index;continue}this.index++}}readString(){let F=this.line,j=this.column,z=this.index;this.index++;let J="";while(!0){let Q=this.source[this.index];if(Q===void 0||Q===`
134
+ `)throw new $("Unterminated string",F,j);if(Q==='"'){this.index++;let X=this.source.slice(z,this.index);return{type:q.STRING,lexeme:X,value:J,line:F,column:j}}if(Q==="\\"){let X=this.source[this.index+1],Z=X===void 0?void 0:m.get(X);if(Z===void 0)throw new $(`Invalid escape sequence \\${X??""}`,this.line,this.column,2);J+=Z,this.index+=2;continue}J+=Q,this.index++}}readNumber(){let F=this.line,j=this.column,z=this.index;while(U(this.source[this.index]))this.index++;if(this.source[this.index]==="."&&U(this.source[this.index+1])){this.index++;while(U(this.source[this.index]))this.index++}let J=this.source.slice(z,this.index);return{type:q.NUMBER,lexeme:J,value:Number(J),line:F,column:j}}readIdentifier(){let F=this.line,j=this.column,z=this.index;while(h(this.source[this.index]))this.index++;let J=this.source.slice(z,this.index);return{type:g.get(J)??q.IDENT,lexeme:J,line:F,column:j}}readOperator(){let F=this.line,j=this.column,z=this.source.slice(this.index,this.index+2),J=u.get(z);if(J!==void 0)return this.index+=2,{type:J,lexeme:z,line:F,column:j};let Q=this.source[this.index];if(Q===void 0)return;let X=E.get(Q);if(X===void 0)return;return this.index+=1,{type:X,lexeme:Q,line:F,column:j}}}class f extends Error{line;column;length;reason;constructor(F,j,z,J=1){super(`${F} (line ${j})`);this.name="ParserError",this.reason=F,this.line=j,this.column=z,this.length=J}}var s=new Map([[q.GTEQ,">="],[q.LTEQ,"<="],[q.GT,">"],[q.LT,"<"],[q.EQEQ,"=="],[q.BANGEQ,"!="]]),i=new Map([[q.PLUS,"+"],[q.MINUS,"-"]]),y=new Map([[q.STAR,"*"],[q.SLASH,"/"]]),H=[q.END],c=[q.ELSE,q.END];function Y(F){switch(F.type){case q.EOF:return"end of input";case q.NEWLINE:return"end of line";default:return`'${F.lexeme}'`}}class K{tokens;index=0;constructor(F=[]){this.tokens=F}parse(){this.index=0;let F=this.peek(),j=this.parseStatements([]),z=this.peek();if(z.type!==q.EOF)throw this.error(z,`Unexpected ${Y(z)} after program`);return{kind:"Program",body:j,line:F.line}}parseStatements(F){let j=[];while(!0){this.skipNewlines();let z=this.peek();if(z.type===q.EOF||F.includes(z.type))return j;j.push(this.parseStatement());let J=this.peek();if(J.type!==q.NEWLINE&&J.type!==q.EOF&&!F.includes(J.type))throw this.error(J,`Expected end of statement but found ${Y(J)}`)}}parseStatement(){switch(this.peek().type){case q.SET:return this.parseVarDecl();case q.PRINT:return this.parsePrintStmt();case q.IF:return this.parseIfStmt();case q.FN:return this.parseFnDecl();case q.FOR:return this.parseForStmt();default:return this.parseExprStmt()}}parseVarDecl(){let F=this.expect(q.SET,"'set'"),j=this.expect(q.IDENT,"a variable name after 'set'");this.expect(q.EQUALS,"'=' after the variable name");let z=this.parseExpression();return{kind:"VarDecl",name:j.lexeme,value:z,line:F.line}}parsePrintStmt(){let F=this.expect(q.PRINT,"'print'");return{kind:"PrintStmt",argument:this.parseExpression(),line:F.line}}parseIfStmt(){let F=this.expect(q.IF,"'if'"),j=this.parseExpression(),z=this.parseStatements(c),J=null;if(this.peek().type===q.ELSE)this.advance(),J=this.parseStatements(H);return this.expect(q.END,"'end' to close the 'if'"),{kind:"IfStmt",condition:j,thenBranch:z,elseBranch:J,line:F.line}}parseFnDecl(){let F=this.expect(q.FN,"'fn'"),j=this.expect(q.IDENT,"a function name after 'fn'");this.expect(q.LPAREN,"'(' after the function name");let z=[];if(this.peek().type!==q.RPAREN){z.push(this.expect(q.IDENT,"a parameter name").lexeme);while(this.peek().type===q.COMMA)this.advance(),z.push(this.expect(q.IDENT,"a parameter name").lexeme)}this.expect(q.RPAREN,"')' after the parameter list");let J=this.parseStatements(H);return this.expect(q.END,"'end' to close the function body"),{kind:"FnDecl",name:j.lexeme,params:z,body:J,line:F.line}}parseForStmt(){let F=this.expect(q.FOR,"'for'"),j=this.expect(q.IDENT,"a loop variable after 'for'");this.expect(q.IN,"'in' after the loop variable");let z=this.parseExpression();this.expect(q.DOTDOT,"'..' between the range bounds");let J=this.parseExpression(),Q=this.parseStatements(H);return this.expect(q.END,"'end' to close the 'for' loop"),{kind:"ForStmt",variable:j.lexeme,start:z,end:J,body:Q,line:F.line}}parseExprStmt(){let F=this.parseExpression();return{kind:"ExprStmt",expression:F,line:F.line}}parseExpression(){return this.parseComparison()}parseComparison(){return this.parseBinary(()=>this.parseTerm(),s)}parseTerm(){return this.parseBinary(()=>this.parseFactor(),i)}parseFactor(){return this.parseBinary(()=>this.parsePrimary(),y)}parseBinary(F,j){let z=F();while(!0){let J=j.get(this.peek().type);if(J===void 0)return z;this.advance();let Q=F();z={kind:"BinaryExpr",operator:J,left:z,right:Q,line:z.line}}}parsePrimary(){let F=this.peek();switch(F.type){case q.NUMBER:return this.advance(),{kind:"NumberLiteral",value:typeof F.value==="number"?F.value:Number(F.lexeme),line:F.line};case q.STRING:return this.advance(),{kind:"StringLiteral",value:typeof F.value==="string"?F.value:F.lexeme.slice(1,-1),line:F.line};case q.IDENT:{if(this.advance(),this.peek().type===q.LPAREN)return this.finishFunctionCall(F);return{kind:"Identifier",name:F.lexeme,line:F.line}}case q.LPAREN:{this.advance();let j=this.parseExpression();return this.expect(q.RPAREN,"')' to close the group"),j}default:throw this.error(F,`Expected an expression but found ${Y(F)}`)}}finishFunctionCall(F){this.expect(q.LPAREN,"'('");let j=[];if(this.peek().type!==q.RPAREN){j.push(this.parseExpression());while(this.peek().type===q.COMMA)this.advance(),j.push(this.parseExpression())}return this.expect(q.RPAREN,"')' to close the argument list"),{kind:"FunctionCall",callee:F.lexeme,args:j,line:F.line}}peek(F=0){let j=this.tokens[this.index+F];if(j===void 0)throw Error("Parser ran past the end of the token stream");return j}advance(){let F=this.peek();return this.index++,F}expect(F,j){let z=this.peek();if(z.type!==F)throw this.error(z,`Expected ${j} but found ${Y(z)}`);return this.index++,z}skipNewlines(){while(this.peek().type===q.NEWLINE)this.advance()}error(F,j){return new f(j,F.line,F.column,Math.max(F.lexeme.length,1))}}globalThis.Synax=V;})();
135
+ </script>
136
+ <script>
137
+ (function () {
138
+ "use strict";
139
+
140
+ var Synax = globalThis.Synax;
141
+
142
+ var EXAMPLES = {
143
+ "Hello World": 'print "Hello, World!"\n',
144
+ "Variables": "set x = 5\nprint x\n",
145
+ "If / else": [
146
+ "set age = 19",
147
+ "",
148
+ "if age >= 18",
149
+ ' print "adult"',
150
+ "else",
151
+ ' print "minor"',
152
+ "end",
153
+ ""
154
+ ].join("\n"),
155
+ "Function": [
156
+ "fn greet(name)",
157
+ ' print "Hello, " + name',
158
+ "end",
159
+ "",
160
+ 'greet("Ayush")',
161
+ ""
162
+ ].join("\n"),
163
+ "Loop": "for i in 1..5\n print i\nend\n",
164
+ "Fibonacci": [
165
+ "fn fib(a, b, n)",
166
+ " if n > 0",
167
+ " print a",
168
+ " fib(b, a + b, n - 1)",
169
+ " end",
170
+ "end",
171
+ "",
172
+ "fib(0, 1, 10)",
173
+ ""
174
+ ].join("\n"),
175
+ "FizzBuzz": [
176
+ "# FizzBuzz for 1..15.",
177
+ "set fizz = 0",
178
+ "set buzz = 0",
179
+ "for i in 1..15",
180
+ " set fizz = fizz + 1",
181
+ " set buzz = buzz + 1",
182
+ " if fizz == 3",
183
+ " set fizz = 0",
184
+ " end",
185
+ " if buzz == 5",
186
+ " set buzz = 0",
187
+ " end",
188
+ " if fizz == 0",
189
+ " if buzz == 0",
190
+ ' print "FizzBuzz"',
191
+ " else",
192
+ ' print "Fizz"',
193
+ " end",
194
+ " else",
195
+ " if buzz == 0",
196
+ ' print "Buzz"',
197
+ " else",
198
+ " print i",
199
+ " end",
200
+ " end",
201
+ "end",
202
+ ""
203
+ ].join("\n")
204
+ };
205
+
206
+ var $editor = document.getElementById("editor");
207
+ var $js = document.getElementById("js");
208
+ var $output = document.getElementById("output");
209
+ var $diagnostic = document.getElementById("diagnostic");
210
+ var $run = document.getElementById("run");
211
+ var $examples = document.getElementById("examples");
212
+
213
+ // Captured console.log calls from the executed program.
214
+ var captured = [];
215
+
216
+ function render() {
217
+ $js.textContent = "";
218
+ $diagnostic.textContent = "";
219
+ $diagnostic.className = "diagnostic";
220
+ try {
221
+ var ast = new Synax.Parser(new Synax.Lexer($editor.value).tokenize()).parse();
222
+ $js.textContent = Synax.generate(ast);
223
+ } catch (error) {
224
+ if (error && (error.name === "LexerError" || error.name === "ParserError")) {
225
+ $diagnostic.textContent = Synax.formatDiagnostic(
226
+ {
227
+ file: "program.snx",
228
+ line: error.line,
229
+ column: error.column,
230
+ length: error.length,
231
+ reason: error.reason,
232
+ },
233
+ $editor.value,
234
+ );
235
+ $diagnostic.className = "diagnostic error";
236
+ } else {
237
+ $diagnostic.textContent = "internal error: " + error;
238
+ $diagnostic.className = "diagnostic error";
239
+ }
240
+ $js.textContent = "";
241
+ }
242
+ }
243
+
244
+ function runProgram() {
245
+ $output.textContent = "";
246
+ if ($js.textContent.trim() === "") return;
247
+ captured = [];
248
+ var log = console.log;
249
+ console.log = function () {
250
+ for (var i = 0; i < arguments.length; i++) captured.push(String(arguments[i]));
251
+ };
252
+ try {
253
+ new Function($js.textContent)();
254
+ } catch (error) {
255
+ captured.push("RuntimeError: " + error.message);
256
+ } finally {
257
+ console.log = log;
258
+ }
259
+ $output.textContent = captured.length ? captured.join("\n") : "(no output)";
260
+ $output.className = "output";
261
+ }
262
+
263
+ function selectExample() {
264
+ var program = EXAMPLES[$examples.value];
265
+ if (program === undefined) return;
266
+ $editor.value = program;
267
+ render();
268
+ runProgram();
269
+ }
270
+
271
+ $editor.addEventListener("input", render);
272
+ $run.addEventListener("click", runProgram);
273
+ $examples.addEventListener("change", selectExample);
274
+
275
+ // Initial state: load an example so the page is alive on first open.
276
+ $examples.value = "If / else";
277
+ selectExample();
278
+ })();
279
+ </script>
280
+ </body>
281
+ </html>