gofront 0.0.1 → 0.0.2
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 +209 -9
- package/package.json +1 -1
- package/src/codegen.js +198 -43
- package/src/compiler.js +13 -6
- package/src/dts-parser.js +0 -4
- package/src/index.js +8 -17
- package/src/lexer.js +76 -6
- package/src/parser.js +172 -47
- package/src/resolver.js +1 -1
- package/src/typechecker.js +107 -13
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
## About
|
|
2
2
|
|
|
3
3
|
I love Go for the backend — simple, type-safe, no nonsense. I like JavaScript for the
|
|
4
4
|
frontend — runs everywhere, no setup. What I don't like is JavaScript's loose typing. And
|
|
@@ -7,7 +7,7 @@ I don't like TypeScript either. I know, controversial. It's fine, I just don't e
|
|
|
7
7
|
So I built GoFront. Go syntax, type safety, compiles to plain ES modules. Same language
|
|
8
8
|
front and back, no runtime, no framework, no tsconfig.json.
|
|
9
9
|
|
|
10
|
-
Is this useful? Probably
|
|
10
|
+
Is this useful? Probably not, but it was fun to build:D
|
|
11
11
|
|
|
12
12
|
```go
|
|
13
13
|
package main
|
|
@@ -50,6 +50,135 @@ Requires Node.js 25+.
|
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
|
+
## How it works
|
|
54
|
+
|
|
55
|
+
GoFront is a four-stage compiler written in pure Node.js (no dependencies). Every stage
|
|
56
|
+
operates on the same AST (abstract syntax tree), running in a single pass per stage:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
source text (.go files)
|
|
60
|
+
→ Lexer tokenize + Go-style semicolon insertion
|
|
61
|
+
→ Parser recursive-descent → AST
|
|
62
|
+
→ Type Checker annotate AST with types + collect errors
|
|
63
|
+
→ Code Gen AST → JavaScript string
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 1. Lexer (`src/lexer.js`)
|
|
67
|
+
|
|
68
|
+
Tokenises the source into a stream of tokens. Implements Go's semicolon insertion rules:
|
|
69
|
+
a semicolon is automatically inserted after a line's final token if that token is an
|
|
70
|
+
identifier, literal, `)`, `]`, `}`, or certain keywords (`return`, `break`, `continue`,
|
|
71
|
+
`fallthrough`). This is why Go doesn't need explicit semicolons — and neither does
|
|
72
|
+
GoFront.
|
|
73
|
+
|
|
74
|
+
### 2. Parser (`src/parser.js`)
|
|
75
|
+
|
|
76
|
+
A hand-written recursive-descent parser. No parser generators, no grammar files — just
|
|
77
|
+
straightforward top-down parsing. Produces an AST where every node is a plain JS object
|
|
78
|
+
with a `kind` field (`"FuncDecl"`, `"IfStmt"`, `"BinaryExpr"`, etc.).
|
|
79
|
+
|
|
80
|
+
Operator precedence is handled via a Pratt-style expression parser with numeric
|
|
81
|
+
precedence levels.
|
|
82
|
+
|
|
83
|
+
### 3. Type Checker (`src/typechecker.js`)
|
|
84
|
+
|
|
85
|
+
Three-pass type checker operating on the AST:
|
|
86
|
+
|
|
87
|
+
1. **Pass 1 — collect types**: register all `type` declarations (structs, interfaces,
|
|
88
|
+
named types) so they can be referenced before definition.
|
|
89
|
+
2. **Pass 2 — collect functions & vars**: register function signatures and package-level
|
|
90
|
+
variables. Resolve embedded struct fields and promote methods.
|
|
91
|
+
3. **Pass 3 — check bodies**: walk every function body, infer expression types, verify
|
|
92
|
+
assignments and call arguments, and report errors with source location.
|
|
93
|
+
|
|
94
|
+
Types are plain JS objects (`{ kind: "basic", name: "int" }`, `{ kind: "slice",
|
|
95
|
+
elem: ... }`, etc.). The special `any` type acts as a recovery/escape hatch — any
|
|
96
|
+
operation on it is silently permitted, preventing cascading errors.
|
|
97
|
+
|
|
98
|
+
### 4. Code Generator (`src/codegen.js`)
|
|
99
|
+
|
|
100
|
+
Walks the typed AST and emits clean, readable JavaScript. No intermediate representation
|
|
101
|
+
— the codegen writes directly to an output buffer with indentation tracking.
|
|
102
|
+
|
|
103
|
+
Runtime helpers (`__len`, `__append`, `__s`, `__sprintf`) are tree-shaken: only emitted
|
|
104
|
+
when actually used. Optional inline source maps are supported via VLQ-encoded mappings.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Go → JavaScript mapping
|
|
109
|
+
|
|
110
|
+
Every Go construct compiles to a specific JavaScript pattern. The output is designed to be
|
|
111
|
+
readable and debuggable — no name mangling, no opaque wrappers.
|
|
112
|
+
|
|
113
|
+
### Data structures
|
|
114
|
+
|
|
115
|
+
| Go | JavaScript | Notes |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `struct` | ES6 `class` | Single destructured-object constructor: `new Point({ X: 1, Y: 2 })` |
|
|
118
|
+
| Methods | Class instance methods | Receiver is `this` |
|
|
119
|
+
| Embedded structs | Flattened fields + delegation stubs | `Greet(...a) { return Base.prototype.Greet.call(this, ...a); }` |
|
|
120
|
+
| `[]T` (slice) | `Array` | `append` → spread, `len` → `.length` |
|
|
121
|
+
| `map[K]V` | Plain object `{}` | Key access via `[]`, iteration via `Object.entries()` |
|
|
122
|
+
| `nil` | `null` | |
|
|
123
|
+
| `error` | `string` | `error("msg")` → `"msg"`, `.Error()` → the string itself |
|
|
124
|
+
| Pointers (`*T`) | `{ value: T }` | `new(T)` allocates a boxed zero value |
|
|
125
|
+
|
|
126
|
+
### Functions
|
|
127
|
+
|
|
128
|
+
| Go | JavaScript |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `func f(a int) int` | `function f(a)` |
|
|
131
|
+
| Multiple returns `return a, b` | `return [a, b]` — destructured at call site: `let [a, b] = f()` |
|
|
132
|
+
| Named returns | Variables pre-declared; bare `return` returns them |
|
|
133
|
+
| Variadic `func f(xs ...int)` | `function f(...xs)` |
|
|
134
|
+
| `func` literal (closure) | Arrow or function expression |
|
|
135
|
+
| `async func` / `await` | `async function` / `await` |
|
|
136
|
+
| `init()` | Emitted as `(function() { ... })()` immediately-invoked |
|
|
137
|
+
| `defer` | `try { ... } finally { deferred() }` |
|
|
138
|
+
|
|
139
|
+
### Control flow
|
|
140
|
+
|
|
141
|
+
| Go | JavaScript |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `for init; cond; post {}` | `for (init; cond; post) {}` |
|
|
144
|
+
| `for cond {}` | `while (cond) {}` |
|
|
145
|
+
| `for {}` | `while (true) {}` |
|
|
146
|
+
| `for i, v := range slice` | `for (const [i, v] of arr.entries())` |
|
|
147
|
+
| `for k, v := range map` | `for (const [k, v] of Object.entries(m))` |
|
|
148
|
+
| `for i, ch := range str` | `for (const [i, ch] of Array.from(s).entries())` |
|
|
149
|
+
| `for i := range n` | `for (let i = 0; i < n; i++)` |
|
|
150
|
+
| `switch` / `fallthrough` | `switch` / case fall-through |
|
|
151
|
+
| `switch v := x.(type)` | `if/else if` with `typeof` / `instanceof` checks |
|
|
152
|
+
| `panic(msg)` | `throw new Error(msg)` |
|
|
153
|
+
| `recover()` | Captured in `defer` via `try/catch` |
|
|
154
|
+
|
|
155
|
+
### Builtins
|
|
156
|
+
|
|
157
|
+
| Go | JavaScript |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `len(x)` | `__len(x)` (tree-shaken helper) or `Object.keys(x).length` for maps |
|
|
160
|
+
| `cap(x)` | `x.length` |
|
|
161
|
+
| `append(s, elems...)` | `[...s, ...elems]` |
|
|
162
|
+
| `copy(dst, src)` | Inline splice helper |
|
|
163
|
+
| `make([]T, n)` | `new Array(n).fill(zero)` |
|
|
164
|
+
| `make(map[K]V)` | `{}` |
|
|
165
|
+
| `delete(m, k)` | `delete m[k]` |
|
|
166
|
+
| `new(T)` | `{ value: zeroOf(T) }` |
|
|
167
|
+
| `min(a, b, ...)` | `Math.min(a, b, ...)` |
|
|
168
|
+
| `max(a, b, ...)` | `Math.max(a, b, ...)` |
|
|
169
|
+
| `clear(x)` | `.length = 0` (slice) or delete-loop (map) |
|
|
170
|
+
| `print` / `println` | `console.log` |
|
|
171
|
+
| `fmt.Sprintf` | `__sprintf` tree-shaken helper |
|
|
172
|
+
|
|
173
|
+
### Type system at runtime
|
|
174
|
+
|
|
175
|
+
All type checking happens at compile time. At runtime, types are erased — there are no
|
|
176
|
+
type tags, no reflection, no runtime overhead. Sized integers (`int8`–`int64`,
|
|
177
|
+
`uint8`–`uint64`) are all `number` at runtime. `float32` is `number`. The type checker
|
|
178
|
+
enforces correctness; JavaScript doesn't need to know.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
53
182
|
## Example — Todo App
|
|
54
183
|
|
|
55
184
|
The only example is a full todo web app in `example/`, split across a
|
|
@@ -105,7 +234,15 @@ gofront --help / -h print this help
|
|
|
105
234
|
|
|
106
235
|
## Multi-file packages
|
|
107
236
|
|
|
108
|
-
All `.go` files in a directory share the same namespace and are compiled as one unit
|
|
237
|
+
All `.go` files in a directory share the same namespace and are compiled as one unit.
|
|
238
|
+
The compiler (`src/compiler.js`) orchestrates this:
|
|
239
|
+
|
|
240
|
+
1. Parse each `.go` file in the directory into a separate AST.
|
|
241
|
+
2. Resolve imports: `js:` d.ts files, npm packages (via `node_modules/` and `@types/`),
|
|
242
|
+
and local GoFront sub-packages (`import "./subpkg"`) which are compiled recursively.
|
|
243
|
+
3. Run the type checker across all ASTs as a single unit — types, functions, and
|
|
244
|
+
variables declared in one file are visible in all other files of the same package.
|
|
245
|
+
4. Generate code for each AST and concatenate. Sub-package code is inlined as a preamble.
|
|
109
246
|
|
|
110
247
|
```
|
|
111
248
|
myapp/
|
|
@@ -115,7 +252,7 @@ myapp/
|
|
|
115
252
|
```
|
|
116
253
|
|
|
117
254
|
```sh
|
|
118
|
-
|
|
255
|
+
gofront myapp -o myapp/bundle.js
|
|
119
256
|
```
|
|
120
257
|
|
|
121
258
|
Cross-package imports are supported via relative paths:
|
|
@@ -132,7 +269,8 @@ func main() {
|
|
|
132
269
|
|
|
133
270
|
## Type checking
|
|
134
271
|
|
|
135
|
-
GoFront performs static type checking before emitting any code, accurately tracking source
|
|
272
|
+
GoFront performs static type checking before emitting any code, accurately tracking source
|
|
273
|
+
locations in error messages across multiple files:
|
|
136
274
|
|
|
137
275
|
```go
|
|
138
276
|
func greet(name string) {
|
|
@@ -149,6 +287,10 @@ import "js:./dom.d.ts"
|
|
|
149
287
|
```
|
|
150
288
|
|
|
151
289
|
npm package types are resolved automatically from `node_modules/` and `@types/`.
|
|
290
|
+
The resolver (`src/resolver.js`) walks up the directory tree to find `node_modules`,
|
|
291
|
+
checks `package.json` `"types"` / `"typings"` fields, falls back to `index.d.ts`,
|
|
292
|
+
then tries `@types/`. The `.d.ts` parser (`src/dts-parser.js`) extracts type
|
|
293
|
+
signatures into GoFront's internal type representation.
|
|
152
294
|
|
|
153
295
|
---
|
|
154
296
|
|
|
@@ -158,6 +300,9 @@ npm package types are resolved automatically from `node_modules/` and `@types/`.
|
|
|
158
300
|
|---|---|
|
|
159
301
|
| Variables (`var`, `:=` short re-declaration) | ✓ |
|
|
160
302
|
| Constants (`const`, `iota`) | ✓ |
|
|
303
|
+
| `if` / `else if` / `else` | ✓ |
|
|
304
|
+
| `if` with init statement (`if err := f(); err != nil`) | ✓ |
|
|
305
|
+
| Type definitions (`type MyType ...`) | ✓ |
|
|
161
306
|
| Functions, multiple returns | ✓ |
|
|
162
307
|
| Named return values | ✓ |
|
|
163
308
|
| Variadic parameters | ✓ |
|
|
@@ -166,30 +311,85 @@ npm package types are resolved automatically from `node_modules/` and `@types/`.
|
|
|
166
311
|
| Embedded structs | ✓ |
|
|
167
312
|
| Pointer receivers | ✓ |
|
|
168
313
|
| Interfaces | ✓ |
|
|
314
|
+
| Interface embedding | ✓ |
|
|
169
315
|
| Closures | ✓ |
|
|
170
316
|
| Slices (`append`, `len`, `make`) | ✓ |
|
|
171
317
|
| Maps (`make`, `delete`, comma-ok) | ✓ — note: iteration order is insertion-order (JS), not randomised (Go) |
|
|
172
318
|
| `for`, `for range`, `for {}` | ✓ |
|
|
173
319
|
| `range` over string | ✓ |
|
|
174
320
|
| `switch` / `fallthrough` | ✓ |
|
|
321
|
+
| `switch` with init statement (`switch x := f(); x {}`) | ✓ |
|
|
175
322
|
| `new(T)` | ✓ |
|
|
176
323
|
| Type conversions | ✓ |
|
|
177
324
|
| Type assertions (`x.(T)`) | ✓ |
|
|
325
|
+
| Type switch (`switch x.(type)`, `switch v := x.(type)`) | ✓ — compiles to `if/else if` with `typeof` / `instanceof` checks |
|
|
178
326
|
| Slice expressions (`s[lo:hi]`) | ✓ |
|
|
179
|
-
| `break` / `continue` | ✓ |
|
|
327
|
+
| `break` / `continue` / labeled `break` / labeled `continue` | ✓ |
|
|
180
328
|
| `cap()`, `copy()`, `panic()` | ✓ |
|
|
329
|
+
| Variadic spread (`f(slice...)`, `append(a, b...)`) | ✓ |
|
|
181
330
|
| Bitwise operators (`&`, `\|`, `^`, `<<`, `>>`) | ✓ |
|
|
182
|
-
| Compound assignment (`+=`, `-=`, `*=`, `/=`,
|
|
331
|
+
| Compound assignment (`+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `\|=`, `^=`, `<<=`, `>>=`) | ✓ |
|
|
183
332
|
| Increment / decrement (`i++`, `i--`) | ✓ |
|
|
184
333
|
| Raw string literals (backticks) | ✓ |
|
|
334
|
+
| Rune / char literals (`'a'`, `'\n'`) | ✓ — compiled to integer char codes |
|
|
185
335
|
| `defer` | ✓ |
|
|
336
|
+
| `panic()` / `recover()` | ✓ |
|
|
337
|
+
| `print` / `println` builtins | ✓ — compile to `console.log` |
|
|
338
|
+
| Pointer operators (`&`, `*`) | ✓ |
|
|
186
339
|
| `error` type / `error("msg")` / `.Error()` | ✓ |
|
|
187
340
|
| `async func` / `await` expressions | ✓ |
|
|
188
|
-
| `fmt.Sprintf` / `fmt.Printf` / `fmt.Println` / `fmt.Errorf` | ✓ |
|
|
341
|
+
| `fmt.Sprintf` / `fmt.Printf` / `fmt.Println` / `fmt.Print` / `fmt.Errorf` | ✓ |
|
|
189
342
|
| External `.d.ts` types | ✓ |
|
|
190
343
|
| npm package types | ✓ |
|
|
191
344
|
| Multi-file packages | ✓ |
|
|
192
345
|
| Cross-package imports | ✓ |
|
|
346
|
+
| Import aliases (`import m "./pkg"`) | ✓ |
|
|
347
|
+
| Sized integer types (`uint`, `int8`–`int64`, `uint8`–`uint64`, `float32`) | ✓ — mapped to `int` / `float64` at runtime |
|
|
348
|
+
| Struct field tags (`` `json:"name"` ``) | ✓ — parsed and ignored (no reflection) |
|
|
349
|
+
| `[]byte(s)` conversion | ✓ — produces UTF-8 byte values via `TextEncoder` |
|
|
350
|
+
| `[]rune(s)` conversion | ✓ — produces Unicode code points via `Array.from` |
|
|
351
|
+
| `[...]T{...}` array length inference | ✓ |
|
|
352
|
+
| Side-effect imports (`import _ "pkg"`) | ✓ — dependency code is bundled; package namespace is not exposed |
|
|
353
|
+
| `min()` / `max()` builtins | ✓ — compiles to `Math.min` / `Math.max` |
|
|
354
|
+
| `clear()` builtin | ✓ — zeroes slice length or deletes all map keys |
|
|
355
|
+
| `range` over integer (`for i := range n`) | ✓ — Go 1.22; also supports `for range n` |
|
|
356
|
+
| Type aliases (`type A = B`) | ✓ — transparent alias; no conversion needed between alias and original |
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## What's not implemented, and why
|
|
361
|
+
|
|
362
|
+
GoFront targets the browser. Several Go features don't translate to JavaScript, and
|
|
363
|
+
some were intentionally left out to keep the compiler simple.
|
|
364
|
+
|
|
365
|
+
### Fundamental mismatches — can't implement without a runtime
|
|
366
|
+
|
|
367
|
+
| Feature | Why |
|
|
368
|
+
|---|---|
|
|
369
|
+
| Goroutines (`go` keyword) | Go's concurrency model has no JS equivalent. Workers are a different abstraction. A userland scheduler would defeat the "no runtime" goal. |
|
|
370
|
+
| Channels (`chan`, `<-`) | Channels are the communication primitive for goroutines. Without goroutines, channels have no purpose. |
|
|
371
|
+
| `select` statement | Multiplexes channel operations. Same dependency. |
|
|
372
|
+
| Generics (Go 1.18+) | Would require monomorphisation or type-erasure strategy. Type parameters (`[T any]`) touch every compiler stage. Possible, but a large effort with limited payoff for a JS target where everything is `any` at runtime. |
|
|
373
|
+
|
|
374
|
+
### Intentionally skipped
|
|
375
|
+
|
|
376
|
+
| Feature | Why |
|
|
377
|
+
|---|---|
|
|
378
|
+
| `goto` statement | Unstructured control flow has no clean JS translation. `goto` is rare in idiomatic Go. Not planned. |
|
|
379
|
+
|
|
380
|
+
### Semantic differences — JS can't match Go behaviour
|
|
381
|
+
|
|
382
|
+
These features exist but behave differently due to fundamental JS/Go runtime differences:
|
|
383
|
+
|
|
384
|
+
| Feature | GoFront behaviour | Go behaviour | Why |
|
|
385
|
+
|---|---|---|---|
|
|
386
|
+
| Map iteration order | Insertion-order (`Object.entries`) | Randomised | JS objects preserve insertion order. Wrapping in a custom `Map` class would add runtime overhead for no practical benefit. |
|
|
387
|
+
| Integer overflow | IEEE 754 float64 wrap | Wraps at type boundary (e.g. `int32`) | All JS numbers are float64. Emitting `\|0` or `BigInt` for every arithmetic op would be prohibitively expensive. |
|
|
388
|
+
| `cap()` | Always equals `len()` | May exceed `len()` | JS arrays don't expose allocation capacity. |
|
|
389
|
+
| Fixed-size arrays (`[n]T`) | Plain JS arrays (no length enforcement) | Fixed at compile time | Could add a length check at construction, but runtime enforcement adds overhead for no safety benefit in JS. |
|
|
390
|
+
| `rune` / `byte` types | Treated as `int` | Distinct types with UTF-8 encoding | JS strings are UTF-16. Proper rune handling would require wrapping every string operation. |
|
|
391
|
+
| `range` over string | JS characters (UTF-16 code units via `Array.from`) | Go runes (UTF-8 code points) | Same UTF-16 vs UTF-8 mismatch. |
|
|
392
|
+
| `len()` on strings | JS `.length` (UTF-16 code units) | Byte count (UTF-8) | Matching Go would require `TextEncoder` on every `len(s)` call. |
|
|
193
393
|
|
|
194
394
|
---
|
|
195
395
|
|
|
@@ -199,4 +399,4 @@ npm package types are resolved automatically from `node_modules/` and `@types/`.
|
|
|
199
399
|
npm test
|
|
200
400
|
```
|
|
201
401
|
|
|
202
|
-
|
|
402
|
+
468 tests covering language features, type errors, edge cases, DOM (jsdom), external `.d.ts`, npm resolver, multi-file compilation, embedded structs, string formatting, and the example app.
|