Skip to content

Latest commit

 

History

History
293 lines (221 loc) · 8.03 KB

README.md

File metadata and controls

293 lines (221 loc) · 8.03 KB

NPM Version GitHub Actions Workflow Status

TypeScript Arcscript Interpreter

@arcweave/arcscript parses and executes Arcscript embedded in the HTML generated by the Arcweave editor. It can evaluate conditions, update variable state, produce rendered HTML, and rewrite variable or scope references.

Installation

npm install @arcweave/arcscript

The package expects browser DOM APIs, including document and DOMParser. Node.js consumers must provide a compatible DOM implementation.

Quick Start

Arcscript code must be provided as an HTML fragment containing <pre><code> elements.

import { Interpreter } from '@arcweave/arcscript';

const interpreter = new Interpreter({
  state: {
    health: {
      id: 'health',
      name: 'health',
      type: 'integer',
      defaultValue: 100,
    },
  },
});

const code = [
  '<pre><code>health -= 10</code></pre>',
  '<pre><code>show(health)</code></pre>',
].join('');

const { changes, output } = interpreter.runScript(code);

console.log(changes);
// { health: 90 }

console.log(output);
// <p>90</p>

Variable State

The state option is a record keyed by variable ID. Each key must match the variable's id property.

const interpreter = new Interpreter({
  state: {
    playerName: {
      id: 'playerName',
      name: 'name',
      type: 'string',
      defaultValue: 'Ada',
    },
    playerHealth: {
      id: 'playerHealth',
      name: 'health',
      type: 'integer',
      defaultValue: 100,
      value: 75,
      scope: 'player',
    },
  },
});
Property Required Description
id Yes Stable variable ID. It must match the containing record key.
name Yes Name referenced by Arcscript.
type Yes string, integer, float, or boolean.
defaultValue Yes Initial value and the value restored by reset().
value No Current value that overrides defaultValue during initialization.
scope No Scope qualifier. Omitted, undefined, or null means unscoped/global.

Any string is treated as a named scope. In particular, scope: "global" defines a literal scope named global; it does not define an unscoped variable.

Scoped Variables

const interpreter = new Interpreter({
  state: {
    playerHealth: {
      id: 'playerHealth',
      name: 'health',
      type: 'integer',
      defaultValue: 100,
      scope: 'player',
    },
  },
});

const { changes } = interpreter.runScript(
  '<pre><code>player.health -= 10</code></pre>'
);

console.log(changes);
// { playerHealth: 90 }

Persisting Values Between Runs

Each runScript() call creates fresh runtime state. Pass previous changes as the second argument when later runs should continue from those values. The following example uses the interpreter from Quick Start.

const firstRun = interpreter.runScript(
  '<pre><code>health -= 10</code></pre>'
);

const secondRun = interpreter.runScript(
  '<pre><code>show(health)</code></pre>',
  firstRun.changes
);

console.log(secondRun.output);
// <p>90</p>

Values supplied as overrides are marked as changed and are included in the returned changes object.

Evaluating Conditions

When the input is a condition, the evaluation result is available through result.condition. The following example uses the interpreter from Quick Start.

const { result } = interpreter.runScript(
  '<pre><code>health > 0</code></pre>'
);

console.log(result.condition);
// true

Error Handling

The package exports ParseError, RuntimeError, and FatalError.

import {
  FatalError,
  ParseError,
  RuntimeError,
} from '@arcweave/arcscript';

try {
  interpreter.runScript(
    '<pre><code>unknownVariable = 1</code></pre>'
  );
} catch (error) {
  if (error instanceof ParseError) {
    console.error('Invalid Arcscript:', error.message);
  } else if (error instanceof RuntimeError) {
    console.error('Arcscript execution failed:', error.message);
  } else if (error instanceof FatalError) {
    console.error('Fatal interpreter error:', error.message);
  } else {
    throw error;
  }
}

Rewriting References

Variable replacements are keyed by variable ID, not by Arcscript variable name.

const code = '<pre><code>player.health -= 10</code></pre>';

const renamedVariable = interpreter.replaceVariables(code, {
  playerHealth: 'hitPoints',
});

const renamedScope = interpreter.replaceScope(
  renamedVariable,
  'player',
  'enemy'
);

Rewriting operates on Arcscript identifier tokens and does not replace matching text inside string literals.

API

new Interpreter(options?)

Creates an interpreter. Calling new Interpreter() is valid and creates an interpreter with an empty variable state.

Option Description
state Variable definitions keyed by ID. Required when an options object is supplied.
elementVisits Visit counts used by the visits() function.
currentElement Current element ID used by element-aware functions.
eventHandler Callback for interpreter events, such as resetVisits.

runScript(code, values?)

Executes a script or evaluates a condition.

It returns:

  • changes: variable values changed by assignments, resets, or supplied overrides.
  • output: rendered HTML produced by the script.
  • result: the visitor result. Conditions expose a boolean condition property.

parse(code)

Parses Arcscript and returns the ANTLR character stream, lexer, token stream, parser, and parse tree. This is a low-level API tied to the generated ANTLR implementation and may change between major versions.

replaceVariables(code, replacements)

Replaces referenced variable names. The replacement record is keyed by variable ID.

replaceScope(code, scope, replacement)

Replaces a scope qualifier for variables known to the interpreter state.

rewriteLegacyVariableReferences(code, replacements, options?)

Safely rewrites references whose stored variable names are not valid Arcscript identifiers. Replacements are keyed by variable ID. The method accepts raw Arcscript code and preserves its whitespace, entities, strings, and mentions.

String literals, mention spans, operators, and identifiers are recognized with the generated Arcscript lexer, and potential Arcscript expressions are checked with the generated parser. Accepted references are replaced directly at their original source ranges.

const interpreter = new Interpreter({
  state: {
    score: {
      id: 'score',
      name: 'score value',
      type: 'integer',
      defaultValue: 0,
      scope: 'hero',
    },
  },
});

const result = interpreter.rewriteLegacyVariableReferences(
  'hero . score value += 1',
  { score: 'score_value' },
  { scopeNames: ['hero'] }
);

console.log(result.code);
// hero . score_value += 1
console.log(result.blockedVariableIds);
// []

scopeNames should contain every board/component scope name, including duplicates. Variables are reported in blockedVariableIds and left unchanged when their references could instead be Arcscript syntax, when their name is duplicated within a scope, when a scoped qualifier is invalid or ambiguous, or when the requested replacement is not a non-reserved Arcscript identifier. All accepted replacements are located in the original source before any are applied, so overlapping names cannot consume each other and replacement text is never reinterpreted.

Development

This project uses the version of pnpm declared in package.json.

pnpm install
pnpm build
pnpm test
pnpm lint

The build writes compiled files and declarations to dist/.

New GitHub releases are automatically published to npmjs.com.

Contributing

  1. Fork the repository.
  2. Create a feature branch.
  3. Add or update tests for behavioral changes.
  4. Run the build and test suite.
  5. Open a pull request.

License

This project is licensed under the terms in LICENSE.txt.