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 CHANGED
@@ -1,4 +1,4 @@
1
- # GoFront
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 only for hobby projects. But it was fun to build and it works.
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
- node src/index.js myapp -o myapp/bundle.js
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 locations in error messages across multiple files:
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
- 191 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.
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gofront",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "author": "Luuk van Venrooij",
5
5
  "license": "MIT",
6
6
  "repository": {