gofront 0.0.8 → 1.0.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.
Files changed (51) hide show
  1. package/README.md +88 -12
  2. package/package.json +8 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/{codegen.js → codegen/index.js} +155 -184
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +102 -62
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +22 -54
  32. package/src/lexer.js +200 -286
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +54 -36
  35. package/src/{parser.js → parser/index.js} +12 -5
  36. package/src/parser/statements.js +29 -35
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-lexer.js +737 -0
  39. package/src/templ-parser.js +235 -0
  40. package/src/tokens.js +109 -0
  41. package/src/typechecker/assignability.js +258 -0
  42. package/src/typechecker/expressions.js +559 -549
  43. package/src/typechecker/index.js +555 -0
  44. package/src/typechecker/resolve.js +421 -0
  45. package/src/typechecker/statements.js +267 -280
  46. package/src/typechecker/stdlib/core.js +592 -0
  47. package/src/typechecker/stdlib/extended.js +332 -0
  48. package/src/typechecker/stdlib.js +9 -1060
  49. package/src/typechecker/termination.js +83 -0
  50. package/src/typechecker/types.js +6 -3
  51. package/src/typechecker.js +0 -1271
package/src/compiler.js CHANGED
@@ -15,80 +15,79 @@
15
15
  // Cross-package access uses the qualified form: `pkg.Foo`. The codegen
16
16
  // de-qualifies it because the dependency is inlined.
17
17
 
18
- import { readdirSync, readFileSync } from "node:fs";
18
+ import { readdirSync, readFileSync, statSync } from "node:fs";
19
19
  import { basename, dirname, join, relative, resolve } from "node:path";
20
- import { CodeGen } from "./codegen.js";
20
+ import { CodeGen } from "./codegen/index.js";
21
21
  import { parseDts } from "./dts-parser.js";
22
22
  import { Lexer } from "./lexer.js";
23
- import { Parser } from "./parser.js";
23
+ import { Parser } from "./parser/index.js";
24
24
  import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
25
- import { TypeChecker } from "./typechecker.js";
25
+ import { TemplLexer } from "./templ-lexer.js";
26
+ import { TemplParser } from "./templ-parser.js";
27
+ import { TypeChecker } from "./typechecker/index.js";
28
+
29
+ // ── Parse cache ──────────────────────────────────────────────
30
+
31
+ // Map<filePath, { mtime: number, ast: object }>
32
+ const _parseCache = new Map();
33
+
34
+ export function clearParseCache() {
35
+ _parseCache.clear();
36
+ }
37
+
38
+ export function parseCacheSize() {
39
+ return _parseCache.size;
40
+ }
26
41
 
27
42
  // ── Helpers ──────────────────────────────────────────────────
28
43
 
29
- function parseFile(filePath) {
44
+ function parseGoFrontFile(filePath) {
45
+ const mtime = statSync(filePath).mtimeMs;
46
+ const cached = _parseCache.get(filePath);
47
+ if (cached && cached.mtime === mtime) return cached.ast;
48
+
30
49
  const source = readFileSync(filePath, "utf8");
31
50
  const filename = basename(filePath);
32
- const tokens = new Lexer(source, filename).tokenize();
33
- const ast = new Parser(tokens, filename, source).parse();
51
+ const isTempl = filePath.endsWith(".templ");
52
+ const tokens = isTempl
53
+ ? new TemplLexer(source, filename).tokenize()
54
+ : new Lexer(source, filename).tokenize();
55
+ const ast = isTempl
56
+ ? new TemplParser(tokens, filename, source).parse()
57
+ : new Parser(tokens, filename, source).parse();
34
58
  ast._source = source;
59
+ _parseCache.set(filePath, { mtime, ast });
35
60
  return ast;
36
61
  }
37
62
 
38
63
  function gwFilesIn(dir) {
39
64
  return readdirSync(dir)
40
- .filter((f) => f.endsWith(".go"))
65
+ .filter((f) => f.endsWith(".go") || f.endsWith(".templ"))
41
66
  .sort() // deterministic order
42
67
  .map((f) => join(dir, f));
43
68
  }
44
69
 
45
- // ── Main entry points ─────────────────────────────────────────
46
-
47
- export function compileDir(dir, options = {}) {
48
- const files = gwFilesIn(dir);
49
- if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
50
- return compileFiles(files, { ...options, fromDir: dir });
51
- }
52
-
53
- export function compileFiles(files, options = {}) {
54
- const fromDir = options.fromDir ?? dirname(resolve(files[0]));
55
-
56
- // ── 1. Parse ─────────────────────────────────────────────────
57
- const parseErrors = [];
58
- const programs = [];
59
- for (const f of files) {
60
- try {
61
- programs.push(parseFile(f));
62
- } catch (e) {
63
- parseErrors.push(e.message);
64
- }
65
- }
66
- if (parseErrors.length > 0) {
67
- throw new Error(parseErrors.join("\n"));
68
- }
69
-
70
- // Validate consistent package name
71
- const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
72
- if (pkgNames.length > 1)
73
- throw new Error(
74
- `Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
75
- );
76
- const pkgName = pkgNames[0];
77
-
78
- // ── 2. Resolve imports ────────────────────────────────────────
79
- const checker = new TypeChecker();
80
- const jsImports = new Map(); // npm imports → exported names (for ESM emit)
81
- const bundledPackages = new Set(); // package names whose code is inlined
82
- const preambles = []; // JS code from compiled sub-packages
83
-
70
+ // ── Import resolution ─────────────────────────────────────────
71
+ //
72
+ // Shared by compileFiles (multi-file) and the single-file path in index.js.
73
+ // Mutates checker, jsImports, bundledPackages, and preambles in place.
74
+
75
+ export function resolveImports(
76
+ programs,
77
+ fromFile,
78
+ checker,
79
+ jsImports,
80
+ bundledPackages,
81
+ preambles,
82
+ ) {
83
+ const fromDir = dirname(resolve(fromFile));
84
84
  const allImports = programs.flatMap((p) => p.imports);
85
- const dummyFromFile = join(fromDir, "_dummy.go");
86
85
 
87
86
  // js: prefix — local .d.ts files
88
87
  for (const imp of allImports) {
89
88
  for (const { path, alias } of imp.imports) {
90
89
  if (!path.startsWith("js:")) continue;
91
- if (alias === "_") continue; // side-effect import — no type definitions
90
+ if (alias === "_") continue;
92
91
  const dtsPath = join(fromDir, path.slice(3));
93
92
  try {
94
93
  const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
@@ -99,12 +98,12 @@ export function compileFiles(files, options = {}) {
99
98
  }
100
99
  }
101
100
 
102
- // npm packages — exclude side-effect imports (alias "_") from type resolution
101
+ // npm packages — exclude side-effect imports from type resolution
103
102
  const allImportsNoSideEffect = allImports.map((imp) => ({
104
103
  ...imp,
105
104
  imports: imp.imports.filter(({ alias }) => alias !== "_"),
106
105
  }));
107
- const resolved = resolveAll(allImportsNoSideEffect, dummyFromFile, parseDts);
106
+ const resolved = resolveAll(allImportsNoSideEffect, fromFile, parseDts);
108
107
  for (const [path, info] of resolved) {
109
108
  if (!info) continue;
110
109
  checker.addDefinitions(info.types, info.values);
@@ -118,29 +117,20 @@ export function compileFiles(files, options = {}) {
118
117
  for (const { path, alias, _line } of imp.imports) {
119
118
  if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
120
119
  seenLocalPaths.add(path);
121
-
122
- const depDir = resolveGwDir(path, dummyFromFile);
120
+ const depDir = resolveGwDir(path, fromFile);
123
121
  if (!depDir) {
124
122
  console.error(
125
123
  `gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
126
124
  );
127
125
  continue;
128
126
  }
129
-
130
- // Recursively compile dependency
131
127
  const dep = compileDir(depDir);
132
128
  preambles.push(dep.js);
133
- if (alias === "_") continue; // side-effect import — bundle code but don't expose namespace
134
-
135
- // Dot import: flatten all exported symbols into global scope
129
+ if (alias === "_") continue;
136
130
  if (alias === ".") {
137
131
  checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
138
- // All dep symbols must be treated as bundled so codegen
139
- // can emit them without a qualifier. We DON'T add a
140
- // namespace, so SelectorExpr never appears for these.
141
132
  continue;
142
133
  }
143
-
144
134
  const nameUsed = alias ?? dep.pkgName;
145
135
  bundledPackages.add(nameUsed);
146
136
  checker.addPackageNamespace(
@@ -152,6 +142,56 @@ export function compileFiles(files, options = {}) {
152
142
  }
153
143
  }
154
144
  }
145
+ }
146
+
147
+ // ── Main entry points ─────────────────────────────────────────
148
+
149
+ export function compileDir(dir, options = {}) {
150
+ const files = gwFilesIn(dir);
151
+ if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
152
+ return compileFiles(files, { ...options, fromDir: dir });
153
+ }
154
+
155
+ export function compileFiles(files, options = {}) {
156
+ const fromDir = options.fromDir ?? dirname(resolve(files[0]));
157
+
158
+ // ── 1. Parse ─────────────────────────────────────────────────
159
+ const parseErrors = [];
160
+ const programs = [];
161
+ for (const f of files) {
162
+ try {
163
+ programs.push(parseGoFrontFile(f));
164
+ } catch (e) {
165
+ parseErrors.push(e.message);
166
+ }
167
+ }
168
+ if (parseErrors.length > 0) {
169
+ throw new Error(parseErrors.join("\n"));
170
+ }
171
+
172
+ // Validate consistent package name
173
+ const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
174
+ if (pkgNames.length > 1)
175
+ throw new Error(
176
+ `Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
177
+ );
178
+ const pkgName = pkgNames[0];
179
+
180
+ // ── 2. Resolve imports ────────────────────────────────────────
181
+ const checker = new TypeChecker();
182
+ const jsImports = new Map(); // npm imports → exported names (for ESM emit)
183
+ const bundledPackages = new Set(); // package names whose code is inlined
184
+ const preambles = []; // JS code from compiled sub-packages
185
+
186
+ const dummyFromFile = join(fromDir, "_dummy.go");
187
+ resolveImports(
188
+ programs,
189
+ dummyFromFile,
190
+ checker,
191
+ jsImports,
192
+ bundledPackages,
193
+ preambles,
194
+ );
155
195
 
156
196
  // ── 3. Type-check ─────────────────────────────────────────────
157
197
  const errors = checker.checkAll(programs);