@opencode/codemode 0.0.0-dev-20027 → 0.0.0-dev-20029

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.
@@ -1,9 +1,9 @@
1
- import type { Program } from "acorn";
1
+ import type { Program, TaggedTemplateExpression } from "acorn";
2
2
  import { Effect } from "effect";
3
3
  import { type Json } from "../data.js";
4
4
  import { type ToolRuntime } from "../tool-runtime.js";
5
5
  import { type Builtins } from "./intrinsics.js";
6
- import { type Cursor, PromiseObj, type Value } from "./objects.js";
6
+ import { type Cursor, Arr, PromiseObj, type Value } from "./objects.js";
7
7
  import { Pending } from "./promises.js";
8
8
  /** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
9
9
  export declare class Interpreter<R> {
@@ -11,6 +11,8 @@ export declare class Interpreter<R> {
11
11
  readonly pending: Pending<R>;
12
12
  readonly builtins: Builtins;
13
13
  readonly logs: Array<string>;
14
+ /** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
15
+ readonly templates: WeakMap<TaggedTemplateExpression, Arr>;
14
16
  private readonly root;
15
17
  constructor(options: {
16
18
  readonly tools: ToolRuntime<R>;
@@ -6,7 +6,7 @@ import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
7
  import { primitivePrototype } from "./intrinsics.js";
8
8
  import { globals } from "./globals.js";
9
- import { assign, Callable, define, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
9
+ import { assign, Callable, define, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
10
10
  import { preserveConsumerError } from "./callback.js";
11
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
12
12
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -163,6 +163,8 @@ export class Interpreter {
163
163
  pending;
164
164
  builtins;
165
165
  logs;
166
+ /** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
167
+ templates = new WeakMap();
166
168
  root;
167
169
  constructor(options) {
168
170
  this.tools = options.tools;
@@ -1016,6 +1018,8 @@ class Frame {
1016
1018
  return this.evaluateArrayExpression(node);
1017
1019
  case "TemplateLiteral":
1018
1020
  return this.evaluateTemplateLiteral(node);
1021
+ case "TaggedTemplateExpression":
1022
+ return this.evaluateTaggedTemplate(node);
1019
1023
  case "ConditionalExpression":
1020
1024
  return this.evaluateConditionalExpression(node);
1021
1025
  case "UpdateExpression":
@@ -1666,7 +1670,7 @@ class Frame {
1666
1670
  return Effect.gen(function* () {
1667
1671
  for (let index = 0; index < quasis.length; index += 1) {
1668
1672
  const quasi = quasis[index];
1669
- // acorn only omits `cooked` for invalid escapes in tagged templates, which are unsupported.
1673
+ // acorn only omits `cooked` for invalid escapes, which are a parse error outside a tagged template.
1670
1674
  if (typeof quasi.value.cooked !== "string") {
1671
1675
  throw typeError("Invalid template literal quasi.", quasi);
1672
1676
  }
@@ -1681,6 +1685,27 @@ class Frame {
1681
1685
  return output;
1682
1686
  });
1683
1687
  }
1688
+ // `tag\`a${x}b\`` is `tag(strings, x)`: the tag is read like a callee (a member keeps its receiver), the
1689
+ // substitutions are evaluated in order, and `strings` carries the escaped source as `strings.raw`.
1690
+ evaluateTaggedTemplate(node) {
1691
+ const self = this;
1692
+ return Effect.gen(function* () {
1693
+ const { callable, thisValue } = node.tag.type === "MemberExpression"
1694
+ ? yield* self.readMethod(node.tag)
1695
+ : { callable: yield* self.evaluateExpression(node.tag), thisValue: undefined };
1696
+ const strings = self.ctx.templates.get(node) ?? self.createTemplateObject(node);
1697
+ const values = yield* Effect.forEach(node.quasi.expressions, (expression) => self.evaluateExpression(expression));
1698
+ return yield* self.call(callable, thisValue, [strings, ...values], node, node.tag);
1699
+ });
1700
+ }
1701
+ createTemplateObject(node) {
1702
+ const array = this.ctx.builtins.Array;
1703
+ // An invalid escape such as `\unicode` cooks to `undefined` and survives only in `raw`.
1704
+ const strings = new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.cooked ?? undefined));
1705
+ define(strings, "raw", new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.raw)), frozen);
1706
+ this.ctx.templates.set(node, strings);
1707
+ return strings;
1708
+ }
1684
1709
  evaluateConditionalExpression(node) {
1685
1710
  return Effect.flatMap(this.evaluateExpression(node.test), (test) => this.evaluateExpression(test ? node.consequent : node.alternate));
1686
1711
  }
@@ -62,7 +62,7 @@ export declare const rangeError: (message: string, node?: AstNode) => PendingThr
62
62
  export declare const referenceError: (message: string, node?: AstNode) => PendingThrow;
63
63
  export declare const syntaxError: (message: string, node?: AstNode) => PendingThrow;
64
64
  export declare const uriError: (message: string, node?: AstNode) => PendingThrow;
65
- export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, tagged templates, BigInt, and custom Symbols. Use plain functions and data objects instead.";
65
+ export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
66
66
  export declare const unsupportedSyntax: (kind: string, node: AstNode) => PendingThrow;
67
67
  export declare const sourceLocation: (node: AstNode) => {
68
68
  readonly line: number;
@@ -49,7 +49,7 @@ export const referenceError = failure("ReferenceError");
49
49
  export const syntaxError = failure("SyntaxError");
50
50
  export const uriError = failure("URIError");
51
51
  // Orient the agent rather than enumerate JavaScript; interpreter-support.md is the full matrix.
52
- export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, tagged templates, BigInt, and custom Symbols. Use plain functions and data objects instead.";
52
+ export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
53
53
  export const unsupportedSyntax = (kind, node) => new PendingThrow("SyntaxError", `Syntax '${kind}' is not supported. ${supportedSyntaxMessage}`, node, "UnsupportedSyntax", [supportedSyntaxMessage]);
54
54
  // Acorn lines are 1-based and its columns are 0-based. Diagnostics use 1-based columns of the submitted source.
55
55
  export const sourceLocation = (node) => ({
@@ -2,9 +2,9 @@ import { Effect } from "effect";
2
2
  import { constructor, fn, methods } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, hidden, Arr, hostIterator, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
5
+ import { define, get, hidden, Arr, hostIterator, Obj, RegExpObj, record, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
7
- import { applyCollectionCallback, isSupportedCallback } from "../interpreter/callback.js";
7
+ import { applyCollectionCallback, isSupportedCallback, toPrimitiveString } from "../interpreter/callback.js";
8
8
  import { matchToValue, toHostRegex } from "./regexp.js";
9
9
  import { coercion } from "./value.js";
10
10
  // console is intercepted by the interpreter before reaching here.
@@ -78,6 +78,25 @@ export const stringGlobal = (ctx) => {
78
78
  methods(builtins, string, [
79
79
  codeUnits("fromCharCode", String.fromCharCode),
80
80
  codeUnits("fromCodePoint", String.fromCodePoint),
81
+ [
82
+ "raw",
83
+ 1,
84
+ (_, args) => {
85
+ const template = args[0];
86
+ const raw = template instanceof Obj ? get(template, "raw") : undefined;
87
+ if (!(raw instanceof Obj))
88
+ throw typeError("String.raw expects a template object with a raw array.");
89
+ const count = Math.max(0, coerceToInteger(get(raw, "length")));
90
+ checkArrayLength(count);
91
+ // Each literal is followed by its substitution, except the last literal, or when substitutions run out.
92
+ const parts = Array.from({ length: count }, (_, index) => index + 1 < count && index + 1 < args.length ? [get(raw, index), args[index + 1]] : [get(raw, index)]).flat();
93
+ return Effect.map(Effect.forEach(parts, (part) => toPrimitiveString(ctx, part)), (strings) => {
94
+ const output = strings.join("");
95
+ checkStringLength(output.length);
96
+ return output;
97
+ });
98
+ },
99
+ ],
81
100
  ]);
82
101
  const self = (thisValue, name) => {
83
102
  if (typeof thisValue === "string")
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/codemode",
4
- "version": "0.0.0-dev-20027",
4
+ "version": "0.0.0-dev-20029",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",