@lango-project/lango-js 0.1.4 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lango-project/lango-js",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/cli.mjs CHANGED
@@ -24,7 +24,9 @@ const buildOptions = (arguments_) => {
24
24
  }
25
25
  const value = arguments_[index + 1];
26
26
  if (!value) return null;
27
- if (option === "--decimal-division-scale")
27
+ if (option === "--target")
28
+ profile.target = value;
29
+ else if (option === "--decimal-division-scale")
28
30
  profile.decimal_division_scale = Number(value);
29
31
  else if (option === "--decimal-division-rounding")
30
32
  profile.decimal_division_rounding = Decimal.Rounding[value] ?? value;
@@ -34,21 +36,22 @@ const buildOptions = (arguments_) => {
34
36
  return { outputPath: arguments_[1], profile };
35
37
  };
36
38
  const build = command === "build" && inputPath ? buildOptions(rest) : null;
37
- const runOptions = (arguments_) => {
38
- const options = { database: null, catalog: null };
39
+ const runOptions = (arguments_, allowBuildDir = false) => {
40
+ const options = { database: null, catalog: null, buildDir: null };
39
41
  let index = 0;
40
- while (arguments_[index] === "--database" || arguments_[index] === "--catalog") {
42
+ while (arguments_[index] === "--database" || arguments_[index] === "--catalog" || (allowBuildDir && arguments_[index] === "--build-dir")) {
41
43
  const option = arguments_[index];
42
44
  const value = arguments_[index + 1];
43
45
  if (!value) return null;
44
46
  if (option === "--database") options.database = value;
45
- else options.catalog = value;
47
+ else if (option === "--catalog") options.catalog = value;
48
+ else options.buildDir = value;
46
49
  index += 2;
47
50
  }
48
51
  if (arguments_[index] === "--") index += 1;
49
52
  return { ...options, args: arguments_.slice(index) };
50
53
  };
51
- const testOptions = command === "test" && inputPath ? runOptions(rest) : null;
54
+ const testOptions = command === "test" && inputPath ? runOptions(rest, true) : null;
52
55
 
53
56
  async function initProject(directory) {
54
57
  const target = path.resolve(directory);
@@ -129,7 +132,9 @@ if (command === "init" && inputPath && rest.length === 0) {
129
132
  try {
130
133
  if (testOptions.args.length)
131
134
  throw new Error("Lango test does not accept program arguments");
132
- const directory = await mkdtemp(path.join(os.tmpdir(), "lango-test-"));
135
+ const directory = testOptions.buildDir
136
+ ? path.resolve(testOptions.buildDir)
137
+ : await mkdtemp(path.join(os.tmpdir(), "lango-test-"));
133
138
  const outputPath = path.join(directory, "main.mjs");
134
139
  await buildFile(path.resolve(inputPath), outputPath, new Set(), true);
135
140
  const runtime = await import(pathToFileURL(path.join(directory, ".lango/runtime.mjs")).href);
@@ -173,6 +178,6 @@ if (command === "init" && inputPath && rest.length === 0) {
173
178
  process.exitCode = error instanceof LangoError ? 1 : 2;
174
179
  }
175
180
  } else {
176
- console.error("Usage: node src/cli.mjs init <directory>\n node src/cli.mjs build <input.lango> --out <output.mjs> [--executable] [--decimal-division-scale <n>] [--decimal-division-rounding <EnumName>]\n node src/cli.mjs run <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>] [-- args...]\n node src/cli.mjs test <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>]\n node src/cli.mjs format <input.lango>");
181
+ console.error("Usage: node src/cli.mjs init <directory>\n node src/cli.mjs build <input.lango> --out <output.mjs> [--target node|browser] [--executable] [--decimal-division-scale <n>] [--decimal-division-rounding <EnumName>]\n node src/cli.mjs run <input.lango> [--database <sqlite-file>] [--catalog <catalog.json>] [-- args...]\n node src/cli.mjs test <input.lango> [--build-dir <directory>] [--database <sqlite-file>] [--catalog <catalog.json>]\n node src/cli.mjs format <input.lango>");
177
182
  process.exitCode = 2;
178
183
  }
package/src/compiler.mjs CHANGED
@@ -32,6 +32,8 @@ export function formatDiagnostics(error) {
32
32
 
33
33
  export async function buildFile(inputPath, outputPath, seen = new Set(), includeTests = false, profile = {}) {
34
34
  const decimalProfile = validateDecimalProfile(profile);
35
+ if (decimalProfile.target === "browser" && profile.executable)
36
+ throw new LangoError("LNG1266", 1, "browser builds cannot use --executable", inputPath);
35
37
  const isEntryPoint = seen.size === 0;
36
38
  if (isEntryPoint)
37
39
  await validateModuleInitializerCycles(inputPath, outputPath);
@@ -87,6 +89,8 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
87
89
  }
88
90
 
89
91
  const compiled = compile(source, inputPath, imports, includeTests, decimalProfile);
92
+ if (decimalProfile.target === "browser" && compiled.program.jsImports.some((item) => item.specifier?.startsWith("node:")))
93
+ throw new LangoError("LNG1266", 1, "browser builds cannot import node: modules", inputPath);
90
94
  let output = compiled.javascript.replace(
91
95
  "//# sourceMappingURL=main.mjs.map",
92
96
  `//# sourceMappingURL=${path.basename(outputPath)}.map`,
@@ -122,7 +126,9 @@ export async function buildFile(inputPath, outputPath, seen = new Set(), include
122
126
  "utf8",
123
127
  );
124
128
  const xmlVendor = new URL("./vendor/xmldom/", import.meta.url);
129
+ if (decimalProfile.target === "browser") return;
125
130
  await mkdir(path.join(directory, ".lango/xmldom"), { recursive: true });
131
+ await writeFile(path.join(directory, ".lango/xmldom/package.json"), '{"type":"commonjs"}\n', "utf8");
126
132
  for (const name of ["conventions.js", "dom-parser.js", "dom.js", "entities.js", "errors.js", "grammar.js", "index.js", "sax.js"])
127
133
  await writeFile(
128
134
  path.join(directory, ".lango/xmldom", name),
@@ -185,13 +191,16 @@ function tabDiagnostics(source, sourceName) {
185
191
  }
186
192
 
187
193
  function validateDecimalProfile(profile) {
194
+ const target = profile.target ?? "node";
195
+ if (target !== "node" && target !== "browser")
196
+ throw new LangoError("LNG1266", 1, `unknown build target ${target}`);
188
197
  const decimal_division_scale = profile.decimal_division_scale ?? 6;
189
198
  const decimal_division_rounding = profile.decimal_division_rounding ?? Decimal.Rounding.HalfEven;
190
199
  if (!Number.isInteger(decimal_division_scale) || decimal_division_scale < 0)
191
200
  throw new LangoError("LNG1262", 1, "decimal_division_scale must be a non-negative integer");
192
201
  if (!Object.values(Decimal.Rounding).includes(decimal_division_rounding))
193
202
  throw new LangoError("LNG1263", 1, `unknown decimal_division_rounding ${decimal_division_rounding}`);
194
- return { decimal_division_scale, decimal_division_rounding };
203
+ return { decimal_division_scale, decimal_division_rounding, target };
195
204
  }
196
205
 
197
206
  const BASE64_VLQ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -1055,7 +1064,10 @@ class Parser {
1055
1064
  const next = this.peek();
1056
1065
  if (next === undefined || spaces(next) < indent)
1057
1066
  this.fail(line, "LNG1262", "unterminated multi-line expression");
1058
- source += ` ${this.line().text.trim()}`;
1067
+ const continuation = this.line().text.trim();
1068
+ const startsField = /^(?:\$?[A-Za-z_][\w-]*|['"][^'"]+['"]): /.test(continuation);
1069
+ if (startsField && !/[{,]\s*$/.test(source)) source += ",";
1070
+ source += ` ${continuation}`;
1059
1071
  depth = curlyDelimiterDepth(source);
1060
1072
  }
1061
1073
  return source;
@@ -4294,9 +4306,9 @@ function emitBoolean(node, flags) {
4294
4306
  flags.boolean = true;
4295
4307
  return `__requireBoolean(${emitExpr(node, flags)})`;
4296
4308
  }
4297
- function emitString(parts) {
4309
+ function emitString(parts, flags) {
4298
4310
  if (parts.length === 1) return JSON.stringify(parts[0].text);
4299
- return `\`${parts.map((item) => `${item.text.replaceAll("`", "\\`")}${item.value === null ? "" : `\${${item.value}}`}`).join("")}\``;
4311
+ return `\`${parts.map((item) => `${item.text.replaceAll("`", "\\`")}${item.value === null ? "" : `\${${emitReceiverName(item.value, flags)}}`}`).join("")}\``;
4300
4312
  }
4301
4313
  function spaces(line) {
4302
4314
  return line.length - line.trimStart().length;
package/src/runtime.mjs CHANGED
@@ -15,6 +15,7 @@ const ROUNDING_MODES = Object.freeze({
15
15
  });
16
16
 
17
17
  export function runtimeSource(profile = {}) {
18
+ const browser = profile.target === "browser";
18
19
  const scale = profile.decimal_division_scale ?? 6;
19
20
  const rounding = profile.decimal_division_rounding ?? Decimal.Rounding.HalfEven;
20
21
  if (!Number.isInteger(scale) || scale < 0)
@@ -22,9 +23,9 @@ export function runtimeSource(profile = {}) {
22
23
  if (!(rounding in ROUNDING_MODES))
23
24
  throw new Error(`unknown decimal_division_rounding ${rounding}`);
24
25
  return `import BigNumber from "./bignumber.mjs";
25
- import { createRequire } from "node:module";
26
+ ${browser ? 'const DOMParser = globalThis.DOMParser;' : `import { createRequire } from "node:module";
26
27
  const require = createRequire(import.meta.url);
27
- const { DOMParser } = require("./xmldom/index.js");
28
+ const { DOMParser } = require("./xmldom/index.js");`}
28
29
 
29
30
  const Decimal = BigNumber.clone({
30
31
  DECIMAL_PLACES: ${scale},
@@ -207,6 +208,8 @@ export function xml2obj(xml) {
207
208
  throw new Error(\`XmlParseError: \${error.message}\`);
208
209
  }
209
210
  if (errors.length) throw new Error(\`XmlParseError: \${errors[0]}\`);
211
+ if (${browser} && document.getElementsByTagName("parsererror").length)
212
+ throw new Error("XmlParseError: invalid XML document");
210
213
  if (!document.documentElement)
211
214
  throw new Error("XmlParseError: XML must contain one root tag");
212
215
  return new Tag(document.documentElement);
@@ -270,10 +273,10 @@ export function println(value) {
270
273
  }
271
274
 
272
275
  const defaultLog = Object.freeze({
273
- debug(value) { return writeLog(process.stdout, "DEBUG", value); },
274
- info(value) { return writeLog(process.stdout, "INFO", value); },
275
- warn(value) { return writeLog(process.stderr, "WARN", value); },
276
- err(value) { return writeLog(process.stderr, "ERROR", value); },
276
+ debug(value) { ${browser ? 'console.debug(String(value)); return null;' : 'return writeLog(process.stdout, "DEBUG", value);'} },
277
+ info(value) { ${browser ? 'console.info(String(value)); return null;' : 'return writeLog(process.stdout, "INFO", value);'} },
278
+ warn(value) { ${browser ? 'console.warn(String(value)); return null;' : 'return writeLog(process.stderr, "WARN", value);'} },
279
+ err(value) { ${browser ? 'console.error(String(value)); return null;' : 'return writeLog(process.stderr, "ERROR", value);'} },
277
280
  });
278
281
 
279
282
  function writeLog(stream, level, value) {