From 368d05996bdf5d5537d98d4aa54b1588198de941 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 12 Mar 2026 17:27:19 -0400 Subject: [PATCH 1/9] Isolate BCH function support from loop work --- docs/proposals/bch-functions-team-review.md | 294 ++++++++++++++++++ examples/README.md | 2 + examples/helper-functions.cash | 24 ++ examples/helper-functions.ts | 24 ++ examples/testing-suite/README.md | 2 +- examples/testing-suite/contracts/example.cash | 4 + examples/testing-suite/test/example.test.ts | 8 +- packages/cashc/src/Errors.ts | 24 ++ packages/cashc/src/artifact/Artifact.ts | 31 +- packages/cashc/src/ast/AST.ts | 1 + packages/cashc/src/ast/SymbolTable.ts | 6 +- packages/cashc/src/cashc-cli.ts | 5 + packages/cashc/src/compiler.ts | 4 +- .../src/generation/GenerateTargetTraversal.ts | 61 +++- packages/cashc/src/grammar/CashScript.g4 | 2 +- .../semantic/EnsureFinalRequireTraversal.ts | 8 +- .../EnsureInvokedFunctionsSafeTraversal.ts | 90 ++++++ .../src/semantic/SymbolTableTraversal.ts | 45 ++- packages/cashc/src/utils.ts | 40 +++ .../direct_recursive_function.cash | 9 + .../mutual_recursive_functions.cash | 13 + .../internal_only_functions.cash | 5 + ...oked_function_uses_contract_parameter.cash | 9 + ...oked_function_uses_contract_parameter.cash | 13 + .../signature_check_in_invoked_function.cash | 9 + ...e_signature_check_in_invoked_function.cash | 13 + .../function_with_global_function_name.cash | 9 + .../cashc/test/generation/generation.test.ts | 113 ++++++- .../function_call_with_function_name.cash | 0 .../internal_helper_functions.cash | 9 + packages/cashscript/src/Contract.ts | 9 + packages/cashscript/src/TransactionBuilder.ts | 6 +- packages/cashscript/src/debugging.ts | 37 ++- .../src/libauth-template/LibauthTemplate.ts | 30 +- .../cashscript/src/libauth-template/utils.ts | 20 +- .../src/network/BitcoinRpcNetworkProvider.ts | 9 +- .../src/network/ElectrumNetworkProvider.ts | 6 +- .../src/network/FullStackNetworkProvider.ts | 10 +- .../cashscript/src/network/NetworkProvider.ts | 8 +- packages/cashscript/test/Contract.test.ts | 249 +++++++++++++++ packages/cashscript/test/debugging.test.ts | 1 + packages/utils/src/artifact.ts | 9 + website/docs/compiler/artifacts.md | 15 +- website/docs/compiler/bch-functions.md | 176 +++++++++++ website/docs/compiler/compiler.md | 9 + website/docs/compiler/grammar.md | 12 +- website/docs/language/contracts.md | 50 ++- website/docs/language/functions.md | 2 +- website/docs/sdk/network-provider.md | 14 + website/docs/sdk/other-network-providers.md | 2 +- website/docs/sdk/testing-setup.md | 8 +- 51 files changed, 1502 insertions(+), 57 deletions(-) create mode 100644 docs/proposals/bch-functions-team-review.md create mode 100644 examples/helper-functions.cash create mode 100644 examples/helper-functions.ts create mode 100644 packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts create mode 100644 packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash create mode 100644 packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash create mode 100644 packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash create mode 100644 packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash create mode 100644 packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash create mode 100644 packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash create mode 100644 packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash create mode 100644 packages/cashc/test/compiler/RedefinitionError/function_with_global_function_name.cash rename packages/cashc/test/{compiler/UndefinedReferenceError => valid-contract-files}/function_call_with_function_name.cash (100%) create mode 100644 packages/cashc/test/valid-contract-files/internal_helper_functions.cash create mode 100644 website/docs/compiler/bch-functions.md diff --git a/docs/proposals/bch-functions-team-review.md b/docs/proposals/bch-functions-team-review.md new file mode 100644 index 00000000..ca66de10 --- /dev/null +++ b/docs/proposals/bch-functions-team-review.md @@ -0,0 +1,294 @@ +# BCH Functions Proposal Review + +This document summarizes the working-tree patch on top of the current `next` branch. It is intended as a developer-facing review aid for the CashScript team. + +The goal of this patch is to add user-defined function calls to CashScript by compiling them to BCH function opcodes while keeping the supported subset safe enough for real integrations to experiment with. + +## Scope + +This proposal adds: + +- user-defined function calls inside a contract +- compilation of those calls to `OP_DEFINE` and `OP_INVOKE` +- an internal helper convention based on function names ending in `_` +- compiler restrictions for cases the current lowering cannot yet support safely +- artifact/runtime metadata so SDK integrations can validate VM-target expectations +- expanded compiler, codegen, SDK, runtime, and docs coverage + +This proposal does not attempt to solve: + +- signature-check support inside invoked helper functions +- constructor-parameter capture inside invoked helper functions +- recursive or mutually-recursive helper calls +- new syntax such as explicit `internal`/`public` modifiers + +## Why This Approach + +The BCH functions CHIP makes internal code reuse possible at the script level with `OP_DEFINE` and `OP_INVOKE`. CashScript can benefit from that, but the compiler and SDK currently assume a flatter execution model: + +- a contract exposes public ABI functions +- signing/debugging generally reason about a single active bytecode body +- function visibility is not part of the language syntax + +Given those constraints, the patch takes a conservative path: + +1. Reuse the existing CashScript function model rather than inventing new syntax. +2. Treat helper functions as a convention, not a grammar change. +3. Only compile the subset we can model safely today. +4. Reject unsupported patterns explicitly at compile time. +5. Carry enough artifact/provider metadata that SDK users can detect VM-target mismatches early. + +This keeps the patch small enough to review while avoiding silent miscompilation. + +## Language And Compiler Model + +### User-defined calls + +CashScript `functionCall` grammar now allows calls to user-defined contract functions as well as built-ins. + +The semantic pass now: + +- registers contract functions in a function-definition map +- resolves user-defined calls distinctly from built-in function symbols +- tracks call edges on each `FunctionDefinitionNode` + +That call graph is then used by code generation and safety analysis. + +### Internal helper convention + +Functions whose names end with `_` are treated as internal helpers. + +Effects: + +- they can still be called by other contract functions +- they are excluded from the artifact ABI +- they are not exposed as SDK unlock methods +- a contract with only helper functions is rejected as empty from the ABI perspective +- user-defined functions still cannot redefine built-in global function names + +The main reason for choosing `foo_()` is that it works with the current grammar, keeps the change small, and avoids introducing visibility syntax without parser/tooling changes. + +### Code generation + +The compiler computes the transitive closure of invoked functions and emits only that reachable set into the BCH function table. + +Lowering works like this: + +1. Public entrypoints remain selected through the existing CashScript ABI dispatch path. +2. Reachable invoked functions are compiled into bytecode fragments. +3. Those fragments are registered with `OP_DEFINE`. +4. Calls to user-defined functions emit the function index followed by `OP_INVOKE`. + +Important consequence: unused helper functions do not generate `OP_DEFINE` entries. + +## Safety Boundaries + +The proposal intentionally rejects cases that are not sound under the current frame/signing model. + +### Disallowed in invoked helper functions + +- `checkSig()` +- `checkMultiSig()` +- `checkDataSig()` +- references to constructor parameters +- direct recursion +- mutual recursion + +### Why these are rejected + +Signature operations are the most important remaining semantic gap. Invoked bytecode changes the covered-bytecode story, and the current SDK/compiler pipeline does not yet model per-invocation signing semantics precisely enough to safely support those operations. + +Constructor-parameter capture is also unsafe in the current lowering. Invoked bytecode runs with a stack frame that is not equivalent to a lexical closure, so referencing constructor parameters from helper bodies would risk reading the wrong stack slot. + +Recursion is rejected because the current implementation uses a predeclared function table and does not try to model recursive helper execution or the associated safety/debug complexity. + +The design principle here is simple: if the compiler cannot prove the current lowering is safe, it should reject the pattern. + +## Artifact And SDK Integration + +### Artifact changes + +Artifacts now record an optional `compiler.target` field. + +For contracts using BCH function opcodes, the compiler records: + +- `compiler.target: 'BCH_2026_05'` + +The `CompilerOptions.target` option and CLI `--target` flag also allow explicit overrides when needed. + +Compiler options stored in artifacts are normalized so `target` is kept as artifact metadata rather than mixed into the generic `compiler.options` bag. + +### Provider/runtime validation + +The SDK now uses provider VM-target metadata where available. + +Changes include: + +- `NetworkProvider` exposes optional `vmTarget` +- `ElectrumNetworkProvider`, `FullStackNetworkProvider`, and `BitcoinRpcNetworkProvider` carry `vmTarget` +- `Contract` fails fast when an artifact target and provider target conflict +- libauth-template generation rejects transactions that mix contracts requiring different VM targets + +This is primarily a safety and developer-experience improvement. It reduces the chance that teams compile a BCH-functions contract and then accidentally test it under a provider/debug configuration targeting older rules. + +## Debugging And Tooling Adjustments + +Two debug-related adjustments were needed to keep the existing experience coherent: + +- locking-script debug slicing now handles nested invoked frames more carefully rather than assuming the last `ip === 0` is the start of the root locking script +- pretty formatting for debug bytecode falls back to execution-order ASM when the contract uses control-flow/function opcodes whose reformatting can destabilize source mapping + +This does not fully redesign debug metadata for nested frames, but it avoids known misleading output in the current tooling. + +One remaining limitation is that debug attribution for `console.log` and `require(...)` inside invoked helper frames is still less precise than for top-level public functions. Runtime behavior is covered by tests, but nested-frame debugging should still be treated as best-effort. + +## Optimizations Included In The Patch + +The implementation also includes a few targeted codegen cleanups: + +- only reachable helper functions are emitted into the BCH function table +- unused helpers produce no `OP_DEFINE`/`OP_INVOKE` overhead +- scoped variable cleanup emits `OP_2DROP` where possible +- shallow replacement patterns use `OP_SWAP`/`OP_ROT` instead of always using integer-push plus `OP_ROLL` +- `require` debug metadata no longer writes `message: undefined` + +These changes are small but worthwhile because helper-function support should not add avoidable bytecode bloat or unnecessary debug noise. + +## Files Touched + +The patch spans four main areas. + +### Compiler and semantic analysis + +- `packages/cashc/src/grammar/CashScript.g4` +- `packages/cashc/src/semantic/SymbolTableTraversal.ts` +- `packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts` +- `packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts` +- `packages/cashc/src/generation/GenerateTargetTraversal.ts` +- `packages/cashc/src/artifact/Artifact.ts` +- `packages/cashc/src/Errors.ts` +- `packages/cashc/src/utils.ts` +- `packages/cashc/src/cashc-cli.ts` + +### SDK and runtime integration + +- `packages/cashscript/src/Contract.ts` +- `packages/cashscript/src/libauth-template/LibauthTemplate.ts` +- `packages/cashscript/src/libauth-template/utils.ts` +- `packages/cashscript/src/debugging.ts` +- `packages/cashscript/src/network/NetworkProvider.ts` +- `packages/cashscript/src/network/ElectrumNetworkProvider.ts` +- `packages/cashscript/src/network/FullStackNetworkProvider.ts` +- `packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts` + +### Shared artifact types + +- `packages/utils/src/artifact.ts` + +### Tests and docs + +- `packages/cashc/test/...` +- `packages/cashscript/test/Contract.test.ts` +- `website/docs/compiler/bch-functions.md` +- `website/docs/compiler/compiler.md` +- `website/docs/compiler/artifacts.md` +- `website/docs/compiler/grammar.md` +- `website/docs/language/contracts.md` +- `website/docs/language/functions.md` + +## Test Coverage Added + +The patch adds or updates tests in several layers. + +### Compiler/codegen tests + +- user-defined function calls are accepted instead of rejected as undefined references +- helper functions are hidden from the ABI +- reachable helper closure emits only reachable `OP_DEFINE`s +- unused helpers emit no BCH function opcodes +- helper-only contracts are rejected +- transitive signature-op usage in helpers is rejected +- transitive constructor-parameter usage in helpers is rejected +- direct recursion is rejected +- mutual recursion is rejected + +### SDK/runtime tests + +- helper-function contracts instantiate cleanly under `BCH_2026_05` +- helper functions do not appear on generated unlocker surfaces +- nested helper chains execute correctly +- shared helper chains across multiple public entrypoints work +- zero-argument helpers execute correctly +- public-to-public internal invocation works while preserving ABI visibility +- failing helper validation fails transaction execution +- older artifacts lacking `compiler.target` remain accepted +- artifact/provider VM-target mismatches fail fast +- mixed-target transaction templates fail fast +- Electrum-provider VM-target mismatch is also validated + +### Validation run status + +The patch has been validated with: + +- `npm run build` +- `cd packages/utils && yarn test` +- `cd packages/cashc && yarn test` +- `cd packages/cashscript && yarn test` +- `cd examples/testing-suite && yarn test` + +Those package-level runs are the most reliable signal in this workspace because root-level parallel test execution can race against sibling-package build outputs. + +## Documentation Updates + +The external docs now cover: + +- the BCH functions compilation model +- the `foo_()` helper convention +- the beta status of the feature +- the current safety limitations +- artifact `compiler.target` metadata +- compiler `--target` / `CompilerOptions.target` +- provider `vmTarget` behavior for local validation +- the BCH 2026 requirement for testing and integration environments + +That should make it much easier for downstream teams to understand what is supported today and what is intentionally out of scope. + +## Tradeoffs And Known Limitations + +The main tradeoff in this proposal is that it prefers a safe restricted subset over a more powerful but underspecified feature. + +Benefits: + +- small and reviewable patch +- no silent unsafe behavior in the unsupported cases we identified +- clear ABI boundary between public functions and helpers +- early VM-target validation in local integrations + +Limitations: + +- no signature ops in invoked helpers +- no constructor-parameter capture in invoked helpers +- no recursion +- visibility remains convention-based instead of syntax-based +- nested-frame debug metadata is improved but not fully redesigned, especially for helper `console.log` / `require(...)` attribution + +## Suggested Review Questions + +The most useful questions for the CashScript team to evaluate are: + +1. Is `foo_()` an acceptable temporary/internal-helper convention until explicit visibility syntax exists? +2. Is the current restricted subset the right release boundary for an experimental BCH-functions integration? +3. Is `compiler.target` the right artifact-level representation for VM-target requirements? +4. Should explicit target selection remain optional/inferred, or should the compiler require it whenever 2026-only opcodes are emitted? +5. Does the team want to preserve the helper convention long-term, or eventually replace it with grammar-level visibility modifiers? + +## Bottom Line + +This patch is intentionally not a full language-level function system. It is a careful integration of BCH `OP_DEFINE`/`OP_INVOKE` into CashScript that: + +- enables internal code reuse today +- avoids the unsafe cases we identified +- gives SDK users better target-awareness and earlier failures +- comes with enough tests and docs to make team review practical + +If the team agrees with the restricted safety model, this should be a solid base for discussing whether BCH functions belong in CashScript and what the next iteration should solve. diff --git a/examples/README.md b/examples/README.md index 803a8015..0084592a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,6 +3,8 @@ This folder contains a number of example CashScript contracts to show off its fu The "Hello World" of cash contracts is defining the P2PKH pattern inside a cash contract, which can be found under [`p2pkh.cash`](/examples/p2pkh.cash). Its usage can be found under [`p2pkh.ts`](/examples/p2pkh.ts). +For BCH function calls and internal helpers compiled via `OP_DEFINE` / `OP_INVOKE`, see [`helper-functions.cash`](/examples/helper-functions.cash) and [`helper-functions.ts`](/examples/helper-functions.ts). This example uses the supported helper-function subset and configures the mock provider for `BCH_2026_05`. + ## Running the examples To run the examples, clone this repository and navigate to the `examples/` directory. Since the examples depend on the SDK, be sure to run `yarn` inside the `examples/` directory, which installs all required packages. diff --git a/examples/helper-functions.cash b/examples/helper-functions.cash new file mode 100644 index 00000000..6bd0cb1c --- /dev/null +++ b/examples/helper-functions.cash @@ -0,0 +1,24 @@ +pragma cashscript ^0.13.0; + +contract HelperFunctions() { + function spend(int value) { + require(isPositiveEven_(value)); + } + + function spendPlusOne(int value) { + require(isPositiveEven_(value + 1)); + } + + function isPositiveEven_(int value) { + require(isPositive_(value)); + require(isEven_(value)); + } + + function isPositive_(int value) { + require(value > 0); + } + + function isEven_(int value) { + require(value % 2 == 0); + } +} diff --git a/examples/helper-functions.ts b/examples/helper-functions.ts new file mode 100644 index 00000000..8271353d --- /dev/null +++ b/examples/helper-functions.ts @@ -0,0 +1,24 @@ +import { stringify } from '@bitauth/libauth'; +import { compileFile } from 'cashc'; +import { Contract, MockNetworkProvider, randomUtxo, TransactionBuilder, VmTarget } from 'cashscript'; +import { URL } from 'url'; + +const artifact = compileFile(new URL('helper-functions.cash', import.meta.url)); +const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); +const contract = new Contract(artifact, [], { provider }); + +provider.addUtxo(contract.address, randomUtxo()); + +const [contractUtxo] = await contract.getUtxos(); +if (!contractUtxo) throw new Error('No contract UTXO found'); + +console.log('contract address:', contract.address); +console.log('public ABI functions:', artifact.abi.map((func) => func.name)); +console.log('compiler target:', artifact.compiler.target); + +const tx = await new TransactionBuilder({ provider }) + .addInput(contractUtxo, contract.unlock.spend(8n)) + .addOutput({ to: contract.address, amount: 10_000n }) + .send(); + +console.log('transaction details:', stringify(tx)); diff --git a/examples/testing-suite/README.md b/examples/testing-suite/README.md index 37b83690..e0b479ab 100644 --- a/examples/testing-suite/README.md +++ b/examples/testing-suite/README.md @@ -4,7 +4,7 @@ This is an example project to demonstrate how to write and compile CashScript co ## Writing a CashScript contract -We have included two simple example contracts in the `contracts/` directory that can be used as a starting point to write your own contracts. The contracts demonstrate logs, require statements and transaction signatures. +We have included two simple example contracts in the `contracts/` directory that can be used as a starting point to write your own contracts. The contracts demonstrate logs, require statements, transaction signatures, and BCH helper-function calls compiled via `OP_DEFINE` / `OP_INVOKE`. ## Compiling the contracts diff --git a/examples/testing-suite/contracts/example.cash b/examples/testing-suite/contracts/example.cash index 3df2d670..3c27ec46 100644 --- a/examples/testing-suite/contracts/example.cash +++ b/examples/testing-suite/contracts/example.cash @@ -1,5 +1,9 @@ contract Example() { function test(int value) { + require(validateValue_(value)); + } + + function validateValue_(int value) { console.log(value, "test"); require(value == 1, "Wrong value passed"); } diff --git a/examples/testing-suite/test/example.test.ts b/examples/testing-suite/test/example.test.ts index f2f2dfbd..b738ca34 100644 --- a/examples/testing-suite/test/example.test.ts +++ b/examples/testing-suite/test/example.test.ts @@ -1,9 +1,9 @@ import artifact from '../artifacts/example.artifact.js'; -import { Contract, MockNetworkProvider, TransactionBuilder, randomUtxo } from 'cashscript'; +import { Contract, MockNetworkProvider, TransactionBuilder, randomUtxo, VmTarget } from 'cashscript'; import 'cashscript/vitest'; describe('test example contract functions', () => { - const provider = new MockNetworkProvider(); + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const contract = new Contract(artifact, [], { provider }); // Create a contract Utxo @@ -28,4 +28,8 @@ describe('test example contract functions', () => { expect(transaction).toFailRequireWith(/Wrong value passed/); }); + it('should expose only the public test function in the ABI', () => { + expect(artifact.abi.map((func) => func.name)).toEqual(['test']); + }); + }); diff --git a/packages/cashc/src/Errors.ts b/packages/cashc/src/Errors.ts index eff20c9d..c1468b2d 100644 --- a/packages/cashc/src/Errors.ts +++ b/packages/cashc/src/Errors.ts @@ -244,6 +244,30 @@ export class ArrayElementError extends CashScriptError { } } +export class InvokedFunctionSignatureCheckError extends CashScriptError { + constructor( + public node: FunctionCallNode, + ) { + super(node, `Invoked functions cannot use '${node.identifier.name}'`); + } +} + +export class InvokedFunctionContractParameterReferenceError extends CashScriptError { + constructor( + public node: IdentifierNode, + ) { + super(node, `Invoked functions cannot reference contract parameter '${node.name}'`); + } +} + +export class CircularFunctionDependencyError extends CashScriptError { + constructor( + public node: FunctionDefinitionNode, + ) { + super(node, `Circular dependency detected for function '${node.name}'`); + } +} + export class IndexOutOfBoundsError extends CashScriptError { constructor( node: TupleIndexOpNode | BinaryOpNode | SliceNode, diff --git a/packages/cashc/src/artifact/Artifact.ts b/packages/cashc/src/artifact/Artifact.ts index 7716cd30..3337f9ff 100644 --- a/packages/cashc/src/artifact/Artifact.ts +++ b/packages/cashc/src/artifact/Artifact.ts @@ -1,8 +1,28 @@ import { - Artifact, CompilerOptions, DebugInformation, Script, scriptToAsm, + Artifact, CompilerOptions, DebugInformation, Script, VmTarget, scriptToAsm, } from '@cashscript/utils'; import { version } from '../index.js'; import { Ast } from '../ast/AST.js'; +import { getPublicFunctions } from '../utils.js'; + +function normaliseCompilerOptions(compilerOptions: CompilerOptions): CompilerOptions { + return Object.fromEntries( + Object.entries(compilerOptions).filter(([key, value]) => { + if (value === undefined) return false; + if (key === 'target') return false; + return true; + }), + ); +} + +function inferCompilerTarget(bytecode: string): VmTarget | undefined { + const requiresBch2026 = [ + 'OP_DEFINE', + 'OP_INVOKE', + ].some((opcode) => bytecode.includes(opcode)); + + return requiresBch2026 ? 'BCH_2026_05' : undefined; +} export function generateArtifact( ast: Ast, @@ -16,7 +36,7 @@ export function generateArtifact( const constructorInputs = contract.parameters .map((parameter) => ({ name: parameter.name, type: parameter.type.toString() })); - const abi = contract.functions.map((func) => ({ + const abi = getPublicFunctions(contract.functions, compilerOptions).map((func) => ({ name: func.name, inputs: func.parameters.map((parameter) => ({ name: parameter.name, @@ -36,7 +56,12 @@ export function generateArtifact( compiler: { name: 'cashc', version, - options: compilerOptions, + ...(compilerOptions.target !== undefined + ? { target: compilerOptions.target } + : inferCompilerTarget(bytecode) !== undefined + ? { target: inferCompilerTarget(bytecode) } + : {}), + options: normaliseCompilerOptions(compilerOptions), }, updatedAt: new Date().toISOString(), }; diff --git a/packages/cashc/src/ast/AST.ts b/packages/cashc/src/ast/AST.ts index a3806e29..8b166a97 100644 --- a/packages/cashc/src/ast/AST.ts +++ b/packages/cashc/src/ast/AST.ts @@ -52,6 +52,7 @@ export class ContractNode extends Node implements Named { export class FunctionDefinitionNode extends Node implements Named { symbolTable?: SymbolTable; opRolls: Map = new Map(); + calledFunctions: Set = new Set(); constructor( public name: string, diff --git a/packages/cashc/src/ast/SymbolTable.ts b/packages/cashc/src/ast/SymbolTable.ts index fcfada94..77046501 100644 --- a/packages/cashc/src/ast/SymbolTable.ts +++ b/packages/cashc/src/ast/SymbolTable.ts @@ -28,6 +28,10 @@ export class Symbol { return new Symbol(name, type, SymbolType.FUNCTION, undefined, parameters); } + static definedFunction(name: string, type: Type, definition: Node, parameters: Type[]): Symbol { + return new Symbol(name, type, SymbolType.FUNCTION, definition, parameters); + } + static class(name: string, type: Type, parameters: Type[]): Symbol { return new Symbol(name, type, SymbolType.CLASS, undefined, parameters); } @@ -73,6 +77,6 @@ export class SymbolTable { unusedSymbols(): Symbol[] { return Array.from(this.symbols) .map((e) => e[1]) - .filter((s) => s.references.length === 0); + .filter((s) => s.symbolType === SymbolType.VARIABLE && s.references.length === 0); } } diff --git a/packages/cashc/src/cashc-cli.ts b/packages/cashc/src/cashc-cli.ts index 9d35ffd0..38e3380b 100644 --- a/packages/cashc/src/cashc-cli.ts +++ b/packages/cashc/src/cashc-cli.ts @@ -26,6 +26,10 @@ program .option('-c, --opcount', 'Display the number of opcodes in the compiled bytecode.') .option('-s, --size', 'Display the size in bytes of the compiled bytecode.') .option('-S, --skip-enforce-function-parameter-types', 'Do not enforce function parameter types.') + .addOption( + new Option('-t, --target ', 'Record a required VM target in the artifact metadata.') + .choices(['BCH_2023_05', 'BCH_2025_05', 'BCH_2026_05', 'BCH_SPEC']), + ) .addOption( new Option('-f, --format ', 'Specify the format of the output.') .choices(['json', 'ts']) @@ -51,6 +55,7 @@ function run(): void { const compilerOptions: CompilerOptions = { enforceFunctionParameterTypes: !opts.skipEnforceFunctionParameterTypes, + ...(opts.target ? { target: opts.target } : {}), }; try { diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index ebc49c06..bf6d318b 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -12,6 +12,7 @@ import CashScriptParser from './grammar/CashScriptParser.js'; import SymbolTableTraversal from './semantic/SymbolTableTraversal.js'; import TypeCheckTraversal from './semantic/TypeCheckTraversal.js'; import EnsureFinalRequireTraversal from './semantic/EnsureFinalRequireTraversal.js'; +import EnsureInvokedFunctionsSafeTraversal from './semantic/EnsureInvokedFunctionsSafeTraversal.js'; export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { enforceFunctionParameterTypes: true, @@ -26,7 +27,8 @@ export function compileString(code: string, compilerOptions: CompilerOptions = { // Semantic analysis ast = ast.accept(new SymbolTableTraversal()) as Ast; ast = ast.accept(new TypeCheckTraversal()) as Ast; - ast = ast.accept(new EnsureFinalRequireTraversal()) as Ast; + ast = ast.accept(new EnsureFinalRequireTraversal(mergedCompilerOptions)) as Ast; + ast = ast.accept(new EnsureInvokedFunctionsSafeTraversal()) as Ast; // Code generation const traversal = new GenerateTargetTraversal(mergedCompilerOptions); diff --git a/packages/cashc/src/generation/GenerateTargetTraversal.ts b/packages/cashc/src/generation/GenerateTargetTraversal.ts index 500ac980..67dec6fb 100644 --- a/packages/cashc/src/generation/GenerateTargetTraversal.ts +++ b/packages/cashc/src/generation/GenerateTargetTraversal.ts @@ -9,6 +9,7 @@ import { PrimitiveType, Script, scriptToAsm, + scriptToBytecode, generateSourceMap, FullLocationData, LogEntry, @@ -65,7 +66,7 @@ import { compileTimeOp, compileUnaryOp, } from './utils.js'; -import { isNumericType } from '../utils.js'; +import { getInvokedFunctionClosure, getPublicFunctions, isNumericType } from '../utils.js'; export default class GenerateTargetTraversalWithLocation extends AstTraversal { private locationData: FullLocationData = []; // detailed location data needed for sourcemap creation @@ -80,6 +81,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { private scopeDepth = 0; private currentFunction: FunctionDefinitionNode; private constructorParameterCount: number; + private functionIndices = new Map(); constructor(private compilerOptions: CompilerOptions) { super(); @@ -145,22 +147,30 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { visitContract(node: ContractNode): Node { node.parameters = this.visitList(node.parameters) as ParameterNode[]; + const publicFunctions = getPublicFunctions(node.functions, this.compilerOptions); // Keep track of constructor parameter count for instructor pointer calculation this.constructorParameterCount = node.parameters.length; - if (node.functions.length === 1) { - node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; + const invokedFunctions = getInvokedFunctionClosure(node.functions); + if (invokedFunctions.size > 0) { + const functionDefinitions = node.functions.filter((func) => invokedFunctions.has(func.name)); + this.functionIndices = new Map(functionDefinitions.map((func, index) => [func.name, index])); + this.emitFunctionDefinitions(functionDefinitions); + } + + if (publicFunctions.length === 1) { + publicFunctions[0] = this.visit(publicFunctions[0]) as FunctionDefinitionNode; } else { this.pushToStack('$$', true); - node.functions = node.functions.map((f, i) => { + publicFunctions.forEach((f, i) => { const locationData = { location: f.location, positionHint: PositionHint.START }; const stackCopy = [...this.stack]; const selectorIndex = this.getStackIndex('$$'); this.emit(encodeInt(BigInt(selectorIndex)), locationData); - if (i === node.functions.length - 1) { + if (i === publicFunctions.length - 1) { this.emit(Op.OP_ROLL, locationData); this.removeFromStack(selectorIndex); } else { @@ -171,23 +181,22 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { this.emit(encodeInt(BigInt(i)), locationData); this.emit(Op.OP_NUMEQUAL, locationData); - if (i < node.functions.length - 1) { + if (i < publicFunctions.length - 1) { this.emit(Op.OP_IF, locationData); } else { this.emit(Op.OP_VERIFY, locationData); } - f = this.visit(f) as FunctionDefinitionNode; + this.visit(f); - if (i < node.functions.length - 1) { + if (i < publicFunctions.length - 1) { this.emit(Op.OP_ELSE, { ...locationData, positionHint: PositionHint.END }); } this.stack = [...stackCopy]; - return f; }); - for (let i = 0; i < node.functions.length - 1; i += 1) { + for (let i = 0; i < publicFunctions.length - 1; i += 1) { this.emit(Op.OP_ENDIF, { location: node.location, positionHint: PositionHint.END }); } } @@ -212,6 +221,29 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { return node; } + private emitFunctionDefinitions(functions: FunctionDefinitionNode[]): void { + functions.forEach((func) => { + const locationData = { location: func.location, positionHint: PositionHint.START }; + const functionBytecode = this.generateFunctionBytecode(func); + + this.emit(functionBytecode, locationData); + this.pushToStack('(value)'); + this.emit(encodeInt(BigInt(this.functionIndices.get(func.name)!)), locationData); + this.pushToStack('(value)'); + this.emit(Op.OP_DEFINE, { location: func.location, positionHint: PositionHint.END }); + this.popFromStack(2); + }); + } + + private generateFunctionBytecode(node: FunctionDefinitionNode): Uint8Array { + const traversal = new GenerateTargetTraversalWithLocation(this.compilerOptions); + traversal.functionIndices = new Map(this.functionIndices); + traversal.constructorParameterCount = this.constructorParameterCount; + node.accept(traversal); + traversal.output = asmToScript(scriptToAsm(traversal.output)); + return scriptToBytecode(traversal.output); + } + removeFinalVerifyFromFunction(functionBodyNode: Node): void { // After EnsureFinalRequireTraversal, we know that the final opcodes are either // "OP_VERIFY", "OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP", "OP_ENDIF" or "OP_UNTIL" @@ -550,6 +582,15 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { node.parameters = this.visitList(node.parameters); + if (node.identifier.definition?.definition instanceof FunctionDefinitionNode) { + this.emit(encodeInt(BigInt(this.functionIndices.get(node.identifier.name)!)), { location: node.location, positionHint: PositionHint.START }); + this.pushToStack('(value)'); + this.emit(Op.OP_INVOKE, { location: node.location, positionHint: PositionHint.END }); + this.popFromStack(node.parameters.length + 1); + this.pushToStack('(value)'); + return node; + } + this.emit( compileGlobalFunction(node.identifier.name as GlobalFunction), { location: node.location, positionHint: PositionHint.END }, diff --git a/packages/cashc/src/grammar/CashScript.g4 b/packages/cashc/src/grammar/CashScript.g4 index d970a06e..19d43d4d 100644 --- a/packages/cashc/src/grammar/CashScript.g4 +++ b/packages/cashc/src/grammar/CashScript.g4 @@ -129,7 +129,7 @@ consoleParameterList ; functionCall - : Identifier expressionList // Only built-in functions are accepted + : Identifier expressionList // Built-in and user-defined functions are accepted ; expressionList diff --git a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts index 5bb1d4bf..6f0f1bb2 100644 --- a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts +++ b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts @@ -1,3 +1,4 @@ +import { CompilerOptions } from '@cashscript/utils'; import { ContractNode, ParameterNode, @@ -13,13 +14,18 @@ import { } from '../ast/AST.js'; import AstTraversal from '../ast/AstTraversal.js'; import { EmptyContractError, EmptyFunctionError, FinalRequireStatementError } from '../Errors.js'; +import { getPublicFunctions } from '../utils.js'; export default class EnsureFinalRequireTraversal extends AstTraversal { + constructor(private compilerOptions: CompilerOptions = {}) { + super(); + } + visitContract(node: ContractNode): ContractNode { node.parameters = this.visitList(node.parameters) as ParameterNode[]; node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; - if (node.functions.length === 0) { + if (getPublicFunctions(node.functions, this.compilerOptions).length === 0) { throw new EmptyContractError(node); } diff --git a/packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts b/packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts new file mode 100644 index 00000000..87b00597 --- /dev/null +++ b/packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts @@ -0,0 +1,90 @@ +import { + ContractNode, + FunctionCallNode, + FunctionDefinitionNode, + IdentifierNode, + Node, + ParameterNode, +} from '../ast/AST.js'; +import AstTraversal from '../ast/AstTraversal.js'; +import { GlobalFunction } from '../ast/Globals.js'; +import { + CircularFunctionDependencyError, + InvokedFunctionContractParameterReferenceError, + InvokedFunctionSignatureCheckError, +} from '../Errors.js'; +import { getInvokedFunctionClosure } from '../utils.js'; + +const SIGNATURE_FUNCTIONS = new Set([ + GlobalFunction.CHECKSIG, + GlobalFunction.CHECKMULTISIG, + GlobalFunction.CHECKDATASIG, +]); + +export default class EnsureInvokedFunctionsSafeTraversal extends AstTraversal { + private invokedFunctions = new Set(); + private contractParameterNames = new Set(); + private currentFunction?: FunctionDefinitionNode; + + visitContract(node: ContractNode): Node { + this.contractParameterNames = new Set(node.parameters.map((parameter) => parameter.name)); + this.invokedFunctions = getInvokedFunctionClosure(node.functions); + this.ensureAcyclicFunctionCalls(node.functions); + + return super.visitContract(node); + } + + visitFunctionDefinition(node: FunctionDefinitionNode): Node { + const previousFunction = this.currentFunction; + this.currentFunction = node; + + const result = this.invokedFunctions.has(node.name) ? super.visitFunctionDefinition(node) : node; + + this.currentFunction = previousFunction; + return result; + } + + visitFunctionCall(node: FunctionCallNode): Node { + if (SIGNATURE_FUNCTIONS.has(node.identifier.name as GlobalFunction)) { + throw new InvokedFunctionSignatureCheckError(node); + } + + return super.visitFunctionCall(node); + } + + visitIdentifier(node: IdentifierNode): Node { + if ( + this.currentFunction + && this.invokedFunctions.has(this.currentFunction.name) + && this.contractParameterNames.has(node.name) + && node.definition?.definition instanceof ParameterNode + ) { + throw new InvokedFunctionContractParameterReferenceError(node); + } + + return super.visitIdentifier(node); + } + + private ensureAcyclicFunctionCalls(functions: FunctionDefinitionNode[]): void { + const functionsByName = new Map(functions.map((func) => [func.name, func])); + const visiting = new Set(); + const visited = new Set(); + + const visit = (func: FunctionDefinitionNode): void => { + if (visited.has(func.name)) return; + if (visiting.has(func.name)) { + throw new CircularFunctionDependencyError(func); + } + + visiting.add(func.name); + func.calledFunctions.forEach((calledFunctionName) => { + const calledFunction = functionsByName.get(calledFunctionName); + if (calledFunction) visit(calledFunction); + }); + visiting.delete(func.name); + visited.add(func.name); + }; + + functions.forEach(visit); + } +} diff --git a/packages/cashc/src/semantic/SymbolTableTraversal.ts b/packages/cashc/src/semantic/SymbolTableTraversal.ts index badf28f6..658142a0 100644 --- a/packages/cashc/src/semantic/SymbolTableTraversal.ts +++ b/packages/cashc/src/semantic/SymbolTableTraversal.ts @@ -1,4 +1,5 @@ import { GLOBAL_SYMBOL_TABLE, Modifier } from '../ast/Globals.js'; +import { PrimitiveType } from '@cashscript/utils'; import { ContractNode, ParameterNode, @@ -29,7 +30,7 @@ import { export default class SymbolTableTraversal extends AstTraversal { private symbolTables: SymbolTable[] = [GLOBAL_SYMBOL_TABLE]; - private functionNames: Map = new Map(); + private functionDefinitions: Map = new Map(); private currentFunction: FunctionDefinitionNode; private expectedSymbolType: SymbolType = SymbolType.VARIABLE; private insideConsoleStatement: boolean = false; @@ -39,6 +40,20 @@ export default class SymbolTableTraversal extends AstTraversal { this.symbolTables.unshift(node.symbolTable); node.parameters = this.visitList(node.parameters) as ParameterNode[]; + + node.functions.forEach((func) => { + if (this.functionDefinitions.get(func.name) || GLOBAL_SYMBOL_TABLE.get(func.name)) { + throw new FunctionRedefinitionError(func); + } + + this.functionDefinitions.set(func.name, Symbol.definedFunction( + func.name, + PrimitiveType.BOOL, + func, + func.parameters.map((parameter) => parameter.type), + )); + }); + node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; const unusedSymbols = node.symbolTable.unusedSymbols(); @@ -47,6 +62,7 @@ export default class SymbolTableTraversal extends AstTraversal { } this.symbolTables.shift(); + this.functionDefinitions.clear(); return node; } @@ -62,13 +78,6 @@ export default class SymbolTableTraversal extends AstTraversal { visitFunctionDefinition(node: FunctionDefinitionNode): Node { this.currentFunction = node; - // Checked for function redefinition, but they are not included in the - // symbol table, as internal function calls are not supported. - if (this.functionNames.get(node.name)) { - throw new FunctionRedefinitionError(node); - } - this.functionNames.set(node.name, true); - node.symbolTable = new SymbolTable(this.symbolTables[0]); this.symbolTables.unshift(node.symbolTable); @@ -157,10 +166,24 @@ export default class SymbolTableTraversal extends AstTraversal { } visitFunctionCall(node: FunctionCallNode): Node { - this.expectedSymbolType = SymbolType.FUNCTION; - node.identifier = this.visit(node.identifier) as IdentifierNode; - this.expectedSymbolType = SymbolType.VARIABLE; + const symbol = this.symbolTables[0].get(node.identifier.name); + if (symbol?.symbolType === SymbolType.FUNCTION) { + node.identifier.definition = symbol; + node.identifier.definition.references.push(node.identifier); + } else if (this.functionDefinitions.has(node.identifier.name)) { + node.identifier.definition = this.functionDefinitions.get(node.identifier.name)!; + } else if (symbol) { + throw new InvalidSymbolTypeError(node.identifier, SymbolType.FUNCTION); + } else { + throw new UndefinedReferenceError(node.identifier); + } + node.parameters = this.visitList(node.parameters); + + if (node.identifier.definition?.definition instanceof FunctionDefinitionNode) { + this.currentFunction.calledFunctions.add(node.identifier.name); + } + return node; } diff --git a/packages/cashc/src/utils.ts b/packages/cashc/src/utils.ts index c24a140d..1bc1fb8d 100644 --- a/packages/cashc/src/utils.ts +++ b/packages/cashc/src/utils.ts @@ -1,5 +1,7 @@ import { BytesType, implicitlyCastable, PrimitiveType, Type } from '@cashscript/utils'; import { BinaryOperator } from './ast/Operator.js'; +import { CompilerOptions } from '@cashscript/utils'; +import { FunctionDefinitionNode } from './ast/AST.js'; export function resultingTypeForBinaryOp( operator: BinaryOperator, @@ -20,3 +22,41 @@ export function resultingTypeForBinaryOp( export function isNumericType(type?: Type): boolean { return type === PrimitiveType.INT || type === PrimitiveType.BOOL; } + +export function isInternalFunctionName( + name: string, + compilerOptions: CompilerOptions = {}, +): boolean { + const prefix = compilerOptions.internalFunctionPrefix; + if (prefix !== undefined) { + return prefix.length > 0 && name.startsWith(prefix); + } + + return name.endsWith('_'); +} + +export function getPublicFunctions( + functions: FunctionDefinitionNode[], + compilerOptions: CompilerOptions = {}, +): FunctionDefinitionNode[] { + return functions.filter((func) => !isInternalFunctionName(func.name, compilerOptions)); +} + +export function getInvokedFunctionClosure(functions: FunctionDefinitionNode[]): Set { + const functionsByName = new Map(functions.map((func) => [func.name, func])); + const reachableFunctions = new Set(); + const pending = functions.flatMap((func) => Array.from(func.calledFunctions)); + + while (pending.length > 0) { + const functionName = pending.pop()!; + if (reachableFunctions.has(functionName)) continue; + + reachableFunctions.add(functionName); + const definition = functionsByName.get(functionName); + if (!definition) continue; + + pending.push(...definition.calledFunctions); + } + + return reachableFunctions; +} diff --git a/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash b/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash new file mode 100644 index 00000000..5a286ec2 --- /dev/null +++ b/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash @@ -0,0 +1,9 @@ +contract Test() { + function spend(int value) { + require(loop_(value)); + } + + function loop_(int value) { + require(loop_(value)); + } +} diff --git a/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash b/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash new file mode 100644 index 00000000..5452e49e --- /dev/null +++ b/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash @@ -0,0 +1,13 @@ +contract Test() { + function spend(int value) { + require(first_(value)); + } + + function first_(int value) { + require(second_(value)); + } + + function second_(int value) { + require(first_(value)); + } +} diff --git a/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash b/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash new file mode 100644 index 00000000..f6db7ec2 --- /dev/null +++ b/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash @@ -0,0 +1,5 @@ +contract Test() { + function only_() { + require(true); + } +} diff --git a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash new file mode 100644 index 00000000..749d1a42 --- /dev/null +++ b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash @@ -0,0 +1,9 @@ +contract Test(int threshold) { + function spend(int value) { + require(isAboveThreshold_(value)); + } + + function isAboveThreshold_(int value) { + require(value > threshold); + } +} diff --git a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash new file mode 100644 index 00000000..4b1c4eee --- /dev/null +++ b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash @@ -0,0 +1,13 @@ +contract Test(int requiredValue) { + function spend(int value) { + require(wrapper_(value)); + } + + function wrapper_(int value) { + require(matchesRequiredValue_(value)); + } + + function matchesRequiredValue_(int value) { + require(value == requiredValue); + } +} diff --git a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash new file mode 100644 index 00000000..090489b2 --- /dev/null +++ b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash @@ -0,0 +1,9 @@ +contract Test() { + function hello(sig s, pubkey pk) { + require(world(s, pk)); + } + + function world(sig s, pubkey pk) { + require(checkSig(s, pk)); + } +} diff --git a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash new file mode 100644 index 00000000..e160bc52 --- /dev/null +++ b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash @@ -0,0 +1,13 @@ +contract Test(pubkey owner) { + function spend(sig ownerSig) { + require(wrapper_(ownerSig)); + } + + function wrapper_(sig ownerSig) { + require(checkOwner_(ownerSig)); + } + + function checkOwner_(sig ownerSig) { + require(checkSig(ownerSig, owner)); + } +} diff --git a/packages/cashc/test/compiler/RedefinitionError/function_with_global_function_name.cash b/packages/cashc/test/compiler/RedefinitionError/function_with_global_function_name.cash new file mode 100644 index 00000000..92f259af --- /dev/null +++ b/packages/cashc/test/compiler/RedefinitionError/function_with_global_function_name.cash @@ -0,0 +1,9 @@ +contract Test() { + function sha256(int value) { + require(value == 1); + } + + function spend() { + require(true); + } +} diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index 4e416881..cef27413 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -4,7 +4,8 @@ */ import { URL } from 'url'; -import { compileFile } from '../../src/index.js'; +import fs from 'fs'; +import { compileFile, compileString } from '../../src/index.js'; import { fixtures } from './fixtures.js'; describe('Code generation & target code optimisation', () => { @@ -14,4 +15,114 @@ describe('Code generation & target code optimisation', () => { expect(artifact).toEqual({ ...fixture.artifact, updatedAt: expect.any(String) }); }); }); + + it('should hide internal helper functions from the ABI and emit BCH function opcodes', () => { + const source = fs.readFileSync( + new URL('../valid-contract-files/internal_helper_functions.cash', import.meta.url), + { encoding: 'utf-8' }, + ); + + const artifact = compileString(source); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode).toContain('OP_DEFINE'); + expect(artifact.bytecode).toContain('OP_INVOKE'); + }); + + it('should only emit OP_DEFINE for reachable internal helpers', () => { + const artifact = compileString(` +contract ReachableHelpers() { + function spend(int x) { + require(doubleCheck_(x)); + } + + function fallback(int x) { + require(isEven_(x)); + } + + function doubleCheck_(int value) { + require(isEven_(value)); + } + + function isEven_(int value) { + require(value % 2 == 0); + } + + function unused_(int value) { + require(value == 42); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + { name: 'fallback', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(2); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + }); + + it('should not emit BCH function opcodes for unused internal helpers', () => { + const artifact = compileString(` +contract UnusedHelpers() { + function spend(int x) { + require(x == 7); + } + + function helper_(int value) { + require(value == 7); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode).not.toContain('OP_DEFINE'); + expect(artifact.bytecode).not.toContain('OP_INVOKE'); + }); + + it('should support invoking a public function from another public function', () => { + const artifact = compileString(` +contract PublicCalls() { + function spend(int x) { + require(validate(x)); + } + + function validate(int value) { + require(value == 7); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + { name: 'validate', inputs: [{ name: 'value', type: 'int' }] }, + ]); + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(1); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(1); + expect(artifact.compiler.target).toBe('BCH_2026_05'); + }); + + it('should respect a custom internal function prefix when requested', () => { + const artifact = compileString(` +contract CustomPrefix() { + function spend(int x) { + require(helperCheck(x)); + } + + function helperCheck(int value) { + require(value == 11); + } +} +`, { internalFunctionPrefix: 'helper' }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode).toContain('OP_DEFINE'); + expect(artifact.bytecode).toContain('OP_INVOKE'); + }); }); diff --git a/packages/cashc/test/compiler/UndefinedReferenceError/function_call_with_function_name.cash b/packages/cashc/test/valid-contract-files/function_call_with_function_name.cash similarity index 100% rename from packages/cashc/test/compiler/UndefinedReferenceError/function_call_with_function_name.cash rename to packages/cashc/test/valid-contract-files/function_call_with_function_name.cash diff --git a/packages/cashc/test/valid-contract-files/internal_helper_functions.cash b/packages/cashc/test/valid-contract-files/internal_helper_functions.cash new file mode 100644 index 00000000..fe234696 --- /dev/null +++ b/packages/cashc/test/valid-contract-files/internal_helper_functions.cash @@ -0,0 +1,9 @@ +contract InternalHelperFunctions() { + function spend(int x) { + require(isTen_(x)); + } + + function isTen_(int value) { + require(value == 10); + } +} diff --git a/packages/cashscript/src/Contract.ts b/packages/cashscript/src/Contract.ts index 0a02600e..9cf93acb 100644 --- a/packages/cashscript/src/Contract.ts +++ b/packages/cashscript/src/Contract.ts @@ -72,6 +72,15 @@ class ContractInternal< throw new Error(`Artifact compiled with unsupported compiler version: ${artifact.compiler.version}, required >=0.7.0`); } + if ( + artifact.compiler.target + && this.provider.vmTarget + && this.provider.vmTarget !== artifact.compiler.target + && this.provider.vmTarget !== 'BCH_SPEC' + ) { + throw new Error(`Artifact requires VM target ${artifact.compiler.target}, but the current provider is configured for ${this.provider.vmTarget}`); + } + if (artifact.constructorInputs.length !== constructorArgs.length) { throw new Error(`Incorrect number of arguments passed to ${artifact.contractName} constructor. Expected ${artifact.constructorInputs.length} arguments (${artifact.constructorInputs.map((input) => input.type)}) but got ${constructorArgs.length}`); } diff --git a/packages/cashscript/src/TransactionBuilder.ts b/packages/cashscript/src/TransactionBuilder.ts index dffeae7a..c912c9f7 100644 --- a/packages/cashscript/src/TransactionBuilder.ts +++ b/packages/cashscript/src/TransactionBuilder.ts @@ -203,9 +203,9 @@ export class TransactionBuilder { .map((input) => 'contract' in input.unlocker ? input.unlocker.contract.artifact.compiler.version : null) .filter((version) => version !== null); - if (!contractVersions.every((version) => semver.satisfies(version, '>=0.11.0'))) { - console.warn('For the best debugging experience, please recompile your contract with cashc version 0.11.0 or newer.'); - } + if (!contractVersions.every((version) => semver.satisfies(version, '>=0.11.0'))) { + console.warn('For the best debugging experience, please recompile your contract with cashc version 0.11.0 or newer.'); + } return debugLibauthTemplate(this.getLibauthTemplate(), this); } diff --git a/packages/cashscript/src/debugging.ts b/packages/cashscript/src/debugging.ts index d5bf3991..20a43bda 100644 --- a/packages/cashscript/src/debugging.ts +++ b/packages/cashscript/src/debugging.ts @@ -69,10 +69,13 @@ const debugSingleScenario = ( const { vm, program } = createProgram(template, unlockingScriptId, scenarioId); const fullDebugSteps = vm.debug(program); + const lockingScriptStartIndex = getLockingScriptStartIndex(fullDebugSteps, artifact); - // P2SH executions have 3 phases, we only want the last one (locking script execution) + // P2SH executions have 3 phases, and BCH functions can introduce nested bytecode frames with `ip === 0`. + // We slice from the first step whose active bytecode ends with the contract bytecode, rather than from the + // last `ip === 0`, so invoked helper functions do not get mistaken for the start of the locking script phase. // https://libauth.org/types/AuthenticationVirtualMachine.html#__type.debug - const lockingScriptDebugResult = fullDebugSteps.slice(findLastIndex(fullDebugSteps, (state) => state.ip === 0)); + const lockingScriptDebugResult = fullDebugSteps.slice(lockingScriptStartIndex); // The controlStack determines whether the current debug step is in the executed branch // It also tracks loop / function usage, but for the purpose of determining whether a step was executed, @@ -192,6 +195,36 @@ const debugSingleScenario = ( return fullDebugSteps; }; +const instructionsToBytecodeHex = (instructions: AuthenticationInstruction[]): string => ( + binToHex(Uint8Array.from(instructions.flatMap((instruction) => Array.from(encodeAuthenticationInstruction(instruction))))) +); + +const getLockingScriptStartIndex = ( + fullDebugSteps: AuthenticationProgramStateCommon[], + artifact: Artifact | undefined, +): number => { + const rootFrameStartIndex = findLastIndex( + fullDebugSteps, + (state) => state.ip === 0 && state.controlStack.length === 0, + ); + + if (rootFrameStartIndex !== -1) { + return rootFrameStartIndex; + } + + if (artifact?.debug?.bytecode) { + const matchingBytecodeIndex = fullDebugSteps.findIndex((state) => ( + state.ip === 0 && instructionsToBytecodeHex(state.instructions).endsWith(artifact.debug!.bytecode) + )); + + if (matchingBytecodeIndex !== -1) { + return matchingBytecodeIndex; + } + } + + return findLastIndex(fullDebugSteps, (state) => state.ip === 0); +}; + // Note: this relies on the naming convention that the scenario ID is of the form _input_evaluate const extractInputIndexFromScenario = (scenarioId: string): number => { const match = scenarioId.match(/_input(\d+)_/); diff --git a/packages/cashscript/src/libauth-template/LibauthTemplate.ts b/packages/cashscript/src/libauth-template/LibauthTemplate.ts index 7983f7d3..6684f643 100644 --- a/packages/cashscript/src/libauth-template/LibauthTemplate.ts +++ b/packages/cashscript/src/libauth-template/LibauthTemplate.ts @@ -36,6 +36,7 @@ import { TransactionBuilder } from '../TransactionBuilder.js'; import { zlibSync } from 'fflate'; import MockNetworkProvider from '../network/MockNetworkProvider.js'; import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, serialiseTokenDetails } from './utils.js'; +import { VmTarget } from '../interfaces.js'; // TODO: Add / improve descriptions throughout the template generation @@ -47,10 +48,35 @@ export const getLibauthTemplate = ( } const libauthTransaction = transactionBuilder.buildLibauthTransaction(); + const requiredArtifactVmTargets = Array.from(new Set( + transactionBuilder.inputs + .flatMap((input) => isContractUnlocker(input.unlocker) + ? [input.unlocker.contract.artifact.compiler.target] + : []) + .filter((target): target is VmTarget => Boolean(target)), + )); + + if (requiredArtifactVmTargets.length > 1) { + throw new Error(`Cannot build a transaction template for contracts requiring different VM targets: ${requiredArtifactVmTargets.join(', ')}`); + } - const vmTarget = transactionBuilder.provider instanceof MockNetworkProvider + const requiredArtifactVmTarget = requiredArtifactVmTargets[0]; + const providerVmTarget = transactionBuilder.provider instanceof MockNetworkProvider ? transactionBuilder.provider.vmTarget - : DEFAULT_VM_TARGET; + : undefined; + + if ( + requiredArtifactVmTarget + && providerVmTarget + && providerVmTarget !== requiredArtifactVmTarget + && providerVmTarget !== VmTarget.BCH_SPEC + ) { + throw new Error(`Contract artifact requires VM target ${requiredArtifactVmTarget}, but the current provider is configured for ${providerVmTarget}`); + } + + const vmTarget = requiredArtifactVmTarget + ?? providerVmTarget + ?? DEFAULT_VM_TARGET; const template: WalletTemplate = { $schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json', diff --git a/packages/cashscript/src/libauth-template/utils.ts b/packages/cashscript/src/libauth-template/utils.ts index d03191ca..db3dd0c0 100644 --- a/packages/cashscript/src/libauth-template/utils.ts +++ b/packages/cashscript/src/libauth-template/utils.ts @@ -1,4 +1,11 @@ -import { AbiFunction, AbiInput, Artifact, bytecodeToScript, formatBitAuthScript } from '@cashscript/utils'; +import { + AbiFunction, + AbiInput, + Artifact, + bytecodeToBitAuthAsm, + bytecodeToScript, + formatBitAuthScript, +} from '@cashscript/utils'; import { HashType, LibauthTokenDetails, SignatureAlgorithm, TokenDetails, VmTarget } from '../interfaces.js'; import { hexToBin, binToHex, isHex, decodeCashAddress, Input, assertSuccess, decodeAuthenticationInstructions, AuthenticationInstructionPush } from '@bitauth/libauth'; import { EncodedFunctionArgument } from '../Argument.js'; @@ -80,6 +87,17 @@ export const formatBytecodeForDebugging = (artifact: Artifact): string => { .join('\n'); } + const usesUnstableControlFlowFormatting = [ + 'OP_DEFINE', + 'OP_INVOKE', + 'OP_BEGIN', + 'OP_UNTIL', + ].some((opcode) => artifact.bytecode.includes(opcode)); + + if (usesUnstableControlFlowFormatting) { + return bytecodeToBitAuthAsm(hexToBin(artifact.debug.bytecode)); + } + return formatBitAuthScript( bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, diff --git a/packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts b/packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts index 4ef55820..80ac032f 100644 --- a/packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts +++ b/packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts @@ -1,5 +1,6 @@ -import { Utxo, Network } from '../interfaces.js'; +import { Utxo, Network, VmTarget } from '../interfaces.js'; import NetworkProvider from './NetworkProvider.js'; +import { DEFAULT_VM_TARGET } from '../libauth-template/utils.js'; import { BchnRpcClient, type GetBlockCount, @@ -10,6 +11,7 @@ import { export default class BitcoinRpcNetworkProvider implements NetworkProvider { private rpcClient: BchnRpcClient; + public vmTarget: VmTarget; constructor( public network: Network, @@ -17,9 +19,12 @@ export default class BitcoinRpcNetworkProvider implements NetworkProvider { opts: { rpcUser: string; rpcPassword: string; + vmTarget?: VmTarget; }, ) { - this.rpcClient = new BchnRpcClient({ url, ...opts }); + const { rpcUser, rpcPassword } = opts; + this.rpcClient = new BchnRpcClient({ url, rpcUser, rpcPassword }); + this.vmTarget = opts.vmTarget ?? DEFAULT_VM_TARGET; } async getUtxos(address: string): Promise { diff --git a/packages/cashscript/src/network/ElectrumNetworkProvider.ts b/packages/cashscript/src/network/ElectrumNetworkProvider.ts index 07ca5fa0..b18d76a3 100644 --- a/packages/cashscript/src/network/ElectrumNetworkProvider.ts +++ b/packages/cashscript/src/network/ElectrumNetworkProvider.ts @@ -5,13 +5,15 @@ import { type RequestResponse, type ElectrumClientEvents, } from '@electrum-cash/network'; -import { Utxo, Network } from '../interfaces.js'; +import { Utxo, Network, VmTarget } from '../interfaces.js'; import NetworkProvider from './NetworkProvider.js'; import { addressToLockScript } from '../utils.js'; +import { DEFAULT_VM_TARGET } from '../libauth-template/utils.js'; interface OptionsBase { manualConnectionManagement?: boolean; + vmTarget?: VmTarget; } interface CustomHostNameOptions extends OptionsBase { @@ -28,10 +30,12 @@ export default class ElectrumNetworkProvider implements NetworkProvider { private electrum: ElectrumClient; private concurrentRequests: number = 0; private manualConnectionManagement: boolean; + public vmTarget: VmTarget; constructor(public network: Network = Network.MAINNET, options: Options = {}) { this.electrum = this.instantiateElectrumClient(network, options); this.manualConnectionManagement = options?.manualConnectionManagement ?? false; + this.vmTarget = options.vmTarget ?? DEFAULT_VM_TARGET; } private instantiateElectrumClient(network: Network, options: Options): ElectrumClient { diff --git a/packages/cashscript/src/network/FullStackNetworkProvider.ts b/packages/cashscript/src/network/FullStackNetworkProvider.ts index 80d289c8..cc0e8314 100644 --- a/packages/cashscript/src/network/FullStackNetworkProvider.ts +++ b/packages/cashscript/src/network/FullStackNetworkProvider.ts @@ -1,5 +1,6 @@ -import { Utxo, Network } from '../interfaces.js'; +import { Utxo, Network, VmTarget } from '../interfaces.js'; import NetworkProvider from './NetworkProvider.js'; +import { DEFAULT_VM_TARGET } from '../libauth-template/utils.js'; export default class FullStackNetworkProvider implements NetworkProvider { /** @@ -10,10 +11,15 @@ export default class FullStackNetworkProvider implements NetworkProvider { * apiToken: 'eyJhbGciO...' // Your JWT token here. * }) */ + public vmTarget: VmTarget; + constructor( public network: Network, private bchjs: BCHJS, - ) { } + vmTarget: VmTarget = DEFAULT_VM_TARGET, + ) { + this.vmTarget = vmTarget; + } async getUtxos(address: string): Promise { const result = await this.bchjs.Electrumx.utxo(address); diff --git a/packages/cashscript/src/network/NetworkProvider.ts b/packages/cashscript/src/network/NetworkProvider.ts index e966982b..b6801eaa 100644 --- a/packages/cashscript/src/network/NetworkProvider.ts +++ b/packages/cashscript/src/network/NetworkProvider.ts @@ -1,4 +1,4 @@ -import { Utxo, Network } from '../interfaces.js'; +import { Utxo, Network, VmTarget } from '../interfaces.js'; export default interface NetworkProvider { /** @@ -6,6 +6,12 @@ export default interface NetworkProvider { */ network: Network; + /** + * Optional VM target metadata used for local validation/debugging. + * Providers that know which BCH VM rules they target should expose this. + */ + vmTarget?: VmTarget; + /** * Retrieve all UTXOs (confirmed and unconfirmed) for a given address. * @param address The CashAddress for which we wish to retrieve UTXOs. diff --git a/packages/cashscript/test/Contract.test.ts b/packages/cashscript/test/Contract.test.ts index d3386e43..8343d384 100644 --- a/packages/cashscript/test/Contract.test.ts +++ b/packages/cashscript/test/Contract.test.ts @@ -1,5 +1,6 @@ import { hexToBin } from '@bitauth/libauth'; import { placeholder } from '@cashscript/utils'; +import { compileString } from 'cashc'; import { Contract, ElectrumNetworkProvider, @@ -8,6 +9,7 @@ import { randomUtxo, SignatureTemplate, TransactionBuilder, + VmTarget, } from '../src/index.js'; import { aliceAddress, @@ -21,6 +23,79 @@ import mecenasArtifact from './fixture/mecenas.artifact.js'; import deprecatedMecenasArtifact from './fixture/deprecated/mecenas-v0.6.0.json' with { type: 'json' }; import boundedBytesArtifact from './fixture/bounded_bytes.artifact.js'; +const helperFunctionArtifact = compileString(` +contract HelperFunctions() { + function spend(int value) { + require(isAtLeastSeven_(value)); + } + + function isAtLeastSeven_(int value) { + require(value >= 7); + } +} +`); + +const nestedHelperFunctionArtifact = compileString(` +contract NestedHelperFunctions() { + function spend(int value) { + require(isPositiveAndEven_(value)); + } + + function spendPlusOne(int value) { + require(isPositiveAndEven_(value + 1)); + } + + function isPositiveAndEven_(int value) { + require(isPositive_(value)); + require(isEven_(value)); + } + + function isPositive_(int value) { + require(value > 0); + } + + function isEven_(int value) { + require(value % 2 == 0); + } + + function unused_(int value) { + require(value == 42); + } +} +`); + +const oldHelperFunctionArtifact = { + ...helperFunctionArtifact, + compiler: { + ...helperFunctionArtifact.compiler, + target: undefined, + }, +}; + +const zeroArgHelperArtifact = compileString(` +contract ZeroArgHelper() { + function spend() { + require(exactlyOneOutput_()); + } + + function exactlyOneOutput_() { + require(tx.outputs.length == 1); + } +} +`); + +const publicFunctionCallArtifact = compileString(` +contract PublicFunctionCalls() { + function spend(int value) { + require(validate(value)); + } + + function validate(int value) { + require(value == 7); + } +} +`); + describe('Contract', () => { describe('new', () => { it('should fail with incorrect constructor args', () => { @@ -196,4 +271,178 @@ describe('Contract', () => { .toEqual(hexToBin('4135fac4118af15e0d66f30548dd0c31e1108f3389af96bb9db4f2305706e18fe52cc7163f6440fae98c48332d09c30380527a90604f14b4b3fc0c3aa0884c9c0a61210373cc07b54c22da627b572a387a20ea190c9382e5e6d48c1d5b89c5cea2c4c0881914512dbb2c8c02efbac8d92431aa0ac33f6b0bf97078a988ac')); }); }); + + describe('helper functions', () => { + it('exposes only public functions in the ABI and generated unlockers', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(helperFunctionArtifact, [], { provider }); + + expect(helperFunctionArtifact.abi.map((func) => func.name)).toEqual(['spend']); + expect(helperFunctionArtifact.compiler.target).toBe(VmTarget.BCH_2026_05); + expect(helperFunctionArtifact.bytecode).toContain('OP_DEFINE'); + expect(helperFunctionArtifact.bytecode).toContain('OP_INVOKE'); + expect(typeof instance.unlock.spend).toBe('function'); + expect((instance.unlock as Record).isAtLeastSeven_).toBeUndefined(); + }); + + it('generates unlockers for helper-function contracts under BCH_2026_05', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(helperFunctionArtifact, [], { provider }); + + const unlocker = instance.unlock.spend(7n); + + expect(unlocker.abiFunction.name).toBe('spend'); + expect(unlocker.params).toEqual([7n]); + expect(instance.address).toContain(':'); + expect(new TransactionBuilder({ provider }) + .addInput(randomUtxo(), unlocker) + .addOutput({ to: instance.address, amount: 1000n }) + .getLibauthTemplate() + .supported[0]).toBe(VmTarget.BCH_2026_05); + }); + + it('can execute a helper-function contract on BCH_2026_05', async () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(helperFunctionArtifact, [], { provider }); + const utxo = randomUtxo(); + provider.addUtxo(instance.address, utxo); + + const successTransaction = new TransactionBuilder({ provider }) + .addInput(utxo, instance.unlock.spend(7n)) + .addOutput({ to: instance.address, amount: 1000n }); + + await expect(successTransaction.send()).resolves.toBeDefined(); + + const failingUtxo = randomUtxo(); + provider.addUtxo(instance.address, failingUtxo); + + const failingTransaction = new TransactionBuilder({ provider }) + .addInput(failingUtxo, instance.unlock.spend(6n)) + .addOutput({ to: instance.address, amount: 1000n }); + + await expect(failingTransaction.send()).rejects.toThrow(); + }); + + it('supports nested helper calls shared across multiple public functions', async () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(nestedHelperFunctionArtifact, [], { provider }); + + expect(nestedHelperFunctionArtifact.abi.map((func) => func.name)).toEqual(['spend', 'spendPlusOne']); + expect(nestedHelperFunctionArtifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(3); + expect(nestedHelperFunctionArtifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + expect((instance.unlock as Record).isPositiveAndEven_).toBeUndefined(); + expect((instance.unlock as Record).unused_).toBeUndefined(); + + const successUtxo = randomUtxo(); + provider.addUtxo(instance.address, successUtxo); + + await expect( + new TransactionBuilder({ provider }) + .addInput(successUtxo, instance.unlock.spend(8n)) + .addOutput({ to: instance.address, amount: 1000n }) + .send(), + ).resolves.toBeDefined(); + + const sharedHelperUtxo = randomUtxo(); + provider.addUtxo(instance.address, sharedHelperUtxo); + + await expect( + new TransactionBuilder({ provider }) + .addInput(sharedHelperUtxo, instance.unlock.spendPlusOne(3n)) + .addOutput({ to: instance.address, amount: 1000n }) + .send(), + ).resolves.toBeDefined(); + }); + + it('supports zero-argument helper functions', async () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(zeroArgHelperArtifact, [], { provider }); + const utxo = randomUtxo(); + provider.addUtxo(instance.address, utxo); + + await expect( + new TransactionBuilder({ provider }) + .addInput(utxo, instance.unlock.spend()) + .addOutput({ to: instance.address, amount: 1000n }) + .send(), + ).resolves.toBeDefined(); + }); + + it('supports invoking a public function from another public function while keeping both ABI methods', async () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(publicFunctionCallArtifact, [], { provider }); + + expect(publicFunctionCallArtifact.abi.map((func) => func.name)).toEqual(['spend', 'validate']); + + const utxo = randomUtxo(); + provider.addUtxo(instance.address, utxo); + + await expect( + new TransactionBuilder({ provider }) + .addInput(utxo, instance.unlock.spend(7n)) + .addOutput({ to: instance.address, amount: 1000n }) + .send(), + ).resolves.toBeDefined(); + }); + + it('fails nested helper validation through the SDK when helper requirements are not met', async () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const instance = new Contract(nestedHelperFunctionArtifact, [], { provider }); + const utxo = randomUtxo(); + provider.addUtxo(instance.address, utxo); + + await expect( + new TransactionBuilder({ provider }) + .addInput(utxo, instance.unlock.spend(3n)) + .addOutput({ to: instance.address, amount: 1000n }) + .send(), + ).rejects.toThrow(); + }); + + it('fails fast when a provider VM target does not match the artifact requirement', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2025_05 }); + + expect(() => new Contract(helperFunctionArtifact, [], { provider })) + .toThrow(/requires VM target BCH_2026_05/); + }); + + it('fails fast when an electrum provider VM target does not match the artifact requirement', () => { + const provider = new ElectrumNetworkProvider(Network.CHIPNET, { vmTarget: VmTarget.BCH_2025_05 }); + + expect(() => new Contract(helperFunctionArtifact, [], { provider })) + .toThrow(/requires VM target BCH_2026_05/); + }); + + it('accepts older artifacts that do not encode compiler.target metadata', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + + expect(() => new Contract(oldHelperFunctionArtifact, [], { provider })).not.toThrow(); + }); + + it('fails when mixing contracts that require different VM targets in one transaction template', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_SPEC }); + const helperInstance = new Contract(helperFunctionArtifact, [], { provider }); + const legacyTargetArtifact = { + ...helperFunctionArtifact, + contractName: 'LegacyTargetHelper', + compiler: { + ...helperFunctionArtifact.compiler, + target: VmTarget.BCH_2025_05, + }, + }; + const legacyInstance = new Contract(legacyTargetArtifact, [], { provider }); + + const utxo1 = randomUtxo(); + const utxo2 = randomUtxo(); + provider.addUtxo(helperInstance.address, utxo1); + provider.addUtxo(legacyInstance.address, utxo2); + + expect(() => new TransactionBuilder({ provider }) + .addInput(utxo1, helperInstance.unlock.spend(7n)) + .addInput(utxo2, legacyInstance.unlock.spend(7n)) + .addOutput({ to: helperInstance.address, amount: 1000n }) + .getLibauthTemplate()) + .toThrow(/different VM targets/); + }); + }); }); diff --git a/packages/cashscript/test/debugging.test.ts b/packages/cashscript/test/debugging.test.ts index 2b27b7a0..8a666c87 100644 --- a/packages/cashscript/test/debugging.test.ts +++ b/packages/cashscript/test/debugging.test.ts @@ -774,6 +774,7 @@ describe('Debugging tests', () => { expect(transaction).toLog(expectedLog); }); }); + }); describe('VM Resources', () => { diff --git a/packages/utils/src/artifact.ts b/packages/utils/src/artifact.ts index 1e44ddcd..c351b30e 100644 --- a/packages/utils/src/artifact.ts +++ b/packages/utils/src/artifact.ts @@ -1,5 +1,13 @@ +export type VmTarget = + | 'BCH_2023_05' + | 'BCH_2025_05' + | 'BCH_2026_05' + | 'BCH_SPEC'; + export interface CompilerOptions { enforceFunctionParameterTypes?: boolean; + internalFunctionPrefix?: string; + target?: VmTarget; } export interface AbiInput { @@ -57,6 +65,7 @@ export interface Artifact { compiler: { name: string; version: string; + target?: VmTarget; options?: CompilerOptions; } updatedAt: string; diff --git a/website/docs/compiler/artifacts.md b/website/docs/compiler/artifacts.md index b2f21d22..5ce46a27 100644 --- a/website/docs/compiler/artifacts.md +++ b/website/docs/compiler/artifacts.md @@ -13,12 +13,13 @@ Artifacts allow any third-party SDKs to be developed, since these SDKs only need interface Artifact { contractName: string // Contract name constructorInputs: AbiInput[] // Arguments required to instantiate a contract - abi: AbiFunction[] // functions that can be called + abi: AbiFunction[] // Public functions that can be called from the SDK bytecode: string // Compiled Script without constructor parameters added (in ASM format) source: string // Source code of the CashScript contract compiler: { name: string // Compiler used to compile this contract version: string // Compiler version used to compile this contract + target?: string // Optional VM target required by this artifact (e.g. BCH_2026_05) options?: CompilerOptions // Compiler options used to compile this contract } debug?: { @@ -56,10 +57,20 @@ interface StackItem { interface RequireStatement { ip: number; // instruction pointer line: number; // line in the source code - message: string; // custom message for failing `require` statement + message?: string; // custom message for failing `require` statement } interface CompilerOptions { enforceFunctionParameterTypes?: boolean; // Enforce function parameter types (default: true) + internalFunctionPrefix?: string; // Optional custom prefix used to mark helper functions as internal + target?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; // Optional explicit VM target override recorded in the artifact metadata } ``` + +:::note +By default, functions whose names end with `_` are excluded from the artifact ABI. They can still be called by other CashScript functions, but they are not exposed as public SDK entrypoints. +::: + +:::note +Artifacts using BCH function opcodes record `compiler.target: 'BCH_2026_05'`. SDK integrations can use this to validate that their runtime/debug environment matches the contract's required VM semantics. +::: diff --git a/website/docs/compiler/bch-functions.md b/website/docs/compiler/bch-functions.md new file mode 100644 index 00000000..1212fc09 --- /dev/null +++ b/website/docs/compiler/bch-functions.md @@ -0,0 +1,176 @@ +--- +title: BCH Functions (beta) +--- + +CashScript supports user-defined function calls within a contract by compiling them to BCH function opcodes. + +:::caution +This feature is currently in beta. The helper-function naming convention and some compilation details may still change in a future release. + +CashScript function calls rely on BCH 2026 function semantics. Teams should only use this feature in environments that support `BCH_2026_05` behavior, and should configure testing/debugging providers accordingly. +::: + +At the Script level, this feature is implemented using: + +- `OP_DEFINE` to register a function body in the function table +- `OP_INVOKE` to execute a previously-defined function + +This page documents how CashScript maps contract functions to that execution model. + +## Overview + +CashScript contract functions now serve two roles: + +- public entrypoints, which appear in the artifact ABI and can be called from the SDK +- internal helpers, which can be called by other CashScript functions but are hidden from the ABI + +Public functions can also call other public functions. In that case, the called function remains in the ABI and is also compiled into the BCH function table if it is invoked internally. + +Example: + +```solidity +contract Example() { + function spend(int x) { + require(isTen_(x)); + } + + function isTen_(int value) { + require(value == 10); + } +} +``` + +In this example: + +- `spend()` is a public function +- `isTen_()` is an internal helper because its name ends with `_` + +## Internal Helpers + +By default, CashScript treats functions whose names end with `_` as internal helpers. + +Internal helpers: + +- can be called by other functions in the same contract +- are excluded from the artifact ABI +- are not exposed as unlock methods in the TypeScript SDK + +For example, `contract.unlock.isTen_` will be unavailable even though `spend()` can still invoke `isTen_()`. + +CashScript also rejects user-defined function names that collide with built-in global function names like `sha256` or `checkSig`. + +## Compilation Model + +When a contract contains user-defined function calls: + +1. CashScript computes the closure of all invoked functions. +2. Each reachable called function is compiled into its own bytecode fragment. +3. Those fragments are registered at the beginning of the script using `OP_DEFINE`. +4. When a function call appears in the contract body, CashScript emits `OP_INVOKE`. + +Only reachable called functions are defined. Unused helper functions are not added to the function table. + +Public entrypoint dispatch remains separate from BCH function invocation: + +- public functions are still selected using CashScript's normal ABI function selector logic +- internal user-defined calls use `OP_DEFINE` and `OP_INVOKE` + +## Return Value Semantics + +CashScript functions conceptually return a boolean success value. + +That means user-defined function calls are most naturally used in boolean positions, for example: + +```solidity +require(validateState_(expectedHash)); +``` + +Internally, CashScript compiles function bodies so that invoked functions leave a single boolean result on the stack. + +## Constructor And Parameter Access + +Invoked helper functions can safely access: + +- their own parameters +- global built-in functions and transaction globals + +They cannot reference constructor parameters. CashScript rejects this at compile time. + +## ABI Behavior + +Only public functions are written to the artifact ABI. + +So for: + +```solidity +contract Example() { + function spend(int x) { + require(isTen_(x)); + } + + function isTen_(int value) { + require(value == 10); + } +} +``` + +the artifact ABI only contains: + +```ts +[ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] } +] +``` + +## Current Limitations + +There is currently one important restriction: + +- internally-invoked functions cannot use `checkSig()`, `checkMultiSig()`, or `checkDataSig()` +- internally-invoked functions cannot reference constructor parameters +- recursive or mutually-recursive user-defined function calls are rejected by the compiler + +This restriction exists because signature coverage for invoked bytecode needs additional SDK/compiler metadata work. + +For now, keep signature validation and constructor-parameter-dependent logic in public entrypoint functions, and use invoked helpers for shared logic that depends only on helper arguments and other globals. + +Artifacts for helper-function contracts record `compiler.target: 'BCH_2026_05'`, and the SDK validates this against provider VM target metadata during local testing/debugging. + +For local testing with the SDK, configure your provider explicitly: + +```ts +import { Contract, MockNetworkProvider, VmTarget } from 'cashscript'; + +const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); +const contract = new Contract(artifact, [], { provider }); +``` + +:::note +Runtime execution of helper functions is covered by the SDK tests, but debug attribution for `console.log` and `require(...)` inside invoked helper frames is still less precise than for top-level public functions. Teams should currently treat nested-helper debugging output as best-effort. +::: + +## Compiler Options + +If needed, the internal helper naming convention can be customized with `internalFunctionPrefix`: + +```ts +interface CompilerOptions { + enforceFunctionParameterTypes?: boolean; + internalFunctionPrefix?: string; +} +``` + +If `internalFunctionPrefix` is set, it overrides the default helper detection rule and marks functions as internal based on that prefix. + +## When To Use This + +This feature is most useful when: + +- multiple public functions share the same validation logic +- you want cleaner contract structure without exposing every helper in the ABI +- you want the compiler to emit reusable BCH function bodies via `OP_DEFINE`/`OP_INVOKE` + +It is less useful when: + +- the helper performs signature checks +- the logic is only used once and inlining is simpler diff --git a/website/docs/compiler/compiler.md b/website/docs/compiler/compiler.md index cab71eae..ca62a091 100644 --- a/website/docs/compiler/compiler.md +++ b/website/docs/compiler/compiler.md @@ -5,6 +5,8 @@ title: Compiler The CashScript compiler is called `cashc` and is used to compile CashScript `.cash` contract files into `.json` (or `.ts`) artifact files. These artifact files can be used to instantiate a CashScript contract with the help of the CashScript SDK. For more information on this artifact format refer to [Artifacts](/docs/compiler/artifacts). +For details on how user-defined function calls compile to BCH `OP_DEFINE` and `OP_INVOKE`, see [BCH Functions (beta)](/docs/compiler/bch-functions). + :::info Because of the separation of the compiler and the SDK, CashScript contracts can be integrated into other programming languages in the future. ::: @@ -37,6 +39,7 @@ Options: -c, --opcount Display the number of opcodes in the compiled bytecode. -s, --size Display the size in bytes of the compiled bytecode. -S, --skip-enforce-function-parameter-types Do not enforce function parameter types. + -t, --target Record a required VM target in the artifact metadata. -f, --format Specify the format of the output. (choices: "json", "ts", default: "json") -?, --help Display help @@ -101,6 +104,8 @@ const P2PKH = compileString(source); ```ts interface CompilerOptions { enforceFunctionParameterTypes?: boolean; + internalFunctionPrefix?: string; + target?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; } ``` @@ -109,3 +114,7 @@ The `enforceFunctionParameterTypes` option is used to enforce function parameter If set to `false`, the compiler will not enforce function parameter types. This means that it is possible for `bytes20` values to have a different length at runtime than the expected 20 bytes. Or that `bool` values are not actually booleans, but integers. This option is useful if you are certain that passing in incorrect function parameter types will not cause runtime vulnerabilities, and you want to save on the extra opcodes that are added to the script to enforce the types. + +The `internalFunctionPrefix` option can be used to customize how the compiler detects internal helper functions. If set, functions using that prefix are excluded from the public ABI and are only callable from other CashScript functions. By default, CashScript uses the `foo_()` convention for internal helpers. + +The `target` option can be used to explicitly record a required VM target in the artifact metadata. In most cases this is inferred automatically when the compiled contract uses BCH function opcodes such as `OP_DEFINE` and `OP_INVOKE`. diff --git a/website/docs/compiler/grammar.md b/website/docs/compiler/grammar.md index 7a6c4018..e354e2c5 100644 --- a/website/docs/compiler/grammar.md +++ b/website/docs/compiler/grammar.md @@ -135,7 +135,7 @@ consoleParameterList ; functionCall - : Identifier expressionList // Only built-in functions are accepted + : Identifier expressionList // Built-in and user-defined functions are accepted ; expressionList @@ -294,3 +294,13 @@ LINE_COMMENT : '//' ~[\r\n]* -> channel(HIDDEN) ; ``` + +:::note +User-defined function calls compile to BCH function opcodes. Functions whose names end with `_` are treated as internal helpers and are excluded from the public ABI. +::: + +For the full compilation model, see [BCH Functions (beta)](/docs/compiler/bch-functions). + +:::caution +Internally-invoked functions currently cannot use `checkSig()`, `checkMultiSig()`, or `checkDataSig()`. +::: diff --git a/website/docs/language/contracts.md b/website/docs/language/contracts.md index 98d4fd86..c56163d5 100644 --- a/website/docs/language/contracts.md +++ b/website/docs/language/contracts.md @@ -59,9 +59,51 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) { } ``` +### Internal Helper Functions (beta) + +CashScript functions can also call other functions in the same contract. + +:::caution +User-defined function calls and internal helper functions are currently in beta. The naming convention and some implementation details may still change in a future release. + +This feature depends on BCH 2026 function semantics. Artifacts for contracts using helper functions record `compiler.target: 'BCH_2026_05'`. When testing or integrating these contracts in apps, make sure your environment is configured for `BCH_2026_05`. +::: + +If a function name ends with `_`, CashScript treats it as an internal helper: + +- it can still be called from other contract functions +- it is not included in the compiled ABI +- it is therefore not exposed as an SDK unlock method + +This is useful for shared validation logic that should not appear as a public entrypoint. + +```solidity +contract Vault(pubkey owner) { + function spend(sig ownerSig, int value) { + require(checkSig(ownerSig, owner)); + require(isPositiveEven_(value)); + } + + function isPositiveEven_(int value) { + require(value > 0); + require(value % 2 == 0); + } +} +``` + +For details on how these helper functions compile to BCH `OP_DEFINE` and `OP_INVOKE`, see [BCH Functions (beta)](/docs/compiler/bch-functions). + +:::caution +Internally-invoked functions currently cannot use `checkSig()`, `checkMultiSig()`, or `checkDataSig()`, and they also cannot reference constructor parameters. Keep signature validation and constructor-parameter-dependent logic in public entrypoint functions for now. +::: + +:::note +When testing helper-function contracts locally, configure your `MockNetworkProvider` for `BCH_2026_05` so local evaluation matches the artifact's required VM target. +::: + ### Function Arguments -Function arguments are provided by the user in the unlocking script of the transaction inputs when spending from the contract. Note that function arguments are variables and can be reassigned inside the function body. +Function arguments are provided by the user in the unlocking script of the transaction inputs when spending from the contract. Note that function arguments are variables and can be reassigned inside the function body. User-defined function calls return a boolean value, so they are usually used inside `require(...)` statements. Because the arguments are provided by the user when spending from the contract, these are 'untrusted arguments'. This means that these arguments can be crafted in a specific way by anyone to see if they can exploit the contract logic. @@ -246,12 +288,12 @@ contract P2PKH(bytes20 pkh) { ## Scope -CashScript uses nested scopes for parameters, variables and global functions. There cannot be two identical names within the same scope or within a nested scope. +CashScript uses nested scopes for parameters, variables, user-defined functions and global functions. There cannot be two identical names within the same scope or within a nested scope. There are the following scopes in the nesting order: - **Global scope** - contains global functions and global variables (e.g. `sha256`, `hash160`, `checkSig`, etc.) -- **Contract scope** - contains contract parameters +- **Contract scope** - contains contract parameters and user-defined function names - **Function scope** - contains function parameters and local variables - **Local scope** - contains local variables introduced by control flow blocks (e.g. `if`, `else`) @@ -259,7 +301,7 @@ There are the following scopes in the nesting order: ```solidity // Global scope (contains global functions and global variables like sha256, hash160, checkSig, etc.) -// Contract scope (contains contract parameters - sender, recipient, timeout) +// Contract scope (contains contract parameters and function names) contract TransferWithTimeout( pubkey sender, pubkey recipient, diff --git a/website/docs/language/functions.md b/website/docs/language/functions.md index f407017a..3d1f64ff 100644 --- a/website/docs/language/functions.md +++ b/website/docs/language/functions.md @@ -2,7 +2,7 @@ title: Global Functions --- -CashScript has several built-in functions for things like cryptographic and arithmetic applications, and it includes many common arithmetic and other operators that you would expect in a programming language. +This page documents CashScript's built-in functions. In addition to these built-ins, contract functions can now call other user-defined functions in the same contract. For public vs internal helper function behavior, helper-function restrictions, and how CashScript compiles these calls to BCH `OP_DEFINE` / `OP_INVOKE`, see the [contract structure documentation](/docs/language/contracts#internal-helper-functions) and [BCH Functions (beta)](/docs/compiler/bch-functions). ## Arithmetic functions ### abs() diff --git a/website/docs/sdk/network-provider.md b/website/docs/sdk/network-provider.md index 66878832..eb73a045 100644 --- a/website/docs/sdk/network-provider.md +++ b/website/docs/sdk/network-provider.md @@ -22,6 +22,20 @@ The network parameter can be one of 6 different options. const connectedNetwork = provider.network; ``` +### vmTarget +```ts +provider.vmTarget?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; +``` + +Providers can optionally expose the BCH VM target they are configured for. CashScript uses this metadata to validate artifact requirements during local testing, debugging, and transaction-template generation. + +This is especially relevant for contracts using BCH helper-function opcodes like `OP_DEFINE` and `OP_INVOKE`, which currently require `BCH_2026_05`. + +#### Example +```ts +const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); +``` + ### getUtxos() ```ts async provider.getUtxos(address: string): Promise; diff --git a/website/docs/sdk/other-network-providers.md b/website/docs/sdk/other-network-providers.md index f884b9b7..a2b7f8d2 100644 --- a/website/docs/sdk/other-network-providers.md +++ b/website/docs/sdk/other-network-providers.md @@ -39,7 +39,7 @@ interface MockNetworkProvider extends NetworkProvider { The `updateUtxoSet` option is used to determine whether the UTXO set should be updated after a transaction is sent. If `updateUtxoSet` is `true` (default), the UTXO set will be updated to reflect the new state of the mock network. If `updateUtxoSet` is `false`, the UTXO set will not be updated. -The `vmTarget` option defaults to the current VM of `BCH_2025_05`, but this can be changed to test your contract against different BCH virtual machine targets. +The `vmTarget` option defaults to the current VM target used by CashScript, currently `BCH_2026_05`, but this can be changed to test your contract against different BCH virtual machine targets. #### Example ```ts diff --git a/website/docs/sdk/testing-setup.md b/website/docs/sdk/testing-setup.md index 0b3e3c3c..e8aac0a8 100644 --- a/website/docs/sdk/testing-setup.md +++ b/website/docs/sdk/testing-setup.md @@ -17,9 +17,9 @@ To create a new virtual UTXO use `provider.addUtxo(address, utxo)`. You can use #### Example ```ts -import { MockNetworkProvider, randomUtxo, randomToken, randomNFT } from 'cashscript'; +import { MockNetworkProvider, randomUtxo, randomToken, randomNFT, VmTarget } from 'cashscript'; -const provider = new MockNetworkProvider(); +const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const contractUtxo = provider.addUtxo(contract.address, { vout: 0, txid: "ab...", satoshis: 10000n }); const aliceUtxo = provider.addUtxo(aliceAddress, randomUtxo({ @@ -28,6 +28,10 @@ const aliceUtxo = provider.addUtxo(aliceAddress, randomUtxo({ })); ``` +:::note +If your contract artifact records `compiler.target` metadata, such as helper-function contracts using BCH `OP_DEFINE` / `OP_INVOKE`, configure your testing provider for the same VM target. Otherwise the SDK will fail fast on target mismatches. +::: + :::note By default, the `MockNetworkProvider` evaluates transactions locally but does not process the transaction updates. This means no UTXOs are consumed and no new UTXOs are created when mocking a transaction `send` using the provider. This can be configured by setting the `updateUtxoSet` option to `true`. ::: From 2b8cb9f60fb13ee88adefd4eee17c629add25bb9 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 12 Mar 2026 18:34:26 -0400 Subject: [PATCH 2/9] Keep loop review changes off function branch --- packages/cashscript/src/libauth-template/utils.ts | 2 -- packages/cashscript/test/debugging.test.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/cashscript/src/libauth-template/utils.ts b/packages/cashscript/src/libauth-template/utils.ts index db3dd0c0..a7ee2966 100644 --- a/packages/cashscript/src/libauth-template/utils.ts +++ b/packages/cashscript/src/libauth-template/utils.ts @@ -90,8 +90,6 @@ export const formatBytecodeForDebugging = (artifact: Artifact): string => { const usesUnstableControlFlowFormatting = [ 'OP_DEFINE', 'OP_INVOKE', - 'OP_BEGIN', - 'OP_UNTIL', ].some((opcode) => artifact.bytecode.includes(opcode)); if (usesUnstableControlFlowFormatting) { diff --git a/packages/cashscript/test/debugging.test.ts b/packages/cashscript/test/debugging.test.ts index 8a666c87..2b27b7a0 100644 --- a/packages/cashscript/test/debugging.test.ts +++ b/packages/cashscript/test/debugging.test.ts @@ -774,7 +774,6 @@ describe('Debugging tests', () => { expect(transaction).toLog(expectedLog); }); }); - }); describe('VM Resources', () => { From 5c4dd156e9d9f54cb0cb6f695f1c0260f0463e16 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 12 Mar 2026 23:36:59 -0400 Subject: [PATCH 3/9] Harden BCH internal functions implementation --- docs/proposals/bch-functions-team-review.md | 357 ++++++++---------- examples/README.md | 2 +- examples/helper-functions.cash | 18 +- examples/testing-suite/README.md | 2 +- examples/testing-suite/contracts/example.cash | 4 - .../contracts/internal_functions.cash | 10 + .../test/internal_functions.test.ts | 39 ++ packages/cashc/src/Errors.ts | 10 + packages/cashc/src/artifact/Artifact.ts | 25 +- packages/cashc/src/ast/AST.ts | 3 +- packages/cashc/src/ast/AstBuilder.ts | 12 +- packages/cashc/src/ast/Globals.ts | 5 + packages/cashc/src/compiler.ts | 98 ++++- .../src/generation/GenerateTargetTraversal.ts | 31 +- packages/cashc/src/index.ts | 2 +- .../src/print/OutputSourceCodeTraversal.ts | 2 +- .../semantic/EnsureFinalRequireTraversal.ts | 7 +- packages/cashc/src/utils.ts | 24 +- packages/cashc/test/ast/AST.test.ts | 20 + .../direct_recursive_function.cash | 8 +- .../mutual_recursive_functions.cash | 12 +- .../internal_only_functions.cash | 2 +- ...oked_function_uses_contract_parameter.cash | 6 +- ...oked_function_uses_contract_parameter.cash | 10 +- .../signature_check_in_invoked_function.cash | 4 +- ...e_signature_check_in_invoked_function.cash | 10 +- packages/cashc/test/compiler/compiler.test.ts | 137 ++++++- .../cashc/test/generation/generation.test.ts | 202 ++++++++-- .../valid-contract-files/2_of_3_multisig.cash | 2 +- .../valid-contract-files/announcement.cash | 2 +- .../test/valid-contract-files/bigint.cash | 2 +- .../test/valid-contract-files/bitshift.cash | 2 +- .../test/valid-contract-files/bitwise.cash | 2 +- .../valid-contract-files/bounded_bytes.cash | 2 +- .../bytes1_equals_byte.cash | 2 +- .../cast_hash_checksig.cash | 2 +- .../test/valid-contract-files/comments.cash | 2 +- .../valid-contract-files/complex_loop.cash | 2 +- .../valid-contract-files/correct_pragma.cash | 4 +- .../test/valid-contract-files/covenant.cash | 2 +- .../covenant_all_fields.cash | 2 +- .../valid-contract-files/date_literal.cash | 2 +- .../valid-contract-files/debug_messages.cash | 2 +- .../valid-contract-files/deep_replace.cash | 2 +- .../deeply_nested-logs.cash | 4 +- .../valid-contract-files/deeply_nested.cash | 4 +- .../valid-contract-files/do_while_loop.cash | 2 +- .../do_while_loop_no_introspection.cash | 2 +- .../do_while_loop_no_tokens_in_inputs.cash | 2 +- .../do_while_loop_require_inside_loop.cash | 2 +- .../do_while_no_require.cash | 2 +- .../valid-contract-files/double_split.cash | 2 +- .../test/valid-contract-files/everything.cash | 2 +- .../valid-contract-files/for_loop_basic.cash | 2 +- .../for_loop_stack_items.cash | 2 +- .../for_while_nested.cash | 2 +- .../force_cast_smaller_bytes.cash | 2 +- .../function_call_with_function_name.cash | 4 +- .../test/valid-contract-files/hodl_vault.cash | 2 +- .../valid-contract-files/if_statement.cash | 2 +- .../if_statement_number_units-logs.cash | 2 +- .../if_statement_number_units.cash | 2 +- .../valid-contract-files/int_to_byte.cash | 2 +- .../integer_formatting.cash | 2 +- .../internal_helper_functions.cash | 6 +- .../test/valid-contract-files/invert.cash | 2 +- .../log_intermediate_results.cash | 2 +- .../test/valid-contract-files/mecenas.cash | 4 +- .../valid-contract-files/multifunction.cash | 4 +- .../multifunction_if_statements.cash | 4 +- .../multiline_array_multisig.cash | 2 +- .../multiline_statements.cash | 2 +- .../valid-contract-files/multiplication.cash | 2 +- .../test/valid-contract-files/num2bin.cash | 2 +- .../num2bin_variable.cash | 2 +- .../valid-contract-files/p2palindrome.cash | 2 +- .../test/valid-contract-files/p2pkh-logs.cash | 2 +- .../test/valid-contract-files/p2pkh.cash | 2 +- .../p2pkh_with_assignment.cash | 2 +- .../valid-contract-files/p2pkh_with_cast.cash | 2 +- .../valid-contract-files/reassignment.cash | 2 +- .../valid-contract-files/simple_cast.cash | 2 +- .../simple_checkdatasig.cash | 2 +- .../valid-contract-files/simple_constant.cash | 2 +- .../valid-contract-files/simple_covenant.cash | 2 +- .../simple_functions.cash | 4 +- .../simple_if_statement.cash | 2 +- .../valid-contract-files/simple_multisig.cash | 2 +- .../valid-contract-files/simple_splice.cash | 2 +- .../simple_variables.cash | 2 +- .../simulating_state.cash | 4 +- .../test/valid-contract-files/slice.cash | 2 +- .../valid-contract-files/slice_optimised.cash | 2 +- .../slice_variable_parameter.cash | 2 +- .../split_or_slice_signature.cash | 2 +- .../test/valid-contract-files/split_size.cash | 2 +- .../valid-contract-files/split_typed.cash | 2 +- .../string_concatenation.cash | 2 +- .../string_with_escaped_characters.cash | 2 +- .../sum_input_amount.cash | 2 +- .../token_category_comparison.cash | 2 +- .../valid-contract-files/trailing_comma.cash | 2 +- .../valid-contract-files/tuple_unpacking.cash | 2 +- .../tuple_unpacking_parameter.cash | 2 +- .../tuple_unpacking_single_side_type.cash | 2 +- .../type_enforcement.cash | 2 +- .../unsafe_bool_cast.cash | 2 +- .../valid-contract-files/unsafe_int_cast.cash | 2 +- .../test/valid-contract-files/while_loop.cash | 2 +- .../while_loop_basic.cash | 2 +- .../while_loop_nested.cash | 2 +- packages/cashscript/src/Errors.ts | 19 +- packages/cashscript/src/TransactionBuilder.ts | 2 +- packages/cashscript/src/debugging.ts | 113 +++++- packages/cashscript/test/Contract.test.ts | 54 +-- packages/cashscript/test/debugging.test.ts | 24 ++ .../fixture/debugging/debugging_contracts.ts | 106 +++--- .../multi_contract_debugging_contracts.ts | 8 +- packages/utils/src/artifact.ts | 7 +- packages/utils/src/script.ts | 7 +- website/docs/compiler/artifacts.md | 7 +- website/docs/compiler/bch-functions.md | 71 ++-- website/docs/compiler/compiler.md | 5 +- website/docs/compiler/grammar.md | 2 +- website/docs/language/contracts.md | 24 +- website/docs/language/functions.md | 2 +- website/docs/sdk/network-provider.md | 2 +- website/docs/sdk/testing-setup.md | 2 +- 128 files changed, 1155 insertions(+), 550 deletions(-) create mode 100644 examples/testing-suite/contracts/internal_functions.cash create mode 100644 examples/testing-suite/test/internal_functions.test.ts diff --git a/docs/proposals/bch-functions-team-review.md b/docs/proposals/bch-functions-team-review.md index ca66de10..61bab144 100644 --- a/docs/proposals/bch-functions-team-review.md +++ b/docs/proposals/bch-functions-team-review.md @@ -1,91 +1,104 @@ # BCH Functions Proposal Review -This document summarizes the working-tree patch on top of the current `next` branch. It is intended as a developer-facing review aid for the CashScript team. +This document summarizes the current working-tree patch on top of `next`. It is intended as a maintainer-facing review aid for the CashScript team. -The goal of this patch is to add user-defined function calls to CashScript by compiling them to BCH function opcodes while keeping the supported subset safe enough for real integrations to experiment with. +The goal of this patch is to add safe user-defined internal functions to CashScript using BCH `OP_DEFINE` / `OP_INVOKE`, while keeping the language model understandable for developers and conservative enough for real integrations. ## Scope -This proposal adds: +This patch adds: -- user-defined function calls inside a contract -- compilation of those calls to `OP_DEFINE` and `OP_INVOKE` -- an internal helper convention based on function names ending in `_` -- compiler restrictions for cases the current lowering cannot yet support safely -- artifact/runtime metadata so SDK integrations can validate VM-target expectations -- expanded compiler, codegen, SDK, runtime, and docs coverage +- explicit function visibility syntax: `public` and `internal` +- user-defined contract-function calls compiled to BCH function opcodes +- ABI filtering so only `public` functions are externally callable +- reachability-based function-table emission from public entrypoints +- compiler restrictions for patterns the current model does not safely support +- artifact/debug/runtime metadata updates needed for BCH 2026 function semantics +- expanded compiler, SDK, debugging, testing-suite, and documentation coverage -This proposal does not attempt to solve: +This patch intentionally does not add: -- signature-check support inside invoked helper functions -- constructor-parameter capture inside invoked helper functions -- recursive or mutually-recursive helper calls -- new syntax such as explicit `internal`/`public` modifiers +- signature checks inside internally invoked functions +- constructor-parameter capture inside internally invoked functions +- recursion or mutual recursion in invoked function graphs +- mandatory explicit visibility on all functions yet +- arbitrary typed internal return values beyond the current boolean-style model -## Why This Approach +## High-Level Model -The BCH functions CHIP makes internal code reuse possible at the script level with `OP_DEFINE` and `OP_INVOKE`. CashScript can benefit from that, but the compiler and SDK currently assume a flatter execution model: +CashScript functions now have two roles: -- a contract exposes public ABI functions -- signing/debugging generally reason about a single active bytecode body -- function visibility is not part of the language syntax +- `public`: appears in the artifact ABI and is exposed by the SDK +- `internal`: callable only from other contract functions and lowered to BCH function opcodes -Given those constraints, the patch takes a conservative path: +If visibility is omitted, the compiler currently defaults to `public` and emits a warning. That keeps existing contracts source-compatible while nudging authors toward explicit visibility. -1. Reuse the existing CashScript function model rather than inventing new syntax. -2. Treat helper functions as a convention, not a grammar change. -3. Only compile the subset we can model safely today. -4. Reject unsupported patterns explicitly at compile time. -5. Carry enough artifact/provider metadata that SDK users can detect VM-target mismatches early. +Public functions may also call other public functions. In that case, the called function remains in the ABI and is also emitted into the BCH function table if it is invoked internally. -This keeps the patch small enough to review while avoiding silent miscompilation. +## Why This Approach -## Language And Compiler Model +The BCH functions CHIP makes internal code reuse possible at the script level, but CashScript and the SDK previously assumed: -### User-defined calls +- ABI functions are the externally visible boundary +- most execution/debug reasoning happens within a single active bytecode body +- visibility is not yet enforced as a mandatory source-level concept -CashScript `functionCall` grammar now allows calls to user-defined contract functions as well as built-ins. +So the patch takes a conservative path: -The semantic pass now: +1. Make visibility explicit instead of relying on naming conventions. +2. Keep omitted visibility backward-compatible for now. +3. Compile only the subset we can model safely today. +4. Reject unsupported patterns at compile time rather than risk silent miscompilation. +5. Carry enough artifact/runtime/debug metadata to make BCH-function contracts inspectable and testable. -- registers contract functions in a function-definition map -- resolves user-defined calls distinctly from built-in function symbols -- tracks call edges on each `FunctionDefinitionNode` +## Language And Compiler Model + +### Visibility -That call graph is then used by code generation and safety analysis. +Examples: -### Internal helper convention +```solidity +contract Example() { + function spend(int x) public { + require(validate(x)); + } -Functions whose names end with `_` are treated as internal helpers. + function validate(int value) internal { + require(value == 7); + } +} +``` Effects: -- they can still be called by other contract functions -- they are excluded from the artifact ABI -- they are not exposed as SDK unlock methods -- a contract with only helper functions is rejected as empty from the ABI perspective -- user-defined functions still cannot redefine built-in global function names +- `public` functions remain ABI entrypoints +- `internal` functions are hidden from the ABI +- internal/public classification no longer depends on prefixes or suffixes -The main reason for choosing `foo_()` is that it works with the current grammar, keeps the change small, and avoids introducing visibility syntax without parser/tooling changes. +### Reachability -### Code generation +The compiler computes the invoked-function closure starting from public entrypoints only. -The compiler computes the transitive closure of invoked functions and emits only that reachable set into the BCH function table. +That means: -Lowering works like this: +- reachable internal/public invocations are compiled into the BCH function table +- dead internal-only call chains are ignored +- helper reachability is anchored to real external entrypoints, not arbitrary dead call edges + +### Code generation -1. Public entrypoints remain selected through the existing CashScript ABI dispatch path. -2. Reachable invoked functions are compiled into bytecode fragments. -3. Those fragments are registered with `OP_DEFINE`. -4. Calls to user-defined functions emit the function index followed by `OP_INVOKE`. +Lowering works like this: -Important consequence: unused helper functions do not generate `OP_DEFINE` entries. +1. Public ABI dispatch stays on the existing CashScript path. +2. Reachable invoked functions are compiled into separate bytecode fragments. +3. Those fragments are registered using `OP_DEFINE`. +4. User-defined calls emit the function index plus `OP_INVOKE`. ## Safety Boundaries -The proposal intentionally rejects cases that are not sound under the current frame/signing model. +The implementation intentionally rejects patterns that are not sound under the current execution/signing/debug model. -### Disallowed in invoked helper functions +### Disallowed in internally invoked functions - `checkSig()` - `checkMultiSig()` @@ -94,201 +107,149 @@ The proposal intentionally rejects cases that are not sound under the current fr - direct recursion - mutual recursion -### Why these are rejected - -Signature operations are the most important remaining semantic gap. Invoked bytecode changes the covered-bytecode story, and the current SDK/compiler pipeline does not yet model per-invocation signing semantics precisely enough to safely support those operations. - -Constructor-parameter capture is also unsafe in the current lowering. Invoked bytecode runs with a stack frame that is not equivalent to a lexical closure, so referencing constructor parameters from helper bodies would risk reading the wrong stack slot. +### Why those restrictions exist -Recursion is rejected because the current implementation uses a predeclared function table and does not try to model recursive helper execution or the associated safety/debug complexity. +These are conservative safety/tooling restrictions, not BCH opcode impossibilities. -The design principle here is simple: if the compiler cannot prove the current lowering is safe, it should reject the pattern. +- Signature operations are blocked because nested invocation frames complicate signing/debug/template attribution, and the current SDK/compiler pipeline should not claim stronger guarantees than it can model. +- Constructor-parameter capture is blocked because it creates closure-like hidden dependencies on outer contract state, which is easy to misunderstand and harder to audit safely. +- Recursion is blocked because the current function-table model is intentionally acyclic and bounded. -## Artifact And SDK Integration +The design principle is straightforward: if the compiler cannot prove the lowering is safe and predictable, it rejects the pattern. -### Artifact changes +## Artifact, Target, And SDK Integration -Artifacts now record an optional `compiler.target` field. +### Artifact behavior -For contracts using BCH function opcodes, the compiler records: +Artifacts now: -- `compiler.target: 'BCH_2026_05'` +- include only `public` functions in the ABI +- record BCH-function contracts with `compiler.target` at least `BCH_2026_05` +- reject explicit lower targets when function opcodes are required +- still allow explicit `BCH_SPEC` -The `CompilerOptions.target` option and CLI `--target` flag also allow explicit overrides when needed. +This prevents artifacts from claiming an older VM target while containing BCH 2026 function opcodes. -Compiler options stored in artifacts are normalized so `target` is kept as artifact metadata rather than mixed into the generic `compiler.options` bag. +### SDK/runtime behavior -### Provider/runtime validation +The SDK validates target compatibility against provider VM-target metadata and continues to expose only public ABI methods. -The SDK now uses provider VM-target metadata where available. +This means: -Changes include: +- internal functions are not available as unlock methods +- BCH-function artifacts fail fast if paired with incompatible provider expectations +- mixed-target local testing is harder to misconfigure silently -- `NetworkProvider` exposes optional `vmTarget` -- `ElectrumNetworkProvider`, `FullStackNetworkProvider`, and `BitcoinRpcNetworkProvider` carry `vmTarget` -- `Contract` fails fast when an artifact target and provider target conflict -- libauth-template generation rejects transactions that mix contracts requiring different VM targets +## Debugging And Tooling -This is primarily a safety and developer-experience improvement. It reduces the chance that teams compile a BCH-functions contract and then accidentally test it under a provider/debug configuration targeting older rules. +Nested invoked-function debugging is materially better than the initial branch state. -## Debugging And Tooling Adjustments +The current patch adds: -Two debug-related adjustments were needed to keep the existing experience coherent: +- frame-aware debug metadata on logs, stack items, and require statements +- frame-aware matching in SDK debug decoding +- better root-locking-script slicing when nested invoked frames are present +- fallback attribution for nested internal-function require failures +- explicit source locations for require statements -- locking-script debug slicing now handles nested invoked frames more carefully rather than assuming the last `ip === 0` is the start of the root locking script -- pretty formatting for debug bytecode falls back to execution-order ASM when the contract uses control-flow/function opcodes whose reformatting can destabilize source mapping +Practically, this means `console.log` and failing `require(...)` inside internally invoked functions can now be surfaced with the internal frame’s source line and statement, rather than collapsing everything onto the public wrapper. -This does not fully redesign debug metadata for nested frames, but it avoids known misleading output in the current tooling. +## Return-Value Semantics -One remaining limitation is that debug attribution for `console.log` and `require(...)` inside invoked helper frames is still less precise than for top-level public functions. Runtime behavior is covered by tests, but nested-frame debugging should still be treated as best-effort. +Internal functions are currently modeled as boolean-style reusable subroutines. -## Optimizations Included In The Patch +In practice: -The implementation also includes a few targeted codegen cleanups: +- user-defined calls are used naturally in boolean positions like `require(validate(x));` +- invoked functions currently compile to leave a single success value on the stack -- only reachable helper functions are emitted into the BCH function table -- unused helpers produce no `OP_DEFINE`/`OP_INVOKE` overhead -- scoped variable cleanup emits `OP_2DROP` where possible -- shallow replacement patterns use `OP_SWAP`/`OP_ROT` instead of always using integer-push plus `OP_ROLL` -- `require` debug metadata no longer writes `message: undefined` +So this is “general internal functions” in visibility/reuse terms, but not yet a full arbitrary typed-return function system. -These changes are small but worthwhile because helper-function support should not add avoidable bytecode bloat or unnecessary debug noise. +## Tests And Validation -## Files Touched +Coverage was expanded in several layers. -The patch spans four main areas. +### Compiler / parser / codegen -### Compiler and semantic analysis +- explicit `public` / `internal` parsing +- comments/newlines around visibility +- no more underscore-based visibility inference +- omitted-visibility warning behavior +- ABI filtering for internal functions +- reachability-based `OP_DEFINE` emission +- rejection of pre-2026 explicit targets for BCH-function contracts +- debug metadata coverage for frame bytecode and require locations -- `packages/cashc/src/grammar/CashScript.g4` -- `packages/cashc/src/semantic/SymbolTableTraversal.ts` -- `packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts` -- `packages/cashc/src/semantic/EnsureInvokedFunctionsSafeTraversal.ts` -- `packages/cashc/src/generation/GenerateTargetTraversal.ts` -- `packages/cashc/src/artifact/Artifact.ts` -- `packages/cashc/src/Errors.ts` -- `packages/cashc/src/utils.ts` -- `packages/cashc/src/cashc-cli.ts` +### SDK / runtime / debugging -### SDK and runtime integration +- internal-function contracts execute under `BCH_2026_05` +- nested internal call chains work across public entrypoints +- zero-argument invoked functions work +- public-to-public internal invocation preserves ABI visibility +- nested internal logs are attributed to the internal source line +- nested internal require failures are attributed to the internal failing statement -- `packages/cashscript/src/Contract.ts` -- `packages/cashscript/src/libauth-template/LibauthTemplate.ts` -- `packages/cashscript/src/libauth-template/utils.ts` -- `packages/cashscript/src/debugging.ts` -- `packages/cashscript/src/network/NetworkProvider.ts` -- `packages/cashscript/src/network/ElectrumNetworkProvider.ts` -- `packages/cashscript/src/network/FullStackNetworkProvider.ts` -- `packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts` +### Example project coverage -### Shared artifact types - -- `packages/utils/src/artifact.ts` - -### Tests and docs - -- `packages/cashc/test/...` -- `packages/cashscript/test/Contract.test.ts` -- `website/docs/compiler/bch-functions.md` -- `website/docs/compiler/compiler.md` -- `website/docs/compiler/artifacts.md` -- `website/docs/compiler/grammar.md` -- `website/docs/language/contracts.md` -- `website/docs/language/functions.md` - -## Test Coverage Added - -The patch adds or updates tests in several layers. - -### Compiler/codegen tests - -- user-defined function calls are accepted instead of rejected as undefined references -- helper functions are hidden from the ABI -- reachable helper closure emits only reachable `OP_DEFINE`s -- unused helpers emit no BCH function opcodes -- helper-only contracts are rejected -- transitive signature-op usage in helpers is rejected -- transitive constructor-parameter usage in helpers is rejected -- direct recursion is rejected -- mutual recursion is rejected - -### SDK/runtime tests - -- helper-function contracts instantiate cleanly under `BCH_2026_05` -- helper functions do not appear on generated unlocker surfaces -- nested helper chains execute correctly -- shared helper chains across multiple public entrypoints work -- zero-argument helpers execute correctly -- public-to-public internal invocation works while preserving ABI visibility -- failing helper validation fails transaction execution -- older artifacts lacking `compiler.target` remain accepted -- artifact/provider VM-target mismatches fail fast -- mixed-target transaction templates fail fast -- Electrum-provider VM-target mismatch is also validated +The testing-suite example now includes a dedicated internal-functions contract and test. Existing generic example contracts were intentionally left in their original form so this patch stays scoped to the new functions feature rather than rewriting the broader examples set before acceptance. ### Validation run status -The patch has been validated with: +The working tree has been validated with: -- `npm run build` -- `cd packages/utils && yarn test` -- `cd packages/cashc && yarn test` -- `cd packages/cashscript && yarn test` +- `cd packages/utils && yarn run build` +- `cd packages/cashc && yarn run build` +- `cd packages/cashscript && yarn run build` +- `yarn vitest run packages/cashc/test/compiler/compiler.test.ts` +- `cd packages/cashc && yarn vitest run test/generation/generation.test.ts` +- `yarn vitest run packages/cashc/test/ast/AST.test.ts` +- `yarn vitest run packages/cashscript/test/debugging.test.ts` +- `yarn vitest run packages/cashscript/test/Contract.test.ts` - `cd examples/testing-suite && yarn test` -Those package-level runs are the most reliable signal in this workspace because root-level parallel test execution can race against sibling-package build outputs. - -## Documentation Updates - -The external docs now cover: +## Documentation And DX Changes -- the BCH functions compilation model -- the `foo_()` helper convention -- the beta status of the feature -- the current safety limitations -- artifact `compiler.target` metadata -- compiler `--target` / `CompilerOptions.target` -- provider `vmTarget` behavior for local validation -- the BCH 2026 requirement for testing and integration environments +Docs were updated to reflect the current branch rather than the earlier helper-naming model. -That should make it much easier for downstream teams to understand what is supported today and what is intentionally out of scope. +The user-facing story is now: -## Tradeoffs And Known Limitations +- explicit `public` / `internal` +- omitted visibility remains backward-compatible but warns +- BCH 2026 is required for function-opcode contracts +- internal-function restrictions are documented explicitly -The main tradeoff in this proposal is that it prefers a safe restricted subset over a more powerful but underspecified feature. +Functions-specific examples and happy-path fixtures were updated to use explicit visibility where helpful, while older generic example contracts were intentionally left alone to keep branch scope tight. -Benefits: +## Remaining Tradeoffs And Caveats -- small and reviewable patch -- no silent unsafe behavior in the unsupported cases we identified -- clear ABI boundary between public functions and helpers -- early VM-target validation in local integrations +This patch looks production-ready enough for maintainer review, but there are still some intentional transitional choices: -Limitations: +- omitted visibility still defaults to `public` +- omitted-visibility warnings are transitional rather than the final UX +- some older compiler error fixtures still trigger warnings because they intentionally preserve legacy-style source +- visibility parsing still uses compiler preprocessing rather than being grammar-native -- no signature ops in invoked helpers -- no constructor-parameter capture in invoked helpers -- no recursion -- visibility remains convention-based instead of syntax-based -- nested-frame debug metadata is improved but not fully redesigned, especially for helper `console.log` / `require(...)` attribution +These are mostly rollout and ergonomics concerns, not correctness/safety blockers. -## Suggested Review Questions +## Review Focus -The most useful questions for the CashScript team to evaluate are: +The highest-value maintainer questions are: -1. Is `foo_()` an acceptable temporary/internal-helper convention until explicit visibility syntax exists? -2. Is the current restricted subset the right release boundary for an experimental BCH-functions integration? -3. Is `compiler.target` the right artifact-level representation for VM-target requirements? -4. Should explicit target selection remain optional/inferred, or should the compiler require it whenever 2026-only opcodes are emitted? -5. Does the team want to preserve the helper convention long-term, or eventually replace it with grammar-level visibility modifiers? +1. Is explicit visibility the right language direction versus naming conventions? +2. Are the current internal-function restrictions conservative enough for merge? +3. Is the BCH 2026 target enforcement strict enough and correctly placed? +4. Is the boolean-style internal return model acceptable for this iteration? +5. Is the preprocessing-based visibility parser acceptable for now, or should grammar-native syntax be required before merge? ## Bottom Line -This patch is intentionally not a full language-level function system. It is a careful integration of BCH `OP_DEFINE`/`OP_INVOKE` into CashScript that: +The patch is now much stronger than the earlier helper-function branch state: -- enables internal code reuse today -- avoids the unsafe cases we identified -- gives SDK users better target-awareness and earlier failures -- comes with enough tests and docs to make team review practical +- visibility is explicit +- ABI boundaries are coherent +- reachability is correct +- target metadata cannot lie +- nested debug attribution is materially improved +- examples/tests/docs are aligned with the intended design -If the team agrees with the restricted safety model, this should be a solid base for discussing whether BCH functions belong in CashScript and what the next iteration should solve. +The remaining concerns are mostly about rollout polish and syntax implementation strategy, not about core semantic safety. diff --git a/examples/README.md b/examples/README.md index 0084592a..d4e5f9f0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,7 +3,7 @@ This folder contains a number of example CashScript contracts to show off its fu The "Hello World" of cash contracts is defining the P2PKH pattern inside a cash contract, which can be found under [`p2pkh.cash`](/examples/p2pkh.cash). Its usage can be found under [`p2pkh.ts`](/examples/p2pkh.ts). -For BCH function calls and internal helpers compiled via `OP_DEFINE` / `OP_INVOKE`, see [`helper-functions.cash`](/examples/helper-functions.cash) and [`helper-functions.ts`](/examples/helper-functions.ts). This example uses the supported helper-function subset and configures the mock provider for `BCH_2026_05`. +For BCH internal functions compiled via `OP_DEFINE` / `OP_INVOKE`, see [`helper-functions.cash`](/examples/helper-functions.cash) and [`helper-functions.ts`](/examples/helper-functions.ts). This example is specific to the BCH functions proposal and configures the mock provider for `BCH_2026_05`. ## Running the examples To run the examples, clone this repository and navigate to the `examples/` directory. Since the examples depend on the SDK, be sure to run `yarn` inside the `examples/` directory, which installs all required packages. diff --git a/examples/helper-functions.cash b/examples/helper-functions.cash index 6bd0cb1c..968086c5 100644 --- a/examples/helper-functions.cash +++ b/examples/helper-functions.cash @@ -1,24 +1,24 @@ pragma cashscript ^0.13.0; contract HelperFunctions() { - function spend(int value) { - require(isPositiveEven_(value)); + function spend(int value) public { + require(isPositiveEven(value)); } - function spendPlusOne(int value) { - require(isPositiveEven_(value + 1)); + function spendPlusOne(int value) public { + require(isPositiveEven(value + 1)); } - function isPositiveEven_(int value) { - require(isPositive_(value)); - require(isEven_(value)); + function isPositiveEven(int value) internal { + require(isPositive(value)); + require(isEven(value)); } - function isPositive_(int value) { + function isPositive(int value) internal { require(value > 0); } - function isEven_(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } diff --git a/examples/testing-suite/README.md b/examples/testing-suite/README.md index e0b479ab..771a57a9 100644 --- a/examples/testing-suite/README.md +++ b/examples/testing-suite/README.md @@ -4,7 +4,7 @@ This is an example project to demonstrate how to write and compile CashScript co ## Writing a CashScript contract -We have included two simple example contracts in the `contracts/` directory that can be used as a starting point to write your own contracts. The contracts demonstrate logs, require statements, transaction signatures, and BCH helper-function calls compiled via `OP_DEFINE` / `OP_INVOKE`. +We have included example contracts in the `contracts/` directory that can be used as a starting point to write your own contracts. The `example.cash` and `transfer_with_timeout.cash` samples remain generic CashScript examples, while `internal_functions.cash` demonstrates BCH internal function calls compiled via `OP_DEFINE` / `OP_INVOKE`. ## Compiling the contracts diff --git a/examples/testing-suite/contracts/example.cash b/examples/testing-suite/contracts/example.cash index 3c27ec46..3df2d670 100644 --- a/examples/testing-suite/contracts/example.cash +++ b/examples/testing-suite/contracts/example.cash @@ -1,9 +1,5 @@ contract Example() { function test(int value) { - require(validateValue_(value)); - } - - function validateValue_(int value) { console.log(value, "test"); require(value == 1, "Wrong value passed"); } diff --git a/examples/testing-suite/contracts/internal_functions.cash b/examples/testing-suite/contracts/internal_functions.cash new file mode 100644 index 00000000..843397c7 --- /dev/null +++ b/examples/testing-suite/contracts/internal_functions.cash @@ -0,0 +1,10 @@ +contract InternalFunctions() { + function test(int value) public { + require(validateValue(value)); + } + + function validateValue(int value) internal { + console.log(value, "internal"); + require(value == 1, "Wrong value passed"); + } +} diff --git a/examples/testing-suite/test/internal_functions.test.ts b/examples/testing-suite/test/internal_functions.test.ts new file mode 100644 index 00000000..d249105f --- /dev/null +++ b/examples/testing-suite/test/internal_functions.test.ts @@ -0,0 +1,39 @@ +import { compileFile } from 'cashc'; +import { Contract, MockNetworkProvider, TransactionBuilder, randomUtxo, VmTarget } from 'cashscript'; +import 'cashscript/vitest'; +import { URL } from 'url'; + +const artifact = compileFile(new URL('../contracts/internal_functions.cash', import.meta.url)); + +describe('test internal function contract', () => { + const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); + const contract = new Contract(artifact, [], { provider }); + + it('should succeed when correct parameter is passed', () => { + const contractUtxo = randomUtxo(); + provider.addUtxo(contract.address, contractUtxo); + + const transaction = new TransactionBuilder({ provider }) + .addInput(contractUtxo, contract.unlock.test(1n)) + .addOutput({ to: contract.address, amount: 10000n }); + + expect(transaction).toLog(/1 internal/); + expect(transaction).not.toFailRequire(); + }); + + it('should fail require statement and log from the internal function when incorrect parameter is passed', () => { + const contractUtxo = randomUtxo(); + provider.addUtxo(contract.address, contractUtxo); + + const transaction = new TransactionBuilder({ provider }) + .addInput(contractUtxo, contract.unlock.test(0n)) + .addOutput({ to: contract.address, amount: 10000n }); + + expect(transaction).toLog(/0 internal/); + expect(transaction).toFailRequireWith(/Wrong value passed/); + }); + + it('should expose only the public test function in the ABI', () => { + expect(artifact.abi.map((func) => func.name)).toEqual(['test']); + }); +}); diff --git a/packages/cashc/src/Errors.ts b/packages/cashc/src/Errors.ts index c1468b2d..6a809aa1 100644 --- a/packages/cashc/src/Errors.ts +++ b/packages/cashc/src/Errors.ts @@ -268,6 +268,16 @@ export class CircularFunctionDependencyError extends CashScriptError { } } +export class UnsupportedTargetError extends Error { + constructor( + public actual: string, + public required: string, + ) { + super(`Compiler target ${actual} is incompatible with this contract; at least ${required} is required`); + this.name = this.constructor.name; + } +} + export class IndexOutOfBoundsError extends CashScriptError { constructor( node: TupleIndexOpNode | BinaryOpNode | SliceNode, diff --git a/packages/cashc/src/artifact/Artifact.ts b/packages/cashc/src/artifact/Artifact.ts index 3337f9ff..f67bac12 100644 --- a/packages/cashc/src/artifact/Artifact.ts +++ b/packages/cashc/src/artifact/Artifact.ts @@ -3,6 +3,7 @@ import { } from '@cashscript/utils'; import { version } from '../index.js'; import { Ast } from '../ast/AST.js'; +import { UnsupportedTargetError } from '../Errors.js'; import { getPublicFunctions } from '../utils.js'; function normaliseCompilerOptions(compilerOptions: CompilerOptions): CompilerOptions { @@ -24,6 +25,21 @@ function inferCompilerTarget(bytecode: string): VmTarget | undefined { return requiresBch2026 ? 'BCH_2026_05' : undefined; } +function resolveCompilerTarget(bytecode: string, compilerOptions: CompilerOptions): VmTarget | undefined { + const inferredTarget = inferCompilerTarget(bytecode); + + if ( + inferredTarget === 'BCH_2026_05' + && compilerOptions.target !== undefined + && compilerOptions.target !== 'BCH_2026_05' + && compilerOptions.target !== 'BCH_SPEC' + ) { + throw new UnsupportedTargetError(compilerOptions.target, inferredTarget); + } + + return compilerOptions.target ?? inferredTarget; +} + export function generateArtifact( ast: Ast, script: Script, @@ -36,7 +52,7 @@ export function generateArtifact( const constructorInputs = contract.parameters .map((parameter) => ({ name: parameter.name, type: parameter.type.toString() })); - const abi = getPublicFunctions(contract.functions, compilerOptions).map((func) => ({ + const abi = getPublicFunctions(contract.functions).map((func) => ({ name: func.name, inputs: func.parameters.map((parameter) => ({ name: parameter.name, @@ -45,6 +61,7 @@ export function generateArtifact( })); const bytecode = scriptToAsm(script); + const compilerTarget = resolveCompilerTarget(bytecode, compilerOptions); return { contractName: contract.name, @@ -56,11 +73,7 @@ export function generateArtifact( compiler: { name: 'cashc', version, - ...(compilerOptions.target !== undefined - ? { target: compilerOptions.target } - : inferCompilerTarget(bytecode) !== undefined - ? { target: inferCompilerTarget(bytecode) } - : {}), + ...(compilerTarget !== undefined ? { target: compilerTarget } : {}), options: normaliseCompilerOptions(compilerOptions), }, updatedAt: new Date().toISOString(), diff --git a/packages/cashc/src/ast/AST.ts b/packages/cashc/src/ast/AST.ts index 8b166a97..3eb7be3a 100644 --- a/packages/cashc/src/ast/AST.ts +++ b/packages/cashc/src/ast/AST.ts @@ -1,5 +1,5 @@ import { Type, PrimitiveType, BytesType } from '@cashscript/utils'; -import { TimeOp } from './Globals.js'; +import { FunctionVisibility, TimeOp } from './Globals.js'; import AstVisitor from './AstVisitor.js'; import { BinaryOperator, NullaryOperator, UnaryOperator } from './Operator.js'; import { Location } from './Location.js'; @@ -58,6 +58,7 @@ export class FunctionDefinitionNode extends Node implements Named { public name: string, public parameters: ParameterNode[], public body: BlockNode, + public visibility: FunctionVisibility = FunctionVisibility.PUBLIC, ) { super(); } diff --git a/packages/cashc/src/ast/AstBuilder.ts b/packages/cashc/src/ast/AstBuilder.ts index f0585716..07bd2d33 100644 --- a/packages/cashc/src/ast/AstBuilder.ts +++ b/packages/cashc/src/ast/AstBuilder.ts @@ -83,6 +83,7 @@ import CashScriptVisitor from '../grammar/CashScriptVisitor.js'; import { Location } from './Location.js'; import { NumberUnit, + FunctionVisibility, TimeOp, } from './Globals.js'; import { getPragmaName, PragmaName, getVersionOpFromCtx } from './Pragma.js'; @@ -92,7 +93,12 @@ import { ParseError, VersionError } from '../Errors.js'; export default class AstBuilder extends ParseTreeVisitor implements CashScriptVisitor { - constructor(private tree: ParseTree) { + private functionVisibilityIndex = 0; + + constructor( + private tree: ParseTree, + private functionVisibilities: FunctionVisibility[] = [], + ) { super(); } @@ -146,8 +152,10 @@ export default class AstBuilder const statements = ctx.statement_list().map((s) => this.visit(s) as StatementNode); const block = new BlockNode(statements); block.location = Location.fromCtx(ctx); + const visibility = this.functionVisibilities[this.functionVisibilityIndex] ?? FunctionVisibility.PUBLIC; + this.functionVisibilityIndex += 1; - const functionDefinition = new FunctionDefinitionNode(name, parameters, block); + const functionDefinition = new FunctionDefinitionNode(name, parameters, block, visibility); functionDefinition.location = Location.fromCtx(ctx); return functionDefinition; } diff --git a/packages/cashc/src/ast/Globals.ts b/packages/cashc/src/ast/Globals.ts index cc1273d7..99a83630 100644 --- a/packages/cashc/src/ast/Globals.ts +++ b/packages/cashc/src/ast/Globals.ts @@ -46,6 +46,11 @@ export enum Modifier { CONSTANT = 'constant', } +export enum FunctionVisibility { + PUBLIC = 'public', + INTERNAL = 'internal', +} + export const GLOBAL_SYMBOL_TABLE = new SymbolTable(); // Classes diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index bf6d318b..3c8c84a7 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -13,21 +13,30 @@ import SymbolTableTraversal from './semantic/SymbolTableTraversal.js'; import TypeCheckTraversal from './semantic/TypeCheckTraversal.js'; import EnsureFinalRequireTraversal from './semantic/EnsureFinalRequireTraversal.js'; import EnsureInvokedFunctionsSafeTraversal from './semantic/EnsureInvokedFunctionsSafeTraversal.js'; +import { FunctionVisibility } from './ast/Globals.js'; export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { enforceFunctionParameterTypes: true, }; +type PreprocessedVisibilityResult = { + code: string; + functionVisibilities: FunctionVisibility[]; + omittedPublicFunctions: Array<{ name: string; line: number; column: number }>; +}; + export function compileString(code: string, compilerOptions: CompilerOptions = {}): Artifact { const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptions }; + const preprocessed = preprocessFunctionVisibility(code); + emitVisibilityWarnings(preprocessed.omittedPublicFunctions); // Lexing + parsing - let ast = parseCode(code); + let ast = parseCodeFromPreprocessed(preprocessed); // Semantic analysis ast = ast.accept(new SymbolTableTraversal()) as Ast; ast = ast.accept(new TypeCheckTraversal()) as Ast; - ast = ast.accept(new EnsureFinalRequireTraversal(mergedCompilerOptions)) as Ast; + ast = ast.accept(new EnsureFinalRequireTraversal()) as Ast; ast = ast.accept(new EnsureInvokedFunctionsSafeTraversal()) as Ast; // Code generation @@ -55,11 +64,23 @@ export function compileString(code: string, compilerOptions: CompilerOptions = { // Attach debug information const sourceTags = generateSourceTags(optimisationResult.sourceTags); + const rootFrameBytecode = binToHex(scriptToBytecode(optimisationResult.script)); const debug = { - bytecode: binToHex(scriptToBytecode(optimisationResult.script)), + bytecode: rootFrameBytecode, sourceMap: generateSourceMap(optimisationResult.locationData), - logs: optimisationResult.logs, - requires: optimisationResult.requires, + logs: optimisationResult.logs.map((log) => ({ + ...log, + frameBytecode: log.frameBytecode ?? rootFrameBytecode, + data: log.data.map((entry) => ( + typeof entry === 'string' + ? entry + : { ...entry, frameBytecode: entry.frameBytecode ?? log.frameBytecode ?? rootFrameBytecode } + )), + })), + requires: optimisationResult.requires.map((require) => ({ + ...require, + frameBytecode: require.frameBytecode ?? rootFrameBytecode, + })), ...(sourceTags ? { sourceTags } : {}), }; @@ -72,8 +93,13 @@ export function compileFile(codeFile: PathLike, compilerOptions: CompilerOptions } export function parseCode(code: string): Ast { + const preprocessed = preprocessFunctionVisibility(code); + return parseCodeFromPreprocessed(preprocessed); +} + +function parseCodeFromPreprocessed(preprocessed: PreprocessedVisibilityResult): Ast { // Lexing (throwing on errors) - const inputStream = new CharStream(code); + const inputStream = new CharStream(preprocessed.code); const lexer = new CashScriptLexer(inputStream); lexer.removeErrorListeners(); lexer.addErrorListener(ThrowingErrorListener.INSTANCE); @@ -86,7 +112,65 @@ export function parseCode(code: string): Ast { const parseTree = parser.sourceFile(); // AST building - const ast = new AstBuilder(parseTree).build() as Ast; + const ast = new AstBuilder(parseTree, preprocessed.functionVisibilities).build() as Ast; return ast; } + +function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResult { + const inputStream = new CharStream(code); + const lexer = new CashScriptLexer(inputStream); + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + const visibleTokens = tokenStream.tokens.filter((token) => token.channel === 0 && token.type !== -1); + const mutableCode = code.split(''); + const functionVisibilities: FunctionVisibility[] = []; + const omittedPublicFunctions: Array<{ name: string; line: number; column: number }> = []; + + for (let i = 0; i < visibleTokens.length; i += 1) { + if (visibleTokens[i].text !== 'function') continue; + + let cursor = i + 1; + if (!visibleTokens[cursor] || !visibleTokens[cursor + 1] || visibleTokens[cursor + 1].text !== '(') continue; + const functionNameToken = visibleTokens[cursor]; + + cursor += 2; + let depth = 1; + while (cursor < visibleTokens.length && depth > 0) { + if (visibleTokens[cursor].text === '(') depth += 1; + if (visibleTokens[cursor].text === ')') depth -= 1; + cursor += 1; + } + + const visibilityToken = visibleTokens[cursor]; + if (visibilityToken?.text === FunctionVisibility.INTERNAL || visibilityToken?.text === FunctionVisibility.PUBLIC) { + functionVisibilities.push(visibilityToken.text as FunctionVisibility); + for (let index = visibilityToken.start; index <= visibilityToken.stop; index += 1) { + mutableCode[index] = ' '; + } + continue; + } + + functionVisibilities.push(FunctionVisibility.PUBLIC); + omittedPublicFunctions.push({ + name: functionNameToken.text!, + line: functionNameToken.line, + column: functionNameToken.column, + }); + } + + return { code: mutableCode.join(''), functionVisibilities, omittedPublicFunctions }; +} + +function emitVisibilityWarnings(omittedPublicFunctions: Array<{ name: string; line: number; column: number }>): void { + if (omittedPublicFunctions.length === 0) return; + + const summary = omittedPublicFunctions + .map(({ name, line, column }) => `${name} (Line ${line}, Column ${column})`) + .join(', '); + + console.warn( + `Warning: ${omittedPublicFunctions.length} function(s) omit visibility and default to public: ${summary}.`, + ); +} diff --git a/packages/cashc/src/generation/GenerateTargetTraversal.ts b/packages/cashc/src/generation/GenerateTargetTraversal.ts index 67dec6fb..74eed8b2 100644 --- a/packages/cashc/src/generation/GenerateTargetTraversal.ts +++ b/packages/cashc/src/generation/GenerateTargetTraversal.ts @@ -1,4 +1,4 @@ -import { hexToBin } from '@bitauth/libauth'; +import { binToHex, hexToBin } from '@bitauth/libauth'; import { asmToScript, encodeBool, @@ -147,7 +147,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { visitContract(node: ContractNode): Node { node.parameters = this.visitList(node.parameters) as ParameterNode[]; - const publicFunctions = getPublicFunctions(node.functions, this.compilerOptions); + const publicFunctions = getPublicFunctions(node.functions); // Keep track of constructor parameter count for instructor pointer calculation this.constructorParameterCount = node.parameters.length; @@ -238,10 +238,31 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { private generateFunctionBytecode(node: FunctionDefinitionNode): Uint8Array { const traversal = new GenerateTargetTraversalWithLocation(this.compilerOptions); traversal.functionIndices = new Map(this.functionIndices); - traversal.constructorParameterCount = this.constructorParameterCount; + traversal.constructorParameterCount = 0; node.accept(traversal); traversal.output = asmToScript(scriptToAsm(traversal.output)); - return scriptToBytecode(traversal.output); + const frameBytecode = scriptToBytecode(traversal.output); + const frameBytecodeHex = binToHex(frameBytecode); + traversal.annotateFrameDebugMetadata(frameBytecodeHex); + this.consoleLogs.push(...traversal.consoleLogs); + this.requires.push(...traversal.requires); + return frameBytecode; + } + + annotateFrameDebugMetadata(frameBytecode: string): void { + this.finalStackUsage = Object.fromEntries( + Object.entries(this.finalStackUsage).map(([name, usage]) => [name, { ...usage, frameBytecode }]), + ); + + this.consoleLogs = this.consoleLogs.map((log) => ({ + ...log, + frameBytecode, + data: log.data.map((entry) => ( + typeof entry === 'string' ? entry : { ...entry, frameBytecode } + )), + })); + + this.requires = this.requires.map((require) => ({ ...require, frameBytecode })); } removeFinalVerifyFromFunction(functionBodyNode: Node): void { @@ -386,6 +407,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { ip: this.getMostRecentInstructionPointer() - 1, line: node.location.start.line, message: node.message, + location: node.location, }); this.popFromStack(); @@ -401,6 +423,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { ip: this.getMostRecentInstructionPointer(), line: node.location.start.line, message: node.message, + location: node.location, }); this.popFromStack(); diff --git a/packages/cashc/src/index.ts b/packages/cashc/src/index.ts index c274a002..afe32e22 100644 --- a/packages/cashc/src/index.ts +++ b/packages/cashc/src/index.ts @@ -1,5 +1,5 @@ export * from './Errors.js'; export * as utils from '@cashscript/utils'; -export { compileFile, compileString } from './compiler.js'; +export { compileFile, compileString, parseCode } from './compiler.js'; export const version = '0.13.0-next.5'; diff --git a/packages/cashc/src/print/OutputSourceCodeTraversal.ts b/packages/cashc/src/print/OutputSourceCodeTraversal.ts index 70f62db2..06f643e8 100644 --- a/packages/cashc/src/print/OutputSourceCodeTraversal.ts +++ b/packages/cashc/src/print/OutputSourceCodeTraversal.ts @@ -79,7 +79,7 @@ export default class OutputSourceCodeTraversal extends AstTraversal { visitFunctionDefinition(node: FunctionDefinitionNode): Node { this.addOutput(`function ${node.name}(`, true); node.parameters = this.visitCommaList(node.parameters) as ParameterNode[]; - this.addOutput(')'); + this.addOutput(`) ${node.visibility}`); this.outputSymbolTable(node.symbolTable); this.addOutput(' '); diff --git a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts index 6f0f1bb2..0254db6d 100644 --- a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts +++ b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts @@ -1,4 +1,3 @@ -import { CompilerOptions } from '@cashscript/utils'; import { ContractNode, ParameterNode, @@ -17,15 +16,11 @@ import { EmptyContractError, EmptyFunctionError, FinalRequireStatementError } fr import { getPublicFunctions } from '../utils.js'; export default class EnsureFinalRequireTraversal extends AstTraversal { - constructor(private compilerOptions: CompilerOptions = {}) { - super(); - } - visitContract(node: ContractNode): ContractNode { node.parameters = this.visitList(node.parameters) as ParameterNode[]; node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; - if (getPublicFunctions(node.functions, this.compilerOptions).length === 0) { + if (getPublicFunctions(node.functions).length === 0) { throw new EmptyContractError(node); } diff --git a/packages/cashc/src/utils.ts b/packages/cashc/src/utils.ts index 1bc1fb8d..280417d4 100644 --- a/packages/cashc/src/utils.ts +++ b/packages/cashc/src/utils.ts @@ -1,7 +1,7 @@ import { BytesType, implicitlyCastable, PrimitiveType, Type } from '@cashscript/utils'; import { BinaryOperator } from './ast/Operator.js'; -import { CompilerOptions } from '@cashscript/utils'; import { FunctionDefinitionNode } from './ast/AST.js'; +import { FunctionVisibility } from './ast/Globals.js'; export function resultingTypeForBinaryOp( operator: BinaryOperator, @@ -23,29 +23,19 @@ export function isNumericType(type?: Type): boolean { return type === PrimitiveType.INT || type === PrimitiveType.BOOL; } -export function isInternalFunctionName( - name: string, - compilerOptions: CompilerOptions = {}, -): boolean { - const prefix = compilerOptions.internalFunctionPrefix; - if (prefix !== undefined) { - return prefix.length > 0 && name.startsWith(prefix); - } - - return name.endsWith('_'); -} - export function getPublicFunctions( functions: FunctionDefinitionNode[], - compilerOptions: CompilerOptions = {}, ): FunctionDefinitionNode[] { - return functions.filter((func) => !isInternalFunctionName(func.name, compilerOptions)); + return functions.filter((func) => func.visibility === FunctionVisibility.PUBLIC); } -export function getInvokedFunctionClosure(functions: FunctionDefinitionNode[]): Set { +export function getInvokedFunctionClosure( + functions: FunctionDefinitionNode[], +): Set { const functionsByName = new Map(functions.map((func) => [func.name, func])); const reachableFunctions = new Set(); - const pending = functions.flatMap((func) => Array.from(func.calledFunctions)); + const pending = getPublicFunctions(functions) + .flatMap((func) => Array.from(func.calledFunctions)); while (pending.length > 0) { const functionName = pending.pop()!; diff --git a/packages/cashc/test/ast/AST.test.ts b/packages/cashc/test/ast/AST.test.ts index 7a7c0076..84bf6e04 100644 --- a/packages/cashc/test/ast/AST.test.ts +++ b/packages/cashc/test/ast/AST.test.ts @@ -52,5 +52,25 @@ describe('AST Builder', () => { expect(rerunOutput).toEqual(initialOutput); }); }); + + it('should preserve explicit function visibility in AST source output', () => { + const input = ` +contract Test() { + function spend(int value) public { + require(validate(value)); + } + + function validate(int value) internal { + require(value == 1); + } +}`; + + const { sourceOutput: initialOutput } = setup(input); + const { sourceOutput: rerunOutput } = setup(initialOutput); + + expect(initialOutput).toContain('function spend(int value) public'); + expect(initialOutput).toContain('function validate(int value) internal'); + expect(rerunOutput).toEqual(initialOutput); + }); }); }); diff --git a/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash b/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash index 5a286ec2..18843482 100644 --- a/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash +++ b/packages/cashc/test/compiler/CircularFunctionDependencyError/direct_recursive_function.cash @@ -1,9 +1,9 @@ contract Test() { - function spend(int value) { - require(loop_(value)); + function spend(int value) public { + require(loop(value)); } - function loop_(int value) { - require(loop_(value)); + function loop(int value) internal { + require(loop(value)); } } diff --git a/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash b/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash index 5452e49e..9bd54d6a 100644 --- a/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash +++ b/packages/cashc/test/compiler/CircularFunctionDependencyError/mutual_recursive_functions.cash @@ -1,13 +1,13 @@ contract Test() { - function spend(int value) { - require(first_(value)); + function spend(int value) public { + require(first(value)); } - function first_(int value) { - require(second_(value)); + function first(int value) internal { + require(second(value)); } - function second_(int value) { - require(first_(value)); + function second(int value) internal { + require(first(value)); } } diff --git a/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash b/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash index f6db7ec2..a8f4aee4 100644 --- a/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash +++ b/packages/cashc/test/compiler/EmptyContractError/internal_only_functions.cash @@ -1,5 +1,5 @@ contract Test() { - function only_() { + function only() internal { require(true); } } diff --git a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash index 749d1a42..d8e29f30 100644 --- a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash +++ b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/invoked_function_uses_contract_parameter.cash @@ -1,9 +1,9 @@ contract Test(int threshold) { - function spend(int value) { - require(isAboveThreshold_(value)); + function spend(int value) public { + require(isAboveThreshold(value)); } - function isAboveThreshold_(int value) { + function isAboveThreshold(int value) internal { require(value > threshold); } } diff --git a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash index 4b1c4eee..db0a4747 100644 --- a/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash +++ b/packages/cashc/test/compiler/InvokedFunctionContractParameterReferenceError/transitive_invoked_function_uses_contract_parameter.cash @@ -1,13 +1,13 @@ contract Test(int requiredValue) { - function spend(int value) { - require(wrapper_(value)); + function spend(int value) public { + require(wrapper(value)); } - function wrapper_(int value) { - require(matchesRequiredValue_(value)); + function wrapper(int value) internal { + require(matchesRequiredValue(value)); } - function matchesRequiredValue_(int value) { + function matchesRequiredValue(int value) internal { require(value == requiredValue); } } diff --git a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash index 090489b2..5eabc70b 100644 --- a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash +++ b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/signature_check_in_invoked_function.cash @@ -1,9 +1,9 @@ contract Test() { - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { require(world(s, pk)); } - function world(sig s, pubkey pk) { + function world(sig s, pubkey pk) internal { require(checkSig(s, pk)); } } diff --git a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash index e160bc52..9168e9cd 100644 --- a/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash +++ b/packages/cashc/test/compiler/InvokedFunctionSignatureCheckError/transitive_signature_check_in_invoked_function.cash @@ -1,13 +1,13 @@ contract Test(pubkey owner) { - function spend(sig ownerSig) { - require(wrapper_(ownerSig)); + function spend(sig ownerSig) public { + require(wrapper(ownerSig)); } - function wrapper_(sig ownerSig) { - require(checkOwner_(ownerSig)); + function wrapper(sig ownerSig) internal { + require(checkOwner(ownerSig)); } - function checkOwner_(sig ownerSig) { + function checkOwner(sig ownerSig) internal { require(checkSig(ownerSig, owner)); } } diff --git a/packages/cashc/test/compiler/compiler.test.ts b/packages/cashc/test/compiler/compiler.test.ts index cad643de..ee9daeeb 100644 --- a/packages/cashc/test/compiler/compiler.test.ts +++ b/packages/cashc/test/compiler/compiler.test.ts @@ -8,10 +8,21 @@ import { URL } from 'url'; import { getSubdirectories, readCashFiles } from '../test-utils.js'; import * as Errors from '../../src/Errors.js'; -import { compileString } from '../../src/index.js'; +import { compileString, parseCode } from '../../src/index.js'; +import { FunctionVisibility } from '../../src/ast/Globals.js'; describe('Compiler', () => { describe('Successful compilation', () => { + let warnSpy: ReturnType; + + beforeEach(() => { + warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + readCashFiles(new URL('../valid-contract-files', import.meta.url)).forEach((file) => { it(`${file.fn} should succeed`, () => { expect(() => compileString(file.contents)).not.toThrow(); @@ -20,6 +31,16 @@ describe('Compiler', () => { }); describe('Compilation errors', () => { + let warnSpy: ReturnType; + + beforeEach(() => { + warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + const errorTypes = getSubdirectories(new URL('.', import.meta.url)); errorTypes.forEach((errorType) => { @@ -37,4 +58,118 @@ describe('Compiler', () => { }); }); }); + + describe('Function visibility', () => { + it('should default omitted visibility to public', () => { + const ast = parseCode(` +contract Test() { + function spend() { + require(true); + } +} +`); + + expect(ast.contract.functions[0]?.visibility).toBe(FunctionVisibility.PUBLIC); + }); + + it('should parse explicit visibility with newlines and comments', () => { + const ast = parseCode(` +contract Test() { + function spend( + int value + ) + /* public entrypoint */ + public { + require(validate(value)); + } + + function validate( + int value + ) + // internal helper + internal { + require(value == 1); + } +} +`); + + expect(ast.contract.functions.map((func) => func.visibility)).toEqual([ + FunctionVisibility.PUBLIC, + FunctionVisibility.INTERNAL, + ]); + }); + + it('should not infer visibility from underscores in names', () => { + const artifact = compileString(` +contract Test() { + function spend_value() { + require(helper_value()); + } + + function helper_value() public { + require(true); + } +} +`); + + expect(artifact.abi.map((func) => func.name)).toEqual(['spend_value', 'helper_value']); + }); + + it('should reject duplicated visibility markers', () => { + expect(() => compileString(` +contract Test() { + function spend() public internal { + require(true); + } +} +`)).toThrow(Errors.ParseError); + }); + + it('should reject visibility before the function keyword', () => { + expect(() => compileString(` +contract Test() { + public function spend() { + require(true); + } +} +`)).toThrow(Errors.ParseError); + }); + + it('should warn when visibility is omitted', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + + compileString(` +contract Test() { + function spend() { + require(true); + } +} +`); + + expect(warnSpy).toHaveBeenCalledWith( + 'Warning: 1 function(s) omit visibility and default to public: spend (Line 3, Column 11).', + ); + + warnSpy.mockRestore(); + }); + + it('should not warn when visibility is explicit', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + + compileString(` +contract Test() { + function spend() public { + require(validate()); + } + + function validate() internal { + require(true); + } +} +`); + + expect(warnSpy).not.toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + }); }); diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index cef27413..be7df20d 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -4,25 +4,62 @@ */ import { URL } from 'url'; -import fs from 'fs'; import { compileFile, compileString } from '../../src/index.js'; import { fixtures } from './fixtures.js'; +import { Artifact, LogData, LogEntry, RequireStatement, StackItem } from '@cashscript/utils'; + +const stripFrameBytecodeFromLogData = (entry: LogData): LogData => { + if (typeof entry === 'string') return entry; + + const stackItem: StackItem = { + ip: entry.ip, + stackIndex: entry.stackIndex, + type: entry.type, + ...(entry.transformations ? { transformations: entry.transformations } : {}), + }; + + return stackItem; +}; + +const stripExtendedDebugMetadata = (artifact: Artifact): Artifact => ({ + ...artifact, + ...(artifact.debug ? { + debug: { + ...artifact.debug, + logs: artifact.debug.logs.map((log): LogEntry => ({ + ip: log.ip, + line: log.line, + data: log.data.map(stripFrameBytecodeFromLogData), + })), + requires: artifact.debug.requires.map((requireStatement): RequireStatement => ({ + ip: requireStatement.ip, + line: requireStatement.line, + ...(requireStatement.message ? { message: requireStatement.message } : {}), + })), + }, + } : {}), +}); describe('Code generation & target code optimisation', () => { fixtures.forEach((fixture) => { it(`should compile ${fixture.fn} to correct Script and artifact`, () => { const artifact = compileFile(new URL(`../valid-contract-files/${fixture.fn}`, import.meta.url), fixture.compilerOptions); - expect(artifact).toEqual({ ...fixture.artifact, updatedAt: expect.any(String) }); + expect(stripExtendedDebugMetadata(artifact)).toEqual({ ...fixture.artifact, updatedAt: expect.any(String) }); }); }); - it('should hide internal helper functions from the ABI and emit BCH function opcodes', () => { - const source = fs.readFileSync( - new URL('../valid-contract-files/internal_helper_functions.cash', import.meta.url), - { encoding: 'utf-8' }, - ); + it('should hide internal functions from the ABI and emit BCH function opcodes', () => { + const artifact = compileString(` +contract InternalFunctions() { + function spend(int x) public { + require(isEven(x)); + } - const artifact = compileString(source); + function isEven(int value) internal { + require(value % 2 == 0); + } +} +`); expect(artifact.abi).toEqual([ { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, @@ -31,26 +68,26 @@ describe('Code generation & target code optimisation', () => { expect(artifact.bytecode).toContain('OP_INVOKE'); }); - it('should only emit OP_DEFINE for reachable internal helpers', () => { + it('should only emit OP_DEFINE for reachable internal functions', () => { const artifact = compileString(` contract ReachableHelpers() { - function spend(int x) { - require(doubleCheck_(x)); + function spend(int x) public { + require(doubleCheck(x)); } - function fallback(int x) { - require(isEven_(x)); + function fallback(int x) public { + require(isEven(x)); } - function doubleCheck_(int value) { - require(isEven_(value)); + function doubleCheck(int value) internal { + require(isEven(value)); } - function isEven_(int value) { + function isEven(int value) internal { require(value % 2 == 0); } - function unused_(int value) { + function unused(int value) internal { require(value == 42); } } @@ -64,14 +101,38 @@ contract ReachableHelpers() { expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); }); - it('should not emit BCH function opcodes for unused internal helpers', () => { + it('should not emit BCH function opcodes for unused internal functions', () => { const artifact = compileString(` contract UnusedHelpers() { - function spend(int x) { + function spend(int x) public { + require(x == 7); + } + + function helper(int value) internal { + require(value == 7); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode).not.toContain('OP_DEFINE'); + expect(artifact.bytecode).not.toContain('OP_INVOKE'); + }); + + it('should ignore internal-only call chains that are unreachable from public entrypoints', () => { + const artifact = compileString(` +contract DeadHelperChain() { + function spend(int x) public { require(x == 7); } - function helper_(int value) { + function helperA(int value) internal { + require(helperB(value)); + } + + function helperB(int value) internal { require(value == 7); } } @@ -82,16 +143,17 @@ contract UnusedHelpers() { ]); expect(artifact.bytecode).not.toContain('OP_DEFINE'); expect(artifact.bytecode).not.toContain('OP_INVOKE'); + expect(artifact.compiler.target).toBeUndefined(); }); it('should support invoking a public function from another public function', () => { const artifact = compileString(` contract PublicCalls() { - function spend(int x) { + function spend(int x) public { require(validate(x)); } - function validate(int value) { + function validate(int value) public { require(value == 7); } } @@ -106,23 +168,107 @@ contract PublicCalls() { expect(artifact.compiler.target).toBe('BCH_2026_05'); }); - it('should respect a custom internal function prefix when requested', () => { + it('should treat function names literally rather than inferring visibility from underscores', () => { const artifact = compileString(` -contract CustomPrefix() { - function spend(int x) { - require(helperCheck(x)); +contract LiteralNames() { + function spend(int x) public { + require(helper_check(x)); } - function helperCheck(int value) { + function helper_check(int value) public { require(value == 11); } } -`, { internalFunctionPrefix: 'helper' }); +`); expect(artifact.abi).toEqual([ { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + { name: 'helper_check', inputs: [{ name: 'value', type: 'int' }] }, ]); expect(artifact.bytecode).toContain('OP_DEFINE'); expect(artifact.bytecode).toContain('OP_INVOKE'); }); + + it('should reject explicit pre-2026 targets when internal-function opcodes are required', () => { + expect(() => compileString(` +contract ExplicitLegacyTarget() { + function spend(int x) public { + require(isTen(x)); + } + + function isTen(int value) internal { + require(value == 10); + } +} +`, { target: 'BCH_2025_05' })).toThrow(/at least BCH_2026_05 is required/); + }); + + it('should allow BCH_SPEC when internal-function opcodes are required', () => { + const artifact = compileString(` +contract SpecTarget() { + function spend(int x) public { + require(isTen(x)); + } + + function isTen(int value) internal { + require(value == 10); + } +} +`, { target: 'BCH_SPEC' }); + + expect(artifact.compiler.target).toBe('BCH_SPEC'); + }); + + it('should support explicit internal visibility without name decoration', () => { + const artifact = compileString(` +contract ExplicitVisibility() { + function spend(int x) public { + require(checkValue(x)); + } + + function checkValue(int value) internal { + require(value == 12); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + ]); + expect(artifact.bytecode).toContain('OP_DEFINE'); + expect(artifact.bytecode).toContain('OP_INVOKE'); + }); + + it('should annotate debug metadata with frame bytecode and require locations', () => { + const artifact = compileString(` +contract DebugMetadata() { + function spend(int x) public { + require(validate(x)); + } + + function validate(int value) internal { + console.log('value:', value); + require(value == 12, 'value must equal 12'); + } +} +`); + + expect(artifact.debug?.logs[0]?.frameBytecode).toBeDefined(); + expect((artifact.debug?.logs[0]?.data[1] as StackItem | undefined)?.frameBytecode).toBeDefined(); + const publicRequire = artifact.debug?.requires.find((requireStatement) => requireStatement.message === undefined); + const internalRequire = artifact.debug?.requires.find((requireStatement) => ( + requireStatement.message === 'value must equal 12' + )); + + expect(publicRequire?.frameBytecode).toBeDefined(); + expect(publicRequire?.location).toEqual({ + start: { line: 4, column: 4 }, + end: { line: 4, column: 25 }, + }); + expect(internalRequire?.frameBytecode).toBeDefined(); + expect(internalRequire?.location).toEqual({ + start: { line: 9, column: 4 }, + end: { line: 9, column: 48 }, + }); + }); }); diff --git a/packages/cashc/test/valid-contract-files/2_of_3_multisig.cash b/packages/cashc/test/valid-contract-files/2_of_3_multisig.cash index abb5bd28..b4b181ee 100644 --- a/packages/cashc/test/valid-contract-files/2_of_3_multisig.cash +++ b/packages/cashc/test/valid-contract-files/2_of_3_multisig.cash @@ -1,5 +1,5 @@ contract MultiSig(pubkey pk1, pubkey pk2, pubkey pk3) { - function spend(sig s1, sig s2) { + function spend(sig s1, sig s2) public { require(checkMultiSig([s1, s2], [pk1, pk2, pk3])); } } diff --git a/packages/cashc/test/valid-contract-files/announcement.cash b/packages/cashc/test/valid-contract-files/announcement.cash index 0226b2e7..a35baf58 100644 --- a/packages/cashc/test/valid-contract-files/announcement.cash +++ b/packages/cashc/test/valid-contract-files/announcement.cash @@ -5,7 +5,7 @@ pragma cashscript >=0.8.0; * remainder of contract funds back to the contract. */ contract Announcement() { - function announce() { + function announce() public { // Create the memo.cash announcement output bytes announcement = new LockingBytecodeNullData([ 0x6d02, diff --git a/packages/cashc/test/valid-contract-files/bigint.cash b/packages/cashc/test/valid-contract-files/bigint.cash index e236f5dd..5e86e503 100644 --- a/packages/cashc/test/valid-contract-files/bigint.cash +++ b/packages/cashc/test/valid-contract-files/bigint.cash @@ -1,5 +1,5 @@ contract BigInt() { - function proofOfBigInt(int x, int y) { + function proofOfBigInt(int x, int y) public { int maxInt32PlusOne = 2147483648; require(x >= maxInt32PlusOne); require(x * y >= maxInt32PlusOne); diff --git a/packages/cashc/test/valid-contract-files/bitshift.cash b/packages/cashc/test/valid-contract-files/bitshift.cash index bce4ad3a..964f9d2a 100644 --- a/packages/cashc/test/valid-contract-files/bitshift.cash +++ b/packages/cashc/test/valid-contract-files/bitshift.cash @@ -1,5 +1,5 @@ contract Bitshift() { - function spend() { + function spend() public { bytes8 x = 0x1122334455667788; bytes8 y = x << 4 >> 4; diff --git a/packages/cashc/test/valid-contract-files/bitwise.cash b/packages/cashc/test/valid-contract-files/bitwise.cash index 3c1f8a30..bfe62642 100644 --- a/packages/cashc/test/valid-contract-files/bitwise.cash +++ b/packages/cashc/test/valid-contract-files/bitwise.cash @@ -1,5 +1,5 @@ contract Test(bytes8 x, bytes8 y) { - function hello() { + function hello() public { require((x | y) == y); } } diff --git a/packages/cashc/test/valid-contract-files/bounded_bytes.cash b/packages/cashc/test/valid-contract-files/bounded_bytes.cash index 2fc2e8b8..3cb6c8a5 100644 --- a/packages/cashc/test/valid-contract-files/bounded_bytes.cash +++ b/packages/cashc/test/valid-contract-files/bounded_bytes.cash @@ -1,5 +1,5 @@ contract BoundedBytes() { - function spend(bytes4 b, int i) { + function spend(bytes4 b, int i) public { require(b == toPaddedBytes(i, 4)); } } diff --git a/packages/cashc/test/valid-contract-files/bytes1_equals_byte.cash b/packages/cashc/test/valid-contract-files/bytes1_equals_byte.cash index 0dc54ca5..c5ecbe4b 100644 --- a/packages/cashc/test/valid-contract-files/bytes1_equals_byte.cash +++ b/packages/cashc/test/valid-contract-files/bytes1_equals_byte.cash @@ -1,5 +1,5 @@ contract Bytes1EqualsByte() { - function hello(int a, byte b) { + function hello(int a, byte b) public { bytes1 c = toPaddedBytes(a, 1); require(b == c); } diff --git a/packages/cashc/test/valid-contract-files/cast_hash_checksig.cash b/packages/cashc/test/valid-contract-files/cast_hash_checksig.cash index e1bc81b5..ec8f4f27 100644 --- a/packages/cashc/test/valid-contract-files/cast_hash_checksig.cash +++ b/packages/cashc/test/valid-contract-files/cast_hash_checksig.cash @@ -1,5 +1,5 @@ contract CastHashChecksig() { - function hello(pubkey pk, sig s) { + function hello(pubkey pk, sig s) public { require((ripemd160(bytes(pk)) == hash160(0x) == !true)); require(checkSig(s, pk)); } diff --git a/packages/cashc/test/valid-contract-files/comments.cash b/packages/cashc/test/valid-contract-files/comments.cash index 8ea8705a..9240ab89 100644 --- a/packages/cashc/test/valid-contract-files/comments.cash +++ b/packages/cashc/test/valid-contract-files/comments.cash @@ -5,7 +5,7 @@ contract Test(int x) { // Line comments are a thing - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { int i = 400 + x; bytes b = 0x07364897987fe87 + bytes(x); diff --git a/packages/cashc/test/valid-contract-files/complex_loop.cash b/packages/cashc/test/valid-contract-files/complex_loop.cash index 338d7525..1929b26a 100644 --- a/packages/cashc/test/valid-contract-files/complex_loop.cash +++ b/packages/cashc/test/valid-contract-files/complex_loop.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int inputSum = 0; int outputSum = 0; int tokenCount = 0; diff --git a/packages/cashc/test/valid-contract-files/correct_pragma.cash b/packages/cashc/test/valid-contract-files/correct_pragma.cash index 58552541..1cd385ab 100644 --- a/packages/cashc/test/valid-contract-files/correct_pragma.cash +++ b/packages/cashc/test/valid-contract-files/correct_pragma.cash @@ -1,11 +1,11 @@ pragma cashscript >=0.8.0; contract Test() { - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { require(checkSig(s, pk)); } - function world(int a) { + function world(int a) public { require(a + 5 == 10); } } diff --git a/packages/cashc/test/valid-contract-files/covenant.cash b/packages/cashc/test/valid-contract-files/covenant.cash index 7e18a30e..0ee5afbe 100644 --- a/packages/cashc/test/valid-contract-files/covenant.cash +++ b/packages/cashc/test/valid-contract-files/covenant.cash @@ -1,5 +1,5 @@ contract Covenant(int requiredVersion) { - function spend() { + function spend() public { require(tx.version == requiredVersion); require(this.activeBytecode == 0x00); } diff --git a/packages/cashc/test/valid-contract-files/covenant_all_fields.cash b/packages/cashc/test/valid-contract-files/covenant_all_fields.cash index 442c1ff3..69f901b4 100644 --- a/packages/cashc/test/valid-contract-files/covenant_all_fields.cash +++ b/packages/cashc/test/valid-contract-files/covenant_all_fields.cash @@ -1,5 +1,5 @@ contract Covenant() { - function spend() { + function spend() public { require(tx.version == 2); require(tx.locktime == 0); require(tx.inputs.length == 1); diff --git a/packages/cashc/test/valid-contract-files/date_literal.cash b/packages/cashc/test/valid-contract-files/date_literal.cash index 05d1e757..02ebcb19 100644 --- a/packages/cashc/test/valid-contract-files/date_literal.cash +++ b/packages/cashc/test/valid-contract-files/date_literal.cash @@ -1,7 +1,7 @@ pragma cashscript >=0.8.0; contract Test() { - function test() { + function test() public { int d = date("2021-02-17T01:30:00"); //YYYY-MM-DDThh:mm:ss require(d == 0); } diff --git a/packages/cashc/test/valid-contract-files/debug_messages.cash b/packages/cashc/test/valid-contract-files/debug_messages.cash index 92203434..b4724170 100644 --- a/packages/cashc/test/valid-contract-files/debug_messages.cash +++ b/packages/cashc/test/valid-contract-files/debug_messages.cash @@ -1,5 +1,5 @@ contract DebugMessages() { - function spend(int value) { + function spend(int value) public { require(value == 1, "Wrong value passed"); console.log(value, "test"); console.log(value, "test2"); diff --git a/packages/cashc/test/valid-contract-files/deep_replace.cash b/packages/cashc/test/valid-contract-files/deep_replace.cash index a1462b94..6b24b115 100644 --- a/packages/cashc/test/valid-contract-files/deep_replace.cash +++ b/packages/cashc/test/valid-contract-files/deep_replace.cash @@ -1,5 +1,5 @@ contract DeepReplace() { - function hello() { + function hello() public { int a = 1; int b = 2; int c = 3; diff --git a/packages/cashc/test/valid-contract-files/deeply_nested-logs.cash b/packages/cashc/test/valid-contract-files/deeply_nested-logs.cash index d6664825..e6b00cd5 100644 --- a/packages/cashc/test/valid-contract-files/deeply_nested-logs.cash +++ b/packages/cashc/test/valid-contract-files/deeply_nested-logs.cash @@ -6,13 +6,13 @@ contract TransferWithTimeout( int timeout ) { // Require recipient's signature to match - function transfer(sig recipientSig) { + function transfer(sig recipientSig) public { require(checkSig(recipientSig, recipient)); console.log('recipientSig is', recipientSig); } // Require timeout time to be reached and sender's signature to match - function timeout(sig senderSig) { + function timeout(sig senderSig) public { require(checkSig(senderSig, sender)); if (timeout > 0) { if (timeout < 10) { diff --git a/packages/cashc/test/valid-contract-files/deeply_nested.cash b/packages/cashc/test/valid-contract-files/deeply_nested.cash index c5f1e31a..0b63bb32 100644 --- a/packages/cashc/test/valid-contract-files/deeply_nested.cash +++ b/packages/cashc/test/valid-contract-files/deeply_nested.cash @@ -6,12 +6,12 @@ contract TransferWithTimeout( int timeout ) { // Require recipient's signature to match - function transfer(sig recipientSig) { + function transfer(sig recipientSig) public { require(checkSig(recipientSig, recipient)); } // Require timeout time to be reached and sender's signature to match - function timeout(sig senderSig) { + function timeout(sig senderSig) public { require(checkSig(senderSig, sender)); if (timeout > 0) { if (timeout < 10) { diff --git a/packages/cashc/test/valid-contract-files/do_while_loop.cash b/packages/cashc/test/valid-contract-files/do_while_loop.cash index 2cfb3292..60ab9dd8 100644 --- a/packages/cashc/test/valid-contract-files/do_while_loop.cash +++ b/packages/cashc/test/valid-contract-files/do_while_loop.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int i = 0; do { diff --git a/packages/cashc/test/valid-contract-files/do_while_loop_no_introspection.cash b/packages/cashc/test/valid-contract-files/do_while_loop_no_introspection.cash index a11a89b1..b0d2fb6c 100644 --- a/packages/cashc/test/valid-contract-files/do_while_loop_no_introspection.cash +++ b/packages/cashc/test/valid-contract-files/do_while_loop_no_introspection.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int i = 0; int x = 2; diff --git a/packages/cashc/test/valid-contract-files/do_while_loop_no_tokens_in_inputs.cash b/packages/cashc/test/valid-contract-files/do_while_loop_no_tokens_in_inputs.cash index 07290469..1c9a20a2 100644 --- a/packages/cashc/test/valid-contract-files/do_while_loop_no_tokens_in_inputs.cash +++ b/packages/cashc/test/valid-contract-files/do_while_loop_no_tokens_in_inputs.cash @@ -1,7 +1,7 @@ pragma cashscript ^0.13.0; contract Loopy() { - function doLoop() { + function doLoop() public { int inputIndex = 0; // Loop over all inputs (variable length), and make sure that none of them contain tokens diff --git a/packages/cashc/test/valid-contract-files/do_while_loop_require_inside_loop.cash b/packages/cashc/test/valid-contract-files/do_while_loop_require_inside_loop.cash index 6a751cf4..3ddec1cd 100644 --- a/packages/cashc/test/valid-contract-files/do_while_loop_require_inside_loop.cash +++ b/packages/cashc/test/valid-contract-files/do_while_loop_require_inside_loop.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int i = 0; do { diff --git a/packages/cashc/test/valid-contract-files/do_while_no_require.cash b/packages/cashc/test/valid-contract-files/do_while_no_require.cash index 4b4fa097..41b95f38 100644 --- a/packages/cashc/test/valid-contract-files/do_while_no_require.cash +++ b/packages/cashc/test/valid-contract-files/do_while_no_require.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int i = 0; require(i < 1); diff --git a/packages/cashc/test/valid-contract-files/double_split.cash b/packages/cashc/test/valid-contract-files/double_split.cash index b0baa894..4a567557 100644 --- a/packages/cashc/test/valid-contract-files/double_split.cash +++ b/packages/cashc/test/valid-contract-files/double_split.cash @@ -1,5 +1,5 @@ contract DoubleSplit(bytes20 pkh) { - function spend() { + function spend() public { bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.split(23)[0].split(3)[1]; require(pkh == actualPkh); } diff --git a/packages/cashc/test/valid-contract-files/everything.cash b/packages/cashc/test/valid-contract-files/everything.cash index 63dab05c..a4960c5e 100644 --- a/packages/cashc/test/valid-contract-files/everything.cash +++ b/packages/cashc/test/valid-contract-files/everything.cash @@ -5,7 +5,7 @@ contract Test(int x, string y) { // Line comments are a thing - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { int i = 400 + x; bytes b = 0x07364897987fe87 + bytes(y); diff --git a/packages/cashc/test/valid-contract-files/for_loop_basic.cash b/packages/cashc/test/valid-contract-files/for_loop_basic.cash index 27fa9a55..abe13519 100644 --- a/packages/cashc/test/valid-contract-files/for_loop_basic.cash +++ b/packages/cashc/test/valid-contract-files/for_loop_basic.cash @@ -1,5 +1,5 @@ contract ForLoopBasic() { - function spend() { + function spend() public { int sum = 0; for (int i = 0; i < 3; i = i + 1) { diff --git a/packages/cashc/test/valid-contract-files/for_loop_stack_items.cash b/packages/cashc/test/valid-contract-files/for_loop_stack_items.cash index 2ee7946f..decb28fa 100644 --- a/packages/cashc/test/valid-contract-files/for_loop_stack_items.cash +++ b/packages/cashc/test/valid-contract-files/for_loop_stack_items.cash @@ -1,5 +1,5 @@ contract ForLoopBasic() { - function spend() { + function spend() public { int sum = 0; int a = 1; int b = 1; diff --git a/packages/cashc/test/valid-contract-files/for_while_nested.cash b/packages/cashc/test/valid-contract-files/for_while_nested.cash index 517e1839..f2fcd149 100644 --- a/packages/cashc/test/valid-contract-files/for_while_nested.cash +++ b/packages/cashc/test/valid-contract-files/for_while_nested.cash @@ -1,5 +1,5 @@ contract ForWhileNested() { - function spend() { + function spend() public { int sum = 0; for (int i = 0; i < 2; i = i + 1) { diff --git a/packages/cashc/test/valid-contract-files/force_cast_smaller_bytes.cash b/packages/cashc/test/valid-contract-files/force_cast_smaller_bytes.cash index ef83dd89..56ab7ba7 100644 --- a/packages/cashc/test/valid-contract-files/force_cast_smaller_bytes.cash +++ b/packages/cashc/test/valid-contract-files/force_cast_smaller_bytes.cash @@ -1,5 +1,5 @@ contract Test() { - function hello() { + function hello() public { // Have to know what you're doing to force cast bytes3 byte_ = unsafe_bytes3(bytes(0x1234)); require(byte_.length == 1); diff --git a/packages/cashc/test/valid-contract-files/function_call_with_function_name.cash b/packages/cashc/test/valid-contract-files/function_call_with_function_name.cash index 5be02f7d..0d99ecf4 100644 --- a/packages/cashc/test/valid-contract-files/function_call_with_function_name.cash +++ b/packages/cashc/test/valid-contract-files/function_call_with_function_name.cash @@ -1,9 +1,9 @@ contract Test(int x) { - function hello() { + function hello() public { require(world(x)); } - function world(int a) { + function world(int a) public { require(a + 5 == 10); } } diff --git a/packages/cashc/test/valid-contract-files/hodl_vault.cash b/packages/cashc/test/valid-contract-files/hodl_vault.cash index dbf2bc8a..6b053882 100644 --- a/packages/cashc/test/valid-contract-files/hodl_vault.cash +++ b/packages/cashc/test/valid-contract-files/hodl_vault.cash @@ -13,7 +13,7 @@ contract HodlVault( sig ownerSig, datasig oracleSig, bytes8 oracleMessage - ) { + ) public { // message: { blockHeight, price } bytes4 blockHeightBin, bytes4 priceBin = oracleMessage.split(4); int blockHeight = int(blockHeightBin); diff --git a/packages/cashc/test/valid-contract-files/if_statement.cash b/packages/cashc/test/valid-contract-files/if_statement.cash index 620ce253..67dd206a 100644 --- a/packages/cashc/test/valid-contract-files/if_statement.cash +++ b/packages/cashc/test/valid-contract-files/if_statement.cash @@ -1,5 +1,5 @@ contract IfStatement(int x, int y) { - function hello(int a, int b) { + function hello(int a, int b) public { int d = a + b; d = d - a; if (d == x - 2) { diff --git a/packages/cashc/test/valid-contract-files/if_statement_number_units-logs.cash b/packages/cashc/test/valid-contract-files/if_statement_number_units-logs.cash index e3994579..09b85c01 100644 --- a/packages/cashc/test/valid-contract-files/if_statement_number_units-logs.cash +++ b/packages/cashc/test/valid-contract-files/if_statement_number_units-logs.cash @@ -1,5 +1,5 @@ contract Test() { - function hello(int a, int b) { + function hello(int a, int b) public { if (a == b - 2 minutes) { require(false); } else if (b == 2 weeks) diff --git a/packages/cashc/test/valid-contract-files/if_statement_number_units.cash b/packages/cashc/test/valid-contract-files/if_statement_number_units.cash index 0cb07f6e..9489a9fb 100644 --- a/packages/cashc/test/valid-contract-files/if_statement_number_units.cash +++ b/packages/cashc/test/valid-contract-files/if_statement_number_units.cash @@ -1,5 +1,5 @@ contract Test() { - function hello(int a, int b) { + function hello(int a, int b) public { if (a == b - 2 minutes) { require(false); } else if (b == 2 weeks) diff --git a/packages/cashc/test/valid-contract-files/int_to_byte.cash b/packages/cashc/test/valid-contract-files/int_to_byte.cash index 18698eff..e491993b 100644 --- a/packages/cashc/test/valid-contract-files/int_to_byte.cash +++ b/packages/cashc/test/valid-contract-files/int_to_byte.cash @@ -1,5 +1,5 @@ contract IntToByte() { - function hello(int a, byte b) { + function hello(int a, byte b) public { byte c = toPaddedBytes(a, 1); require(b == c); } diff --git a/packages/cashc/test/valid-contract-files/integer_formatting.cash b/packages/cashc/test/valid-contract-files/integer_formatting.cash index c1d98d91..3bcf47f6 100644 --- a/packages/cashc/test/valid-contract-files/integer_formatting.cash +++ b/packages/cashc/test/valid-contract-files/integer_formatting.cash @@ -1,5 +1,5 @@ contract IntegerFormatting() { - function test() { + function test() public { int scientific1 = 1e12; int scientific2 = 1E12; int underscores = 1_000_000_000_000; diff --git a/packages/cashc/test/valid-contract-files/internal_helper_functions.cash b/packages/cashc/test/valid-contract-files/internal_helper_functions.cash index fe234696..7e87acb5 100644 --- a/packages/cashc/test/valid-contract-files/internal_helper_functions.cash +++ b/packages/cashc/test/valid-contract-files/internal_helper_functions.cash @@ -1,9 +1,9 @@ contract InternalHelperFunctions() { - function spend(int x) { - require(isTen_(x)); + function spend(int x) public { + require(isTen(x)); } - function isTen_(int value) { + function isTen(int value) internal { require(value == 10); } } diff --git a/packages/cashc/test/valid-contract-files/invert.cash b/packages/cashc/test/valid-contract-files/invert.cash index 463c4ae0..fd744323 100644 --- a/packages/cashc/test/valid-contract-files/invert.cash +++ b/packages/cashc/test/valid-contract-files/invert.cash @@ -1,5 +1,5 @@ contract Test() { - function spend() { + function spend() public { bytes4 x = 0x00000000; bytes4 y = ~x; diff --git a/packages/cashc/test/valid-contract-files/log_intermediate_results.cash b/packages/cashc/test/valid-contract-files/log_intermediate_results.cash index d14e096a..14672a48 100644 --- a/packages/cashc/test/valid-contract-files/log_intermediate_results.cash +++ b/packages/cashc/test/valid-contract-files/log_intermediate_results.cash @@ -1,5 +1,5 @@ contract LogIntermediateResults(pubkey owner) { - function test_log_intermediate_result() { + function test_log_intermediate_result() public { bytes32 singleHash = sha256(owner); console.log(singleHash); bytes32 doubleHash = sha256(singleHash); diff --git a/packages/cashc/test/valid-contract-files/mecenas.cash b/packages/cashc/test/valid-contract-files/mecenas.cash index 563a3aa1..01c069d4 100644 --- a/packages/cashc/test/valid-contract-files/mecenas.cash +++ b/packages/cashc/test/valid-contract-files/mecenas.cash @@ -1,5 +1,5 @@ contract Mecenas(bytes20 recipient, bytes20 funder, int pledge, int period) { - function receive() { + function receive() public { require(this.age >= period); // Check that the first output sends to the recipient @@ -20,7 +20,7 @@ contract Mecenas(bytes20 recipient, bytes20 funder, int pledge, int period) { } } - function reclaim(pubkey pk, sig s) { + function reclaim(pubkey pk, sig s) public { require(hash160(pk) == funder); require(checkSig(s, pk)); } diff --git a/packages/cashc/test/valid-contract-files/multifunction.cash b/packages/cashc/test/valid-contract-files/multifunction.cash index 881aae9e..8311822c 100644 --- a/packages/cashc/test/valid-contract-files/multifunction.cash +++ b/packages/cashc/test/valid-contract-files/multifunction.cash @@ -3,11 +3,11 @@ contract MultiFunction( pubkey recipient, int timeout ) { - function transfer(sig recipientSig) { + function transfer(sig recipientSig) public { require(checkSig(recipientSig, recipient)); } - function timeout(sig senderSig) { + function timeout(sig senderSig) public { require(checkSig(senderSig, sender)); require(tx.time >= timeout); } diff --git a/packages/cashc/test/valid-contract-files/multifunction_if_statements.cash b/packages/cashc/test/valid-contract-files/multifunction_if_statements.cash index b3a1547a..b52455c9 100644 --- a/packages/cashc/test/valid-contract-files/multifunction_if_statements.cash +++ b/packages/cashc/test/valid-contract-files/multifunction_if_statements.cash @@ -1,5 +1,5 @@ contract MultiFunctionIfStatements(int x, int y) { - function transfer(int a, int b) { + function transfer(int a, int b) public { int d = a + b; d = d - a; if (d == x && bool(x)) { @@ -13,7 +13,7 @@ contract MultiFunctionIfStatements(int x, int y) { require(d == y); } - function timeout(int b) { + function timeout(int b) public { int d = b; d = d + 2; if (d == x) { diff --git a/packages/cashc/test/valid-contract-files/multiline_array_multisig.cash b/packages/cashc/test/valid-contract-files/multiline_array_multisig.cash index 1945879e..65ae7dc9 100644 --- a/packages/cashc/test/valid-contract-files/multiline_array_multisig.cash +++ b/packages/cashc/test/valid-contract-files/multiline_array_multisig.cash @@ -1,5 +1,5 @@ contract Test() { - function cms(sig s, pubkey pk) { + function cms(sig s, pubkey pk) public { require(checkMultiSig( [ s, sig(0x00) diff --git a/packages/cashc/test/valid-contract-files/multiline_statements.cash b/packages/cashc/test/valid-contract-files/multiline_statements.cash index 814ae571..5d69f5f8 100644 --- a/packages/cashc/test/valid-contract-files/multiline_statements.cash +++ b/packages/cashc/test/valid-contract-files/multiline_statements.cash @@ -5,7 +5,7 @@ contract MultilineStatements( function spend( int a, string b - ) { + ) public { if (a == x - 2 && b == y) { require(false); diff --git a/packages/cashc/test/valid-contract-files/multiplication.cash b/packages/cashc/test/valid-contract-files/multiplication.cash index 14184412..fd1d50ac 100644 --- a/packages/cashc/test/valid-contract-files/multiplication.cash +++ b/packages/cashc/test/valid-contract-files/multiplication.cash @@ -1,5 +1,5 @@ contract Test(int x) { - function hello() { + function hello() public { int myVariable = 10 - 4; int myOtherVariable = 20 * myVariable % 2; require(myOtherVariable > x); diff --git a/packages/cashc/test/valid-contract-files/num2bin.cash b/packages/cashc/test/valid-contract-files/num2bin.cash index 6d06a21d..74fa046e 100644 --- a/packages/cashc/test/valid-contract-files/num2bin.cash +++ b/packages/cashc/test/valid-contract-files/num2bin.cash @@ -1,5 +1,5 @@ contract Test() { - function hello() { + function hello() public { bytes2 byte_ = toPaddedBytes(10, 2); require(int(byte_) == 10); } diff --git a/packages/cashc/test/valid-contract-files/num2bin_variable.cash b/packages/cashc/test/valid-contract-files/num2bin_variable.cash index a86db2ba..b1b32f67 100644 --- a/packages/cashc/test/valid-contract-files/num2bin_variable.cash +++ b/packages/cashc/test/valid-contract-files/num2bin_variable.cash @@ -1,5 +1,5 @@ contract Num2Bin() { - function spend(int size) { + function spend(int size) public { bytes byte_ = toPaddedBytes(10, size); require(int(byte_) == 10); } diff --git a/packages/cashc/test/valid-contract-files/p2palindrome.cash b/packages/cashc/test/valid-contract-files/p2palindrome.cash index f39a07be..f3930b97 100644 --- a/packages/cashc/test/valid-contract-files/p2palindrome.cash +++ b/packages/cashc/test/valid-contract-files/p2palindrome.cash @@ -1,5 +1,5 @@ contract P2Palindrome() { - function spend(string palindrome) { + function spend(string palindrome) public { require(palindrome.reverse() == palindrome); } } diff --git a/packages/cashc/test/valid-contract-files/p2pkh-logs.cash b/packages/cashc/test/valid-contract-files/p2pkh-logs.cash index fa6f2765..06152d4d 100644 --- a/packages/cashc/test/valid-contract-files/p2pkh-logs.cash +++ b/packages/cashc/test/valid-contract-files/p2pkh-logs.cash @@ -1,5 +1,5 @@ contract P2PKH(bytes20 pkh) { - function spend(pubkey pk, sig s) { + function spend(pubkey pk, sig s) public { require(hash160(pk) == pkh); console.log(pk, pkh, s); require(checkSig(s, pk)); diff --git a/packages/cashc/test/valid-contract-files/p2pkh.cash b/packages/cashc/test/valid-contract-files/p2pkh.cash index 3053dacc..4f60c59c 100644 --- a/packages/cashc/test/valid-contract-files/p2pkh.cash +++ b/packages/cashc/test/valid-contract-files/p2pkh.cash @@ -1,5 +1,5 @@ contract P2PKH(bytes20 pkh) { - function spend(pubkey pk, sig s) { + function spend(pubkey pk, sig s) public { require(hash160(pk) == pkh); require(checkSig(s, pk)); } diff --git a/packages/cashc/test/valid-contract-files/p2pkh_with_assignment.cash b/packages/cashc/test/valid-contract-files/p2pkh_with_assignment.cash index eb849907..0db191cb 100644 --- a/packages/cashc/test/valid-contract-files/p2pkh_with_assignment.cash +++ b/packages/cashc/test/valid-contract-files/p2pkh_with_assignment.cash @@ -1,7 +1,7 @@ pragma cashscript >=0.8.0; contract P2PKH(bytes20 pkh) { - function spend(pubkey pk, sig s) { + function spend(pubkey pk, sig s) public { bytes20 passedPkh = hash160(pk); require(passedPkh == pkh); require(checkSig(s, pk)); diff --git a/packages/cashc/test/valid-contract-files/p2pkh_with_cast.cash b/packages/cashc/test/valid-contract-files/p2pkh_with_cast.cash index 91152dac..b2412529 100644 --- a/packages/cashc/test/valid-contract-files/p2pkh_with_cast.cash +++ b/packages/cashc/test/valid-contract-files/p2pkh_with_cast.cash @@ -1,7 +1,7 @@ pragma cashscript >=0.8.0; contract P2PKH(bytes20 pkh) { - function spend(pubkey pk, bytes65 s) { + function spend(pubkey pk, bytes65 s) public { require(hash160(pk) == pkh); require(checkSig(sig(s), pk)); } diff --git a/packages/cashc/test/valid-contract-files/reassignment.cash b/packages/cashc/test/valid-contract-files/reassignment.cash index 332e3458..4d58cbd2 100644 --- a/packages/cashc/test/valid-contract-files/reassignment.cash +++ b/packages/cashc/test/valid-contract-files/reassignment.cash @@ -1,5 +1,5 @@ contract Reassignment(int x, string y) { - function hello(pubkey pk, sig s) { + function hello(pubkey pk, sig s) public { int myVariable = 10 - 4; int myOtherVariable = 20 + myVariable % 2; require(myOtherVariable > x); diff --git a/packages/cashc/test/valid-contract-files/simple_cast.cash b/packages/cashc/test/valid-contract-files/simple_cast.cash index c0f337b3..3752cd6e 100644 --- a/packages/cashc/test/valid-contract-files/simple_cast.cash +++ b/packages/cashc/test/valid-contract-files/simple_cast.cash @@ -1,5 +1,5 @@ contract Test(int x, string y) { - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { int myVariable = 10 - int(true); int myOtherVariable = 20 + myVariable % 2; require(myOtherVariable > x); diff --git a/packages/cashc/test/valid-contract-files/simple_checkdatasig.cash b/packages/cashc/test/valid-contract-files/simple_checkdatasig.cash index 1e445a4d..d82c9b3d 100644 --- a/packages/cashc/test/valid-contract-files/simple_checkdatasig.cash +++ b/packages/cashc/test/valid-contract-files/simple_checkdatasig.cash @@ -1,5 +1,5 @@ contract Test(datasig s, pubkey pk) { - function cds(bytes data) { + function cds(bytes data) public { require(checkDataSig(s, data, pk)); } } diff --git a/packages/cashc/test/valid-contract-files/simple_constant.cash b/packages/cashc/test/valid-contract-files/simple_constant.cash index c8d16b99..48e2bd80 100644 --- a/packages/cashc/test/valid-contract-files/simple_constant.cash +++ b/packages/cashc/test/valid-contract-files/simple_constant.cash @@ -1,5 +1,5 @@ contract Test() { - function hello() { + function hello() public { string constant m = "hello"; require(m == "hello"); } diff --git a/packages/cashc/test/valid-contract-files/simple_covenant.cash b/packages/cashc/test/valid-contract-files/simple_covenant.cash index 4546db79..78dd1da3 100644 --- a/packages/cashc/test/valid-contract-files/simple_covenant.cash +++ b/packages/cashc/test/valid-contract-files/simple_covenant.cash @@ -1,5 +1,5 @@ contract Test() { - function covenant() { + function covenant() public { require(tx.version == 2); } } diff --git a/packages/cashc/test/valid-contract-files/simple_functions.cash b/packages/cashc/test/valid-contract-files/simple_functions.cash index 21d62ba1..ba141649 100644 --- a/packages/cashc/test/valid-contract-files/simple_functions.cash +++ b/packages/cashc/test/valid-contract-files/simple_functions.cash @@ -1,9 +1,9 @@ contract Test() { - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { require(checkSig(s, pk)); } - function world(int a) { + function world(int a) public { require(a + 5 == 10); } } diff --git a/packages/cashc/test/valid-contract-files/simple_if_statement.cash b/packages/cashc/test/valid-contract-files/simple_if_statement.cash index 03221367..2307fe08 100644 --- a/packages/cashc/test/valid-contract-files/simple_if_statement.cash +++ b/packages/cashc/test/valid-contract-files/simple_if_statement.cash @@ -1,5 +1,5 @@ contract Test(int x, string y) { - function hello(int a, string b) { + function hello(int a, string b) public { if (a == x - 2) { require(false); } else if (b == "Hello " + y) diff --git a/packages/cashc/test/valid-contract-files/simple_multisig.cash b/packages/cashc/test/valid-contract-files/simple_multisig.cash index 5babb26d..88d68405 100644 --- a/packages/cashc/test/valid-contract-files/simple_multisig.cash +++ b/packages/cashc/test/valid-contract-files/simple_multisig.cash @@ -1,5 +1,5 @@ contract Test() { - function cms(sig s, pubkey pk) { + function cms(sig s, pubkey pk) public { require(checkMultiSig([s, sig(0x00)], [pk, pubkey(0x00)])); } } diff --git a/packages/cashc/test/valid-contract-files/simple_splice.cash b/packages/cashc/test/valid-contract-files/simple_splice.cash index 2a9edbbc..dc40cf22 100644 --- a/packages/cashc/test/valid-contract-files/simple_splice.cash +++ b/packages/cashc/test/valid-contract-files/simple_splice.cash @@ -1,5 +1,5 @@ contract Test(bytes b) { - function spend() { + function spend() public { bytes x = b.split(5)[1]; require(x != b); require (b.split(4)[0] != x); diff --git a/packages/cashc/test/valid-contract-files/simple_variables.cash b/packages/cashc/test/valid-contract-files/simple_variables.cash index 31a1069f..0584e08c 100644 --- a/packages/cashc/test/valid-contract-files/simple_variables.cash +++ b/packages/cashc/test/valid-contract-files/simple_variables.cash @@ -1,5 +1,5 @@ contract Test(int x, string y) { - function hello(sig s, pubkey pk) { + function hello(sig s, pubkey pk) public { int myVariable = 10 - 4; int myOtherVariable = 20 + myVariable % 2; require(myOtherVariable > x); diff --git a/packages/cashc/test/valid-contract-files/simulating_state.cash b/packages/cashc/test/valid-contract-files/simulating_state.cash index f2b0b3a9..d2154f29 100644 --- a/packages/cashc/test/valid-contract-files/simulating_state.cash +++ b/packages/cashc/test/valid-contract-files/simulating_state.cash @@ -4,7 +4,7 @@ contract SimulatingState( int pledgePerBlock, bytes8 initialBlock, ) { - function receive() { + function receive() public { // Check that the first output sends to the recipient bytes25 recipientLockingBytecode = new LockingBytecodeP2PKH(recipient); require(tx.outputs[0].lockingBytecode == recipientLockingBytecode); @@ -45,7 +45,7 @@ contract SimulatingState( } } - function reclaim(pubkey pk, sig s) { + function reclaim(pubkey pk, sig s) public { require(hash160(pk) == funder); require(checkSig(s, pk)); } diff --git a/packages/cashc/test/valid-contract-files/slice.cash b/packages/cashc/test/valid-contract-files/slice.cash index 087014f0..e9806190 100644 --- a/packages/cashc/test/valid-contract-files/slice.cash +++ b/packages/cashc/test/valid-contract-files/slice.cash @@ -1,5 +1,5 @@ contract Slice(bytes20 pkh) { - function spend() { + function spend() public { bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice(3, 23); require(pkh == actualPkh); } diff --git a/packages/cashc/test/valid-contract-files/slice_optimised.cash b/packages/cashc/test/valid-contract-files/slice_optimised.cash index d6f92390..7b3c6d68 100644 --- a/packages/cashc/test/valid-contract-files/slice_optimised.cash +++ b/packages/cashc/test/valid-contract-files/slice_optimised.cash @@ -1,5 +1,5 @@ contract Slice(bytes32 data) { - function spend() { + function spend() public { bytes20 pkh = data.slice(0, 20); require(pkh == toPaddedBytes(0, 20)); } diff --git a/packages/cashc/test/valid-contract-files/slice_variable_parameter.cash b/packages/cashc/test/valid-contract-files/slice_variable_parameter.cash index 46e914c9..845fbe73 100644 --- a/packages/cashc/test/valid-contract-files/slice_variable_parameter.cash +++ b/packages/cashc/test/valid-contract-files/slice_variable_parameter.cash @@ -1,5 +1,5 @@ contract Slice(bytes20 pkh) { - function spend() { + function spend() public { int x = 3; bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice(x, 23); require(pkh == actualPkh); diff --git a/packages/cashc/test/valid-contract-files/split_or_slice_signature.cash b/packages/cashc/test/valid-contract-files/split_or_slice_signature.cash index 98652eaf..82ae9a54 100644 --- a/packages/cashc/test/valid-contract-files/split_or_slice_signature.cash +++ b/packages/cashc/test/valid-contract-files/split_or_slice_signature.cash @@ -1,5 +1,5 @@ contract Test(sig signature) { - function spend() { + function spend() public { // Assume Schnorr bytes hashtype1 = signature.split(64)[1]; bytes1 hashtype2 = signature.slice(64, 65); diff --git a/packages/cashc/test/valid-contract-files/split_size.cash b/packages/cashc/test/valid-contract-files/split_size.cash index 2499888c..d0d90b6e 100644 --- a/packages/cashc/test/valid-contract-files/split_size.cash +++ b/packages/cashc/test/valid-contract-files/split_size.cash @@ -1,5 +1,5 @@ contract SplitSize(bytes b) { - function spend() { + function spend() public { bytes x = b.split(b.length / 2)[1]; require(x != b); require(b.split(4)[0] != x); diff --git a/packages/cashc/test/valid-contract-files/split_typed.cash b/packages/cashc/test/valid-contract-files/split_typed.cash index b7a8fcf8..536347d0 100644 --- a/packages/cashc/test/valid-contract-files/split_typed.cash +++ b/packages/cashc/test/valid-contract-files/split_typed.cash @@ -1,5 +1,5 @@ contract SplitTyped(bytes b) { - function spend() { + function spend() public { bytes4 x = b.split(4)[0]; require(x != b); } diff --git a/packages/cashc/test/valid-contract-files/string_concatenation.cash b/packages/cashc/test/valid-contract-files/string_concatenation.cash index f615a66c..19374762 100644 --- a/packages/cashc/test/valid-contract-files/string_concatenation.cash +++ b/packages/cashc/test/valid-contract-files/string_concatenation.cash @@ -1,5 +1,5 @@ contract Test() { - function hello(string who) { + function hello(string who) public { require(("hello " + who).length + 2 > 5); } } diff --git a/packages/cashc/test/valid-contract-files/string_with_escaped_characters.cash b/packages/cashc/test/valid-contract-files/string_with_escaped_characters.cash index 5b44e10d..c51ddbbf 100644 --- a/packages/cashc/test/valid-contract-files/string_with_escaped_characters.cash +++ b/packages/cashc/test/valid-contract-files/string_with_escaped_characters.cash @@ -1,5 +1,5 @@ contract Test(int x) { - function hello() { + function hello() public { int myVariable = 10 - 4; int myOtherVariable = 20 + myVariable % 2; require(myOtherVariable > x); diff --git a/packages/cashc/test/valid-contract-files/sum_input_amount.cash b/packages/cashc/test/valid-contract-files/sum_input_amount.cash index ebfefd44..e216d740 100644 --- a/packages/cashc/test/valid-contract-files/sum_input_amount.cash +++ b/packages/cashc/test/valid-contract-files/sum_input_amount.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int sum = 0; int i = 0; diff --git a/packages/cashc/test/valid-contract-files/token_category_comparison.cash b/packages/cashc/test/valid-contract-files/token_category_comparison.cash index a4dacfa7..e40b15c7 100644 --- a/packages/cashc/test/valid-contract-files/token_category_comparison.cash +++ b/packages/cashc/test/valid-contract-files/token_category_comparison.cash @@ -1,5 +1,5 @@ contract Test() { - function send() { + function send() public { require(tx.inputs[1].tokenCategory == 0x); } } diff --git a/packages/cashc/test/valid-contract-files/trailing_comma.cash b/packages/cashc/test/valid-contract-files/trailing_comma.cash index 29848b6b..6a040248 100644 --- a/packages/cashc/test/valid-contract-files/trailing_comma.cash +++ b/packages/cashc/test/valid-contract-files/trailing_comma.cash @@ -6,7 +6,7 @@ contract Contract( sig ownerSig, datasig oracleMsgSig, sig oracleTxSig, - ) { + ) public { bytes oracleMessage = bytes('Spend') + bytes(12,); require(checkDataSig( oracleMsgSig, diff --git a/packages/cashc/test/valid-contract-files/tuple_unpacking.cash b/packages/cashc/test/valid-contract-files/tuple_unpacking.cash index ddee9238..1b4a68a9 100644 --- a/packages/cashc/test/valid-contract-files/tuple_unpacking.cash +++ b/packages/cashc/test/valid-contract-files/tuple_unpacking.cash @@ -1,5 +1,5 @@ contract Test() { - function split() { + function split() public { string s1 = "hello"; string s2 = "there"; string hello, string there = (s1+s2).split(5); diff --git a/packages/cashc/test/valid-contract-files/tuple_unpacking_parameter.cash b/packages/cashc/test/valid-contract-files/tuple_unpacking_parameter.cash index 31b7f3f2..65c04381 100644 --- a/packages/cashc/test/valid-contract-files/tuple_unpacking_parameter.cash +++ b/packages/cashc/test/valid-contract-files/tuple_unpacking_parameter.cash @@ -1,5 +1,5 @@ contract Test() { - function split(bytes32 b) { + function split(bytes32 b) public { bytes16 x, bytes16 y = b.split(16); require(x == y); } diff --git a/packages/cashc/test/valid-contract-files/tuple_unpacking_single_side_type.cash b/packages/cashc/test/valid-contract-files/tuple_unpacking_single_side_type.cash index b4dfa7c5..d4c2212d 100644 --- a/packages/cashc/test/valid-contract-files/tuple_unpacking_single_side_type.cash +++ b/packages/cashc/test/valid-contract-files/tuple_unpacking_single_side_type.cash @@ -1,5 +1,5 @@ contract Test() { - function split(bytes b) { + function split(bytes b) public { bytes16 x, bytes y = b.split(16); require(x == y); } diff --git a/packages/cashc/test/valid-contract-files/type_enforcement.cash b/packages/cashc/test/valid-contract-files/type_enforcement.cash index 327b835d..8d112a7c 100644 --- a/packages/cashc/test/valid-contract-files/type_enforcement.cash +++ b/packages/cashc/test/valid-contract-files/type_enforcement.cash @@ -4,7 +4,7 @@ contract TypeEnforcement() { bool enforcedBool, bytes4 enforcedBytes, bytes nonEnforcedBytes - ) { + ) public { if (enforcedBool == true) { require(nonEnforcedInt > 6); } diff --git a/packages/cashc/test/valid-contract-files/unsafe_bool_cast.cash b/packages/cashc/test/valid-contract-files/unsafe_bool_cast.cash index da63b155..8fbc5982 100644 --- a/packages/cashc/test/valid-contract-files/unsafe_bool_cast.cash +++ b/packages/cashc/test/valid-contract-files/unsafe_bool_cast.cash @@ -1,5 +1,5 @@ contract Test(int x) { - function test() { + function test() public { require(unsafe_bool(x)); } } diff --git a/packages/cashc/test/valid-contract-files/unsafe_int_cast.cash b/packages/cashc/test/valid-contract-files/unsafe_int_cast.cash index 97212d94..f78a7db1 100644 --- a/packages/cashc/test/valid-contract-files/unsafe_int_cast.cash +++ b/packages/cashc/test/valid-contract-files/unsafe_int_cast.cash @@ -1,5 +1,5 @@ contract Test(bytes4 x) { - function test(bytes4 y) { + function test(bytes4 y) public { require(unsafe_int(x) > unsafe_int(y)); } } diff --git a/packages/cashc/test/valid-contract-files/while_loop.cash b/packages/cashc/test/valid-contract-files/while_loop.cash index e88d0222..8c2bca46 100644 --- a/packages/cashc/test/valid-contract-files/while_loop.cash +++ b/packages/cashc/test/valid-contract-files/while_loop.cash @@ -1,5 +1,5 @@ contract Loopy() { - function doLoop() { + function doLoop() public { int i = 0; while (i < tx.inputs.length) { diff --git a/packages/cashc/test/valid-contract-files/while_loop_basic.cash b/packages/cashc/test/valid-contract-files/while_loop_basic.cash index 789850c4..b47b9430 100644 --- a/packages/cashc/test/valid-contract-files/while_loop_basic.cash +++ b/packages/cashc/test/valid-contract-files/while_loop_basic.cash @@ -1,5 +1,5 @@ contract WhileLoopBasic() { - function spend() { + function spend() public { int i = 0; while (i < 3) { diff --git a/packages/cashc/test/valid-contract-files/while_loop_nested.cash b/packages/cashc/test/valid-contract-files/while_loop_nested.cash index ed79c7d2..a8aec215 100644 --- a/packages/cashc/test/valid-contract-files/while_loop_nested.cash +++ b/packages/cashc/test/valid-contract-files/while_loop_nested.cash @@ -1,5 +1,5 @@ contract WhileLoopNested() { - function spend() { + function spend() public { int i = 0; int total = 0; diff --git a/packages/cashscript/src/Errors.ts b/packages/cashscript/src/Errors.ts index d717253c..488dc53e 100644 --- a/packages/cashscript/src/Errors.ts +++ b/packages/cashscript/src/Errors.ts @@ -71,7 +71,9 @@ export class FailedRequireError extends FailedTransactionError { public bitauthUri: string, public libauthErrorMessage?: string, ) { - const { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer); + const { statement, lineNumber } = requireStatement.location + ? getLocationDataForLocation(artifact, requireStatement.location) + : getLocationDataForInstructionPointer(artifact, failingInstructionPointer); const baseMessage = `${artifact.contractName}.cash:${lineNumber} Require statement failed at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}`; const baseMessageWithRequireMessage = `${baseMessage} with the following message: ${requireStatement.message}`; @@ -105,3 +107,18 @@ const getLocationDataForInstructionPointer = ( return { statement, lineNumber }; }; + +const getLocationDataForLocation = ( + artifact: Artifact, + location: NonNullable, +): { lineNumber: number, statement: string } => { + const failingLines = artifact.source.split('\n').slice(location.start.line - 1, location.end.line); + + failingLines[failingLines.length - 1] = failingLines[failingLines.length - 1].slice(0, location.end.column); + failingLines[0] = failingLines[0].slice(location.start.column); + + return { + lineNumber: location.start.line, + statement: failingLines.join('\n'), + }; +}; diff --git a/packages/cashscript/src/TransactionBuilder.ts b/packages/cashscript/src/TransactionBuilder.ts index c912c9f7..bc97479e 100644 --- a/packages/cashscript/src/TransactionBuilder.ts +++ b/packages/cashscript/src/TransactionBuilder.ts @@ -203,7 +203,7 @@ export class TransactionBuilder { .map((input) => 'contract' in input.unlocker ? input.unlocker.contract.artifact.compiler.version : null) .filter((version) => version !== null); - if (!contractVersions.every((version) => semver.satisfies(version, '>=0.11.0'))) { + if (!contractVersions.every((version) => semver.satisfies(version, '>=0.11.0', { includePrerelease: true }))) { console.warn('For the best debugging experience, please recompile your contract with cashc version 0.11.0 or newer.'); } diff --git a/packages/cashscript/src/debugging.ts b/packages/cashscript/src/debugging.ts index 20a43bda..b9ee2691 100644 --- a/packages/cashscript/src/debugging.ts +++ b/packages/cashscript/src/debugging.ts @@ -67,6 +67,7 @@ const debugSingleScenario = ( template: WalletTemplate, artifact: Artifact | undefined, unlockingScriptId: string, scenarioId: string, ): DebugResult => { const { vm, program } = createProgram(template, unlockingScriptId, scenarioId); + const frameBytecodeCache = new WeakMap(); const fullDebugSteps = vm.debug(program); const lockingScriptStartIndex = getLockingScriptStartIndex(fullDebugSteps, artifact); @@ -92,14 +93,17 @@ const debugSingleScenario = ( // Also note that multiple log statements may exist for the same ip, so we need to handle all of them const executedLogs = executedDebugSteps .flatMap((debugStep, index) => { - const logEntries = artifact.debug?.logs?.filter((log) => log.ip === debugStep.ip); + const logEntries = artifact.debug?.logs?.filter((log) => ( + log.ip === debugStep.ip + && matchesFrameBytecode(debugStep, log.frameBytecode, frameBytecodeCache) + )); if (!logEntries || logEntries.length === 0) return []; const reversedPriorDebugSteps = executedDebugSteps.slice(0, index + 1).reverse(); return logEntries.map((logEntry) => { const decodedLogData = logEntry.data - .map((dataEntry) => decodeLogDataEntry(dataEntry, reversedPriorDebugSteps, vm)); + .map((dataEntry) => decodeLogDataEntry(dataEntry, reversedPriorDebugSteps, vm, logEntry.frameBytecode, frameBytecodeCache)); return { logEntry, decodedLogData }; }); }); @@ -139,7 +143,10 @@ const debugSingleScenario = ( } const requireStatement = (artifact.debug?.requires ?? []) - .find((statement) => statement.ip === requireStatementIp); + .find((statement) => ( + statement.ip === requireStatementIp + && matchesFrameBytecode(lastExecutedDebugStep, statement.frameBytecode, frameBytecodeCache) + )); if (requireStatement) { // Note that we use failingIp here rather than requireStatementIp, see comment above @@ -179,7 +186,10 @@ const debugSingleScenario = ( } const requireStatement = (artifact.debug?.requires ?? []) - .find((message) => message.ip === finalExecutedVerifyIp); + .find((message) => ( + message.ip === finalExecutedVerifyIp + && matchesFrameBytecode(lastExecutedDebugStep, message.frameBytecode, frameBytecodeCache) + )); if (requireStatement) { throw new FailedRequireError( @@ -187,6 +197,22 @@ const debugSingleScenario = ( ); } + const nestedRequireStatement = findMostRecentExecutedRequireStatement( + executedDebugSteps, + artifact, + frameBytecodeCache, + ); + + if (nestedRequireStatement) { + throw new FailedRequireError( + artifact, + nestedRequireStatement.ip, + nestedRequireStatement, + inputIndex, + getBitauthUri(template), + ); + } + throw new FailedTransactionEvaluationError( artifact, sourcemapInstructionPointer, inputIndex, getBitauthUri(template), evaluationResult, ); @@ -279,10 +305,15 @@ const decodeLogDataEntry = ( dataEntry: LogData, reversedPriorDebugSteps: AuthenticationProgramStateCommon[], vm: VM, + fallbackFrameBytecode: string | undefined, + frameBytecodeCache: WeakMap, ): string | bigint | boolean => { if (typeof dataEntry === 'string') return dataEntry; - const dataEntryDebugStep = reversedPriorDebugSteps.find((step) => step.ip === dataEntry.ip); + const dataEntryDebugStep = reversedPriorDebugSteps.find((step) => ( + step.ip === dataEntry.ip + && matchesFrameBytecode(step, dataEntry.frameBytecode ?? fallbackFrameBytecode, frameBytecodeCache) + )); if (!dataEntryDebugStep) { throw new Error(`Should not happen: corresponding data entry debug step not found for entry at ip ${dataEntry.ip}`); @@ -292,6 +323,78 @@ const decodeLogDataEntry = ( return decodeStackItem(dataEntry, transformedDebugStep.stack); }; +const matchesFrameBytecode = ( + debugStep: AuthenticationProgramStateCommon, + expectedFrameBytecode: string | undefined, + cache: WeakMap, +): boolean => { + if (!expectedFrameBytecode) return true; + + let actualFrameBytecode = cache.get(debugStep.instructions); + if (!actualFrameBytecode) { + actualFrameBytecode = instructionsToBytecodeHex(debugStep.instructions); + cache.set(debugStep.instructions, actualFrameBytecode); + } + + return actualFrameBytecode === expectedFrameBytecode || actualFrameBytecode.endsWith(expectedFrameBytecode); +}; + +const findMostRecentExecutedRequireStatement = ( + executedDebugSteps: AuthenticationProgramStateCommon[], + artifact: Artifact, + frameBytecodeCache: WeakMap, +) => { + const matchingRequireStatements = executedDebugSteps + .slice() + .reverse() + .flatMap((debugStep) => ( + (artifact.debug?.requires ?? []).filter((requireStatement) => ( + requireStatement.ip === debugStep.ip + && matchesFrameBytecode(debugStep, requireStatement.frameBytecode, frameBytecodeCache) + )) + )); + + const nestedRequireStatement = matchingRequireStatements.find((requireStatement) => ( + requireStatement.frameBytecode !== undefined + && requireStatement.frameBytecode !== artifact.debug?.bytecode + )); + + if (nestedRequireStatement) { + return nestedRequireStatement; + } + + const nestedFrameBytecode = executedDebugSteps + .slice() + .reverse() + .map((debugStep) => getFrameBytecode(debugStep, frameBytecodeCache)) + .find((frameBytecode) => frameBytecode !== artifact.debug?.bytecode); + + if (nestedFrameBytecode) { + const nestedFrameRequireStatement = (artifact.debug?.requires ?? []) + .filter((requireStatement) => requireStatement.frameBytecode === nestedFrameBytecode) + .at(-1); + + if (nestedFrameRequireStatement) { + return nestedFrameRequireStatement; + } + } + + return matchingRequireStatements.at(0); +}; + +const getFrameBytecode = ( + debugStep: AuthenticationProgramStateCommon, + cache: WeakMap, +): string => { + let actualFrameBytecode = cache.get(debugStep.instructions); + if (!actualFrameBytecode) { + actualFrameBytecode = instructionsToBytecodeHex(debugStep.instructions); + cache.set(debugStep.instructions, actualFrameBytecode); + } + + return actualFrameBytecode; +}; + const applyStackItemTransformations = ( element: StackItem, debugStep: AuthenticationProgramStateCommon, diff --git a/packages/cashscript/test/Contract.test.ts b/packages/cashscript/test/Contract.test.ts index 8343d384..9e02dac9 100644 --- a/packages/cashscript/test/Contract.test.ts +++ b/packages/cashscript/test/Contract.test.ts @@ -25,11 +25,11 @@ import boundedBytesArtifact from './fixture/bounded_bytes.artifact.js'; const helperFunctionArtifact = compileString(` contract HelperFunctions() { - function spend(int value) { - require(isAtLeastSeven_(value)); + function spend(int value) public { + require(isAtLeastSeven(value)); } - function isAtLeastSeven_(int value) { + function isAtLeastSeven(int value) internal { require(value >= 7); } } @@ -37,28 +37,28 @@ contract HelperFunctions() { const nestedHelperFunctionArtifact = compileString(` contract NestedHelperFunctions() { - function spend(int value) { - require(isPositiveAndEven_(value)); + function spend(int value) public { + require(isPositiveAndEven(value)); } - function spendPlusOne(int value) { - require(isPositiveAndEven_(value + 1)); + function spendPlusOne(int value) public { + require(isPositiveAndEven(value + 1)); } - function isPositiveAndEven_(int value) { - require(isPositive_(value)); - require(isEven_(value)); + function isPositiveAndEven(int value) internal { + require(isPositive(value)); + require(isEven(value)); } - function isPositive_(int value) { + function isPositive(int value) internal { require(value > 0); } - function isEven_(int value) { + function isEven(int value) internal { require(value % 2 == 0); } - function unused_(int value) { + function unused(int value) internal { require(value == 42); } } @@ -74,11 +74,11 @@ const oldHelperFunctionArtifact = { const zeroArgHelperArtifact = compileString(` contract ZeroArgHelper() { - function spend() { - require(exactlyOneOutput_()); + function spend() public { + require(exactlyOneOutput()); } - function exactlyOneOutput_() { + function exactlyOneOutput() internal { require(tx.outputs.length == 1); } } @@ -86,11 +86,11 @@ contract ZeroArgHelper() { const publicFunctionCallArtifact = compileString(` contract PublicFunctionCalls() { - function spend(int value) { + function spend(int value) public { require(validate(value)); } - function validate(int value) { + function validate(int value) public { require(value == 7); } } @@ -272,7 +272,7 @@ describe('Contract', () => { }); }); - describe('helper functions', () => { + describe('internal functions', () => { it('exposes only public functions in the ABI and generated unlockers', () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(helperFunctionArtifact, [], { provider }); @@ -282,10 +282,10 @@ describe('Contract', () => { expect(helperFunctionArtifact.bytecode).toContain('OP_DEFINE'); expect(helperFunctionArtifact.bytecode).toContain('OP_INVOKE'); expect(typeof instance.unlock.spend).toBe('function'); - expect((instance.unlock as Record).isAtLeastSeven_).toBeUndefined(); + expect((instance.unlock as Record).isAtLeastSeven).toBeUndefined(); }); - it('generates unlockers for helper-function contracts under BCH_2026_05', () => { + it('generates unlockers for internal-function contracts under BCH_2026_05', () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(helperFunctionArtifact, [], { provider }); @@ -301,7 +301,7 @@ describe('Contract', () => { .supported[0]).toBe(VmTarget.BCH_2026_05); }); - it('can execute a helper-function contract on BCH_2026_05', async () => { + it('can execute an internal-function contract on BCH_2026_05', async () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(helperFunctionArtifact, [], { provider }); const utxo = randomUtxo(); @@ -323,15 +323,15 @@ describe('Contract', () => { await expect(failingTransaction.send()).rejects.toThrow(); }); - it('supports nested helper calls shared across multiple public functions', async () => { + it('supports nested internal calls shared across multiple public functions', async () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(nestedHelperFunctionArtifact, [], { provider }); expect(nestedHelperFunctionArtifact.abi.map((func) => func.name)).toEqual(['spend', 'spendPlusOne']); expect(nestedHelperFunctionArtifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(3); expect(nestedHelperFunctionArtifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); - expect((instance.unlock as Record).isPositiveAndEven_).toBeUndefined(); - expect((instance.unlock as Record).unused_).toBeUndefined(); + expect((instance.unlock as Record).isPositiveAndEven).toBeUndefined(); + expect((instance.unlock as Record).unused).toBeUndefined(); const successUtxo = randomUtxo(); provider.addUtxo(instance.address, successUtxo); @@ -354,7 +354,7 @@ describe('Contract', () => { ).resolves.toBeDefined(); }); - it('supports zero-argument helper functions', async () => { + it('supports zero-argument internal functions', async () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(zeroArgHelperArtifact, [], { provider }); const utxo = randomUtxo(); @@ -385,7 +385,7 @@ describe('Contract', () => { ).resolves.toBeDefined(); }); - it('fails nested helper validation through the SDK when helper requirements are not met', async () => { + it('fails nested internal-function validation through the SDK when requirements are not met', async () => { const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const instance = new Contract(nestedHelperFunctionArtifact, [], { provider }); const utxo = randomUtxo(); diff --git a/packages/cashscript/test/debugging.test.ts b/packages/cashscript/test/debugging.test.ts index 2b27b7a0..ddd37053 100644 --- a/packages/cashscript/test/debugging.test.ts +++ b/packages/cashscript/test/debugging.test.ts @@ -14,6 +14,7 @@ import { artifactTestZeroHandling, artifactTestRequireInsideLoop, artifactTestLogInsideLoop, + artifactTestInternalFunctions, } from './fixture/debugging/debugging_contracts.js'; import { sha256 } from '@cashscript/utils'; @@ -28,6 +29,9 @@ describe('Debugging tests', () => { const contractTestLogInsideLoop = new Contract(artifactTestLogInsideLoop, [], { provider }); const contractTestLogInsideLoopUtxo = randomUtxo(); provider.addUtxo(contractTestLogInsideLoop.address, contractTestLogInsideLoopUtxo); + const contractTestInternalFunctions = new Contract(artifactTestInternalFunctions, [], { provider }); + const contractTestInternalFunctionsUtxo = randomUtxo(); + provider.addUtxo(contractTestInternalFunctions.address, contractTestInternalFunctionsUtxo); it('should log correct values', async () => { const transaction = new TransactionBuilder({ provider }) @@ -213,6 +217,14 @@ describe('Debugging tests', () => { }); it.todo('should log intermediate results that get optimised out inside a loop'); + + it('should log from an internal function with the correct source line', async () => { + const transaction = new TransactionBuilder({ provider }) + .addInput(contractTestInternalFunctionsUtxo, contractTestInternalFunctions.unlock.test_internal_logs(2n)) + .addOutput({ to: contractTestInternalFunctions.address, amount: 10000n }); + + expect(transaction).toLog(new RegExp('^\\[Input #0] Test.cash:12 internal value: 2$')); + }); }); describe('require statements', () => { @@ -228,6 +240,9 @@ describe('Debugging tests', () => { const contractTestRequireInsideLoop = new Contract(artifactTestRequireInsideLoop, [], { provider }); const contractTestRequireInsideLoopUtxo = randomUtxo(); provider.addUtxo(contractTestRequireInsideLoop.address, contractTestRequireInsideLoopUtxo); + const contractTestInternalFunctions = new Contract(artifactTestInternalFunctions, [], { provider }); + const contractTestInternalFunctionsUtxo = randomUtxo(); + provider.addUtxo(contractTestInternalFunctions.address, contractTestInternalFunctionsUtxo); // test_require it('should fail with error message when require statement fails in a multi-function contract', async () => { @@ -265,6 +280,15 @@ describe('Debugging tests', () => { expect(transaction).not.toFailRequireWith(/1 should equal 1/); }); + it('should fail with the internal function require message and statement', async () => { + const transaction = new TransactionBuilder({ provider }) + .addInput(contractTestInternalFunctionsUtxo, contractTestInternalFunctions.unlock.test_internal_require(0n)) + .addOutput({ to: contractTestInternalFunctions.address, amount: 1000n }); + + expect(transaction).toFailRequireWith('Test.cash:17 Require statement failed at input 0 in contract Test.cash at line 17 with the following message: internal value should be 1.'); + expect(transaction).toFailRequireWith('Failing statement: require(value == 1, \'internal value should be 1\')'); + }); + // test_multiple_require_statements_final_fails it('it should only fail with correct error message when there are multiple require statements where the final statement fails', async () => { const transaction = new TransactionBuilder({ provider }) diff --git a/packages/cashscript/test/fixture/debugging/debugging_contracts.ts b/packages/cashscript/test/fixture/debugging/debugging_contracts.ts index be048ef2..00edd56b 100644 --- a/packages/cashscript/test/fixture/debugging/debugging_contracts.ts +++ b/packages/cashscript/test/fixture/debugging/debugging_contracts.ts @@ -2,44 +2,44 @@ import { compileString } from 'cashc'; const CONTRACT_TEST_REQUIRES = ` contract Test() { - function test_logs() { + function test_logs() public { console.log("Hello World"); require(1 == 2); } - function test_no_logs() { + function test_no_logs() public { require(1 == 2); } - function test_require() { + function test_require() public { require(1 == 2, "1 should equal 2"); } - function test_require_no_failure() { + function test_require_no_failure() public { require(1 == 1, "1 should equal 1"); } - function test_multiple_require_statements() { + function test_multiple_require_statements() public { require(1 == 2, "1 should equal 2"); require(1 == 1, "1 should equal 1"); } - function test_multiple_require_statements_final_fails() { + function test_multiple_require_statements_final_fails() public { require(1 == 1, "1 should equal 1"); require(1 == 2, "1 should equal 2"); } - function test_multiple_require_statements_no_message_final() { + function test_multiple_require_statements_no_message_final() public { require(1 == 1, "1 should equal 1"); require(1 == 2); } - function test_timeops_as_final_require() { + function test_timeops_as_final_require() public { require(1 == 1, "1 should equal 1"); require(tx.time >= 100000000, "time should be HUGE"); } - function test_final_require_in_if_statement(int switch) { + function test_final_require_in_if_statement(int switch) public { if (switch == 1) { int a = 2; require(1 == a, "1 should equal 2"); @@ -52,7 +52,7 @@ contract Test() { } } - function test_final_require_in_if_statement_with_deep_reassignment() { + function test_final_require_in_if_statement_with_deep_reassignment() public { int a = 0; int b = 1; int c = 2; @@ -65,30 +65,30 @@ contract Test() { } } - function test_invalid_split_range() { + function test_invalid_split_range() public { bytes test = 0x1234; bytes test2 = test.split(4)[0]; require(test2 == 0x1234); } - function test_invalid_input_index() { + function test_invalid_input_index() public { require(tx.inputs[5].value == 1000); } - function test_fail_checksig(sig s, pubkey pk) { + function test_fail_checksig(sig s, pubkey pk) public { require(checkSig(s, pk), "Signatures do not match"); require(1 == 2, "1 should equal 2"); } - function test_fail_checksig_final_verify(sig s, pubkey pk) { + function test_fail_checksig_final_verify(sig s, pubkey pk) public { require(checkSig(s, pk), "Signatures do not match"); } - function test_fail_checkdatasig(datasig s, bytes message, pubkey pk) { + function test_fail_checkdatasig(datasig s, bytes message, pubkey pk) public { require(checkDataSig(s, message, pk), "Data Signatures do not match"); } - function test_fail_checkmultisig(sig s1, pubkey pk1, sig s2, pubkey pk2) { + function test_fail_checkmultisig(sig s1, pubkey pk1, sig s2, pubkey pk2) public { require(checkMultiSig([s1, s2], [pk1, pk2]), "Multi Signatures do not match"); } } @@ -96,7 +96,7 @@ contract Test() { const CONTRACT_TEST_REQUIRE_INSIDE_LOOP = ` contract Test() { - function test_require_inside_loop() { + function test_require_inside_loop() public { int i = 0; do { i = i + 1; @@ -104,7 +104,7 @@ contract Test() { } while (i < 10); } - function test_require_inside_while_loop() { + function test_require_inside_while_loop() public { int i = 0; while (i < 10) { @@ -115,7 +115,7 @@ contract Test() { require(i == 10); } - function test_require_inside_for_loop() { + function test_require_inside_for_loop() public { int i = 0; for (i = 0; i < 4; i = i + 1) { @@ -125,7 +125,7 @@ contract Test() { require(i == 4); } - function test_require_inside_loop_complex() { + function test_require_inside_loop_complex() public { int i = 0; do { @@ -147,7 +147,7 @@ contract Test() { const CONTRACT_TEST_REQUIRE_SINGLE_FUNCTION = ` contract Test() { - function test_require_single_function() { + function test_require_single_function() public { require(tx.outputs.length == 1, "should have 1 output"); } }`; @@ -155,7 +155,7 @@ contract Test() { const CONTRACT_TEST_MULTILINE_REQUIRES = ` contract Test() { // We test this because the cleanup looks different and the final OP_VERIFY isn't removed for these kinds of functions - function test_fail_large_cleanup() { + function test_fail_large_cleanup() public { int a = 1; int b = 2; int c = 3; @@ -176,7 +176,7 @@ contract Test() { require(1 == 2, "1 should equal 2"); } - function test_fail_multiline_require() { + function test_fail_multiline_require() public { require( 1 == 2, "1 should equal 2" @@ -185,14 +185,14 @@ contract Test() { require(1 == 1); } - function test_fail_multiline_final_require() { + function test_fail_multiline_final_require() public { require( 1 == 2, "1 should equal 2" ); } - function test_multiline_non_require_error() { + function test_multiline_non_require_error() public { int x = tx.outputs[ 5 @@ -201,7 +201,7 @@ contract Test() { require(x == 1000); } - function test_multiline_require_with_unary_op() { + function test_multiline_require_with_unary_op() public { require( !( 0x000000 @@ -219,7 +219,7 @@ contract Test() { require(1 == 1); } - function test_multiline_require_with_instantiation() { + function test_multiline_require_with_instantiation() public { require( new LockingBytecodeP2PKH( hash160(0x000000) @@ -238,7 +238,7 @@ contract Test() { const CONTRACT_TEST_ZERO_HANDLING = ` contract Test(int a) { - function test_zero_handling(int b) { + function test_zero_handling(int b) public { require(a == 0, "a should be 0"); require(b == 0, "b should be 0"); require(a == b, "a should equal b"); @@ -248,7 +248,7 @@ contract Test(int a) { const CONTRACT_TEST_LOGS = ` contract Test(pubkey owner) { - function transfer(sig ownerSig, int num) { + function transfer(sig ownerSig, int num) public { console.log('Hello First Function'); require(checkSig(ownerSig, owner)); @@ -260,12 +260,12 @@ contract Test(pubkey owner) { require(num == 1000); } - function secondFunction() { + function secondFunction() public { console.log("Hello Second Function"); require(1 == 1); } - function functionWithIfStatement(int a) { + function functionWithIfStatement(int a) public { int b = 0; if (a == 1) { @@ -282,11 +282,11 @@ contract Test(pubkey owner) { require(1 == 1); } - function noLogs() { + function noLogs() public { require(1 == 1); } - function test_log_intermediate_result() { + function test_log_intermediate_result() public { bytes32 singleHash = sha256(owner); console.log(singleHash); @@ -295,7 +295,7 @@ contract Test(pubkey owner) { require(doubleHash.length == 32, "doubleHash should be 32 bytes"); } - function test_log_inside_notif_statement(bool isLastScriptHash) { + function test_log_inside_notif_statement(bool isLastScriptHash) public { int inputValue = tx.inputs[this.activeInputIndex].value; console.log('before:', inputValue); if (!isLastScriptHash) { @@ -308,7 +308,7 @@ contract Test(pubkey owner) { const CONTRACT_TEST_LOG_INSIDE_LOOP = ` contract Test() { - function test_log_inside_loop() { + function test_log_inside_loop() public { int i = 0; do { console.log('i:', i); @@ -318,7 +318,7 @@ contract Test() { require(i == 10); } - function test_log_inside_while_loop() { + function test_log_inside_while_loop() public { int i = 0; while (i < 3) { @@ -329,7 +329,7 @@ contract Test() { require(i == 3); } - function test_log_inside_for_loop() { + function test_log_inside_for_loop() public { int sum = 0; for (int i = 0; i < 3; i = i + 1) { @@ -340,7 +340,7 @@ contract Test() { require(sum == 3); } - function test_log_inside_loop_complex() { + function test_log_inside_loop_complex() public { int i = 0; int l = 5; @@ -371,7 +371,7 @@ contract Test() { const CONTRACT_TEST_CONSECUTIVE_LOGS = ` contract Test(pubkey owner) { - function transfer(sig ownerSig, int num) { + function transfer(sig ownerSig, int num) public { require(checkSig(ownerSig, owner)); bytes2 beef = 0xbeef; @@ -387,7 +387,7 @@ contract Test(pubkey owner) { const CONTRACT_TEST_MULTIPLE_LOGS = ` contract Test(pubkey owner) { - function transfer(sig ownerSig, int num) { + function transfer(sig ownerSig, int num) public { require(checkSig(ownerSig, owner)); console.log(ownerSig, owner, num); @@ -404,7 +404,7 @@ contract Test(pubkey owner) { const CONTRACT_TEST_MULTIPLE_CONSTRUCTOR_PARAMETERS = ` contract Test(pubkey owner, int num, int num2, int num3, int num4, int num5) { - function transfer(sig ownerSig) { + function transfer(sig ownerSig) public { console.log('Hello First Function'); require(checkSig(ownerSig, owner)); @@ -420,13 +420,34 @@ contract Test(pubkey owner, int num, int num2, int num3, int num4, int num5) { require(num5 == 5000); } - function secondFunction() { + function secondFunction() public { console.log("Hello Second Function"); require(1 == 1); } } `; +const CONTRACT_TEST_INTERNAL_FUNCTIONS = ` +contract Test() { + function test_internal_logs(int value) public { + require(runInternalLog(value)); + } + + function test_internal_require(int value) public { + require(runInternalRequire(value)); + } + + function runInternalLog(int value) internal { + console.log('internal value:', value); + require(value >= 0, 'value should be non-negative'); + } + + function runInternalRequire(int value) internal { + require(value == 1, 'internal value should be 1'); + } +} +`; + export const artifactTestRequires = compileString(CONTRACT_TEST_REQUIRES); export const artifactTestSingleFunction = compileString(CONTRACT_TEST_REQUIRE_SINGLE_FUNCTION); export const artifactTestMultilineRequires = compileString(CONTRACT_TEST_MULTILINE_REQUIRES); @@ -437,3 +458,4 @@ export const artifactTestMultipleLogs = compileString(CONTRACT_TEST_MULTIPLE_LOG export const artifactTestMultipleConstructorParameters = compileString(CONTRACT_TEST_MULTIPLE_CONSTRUCTOR_PARAMETERS); export const artifactTestRequireInsideLoop = compileString(CONTRACT_TEST_REQUIRE_INSIDE_LOOP); export const artifactTestLogInsideLoop = compileString(CONTRACT_TEST_LOG_INSIDE_LOOP); +export const artifactTestInternalFunctions = compileString(CONTRACT_TEST_INTERNAL_FUNCTIONS); diff --git a/packages/cashscript/test/fixture/debugging/multi_contract_debugging_contracts.ts b/packages/cashscript/test/fixture/debugging/multi_contract_debugging_contracts.ts index 412a7b42..ff53f2d3 100644 --- a/packages/cashscript/test/fixture/debugging/multi_contract_debugging_contracts.ts +++ b/packages/cashscript/test/fixture/debugging/multi_contract_debugging_contracts.ts @@ -2,7 +2,7 @@ import { compileString } from 'cashc'; const SAME_NAME_DIFFERENT_PATH = ` contract SameNameDifferentPath(int a) { - function function_1(int b) { + function function_1(int b) public { if (a == 0) { console.log("a is 0"); require(b == 0, "b should be 0"); @@ -16,7 +16,7 @@ contract SameNameDifferentPath(int a) { const NAME_COLLISION = ` contract NameCollision(int a) { - function name_collision(int b) { + function name_collision(int b) public { require(a == 0, "a should be 0"); require(b == 0, "b should be 0"); } @@ -25,7 +25,7 @@ contract NameCollision(int a) { const CONTRACT_NAME_COLLISION = ` contract NameCollision(int a) { - function name_collision(int b) { + function name_collision(int b) public { require(b == 1, "b should be 1"); require(a == 1, "a should be 1"); } @@ -34,7 +34,7 @@ contract NameCollision(int a) { const FUNCTION_NAME_COLLISION = ` contract FunctionNameCollision(int a) { - function name_collision(int b) { + function name_collision(int b) public { require(b == 1, "b should be 1"); require(a == 1, "a should be 1"); } diff --git a/packages/utils/src/artifact.ts b/packages/utils/src/artifact.ts index c351b30e..04aef5e7 100644 --- a/packages/utils/src/artifact.ts +++ b/packages/utils/src/artifact.ts @@ -1,3 +1,5 @@ +import { LocationI } from './types.js'; + export type VmTarget = | 'BCH_2023_05' | 'BCH_2025_05' @@ -6,7 +8,6 @@ export type VmTarget = export interface CompilerOptions { enforceFunctionParameterTypes?: boolean; - internalFunctionPrefix?: string; target?: VmTarget; } @@ -32,6 +33,7 @@ export interface LogEntry { ip: number; // instruction pointer line: number; // line in the source code data: readonly LogData[]; // data to be logged + frameBytecode?: string; // active bytecode frame in which this log executes } export interface StackItem { @@ -41,6 +43,7 @@ export interface StackItem { stackIndex: number; // Instruction pointer at which we can access the logged variable ip: number; + frameBytecode?: string; // active bytecode frame in which this stack item is available // Operations to apply to the debug state at the specified instruction pointer to make sure that the variable is // on the correct position on the stack. This is used when we're optimising bytecode where the logged variable is // an intermediate result that existed in the unoptimised bytecode, but no longer exists in the optimised bytecode. @@ -53,6 +56,8 @@ export interface RequireStatement { ip: number; // instruction pointer line: number; // line in the source code message?: string; // custom message for failing `require` statement + frameBytecode?: string; // active bytecode frame in which this require executes + location?: LocationI; // source location of the full require statement } export interface Artifact { diff --git a/packages/utils/src/script.ts b/packages/utils/src/script.ts index d19dd150..646f2ced 100644 --- a/packages/utils/src/script.ts +++ b/packages/utils/src/script.ts @@ -320,6 +320,8 @@ function replaceOps( const scriptIp = scriptIndex + constructorParamLength; newRequires = newRequires.map((require) => { + if (require.frameBytecode) return require; + // We calculate the new ip of the require by subtracting the length diff between the matched pattern and replacement const newCalculatedRequireIp = require.ip - lengthDiff; @@ -332,15 +334,18 @@ function replaceOps( }); newLogs = newLogs.map((log) => { + if (log.frameBytecode) return log; + // We calculate the new ip of the log by subtracting the length diff between the matched pattern and replacement const newCalculatedLogIp = log.ip - lengthDiff; return { + ...log, // If the log is within the pattern, we want to make sure that the new ip is at least the scriptIp ip: log.ip >= scriptIp ? Math.max(scriptIp, newCalculatedLogIp) : log.ip, - line: log.line, data: log.data.map((data) => { if (typeof data === 'string') return data; + if (data.frameBytecode) return data; // If the log is completely before the pattern, we don't need to change anything if (data.ip <= scriptIp) return data; diff --git a/website/docs/compiler/artifacts.md b/website/docs/compiler/artifacts.md index 5ce46a27..6627ea65 100644 --- a/website/docs/compiler/artifacts.md +++ b/website/docs/compiler/artifacts.md @@ -62,15 +62,14 @@ interface RequireStatement { interface CompilerOptions { enforceFunctionParameterTypes?: boolean; // Enforce function parameter types (default: true) - internalFunctionPrefix?: string; // Optional custom prefix used to mark helper functions as internal - target?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; // Optional explicit VM target override recorded in the artifact metadata + target?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; // Optional explicit VM target recorded in the artifact metadata } ``` :::note -By default, functions whose names end with `_` are excluded from the artifact ABI. They can still be called by other CashScript functions, but they are not exposed as public SDK entrypoints. +Functions declared `internal` are excluded from the artifact ABI. They can still be called by other CashScript functions, but they are not exposed as public SDK entrypoints. ::: :::note -Artifacts using BCH function opcodes record `compiler.target: 'BCH_2026_05'`. SDK integrations can use this to validate that their runtime/debug environment matches the contract's required VM semantics. +Artifacts using BCH function opcodes record a compatible target of at least `BCH_2026_05`. The compiler rejects lower explicit targets for contracts that require `OP_DEFINE` or `OP_INVOKE`. SDK integrations can use this to validate that their runtime/debug environment matches the contract's required VM semantics. ::: diff --git a/website/docs/compiler/bch-functions.md b/website/docs/compiler/bch-functions.md index 1212fc09..d200b341 100644 --- a/website/docs/compiler/bch-functions.md +++ b/website/docs/compiler/bch-functions.md @@ -1,11 +1,11 @@ --- -title: BCH Functions (beta) +title: BCH Internal Functions (beta) --- -CashScript supports user-defined function calls within a contract by compiling them to BCH function opcodes. +CashScript supports user-defined internal function calls within a contract by compiling them to BCH function opcodes. :::caution -This feature is currently in beta. The helper-function naming convention and some compilation details may still change in a future release. +This feature is currently in beta. The visibility syntax and some compilation details may still change in a future release. CashScript function calls rely on BCH 2026 function semantics. Teams should only use this feature in environments that support `BCH_2026_05` behavior, and should configure testing/debugging providers accordingly. ::: @@ -22,7 +22,7 @@ This page documents how CashScript maps contract functions to that execution mod CashScript contract functions now serve two roles: - public entrypoints, which appear in the artifact ABI and can be called from the SDK -- internal helpers, which can be called by other CashScript functions but are hidden from the ABI +- internal functions, which can be called by other CashScript functions but are hidden from the ABI Public functions can also call other public functions. In that case, the called function remains in the ABI and is also compiled into the BCH function table if it is invoked internally. @@ -30,11 +30,11 @@ Example: ```solidity contract Example() { - function spend(int x) { - require(isTen_(x)); + function spend(int x) public { + require(isTen(x)); } - function isTen_(int value) { + function isTen(int value) internal { require(value == 10); } } @@ -43,19 +43,21 @@ contract Example() { In this example: - `spend()` is a public function -- `isTen_()` is an internal helper because its name ends with `_` +- `isTen()` is an internal function because it is declared `internal` -## Internal Helpers +## Internal Functions -By default, CashScript treats functions whose names end with `_` as internal helpers. +CashScript's execution model supports general internal functions. Visibility is expressed explicitly in function declarations. -Internal helpers: +For backward compatibility, omitted visibility currently still defaults to `public` and produces a compiler warning. + +Internal functions: - can be called by other functions in the same contract - are excluded from the artifact ABI - are not exposed as unlock methods in the TypeScript SDK -For example, `contract.unlock.isTen_` will be unavailable even though `spend()` can still invoke `isTen_()`. +For example, `contract.unlock.isTen` will be unavailable even though `spend()` can still invoke `isTen()`. CashScript also rejects user-defined function names that collide with built-in global function names like `sha256` or `checkSig`. @@ -64,11 +66,11 @@ CashScript also rejects user-defined function names that collide with built-in g When a contract contains user-defined function calls: 1. CashScript computes the closure of all invoked functions. -2. Each reachable called function is compiled into its own bytecode fragment. +2. Each function reachable from a public entrypoint through internal calls is compiled into its own bytecode fragment. 3. Those fragments are registered at the beginning of the script using `OP_DEFINE`. 4. When a function call appears in the contract body, CashScript emits `OP_INVOKE`. -Only reachable called functions are defined. Unused helper functions are not added to the function table. +Only functions reachable from public entrypoints are defined. Dead internal-only call chains are not added to the function table. Public entrypoint dispatch remains separate from BCH function invocation: @@ -77,19 +79,19 @@ Public entrypoint dispatch remains separate from BCH function invocation: ## Return Value Semantics -CashScript functions conceptually return a boolean success value. +CashScript internal functions currently return a boolean success value. That means user-defined function calls are most naturally used in boolean positions, for example: ```solidity -require(validateState_(expectedHash)); +require(validateState(expectedHash)); ``` -Internally, CashScript compiles function bodies so that invoked functions leave a single boolean result on the stack. +Internally, CashScript compiles invoked function bodies so they leave a single boolean result on the stack. ## Constructor And Parameter Access -Invoked helper functions can safely access: +Invoked internal functions can safely access: - their own parameters - global built-in functions and transaction globals @@ -104,11 +106,11 @@ So for: ```solidity contract Example() { - function spend(int x) { - require(isTen_(x)); + function spend(int x) public { + require(isTen(x)); } - function isTen_(int value) { + function isTen(int value) internal { require(value == 10); } } @@ -124,7 +126,7 @@ the artifact ABI only contains: ## Current Limitations -There is currently one important restriction: +There are currently several important restrictions: - internally-invoked functions cannot use `checkSig()`, `checkMultiSig()`, or `checkDataSig()` - internally-invoked functions cannot reference constructor parameters @@ -132,9 +134,9 @@ There is currently one important restriction: This restriction exists because signature coverage for invoked bytecode needs additional SDK/compiler metadata work. -For now, keep signature validation and constructor-parameter-dependent logic in public entrypoint functions, and use invoked helpers for shared logic that depends only on helper arguments and other globals. +For now, keep signature validation and constructor-parameter-dependent logic in public entrypoint functions, and use internal functions for shared logic that depends only on function arguments and other globals. -Artifacts for helper-function contracts record `compiler.target: 'BCH_2026_05'`, and the SDK validates this against provider VM target metadata during local testing/debugging. +Artifacts using BCH function opcodes record at least `compiler.target: 'BCH_2026_05'`, and the compiler rejects lower explicit targets. The SDK validates this against provider VM target metadata during local testing/debugging. For local testing with the SDK, configure your provider explicitly: @@ -145,32 +147,21 @@ const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); const contract = new Contract(artifact, [], { provider }); ``` -:::note -Runtime execution of helper functions is covered by the SDK tests, but debug attribution for `console.log` and `require(...)` inside invoked helper frames is still less precise than for top-level public functions. Teams should currently treat nested-helper debugging output as best-effort. -::: +Nested `console.log` and `require(...)` statements inside invoked internal functions are included in CashScript's debug output, including the internal frame's source line and failing `require(...)` statement when available. ## Compiler Options -If needed, the internal helper naming convention can be customized with `internalFunctionPrefix`: - -```ts -interface CompilerOptions { - enforceFunctionParameterTypes?: boolean; - internalFunctionPrefix?: string; -} -``` - -If `internalFunctionPrefix` is set, it overrides the default helper detection rule and marks functions as internal based on that prefix. +Function visibility is part of the source syntax, so no compiler option is required to decide whether a function is public or internal. ## When To Use This This feature is most useful when: -- multiple public functions share the same validation logic -- you want cleaner contract structure without exposing every helper in the ABI +- multiple public functions share the same logic +- you want cleaner contract structure without exposing every internal function in the ABI - you want the compiler to emit reusable BCH function bodies via `OP_DEFINE`/`OP_INVOKE` It is less useful when: -- the helper performs signature checks +- the internal function performs signature checks - the logic is only used once and inlining is simpler diff --git a/website/docs/compiler/compiler.md b/website/docs/compiler/compiler.md index ca62a091..6cc29c7b 100644 --- a/website/docs/compiler/compiler.md +++ b/website/docs/compiler/compiler.md @@ -104,7 +104,6 @@ const P2PKH = compileString(source); ```ts interface CompilerOptions { enforceFunctionParameterTypes?: boolean; - internalFunctionPrefix?: string; target?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; } ``` @@ -115,6 +114,6 @@ If set to `false`, the compiler will not enforce function parameter types. This This option is useful if you are certain that passing in incorrect function parameter types will not cause runtime vulnerabilities, and you want to save on the extra opcodes that are added to the script to enforce the types. -The `internalFunctionPrefix` option can be used to customize how the compiler detects internal helper functions. If set, functions using that prefix are excluded from the public ABI and are only callable from other CashScript functions. By default, CashScript uses the `foo_()` convention for internal helpers. +The `target` option can be used to explicitly record a required VM target in the artifact metadata. In most cases this is inferred automatically when the compiled contract uses BCH function opcodes such as `OP_DEFINE` and `OP_INVOKE`. If your contract requires those opcodes, the compiler will reject explicit targets below `BCH_2026_05`. -The `target` option can be used to explicitly record a required VM target in the artifact metadata. In most cases this is inferred automatically when the compiled contract uses BCH function opcodes such as `OP_DEFINE` and `OP_INVOKE`. +Function visibility is part of the source syntax. During the current transition, omitted visibility still defaults to `public` and produces a compiler warning. diff --git a/website/docs/compiler/grammar.md b/website/docs/compiler/grammar.md index e354e2c5..14425abb 100644 --- a/website/docs/compiler/grammar.md +++ b/website/docs/compiler/grammar.md @@ -296,7 +296,7 @@ LINE_COMMENT ``` :::note -User-defined function calls compile to BCH function opcodes. Functions whose names end with `_` are treated as internal helpers and are excluded from the public ABI. +User-defined function calls compile to BCH function opcodes. Functions declared `internal` are excluded from the public ABI. ::: For the full compilation model, see [BCH Functions (beta)](/docs/compiler/bch-functions). diff --git a/website/docs/language/contracts.md b/website/docs/language/contracts.md index c56163d5..1022a441 100644 --- a/website/docs/language/contracts.md +++ b/website/docs/language/contracts.md @@ -59,46 +59,50 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) { } ``` -### Internal Helper Functions (beta) +### Internal Functions (beta) CashScript functions can also call other functions in the same contract. :::caution -User-defined function calls and internal helper functions are currently in beta. The naming convention and some implementation details may still change in a future release. +User-defined internal functions are currently in beta. The visibility syntax and some implementation details may still change in a future release. -This feature depends on BCH 2026 function semantics. Artifacts for contracts using helper functions record `compiler.target: 'BCH_2026_05'`. When testing or integrating these contracts in apps, make sure your environment is configured for `BCH_2026_05`. +This feature depends on BCH 2026 function semantics. Artifacts for contracts using internal functions record `compiler.target: 'BCH_2026_05'`. When testing or integrating these contracts in apps, make sure your environment is configured for `BCH_2026_05`. ::: -If a function name ends with `_`, CashScript treats it as an internal helper: +Visibility is declared explicitly on the function definition: - it can still be called from other contract functions - it is not included in the compiled ABI - it is therefore not exposed as an SDK unlock method -This is useful for shared validation logic that should not appear as a public entrypoint. +For backward compatibility, omitted visibility currently still defaults to `public` and produces a compiler warning. + +This is useful for shared logic that should not appear as a public entrypoint. ```solidity contract Vault(pubkey owner) { - function spend(sig ownerSig, int value) { + function spend(sig ownerSig, int value) public { require(checkSig(ownerSig, owner)); - require(isPositiveEven_(value)); + require(isPositiveEven(value)); } - function isPositiveEven_(int value) { + function isPositiveEven(int value) internal { require(value > 0); require(value % 2 == 0); } } ``` -For details on how these helper functions compile to BCH `OP_DEFINE` and `OP_INVOKE`, see [BCH Functions (beta)](/docs/compiler/bch-functions). +These are general internal functions, not a separate "predicate-only" feature. + +For details on how these internal functions compile to BCH `OP_DEFINE` and `OP_INVOKE`, see [BCH Internal Functions (beta)](/docs/compiler/bch-functions). :::caution Internally-invoked functions currently cannot use `checkSig()`, `checkMultiSig()`, or `checkDataSig()`, and they also cannot reference constructor parameters. Keep signature validation and constructor-parameter-dependent logic in public entrypoint functions for now. ::: :::note -When testing helper-function contracts locally, configure your `MockNetworkProvider` for `BCH_2026_05` so local evaluation matches the artifact's required VM target. +When testing contracts that use internal functions locally, configure your `MockNetworkProvider` for `BCH_2026_05` so local evaluation matches the artifact's required VM target. ::: ### Function Arguments diff --git a/website/docs/language/functions.md b/website/docs/language/functions.md index 3d1f64ff..e49fe8b2 100644 --- a/website/docs/language/functions.md +++ b/website/docs/language/functions.md @@ -2,7 +2,7 @@ title: Global Functions --- -This page documents CashScript's built-in functions. In addition to these built-ins, contract functions can now call other user-defined functions in the same contract. For public vs internal helper function behavior, helper-function restrictions, and how CashScript compiles these calls to BCH `OP_DEFINE` / `OP_INVOKE`, see the [contract structure documentation](/docs/language/contracts#internal-helper-functions) and [BCH Functions (beta)](/docs/compiler/bch-functions). +This page documents CashScript's built-in functions. In addition to these built-ins, contract functions can now call other user-defined internal functions in the same contract. For public vs internal function behavior, current visibility rules, restrictions, and how CashScript compiles these calls to BCH `OP_DEFINE` / `OP_INVOKE`, see the [contract structure documentation](/docs/language/contracts#internal-functions-beta) and [BCH Internal Functions (beta)](/docs/compiler/bch-functions). ## Arithmetic functions ### abs() diff --git a/website/docs/sdk/network-provider.md b/website/docs/sdk/network-provider.md index eb73a045..a0bb8943 100644 --- a/website/docs/sdk/network-provider.md +++ b/website/docs/sdk/network-provider.md @@ -29,7 +29,7 @@ provider.vmTarget?: 'BCH_2023_05' | 'BCH_2025_05' | 'BCH_2026_05' | 'BCH_SPEC'; Providers can optionally expose the BCH VM target they are configured for. CashScript uses this metadata to validate artifact requirements during local testing, debugging, and transaction-template generation. -This is especially relevant for contracts using BCH helper-function opcodes like `OP_DEFINE` and `OP_INVOKE`, which currently require `BCH_2026_05`. +This is especially relevant for contracts using BCH internal-function opcodes like `OP_DEFINE` and `OP_INVOKE`, which currently require `BCH_2026_05`. #### Example ```ts diff --git a/website/docs/sdk/testing-setup.md b/website/docs/sdk/testing-setup.md index e8aac0a8..d0a2a39a 100644 --- a/website/docs/sdk/testing-setup.md +++ b/website/docs/sdk/testing-setup.md @@ -29,7 +29,7 @@ const aliceUtxo = provider.addUtxo(aliceAddress, randomUtxo({ ``` :::note -If your contract artifact records `compiler.target` metadata, such as helper-function contracts using BCH `OP_DEFINE` / `OP_INVOKE`, configure your testing provider for the same VM target. Otherwise the SDK will fail fast on target mismatches. +If your contract artifact records `compiler.target` metadata, such as contracts using internal functions compiled to BCH `OP_DEFINE` / `OP_INVOKE`, configure your testing provider for the same VM target. Otherwise the SDK will fail fast on target mismatches. ::: :::note From 7f49d744b634ef1b105440b8737d026159fac7b0 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 2 Apr 2026 19:03:06 -0400 Subject: [PATCH 4/9] Add internal function review and library integration plan --- docs/proposals/internal-functions-review.md | 205 +++++++++ docs/proposals/library-integration-plan.md | 464 ++++++++++++++++++++ 2 files changed, 669 insertions(+) create mode 100644 docs/proposals/internal-functions-review.md create mode 100644 docs/proposals/library-integration-plan.md diff --git a/docs/proposals/internal-functions-review.md b/docs/proposals/internal-functions-review.md new file mode 100644 index 00000000..edfa20ae --- /dev/null +++ b/docs/proposals/internal-functions-review.md @@ -0,0 +1,205 @@ +# Internal Functions Review + +## Scope + +This note records review findings, design constraints, and follow-up ideas for the local `feature/functions-only` strategy. + +It is intended as a future reference for: + +- continuing the internal-function work +- evaluating import-based helper files on top of internal functions +- reviewing attack surfaces and spend-path risks introduced by BCH function support + +The current strategy is: + +- internal functions are compiled to BCH 2026 function frames using `OP_DEFINE` and `OP_INVOKE` +- public functions remain the contract ABI surface +- internal functions are meant to behave like reusable helper logic, not user-facing entrypoints + +## Current Design Direction + +### Internal functions + +The current branch treats internal functions as helper bodies that can be invoked from public entrypoints or from other invoked functions. + +Current safety checks already block several dangerous cases: + +- recursive call graphs +- signature-check built-ins inside invoked functions +- direct contract-parameter capture inside invoked functions + +This is a sound base. + +### Future imported helper files + +The intended direction for imported function files should stay narrow: + +- imported files should act like utility/helper files +- imported files should not define spendable/user-facing contract entrypoints +- imported code should complement a main contract rather than behave like a second contract + +This is conceptually similar to OpenZeppelin utility modules: + +- shared helper logic +- no independent public spend surface +- explicit reuse from a main contract + +The practical implication is: + +- imported files should contribute only helper/internal functions +- imported files should not expose ABI/public functions +- imported files should not define independent contract spend paths +- imported files should eventually be modeled as `library`, not as ordinary spendable contracts + +## Findings + +### 1. Omitted visibility defaults to `public` + +Severity: High + +Relevant code: + +- `packages/cashc/src/compiler.ts` +- `packages/cashc/test/compiler/compiler.test.ts` + +The current preprocessing logic treats omitted visibility as `public` and only emits a warning. + +That is much riskier now that helper functions exist. A developer who forgets to mark a helper as `internal` accidentally creates a new externally callable entrypoint. + +Why this matters: + +- the helper may bypass assumptions enforced only in the intended top-level function +- the helper may become a user-spend path even though it was written as support logic +- warnings are too weak for consensus-critical contract code + +Current design decision: + +- do not force an immediate explicit-visibility migration for all older contracts +- instead, use first-class `library` containers as the stronger boundary for imported helper files + +Recommendation: + +- keep this as a known hardening issue for normal contracts +- do not rely on omitted-visibility behavior for imported helper code +- consider a stricter compiler mode later for contracts that want explicit visibility guarantees + +### 2. Nested-frame debugging is still incomplete for non-`require` failures + +Severity: Medium + +Relevant code: + +- `packages/cashscript/src/debugging.ts` +- `packages/cashscript/src/Errors.ts` + +The branch adds frame bytecode metadata and improves nested `require(...)` attribution, but unmatched evaluation failures still fall back to root-frame sourcemap lookup. + +This means failures inside invoked frames that are not cleanly matched to a `require(...)` can still be reported against the wrong source location. + +Examples to care about: + +- introspection/indexing errors inside an invoked helper +- malformed split/slice behavior inside an invoked helper +- any VM failure path not tied back to a recorded `RequireStatement` + +Recommendation: + +- treat nested-frame evaluation errors as first-class debug cases +- add frame-aware source resolution rather than only frame-aware `require`/log matching +- add tests for internal helper failures that are not ordinary `require(...)` failures + +### 3. Locking-script start detection in debugging is brittle + +Severity: Medium + +Relevant code: + +- `packages/cashscript/src/debugging.ts` + +The comments describe bytecode-based root-frame detection as the safe approach once nested function frames exist, but the current implementation still prefers the last `ip === 0 && controlStack.length === 0` match. + +That is a brittle heuristic if nested frames can also satisfy that shape. + +Why this matters: + +- log slicing can start from the wrong frame +- error attribution can be anchored incorrectly +- correctness depends on current VM debug behavior rather than a stable discriminator + +Recommendation: + +- prefer bytecode/frame identity first +- keep the heuristic fallback only as a last resort +- add tests where nested invoked frames also begin at `ip === 0` + +### 4. Public-to-public invocation duplicates function bodies + +Severity: Medium + +Relevant code: + +- `packages/cashc/src/generation/GenerateTargetTraversal.ts` +- `packages/cashc/test/generation/generation.test.ts` + +The current implementation emits invoked functions as `OP_DEFINE` frames while still inlining public ABI functions in the top-level dispatcher. + +If a public function is called by another public function, its logic is effectively represented twice: + +- once as a dispatch branch +- once as a function frame + +Why this matters: + +- larger bytecode +- higher risk of hitting policy/size/resource limits +- more surface for future debugging drift + +Current design decision: + +- public-to-public behavior is acceptable semantically +- the concern is implementation quality and efficiency, not the feature itself + +Recommendation: + +- preserve public-to-public semantics +- later compile public ABI wrappers that invoke a shared body if we want to remove duplication cleanly + +## General Security Posture + +### What already looks good + +- `EnsureInvokedFunctionsSafeTraversal` blocks recursive helper graphs +- it blocks signature-check built-ins inside invoked functions +- it blocks contract-parameter capture inside invoked functions +- unreachable internal-only helpers are not emitted +- compiler target inference correctly detects `OP_DEFINE` / `OP_INVOKE` and requires BCH 2026 semantics + +### What still needs hardening + +- visibility mistakes should be made harder to miss in normal contracts +- nested-frame debugging should become frame-correct for all failure classes +- codegen should avoid duplicating public bodies where possible +- tests should cover more pathological/internal-frame error cases + +## Suggested Rules For Imported Helper Files + +If import-based helper files are built on top of this strategy, they should follow these restrictions: + +- imported files may only contribute helper/internal functions +- imported files may not define public ABI entrypoints +- imported files may not define independent spend paths +- imported helper functions must satisfy the same safety checks as local invoked functions +- imported helper functions should be parameter-driven and context-closed +- imported helper functions should not capture contract constructor parameters +- imported helper functions should not contain signature-check built-ins +- imported helper files should eventually be represented by a non-spendable `library` container + +This keeps imported files in the role of utility modules, not secondary contracts. + +## Recommended Next Steps + +1. Introduce first-class `library` semantics for imported helper code. +2. Add nested-frame failure tests for non-`require` VM errors. +3. Rework root-frame detection in debugging to prefer bytecode/frame identity. +4. Preserve public-to-public behavior, but reduce duplicated codegen later. +5. Keep imported files utility-only and non-spendable. diff --git a/docs/proposals/library-integration-plan.md b/docs/proposals/library-integration-plan.md new file mode 100644 index 00000000..f36e1054 --- /dev/null +++ b/docs/proposals/library-integration-plan.md @@ -0,0 +1,464 @@ +# Library Integration Plan + +## Purpose + +This document plans the next stage after `feature/functions-only`: + +- keep BCH function opcodes as first-class compilation targets +- preserve backward compatibility for existing contracts as much as possible +- introduce a non-spendable helper container for reusable imported logic + +The proposed helper container is `library`. + +This plan is intentionally architecture-focused. It records: + +- the design choices we are making +- what we are explicitly not doing +- where the compiler should enforce boundaries +- open questions and future gaps + +## Design Goals + +### Core goals + +- preserve current contract semantics for older contracts where practical +- treat `OP_DEFINE` and `OP_INVOKE` as first-class compiler outputs rather than a side feature +- separate spendable entrypoints from helper-only function bodies +- make imported helper code reusable without letting it define its own spend surface +- keep imported code deterministic, auditable, and easy to reason about + +### Non-goals for the first iteration + +- building a package ecosystem +- supporting arbitrary multi-contract import graphs +- allowing imported files to act as standalone spendable contracts +- reproducing Solidity inheritance/module behavior broadly + +## High-Level Model + +### `contract` + +A `contract` remains the normal spendable container. + +It may contain: + +- public entrypoint functions +- helper/internal functions + +It remains the source of: + +- constructor parameters +- ABI/public function surface +- top-level spend authorization paths + +### `library` + +A `library` is a non-spendable helper container. + +It may contain: + +- helper/internal functions only + +It must not contain: + +- public ABI entrypoints +- constructor parameters +- any independent spendable contract surface + +The compiler should treat library functions as helper frames only, never as dispatcher-selectable entry frames. + +## Why `library` Instead of Tightening Visibility Alone + +We do not want to break older contracts too aggressively by requiring all existing contracts to adopt locally introduced `public` / `internal` labels immediately. + +Using a first-class `library` container gives a stronger and cleaner boundary: + +- old `contract` behavior can stay largely compatible +- imported helper files can still be strongly constrained +- the spendable vs non-spendable distinction is modeled at the container level, not only by function annotations + +This fits the intended OpenZeppelin-like utility model better: + +- shared helper modules +- no independent user spend surface +- explicit reuse from a main contract + +## First-Class Opcode Strategy + +The important architectural decision is: + +- function opcodes are first-class execution primitives +- the compiler’s job is to map source-level function containers onto entry frames vs helper frames + +Under this model: + +- public contract functions become dispatcher-selectable entry frames +- helper/internal contract functions become helper frames +- library functions become helper frames only + +This avoids treating helper imports as string flattening first and semantics second. The compiler should explicitly understand: + +- which functions are entrypoints +- which functions are helper-only +- which containers are allowed to contribute entrypoints + +## Proposed Semantic Rules + +### Contract rules + +Regular contracts: + +- may define public entrypoints +- may define helper/internal functions +- may call their own helper functions +- may call imported library helper functions +- remain the only spendable top-level compilation units + +Compatibility policy: + +- existing omitted visibility behavior may remain for contracts if needed for backward compatibility +- helper/public distinction inside contracts can continue to evolve without forcing an immediate break on legacy code + +### Library rules + +Libraries: + +- may define helper/internal functions only +- may not define public ABI functions +- may not define constructor parameters +- may not compile as standalone spendable artifacts +- may only be imported into a contract or another library if we choose to support transitive library imports + +### Imported helper restrictions + +Imported helper functions should satisfy the same safety constraints as local invoked functions, plus the container-level rules above. + +That means imported helper functions must not: + +- expose dispatcher-selectable/public entrypoints +- reference contract constructor parameters +- use signature-check built-ins +- create their own independent spend authorization paths + +Important clarification: + +Imported helper functions may still contain checks such as `require(...)`. + +What is forbidden is not “having checks”. +What is forbidden is “being independently callable as a top-level spend surface”. + +## Recommended Compiler Shape + +### Phase 1: first-class `library` parsing + +Add grammar support for a second top-level container: + +- `contract` +- `library` + +The AST should represent container kind explicitly rather than hiding library semantics in preprocessing. + +Possible direction: + +- `SourceFileNode` contains a top-level container node +- introduce `LibraryNode` alongside `ContractNode` +- or generalize into a shared container abstraction if that reads cleanly + +### Phase 2: semantic container validation + +Add a container-aware semantic pass that enforces: + +- contracts may define entrypoints +- libraries may not define entrypoints +- libraries may not define constructor parameters +- libraries may only contain helper functions + +This is where “imported helpers cannot make their own transaction” should be enforced semantically. + +### Phase 3: entry-frame vs helper-frame lowering + +Refactor code generation around two explicit categories: + +- entry frames +- helper frames + +Desired compiler behavior: + +- contract public functions become entry frames +- contract helper functions become helper frames if reachable +- library functions become helper frames if reachable +- libraries never contribute dispatcher branches + +This should keep opcode lowering consistent with the source model. + +### Phase 4: import integration + +Imports should become a mechanism for bringing library helper frames into a contract’s reachable helper graph. + +For MVP: + +- import only `library` files +- import resolution should remain deterministic and narrow +- imported library functions should merge into the reachable helper graph +- imported library functions should never alter ABI/public dispatch directly + +## Import Strategy + +### MVP import policy + +Allowed: + +- relative imports only +- imported `library` files only +- namespace-qualified library use if we want source clarity +- deterministic compile-time integration + +Not allowed initially: + +- package-manager style resolution +- remote imports +- importing `contract` files +- imported public entrypoints +- imported independent spend surfaces + +### Syntax direction + +The likely source-level direction is something like: + +```cash +import "./math.cash" as Math; + +contract Vault(pubkey owner) { + function spend(sig s, int value) { + require(Math.isPositiveEven(value)); + } +} +``` + +Imported file: + +```cash +library Math { + function isPositiveEven(int value) { + require(isPositive(value)); + require(isEven(value)); + } + + function isPositive(int value) { + require(value > 0); + } + + function isEven(int value) { + require(value % 2 == 0); + } +} +``` + +The final choice on whether library functions need an explicit `internal` marker is still open. Since the container is already non-spendable, implicit helper-only semantics may be acceptable inside `library`. + +## Backward Compatibility Choices + +### What we want to preserve + +- existing `contract` files should continue to compile where possible +- existing omission of `public` should not automatically break older sources if we can avoid it +- helper functions inside normal contracts should remain supported + +### What we can tighten safely + +- imported helper files should be subject to stricter rules than ordinary contracts +- `library` can be strict from day one because it is new surface area +- “no spendable entrypoints in libraries” should be a hard rule, not a warning + +This gives us a compatibility-friendly path: + +- legacy `contract` behavior remains mostly stable +- new `library` behavior is deliberately constrained + +## Architectural Tradeoffs + +### Preprocessing vs first-class AST support + +The exploration branch used a preprocessing/flattening approach. + +That is attractive for speed of implementation, but weaker architecturally because: + +- spendable vs non-spendable semantics are implicit +- debugging and provenance become bolt-ons +- imports are resolved as source rewriting first rather than compiler semantics first + +For this strategy, first-class container semantics are preferred. + +### Visibility policy inside contracts + +We do not currently want to require all old contracts to add explicit `public` / `internal`. + +So the likely direction is: + +- keep contract visibility behavior reasonably compatible +- rely on `library` to enforce strong imported-helper boundaries +- revisit stricter explicit visibility later if the safety tradeoff justifies it + +### Public-to-public invocation + +Current understanding: + +- public-to-public behavior is not inherently wrong +- it matches the idea that one contract function may route into another function body + +So this should not be treated as a semantic bug. + +The remaining concern is implementation quality: + +- avoid needless duplication of public bodies if a cleaner shared-body architecture is possible + +## Security Boundaries To Enforce + +### Spend-surface boundary + +The most important rule: + +- only contract entry frames may define top-level spend authorization paths + +Libraries must not be able to create: + +- ABI-visible entrypoints +- dispatcher branches +- standalone transaction authorization surfaces + +### Context-capture boundary + +Helper/library functions should remain reusable and context-closed. + +They must not silently depend on: + +- constructor parameters +- hidden contract-local authorization assumptions +- signature validation side effects + +### Determinism boundary + +Imports should remain deterministic. + +We should avoid: + +- flexible package resolution +- source drift across environments +- import systems that obscure what helper code was actually compiled + +## Testing Plan + +### Required tests for `library` + +- library file with helper functions compiles only when imported into a contract +- library file cannot define a public spendable entrypoint +- library file cannot define constructor parameters +- imported library helper can be called from a contract +- imported library helper cannot capture contract constructor parameters +- imported library helper cannot use signature-check built-ins +- imported library helper graph rejects cycles + +### Required tests for compiler architecture + +- contract public functions become dispatcher entries +- contract helper functions do not become ABI entries +- library functions never become dispatcher entries +- imported library functions lower to helper frames only +- imported helper failures are reported with correct source attribution once debugging is extended + +## Open Questions + +### 1. Should library functions require explicit `internal`? + +Options: + +- yes, for symmetry with contracts +- no, because the container already guarantees helper-only semantics + +Current leaning: + +- do not require explicit `internal` inside `library` unless it materially simplifies the compiler + +### 2. Should transitive library imports be allowed in v1? + +Options: + +- no, keep one-level imports only for simplicity +- yes, but only for relative local libraries with strict cycle rejection + +Current leaning: + +- start with one-level imports if implementation simplicity matters +- add transitive imports later if needed + +### 3. How should source-level debugging work across libraries? + +Current internal-function debugging already needs more frame-aware handling. +Library support increases that need further. + +Open question: + +- do we postpone full multi-file source fidelity until after semantic integration +- or do we require it before considering library support complete + +Current leaning: + +- semantic/container correctness first +- better multi-file/frame-aware debugging as a required follow-up before calling the feature mature + +## Future Gaps + +### 1. Nested-frame debugging + +The current internal-function strategy still has known gaps around nested-frame failure attribution. + +Library support should not ship as “complete” without improving: + +- frame-aware root detection +- frame-aware evaluation error attribution +- file-aware source mapping if imports are supported + +### 2. Shared-body codegen + +If public-to-public calls remain supported, codegen should eventually avoid compiling the same public logic twice. + +This is an optimization and architectural cleanliness task, not a semantic blocker. + +### 3. Import provenance + +If imports are introduced, artifact/debug metadata should eventually make it easy to answer: + +- which library files were used +- which helper body a failure came from +- what exact source set produced the artifact + +### 4. Formal container semantics + +Right now the AST/grammar are contract-centric. + +Library support likely deserves: + +- explicit container kinds in the grammar +- explicit container nodes in the AST +- explicit semantic passes for entrypoint eligibility + +## Recommended Implementation Order + +1. Introduce first-class `library` grammar and AST support. +2. Add semantic validation that libraries are non-spendable helper containers. +3. Refactor codegen around entry frames vs helper frames. +4. Keep regular contract compatibility as intact as possible. +5. Add minimal deterministic library import support. +6. Improve debugging/provenance for nested frames and imported helpers. + +## Summary + +The recommended direction is: + +- keep old `contract` behavior mostly compatible +- introduce `library` as a strict non-spendable helper container +- keep BCH function opcodes as first-class compiler targets +- model entry frames vs helper frames explicitly in the compiler +- ensure imported helper code can never define its own spend surface + +This gives us a clean path from `functions-only` toward reusable helper modules without blurring the boundary between utility logic and spend authorization. From 079c5e30544b6271ed1fd5a306bee8e377992277 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 2 Apr 2026 20:00:26 -0400 Subject: [PATCH 5/9] Add library containers for imported internal helpers --- packages/cashc/src/Errors.ts | 45 ++ packages/cashc/src/ast/AST.ts | 3 + packages/cashc/src/ast/AstBuilder.ts | 3 +- packages/cashc/src/compiler.ts | 130 +++- packages/cashc/src/imports.ts | 662 ++++++++++++++++++ packages/cashc/src/index.ts | 2 + .../src/print/OutputSourceCodeTraversal.ts | 2 +- .../EnsureContainerSemanticsTraversal.ts | 34 + .../semantic/EnsureFinalRequireTraversal.ts | 2 +- packages/cashc/test/compiler/compiler.test.ts | 165 +++++ .../cashc/test/generation/generation.test.ts | 28 + 11 files changed, 1056 insertions(+), 20 deletions(-) create mode 100644 packages/cashc/src/imports.ts create mode 100644 packages/cashc/src/semantic/EnsureContainerSemanticsTraversal.ts diff --git a/packages/cashc/src/Errors.ts b/packages/cashc/src/Errors.ts index 6a809aa1..23b3e2f0 100644 --- a/packages/cashc/src/Errors.ts +++ b/packages/cashc/src/Errors.ts @@ -59,6 +59,27 @@ export class ParseError extends Error { } } +export class ImportResolutionError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} + +export class InvalidImportDirectiveError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} + +export class InvalidLibraryImportError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} + export class UndefinedReferenceError extends CashScriptError { constructor( public node: IdentifierNode, @@ -110,6 +131,30 @@ export class EmptyContractError extends CashScriptError { } } +export class LibraryParameterError extends CashScriptError { + constructor( + public node: ContractNode, + ) { + super(node, `Library ${node.name} cannot declare constructor parameters`); + } +} + +export class LibraryPublicFunctionError extends CashScriptError { + constructor( + public node: FunctionDefinitionNode, + ) { + super(node, `Library functions cannot be public: ${node.name}`); + } +} + +export class NonSpendableCompilationError extends CashScriptError { + constructor( + public node: ContractNode, + ) { + super(node, `Library ${node.name} cannot be compiled as a spendable artifact; import it into a contract instead`); + } +} + export class EmptyFunctionError extends CashScriptError { constructor( public node: FunctionDefinitionNode, diff --git a/packages/cashc/src/ast/AST.ts b/packages/cashc/src/ast/AST.ts index 3eb7be3a..e845f6d7 100644 --- a/packages/cashc/src/ast/AST.ts +++ b/packages/cashc/src/ast/AST.ts @@ -21,6 +21,8 @@ export interface Typed { type: Type; } +export type ContainerKind = 'contract' | 'library'; + export class SourceFileNode extends Node { constructor( public contract: ContractNode, @@ -40,6 +42,7 @@ export class ContractNode extends Node implements Named { public name: string, public parameters: ParameterNode[], public functions: FunctionDefinitionNode[], + public kind: ContainerKind = 'contract', ) { super(); } diff --git a/packages/cashc/src/ast/AstBuilder.ts b/packages/cashc/src/ast/AstBuilder.ts index 07bd2d33..8ade7bfd 100644 --- a/packages/cashc/src/ast/AstBuilder.ts +++ b/packages/cashc/src/ast/AstBuilder.ts @@ -98,6 +98,7 @@ export default class AstBuilder constructor( private tree: ParseTree, private functionVisibilities: FunctionVisibility[] = [], + private containerKind: 'contract' | 'library' = 'contract', ) { super(); } @@ -141,7 +142,7 @@ export default class AstBuilder const name = ctx.Identifier().getText(); const parameters = ctx.parameterList().parameter_list().map((p) => this.visit(p) as ParameterNode); const functions = ctx.functionDefinition_list().map((f) => this.visit(f) as FunctionDefinitionNode); - const contract = new ContractNode(name, parameters, functions); + const contract = new ContractNode(name, parameters, functions, this.containerKind); contract.location = Location.fromCtx(ctx); return contract; } diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index 3c8c84a7..90fcfb9c 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -2,6 +2,7 @@ import { CharStream, CommonTokenStream } from 'antlr4'; import { binToHex } from '@bitauth/libauth'; import { Artifact, CompilerOptions, generateSourceMap, generateSourceTags, optimiseBytecode, optimiseBytecodeOld, scriptToAsm, scriptToBytecode, sourceMapToLocationData } from '@cashscript/utils'; import fs, { PathLike } from 'fs'; +import { fileURLToPath } from 'url'; import { generateArtifact } from './artifact/Artifact.js'; import { Ast } from './ast/AST.js'; import AstBuilder from './ast/AstBuilder.js'; @@ -13,21 +14,39 @@ import SymbolTableTraversal from './semantic/SymbolTableTraversal.js'; import TypeCheckTraversal from './semantic/TypeCheckTraversal.js'; import EnsureFinalRequireTraversal from './semantic/EnsureFinalRequireTraversal.js'; import EnsureInvokedFunctionsSafeTraversal from './semantic/EnsureInvokedFunctionsSafeTraversal.js'; +import EnsureContainerSemanticsTraversal from './semantic/EnsureContainerSemanticsTraversal.js'; import { FunctionVisibility } from './ast/Globals.js'; +import { NonSpendableCompilationError, ParseError } from './Errors.js'; +import { ImportResolver, preprocessImports } from './imports.js'; export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { enforceFunctionParameterTypes: true, }; +export interface CompileOptions extends CompilerOptions { + sourcePath?: string; + resolveImport?: ImportResolver; +} + +type ContainerKind = 'contract' | 'library'; + +type PreprocessedContainerResult = { + code: string; + containerKind: ContainerKind; +}; + type PreprocessedVisibilityResult = { code: string; + containerKind: ContainerKind; functionVisibilities: FunctionVisibility[]; omittedPublicFunctions: Array<{ name: string; line: number; column: number }>; }; -export function compileString(code: string, compilerOptions: CompilerOptions = {}): Artifact { - const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptions }; - const preprocessed = preprocessFunctionVisibility(code); +export function compileString(code: string, compilerOptions: CompileOptions = {}): Artifact { + const { sourcePath, resolveImport, ...serialisableCompilerOptions } = compilerOptions; + const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...serialisableCompilerOptions }; + const importedCode = preprocessImports(code, { sourcePath, resolveImport }); + const preprocessed = preprocessFunctionVisibility(preprocessTopLevelContainer(importedCode.code)); emitVisibilityWarnings(preprocessed.omittedPublicFunctions); // Lexing + parsing @@ -36,9 +55,14 @@ export function compileString(code: string, compilerOptions: CompilerOptions = { // Semantic analysis ast = ast.accept(new SymbolTableTraversal()) as Ast; ast = ast.accept(new TypeCheckTraversal()) as Ast; + ast = ast.accept(new EnsureContainerSemanticsTraversal()) as Ast; ast = ast.accept(new EnsureFinalRequireTraversal()) as Ast; ast = ast.accept(new EnsureInvokedFunctionsSafeTraversal()) as Ast; + if (ast.contract.kind === 'library') { + throw new NonSpendableCompilationError(ast.contract); + } + // Code generation const traversal = new GenerateTargetTraversal(mergedCompilerOptions); ast = ast.accept(traversal) as Ast; @@ -84,16 +108,17 @@ export function compileString(code: string, compilerOptions: CompilerOptions = { ...(sourceTags ? { sourceTags } : {}), }; - return generateArtifact(ast, optimisationResult.script, code, debug, mergedCompilerOptions); + return generateArtifact(ast, optimisationResult.script, importedCode.code, debug, mergedCompilerOptions); } -export function compileFile(codeFile: PathLike, compilerOptions: CompilerOptions = {}): Artifact { +export function compileFile(codeFile: PathLike, compilerOptions: CompileOptions = {}): Artifact { const code = fs.readFileSync(codeFile, { encoding: 'utf-8' }); - return compileString(code, compilerOptions); + return compileString(code, { ...compilerOptions, sourcePath: normaliseSourcePath(codeFile) }); } -export function parseCode(code: string): Ast { - const preprocessed = preprocessFunctionVisibility(code); +export function parseCode(code: string, compilerOptions: Pick = {}): Ast { + const importedCode = preprocessImports(code, compilerOptions); + const preprocessed = preprocessFunctionVisibility(preprocessTopLevelContainer(importedCode.code)); return parseCodeFromPreprocessed(preprocessed); } @@ -112,12 +137,46 @@ function parseCodeFromPreprocessed(preprocessed: PreprocessedVisibilityResult): const parseTree = parser.sourceFile(); // AST building - const ast = new AstBuilder(parseTree, preprocessed.functionVisibilities).build() as Ast; + const ast = new AstBuilder(parseTree, preprocessed.functionVisibilities, preprocessed.containerKind).build() as Ast; return ast; } -function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResult { +function preprocessTopLevelContainer(code: string): PreprocessedContainerResult { + const tokens = getVisibleTokens(code); + let cursor = 0; + + while (cursor < tokens.length && tokens[cursor].text === 'pragma') { + cursor = advanceToSemicolon(tokens, cursor + 1); + } + + const rootToken = tokens[cursor]; + if (!rootToken) { + throw new ParseError('Expected a root contract or library definition'); + } + + if (rootToken.text === 'contract') { + return { code, containerKind: 'contract' }; + } + + if (rootToken.text !== 'library') { + throw new ParseError(`Expected a root contract or library definition, found '${rootToken.text}'`); + } + + const nameToken = tokens[cursor + 1]; + const nextToken = tokens[cursor + 2]; + if (!nameToken?.text || nextToken?.text !== '{') { + throw new ParseError('Library definitions must use the form library Name { ... }'); + } + + return { + code: `${code.slice(0, rootToken.start)}contract${code.slice(rootToken.stop + 1, nextToken.start)}()${code.slice(nextToken.start)}`, + containerKind: 'library', + }; +} + +function preprocessFunctionVisibility(preprocessedContainer: PreprocessedContainerResult): PreprocessedVisibilityResult { + const { code, containerKind } = preprocessedContainer; const inputStream = new CharStream(code); const lexer = new CashScriptLexer(inputStream); const tokenStream = new CommonTokenStream(lexer); @@ -152,15 +211,18 @@ function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResul continue; } - functionVisibilities.push(FunctionVisibility.PUBLIC); - omittedPublicFunctions.push({ - name: functionNameToken.text!, - line: functionNameToken.line, - column: functionNameToken.column, - }); + const defaultVisibility = containerKind === 'library' ? FunctionVisibility.INTERNAL : FunctionVisibility.PUBLIC; + functionVisibilities.push(defaultVisibility); + if (defaultVisibility === FunctionVisibility.PUBLIC) { + omittedPublicFunctions.push({ + name: functionNameToken.text!, + line: functionNameToken.line, + column: functionNameToken.column, + }); + } } - return { code: mutableCode.join(''), functionVisibilities, omittedPublicFunctions }; + return { code: mutableCode.join(''), containerKind, functionVisibilities, omittedPublicFunctions }; } function emitVisibilityWarnings(omittedPublicFunctions: Array<{ name: string; line: number; column: number }>): void { @@ -174,3 +236,37 @@ function emitVisibilityWarnings(omittedPublicFunctions: Array<{ name: string; li `Warning: ${omittedPublicFunctions.length} function(s) omit visibility and default to public: ${summary}.`, ); } + +function getVisibleTokens(code: string) { + const inputStream = new CharStream(code); + const lexer = new CashScriptLexer(inputStream); + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + return tokenStream.tokens.filter((token) => token.channel === 0 && token.type !== -1); +} + +function advanceToSemicolon(tokens: ReturnType, cursor: number): number { + while (cursor < tokens.length && tokens[cursor].text !== ';') { + cursor += 1; + } + + if (tokens[cursor]?.text !== ';') { + throw new ParseError('Expected semicolon while parsing pragmas'); + } + + return cursor + 1; +} + +function normaliseSourcePath(codeFile: PathLike): string { + if (codeFile instanceof URL) { + return fileURLToPath(codeFile); + } + + const sourcePath = String(codeFile); + if (sourcePath.startsWith('file://')) { + return fileURLToPath(sourcePath); + } + + return sourcePath; +} diff --git a/packages/cashc/src/imports.ts b/packages/cashc/src/imports.ts new file mode 100644 index 00000000..e8704ab3 --- /dev/null +++ b/packages/cashc/src/imports.ts @@ -0,0 +1,662 @@ +import { CharStream, CommonTokenStream, Token } from 'antlr4'; +import fs from 'fs'; +import path from 'path'; +import semver from 'semver'; +import { fileURLToPath } from 'url'; +import { Class, FunctionVisibility, GLOBAL_SYMBOL_TABLE } from './ast/Globals.js'; +import { + ImportResolutionError, + InvalidImportDirectiveError, + InvalidLibraryImportError, + VersionError, +} from './Errors.js'; +import CashScriptLexer from './grammar/CashScriptLexer.js'; +import { version } from './index.js'; + +export interface ImportResolverResult { + source: string; + path?: string; +} + +export type ImportResolver = (specifier: string, from?: string) => ImportResolverResult | string; + +export interface ImportPreprocessOptions { + sourcePath?: string; + resolveImport?: ImportResolver; +} + +export interface ImportPreprocessResult { + code: string; +} + +interface ImportDirective { + specifier: string; + alias: string; + start: number; + stop: number; +} + +interface LibraryDefinition { + name: string; + body: string; +} + +interface LibraryTransformResult { + body: string; + functions: Set; +} + +interface ParsedLibraryFile { + library: LibraryDefinition; + pragmaConstraints: string[]; +} + +interface ResolvedImport extends ImportResolverResult { + specifier: string; +} + +interface Replacement { + start: number; + stop: number; + text: string; +} + +const IDENTIFIER_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/; +const BUILT_IN_STATEMENTS = new Set(['require']); +const GLOBAL_FUNCTIONS = new Set( + Array.from(GLOBAL_SYMBOL_TABLE.symbols.values()) + .filter((symbol) => symbol.symbolType === 'function') + .map((symbol) => symbol.name), +); +const GLOBAL_CLASSES = new Set(Object.values(Class)); + +export function preprocessImports(code: string, options: ImportPreprocessOptions = {}): ImportPreprocessResult { + const importDirectives = parseTopLevelImports(code); + if (importDirectives.length === 0) { + return { code }; + } + + const mutableCode = code.split(''); + const transformedLibraries: string[] = []; + const rootFunctionNames = collectFunctionNames(code); + const usedAliases = new Set(); + const usedMangledNames = new Set(rootFunctionNames); + const transformedLibrariesByAlias = new Map(); + + for (const directive of importDirectives) { + if (usedAliases.has(directive.alias)) { + throw new InvalidImportDirectiveError(`Duplicate import alias '${directive.alias}' is not allowed.`); + } + usedAliases.add(directive.alias); + + blankRangePreservingNewlines(mutableCode, directive.start, directive.stop); + + const transformedLibrary = preprocessImportedLibrary( + directive, + options, + usedMangledNames, + options.sourcePath, + ); + transformedLibrariesByAlias.set(directive.alias, transformedLibrary); + transformedLibraries.push( + `\n // Imported from ${JSON.stringify(directive.specifier)} as ${directive.alias}\n${indentLibraryBody(transformedLibrary.body)}\n`, + ); + } + + let flattenedCode = mutableCode.join(''); + transformedLibrariesByAlias.forEach((library, alias) => { + flattenedCode = rewriteNamespacedCalls(flattenedCode, alias, library.functions); + }); + + const contractCloseIndex = findRootCloseIndex(flattenedCode); + const injectedCode = transformedLibraries.join('\n'); + + return { + code: `${flattenedCode.slice(0, contractCloseIndex)}${injectedCode}${flattenedCode.slice(contractCloseIndex)}`, + }; +} + +function preprocessImportedLibrary( + directive: ImportDirective, + options: ImportPreprocessOptions, + usedMangledNames: Set, + fromPath?: string, +): LibraryTransformResult { + const resolvedImport = resolveImportSource(directive.specifier, options, fromPath); + const parsedLibrary = parseLibraryFileWithPragmas(resolvedImport.source, resolvedImport.path ?? directive.specifier); + validateLibraryPragmas(parsedLibrary.pragmaConstraints); + + return transformLibrary(parsedLibrary.library, directive.alias, usedMangledNames); +} + +function resolveImportSource(specifier: string, options: ImportPreprocessOptions, fromPath?: string): ResolvedImport { + if (options.resolveImport) { + const result = options.resolveImport(specifier, fromPath ?? options.sourcePath); + if (typeof result === 'string') { + return { source: result, specifier }; + } + + return { ...result, specifier }; + } + + const resolutionBasePath = fromPath ?? options.sourcePath; + if (!resolutionBasePath) { + throw new ImportResolutionError( + `Cannot resolve import '${specifier}' without a sourcePath or resolveImport callback.`, + ); + } + + if (!specifier.startsWith('./') && !specifier.startsWith('../')) { + throw new InvalidImportDirectiveError( + `Only relative import specifiers are supported. Found '${specifier}'.`, + ); + } + + const resolvedPath = path.resolve(path.dirname(normaliseFilesystemPath(resolutionBasePath)), specifier); + if (!fs.existsSync(resolvedPath)) { + throw new ImportResolutionError(`Unable to resolve import '${specifier}' from '${resolutionBasePath}'.`); + } + + return { + specifier, + source: fs.readFileSync(resolvedPath, { encoding: 'utf-8' }), + path: resolvedPath, + }; +} + +function parseTopLevelImports(code: string): ImportDirective[] { + const tokens = getVisibleTokens(code); + const imports: ImportDirective[] = []; + let cursor = 0; + + while (cursor < tokens.length) { + const token = tokens[cursor]; + + if (token.text === 'pragma') { + cursor = advanceToSemicolon(tokens, cursor + 1); + continue; + } + + if (token.text === 'import') { + const specifierToken = tokens[cursor + 1]; + const asToken = tokens[cursor + 2]; + const aliasToken = tokens[cursor + 3]; + const semicolonToken = tokens[cursor + 4]; + + if (!specifierToken?.text || !isStringLiteral(specifierToken.text) || asToken?.text !== 'as' + || !aliasToken?.text?.match(IDENTIFIER_PATTERN) || semicolonToken?.text !== ';') { + throw new InvalidImportDirectiveError( + 'Import directives must use the form import "./helpers.cash" as Helpers;', + ); + } + + imports.push({ + specifier: parseStringLiteral(specifierToken.text), + alias: aliasToken.text, + start: token.start, + stop: semicolonToken.stop, + }); + + cursor += 5; + continue; + } + + if (token.text === 'contract' || token.text === 'library') { + break; + } + + break; + } + + const misplacedImport = tokens.slice(cursor).find((token) => token.text === 'import'); + if (misplacedImport) { + throw new InvalidImportDirectiveError('Import directives must appear before the root contract or library definition.'); + } + + return imports; +} + +function parseLibraryFileWithPragmas(code: string, sourceLabel: string): ParsedLibraryFile { + const tokens = getVisibleTokens(code); + + let cursor = 0; + const pragmaConstraints: string[] = []; + while (cursor < tokens.length && tokens[cursor].text === 'pragma') { + pragmaConstraints.push(...readPragmaConstraints(tokens, cursor, sourceLabel)); + cursor = advanceToSemicolon(tokens, cursor + 1); + } + + const nestedImports = parseTopLevelImports(code); + if (nestedImports.length > 0) { + throw new InvalidLibraryImportError( + `Imported library '${sourceLabel}' may not import other libraries in the current MVP.`, + ); + } + + const libraryToken = tokens[cursor]; + const nameToken = tokens[cursor + 1]; + const openBraceToken = tokens[cursor + 2]; + + if (libraryToken?.text !== 'library' || !nameToken?.text?.match(IDENTIFIER_PATTERN) || openBraceToken?.text !== '{') { + throw new InvalidLibraryImportError( + `Imported file '${sourceLabel}' must contain exactly one top-level library.`, + ); + } + + const closeBraceIndex = findMatchingBrace(tokens, cursor + 2); + const closeBraceToken = tokens[closeBraceIndex]; + const trailingTokens = tokens.slice(closeBraceIndex + 1); + if (trailingTokens.length !== 0) { + throw new InvalidLibraryImportError( + `Imported file '${sourceLabel}' may only contain a single library definition.`, + ); + } + + return { + library: { + name: nameToken.text, + body: code.slice(openBraceToken.stop + 1, closeBraceToken.start), + }, + pragmaConstraints, + }; +} + +function transformLibrary( + library: LibraryDefinition, + alias: string, + usedMangledNames: Set, +): LibraryTransformResult { + const tokens = getVisibleTokens(library.body); + const functionNames = collectFunctionDefinitions(tokens); + + if (functionNames.length === 0) { + throw new InvalidLibraryImportError(`Library '${library.name}' does not define any functions.`); + } + + const localFunctions = new Set(functionNames.map((definition) => definition.name)); + const mangledNames = new Map(); + const replacements: Replacement[] = []; + + functionNames.forEach(({ name, token }) => { + const mangledName = `${alias}_${name}`; + if (usedMangledNames.has(mangledName)) { + throw new InvalidLibraryImportError( + `Imported function '${alias}.${name}' conflicts with an existing function named '${mangledName}'.`, + ); + } + + usedMangledNames.add(mangledName); + mangledNames.set(name, mangledName); + replacements.push({ start: token.start, stop: token.stop, text: mangledName }); + }); + + validateLibraryCalls(tokens, localFunctions, library.name); + replacements.push(...collectLocalFunctionCallReplacements(tokens, mangledNames)); + + return { + body: applyReplacements(library.body, replacements).trim(), + functions: new Set(functionNames.map((definition) => definition.name)), + }; +} + +function collectFunctionDefinitions(tokens: Token[]): Array<{ name: string; token: Token }> { + const definitions: Array<{ name: string; token: Token }> = []; + + for (let index = 0; index < tokens.length; index += 1) { + if (tokens[index].text !== 'function') continue; + + const nameToken = tokens[index + 1]; + const openParenToken = tokens[index + 2]; + if (!nameToken?.text?.match(IDENTIFIER_PATTERN) || openParenToken?.text !== '(') { + throw new InvalidLibraryImportError('Invalid function definition in imported library.'); + } + + let cursor = index + 3; + let depth = 1; + while (cursor < tokens.length && depth > 0) { + if (tokens[cursor].text === '(') depth += 1; + if (tokens[cursor].text === ')') depth -= 1; + cursor += 1; + } + + const visibilityToken = tokens[cursor]; + if (visibilityToken?.text !== FunctionVisibility.INTERNAL) { + throw new InvalidLibraryImportError( + `Imported library functions must be declared explicit internal. Offending function: '${nameToken.text}'.`, + ); + } + + definitions.push({ name: nameToken.text, token: nameToken }); + } + + return definitions; +} + +function validateLibraryCalls(tokens: Token[], localFunctions: Set, libraryName: string): void { + for (let index = 0; index < tokens.length - 1; index += 1) { + const token = tokens[index]; + const nextToken = tokens[index + 1]; + const previousToken = tokens[index - 1]; + + if (!token.text?.match(IDENTIFIER_PATTERN) || nextToken?.text !== '(') continue; + if (previousToken?.text === 'function' || previousToken?.text === 'new' || previousToken?.text === '.') continue; + + if ( + localFunctions.has(token.text) + || GLOBAL_FUNCTIONS.has(token.text) + || GLOBAL_CLASSES.has(token.text as Class) + || BUILT_IN_STATEMENTS.has(token.text) + ) { + continue; + } + + throw new InvalidLibraryImportError( + `Library '${libraryName}' references non-library function '${token.text}'. Imported libraries may only call their own functions or built-ins.`, + ); + } +} + +function collectLocalFunctionCallReplacements(tokens: Token[], mangledNames: Map): Replacement[] { + const replacements: Replacement[] = []; + + for (let index = 0; index < tokens.length - 1; index += 1) { + const token = tokens[index]; + const nextToken = tokens[index + 1]; + const previousToken = tokens[index - 1]; + + if (!token.text?.match(IDENTIFIER_PATTERN) || nextToken?.text !== '(') continue; + if (previousToken?.text === 'function' || previousToken?.text === 'new' || previousToken?.text === '.') continue; + + const mangledName = mangledNames.get(token.text); + if (!mangledName) continue; + + replacements.push({ start: token.start, stop: token.stop, text: mangledName }); + } + + return replacements; +} + +function rewriteNamespacedCalls( + code: string, + alias: string, + functions: Set, + replacementPrefix: string = alias, +): string { + let output = ''; + + for (let index = 0; index < code.length;) { + if (startsWithLineComment(code, index)) { + const commentEnd = findLineCommentEnd(code, index); + output += code.slice(index, commentEnd); + index = commentEnd; + continue; + } + + if (startsWithBlockComment(code, index)) { + const commentEnd = findBlockCommentEnd(code, index); + output += code.slice(index, commentEnd); + index = commentEnd; + continue; + } + + if (code[index] === '"' || code[index] === '\'') { + const stringEnd = findStringEnd(code, index); + output += code.slice(index, stringEnd); + index = stringEnd; + continue; + } + + const rewrite = tryRewriteNamespacedCall(code, index, alias, functions, replacementPrefix); + if (rewrite) { + output += rewrite.replacement; + index = rewrite.nextIndex; + continue; + } + + output += code[index]; + index += 1; + } + + return output; +} + +function collectFunctionNames(code: string): Set { + const names = new Set(); + const tokens = getVisibleTokens(code); + + for (let index = 0; index < tokens.length - 2; index += 1) { + if (tokens[index].text !== 'function') continue; + + const nameToken = tokens[index + 1]; + const openParenToken = tokens[index + 2]; + if (nameToken?.text?.match(IDENTIFIER_PATTERN) && openParenToken?.text === '(') { + names.add(nameToken.text); + } + } + + return names; +} + +function findRootCloseIndex(code: string): number { + const tokens = getVisibleTokens(code); + const rootIndex = tokens.findIndex((token) => token.text === 'contract' || token.text === 'library'); + if (rootIndex === -1) { + throw new InvalidImportDirectiveError('Imports require a root contract or library definition.'); + } + + const openBraceIndex = tokens.findIndex((token, index) => index > rootIndex && token.text === '{'); + if (openBraceIndex === -1) { + throw new InvalidImportDirectiveError('Unable to locate the root body.'); + } + + return tokens[findMatchingBrace(tokens, openBraceIndex)].start; +} + +function indentLibraryBody(body: string): string { + return body + .split('\n') + .map((line) => (line.length === 0 ? line : ` ${line}`)) + .join('\n'); +} + +function findMatchingBrace(tokens: Token[], openBraceIndex: number): number { + let depth = 0; + for (let index = openBraceIndex; index < tokens.length; index += 1) { + if (tokens[index].text === '{') depth += 1; + if (tokens[index].text === '}') { + depth -= 1; + if (depth === 0) return index; + } + } + + throw new InvalidImportDirectiveError('Could not match braces while preprocessing imports.'); +} + +function advanceToSemicolon(tokens: Token[], cursor: number): number { + while (cursor < tokens.length && tokens[cursor].text !== ';') { + cursor += 1; + } + + if (tokens[cursor]?.text !== ';') { + throw new InvalidImportDirectiveError('Expected semicolon while preprocessing imports.'); + } + + return cursor + 1; +} + +function blankRangePreservingNewlines(chars: string[], start: number, stop: number): void { + for (let index = start; index <= stop; index += 1) { + if (chars[index] !== '\n' && chars[index] !== '\r') { + chars[index] = ' '; + } + } +} + +function applyReplacements(code: string, replacements: Replacement[]): string { + const orderedReplacements = [...replacements].sort((left, right) => right.start - left.start); + let output = code; + + orderedReplacements.forEach(({ start, stop, text }) => { + output = `${output.slice(0, start)}${text}${output.slice(stop + 1)}`; + }); + + return output; +} + +function getVisibleTokens(code: string): Token[] { + const inputStream = new CharStream(code); + const lexer = new CashScriptLexer(inputStream); + lexer.removeErrorListeners(); + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + return tokenStream.tokens.filter((token) => token.channel === 0 && token.type !== -1); +} + +function tryRewriteNamespacedCall( + code: string, + index: number, + alias: string, + functions: Set, + replacementPrefix: string, +): { replacement: string; nextIndex: number } | undefined { + if (!code.startsWith(alias, index)) return undefined; + if (!isIdentifierBoundary(code[index - 1])) return undefined; + if (!isIdentifierBoundary(code[index + alias.length])) return undefined; + + let cursor = skipWhitespace(code, index + alias.length); + if (code[cursor] !== '.') return undefined; + + cursor = skipWhitespace(code, cursor + 1); + const functionNameMatch = /^[a-zA-Z][a-zA-Z0-9_]*/.exec(code.slice(cursor)); + if (!functionNameMatch) { + throw new InvalidImportDirectiveError(`Invalid imported function call '${alias}.' in contract source.`); + } + + const functionName = functionNameMatch[0]; + if (!functions.has(functionName)) { + throw new InvalidImportDirectiveError(`Imported library '${alias}' has no function named '${functionName}'.`); + } + + cursor = skipWhitespace(code, cursor + functionName.length); + if (code[cursor] !== '(') { + throw new InvalidImportDirectiveError( + `Imported library function calls must use the form '${alias}.${functionName}(...)'.`, + ); + } + + return { + replacement: `${replacementPrefix}_${functionName}`, + nextIndex: cursor, + }; +} + +function skipWhitespace(code: string, index: number): number { + let cursor = index; + while (cursor < code.length && /\s/.test(code[cursor])) { + cursor += 1; + } + return cursor; +} + +function startsWithLineComment(code: string, index: number): boolean { + return code[index] === '/' && code[index + 1] === '/'; +} + +function startsWithBlockComment(code: string, index: number): boolean { + return code[index] === '/' && code[index + 1] === '*'; +} + +function findLineCommentEnd(code: string, index: number): number { + let cursor = index; + while (cursor < code.length && code[cursor] !== '\n') { + cursor += 1; + } + return cursor; +} + +function findBlockCommentEnd(code: string, index: number): number { + const closingIndex = code.indexOf('*/', index + 2); + return closingIndex === -1 ? code.length : closingIndex + 2; +} + +function findStringEnd(code: string, index: number): number { + const quote = code[index]; + let cursor = index + 1; + + while (cursor < code.length) { + if (code[cursor] === '\\') { + cursor += 2; + continue; + } + + if (code[cursor] === quote) { + return cursor + 1; + } + + cursor += 1; + } + + return code.length; +} + +function isIdentifierBoundary(character: string | undefined): boolean { + return !character || !/[a-zA-Z0-9_]/.test(character); +} + +function isStringLiteral(text: string): boolean { + return (text.startsWith('"') && text.endsWith('"')) || (text.startsWith('\'') && text.endsWith('\'')); +} + +function parseStringLiteral(text: string): string { + return text.slice(1, -1); +} + +function readPragmaConstraints(tokens: Token[], pragmaIndex: number, sourceLabel: string): string[] { + const pragmaName = tokens[pragmaIndex + 1]; + if (pragmaName?.text !== 'cashscript') { + throw new InvalidLibraryImportError( + `Imported library '${sourceLabel}' uses unsupported pragma '${pragmaName?.text ?? ''}'.`, + ); + } + + const constraints: string[] = []; + let cursor = pragmaIndex + 2; + while (cursor < tokens.length && tokens[cursor].text !== ';') { + const current = tokens[cursor]; + const next = tokens[cursor + 1]; + + if (!current?.text) break; + + if (/^[\^~><=]/.test(current.text) && next?.text && semver.valid(next.text)) { + constraints.push(`${current.text}${next.text}`); + cursor += 2; + continue; + } + + if (semver.valid(current.text)) { + constraints.push(current.text); + cursor += 1; + continue; + } + + throw new InvalidLibraryImportError( + `Imported library '${sourceLabel}' has an invalid pragma constraint near '${current.text}'.`, + ); + } + + return constraints; +} + +function validateLibraryPragmas(pragmaConstraints: string[]): void { + const actualVersion = version.replace(/-.*/g, ''); + pragmaConstraints.forEach((constraint) => { + if (!semver.satisfies(actualVersion, constraint)) { + throw new VersionError(actualVersion, constraint); + } + }); +} + +function normaliseFilesystemPath(sourcePath: string): string { + return sourcePath.startsWith('file://') ? fileURLToPath(sourcePath) : sourcePath; +} diff --git a/packages/cashc/src/index.ts b/packages/cashc/src/index.ts index 743fb9ea..55265484 100644 --- a/packages/cashc/src/index.ts +++ b/packages/cashc/src/index.ts @@ -1,5 +1,7 @@ export * from './Errors.js'; export * as utils from '@cashscript/utils'; +export type { CompileOptions } from './compiler.js'; +export type { ImportResolver, ImportResolverResult } from './imports.js'; export { compileFile, compileString, parseCode } from './compiler.js'; export const version = '0.13.0-next.6'; diff --git a/packages/cashc/src/print/OutputSourceCodeTraversal.ts b/packages/cashc/src/print/OutputSourceCodeTraversal.ts index 06f643e8..7347cd16 100644 --- a/packages/cashc/src/print/OutputSourceCodeTraversal.ts +++ b/packages/cashc/src/print/OutputSourceCodeTraversal.ts @@ -62,7 +62,7 @@ export default class OutputSourceCodeTraversal extends AstTraversal { } visitContract(node: ContractNode): Node { - this.addOutput(`contract ${node.name}(`, true); + this.addOutput(`${node.kind} ${node.name}(`, true); node.parameters = this.visitCommaList(node.parameters) as ParameterNode[]; this.addOutput(') {'); this.outputSymbolTable(node.symbolTable); diff --git a/packages/cashc/src/semantic/EnsureContainerSemanticsTraversal.ts b/packages/cashc/src/semantic/EnsureContainerSemanticsTraversal.ts new file mode 100644 index 00000000..9661e4a7 --- /dev/null +++ b/packages/cashc/src/semantic/EnsureContainerSemanticsTraversal.ts @@ -0,0 +1,34 @@ +import { + ContractNode, + FunctionDefinitionNode, + ParameterNode, + Node, +} from '../ast/AST.js'; +import AstTraversal from '../ast/AstTraversal.js'; +import { FunctionVisibility } from '../ast/Globals.js'; +import { + LibraryParameterError, + LibraryPublicFunctionError, +} from '../Errors.js'; + +export default class EnsureContainerSemanticsTraversal extends AstTraversal { + visitContract(node: ContractNode): Node { + node.parameters = this.visitList(node.parameters) as ParameterNode[]; + node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; + + if (node.kind !== 'library') { + return node; + } + + if (node.parameters.length > 0) { + throw new LibraryParameterError(node); + } + + const publicFunction = node.functions.find((func) => func.visibility === FunctionVisibility.PUBLIC); + if (publicFunction) { + throw new LibraryPublicFunctionError(publicFunction); + } + + return node; + } +} diff --git a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts index 0254db6d..9114e053 100644 --- a/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts +++ b/packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts @@ -20,7 +20,7 @@ export default class EnsureFinalRequireTraversal extends AstTraversal { node.parameters = this.visitList(node.parameters) as ParameterNode[]; node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; - if (getPublicFunctions(node.functions).length === 0) { + if (node.kind !== 'library' && getPublicFunctions(node.functions).length === 0) { throw new EmptyContractError(node); } diff --git a/packages/cashc/test/compiler/compiler.test.ts b/packages/cashc/test/compiler/compiler.test.ts index ee9daeeb..81dd9a23 100644 --- a/packages/cashc/test/compiler/compiler.test.ts +++ b/packages/cashc/test/compiler/compiler.test.ts @@ -172,4 +172,169 @@ contract Test() { warnSpy.mockRestore(); }); }); + + describe('Libraries', () => { + it('should parse top-level libraries as non-spendable helper containers', () => { + const ast = parseCode(` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`); + + expect(ast.contract.kind).toBe('library'); + expect(ast.contract.parameters).toEqual([]); + expect(ast.contract.functions[0]?.visibility).toBe(FunctionVisibility.INTERNAL); + }); + + it('should reject compiling a top-level library to an artifact', () => { + expect(() => compileString(` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`)).toThrow(Errors.NonSpendableCompilationError); + }); + + it('should reject public functions inside a library', () => { + expect(() => compileString(` +library BadHelpers { + function isEven(int value) public { + require(value % 2 == 0); + } +} +`)).toThrow(Errors.LibraryPublicFunctionError); + }); + + it('should compile contracts that import helper libraries', () => { + const artifact = compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + expect(specifier).toBe('./math.cash'); + return ` +library MathHelpers { + function isEven(int value) internal { + require(value % 2 == 0); + } +} +`; + }, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + }); + + it('should reject importing a spendable contract as a helper library', () => { + expect(() => compileString(` +import "./other.cash" as Other; + +contract UsesLibrary() { + function spend(int value) public { + require(Other.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +contract Other() { + function isEven(int value) public { + require(value % 2 == 0); + } +} +`, + })).toThrow(Errors.InvalidLibraryImportError); + }); + + it('should reject imported library functions that omit explicit internal visibility', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`, + })).toThrow(Errors.InvalidLibraryImportError); + }); + + it('should reject imported libraries that reference non-library local functions', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) internal { + require(check(value)); + } +} +`, + })).toThrow(Errors.InvalidLibraryImportError); + }); + + it('should reject nested library imports in the current MVP', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + if (specifier === './math.cash') { + return { + path: '/contracts/math.cash', + source: ` +import "./core.cash" as Core; + +library MathHelpers { + function isEven(int value) internal { + require(Core.isZero(value % 2)); + } +} +`, + }; + } + + return ` +library CoreHelpers { + function isZero(int value) internal { + require(value == 0); + } +} +`; + }, + })).toThrow(Errors.InvalidLibraryImportError); + }); + }); }); diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index be7df20d..040b2ae1 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -271,4 +271,32 @@ contract DebugMetadata() { end: { line: 9, column: 48 }, }); }); + + it('should lower imported library helpers to BCH function opcodes without exposing them in the ABI', () => { + const artifact = compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) internal { + require(value % 2 == 0); + } +} +`, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + expect(artifact.bytecode).toContain('OP_DEFINE'); + expect(artifact.bytecode).toContain('OP_INVOKE'); + expect(artifact.compiler.target).toBe('BCH_2026_05'); + }); }); From aaa92bec93952b855fcbf5bdfcca676a797a9e05 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 2 Apr 2026 20:39:38 -0400 Subject: [PATCH 6/9] Add parser-native library grammar support --- packages/cashc/src/ast/AstBuilder.ts | 23 +- packages/cashc/src/compiler.ts | 72 +- packages/cashc/src/grammar/CashScript.g4 | 11 +- packages/cashc/src/grammar/CashScript.interp | 6 +- packages/cashc/src/grammar/CashScript.tokens | 132 +- .../cashc/src/grammar/CashScriptLexer.interp | 5 +- .../cashc/src/grammar/CashScriptLexer.tokens | 132 +- packages/cashc/src/grammar/CashScriptLexer.ts | 639 ++++---- .../cashc/src/grammar/CashScriptParser.ts | 1435 +++++++++-------- .../cashc/src/grammar/CashScriptVisitor.ts | 16 +- packages/cashc/src/imports.ts | 46 +- packages/cashc/test/compiler/compiler.test.ts | 10 +- 12 files changed, 1374 insertions(+), 1153 deletions(-) diff --git a/packages/cashc/src/ast/AstBuilder.ts b/packages/cashc/src/ast/AstBuilder.ts index 8ade7bfd..26931ebc 100644 --- a/packages/cashc/src/ast/AstBuilder.ts +++ b/packages/cashc/src/ast/AstBuilder.ts @@ -41,6 +41,7 @@ import { import { UnaryOperator, BinaryOperator, NullaryOperator } from './Operator.js'; import type { ContractDefinitionContext, + LibraryDefinitionContext, FunctionDefinitionContext, VariableDefinitionContext, TupleAssignmentContext, @@ -51,6 +52,7 @@ import type { CastContext, LiteralContext, SourceFileContext, + TopLevelDefinitionContext, BlockContext, TimeOpStatementContext, ArrayContext, @@ -98,7 +100,6 @@ export default class AstBuilder constructor( private tree: ParseTree, private functionVisibilities: FunctionVisibility[] = [], - private containerKind: 'contract' | 'library' = 'contract', ) { super(); } @@ -116,12 +117,20 @@ export default class AstBuilder this.processPragma(pragma); }); - const contract = this.visit(ctx.contractDefinition()) as ContractNode; + const contract = this.visit(ctx.topLevelDefinition()) as ContractNode; const sourceFileNode = new SourceFileNode(contract); sourceFileNode.location = Location.fromCtx(ctx); return sourceFileNode; } + visitTopLevelDefinition(ctx: TopLevelDefinitionContext): ContractNode { + if (ctx.contractDefinition()) { + return this.visit(ctx.contractDefinition()) as ContractNode; + } + + return this.visit(ctx.libraryDefinition()!) as ContractNode; + } + processPragma(ctx: PragmaDirectiveContext): void { const pragmaName = getPragmaName(ctx.pragmaName().getText()); if (pragmaName !== PragmaName.CASHSCRIPT) throw new Error(); // Shouldn't happen @@ -142,7 +151,15 @@ export default class AstBuilder const name = ctx.Identifier().getText(); const parameters = ctx.parameterList().parameter_list().map((p) => this.visit(p) as ParameterNode); const functions = ctx.functionDefinition_list().map((f) => this.visit(f) as FunctionDefinitionNode); - const contract = new ContractNode(name, parameters, functions, this.containerKind); + const contract = new ContractNode(name, parameters, functions, 'contract'); + contract.location = Location.fromCtx(ctx); + return contract; + } + + visitLibraryDefinition(ctx: LibraryDefinitionContext): ContractNode { + const name = ctx.Identifier().getText(); + const functions = ctx.functionDefinition_list().map((f) => this.visit(f) as FunctionDefinitionNode); + const contract = new ContractNode(name, [], functions, 'library'); contract.location = Location.fromCtx(ctx); return contract; } diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index 90fcfb9c..9310f3a4 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -30,11 +30,6 @@ export interface CompileOptions extends CompilerOptions { type ContainerKind = 'contract' | 'library'; -type PreprocessedContainerResult = { - code: string; - containerKind: ContainerKind; -}; - type PreprocessedVisibilityResult = { code: string; containerKind: ContainerKind; @@ -46,7 +41,7 @@ export function compileString(code: string, compilerOptions: CompileOptions = {} const { sourcePath, resolveImport, ...serialisableCompilerOptions } = compilerOptions; const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...serialisableCompilerOptions }; const importedCode = preprocessImports(code, { sourcePath, resolveImport }); - const preprocessed = preprocessFunctionVisibility(preprocessTopLevelContainer(importedCode.code)); + const preprocessed = preprocessFunctionVisibility(importedCode.code); emitVisibilityWarnings(preprocessed.omittedPublicFunctions); // Lexing + parsing @@ -118,7 +113,7 @@ export function compileFile(codeFile: PathLike, compilerOptions: CompileOptions export function parseCode(code: string, compilerOptions: Pick = {}): Ast { const importedCode = preprocessImports(code, compilerOptions); - const preprocessed = preprocessFunctionVisibility(preprocessTopLevelContainer(importedCode.code)); + const preprocessed = preprocessFunctionVisibility(importedCode.code); return parseCodeFromPreprocessed(preprocessed); } @@ -137,46 +132,13 @@ function parseCodeFromPreprocessed(preprocessed: PreprocessedVisibilityResult): const parseTree = parser.sourceFile(); // AST building - const ast = new AstBuilder(parseTree, preprocessed.functionVisibilities, preprocessed.containerKind).build() as Ast; + const ast = new AstBuilder(parseTree, preprocessed.functionVisibilities).build() as Ast; return ast; } -function preprocessTopLevelContainer(code: string): PreprocessedContainerResult { - const tokens = getVisibleTokens(code); - let cursor = 0; - - while (cursor < tokens.length && tokens[cursor].text === 'pragma') { - cursor = advanceToSemicolon(tokens, cursor + 1); - } - - const rootToken = tokens[cursor]; - if (!rootToken) { - throw new ParseError('Expected a root contract or library definition'); - } - - if (rootToken.text === 'contract') { - return { code, containerKind: 'contract' }; - } - - if (rootToken.text !== 'library') { - throw new ParseError(`Expected a root contract or library definition, found '${rootToken.text}'`); - } - - const nameToken = tokens[cursor + 1]; - const nextToken = tokens[cursor + 2]; - if (!nameToken?.text || nextToken?.text !== '{') { - throw new ParseError('Library definitions must use the form library Name { ... }'); - } - - return { - code: `${code.slice(0, rootToken.start)}contract${code.slice(rootToken.stop + 1, nextToken.start)}()${code.slice(nextToken.start)}`, - containerKind: 'library', - }; -} - -function preprocessFunctionVisibility(preprocessedContainer: PreprocessedContainerResult): PreprocessedVisibilityResult { - const { code, containerKind } = preprocessedContainer; +function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResult { + const containerKind = getTopLevelContainerKind(code); const inputStream = new CharStream(code); const lexer = new CashScriptLexer(inputStream); const tokenStream = new CommonTokenStream(lexer); @@ -225,6 +187,30 @@ function preprocessFunctionVisibility(preprocessedContainer: PreprocessedContain return { code: mutableCode.join(''), containerKind, functionVisibilities, omittedPublicFunctions }; } +function getTopLevelContainerKind(code: string): ContainerKind { + const tokens = getVisibleTokens(code); + let cursor = 0; + + while (cursor < tokens.length && tokens[cursor].text === 'pragma') { + cursor = advanceToSemicolon(tokens, cursor + 1); + } + + while (cursor < tokens.length && tokens[cursor].text === 'import') { + cursor = advanceToSemicolon(tokens, cursor + 1); + } + + const rootToken = tokens[cursor]; + if (!rootToken) { + throw new ParseError('Expected a root contract or library definition'); + } + + if (rootToken.text === 'contract' || rootToken.text === 'library') { + return rootToken.text; + } + + throw new ParseError(`Expected a root contract or library definition, found '${rootToken.text}'`); +} + function emitVisibilityWarnings(omittedPublicFunctions: Array<{ name: string; line: number; column: number }>): void { if (omittedPublicFunctions.length === 0) return; diff --git a/packages/cashc/src/grammar/CashScript.g4 b/packages/cashc/src/grammar/CashScript.g4 index 19d43d4d..46dcd6a8 100644 --- a/packages/cashc/src/grammar/CashScript.g4 +++ b/packages/cashc/src/grammar/CashScript.g4 @@ -1,7 +1,12 @@ grammar CashScript; sourceFile - : pragmaDirective* contractDefinition EOF + : pragmaDirective* topLevelDefinition EOF + ; + +topLevelDefinition + : contractDefinition + | libraryDefinition ; pragmaDirective @@ -28,6 +33,10 @@ contractDefinition : 'contract' Identifier parameterList '{' functionDefinition* '}' ; +libraryDefinition + : 'library' Identifier '{' functionDefinition* '}' + ; + functionDefinition : 'function' Identifier parameterList '{' statement* '}' ; diff --git a/packages/cashc/src/grammar/CashScript.interp b/packages/cashc/src/grammar/CashScript.interp index 0d3d6c29..8fc7f83f 100644 --- a/packages/cashc/src/grammar/CashScript.interp +++ b/packages/cashc/src/grammar/CashScript.interp @@ -13,6 +13,7 @@ null 'contract' '{' '}' +'library' 'function' '(' ',' @@ -137,6 +138,7 @@ null null null null +null VersionLiteral BooleanLiteral NumberUnit @@ -160,12 +162,14 @@ LINE_COMMENT rule names: sourceFile +topLevelDefinition pragmaDirective pragmaName pragmaValue versionConstraint versionOperator contractDefinition +libraryDefinition functionDefinition parameterList parameter @@ -199,4 +203,4 @@ typeCast atn: -[4, 1, 77, 426, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 5, 0, 76, 8, 0, 10, 0, 12, 0, 79, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 3, 3, 93, 8, 3, 1, 4, 3, 4, 96, 8, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 107, 8, 6, 10, 6, 12, 6, 110, 9, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 119, 8, 7, 10, 7, 12, 7, 122, 9, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 130, 8, 8, 10, 8, 12, 8, 133, 9, 8, 1, 8, 3, 8, 136, 8, 8, 3, 8, 138, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 5, 10, 147, 8, 10, 10, 10, 12, 10, 150, 9, 10, 1, 10, 1, 10, 3, 10, 154, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 160, 8, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 168, 8, 12, 1, 13, 1, 13, 3, 13, 172, 8, 13, 1, 14, 1, 14, 5, 14, 176, 8, 14, 10, 14, 12, 14, 179, 9, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 204, 8, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 213, 8, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 227, 8, 20, 1, 21, 1, 21, 1, 21, 3, 21, 232, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 260, 8, 25, 1, 26, 1, 26, 1, 27, 1, 27, 3, 27, 266, 8, 27, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 272, 8, 28, 10, 28, 12, 28, 275, 9, 28, 1, 28, 3, 28, 278, 8, 28, 3, 28, 280, 8, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 291, 8, 30, 10, 30, 12, 30, 294, 9, 30, 1, 30, 3, 30, 297, 8, 30, 3, 30, 299, 8, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 312, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 338, 8, 31, 10, 31, 12, 31, 341, 9, 31, 1, 31, 3, 31, 344, 8, 31, 3, 31, 346, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 352, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 404, 8, 31, 10, 31, 12, 31, 407, 9, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 3, 33, 416, 8, 33, 1, 34, 1, 34, 3, 34, 420, 8, 34, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 0, 1, 62, 37, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 0, 12, 1, 0, 4, 10, 1, 0, 29, 33, 2, 0, 29, 33, 35, 38, 2, 0, 5, 5, 43, 44, 1, 0, 45, 47, 2, 0, 44, 44, 48, 48, 1, 0, 49, 50, 1, 0, 6, 9, 1, 0, 51, 52, 1, 0, 39, 40, 1, 0, 64, 66, 2, 0, 64, 65, 72, 72, 452, 0, 77, 1, 0, 0, 0, 2, 83, 1, 0, 0, 0, 4, 88, 1, 0, 0, 0, 6, 90, 1, 0, 0, 0, 8, 95, 1, 0, 0, 0, 10, 99, 1, 0, 0, 0, 12, 101, 1, 0, 0, 0, 14, 113, 1, 0, 0, 0, 16, 125, 1, 0, 0, 0, 18, 141, 1, 0, 0, 0, 20, 153, 1, 0, 0, 0, 22, 159, 1, 0, 0, 0, 24, 167, 1, 0, 0, 0, 26, 171, 1, 0, 0, 0, 28, 173, 1, 0, 0, 0, 30, 184, 1, 0, 0, 0, 32, 192, 1, 0, 0, 0, 34, 196, 1, 0, 0, 0, 36, 207, 1, 0, 0, 0, 38, 216, 1, 0, 0, 0, 40, 219, 1, 0, 0, 0, 42, 231, 1, 0, 0, 0, 44, 233, 1, 0, 0, 0, 46, 241, 1, 0, 0, 0, 48, 247, 1, 0, 0, 0, 50, 259, 1, 0, 0, 0, 52, 261, 1, 0, 0, 0, 54, 265, 1, 0, 0, 0, 56, 267, 1, 0, 0, 0, 58, 283, 1, 0, 0, 0, 60, 286, 1, 0, 0, 0, 62, 351, 1, 0, 0, 0, 64, 408, 1, 0, 0, 0, 66, 415, 1, 0, 0, 0, 68, 417, 1, 0, 0, 0, 70, 421, 1, 0, 0, 0, 72, 423, 1, 0, 0, 0, 74, 76, 3, 2, 1, 0, 75, 74, 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 77, 78, 1, 0, 0, 0, 78, 80, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 80, 81, 3, 12, 6, 0, 81, 82, 5, 0, 0, 1, 82, 1, 1, 0, 0, 0, 83, 84, 5, 1, 0, 0, 84, 85, 3, 4, 2, 0, 85, 86, 3, 6, 3, 0, 86, 87, 5, 2, 0, 0, 87, 3, 1, 0, 0, 0, 88, 89, 5, 3, 0, 0, 89, 5, 1, 0, 0, 0, 90, 92, 3, 8, 4, 0, 91, 93, 3, 8, 4, 0, 92, 91, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, 7, 1, 0, 0, 0, 94, 96, 3, 10, 5, 0, 95, 94, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 97, 1, 0, 0, 0, 97, 98, 5, 58, 0, 0, 98, 9, 1, 0, 0, 0, 99, 100, 7, 0, 0, 0, 100, 11, 1, 0, 0, 0, 101, 102, 5, 11, 0, 0, 102, 103, 5, 74, 0, 0, 103, 104, 3, 16, 8, 0, 104, 108, 5, 12, 0, 0, 105, 107, 3, 14, 7, 0, 106, 105, 1, 0, 0, 0, 107, 110, 1, 0, 0, 0, 108, 106, 1, 0, 0, 0, 108, 109, 1, 0, 0, 0, 109, 111, 1, 0, 0, 0, 110, 108, 1, 0, 0, 0, 111, 112, 5, 13, 0, 0, 112, 13, 1, 0, 0, 0, 113, 114, 5, 14, 0, 0, 114, 115, 5, 74, 0, 0, 115, 116, 3, 16, 8, 0, 116, 120, 5, 12, 0, 0, 117, 119, 3, 22, 11, 0, 118, 117, 1, 0, 0, 0, 119, 122, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 120, 121, 1, 0, 0, 0, 121, 123, 1, 0, 0, 0, 122, 120, 1, 0, 0, 0, 123, 124, 5, 13, 0, 0, 124, 15, 1, 0, 0, 0, 125, 137, 5, 15, 0, 0, 126, 131, 3, 18, 9, 0, 127, 128, 5, 16, 0, 0, 128, 130, 3, 18, 9, 0, 129, 127, 1, 0, 0, 0, 130, 133, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 135, 1, 0, 0, 0, 133, 131, 1, 0, 0, 0, 134, 136, 5, 16, 0, 0, 135, 134, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 138, 1, 0, 0, 0, 137, 126, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 140, 5, 17, 0, 0, 140, 17, 1, 0, 0, 0, 141, 142, 3, 70, 35, 0, 142, 143, 5, 74, 0, 0, 143, 19, 1, 0, 0, 0, 144, 148, 5, 12, 0, 0, 145, 147, 3, 22, 11, 0, 146, 145, 1, 0, 0, 0, 147, 150, 1, 0, 0, 0, 148, 146, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 151, 1, 0, 0, 0, 150, 148, 1, 0, 0, 0, 151, 154, 5, 13, 0, 0, 152, 154, 3, 22, 11, 0, 153, 144, 1, 0, 0, 0, 153, 152, 1, 0, 0, 0, 154, 21, 1, 0, 0, 0, 155, 160, 3, 26, 13, 0, 156, 157, 3, 24, 12, 0, 157, 158, 5, 2, 0, 0, 158, 160, 1, 0, 0, 0, 159, 155, 1, 0, 0, 0, 159, 156, 1, 0, 0, 0, 160, 23, 1, 0, 0, 0, 161, 168, 3, 28, 14, 0, 162, 168, 3, 30, 15, 0, 163, 168, 3, 32, 16, 0, 164, 168, 3, 34, 17, 0, 165, 168, 3, 36, 18, 0, 166, 168, 3, 38, 19, 0, 167, 161, 1, 0, 0, 0, 167, 162, 1, 0, 0, 0, 167, 163, 1, 0, 0, 0, 167, 164, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 166, 1, 0, 0, 0, 168, 25, 1, 0, 0, 0, 169, 172, 3, 40, 20, 0, 170, 172, 3, 42, 21, 0, 171, 169, 1, 0, 0, 0, 171, 170, 1, 0, 0, 0, 172, 27, 1, 0, 0, 0, 173, 177, 3, 70, 35, 0, 174, 176, 3, 64, 32, 0, 175, 174, 1, 0, 0, 0, 176, 179, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 177, 178, 1, 0, 0, 0, 178, 180, 1, 0, 0, 0, 179, 177, 1, 0, 0, 0, 180, 181, 5, 74, 0, 0, 181, 182, 5, 10, 0, 0, 182, 183, 3, 62, 31, 0, 183, 29, 1, 0, 0, 0, 184, 185, 3, 70, 35, 0, 185, 186, 5, 74, 0, 0, 186, 187, 5, 16, 0, 0, 187, 188, 3, 70, 35, 0, 188, 189, 5, 74, 0, 0, 189, 190, 5, 10, 0, 0, 190, 191, 3, 62, 31, 0, 191, 31, 1, 0, 0, 0, 192, 193, 5, 74, 0, 0, 193, 194, 5, 10, 0, 0, 194, 195, 3, 62, 31, 0, 195, 33, 1, 0, 0, 0, 196, 197, 5, 18, 0, 0, 197, 198, 5, 15, 0, 0, 198, 199, 5, 71, 0, 0, 199, 200, 5, 6, 0, 0, 200, 203, 3, 62, 31, 0, 201, 202, 5, 16, 0, 0, 202, 204, 3, 52, 26, 0, 203, 201, 1, 0, 0, 0, 203, 204, 1, 0, 0, 0, 204, 205, 1, 0, 0, 0, 205, 206, 5, 17, 0, 0, 206, 35, 1, 0, 0, 0, 207, 208, 5, 18, 0, 0, 208, 209, 5, 15, 0, 0, 209, 212, 3, 62, 31, 0, 210, 211, 5, 16, 0, 0, 211, 213, 3, 52, 26, 0, 212, 210, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 214, 1, 0, 0, 0, 214, 215, 5, 17, 0, 0, 215, 37, 1, 0, 0, 0, 216, 217, 5, 19, 0, 0, 217, 218, 3, 56, 28, 0, 218, 39, 1, 0, 0, 0, 219, 220, 5, 20, 0, 0, 220, 221, 5, 15, 0, 0, 221, 222, 3, 62, 31, 0, 222, 223, 5, 17, 0, 0, 223, 226, 3, 20, 10, 0, 224, 225, 5, 21, 0, 0, 225, 227, 3, 20, 10, 0, 226, 224, 1, 0, 0, 0, 226, 227, 1, 0, 0, 0, 227, 41, 1, 0, 0, 0, 228, 232, 3, 44, 22, 0, 229, 232, 3, 46, 23, 0, 230, 232, 3, 48, 24, 0, 231, 228, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 231, 230, 1, 0, 0, 0, 232, 43, 1, 0, 0, 0, 233, 234, 5, 22, 0, 0, 234, 235, 3, 20, 10, 0, 235, 236, 5, 23, 0, 0, 236, 237, 5, 15, 0, 0, 237, 238, 3, 62, 31, 0, 238, 239, 5, 17, 0, 0, 239, 240, 5, 2, 0, 0, 240, 45, 1, 0, 0, 0, 241, 242, 5, 23, 0, 0, 242, 243, 5, 15, 0, 0, 243, 244, 3, 62, 31, 0, 244, 245, 5, 17, 0, 0, 245, 246, 3, 20, 10, 0, 246, 47, 1, 0, 0, 0, 247, 248, 5, 24, 0, 0, 248, 249, 5, 15, 0, 0, 249, 250, 3, 50, 25, 0, 250, 251, 5, 2, 0, 0, 251, 252, 3, 62, 31, 0, 252, 253, 5, 2, 0, 0, 253, 254, 3, 32, 16, 0, 254, 255, 5, 17, 0, 0, 255, 256, 3, 20, 10, 0, 256, 49, 1, 0, 0, 0, 257, 260, 3, 28, 14, 0, 258, 260, 3, 32, 16, 0, 259, 257, 1, 0, 0, 0, 259, 258, 1, 0, 0, 0, 260, 51, 1, 0, 0, 0, 261, 262, 5, 68, 0, 0, 262, 53, 1, 0, 0, 0, 263, 266, 5, 74, 0, 0, 264, 266, 3, 66, 33, 0, 265, 263, 1, 0, 0, 0, 265, 264, 1, 0, 0, 0, 266, 55, 1, 0, 0, 0, 267, 279, 5, 15, 0, 0, 268, 273, 3, 54, 27, 0, 269, 270, 5, 16, 0, 0, 270, 272, 3, 54, 27, 0, 271, 269, 1, 0, 0, 0, 272, 275, 1, 0, 0, 0, 273, 271, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 277, 1, 0, 0, 0, 275, 273, 1, 0, 0, 0, 276, 278, 5, 16, 0, 0, 277, 276, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 280, 1, 0, 0, 0, 279, 268, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 282, 5, 17, 0, 0, 282, 57, 1, 0, 0, 0, 283, 284, 5, 74, 0, 0, 284, 285, 3, 60, 30, 0, 285, 59, 1, 0, 0, 0, 286, 298, 5, 15, 0, 0, 287, 292, 3, 62, 31, 0, 288, 289, 5, 16, 0, 0, 289, 291, 3, 62, 31, 0, 290, 288, 1, 0, 0, 0, 291, 294, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 296, 1, 0, 0, 0, 294, 292, 1, 0, 0, 0, 295, 297, 5, 16, 0, 0, 296, 295, 1, 0, 0, 0, 296, 297, 1, 0, 0, 0, 297, 299, 1, 0, 0, 0, 298, 287, 1, 0, 0, 0, 298, 299, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 301, 5, 17, 0, 0, 301, 61, 1, 0, 0, 0, 302, 303, 6, 31, -1, 0, 303, 304, 5, 15, 0, 0, 304, 305, 3, 62, 31, 0, 305, 306, 5, 17, 0, 0, 306, 352, 1, 0, 0, 0, 307, 308, 3, 72, 36, 0, 308, 309, 5, 15, 0, 0, 309, 311, 3, 62, 31, 0, 310, 312, 5, 16, 0, 0, 311, 310, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 314, 5, 17, 0, 0, 314, 352, 1, 0, 0, 0, 315, 352, 3, 58, 29, 0, 316, 317, 5, 25, 0, 0, 317, 318, 5, 74, 0, 0, 318, 352, 3, 60, 30, 0, 319, 320, 5, 28, 0, 0, 320, 321, 5, 26, 0, 0, 321, 322, 3, 62, 31, 0, 322, 323, 5, 27, 0, 0, 323, 324, 7, 1, 0, 0, 324, 352, 1, 0, 0, 0, 325, 326, 5, 34, 0, 0, 326, 327, 5, 26, 0, 0, 327, 328, 3, 62, 31, 0, 328, 329, 5, 27, 0, 0, 329, 330, 7, 2, 0, 0, 330, 352, 1, 0, 0, 0, 331, 332, 7, 3, 0, 0, 332, 352, 3, 62, 31, 15, 333, 345, 5, 26, 0, 0, 334, 339, 3, 62, 31, 0, 335, 336, 5, 16, 0, 0, 336, 338, 3, 62, 31, 0, 337, 335, 1, 0, 0, 0, 338, 341, 1, 0, 0, 0, 339, 337, 1, 0, 0, 0, 339, 340, 1, 0, 0, 0, 340, 343, 1, 0, 0, 0, 341, 339, 1, 0, 0, 0, 342, 344, 5, 16, 0, 0, 343, 342, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 346, 1, 0, 0, 0, 345, 334, 1, 0, 0, 0, 345, 346, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, 347, 352, 5, 27, 0, 0, 348, 352, 5, 73, 0, 0, 349, 352, 5, 74, 0, 0, 350, 352, 3, 66, 33, 0, 351, 302, 1, 0, 0, 0, 351, 307, 1, 0, 0, 0, 351, 315, 1, 0, 0, 0, 351, 316, 1, 0, 0, 0, 351, 319, 1, 0, 0, 0, 351, 325, 1, 0, 0, 0, 351, 331, 1, 0, 0, 0, 351, 333, 1, 0, 0, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 405, 1, 0, 0, 0, 353, 354, 10, 14, 0, 0, 354, 355, 7, 4, 0, 0, 355, 404, 3, 62, 31, 15, 356, 357, 10, 13, 0, 0, 357, 358, 7, 5, 0, 0, 358, 404, 3, 62, 31, 14, 359, 360, 10, 12, 0, 0, 360, 361, 7, 6, 0, 0, 361, 404, 3, 62, 31, 13, 362, 363, 10, 11, 0, 0, 363, 364, 7, 7, 0, 0, 364, 404, 3, 62, 31, 12, 365, 366, 10, 10, 0, 0, 366, 367, 7, 8, 0, 0, 367, 404, 3, 62, 31, 11, 368, 369, 10, 9, 0, 0, 369, 370, 5, 53, 0, 0, 370, 404, 3, 62, 31, 10, 371, 372, 10, 8, 0, 0, 372, 373, 5, 4, 0, 0, 373, 404, 3, 62, 31, 9, 374, 375, 10, 7, 0, 0, 375, 376, 5, 54, 0, 0, 376, 404, 3, 62, 31, 8, 377, 378, 10, 6, 0, 0, 378, 379, 5, 55, 0, 0, 379, 404, 3, 62, 31, 7, 380, 381, 10, 5, 0, 0, 381, 382, 5, 56, 0, 0, 382, 404, 3, 62, 31, 6, 383, 384, 10, 21, 0, 0, 384, 385, 5, 26, 0, 0, 385, 386, 5, 61, 0, 0, 386, 404, 5, 27, 0, 0, 387, 388, 10, 18, 0, 0, 388, 404, 7, 9, 0, 0, 389, 390, 10, 17, 0, 0, 390, 391, 5, 41, 0, 0, 391, 392, 5, 15, 0, 0, 392, 393, 3, 62, 31, 0, 393, 394, 5, 17, 0, 0, 394, 404, 1, 0, 0, 0, 395, 396, 10, 16, 0, 0, 396, 397, 5, 42, 0, 0, 397, 398, 5, 15, 0, 0, 398, 399, 3, 62, 31, 0, 399, 400, 5, 16, 0, 0, 400, 401, 3, 62, 31, 0, 401, 402, 5, 17, 0, 0, 402, 404, 1, 0, 0, 0, 403, 353, 1, 0, 0, 0, 403, 356, 1, 0, 0, 0, 403, 359, 1, 0, 0, 0, 403, 362, 1, 0, 0, 0, 403, 365, 1, 0, 0, 0, 403, 368, 1, 0, 0, 0, 403, 371, 1, 0, 0, 0, 403, 374, 1, 0, 0, 0, 403, 377, 1, 0, 0, 0, 403, 380, 1, 0, 0, 0, 403, 383, 1, 0, 0, 0, 403, 387, 1, 0, 0, 0, 403, 389, 1, 0, 0, 0, 403, 395, 1, 0, 0, 0, 404, 407, 1, 0, 0, 0, 405, 403, 1, 0, 0, 0, 405, 406, 1, 0, 0, 0, 406, 63, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 408, 409, 5, 57, 0, 0, 409, 65, 1, 0, 0, 0, 410, 416, 5, 59, 0, 0, 411, 416, 3, 68, 34, 0, 412, 416, 5, 68, 0, 0, 413, 416, 5, 69, 0, 0, 414, 416, 5, 70, 0, 0, 415, 410, 1, 0, 0, 0, 415, 411, 1, 0, 0, 0, 415, 412, 1, 0, 0, 0, 415, 413, 1, 0, 0, 0, 415, 414, 1, 0, 0, 0, 416, 67, 1, 0, 0, 0, 417, 419, 5, 61, 0, 0, 418, 420, 5, 60, 0, 0, 419, 418, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, 69, 1, 0, 0, 0, 421, 422, 7, 10, 0, 0, 422, 71, 1, 0, 0, 0, 423, 424, 7, 11, 0, 0, 424, 73, 1, 0, 0, 0, 35, 77, 92, 95, 108, 120, 131, 135, 137, 148, 153, 159, 167, 171, 177, 203, 212, 226, 231, 259, 265, 273, 277, 279, 292, 296, 298, 311, 339, 343, 345, 351, 403, 405, 415, 419] \ No newline at end of file +[4, 1, 78, 445, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 1, 0, 5, 0, 80, 8, 0, 10, 0, 12, 0, 83, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 90, 8, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 101, 8, 4, 1, 5, 3, 5, 104, 8, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 115, 8, 7, 10, 7, 12, 7, 118, 9, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 126, 8, 8, 10, 8, 12, 8, 129, 9, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 138, 8, 9, 10, 9, 12, 9, 141, 9, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 149, 8, 10, 10, 10, 12, 10, 152, 9, 10, 1, 10, 3, 10, 155, 8, 10, 3, 10, 157, 8, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 5, 12, 166, 8, 12, 10, 12, 12, 12, 169, 9, 12, 1, 12, 1, 12, 3, 12, 173, 8, 12, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 179, 8, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 187, 8, 14, 1, 15, 1, 15, 3, 15, 191, 8, 15, 1, 16, 1, 16, 5, 16, 195, 8, 16, 10, 16, 12, 16, 198, 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 223, 8, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 232, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 246, 8, 22, 1, 23, 1, 23, 1, 23, 3, 23, 251, 8, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 3, 27, 279, 8, 27, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 285, 8, 29, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 291, 8, 30, 10, 30, 12, 30, 294, 9, 30, 1, 30, 3, 30, 297, 8, 30, 3, 30, 299, 8, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 310, 8, 32, 10, 32, 12, 32, 313, 9, 32, 1, 32, 3, 32, 316, 8, 32, 3, 32, 318, 8, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 3, 33, 331, 8, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 357, 8, 33, 10, 33, 12, 33, 360, 9, 33, 1, 33, 3, 33, 363, 8, 33, 3, 33, 365, 8, 33, 1, 33, 1, 33, 1, 33, 1, 33, 3, 33, 371, 8, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 423, 8, 33, 10, 33, 12, 33, 426, 9, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 435, 8, 35, 1, 36, 1, 36, 3, 36, 439, 8, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 0, 1, 66, 39, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 0, 12, 1, 0, 4, 10, 1, 0, 30, 34, 2, 0, 30, 34, 36, 39, 2, 0, 5, 5, 44, 45, 1, 0, 46, 48, 2, 0, 45, 45, 49, 49, 1, 0, 50, 51, 1, 0, 6, 9, 1, 0, 52, 53, 1, 0, 40, 41, 1, 0, 65, 67, 2, 0, 65, 66, 73, 73, 471, 0, 81, 1, 0, 0, 0, 2, 89, 1, 0, 0, 0, 4, 91, 1, 0, 0, 0, 6, 96, 1, 0, 0, 0, 8, 98, 1, 0, 0, 0, 10, 103, 1, 0, 0, 0, 12, 107, 1, 0, 0, 0, 14, 109, 1, 0, 0, 0, 16, 121, 1, 0, 0, 0, 18, 132, 1, 0, 0, 0, 20, 144, 1, 0, 0, 0, 22, 160, 1, 0, 0, 0, 24, 172, 1, 0, 0, 0, 26, 178, 1, 0, 0, 0, 28, 186, 1, 0, 0, 0, 30, 190, 1, 0, 0, 0, 32, 192, 1, 0, 0, 0, 34, 203, 1, 0, 0, 0, 36, 211, 1, 0, 0, 0, 38, 215, 1, 0, 0, 0, 40, 226, 1, 0, 0, 0, 42, 235, 1, 0, 0, 0, 44, 238, 1, 0, 0, 0, 46, 250, 1, 0, 0, 0, 48, 252, 1, 0, 0, 0, 50, 260, 1, 0, 0, 0, 52, 266, 1, 0, 0, 0, 54, 278, 1, 0, 0, 0, 56, 280, 1, 0, 0, 0, 58, 284, 1, 0, 0, 0, 60, 286, 1, 0, 0, 0, 62, 302, 1, 0, 0, 0, 64, 305, 1, 0, 0, 0, 66, 370, 1, 0, 0, 0, 68, 427, 1, 0, 0, 0, 70, 434, 1, 0, 0, 0, 72, 436, 1, 0, 0, 0, 74, 440, 1, 0, 0, 0, 76, 442, 1, 0, 0, 0, 78, 80, 3, 4, 2, 0, 79, 78, 1, 0, 0, 0, 80, 83, 1, 0, 0, 0, 81, 79, 1, 0, 0, 0, 81, 82, 1, 0, 0, 0, 82, 84, 1, 0, 0, 0, 83, 81, 1, 0, 0, 0, 84, 85, 3, 2, 1, 0, 85, 86, 5, 0, 0, 1, 86, 1, 1, 0, 0, 0, 87, 90, 3, 14, 7, 0, 88, 90, 3, 16, 8, 0, 89, 87, 1, 0, 0, 0, 89, 88, 1, 0, 0, 0, 90, 3, 1, 0, 0, 0, 91, 92, 5, 1, 0, 0, 92, 93, 3, 6, 3, 0, 93, 94, 3, 8, 4, 0, 94, 95, 5, 2, 0, 0, 95, 5, 1, 0, 0, 0, 96, 97, 5, 3, 0, 0, 97, 7, 1, 0, 0, 0, 98, 100, 3, 10, 5, 0, 99, 101, 3, 10, 5, 0, 100, 99, 1, 0, 0, 0, 100, 101, 1, 0, 0, 0, 101, 9, 1, 0, 0, 0, 102, 104, 3, 12, 6, 0, 103, 102, 1, 0, 0, 0, 103, 104, 1, 0, 0, 0, 104, 105, 1, 0, 0, 0, 105, 106, 5, 59, 0, 0, 106, 11, 1, 0, 0, 0, 107, 108, 7, 0, 0, 0, 108, 13, 1, 0, 0, 0, 109, 110, 5, 11, 0, 0, 110, 111, 5, 75, 0, 0, 111, 112, 3, 20, 10, 0, 112, 116, 5, 12, 0, 0, 113, 115, 3, 18, 9, 0, 114, 113, 1, 0, 0, 0, 115, 118, 1, 0, 0, 0, 116, 114, 1, 0, 0, 0, 116, 117, 1, 0, 0, 0, 117, 119, 1, 0, 0, 0, 118, 116, 1, 0, 0, 0, 119, 120, 5, 13, 0, 0, 120, 15, 1, 0, 0, 0, 121, 122, 5, 14, 0, 0, 122, 123, 5, 75, 0, 0, 123, 127, 5, 12, 0, 0, 124, 126, 3, 18, 9, 0, 125, 124, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 130, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 130, 131, 5, 13, 0, 0, 131, 17, 1, 0, 0, 0, 132, 133, 5, 15, 0, 0, 133, 134, 5, 75, 0, 0, 134, 135, 3, 20, 10, 0, 135, 139, 5, 12, 0, 0, 136, 138, 3, 26, 13, 0, 137, 136, 1, 0, 0, 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 142, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 142, 143, 5, 13, 0, 0, 143, 19, 1, 0, 0, 0, 144, 156, 5, 16, 0, 0, 145, 150, 3, 22, 11, 0, 146, 147, 5, 17, 0, 0, 147, 149, 3, 22, 11, 0, 148, 146, 1, 0, 0, 0, 149, 152, 1, 0, 0, 0, 150, 148, 1, 0, 0, 0, 150, 151, 1, 0, 0, 0, 151, 154, 1, 0, 0, 0, 152, 150, 1, 0, 0, 0, 153, 155, 5, 17, 0, 0, 154, 153, 1, 0, 0, 0, 154, 155, 1, 0, 0, 0, 155, 157, 1, 0, 0, 0, 156, 145, 1, 0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 158, 1, 0, 0, 0, 158, 159, 5, 18, 0, 0, 159, 21, 1, 0, 0, 0, 160, 161, 3, 74, 37, 0, 161, 162, 5, 75, 0, 0, 162, 23, 1, 0, 0, 0, 163, 167, 5, 12, 0, 0, 164, 166, 3, 26, 13, 0, 165, 164, 1, 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 170, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 173, 5, 13, 0, 0, 171, 173, 3, 26, 13, 0, 172, 163, 1, 0, 0, 0, 172, 171, 1, 0, 0, 0, 173, 25, 1, 0, 0, 0, 174, 179, 3, 30, 15, 0, 175, 176, 3, 28, 14, 0, 176, 177, 5, 2, 0, 0, 177, 179, 1, 0, 0, 0, 178, 174, 1, 0, 0, 0, 178, 175, 1, 0, 0, 0, 179, 27, 1, 0, 0, 0, 180, 187, 3, 32, 16, 0, 181, 187, 3, 34, 17, 0, 182, 187, 3, 36, 18, 0, 183, 187, 3, 38, 19, 0, 184, 187, 3, 40, 20, 0, 185, 187, 3, 42, 21, 0, 186, 180, 1, 0, 0, 0, 186, 181, 1, 0, 0, 0, 186, 182, 1, 0, 0, 0, 186, 183, 1, 0, 0, 0, 186, 184, 1, 0, 0, 0, 186, 185, 1, 0, 0, 0, 187, 29, 1, 0, 0, 0, 188, 191, 3, 44, 22, 0, 189, 191, 3, 46, 23, 0, 190, 188, 1, 0, 0, 0, 190, 189, 1, 0, 0, 0, 191, 31, 1, 0, 0, 0, 192, 196, 3, 74, 37, 0, 193, 195, 3, 68, 34, 0, 194, 193, 1, 0, 0, 0, 195, 198, 1, 0, 0, 0, 196, 194, 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 199, 1, 0, 0, 0, 198, 196, 1, 0, 0, 0, 199, 200, 5, 75, 0, 0, 200, 201, 5, 10, 0, 0, 201, 202, 3, 66, 33, 0, 202, 33, 1, 0, 0, 0, 203, 204, 3, 74, 37, 0, 204, 205, 5, 75, 0, 0, 205, 206, 5, 17, 0, 0, 206, 207, 3, 74, 37, 0, 207, 208, 5, 75, 0, 0, 208, 209, 5, 10, 0, 0, 209, 210, 3, 66, 33, 0, 210, 35, 1, 0, 0, 0, 211, 212, 5, 75, 0, 0, 212, 213, 5, 10, 0, 0, 213, 214, 3, 66, 33, 0, 214, 37, 1, 0, 0, 0, 215, 216, 5, 19, 0, 0, 216, 217, 5, 16, 0, 0, 217, 218, 5, 72, 0, 0, 218, 219, 5, 6, 0, 0, 219, 222, 3, 66, 33, 0, 220, 221, 5, 17, 0, 0, 221, 223, 3, 56, 28, 0, 222, 220, 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 225, 5, 18, 0, 0, 225, 39, 1, 0, 0, 0, 226, 227, 5, 19, 0, 0, 227, 228, 5, 16, 0, 0, 228, 231, 3, 66, 33, 0, 229, 230, 5, 17, 0, 0, 230, 232, 3, 56, 28, 0, 231, 229, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 233, 1, 0, 0, 0, 233, 234, 5, 18, 0, 0, 234, 41, 1, 0, 0, 0, 235, 236, 5, 20, 0, 0, 236, 237, 3, 60, 30, 0, 237, 43, 1, 0, 0, 0, 238, 239, 5, 21, 0, 0, 239, 240, 5, 16, 0, 0, 240, 241, 3, 66, 33, 0, 241, 242, 5, 18, 0, 0, 242, 245, 3, 24, 12, 0, 243, 244, 5, 22, 0, 0, 244, 246, 3, 24, 12, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 45, 1, 0, 0, 0, 247, 251, 3, 48, 24, 0, 248, 251, 3, 50, 25, 0, 249, 251, 3, 52, 26, 0, 250, 247, 1, 0, 0, 0, 250, 248, 1, 0, 0, 0, 250, 249, 1, 0, 0, 0, 251, 47, 1, 0, 0, 0, 252, 253, 5, 23, 0, 0, 253, 254, 3, 24, 12, 0, 254, 255, 5, 24, 0, 0, 255, 256, 5, 16, 0, 0, 256, 257, 3, 66, 33, 0, 257, 258, 5, 18, 0, 0, 258, 259, 5, 2, 0, 0, 259, 49, 1, 0, 0, 0, 260, 261, 5, 24, 0, 0, 261, 262, 5, 16, 0, 0, 262, 263, 3, 66, 33, 0, 263, 264, 5, 18, 0, 0, 264, 265, 3, 24, 12, 0, 265, 51, 1, 0, 0, 0, 266, 267, 5, 25, 0, 0, 267, 268, 5, 16, 0, 0, 268, 269, 3, 54, 27, 0, 269, 270, 5, 2, 0, 0, 270, 271, 3, 66, 33, 0, 271, 272, 5, 2, 0, 0, 272, 273, 3, 36, 18, 0, 273, 274, 5, 18, 0, 0, 274, 275, 3, 24, 12, 0, 275, 53, 1, 0, 0, 0, 276, 279, 3, 32, 16, 0, 277, 279, 3, 36, 18, 0, 278, 276, 1, 0, 0, 0, 278, 277, 1, 0, 0, 0, 279, 55, 1, 0, 0, 0, 280, 281, 5, 69, 0, 0, 281, 57, 1, 0, 0, 0, 282, 285, 5, 75, 0, 0, 283, 285, 3, 70, 35, 0, 284, 282, 1, 0, 0, 0, 284, 283, 1, 0, 0, 0, 285, 59, 1, 0, 0, 0, 286, 298, 5, 16, 0, 0, 287, 292, 3, 58, 29, 0, 288, 289, 5, 17, 0, 0, 289, 291, 3, 58, 29, 0, 290, 288, 1, 0, 0, 0, 291, 294, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 296, 1, 0, 0, 0, 294, 292, 1, 0, 0, 0, 295, 297, 5, 17, 0, 0, 296, 295, 1, 0, 0, 0, 296, 297, 1, 0, 0, 0, 297, 299, 1, 0, 0, 0, 298, 287, 1, 0, 0, 0, 298, 299, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 301, 5, 18, 0, 0, 301, 61, 1, 0, 0, 0, 302, 303, 5, 75, 0, 0, 303, 304, 3, 64, 32, 0, 304, 63, 1, 0, 0, 0, 305, 317, 5, 16, 0, 0, 306, 311, 3, 66, 33, 0, 307, 308, 5, 17, 0, 0, 308, 310, 3, 66, 33, 0, 309, 307, 1, 0, 0, 0, 310, 313, 1, 0, 0, 0, 311, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 315, 1, 0, 0, 0, 313, 311, 1, 0, 0, 0, 314, 316, 5, 17, 0, 0, 315, 314, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 318, 1, 0, 0, 0, 317, 306, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 319, 1, 0, 0, 0, 319, 320, 5, 18, 0, 0, 320, 65, 1, 0, 0, 0, 321, 322, 6, 33, -1, 0, 322, 323, 5, 16, 0, 0, 323, 324, 3, 66, 33, 0, 324, 325, 5, 18, 0, 0, 325, 371, 1, 0, 0, 0, 326, 327, 3, 76, 38, 0, 327, 328, 5, 16, 0, 0, 328, 330, 3, 66, 33, 0, 329, 331, 5, 17, 0, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 333, 5, 18, 0, 0, 333, 371, 1, 0, 0, 0, 334, 371, 3, 62, 31, 0, 335, 336, 5, 26, 0, 0, 336, 337, 5, 75, 0, 0, 337, 371, 3, 64, 32, 0, 338, 339, 5, 29, 0, 0, 339, 340, 5, 27, 0, 0, 340, 341, 3, 66, 33, 0, 341, 342, 5, 28, 0, 0, 342, 343, 7, 1, 0, 0, 343, 371, 1, 0, 0, 0, 344, 345, 5, 35, 0, 0, 345, 346, 5, 27, 0, 0, 346, 347, 3, 66, 33, 0, 347, 348, 5, 28, 0, 0, 348, 349, 7, 2, 0, 0, 349, 371, 1, 0, 0, 0, 350, 351, 7, 3, 0, 0, 351, 371, 3, 66, 33, 15, 352, 364, 5, 27, 0, 0, 353, 358, 3, 66, 33, 0, 354, 355, 5, 17, 0, 0, 355, 357, 3, 66, 33, 0, 356, 354, 1, 0, 0, 0, 357, 360, 1, 0, 0, 0, 358, 356, 1, 0, 0, 0, 358, 359, 1, 0, 0, 0, 359, 362, 1, 0, 0, 0, 360, 358, 1, 0, 0, 0, 361, 363, 5, 17, 0, 0, 362, 361, 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 365, 1, 0, 0, 0, 364, 353, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 366, 1, 0, 0, 0, 366, 371, 5, 28, 0, 0, 367, 371, 5, 74, 0, 0, 368, 371, 5, 75, 0, 0, 369, 371, 3, 70, 35, 0, 370, 321, 1, 0, 0, 0, 370, 326, 1, 0, 0, 0, 370, 334, 1, 0, 0, 0, 370, 335, 1, 0, 0, 0, 370, 338, 1, 0, 0, 0, 370, 344, 1, 0, 0, 0, 370, 350, 1, 0, 0, 0, 370, 352, 1, 0, 0, 0, 370, 367, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370, 369, 1, 0, 0, 0, 371, 424, 1, 0, 0, 0, 372, 373, 10, 14, 0, 0, 373, 374, 7, 4, 0, 0, 374, 423, 3, 66, 33, 15, 375, 376, 10, 13, 0, 0, 376, 377, 7, 5, 0, 0, 377, 423, 3, 66, 33, 14, 378, 379, 10, 12, 0, 0, 379, 380, 7, 6, 0, 0, 380, 423, 3, 66, 33, 13, 381, 382, 10, 11, 0, 0, 382, 383, 7, 7, 0, 0, 383, 423, 3, 66, 33, 12, 384, 385, 10, 10, 0, 0, 385, 386, 7, 8, 0, 0, 386, 423, 3, 66, 33, 11, 387, 388, 10, 9, 0, 0, 388, 389, 5, 54, 0, 0, 389, 423, 3, 66, 33, 10, 390, 391, 10, 8, 0, 0, 391, 392, 5, 4, 0, 0, 392, 423, 3, 66, 33, 9, 393, 394, 10, 7, 0, 0, 394, 395, 5, 55, 0, 0, 395, 423, 3, 66, 33, 8, 396, 397, 10, 6, 0, 0, 397, 398, 5, 56, 0, 0, 398, 423, 3, 66, 33, 7, 399, 400, 10, 5, 0, 0, 400, 401, 5, 57, 0, 0, 401, 423, 3, 66, 33, 6, 402, 403, 10, 21, 0, 0, 403, 404, 5, 27, 0, 0, 404, 405, 5, 62, 0, 0, 405, 423, 5, 28, 0, 0, 406, 407, 10, 18, 0, 0, 407, 423, 7, 9, 0, 0, 408, 409, 10, 17, 0, 0, 409, 410, 5, 42, 0, 0, 410, 411, 5, 16, 0, 0, 411, 412, 3, 66, 33, 0, 412, 413, 5, 18, 0, 0, 413, 423, 1, 0, 0, 0, 414, 415, 10, 16, 0, 0, 415, 416, 5, 43, 0, 0, 416, 417, 5, 16, 0, 0, 417, 418, 3, 66, 33, 0, 418, 419, 5, 17, 0, 0, 419, 420, 3, 66, 33, 0, 420, 421, 5, 18, 0, 0, 421, 423, 1, 0, 0, 0, 422, 372, 1, 0, 0, 0, 422, 375, 1, 0, 0, 0, 422, 378, 1, 0, 0, 0, 422, 381, 1, 0, 0, 0, 422, 384, 1, 0, 0, 0, 422, 387, 1, 0, 0, 0, 422, 390, 1, 0, 0, 0, 422, 393, 1, 0, 0, 0, 422, 396, 1, 0, 0, 0, 422, 399, 1, 0, 0, 0, 422, 402, 1, 0, 0, 0, 422, 406, 1, 0, 0, 0, 422, 408, 1, 0, 0, 0, 422, 414, 1, 0, 0, 0, 423, 426, 1, 0, 0, 0, 424, 422, 1, 0, 0, 0, 424, 425, 1, 0, 0, 0, 425, 67, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 427, 428, 5, 58, 0, 0, 428, 69, 1, 0, 0, 0, 429, 435, 5, 60, 0, 0, 430, 435, 3, 72, 36, 0, 431, 435, 5, 69, 0, 0, 432, 435, 5, 70, 0, 0, 433, 435, 5, 71, 0, 0, 434, 429, 1, 0, 0, 0, 434, 430, 1, 0, 0, 0, 434, 431, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 433, 1, 0, 0, 0, 435, 71, 1, 0, 0, 0, 436, 438, 5, 62, 0, 0, 437, 439, 5, 61, 0, 0, 438, 437, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439, 73, 1, 0, 0, 0, 440, 441, 7, 10, 0, 0, 441, 75, 1, 0, 0, 0, 442, 443, 7, 11, 0, 0, 443, 77, 1, 0, 0, 0, 37, 81, 89, 100, 103, 116, 127, 139, 150, 154, 156, 167, 172, 178, 186, 190, 196, 222, 231, 245, 250, 278, 284, 292, 296, 298, 311, 315, 317, 330, 358, 362, 364, 370, 422, 424, 434, 438] \ No newline at end of file diff --git a/packages/cashc/src/grammar/CashScript.tokens b/packages/cashc/src/grammar/CashScript.tokens index 3b21c6f2..216b113a 100644 --- a/packages/cashc/src/grammar/CashScript.tokens +++ b/packages/cashc/src/grammar/CashScript.tokens @@ -55,26 +55,27 @@ T__53=54 T__54=55 T__55=56 T__56=57 -VersionLiteral=58 -BooleanLiteral=59 -NumberUnit=60 -NumberLiteral=61 -NumberPart=62 -ExponentPart=63 -PrimitiveType=64 -UnboundedBytes=65 -BoundedBytes=66 -Bound=67 -StringLiteral=68 -DateLiteral=69 -HexLiteral=70 -TxVar=71 -UnsafeCast=72 -NullaryOp=73 -Identifier=74 -WHITESPACE=75 -COMMENT=76 -LINE_COMMENT=77 +T__57=58 +VersionLiteral=59 +BooleanLiteral=60 +NumberUnit=61 +NumberLiteral=62 +NumberPart=63 +ExponentPart=64 +PrimitiveType=65 +UnboundedBytes=66 +BoundedBytes=67 +Bound=68 +StringLiteral=69 +DateLiteral=70 +HexLiteral=71 +TxVar=72 +UnsafeCast=73 +NullaryOp=74 +Identifier=75 +WHITESPACE=76 +COMMENT=77 +LINE_COMMENT=78 'pragma'=1 ';'=2 'cashscript'=3 @@ -88,48 +89,49 @@ LINE_COMMENT=77 'contract'=11 '{'=12 '}'=13 -'function'=14 -'('=15 -','=16 -')'=17 -'require'=18 -'console.log'=19 -'if'=20 -'else'=21 -'do'=22 -'while'=23 -'for'=24 -'new'=25 -'['=26 -']'=27 -'tx.outputs'=28 -'.value'=29 -'.lockingBytecode'=30 -'.tokenCategory'=31 -'.nftCommitment'=32 -'.tokenAmount'=33 -'tx.inputs'=34 -'.outpointTransactionHash'=35 -'.outpointIndex'=36 -'.unlockingBytecode'=37 -'.sequenceNumber'=38 -'.reverse()'=39 -'.length'=40 -'.split'=41 -'.slice'=42 -'!'=43 -'-'=44 -'*'=45 -'/'=46 -'%'=47 -'+'=48 -'>>'=49 -'<<'=50 -'=='=51 -'!='=52 -'&'=53 -'|'=54 -'&&'=55 -'||'=56 -'constant'=57 -'bytes'=65 +'library'=14 +'function'=15 +'('=16 +','=17 +')'=18 +'require'=19 +'console.log'=20 +'if'=21 +'else'=22 +'do'=23 +'while'=24 +'for'=25 +'new'=26 +'['=27 +']'=28 +'tx.outputs'=29 +'.value'=30 +'.lockingBytecode'=31 +'.tokenCategory'=32 +'.nftCommitment'=33 +'.tokenAmount'=34 +'tx.inputs'=35 +'.outpointTransactionHash'=36 +'.outpointIndex'=37 +'.unlockingBytecode'=38 +'.sequenceNumber'=39 +'.reverse()'=40 +'.length'=41 +'.split'=42 +'.slice'=43 +'!'=44 +'-'=45 +'*'=46 +'/'=47 +'%'=48 +'+'=49 +'>>'=50 +'<<'=51 +'=='=52 +'!='=53 +'&'=54 +'|'=55 +'&&'=56 +'||'=57 +'constant'=58 +'bytes'=66 diff --git a/packages/cashc/src/grammar/CashScriptLexer.interp b/packages/cashc/src/grammar/CashScriptLexer.interp index b75527d4..90c886cc 100644 --- a/packages/cashc/src/grammar/CashScriptLexer.interp +++ b/packages/cashc/src/grammar/CashScriptLexer.interp @@ -13,6 +13,7 @@ null 'contract' '{' '}' +'library' 'function' '(' ',' @@ -137,6 +138,7 @@ null null null null +null VersionLiteral BooleanLiteral NumberUnit @@ -216,6 +218,7 @@ T__53 T__54 T__55 T__56 +T__57 VersionLiteral BooleanLiteral NumberUnit @@ -245,4 +248,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 77, 918, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 4, 57, 509, 8, 57, 11, 57, 12, 57, 510, 1, 57, 1, 57, 4, 57, 515, 8, 57, 11, 57, 12, 57, 516, 1, 57, 1, 57, 4, 57, 521, 8, 57, 11, 57, 12, 57, 522, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 534, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 593, 8, 59, 1, 60, 3, 60, 596, 8, 60, 1, 60, 1, 60, 3, 60, 600, 8, 60, 1, 61, 4, 61, 603, 8, 61, 11, 61, 12, 61, 604, 1, 61, 1, 61, 4, 61, 609, 8, 61, 11, 61, 12, 61, 610, 5, 61, 613, 8, 61, 10, 61, 12, 61, 616, 9, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 650, 8, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 669, 8, 65, 1, 66, 1, 66, 5, 66, 673, 8, 66, 10, 66, 12, 66, 676, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 682, 8, 67, 10, 67, 12, 67, 685, 9, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 692, 8, 67, 10, 67, 12, 67, 695, 9, 67, 1, 67, 3, 67, 698, 8, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 5, 69, 712, 8, 69, 10, 69, 12, 69, 715, 9, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 3, 70, 732, 8, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 3, 71, 769, 8, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 3, 71, 782, 8, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 3, 72, 878, 8, 72, 1, 73, 1, 73, 5, 73, 882, 8, 73, 10, 73, 12, 73, 885, 9, 73, 1, 74, 4, 74, 888, 8, 74, 11, 74, 12, 74, 889, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 5, 75, 898, 8, 75, 10, 75, 12, 75, 901, 9, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 5, 76, 912, 8, 76, 10, 76, 12, 76, 915, 9, 76, 1, 76, 1, 76, 3, 683, 693, 899, 0, 77, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, 74, 149, 75, 151, 76, 153, 77, 1, 0, 11, 1, 0, 48, 57, 2, 0, 69, 69, 101, 101, 1, 0, 49, 57, 3, 0, 10, 10, 13, 13, 34, 34, 3, 0, 10, 10, 13, 13, 39, 39, 2, 0, 88, 88, 120, 120, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 12, 13, 32, 32, 2, 0, 10, 10, 13, 13, 962, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 3, 162, 1, 0, 0, 0, 5, 164, 1, 0, 0, 0, 7, 175, 1, 0, 0, 0, 9, 177, 1, 0, 0, 0, 11, 179, 1, 0, 0, 0, 13, 182, 1, 0, 0, 0, 15, 184, 1, 0, 0, 0, 17, 186, 1, 0, 0, 0, 19, 189, 1, 0, 0, 0, 21, 191, 1, 0, 0, 0, 23, 200, 1, 0, 0, 0, 25, 202, 1, 0, 0, 0, 27, 204, 1, 0, 0, 0, 29, 213, 1, 0, 0, 0, 31, 215, 1, 0, 0, 0, 33, 217, 1, 0, 0, 0, 35, 219, 1, 0, 0, 0, 37, 227, 1, 0, 0, 0, 39, 239, 1, 0, 0, 0, 41, 242, 1, 0, 0, 0, 43, 247, 1, 0, 0, 0, 45, 250, 1, 0, 0, 0, 47, 256, 1, 0, 0, 0, 49, 260, 1, 0, 0, 0, 51, 264, 1, 0, 0, 0, 53, 266, 1, 0, 0, 0, 55, 268, 1, 0, 0, 0, 57, 279, 1, 0, 0, 0, 59, 286, 1, 0, 0, 0, 61, 303, 1, 0, 0, 0, 63, 318, 1, 0, 0, 0, 65, 333, 1, 0, 0, 0, 67, 346, 1, 0, 0, 0, 69, 356, 1, 0, 0, 0, 71, 381, 1, 0, 0, 0, 73, 396, 1, 0, 0, 0, 75, 415, 1, 0, 0, 0, 77, 431, 1, 0, 0, 0, 79, 442, 1, 0, 0, 0, 81, 450, 1, 0, 0, 0, 83, 457, 1, 0, 0, 0, 85, 464, 1, 0, 0, 0, 87, 466, 1, 0, 0, 0, 89, 468, 1, 0, 0, 0, 91, 470, 1, 0, 0, 0, 93, 472, 1, 0, 0, 0, 95, 474, 1, 0, 0, 0, 97, 476, 1, 0, 0, 0, 99, 479, 1, 0, 0, 0, 101, 482, 1, 0, 0, 0, 103, 485, 1, 0, 0, 0, 105, 488, 1, 0, 0, 0, 107, 490, 1, 0, 0, 0, 109, 492, 1, 0, 0, 0, 111, 495, 1, 0, 0, 0, 113, 498, 1, 0, 0, 0, 115, 508, 1, 0, 0, 0, 117, 533, 1, 0, 0, 0, 119, 592, 1, 0, 0, 0, 121, 595, 1, 0, 0, 0, 123, 602, 1, 0, 0, 0, 125, 617, 1, 0, 0, 0, 127, 649, 1, 0, 0, 0, 129, 651, 1, 0, 0, 0, 131, 668, 1, 0, 0, 0, 133, 670, 1, 0, 0, 0, 135, 697, 1, 0, 0, 0, 137, 699, 1, 0, 0, 0, 139, 708, 1, 0, 0, 0, 141, 731, 1, 0, 0, 0, 143, 781, 1, 0, 0, 0, 145, 877, 1, 0, 0, 0, 147, 879, 1, 0, 0, 0, 149, 887, 1, 0, 0, 0, 151, 893, 1, 0, 0, 0, 153, 907, 1, 0, 0, 0, 155, 156, 5, 112, 0, 0, 156, 157, 5, 114, 0, 0, 157, 158, 5, 97, 0, 0, 158, 159, 5, 103, 0, 0, 159, 160, 5, 109, 0, 0, 160, 161, 5, 97, 0, 0, 161, 2, 1, 0, 0, 0, 162, 163, 5, 59, 0, 0, 163, 4, 1, 0, 0, 0, 164, 165, 5, 99, 0, 0, 165, 166, 5, 97, 0, 0, 166, 167, 5, 115, 0, 0, 167, 168, 5, 104, 0, 0, 168, 169, 5, 115, 0, 0, 169, 170, 5, 99, 0, 0, 170, 171, 5, 114, 0, 0, 171, 172, 5, 105, 0, 0, 172, 173, 5, 112, 0, 0, 173, 174, 5, 116, 0, 0, 174, 6, 1, 0, 0, 0, 175, 176, 5, 94, 0, 0, 176, 8, 1, 0, 0, 0, 177, 178, 5, 126, 0, 0, 178, 10, 1, 0, 0, 0, 179, 180, 5, 62, 0, 0, 180, 181, 5, 61, 0, 0, 181, 12, 1, 0, 0, 0, 182, 183, 5, 62, 0, 0, 183, 14, 1, 0, 0, 0, 184, 185, 5, 60, 0, 0, 185, 16, 1, 0, 0, 0, 186, 187, 5, 60, 0, 0, 187, 188, 5, 61, 0, 0, 188, 18, 1, 0, 0, 0, 189, 190, 5, 61, 0, 0, 190, 20, 1, 0, 0, 0, 191, 192, 5, 99, 0, 0, 192, 193, 5, 111, 0, 0, 193, 194, 5, 110, 0, 0, 194, 195, 5, 116, 0, 0, 195, 196, 5, 114, 0, 0, 196, 197, 5, 97, 0, 0, 197, 198, 5, 99, 0, 0, 198, 199, 5, 116, 0, 0, 199, 22, 1, 0, 0, 0, 200, 201, 5, 123, 0, 0, 201, 24, 1, 0, 0, 0, 202, 203, 5, 125, 0, 0, 203, 26, 1, 0, 0, 0, 204, 205, 5, 102, 0, 0, 205, 206, 5, 117, 0, 0, 206, 207, 5, 110, 0, 0, 207, 208, 5, 99, 0, 0, 208, 209, 5, 116, 0, 0, 209, 210, 5, 105, 0, 0, 210, 211, 5, 111, 0, 0, 211, 212, 5, 110, 0, 0, 212, 28, 1, 0, 0, 0, 213, 214, 5, 40, 0, 0, 214, 30, 1, 0, 0, 0, 215, 216, 5, 44, 0, 0, 216, 32, 1, 0, 0, 0, 217, 218, 5, 41, 0, 0, 218, 34, 1, 0, 0, 0, 219, 220, 5, 114, 0, 0, 220, 221, 5, 101, 0, 0, 221, 222, 5, 113, 0, 0, 222, 223, 5, 117, 0, 0, 223, 224, 5, 105, 0, 0, 224, 225, 5, 114, 0, 0, 225, 226, 5, 101, 0, 0, 226, 36, 1, 0, 0, 0, 227, 228, 5, 99, 0, 0, 228, 229, 5, 111, 0, 0, 229, 230, 5, 110, 0, 0, 230, 231, 5, 115, 0, 0, 231, 232, 5, 111, 0, 0, 232, 233, 5, 108, 0, 0, 233, 234, 5, 101, 0, 0, 234, 235, 5, 46, 0, 0, 235, 236, 5, 108, 0, 0, 236, 237, 5, 111, 0, 0, 237, 238, 5, 103, 0, 0, 238, 38, 1, 0, 0, 0, 239, 240, 5, 105, 0, 0, 240, 241, 5, 102, 0, 0, 241, 40, 1, 0, 0, 0, 242, 243, 5, 101, 0, 0, 243, 244, 5, 108, 0, 0, 244, 245, 5, 115, 0, 0, 245, 246, 5, 101, 0, 0, 246, 42, 1, 0, 0, 0, 247, 248, 5, 100, 0, 0, 248, 249, 5, 111, 0, 0, 249, 44, 1, 0, 0, 0, 250, 251, 5, 119, 0, 0, 251, 252, 5, 104, 0, 0, 252, 253, 5, 105, 0, 0, 253, 254, 5, 108, 0, 0, 254, 255, 5, 101, 0, 0, 255, 46, 1, 0, 0, 0, 256, 257, 5, 102, 0, 0, 257, 258, 5, 111, 0, 0, 258, 259, 5, 114, 0, 0, 259, 48, 1, 0, 0, 0, 260, 261, 5, 110, 0, 0, 261, 262, 5, 101, 0, 0, 262, 263, 5, 119, 0, 0, 263, 50, 1, 0, 0, 0, 264, 265, 5, 91, 0, 0, 265, 52, 1, 0, 0, 0, 266, 267, 5, 93, 0, 0, 267, 54, 1, 0, 0, 0, 268, 269, 5, 116, 0, 0, 269, 270, 5, 120, 0, 0, 270, 271, 5, 46, 0, 0, 271, 272, 5, 111, 0, 0, 272, 273, 5, 117, 0, 0, 273, 274, 5, 116, 0, 0, 274, 275, 5, 112, 0, 0, 275, 276, 5, 117, 0, 0, 276, 277, 5, 116, 0, 0, 277, 278, 5, 115, 0, 0, 278, 56, 1, 0, 0, 0, 279, 280, 5, 46, 0, 0, 280, 281, 5, 118, 0, 0, 281, 282, 5, 97, 0, 0, 282, 283, 5, 108, 0, 0, 283, 284, 5, 117, 0, 0, 284, 285, 5, 101, 0, 0, 285, 58, 1, 0, 0, 0, 286, 287, 5, 46, 0, 0, 287, 288, 5, 108, 0, 0, 288, 289, 5, 111, 0, 0, 289, 290, 5, 99, 0, 0, 290, 291, 5, 107, 0, 0, 291, 292, 5, 105, 0, 0, 292, 293, 5, 110, 0, 0, 293, 294, 5, 103, 0, 0, 294, 295, 5, 66, 0, 0, 295, 296, 5, 121, 0, 0, 296, 297, 5, 116, 0, 0, 297, 298, 5, 101, 0, 0, 298, 299, 5, 99, 0, 0, 299, 300, 5, 111, 0, 0, 300, 301, 5, 100, 0, 0, 301, 302, 5, 101, 0, 0, 302, 60, 1, 0, 0, 0, 303, 304, 5, 46, 0, 0, 304, 305, 5, 116, 0, 0, 305, 306, 5, 111, 0, 0, 306, 307, 5, 107, 0, 0, 307, 308, 5, 101, 0, 0, 308, 309, 5, 110, 0, 0, 309, 310, 5, 67, 0, 0, 310, 311, 5, 97, 0, 0, 311, 312, 5, 116, 0, 0, 312, 313, 5, 101, 0, 0, 313, 314, 5, 103, 0, 0, 314, 315, 5, 111, 0, 0, 315, 316, 5, 114, 0, 0, 316, 317, 5, 121, 0, 0, 317, 62, 1, 0, 0, 0, 318, 319, 5, 46, 0, 0, 319, 320, 5, 110, 0, 0, 320, 321, 5, 102, 0, 0, 321, 322, 5, 116, 0, 0, 322, 323, 5, 67, 0, 0, 323, 324, 5, 111, 0, 0, 324, 325, 5, 109, 0, 0, 325, 326, 5, 109, 0, 0, 326, 327, 5, 105, 0, 0, 327, 328, 5, 116, 0, 0, 328, 329, 5, 109, 0, 0, 329, 330, 5, 101, 0, 0, 330, 331, 5, 110, 0, 0, 331, 332, 5, 116, 0, 0, 332, 64, 1, 0, 0, 0, 333, 334, 5, 46, 0, 0, 334, 335, 5, 116, 0, 0, 335, 336, 5, 111, 0, 0, 336, 337, 5, 107, 0, 0, 337, 338, 5, 101, 0, 0, 338, 339, 5, 110, 0, 0, 339, 340, 5, 65, 0, 0, 340, 341, 5, 109, 0, 0, 341, 342, 5, 111, 0, 0, 342, 343, 5, 117, 0, 0, 343, 344, 5, 110, 0, 0, 344, 345, 5, 116, 0, 0, 345, 66, 1, 0, 0, 0, 346, 347, 5, 116, 0, 0, 347, 348, 5, 120, 0, 0, 348, 349, 5, 46, 0, 0, 349, 350, 5, 105, 0, 0, 350, 351, 5, 110, 0, 0, 351, 352, 5, 112, 0, 0, 352, 353, 5, 117, 0, 0, 353, 354, 5, 116, 0, 0, 354, 355, 5, 115, 0, 0, 355, 68, 1, 0, 0, 0, 356, 357, 5, 46, 0, 0, 357, 358, 5, 111, 0, 0, 358, 359, 5, 117, 0, 0, 359, 360, 5, 116, 0, 0, 360, 361, 5, 112, 0, 0, 361, 362, 5, 111, 0, 0, 362, 363, 5, 105, 0, 0, 363, 364, 5, 110, 0, 0, 364, 365, 5, 116, 0, 0, 365, 366, 5, 84, 0, 0, 366, 367, 5, 114, 0, 0, 367, 368, 5, 97, 0, 0, 368, 369, 5, 110, 0, 0, 369, 370, 5, 115, 0, 0, 370, 371, 5, 97, 0, 0, 371, 372, 5, 99, 0, 0, 372, 373, 5, 116, 0, 0, 373, 374, 5, 105, 0, 0, 374, 375, 5, 111, 0, 0, 375, 376, 5, 110, 0, 0, 376, 377, 5, 72, 0, 0, 377, 378, 5, 97, 0, 0, 378, 379, 5, 115, 0, 0, 379, 380, 5, 104, 0, 0, 380, 70, 1, 0, 0, 0, 381, 382, 5, 46, 0, 0, 382, 383, 5, 111, 0, 0, 383, 384, 5, 117, 0, 0, 384, 385, 5, 116, 0, 0, 385, 386, 5, 112, 0, 0, 386, 387, 5, 111, 0, 0, 387, 388, 5, 105, 0, 0, 388, 389, 5, 110, 0, 0, 389, 390, 5, 116, 0, 0, 390, 391, 5, 73, 0, 0, 391, 392, 5, 110, 0, 0, 392, 393, 5, 100, 0, 0, 393, 394, 5, 101, 0, 0, 394, 395, 5, 120, 0, 0, 395, 72, 1, 0, 0, 0, 396, 397, 5, 46, 0, 0, 397, 398, 5, 117, 0, 0, 398, 399, 5, 110, 0, 0, 399, 400, 5, 108, 0, 0, 400, 401, 5, 111, 0, 0, 401, 402, 5, 99, 0, 0, 402, 403, 5, 107, 0, 0, 403, 404, 5, 105, 0, 0, 404, 405, 5, 110, 0, 0, 405, 406, 5, 103, 0, 0, 406, 407, 5, 66, 0, 0, 407, 408, 5, 121, 0, 0, 408, 409, 5, 116, 0, 0, 409, 410, 5, 101, 0, 0, 410, 411, 5, 99, 0, 0, 411, 412, 5, 111, 0, 0, 412, 413, 5, 100, 0, 0, 413, 414, 5, 101, 0, 0, 414, 74, 1, 0, 0, 0, 415, 416, 5, 46, 0, 0, 416, 417, 5, 115, 0, 0, 417, 418, 5, 101, 0, 0, 418, 419, 5, 113, 0, 0, 419, 420, 5, 117, 0, 0, 420, 421, 5, 101, 0, 0, 421, 422, 5, 110, 0, 0, 422, 423, 5, 99, 0, 0, 423, 424, 5, 101, 0, 0, 424, 425, 5, 78, 0, 0, 425, 426, 5, 117, 0, 0, 426, 427, 5, 109, 0, 0, 427, 428, 5, 98, 0, 0, 428, 429, 5, 101, 0, 0, 429, 430, 5, 114, 0, 0, 430, 76, 1, 0, 0, 0, 431, 432, 5, 46, 0, 0, 432, 433, 5, 114, 0, 0, 433, 434, 5, 101, 0, 0, 434, 435, 5, 118, 0, 0, 435, 436, 5, 101, 0, 0, 436, 437, 5, 114, 0, 0, 437, 438, 5, 115, 0, 0, 438, 439, 5, 101, 0, 0, 439, 440, 5, 40, 0, 0, 440, 441, 5, 41, 0, 0, 441, 78, 1, 0, 0, 0, 442, 443, 5, 46, 0, 0, 443, 444, 5, 108, 0, 0, 444, 445, 5, 101, 0, 0, 445, 446, 5, 110, 0, 0, 446, 447, 5, 103, 0, 0, 447, 448, 5, 116, 0, 0, 448, 449, 5, 104, 0, 0, 449, 80, 1, 0, 0, 0, 450, 451, 5, 46, 0, 0, 451, 452, 5, 115, 0, 0, 452, 453, 5, 112, 0, 0, 453, 454, 5, 108, 0, 0, 454, 455, 5, 105, 0, 0, 455, 456, 5, 116, 0, 0, 456, 82, 1, 0, 0, 0, 457, 458, 5, 46, 0, 0, 458, 459, 5, 115, 0, 0, 459, 460, 5, 108, 0, 0, 460, 461, 5, 105, 0, 0, 461, 462, 5, 99, 0, 0, 462, 463, 5, 101, 0, 0, 463, 84, 1, 0, 0, 0, 464, 465, 5, 33, 0, 0, 465, 86, 1, 0, 0, 0, 466, 467, 5, 45, 0, 0, 467, 88, 1, 0, 0, 0, 468, 469, 5, 42, 0, 0, 469, 90, 1, 0, 0, 0, 470, 471, 5, 47, 0, 0, 471, 92, 1, 0, 0, 0, 472, 473, 5, 37, 0, 0, 473, 94, 1, 0, 0, 0, 474, 475, 5, 43, 0, 0, 475, 96, 1, 0, 0, 0, 476, 477, 5, 62, 0, 0, 477, 478, 5, 62, 0, 0, 478, 98, 1, 0, 0, 0, 479, 480, 5, 60, 0, 0, 480, 481, 5, 60, 0, 0, 481, 100, 1, 0, 0, 0, 482, 483, 5, 61, 0, 0, 483, 484, 5, 61, 0, 0, 484, 102, 1, 0, 0, 0, 485, 486, 5, 33, 0, 0, 486, 487, 5, 61, 0, 0, 487, 104, 1, 0, 0, 0, 488, 489, 5, 38, 0, 0, 489, 106, 1, 0, 0, 0, 490, 491, 5, 124, 0, 0, 491, 108, 1, 0, 0, 0, 492, 493, 5, 38, 0, 0, 493, 494, 5, 38, 0, 0, 494, 110, 1, 0, 0, 0, 495, 496, 5, 124, 0, 0, 496, 497, 5, 124, 0, 0, 497, 112, 1, 0, 0, 0, 498, 499, 5, 99, 0, 0, 499, 500, 5, 111, 0, 0, 500, 501, 5, 110, 0, 0, 501, 502, 5, 115, 0, 0, 502, 503, 5, 116, 0, 0, 503, 504, 5, 97, 0, 0, 504, 505, 5, 110, 0, 0, 505, 506, 5, 116, 0, 0, 506, 114, 1, 0, 0, 0, 507, 509, 7, 0, 0, 0, 508, 507, 1, 0, 0, 0, 509, 510, 1, 0, 0, 0, 510, 508, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 514, 5, 46, 0, 0, 513, 515, 7, 0, 0, 0, 514, 513, 1, 0, 0, 0, 515, 516, 1, 0, 0, 0, 516, 514, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 520, 5, 46, 0, 0, 519, 521, 7, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 520, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 116, 1, 0, 0, 0, 524, 525, 5, 116, 0, 0, 525, 526, 5, 114, 0, 0, 526, 527, 5, 117, 0, 0, 527, 534, 5, 101, 0, 0, 528, 529, 5, 102, 0, 0, 529, 530, 5, 97, 0, 0, 530, 531, 5, 108, 0, 0, 531, 532, 5, 115, 0, 0, 532, 534, 5, 101, 0, 0, 533, 524, 1, 0, 0, 0, 533, 528, 1, 0, 0, 0, 534, 118, 1, 0, 0, 0, 535, 536, 5, 115, 0, 0, 536, 537, 5, 97, 0, 0, 537, 538, 5, 116, 0, 0, 538, 539, 5, 111, 0, 0, 539, 540, 5, 115, 0, 0, 540, 541, 5, 104, 0, 0, 541, 542, 5, 105, 0, 0, 542, 593, 5, 115, 0, 0, 543, 544, 5, 115, 0, 0, 544, 545, 5, 97, 0, 0, 545, 546, 5, 116, 0, 0, 546, 593, 5, 115, 0, 0, 547, 548, 5, 102, 0, 0, 548, 549, 5, 105, 0, 0, 549, 550, 5, 110, 0, 0, 550, 551, 5, 110, 0, 0, 551, 552, 5, 101, 0, 0, 552, 593, 5, 121, 0, 0, 553, 554, 5, 98, 0, 0, 554, 555, 5, 105, 0, 0, 555, 556, 5, 116, 0, 0, 556, 593, 5, 115, 0, 0, 557, 558, 5, 98, 0, 0, 558, 559, 5, 105, 0, 0, 559, 560, 5, 116, 0, 0, 560, 561, 5, 99, 0, 0, 561, 562, 5, 111, 0, 0, 562, 563, 5, 105, 0, 0, 563, 593, 5, 110, 0, 0, 564, 565, 5, 115, 0, 0, 565, 566, 5, 101, 0, 0, 566, 567, 5, 99, 0, 0, 567, 568, 5, 111, 0, 0, 568, 569, 5, 110, 0, 0, 569, 570, 5, 100, 0, 0, 570, 593, 5, 115, 0, 0, 571, 572, 5, 109, 0, 0, 572, 573, 5, 105, 0, 0, 573, 574, 5, 110, 0, 0, 574, 575, 5, 117, 0, 0, 575, 576, 5, 116, 0, 0, 576, 577, 5, 101, 0, 0, 577, 593, 5, 115, 0, 0, 578, 579, 5, 104, 0, 0, 579, 580, 5, 111, 0, 0, 580, 581, 5, 117, 0, 0, 581, 582, 5, 114, 0, 0, 582, 593, 5, 115, 0, 0, 583, 584, 5, 100, 0, 0, 584, 585, 5, 97, 0, 0, 585, 586, 5, 121, 0, 0, 586, 593, 5, 115, 0, 0, 587, 588, 5, 119, 0, 0, 588, 589, 5, 101, 0, 0, 589, 590, 5, 101, 0, 0, 590, 591, 5, 107, 0, 0, 591, 593, 5, 115, 0, 0, 592, 535, 1, 0, 0, 0, 592, 543, 1, 0, 0, 0, 592, 547, 1, 0, 0, 0, 592, 553, 1, 0, 0, 0, 592, 557, 1, 0, 0, 0, 592, 564, 1, 0, 0, 0, 592, 571, 1, 0, 0, 0, 592, 578, 1, 0, 0, 0, 592, 583, 1, 0, 0, 0, 592, 587, 1, 0, 0, 0, 593, 120, 1, 0, 0, 0, 594, 596, 5, 45, 0, 0, 595, 594, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 599, 3, 123, 61, 0, 598, 600, 3, 125, 62, 0, 599, 598, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 122, 1, 0, 0, 0, 601, 603, 7, 0, 0, 0, 602, 601, 1, 0, 0, 0, 603, 604, 1, 0, 0, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 614, 1, 0, 0, 0, 606, 608, 5, 95, 0, 0, 607, 609, 7, 0, 0, 0, 608, 607, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 608, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 613, 1, 0, 0, 0, 612, 606, 1, 0, 0, 0, 613, 616, 1, 0, 0, 0, 614, 612, 1, 0, 0, 0, 614, 615, 1, 0, 0, 0, 615, 124, 1, 0, 0, 0, 616, 614, 1, 0, 0, 0, 617, 618, 7, 1, 0, 0, 618, 619, 3, 123, 61, 0, 619, 126, 1, 0, 0, 0, 620, 621, 5, 105, 0, 0, 621, 622, 5, 110, 0, 0, 622, 650, 5, 116, 0, 0, 623, 624, 5, 98, 0, 0, 624, 625, 5, 111, 0, 0, 625, 626, 5, 111, 0, 0, 626, 650, 5, 108, 0, 0, 627, 628, 5, 115, 0, 0, 628, 629, 5, 116, 0, 0, 629, 630, 5, 114, 0, 0, 630, 631, 5, 105, 0, 0, 631, 632, 5, 110, 0, 0, 632, 650, 5, 103, 0, 0, 633, 634, 5, 112, 0, 0, 634, 635, 5, 117, 0, 0, 635, 636, 5, 98, 0, 0, 636, 637, 5, 107, 0, 0, 637, 638, 5, 101, 0, 0, 638, 650, 5, 121, 0, 0, 639, 640, 5, 115, 0, 0, 640, 641, 5, 105, 0, 0, 641, 650, 5, 103, 0, 0, 642, 643, 5, 100, 0, 0, 643, 644, 5, 97, 0, 0, 644, 645, 5, 116, 0, 0, 645, 646, 5, 97, 0, 0, 646, 647, 5, 115, 0, 0, 647, 648, 5, 105, 0, 0, 648, 650, 5, 103, 0, 0, 649, 620, 1, 0, 0, 0, 649, 623, 1, 0, 0, 0, 649, 627, 1, 0, 0, 0, 649, 633, 1, 0, 0, 0, 649, 639, 1, 0, 0, 0, 649, 642, 1, 0, 0, 0, 650, 128, 1, 0, 0, 0, 651, 652, 5, 98, 0, 0, 652, 653, 5, 121, 0, 0, 653, 654, 5, 116, 0, 0, 654, 655, 5, 101, 0, 0, 655, 656, 5, 115, 0, 0, 656, 130, 1, 0, 0, 0, 657, 658, 5, 98, 0, 0, 658, 659, 5, 121, 0, 0, 659, 660, 5, 116, 0, 0, 660, 661, 5, 101, 0, 0, 661, 662, 5, 115, 0, 0, 662, 663, 1, 0, 0, 0, 663, 669, 3, 133, 66, 0, 664, 665, 5, 98, 0, 0, 665, 666, 5, 121, 0, 0, 666, 667, 5, 116, 0, 0, 667, 669, 5, 101, 0, 0, 668, 657, 1, 0, 0, 0, 668, 664, 1, 0, 0, 0, 669, 132, 1, 0, 0, 0, 670, 674, 7, 2, 0, 0, 671, 673, 7, 0, 0, 0, 672, 671, 1, 0, 0, 0, 673, 676, 1, 0, 0, 0, 674, 672, 1, 0, 0, 0, 674, 675, 1, 0, 0, 0, 675, 134, 1, 0, 0, 0, 676, 674, 1, 0, 0, 0, 677, 683, 5, 34, 0, 0, 678, 679, 5, 92, 0, 0, 679, 682, 5, 34, 0, 0, 680, 682, 8, 3, 0, 0, 681, 678, 1, 0, 0, 0, 681, 680, 1, 0, 0, 0, 682, 685, 1, 0, 0, 0, 683, 684, 1, 0, 0, 0, 683, 681, 1, 0, 0, 0, 684, 686, 1, 0, 0, 0, 685, 683, 1, 0, 0, 0, 686, 698, 5, 34, 0, 0, 687, 693, 5, 39, 0, 0, 688, 689, 5, 92, 0, 0, 689, 692, 5, 39, 0, 0, 690, 692, 8, 4, 0, 0, 691, 688, 1, 0, 0, 0, 691, 690, 1, 0, 0, 0, 692, 695, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 693, 691, 1, 0, 0, 0, 694, 696, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 696, 698, 5, 39, 0, 0, 697, 677, 1, 0, 0, 0, 697, 687, 1, 0, 0, 0, 698, 136, 1, 0, 0, 0, 699, 700, 5, 100, 0, 0, 700, 701, 5, 97, 0, 0, 701, 702, 5, 116, 0, 0, 702, 703, 5, 101, 0, 0, 703, 704, 5, 40, 0, 0, 704, 705, 1, 0, 0, 0, 705, 706, 3, 135, 67, 0, 706, 707, 5, 41, 0, 0, 707, 138, 1, 0, 0, 0, 708, 709, 5, 48, 0, 0, 709, 713, 7, 5, 0, 0, 710, 712, 7, 6, 0, 0, 711, 710, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 140, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 116, 0, 0, 717, 718, 5, 104, 0, 0, 718, 719, 5, 105, 0, 0, 719, 720, 5, 115, 0, 0, 720, 721, 5, 46, 0, 0, 721, 722, 5, 97, 0, 0, 722, 723, 5, 103, 0, 0, 723, 732, 5, 101, 0, 0, 724, 725, 5, 116, 0, 0, 725, 726, 5, 120, 0, 0, 726, 727, 5, 46, 0, 0, 727, 728, 5, 116, 0, 0, 728, 729, 5, 105, 0, 0, 729, 730, 5, 109, 0, 0, 730, 732, 5, 101, 0, 0, 731, 716, 1, 0, 0, 0, 731, 724, 1, 0, 0, 0, 732, 142, 1, 0, 0, 0, 733, 734, 5, 117, 0, 0, 734, 735, 5, 110, 0, 0, 735, 736, 5, 115, 0, 0, 736, 737, 5, 97, 0, 0, 737, 738, 5, 102, 0, 0, 738, 739, 5, 101, 0, 0, 739, 740, 5, 95, 0, 0, 740, 741, 5, 105, 0, 0, 741, 742, 5, 110, 0, 0, 742, 782, 5, 116, 0, 0, 743, 744, 5, 117, 0, 0, 744, 745, 5, 110, 0, 0, 745, 746, 5, 115, 0, 0, 746, 747, 5, 97, 0, 0, 747, 748, 5, 102, 0, 0, 748, 749, 5, 101, 0, 0, 749, 750, 5, 95, 0, 0, 750, 751, 5, 98, 0, 0, 751, 752, 5, 111, 0, 0, 752, 753, 5, 111, 0, 0, 753, 782, 5, 108, 0, 0, 754, 755, 5, 117, 0, 0, 755, 756, 5, 110, 0, 0, 756, 757, 5, 115, 0, 0, 757, 758, 5, 97, 0, 0, 758, 759, 5, 102, 0, 0, 759, 760, 5, 101, 0, 0, 760, 761, 5, 95, 0, 0, 761, 762, 5, 98, 0, 0, 762, 763, 5, 121, 0, 0, 763, 764, 5, 116, 0, 0, 764, 765, 5, 101, 0, 0, 765, 766, 5, 115, 0, 0, 766, 768, 1, 0, 0, 0, 767, 769, 3, 133, 66, 0, 768, 767, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 782, 1, 0, 0, 0, 770, 771, 5, 117, 0, 0, 771, 772, 5, 110, 0, 0, 772, 773, 5, 115, 0, 0, 773, 774, 5, 97, 0, 0, 774, 775, 5, 102, 0, 0, 775, 776, 5, 101, 0, 0, 776, 777, 5, 95, 0, 0, 777, 778, 5, 98, 0, 0, 778, 779, 5, 121, 0, 0, 779, 780, 5, 116, 0, 0, 780, 782, 5, 101, 0, 0, 781, 733, 1, 0, 0, 0, 781, 743, 1, 0, 0, 0, 781, 754, 1, 0, 0, 0, 781, 770, 1, 0, 0, 0, 782, 144, 1, 0, 0, 0, 783, 784, 5, 116, 0, 0, 784, 785, 5, 104, 0, 0, 785, 786, 5, 105, 0, 0, 786, 787, 5, 115, 0, 0, 787, 788, 5, 46, 0, 0, 788, 789, 5, 97, 0, 0, 789, 790, 5, 99, 0, 0, 790, 791, 5, 116, 0, 0, 791, 792, 5, 105, 0, 0, 792, 793, 5, 118, 0, 0, 793, 794, 5, 101, 0, 0, 794, 795, 5, 73, 0, 0, 795, 796, 5, 110, 0, 0, 796, 797, 5, 112, 0, 0, 797, 798, 5, 117, 0, 0, 798, 799, 5, 116, 0, 0, 799, 800, 5, 73, 0, 0, 800, 801, 5, 110, 0, 0, 801, 802, 5, 100, 0, 0, 802, 803, 5, 101, 0, 0, 803, 878, 5, 120, 0, 0, 804, 805, 5, 116, 0, 0, 805, 806, 5, 104, 0, 0, 806, 807, 5, 105, 0, 0, 807, 808, 5, 115, 0, 0, 808, 809, 5, 46, 0, 0, 809, 810, 5, 97, 0, 0, 810, 811, 5, 99, 0, 0, 811, 812, 5, 116, 0, 0, 812, 813, 5, 105, 0, 0, 813, 814, 5, 118, 0, 0, 814, 815, 5, 101, 0, 0, 815, 816, 5, 66, 0, 0, 816, 817, 5, 121, 0, 0, 817, 818, 5, 116, 0, 0, 818, 819, 5, 101, 0, 0, 819, 820, 5, 99, 0, 0, 820, 821, 5, 111, 0, 0, 821, 822, 5, 100, 0, 0, 822, 878, 5, 101, 0, 0, 823, 824, 5, 116, 0, 0, 824, 825, 5, 120, 0, 0, 825, 826, 5, 46, 0, 0, 826, 827, 5, 105, 0, 0, 827, 828, 5, 110, 0, 0, 828, 829, 5, 112, 0, 0, 829, 830, 5, 117, 0, 0, 830, 831, 5, 116, 0, 0, 831, 832, 5, 115, 0, 0, 832, 833, 5, 46, 0, 0, 833, 834, 5, 108, 0, 0, 834, 835, 5, 101, 0, 0, 835, 836, 5, 110, 0, 0, 836, 837, 5, 103, 0, 0, 837, 838, 5, 116, 0, 0, 838, 878, 5, 104, 0, 0, 839, 840, 5, 116, 0, 0, 840, 841, 5, 120, 0, 0, 841, 842, 5, 46, 0, 0, 842, 843, 5, 111, 0, 0, 843, 844, 5, 117, 0, 0, 844, 845, 5, 116, 0, 0, 845, 846, 5, 112, 0, 0, 846, 847, 5, 117, 0, 0, 847, 848, 5, 116, 0, 0, 848, 849, 5, 115, 0, 0, 849, 850, 5, 46, 0, 0, 850, 851, 5, 108, 0, 0, 851, 852, 5, 101, 0, 0, 852, 853, 5, 110, 0, 0, 853, 854, 5, 103, 0, 0, 854, 855, 5, 116, 0, 0, 855, 878, 5, 104, 0, 0, 856, 857, 5, 116, 0, 0, 857, 858, 5, 120, 0, 0, 858, 859, 5, 46, 0, 0, 859, 860, 5, 118, 0, 0, 860, 861, 5, 101, 0, 0, 861, 862, 5, 114, 0, 0, 862, 863, 5, 115, 0, 0, 863, 864, 5, 105, 0, 0, 864, 865, 5, 111, 0, 0, 865, 878, 5, 110, 0, 0, 866, 867, 5, 116, 0, 0, 867, 868, 5, 120, 0, 0, 868, 869, 5, 46, 0, 0, 869, 870, 5, 108, 0, 0, 870, 871, 5, 111, 0, 0, 871, 872, 5, 99, 0, 0, 872, 873, 5, 107, 0, 0, 873, 874, 5, 116, 0, 0, 874, 875, 5, 105, 0, 0, 875, 876, 5, 109, 0, 0, 876, 878, 5, 101, 0, 0, 877, 783, 1, 0, 0, 0, 877, 804, 1, 0, 0, 0, 877, 823, 1, 0, 0, 0, 877, 839, 1, 0, 0, 0, 877, 856, 1, 0, 0, 0, 877, 866, 1, 0, 0, 0, 878, 146, 1, 0, 0, 0, 879, 883, 7, 7, 0, 0, 880, 882, 7, 8, 0, 0, 881, 880, 1, 0, 0, 0, 882, 885, 1, 0, 0, 0, 883, 881, 1, 0, 0, 0, 883, 884, 1, 0, 0, 0, 884, 148, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 886, 888, 7, 9, 0, 0, 887, 886, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 887, 1, 0, 0, 0, 889, 890, 1, 0, 0, 0, 890, 891, 1, 0, 0, 0, 891, 892, 6, 74, 0, 0, 892, 150, 1, 0, 0, 0, 893, 894, 5, 47, 0, 0, 894, 895, 5, 42, 0, 0, 895, 899, 1, 0, 0, 0, 896, 898, 9, 0, 0, 0, 897, 896, 1, 0, 0, 0, 898, 901, 1, 0, 0, 0, 899, 900, 1, 0, 0, 0, 899, 897, 1, 0, 0, 0, 900, 902, 1, 0, 0, 0, 901, 899, 1, 0, 0, 0, 902, 903, 5, 42, 0, 0, 903, 904, 5, 47, 0, 0, 904, 905, 1, 0, 0, 0, 905, 906, 6, 75, 1, 0, 906, 152, 1, 0, 0, 0, 907, 908, 5, 47, 0, 0, 908, 909, 5, 47, 0, 0, 909, 913, 1, 0, 0, 0, 910, 912, 8, 10, 0, 0, 911, 910, 1, 0, 0, 0, 912, 915, 1, 0, 0, 0, 913, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 913, 1, 0, 0, 0, 916, 917, 6, 76, 1, 0, 917, 154, 1, 0, 0, 0, 28, 0, 510, 516, 522, 533, 592, 595, 599, 604, 610, 614, 649, 668, 674, 681, 683, 691, 693, 697, 713, 731, 768, 781, 877, 883, 889, 899, 913, 2, 6, 0, 0, 0, 1, 0] \ No newline at end of file +[4, 0, 78, 928, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 4, 58, 519, 8, 58, 11, 58, 12, 58, 520, 1, 58, 1, 58, 4, 58, 525, 8, 58, 11, 58, 12, 58, 526, 1, 58, 1, 58, 4, 58, 531, 8, 58, 11, 58, 12, 58, 532, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 544, 8, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 603, 8, 60, 1, 61, 3, 61, 606, 8, 61, 1, 61, 1, 61, 3, 61, 610, 8, 61, 1, 62, 4, 62, 613, 8, 62, 11, 62, 12, 62, 614, 1, 62, 1, 62, 4, 62, 619, 8, 62, 11, 62, 12, 62, 620, 5, 62, 623, 8, 62, 10, 62, 12, 62, 626, 9, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 660, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 679, 8, 66, 1, 67, 1, 67, 5, 67, 683, 8, 67, 10, 67, 12, 67, 686, 9, 67, 1, 68, 1, 68, 1, 68, 1, 68, 5, 68, 692, 8, 68, 10, 68, 12, 68, 695, 9, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 5, 68, 702, 8, 68, 10, 68, 12, 68, 705, 9, 68, 1, 68, 3, 68, 708, 8, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 5, 70, 722, 8, 70, 10, 70, 12, 70, 725, 9, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 3, 71, 742, 8, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 3, 72, 779, 8, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 3, 72, 792, 8, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 3, 73, 888, 8, 73, 1, 74, 1, 74, 5, 74, 892, 8, 74, 10, 74, 12, 74, 895, 9, 74, 1, 75, 4, 75, 898, 8, 75, 11, 75, 12, 75, 899, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 5, 76, 908, 8, 76, 10, 76, 12, 76, 911, 9, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 5, 77, 922, 8, 77, 10, 77, 12, 77, 925, 9, 77, 1, 77, 1, 77, 3, 693, 703, 909, 0, 78, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, 74, 149, 75, 151, 76, 153, 77, 155, 78, 1, 0, 11, 1, 0, 48, 57, 2, 0, 69, 69, 101, 101, 1, 0, 49, 57, 3, 0, 10, 10, 13, 13, 34, 34, 3, 0, 10, 10, 13, 13, 39, 39, 2, 0, 88, 88, 120, 120, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 12, 13, 32, 32, 2, 0, 10, 10, 13, 13, 972, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 0, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 3, 164, 1, 0, 0, 0, 5, 166, 1, 0, 0, 0, 7, 177, 1, 0, 0, 0, 9, 179, 1, 0, 0, 0, 11, 181, 1, 0, 0, 0, 13, 184, 1, 0, 0, 0, 15, 186, 1, 0, 0, 0, 17, 188, 1, 0, 0, 0, 19, 191, 1, 0, 0, 0, 21, 193, 1, 0, 0, 0, 23, 202, 1, 0, 0, 0, 25, 204, 1, 0, 0, 0, 27, 206, 1, 0, 0, 0, 29, 214, 1, 0, 0, 0, 31, 223, 1, 0, 0, 0, 33, 225, 1, 0, 0, 0, 35, 227, 1, 0, 0, 0, 37, 229, 1, 0, 0, 0, 39, 237, 1, 0, 0, 0, 41, 249, 1, 0, 0, 0, 43, 252, 1, 0, 0, 0, 45, 257, 1, 0, 0, 0, 47, 260, 1, 0, 0, 0, 49, 266, 1, 0, 0, 0, 51, 270, 1, 0, 0, 0, 53, 274, 1, 0, 0, 0, 55, 276, 1, 0, 0, 0, 57, 278, 1, 0, 0, 0, 59, 289, 1, 0, 0, 0, 61, 296, 1, 0, 0, 0, 63, 313, 1, 0, 0, 0, 65, 328, 1, 0, 0, 0, 67, 343, 1, 0, 0, 0, 69, 356, 1, 0, 0, 0, 71, 366, 1, 0, 0, 0, 73, 391, 1, 0, 0, 0, 75, 406, 1, 0, 0, 0, 77, 425, 1, 0, 0, 0, 79, 441, 1, 0, 0, 0, 81, 452, 1, 0, 0, 0, 83, 460, 1, 0, 0, 0, 85, 467, 1, 0, 0, 0, 87, 474, 1, 0, 0, 0, 89, 476, 1, 0, 0, 0, 91, 478, 1, 0, 0, 0, 93, 480, 1, 0, 0, 0, 95, 482, 1, 0, 0, 0, 97, 484, 1, 0, 0, 0, 99, 486, 1, 0, 0, 0, 101, 489, 1, 0, 0, 0, 103, 492, 1, 0, 0, 0, 105, 495, 1, 0, 0, 0, 107, 498, 1, 0, 0, 0, 109, 500, 1, 0, 0, 0, 111, 502, 1, 0, 0, 0, 113, 505, 1, 0, 0, 0, 115, 508, 1, 0, 0, 0, 117, 518, 1, 0, 0, 0, 119, 543, 1, 0, 0, 0, 121, 602, 1, 0, 0, 0, 123, 605, 1, 0, 0, 0, 125, 612, 1, 0, 0, 0, 127, 627, 1, 0, 0, 0, 129, 659, 1, 0, 0, 0, 131, 661, 1, 0, 0, 0, 133, 678, 1, 0, 0, 0, 135, 680, 1, 0, 0, 0, 137, 707, 1, 0, 0, 0, 139, 709, 1, 0, 0, 0, 141, 718, 1, 0, 0, 0, 143, 741, 1, 0, 0, 0, 145, 791, 1, 0, 0, 0, 147, 887, 1, 0, 0, 0, 149, 889, 1, 0, 0, 0, 151, 897, 1, 0, 0, 0, 153, 903, 1, 0, 0, 0, 155, 917, 1, 0, 0, 0, 157, 158, 5, 112, 0, 0, 158, 159, 5, 114, 0, 0, 159, 160, 5, 97, 0, 0, 160, 161, 5, 103, 0, 0, 161, 162, 5, 109, 0, 0, 162, 163, 5, 97, 0, 0, 163, 2, 1, 0, 0, 0, 164, 165, 5, 59, 0, 0, 165, 4, 1, 0, 0, 0, 166, 167, 5, 99, 0, 0, 167, 168, 5, 97, 0, 0, 168, 169, 5, 115, 0, 0, 169, 170, 5, 104, 0, 0, 170, 171, 5, 115, 0, 0, 171, 172, 5, 99, 0, 0, 172, 173, 5, 114, 0, 0, 173, 174, 5, 105, 0, 0, 174, 175, 5, 112, 0, 0, 175, 176, 5, 116, 0, 0, 176, 6, 1, 0, 0, 0, 177, 178, 5, 94, 0, 0, 178, 8, 1, 0, 0, 0, 179, 180, 5, 126, 0, 0, 180, 10, 1, 0, 0, 0, 181, 182, 5, 62, 0, 0, 182, 183, 5, 61, 0, 0, 183, 12, 1, 0, 0, 0, 184, 185, 5, 62, 0, 0, 185, 14, 1, 0, 0, 0, 186, 187, 5, 60, 0, 0, 187, 16, 1, 0, 0, 0, 188, 189, 5, 60, 0, 0, 189, 190, 5, 61, 0, 0, 190, 18, 1, 0, 0, 0, 191, 192, 5, 61, 0, 0, 192, 20, 1, 0, 0, 0, 193, 194, 5, 99, 0, 0, 194, 195, 5, 111, 0, 0, 195, 196, 5, 110, 0, 0, 196, 197, 5, 116, 0, 0, 197, 198, 5, 114, 0, 0, 198, 199, 5, 97, 0, 0, 199, 200, 5, 99, 0, 0, 200, 201, 5, 116, 0, 0, 201, 22, 1, 0, 0, 0, 202, 203, 5, 123, 0, 0, 203, 24, 1, 0, 0, 0, 204, 205, 5, 125, 0, 0, 205, 26, 1, 0, 0, 0, 206, 207, 5, 108, 0, 0, 207, 208, 5, 105, 0, 0, 208, 209, 5, 98, 0, 0, 209, 210, 5, 114, 0, 0, 210, 211, 5, 97, 0, 0, 211, 212, 5, 114, 0, 0, 212, 213, 5, 121, 0, 0, 213, 28, 1, 0, 0, 0, 214, 215, 5, 102, 0, 0, 215, 216, 5, 117, 0, 0, 216, 217, 5, 110, 0, 0, 217, 218, 5, 99, 0, 0, 218, 219, 5, 116, 0, 0, 219, 220, 5, 105, 0, 0, 220, 221, 5, 111, 0, 0, 221, 222, 5, 110, 0, 0, 222, 30, 1, 0, 0, 0, 223, 224, 5, 40, 0, 0, 224, 32, 1, 0, 0, 0, 225, 226, 5, 44, 0, 0, 226, 34, 1, 0, 0, 0, 227, 228, 5, 41, 0, 0, 228, 36, 1, 0, 0, 0, 229, 230, 5, 114, 0, 0, 230, 231, 5, 101, 0, 0, 231, 232, 5, 113, 0, 0, 232, 233, 5, 117, 0, 0, 233, 234, 5, 105, 0, 0, 234, 235, 5, 114, 0, 0, 235, 236, 5, 101, 0, 0, 236, 38, 1, 0, 0, 0, 237, 238, 5, 99, 0, 0, 238, 239, 5, 111, 0, 0, 239, 240, 5, 110, 0, 0, 240, 241, 5, 115, 0, 0, 241, 242, 5, 111, 0, 0, 242, 243, 5, 108, 0, 0, 243, 244, 5, 101, 0, 0, 244, 245, 5, 46, 0, 0, 245, 246, 5, 108, 0, 0, 246, 247, 5, 111, 0, 0, 247, 248, 5, 103, 0, 0, 248, 40, 1, 0, 0, 0, 249, 250, 5, 105, 0, 0, 250, 251, 5, 102, 0, 0, 251, 42, 1, 0, 0, 0, 252, 253, 5, 101, 0, 0, 253, 254, 5, 108, 0, 0, 254, 255, 5, 115, 0, 0, 255, 256, 5, 101, 0, 0, 256, 44, 1, 0, 0, 0, 257, 258, 5, 100, 0, 0, 258, 259, 5, 111, 0, 0, 259, 46, 1, 0, 0, 0, 260, 261, 5, 119, 0, 0, 261, 262, 5, 104, 0, 0, 262, 263, 5, 105, 0, 0, 263, 264, 5, 108, 0, 0, 264, 265, 5, 101, 0, 0, 265, 48, 1, 0, 0, 0, 266, 267, 5, 102, 0, 0, 267, 268, 5, 111, 0, 0, 268, 269, 5, 114, 0, 0, 269, 50, 1, 0, 0, 0, 270, 271, 5, 110, 0, 0, 271, 272, 5, 101, 0, 0, 272, 273, 5, 119, 0, 0, 273, 52, 1, 0, 0, 0, 274, 275, 5, 91, 0, 0, 275, 54, 1, 0, 0, 0, 276, 277, 5, 93, 0, 0, 277, 56, 1, 0, 0, 0, 278, 279, 5, 116, 0, 0, 279, 280, 5, 120, 0, 0, 280, 281, 5, 46, 0, 0, 281, 282, 5, 111, 0, 0, 282, 283, 5, 117, 0, 0, 283, 284, 5, 116, 0, 0, 284, 285, 5, 112, 0, 0, 285, 286, 5, 117, 0, 0, 286, 287, 5, 116, 0, 0, 287, 288, 5, 115, 0, 0, 288, 58, 1, 0, 0, 0, 289, 290, 5, 46, 0, 0, 290, 291, 5, 118, 0, 0, 291, 292, 5, 97, 0, 0, 292, 293, 5, 108, 0, 0, 293, 294, 5, 117, 0, 0, 294, 295, 5, 101, 0, 0, 295, 60, 1, 0, 0, 0, 296, 297, 5, 46, 0, 0, 297, 298, 5, 108, 0, 0, 298, 299, 5, 111, 0, 0, 299, 300, 5, 99, 0, 0, 300, 301, 5, 107, 0, 0, 301, 302, 5, 105, 0, 0, 302, 303, 5, 110, 0, 0, 303, 304, 5, 103, 0, 0, 304, 305, 5, 66, 0, 0, 305, 306, 5, 121, 0, 0, 306, 307, 5, 116, 0, 0, 307, 308, 5, 101, 0, 0, 308, 309, 5, 99, 0, 0, 309, 310, 5, 111, 0, 0, 310, 311, 5, 100, 0, 0, 311, 312, 5, 101, 0, 0, 312, 62, 1, 0, 0, 0, 313, 314, 5, 46, 0, 0, 314, 315, 5, 116, 0, 0, 315, 316, 5, 111, 0, 0, 316, 317, 5, 107, 0, 0, 317, 318, 5, 101, 0, 0, 318, 319, 5, 110, 0, 0, 319, 320, 5, 67, 0, 0, 320, 321, 5, 97, 0, 0, 321, 322, 5, 116, 0, 0, 322, 323, 5, 101, 0, 0, 323, 324, 5, 103, 0, 0, 324, 325, 5, 111, 0, 0, 325, 326, 5, 114, 0, 0, 326, 327, 5, 121, 0, 0, 327, 64, 1, 0, 0, 0, 328, 329, 5, 46, 0, 0, 329, 330, 5, 110, 0, 0, 330, 331, 5, 102, 0, 0, 331, 332, 5, 116, 0, 0, 332, 333, 5, 67, 0, 0, 333, 334, 5, 111, 0, 0, 334, 335, 5, 109, 0, 0, 335, 336, 5, 109, 0, 0, 336, 337, 5, 105, 0, 0, 337, 338, 5, 116, 0, 0, 338, 339, 5, 109, 0, 0, 339, 340, 5, 101, 0, 0, 340, 341, 5, 110, 0, 0, 341, 342, 5, 116, 0, 0, 342, 66, 1, 0, 0, 0, 343, 344, 5, 46, 0, 0, 344, 345, 5, 116, 0, 0, 345, 346, 5, 111, 0, 0, 346, 347, 5, 107, 0, 0, 347, 348, 5, 101, 0, 0, 348, 349, 5, 110, 0, 0, 349, 350, 5, 65, 0, 0, 350, 351, 5, 109, 0, 0, 351, 352, 5, 111, 0, 0, 352, 353, 5, 117, 0, 0, 353, 354, 5, 110, 0, 0, 354, 355, 5, 116, 0, 0, 355, 68, 1, 0, 0, 0, 356, 357, 5, 116, 0, 0, 357, 358, 5, 120, 0, 0, 358, 359, 5, 46, 0, 0, 359, 360, 5, 105, 0, 0, 360, 361, 5, 110, 0, 0, 361, 362, 5, 112, 0, 0, 362, 363, 5, 117, 0, 0, 363, 364, 5, 116, 0, 0, 364, 365, 5, 115, 0, 0, 365, 70, 1, 0, 0, 0, 366, 367, 5, 46, 0, 0, 367, 368, 5, 111, 0, 0, 368, 369, 5, 117, 0, 0, 369, 370, 5, 116, 0, 0, 370, 371, 5, 112, 0, 0, 371, 372, 5, 111, 0, 0, 372, 373, 5, 105, 0, 0, 373, 374, 5, 110, 0, 0, 374, 375, 5, 116, 0, 0, 375, 376, 5, 84, 0, 0, 376, 377, 5, 114, 0, 0, 377, 378, 5, 97, 0, 0, 378, 379, 5, 110, 0, 0, 379, 380, 5, 115, 0, 0, 380, 381, 5, 97, 0, 0, 381, 382, 5, 99, 0, 0, 382, 383, 5, 116, 0, 0, 383, 384, 5, 105, 0, 0, 384, 385, 5, 111, 0, 0, 385, 386, 5, 110, 0, 0, 386, 387, 5, 72, 0, 0, 387, 388, 5, 97, 0, 0, 388, 389, 5, 115, 0, 0, 389, 390, 5, 104, 0, 0, 390, 72, 1, 0, 0, 0, 391, 392, 5, 46, 0, 0, 392, 393, 5, 111, 0, 0, 393, 394, 5, 117, 0, 0, 394, 395, 5, 116, 0, 0, 395, 396, 5, 112, 0, 0, 396, 397, 5, 111, 0, 0, 397, 398, 5, 105, 0, 0, 398, 399, 5, 110, 0, 0, 399, 400, 5, 116, 0, 0, 400, 401, 5, 73, 0, 0, 401, 402, 5, 110, 0, 0, 402, 403, 5, 100, 0, 0, 403, 404, 5, 101, 0, 0, 404, 405, 5, 120, 0, 0, 405, 74, 1, 0, 0, 0, 406, 407, 5, 46, 0, 0, 407, 408, 5, 117, 0, 0, 408, 409, 5, 110, 0, 0, 409, 410, 5, 108, 0, 0, 410, 411, 5, 111, 0, 0, 411, 412, 5, 99, 0, 0, 412, 413, 5, 107, 0, 0, 413, 414, 5, 105, 0, 0, 414, 415, 5, 110, 0, 0, 415, 416, 5, 103, 0, 0, 416, 417, 5, 66, 0, 0, 417, 418, 5, 121, 0, 0, 418, 419, 5, 116, 0, 0, 419, 420, 5, 101, 0, 0, 420, 421, 5, 99, 0, 0, 421, 422, 5, 111, 0, 0, 422, 423, 5, 100, 0, 0, 423, 424, 5, 101, 0, 0, 424, 76, 1, 0, 0, 0, 425, 426, 5, 46, 0, 0, 426, 427, 5, 115, 0, 0, 427, 428, 5, 101, 0, 0, 428, 429, 5, 113, 0, 0, 429, 430, 5, 117, 0, 0, 430, 431, 5, 101, 0, 0, 431, 432, 5, 110, 0, 0, 432, 433, 5, 99, 0, 0, 433, 434, 5, 101, 0, 0, 434, 435, 5, 78, 0, 0, 435, 436, 5, 117, 0, 0, 436, 437, 5, 109, 0, 0, 437, 438, 5, 98, 0, 0, 438, 439, 5, 101, 0, 0, 439, 440, 5, 114, 0, 0, 440, 78, 1, 0, 0, 0, 441, 442, 5, 46, 0, 0, 442, 443, 5, 114, 0, 0, 443, 444, 5, 101, 0, 0, 444, 445, 5, 118, 0, 0, 445, 446, 5, 101, 0, 0, 446, 447, 5, 114, 0, 0, 447, 448, 5, 115, 0, 0, 448, 449, 5, 101, 0, 0, 449, 450, 5, 40, 0, 0, 450, 451, 5, 41, 0, 0, 451, 80, 1, 0, 0, 0, 452, 453, 5, 46, 0, 0, 453, 454, 5, 108, 0, 0, 454, 455, 5, 101, 0, 0, 455, 456, 5, 110, 0, 0, 456, 457, 5, 103, 0, 0, 457, 458, 5, 116, 0, 0, 458, 459, 5, 104, 0, 0, 459, 82, 1, 0, 0, 0, 460, 461, 5, 46, 0, 0, 461, 462, 5, 115, 0, 0, 462, 463, 5, 112, 0, 0, 463, 464, 5, 108, 0, 0, 464, 465, 5, 105, 0, 0, 465, 466, 5, 116, 0, 0, 466, 84, 1, 0, 0, 0, 467, 468, 5, 46, 0, 0, 468, 469, 5, 115, 0, 0, 469, 470, 5, 108, 0, 0, 470, 471, 5, 105, 0, 0, 471, 472, 5, 99, 0, 0, 472, 473, 5, 101, 0, 0, 473, 86, 1, 0, 0, 0, 474, 475, 5, 33, 0, 0, 475, 88, 1, 0, 0, 0, 476, 477, 5, 45, 0, 0, 477, 90, 1, 0, 0, 0, 478, 479, 5, 42, 0, 0, 479, 92, 1, 0, 0, 0, 480, 481, 5, 47, 0, 0, 481, 94, 1, 0, 0, 0, 482, 483, 5, 37, 0, 0, 483, 96, 1, 0, 0, 0, 484, 485, 5, 43, 0, 0, 485, 98, 1, 0, 0, 0, 486, 487, 5, 62, 0, 0, 487, 488, 5, 62, 0, 0, 488, 100, 1, 0, 0, 0, 489, 490, 5, 60, 0, 0, 490, 491, 5, 60, 0, 0, 491, 102, 1, 0, 0, 0, 492, 493, 5, 61, 0, 0, 493, 494, 5, 61, 0, 0, 494, 104, 1, 0, 0, 0, 495, 496, 5, 33, 0, 0, 496, 497, 5, 61, 0, 0, 497, 106, 1, 0, 0, 0, 498, 499, 5, 38, 0, 0, 499, 108, 1, 0, 0, 0, 500, 501, 5, 124, 0, 0, 501, 110, 1, 0, 0, 0, 502, 503, 5, 38, 0, 0, 503, 504, 5, 38, 0, 0, 504, 112, 1, 0, 0, 0, 505, 506, 5, 124, 0, 0, 506, 507, 5, 124, 0, 0, 507, 114, 1, 0, 0, 0, 508, 509, 5, 99, 0, 0, 509, 510, 5, 111, 0, 0, 510, 511, 5, 110, 0, 0, 511, 512, 5, 115, 0, 0, 512, 513, 5, 116, 0, 0, 513, 514, 5, 97, 0, 0, 514, 515, 5, 110, 0, 0, 515, 516, 5, 116, 0, 0, 516, 116, 1, 0, 0, 0, 517, 519, 7, 0, 0, 0, 518, 517, 1, 0, 0, 0, 519, 520, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 521, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 524, 5, 46, 0, 0, 523, 525, 7, 0, 0, 0, 524, 523, 1, 0, 0, 0, 525, 526, 1, 0, 0, 0, 526, 524, 1, 0, 0, 0, 526, 527, 1, 0, 0, 0, 527, 528, 1, 0, 0, 0, 528, 530, 5, 46, 0, 0, 529, 531, 7, 0, 0, 0, 530, 529, 1, 0, 0, 0, 531, 532, 1, 0, 0, 0, 532, 530, 1, 0, 0, 0, 532, 533, 1, 0, 0, 0, 533, 118, 1, 0, 0, 0, 534, 535, 5, 116, 0, 0, 535, 536, 5, 114, 0, 0, 536, 537, 5, 117, 0, 0, 537, 544, 5, 101, 0, 0, 538, 539, 5, 102, 0, 0, 539, 540, 5, 97, 0, 0, 540, 541, 5, 108, 0, 0, 541, 542, 5, 115, 0, 0, 542, 544, 5, 101, 0, 0, 543, 534, 1, 0, 0, 0, 543, 538, 1, 0, 0, 0, 544, 120, 1, 0, 0, 0, 545, 546, 5, 115, 0, 0, 546, 547, 5, 97, 0, 0, 547, 548, 5, 116, 0, 0, 548, 549, 5, 111, 0, 0, 549, 550, 5, 115, 0, 0, 550, 551, 5, 104, 0, 0, 551, 552, 5, 105, 0, 0, 552, 603, 5, 115, 0, 0, 553, 554, 5, 115, 0, 0, 554, 555, 5, 97, 0, 0, 555, 556, 5, 116, 0, 0, 556, 603, 5, 115, 0, 0, 557, 558, 5, 102, 0, 0, 558, 559, 5, 105, 0, 0, 559, 560, 5, 110, 0, 0, 560, 561, 5, 110, 0, 0, 561, 562, 5, 101, 0, 0, 562, 603, 5, 121, 0, 0, 563, 564, 5, 98, 0, 0, 564, 565, 5, 105, 0, 0, 565, 566, 5, 116, 0, 0, 566, 603, 5, 115, 0, 0, 567, 568, 5, 98, 0, 0, 568, 569, 5, 105, 0, 0, 569, 570, 5, 116, 0, 0, 570, 571, 5, 99, 0, 0, 571, 572, 5, 111, 0, 0, 572, 573, 5, 105, 0, 0, 573, 603, 5, 110, 0, 0, 574, 575, 5, 115, 0, 0, 575, 576, 5, 101, 0, 0, 576, 577, 5, 99, 0, 0, 577, 578, 5, 111, 0, 0, 578, 579, 5, 110, 0, 0, 579, 580, 5, 100, 0, 0, 580, 603, 5, 115, 0, 0, 581, 582, 5, 109, 0, 0, 582, 583, 5, 105, 0, 0, 583, 584, 5, 110, 0, 0, 584, 585, 5, 117, 0, 0, 585, 586, 5, 116, 0, 0, 586, 587, 5, 101, 0, 0, 587, 603, 5, 115, 0, 0, 588, 589, 5, 104, 0, 0, 589, 590, 5, 111, 0, 0, 590, 591, 5, 117, 0, 0, 591, 592, 5, 114, 0, 0, 592, 603, 5, 115, 0, 0, 593, 594, 5, 100, 0, 0, 594, 595, 5, 97, 0, 0, 595, 596, 5, 121, 0, 0, 596, 603, 5, 115, 0, 0, 597, 598, 5, 119, 0, 0, 598, 599, 5, 101, 0, 0, 599, 600, 5, 101, 0, 0, 600, 601, 5, 107, 0, 0, 601, 603, 5, 115, 0, 0, 602, 545, 1, 0, 0, 0, 602, 553, 1, 0, 0, 0, 602, 557, 1, 0, 0, 0, 602, 563, 1, 0, 0, 0, 602, 567, 1, 0, 0, 0, 602, 574, 1, 0, 0, 0, 602, 581, 1, 0, 0, 0, 602, 588, 1, 0, 0, 0, 602, 593, 1, 0, 0, 0, 602, 597, 1, 0, 0, 0, 603, 122, 1, 0, 0, 0, 604, 606, 5, 45, 0, 0, 605, 604, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 609, 3, 125, 62, 0, 608, 610, 3, 127, 63, 0, 609, 608, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 124, 1, 0, 0, 0, 611, 613, 7, 0, 0, 0, 612, 611, 1, 0, 0, 0, 613, 614, 1, 0, 0, 0, 614, 612, 1, 0, 0, 0, 614, 615, 1, 0, 0, 0, 615, 624, 1, 0, 0, 0, 616, 618, 5, 95, 0, 0, 617, 619, 7, 0, 0, 0, 618, 617, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 623, 1, 0, 0, 0, 622, 616, 1, 0, 0, 0, 623, 626, 1, 0, 0, 0, 624, 622, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 126, 1, 0, 0, 0, 626, 624, 1, 0, 0, 0, 627, 628, 7, 1, 0, 0, 628, 629, 3, 125, 62, 0, 629, 128, 1, 0, 0, 0, 630, 631, 5, 105, 0, 0, 631, 632, 5, 110, 0, 0, 632, 660, 5, 116, 0, 0, 633, 634, 5, 98, 0, 0, 634, 635, 5, 111, 0, 0, 635, 636, 5, 111, 0, 0, 636, 660, 5, 108, 0, 0, 637, 638, 5, 115, 0, 0, 638, 639, 5, 116, 0, 0, 639, 640, 5, 114, 0, 0, 640, 641, 5, 105, 0, 0, 641, 642, 5, 110, 0, 0, 642, 660, 5, 103, 0, 0, 643, 644, 5, 112, 0, 0, 644, 645, 5, 117, 0, 0, 645, 646, 5, 98, 0, 0, 646, 647, 5, 107, 0, 0, 647, 648, 5, 101, 0, 0, 648, 660, 5, 121, 0, 0, 649, 650, 5, 115, 0, 0, 650, 651, 5, 105, 0, 0, 651, 660, 5, 103, 0, 0, 652, 653, 5, 100, 0, 0, 653, 654, 5, 97, 0, 0, 654, 655, 5, 116, 0, 0, 655, 656, 5, 97, 0, 0, 656, 657, 5, 115, 0, 0, 657, 658, 5, 105, 0, 0, 658, 660, 5, 103, 0, 0, 659, 630, 1, 0, 0, 0, 659, 633, 1, 0, 0, 0, 659, 637, 1, 0, 0, 0, 659, 643, 1, 0, 0, 0, 659, 649, 1, 0, 0, 0, 659, 652, 1, 0, 0, 0, 660, 130, 1, 0, 0, 0, 661, 662, 5, 98, 0, 0, 662, 663, 5, 121, 0, 0, 663, 664, 5, 116, 0, 0, 664, 665, 5, 101, 0, 0, 665, 666, 5, 115, 0, 0, 666, 132, 1, 0, 0, 0, 667, 668, 5, 98, 0, 0, 668, 669, 5, 121, 0, 0, 669, 670, 5, 116, 0, 0, 670, 671, 5, 101, 0, 0, 671, 672, 5, 115, 0, 0, 672, 673, 1, 0, 0, 0, 673, 679, 3, 135, 67, 0, 674, 675, 5, 98, 0, 0, 675, 676, 5, 121, 0, 0, 676, 677, 5, 116, 0, 0, 677, 679, 5, 101, 0, 0, 678, 667, 1, 0, 0, 0, 678, 674, 1, 0, 0, 0, 679, 134, 1, 0, 0, 0, 680, 684, 7, 2, 0, 0, 681, 683, 7, 0, 0, 0, 682, 681, 1, 0, 0, 0, 683, 686, 1, 0, 0, 0, 684, 682, 1, 0, 0, 0, 684, 685, 1, 0, 0, 0, 685, 136, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 687, 693, 5, 34, 0, 0, 688, 689, 5, 92, 0, 0, 689, 692, 5, 34, 0, 0, 690, 692, 8, 3, 0, 0, 691, 688, 1, 0, 0, 0, 691, 690, 1, 0, 0, 0, 692, 695, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 693, 691, 1, 0, 0, 0, 694, 696, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 696, 708, 5, 34, 0, 0, 697, 703, 5, 39, 0, 0, 698, 699, 5, 92, 0, 0, 699, 702, 5, 39, 0, 0, 700, 702, 8, 4, 0, 0, 701, 698, 1, 0, 0, 0, 701, 700, 1, 0, 0, 0, 702, 705, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 704, 706, 1, 0, 0, 0, 705, 703, 1, 0, 0, 0, 706, 708, 5, 39, 0, 0, 707, 687, 1, 0, 0, 0, 707, 697, 1, 0, 0, 0, 708, 138, 1, 0, 0, 0, 709, 710, 5, 100, 0, 0, 710, 711, 5, 97, 0, 0, 711, 712, 5, 116, 0, 0, 712, 713, 5, 101, 0, 0, 713, 714, 5, 40, 0, 0, 714, 715, 1, 0, 0, 0, 715, 716, 3, 137, 68, 0, 716, 717, 5, 41, 0, 0, 717, 140, 1, 0, 0, 0, 718, 719, 5, 48, 0, 0, 719, 723, 7, 5, 0, 0, 720, 722, 7, 6, 0, 0, 721, 720, 1, 0, 0, 0, 722, 725, 1, 0, 0, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 142, 1, 0, 0, 0, 725, 723, 1, 0, 0, 0, 726, 727, 5, 116, 0, 0, 727, 728, 5, 104, 0, 0, 728, 729, 5, 105, 0, 0, 729, 730, 5, 115, 0, 0, 730, 731, 5, 46, 0, 0, 731, 732, 5, 97, 0, 0, 732, 733, 5, 103, 0, 0, 733, 742, 5, 101, 0, 0, 734, 735, 5, 116, 0, 0, 735, 736, 5, 120, 0, 0, 736, 737, 5, 46, 0, 0, 737, 738, 5, 116, 0, 0, 738, 739, 5, 105, 0, 0, 739, 740, 5, 109, 0, 0, 740, 742, 5, 101, 0, 0, 741, 726, 1, 0, 0, 0, 741, 734, 1, 0, 0, 0, 742, 144, 1, 0, 0, 0, 743, 744, 5, 117, 0, 0, 744, 745, 5, 110, 0, 0, 745, 746, 5, 115, 0, 0, 746, 747, 5, 97, 0, 0, 747, 748, 5, 102, 0, 0, 748, 749, 5, 101, 0, 0, 749, 750, 5, 95, 0, 0, 750, 751, 5, 105, 0, 0, 751, 752, 5, 110, 0, 0, 752, 792, 5, 116, 0, 0, 753, 754, 5, 117, 0, 0, 754, 755, 5, 110, 0, 0, 755, 756, 5, 115, 0, 0, 756, 757, 5, 97, 0, 0, 757, 758, 5, 102, 0, 0, 758, 759, 5, 101, 0, 0, 759, 760, 5, 95, 0, 0, 760, 761, 5, 98, 0, 0, 761, 762, 5, 111, 0, 0, 762, 763, 5, 111, 0, 0, 763, 792, 5, 108, 0, 0, 764, 765, 5, 117, 0, 0, 765, 766, 5, 110, 0, 0, 766, 767, 5, 115, 0, 0, 767, 768, 5, 97, 0, 0, 768, 769, 5, 102, 0, 0, 769, 770, 5, 101, 0, 0, 770, 771, 5, 95, 0, 0, 771, 772, 5, 98, 0, 0, 772, 773, 5, 121, 0, 0, 773, 774, 5, 116, 0, 0, 774, 775, 5, 101, 0, 0, 775, 776, 5, 115, 0, 0, 776, 778, 1, 0, 0, 0, 777, 779, 3, 135, 67, 0, 778, 777, 1, 0, 0, 0, 778, 779, 1, 0, 0, 0, 779, 792, 1, 0, 0, 0, 780, 781, 5, 117, 0, 0, 781, 782, 5, 110, 0, 0, 782, 783, 5, 115, 0, 0, 783, 784, 5, 97, 0, 0, 784, 785, 5, 102, 0, 0, 785, 786, 5, 101, 0, 0, 786, 787, 5, 95, 0, 0, 787, 788, 5, 98, 0, 0, 788, 789, 5, 121, 0, 0, 789, 790, 5, 116, 0, 0, 790, 792, 5, 101, 0, 0, 791, 743, 1, 0, 0, 0, 791, 753, 1, 0, 0, 0, 791, 764, 1, 0, 0, 0, 791, 780, 1, 0, 0, 0, 792, 146, 1, 0, 0, 0, 793, 794, 5, 116, 0, 0, 794, 795, 5, 104, 0, 0, 795, 796, 5, 105, 0, 0, 796, 797, 5, 115, 0, 0, 797, 798, 5, 46, 0, 0, 798, 799, 5, 97, 0, 0, 799, 800, 5, 99, 0, 0, 800, 801, 5, 116, 0, 0, 801, 802, 5, 105, 0, 0, 802, 803, 5, 118, 0, 0, 803, 804, 5, 101, 0, 0, 804, 805, 5, 73, 0, 0, 805, 806, 5, 110, 0, 0, 806, 807, 5, 112, 0, 0, 807, 808, 5, 117, 0, 0, 808, 809, 5, 116, 0, 0, 809, 810, 5, 73, 0, 0, 810, 811, 5, 110, 0, 0, 811, 812, 5, 100, 0, 0, 812, 813, 5, 101, 0, 0, 813, 888, 5, 120, 0, 0, 814, 815, 5, 116, 0, 0, 815, 816, 5, 104, 0, 0, 816, 817, 5, 105, 0, 0, 817, 818, 5, 115, 0, 0, 818, 819, 5, 46, 0, 0, 819, 820, 5, 97, 0, 0, 820, 821, 5, 99, 0, 0, 821, 822, 5, 116, 0, 0, 822, 823, 5, 105, 0, 0, 823, 824, 5, 118, 0, 0, 824, 825, 5, 101, 0, 0, 825, 826, 5, 66, 0, 0, 826, 827, 5, 121, 0, 0, 827, 828, 5, 116, 0, 0, 828, 829, 5, 101, 0, 0, 829, 830, 5, 99, 0, 0, 830, 831, 5, 111, 0, 0, 831, 832, 5, 100, 0, 0, 832, 888, 5, 101, 0, 0, 833, 834, 5, 116, 0, 0, 834, 835, 5, 120, 0, 0, 835, 836, 5, 46, 0, 0, 836, 837, 5, 105, 0, 0, 837, 838, 5, 110, 0, 0, 838, 839, 5, 112, 0, 0, 839, 840, 5, 117, 0, 0, 840, 841, 5, 116, 0, 0, 841, 842, 5, 115, 0, 0, 842, 843, 5, 46, 0, 0, 843, 844, 5, 108, 0, 0, 844, 845, 5, 101, 0, 0, 845, 846, 5, 110, 0, 0, 846, 847, 5, 103, 0, 0, 847, 848, 5, 116, 0, 0, 848, 888, 5, 104, 0, 0, 849, 850, 5, 116, 0, 0, 850, 851, 5, 120, 0, 0, 851, 852, 5, 46, 0, 0, 852, 853, 5, 111, 0, 0, 853, 854, 5, 117, 0, 0, 854, 855, 5, 116, 0, 0, 855, 856, 5, 112, 0, 0, 856, 857, 5, 117, 0, 0, 857, 858, 5, 116, 0, 0, 858, 859, 5, 115, 0, 0, 859, 860, 5, 46, 0, 0, 860, 861, 5, 108, 0, 0, 861, 862, 5, 101, 0, 0, 862, 863, 5, 110, 0, 0, 863, 864, 5, 103, 0, 0, 864, 865, 5, 116, 0, 0, 865, 888, 5, 104, 0, 0, 866, 867, 5, 116, 0, 0, 867, 868, 5, 120, 0, 0, 868, 869, 5, 46, 0, 0, 869, 870, 5, 118, 0, 0, 870, 871, 5, 101, 0, 0, 871, 872, 5, 114, 0, 0, 872, 873, 5, 115, 0, 0, 873, 874, 5, 105, 0, 0, 874, 875, 5, 111, 0, 0, 875, 888, 5, 110, 0, 0, 876, 877, 5, 116, 0, 0, 877, 878, 5, 120, 0, 0, 878, 879, 5, 46, 0, 0, 879, 880, 5, 108, 0, 0, 880, 881, 5, 111, 0, 0, 881, 882, 5, 99, 0, 0, 882, 883, 5, 107, 0, 0, 883, 884, 5, 116, 0, 0, 884, 885, 5, 105, 0, 0, 885, 886, 5, 109, 0, 0, 886, 888, 5, 101, 0, 0, 887, 793, 1, 0, 0, 0, 887, 814, 1, 0, 0, 0, 887, 833, 1, 0, 0, 0, 887, 849, 1, 0, 0, 0, 887, 866, 1, 0, 0, 0, 887, 876, 1, 0, 0, 0, 888, 148, 1, 0, 0, 0, 889, 893, 7, 7, 0, 0, 890, 892, 7, 8, 0, 0, 891, 890, 1, 0, 0, 0, 892, 895, 1, 0, 0, 0, 893, 891, 1, 0, 0, 0, 893, 894, 1, 0, 0, 0, 894, 150, 1, 0, 0, 0, 895, 893, 1, 0, 0, 0, 896, 898, 7, 9, 0, 0, 897, 896, 1, 0, 0, 0, 898, 899, 1, 0, 0, 0, 899, 897, 1, 0, 0, 0, 899, 900, 1, 0, 0, 0, 900, 901, 1, 0, 0, 0, 901, 902, 6, 75, 0, 0, 902, 152, 1, 0, 0, 0, 903, 904, 5, 47, 0, 0, 904, 905, 5, 42, 0, 0, 905, 909, 1, 0, 0, 0, 906, 908, 9, 0, 0, 0, 907, 906, 1, 0, 0, 0, 908, 911, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 909, 907, 1, 0, 0, 0, 910, 912, 1, 0, 0, 0, 911, 909, 1, 0, 0, 0, 912, 913, 5, 42, 0, 0, 913, 914, 5, 47, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 6, 76, 1, 0, 916, 154, 1, 0, 0, 0, 917, 918, 5, 47, 0, 0, 918, 919, 5, 47, 0, 0, 919, 923, 1, 0, 0, 0, 920, 922, 8, 10, 0, 0, 921, 920, 1, 0, 0, 0, 922, 925, 1, 0, 0, 0, 923, 921, 1, 0, 0, 0, 923, 924, 1, 0, 0, 0, 924, 926, 1, 0, 0, 0, 925, 923, 1, 0, 0, 0, 926, 927, 6, 77, 1, 0, 927, 156, 1, 0, 0, 0, 28, 0, 520, 526, 532, 543, 602, 605, 609, 614, 620, 624, 659, 678, 684, 691, 693, 701, 703, 707, 723, 741, 778, 791, 887, 893, 899, 909, 923, 2, 6, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/packages/cashc/src/grammar/CashScriptLexer.tokens b/packages/cashc/src/grammar/CashScriptLexer.tokens index 3b21c6f2..216b113a 100644 --- a/packages/cashc/src/grammar/CashScriptLexer.tokens +++ b/packages/cashc/src/grammar/CashScriptLexer.tokens @@ -55,26 +55,27 @@ T__53=54 T__54=55 T__55=56 T__56=57 -VersionLiteral=58 -BooleanLiteral=59 -NumberUnit=60 -NumberLiteral=61 -NumberPart=62 -ExponentPart=63 -PrimitiveType=64 -UnboundedBytes=65 -BoundedBytes=66 -Bound=67 -StringLiteral=68 -DateLiteral=69 -HexLiteral=70 -TxVar=71 -UnsafeCast=72 -NullaryOp=73 -Identifier=74 -WHITESPACE=75 -COMMENT=76 -LINE_COMMENT=77 +T__57=58 +VersionLiteral=59 +BooleanLiteral=60 +NumberUnit=61 +NumberLiteral=62 +NumberPart=63 +ExponentPart=64 +PrimitiveType=65 +UnboundedBytes=66 +BoundedBytes=67 +Bound=68 +StringLiteral=69 +DateLiteral=70 +HexLiteral=71 +TxVar=72 +UnsafeCast=73 +NullaryOp=74 +Identifier=75 +WHITESPACE=76 +COMMENT=77 +LINE_COMMENT=78 'pragma'=1 ';'=2 'cashscript'=3 @@ -88,48 +89,49 @@ LINE_COMMENT=77 'contract'=11 '{'=12 '}'=13 -'function'=14 -'('=15 -','=16 -')'=17 -'require'=18 -'console.log'=19 -'if'=20 -'else'=21 -'do'=22 -'while'=23 -'for'=24 -'new'=25 -'['=26 -']'=27 -'tx.outputs'=28 -'.value'=29 -'.lockingBytecode'=30 -'.tokenCategory'=31 -'.nftCommitment'=32 -'.tokenAmount'=33 -'tx.inputs'=34 -'.outpointTransactionHash'=35 -'.outpointIndex'=36 -'.unlockingBytecode'=37 -'.sequenceNumber'=38 -'.reverse()'=39 -'.length'=40 -'.split'=41 -'.slice'=42 -'!'=43 -'-'=44 -'*'=45 -'/'=46 -'%'=47 -'+'=48 -'>>'=49 -'<<'=50 -'=='=51 -'!='=52 -'&'=53 -'|'=54 -'&&'=55 -'||'=56 -'constant'=57 -'bytes'=65 +'library'=14 +'function'=15 +'('=16 +','=17 +')'=18 +'require'=19 +'console.log'=20 +'if'=21 +'else'=22 +'do'=23 +'while'=24 +'for'=25 +'new'=26 +'['=27 +']'=28 +'tx.outputs'=29 +'.value'=30 +'.lockingBytecode'=31 +'.tokenCategory'=32 +'.nftCommitment'=33 +'.tokenAmount'=34 +'tx.inputs'=35 +'.outpointTransactionHash'=36 +'.outpointIndex'=37 +'.unlockingBytecode'=38 +'.sequenceNumber'=39 +'.reverse()'=40 +'.length'=41 +'.split'=42 +'.slice'=43 +'!'=44 +'-'=45 +'*'=46 +'/'=47 +'%'=48 +'+'=49 +'>>'=50 +'<<'=51 +'=='=52 +'!='=53 +'&'=54 +'|'=55 +'&&'=56 +'||'=57 +'constant'=58 +'bytes'=66 diff --git a/packages/cashc/src/grammar/CashScriptLexer.ts b/packages/cashc/src/grammar/CashScriptLexer.ts index 3f8913a6..8df549e0 100644 --- a/packages/cashc/src/grammar/CashScriptLexer.ts +++ b/packages/cashc/src/grammar/CashScriptLexer.ts @@ -1,4 +1,4 @@ -// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.1 +// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.2 // noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols import { ATN, @@ -69,26 +69,27 @@ export default class CashScriptLexer extends Lexer { public static readonly T__54 = 55; public static readonly T__55 = 56; public static readonly T__56 = 57; - public static readonly VersionLiteral = 58; - public static readonly BooleanLiteral = 59; - public static readonly NumberUnit = 60; - public static readonly NumberLiteral = 61; - public static readonly NumberPart = 62; - public static readonly ExponentPart = 63; - public static readonly PrimitiveType = 64; - public static readonly UnboundedBytes = 65; - public static readonly BoundedBytes = 66; - public static readonly Bound = 67; - public static readonly StringLiteral = 68; - public static readonly DateLiteral = 69; - public static readonly HexLiteral = 70; - public static readonly TxVar = 71; - public static readonly UnsafeCast = 72; - public static readonly NullaryOp = 73; - public static readonly Identifier = 74; - public static readonly WHITESPACE = 75; - public static readonly COMMENT = 76; - public static readonly LINE_COMMENT = 77; + public static readonly T__57 = 58; + public static readonly VersionLiteral = 59; + public static readonly BooleanLiteral = 60; + public static readonly NumberUnit = 61; + public static readonly NumberLiteral = 62; + public static readonly NumberPart = 63; + public static readonly ExponentPart = 64; + public static readonly PrimitiveType = 65; + public static readonly UnboundedBytes = 66; + public static readonly BoundedBytes = 67; + public static readonly Bound = 68; + public static readonly StringLiteral = 69; + public static readonly DateLiteral = 70; + public static readonly HexLiteral = 71; + public static readonly TxVar = 72; + public static readonly UnsafeCast = 73; + public static readonly NullaryOp = 74; + public static readonly Identifier = 75; + public static readonly WHITESPACE = 76; + public static readonly COMMENT = 77; + public static readonly LINE_COMMENT = 78; public static readonly EOF = Token.EOF; public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; @@ -99,6 +100,7 @@ export default class CashScriptLexer extends Lexer { "'<'", "'<='", "'='", "'contract'", "'{'", "'}'", + "'library'", "'function'", "'('", "','", "')'", "'require'", @@ -163,7 +165,7 @@ export default class CashScriptLexer extends Lexer { null, null, null, null, null, null, - "VersionLiteral", + null, "VersionLiteral", "BooleanLiteral", "NumberUnit", "NumberLiteral", @@ -191,10 +193,10 @@ export default class CashScriptLexer extends Lexer { "T__33", "T__34", "T__35", "T__36", "T__37", "T__38", "T__39", "T__40", "T__41", "T__42", "T__43", "T__44", "T__45", "T__46", "T__47", "T__48", "T__49", "T__50", "T__51", "T__52", "T__53", "T__54", "T__55", "T__56", - "VersionLiteral", "BooleanLiteral", "NumberUnit", "NumberLiteral", "NumberPart", - "ExponentPart", "PrimitiveType", "UnboundedBytes", "BoundedBytes", "Bound", - "StringLiteral", "DateLiteral", "HexLiteral", "TxVar", "UnsafeCast", "NullaryOp", - "Identifier", "WHITESPACE", "COMMENT", "LINE_COMMENT", + "T__57", "VersionLiteral", "BooleanLiteral", "NumberUnit", "NumberLiteral", + "NumberPart", "ExponentPart", "PrimitiveType", "UnboundedBytes", "BoundedBytes", + "Bound", "StringLiteral", "DateLiteral", "HexLiteral", "TxVar", "UnsafeCast", + "NullaryOp", "Identifier", "WHITESPACE", "COMMENT", "LINE_COMMENT", ]; @@ -215,7 +217,7 @@ export default class CashScriptLexer extends Lexer { public get modeNames(): string[] { return CashScriptLexer.modeNames; } - public static readonly _serializedATN: number[] = [4,0,77,918,6,-1,2,0, + public static readonly _serializedATN: number[] = [4,0,78,928,6,-1,2,0, 7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9, 7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7, 16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23, @@ -226,297 +228,300 @@ export default class CashScriptLexer extends Lexer { 2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59,2, 60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,2,66,7,66,2,67, 7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74,7, - 74,2,75,7,75,2,76,7,76,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,2,1,2,1,2, - 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,1,6,1,6,1,7, - 1,7,1,8,1,8,1,8,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1, - 11,1,11,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14, - 1,15,1,15,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1, - 18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,20,1,20, - 1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1, - 23,1,23,1,24,1,24,1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,1,27,1,27,1,27, - 1,27,1,27,1,27,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,29,1, - 29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29, - 1,29,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1, - 30,1,30,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31, - 1,31,1,31,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1, - 32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,34,1,34,1,34,1,34, - 1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1, - 34,1,34,1,34,1,34,1,34,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35, - 1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1, - 36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,37,1,37,1,37, - 1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,38,1, - 38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,39, - 1,39,1,39,1,39,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,41,1,41,1,41,1,41,1, - 41,1,41,1,41,1,42,1,42,1,43,1,43,1,44,1,44,1,45,1,45,1,46,1,46,1,47,1,47, - 1,48,1,48,1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,51,1,51,1,51,1,52,1,52,1, - 53,1,53,1,54,1,54,1,54,1,55,1,55,1,55,1,56,1,56,1,56,1,56,1,56,1,56,1,56, - 1,56,1,56,1,57,4,57,509,8,57,11,57,12,57,510,1,57,1,57,4,57,515,8,57,11, - 57,12,57,516,1,57,1,57,4,57,521,8,57,11,57,12,57,522,1,58,1,58,1,58,1,58, - 1,58,1,58,1,58,1,58,1,58,3,58,534,8,58,1,59,1,59,1,59,1,59,1,59,1,59,1, - 59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59, - 1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1, - 59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59, - 1,59,1,59,1,59,1,59,1,59,1,59,1,59,3,59,593,8,59,1,60,3,60,596,8,60,1,60, - 1,60,3,60,600,8,60,1,61,4,61,603,8,61,11,61,12,61,604,1,61,1,61,4,61,609, - 8,61,11,61,12,61,610,5,61,613,8,61,10,61,12,61,616,9,61,1,62,1,62,1,62, - 1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1, - 63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63, - 3,63,650,8,63,1,64,1,64,1,64,1,64,1,64,1,64,1,65,1,65,1,65,1,65,1,65,1, - 65,1,65,1,65,1,65,1,65,1,65,3,65,669,8,65,1,66,1,66,5,66,673,8,66,10,66, - 12,66,676,9,66,1,67,1,67,1,67,1,67,5,67,682,8,67,10,67,12,67,685,9,67,1, - 67,1,67,1,67,1,67,1,67,5,67,692,8,67,10,67,12,67,695,9,67,1,67,3,67,698, - 8,67,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,69,1,69,1,69,5,69,712, - 8,69,10,69,12,69,715,9,69,1,70,1,70,1,70,1,70,1,70,1,70,1,70,1,70,1,70, - 1,70,1,70,1,70,1,70,1,70,1,70,3,70,732,8,70,1,71,1,71,1,71,1,71,1,71,1, - 71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71, - 1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1, - 71,3,71,769,8,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71, - 3,71,782,8,71,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1, - 72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72, + 74,2,75,7,75,2,76,7,76,2,77,7,77,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1, + 2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,1, + 6,1,6,1,7,1,7,1,8,1,8,1,8,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1, + 10,1,10,1,11,1,11,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14, + 1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,16,1,16,1,17,1,17,1, + 18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,19,1,19, + 1,19,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,21,1,21,1,21,1,21,1,21,1,22,1, + 22,1,22,1,23,1,23,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,25,1,25,1,25, + 1,25,1,26,1,26,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1,28,1, + 28,1,28,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,30,1,30, + 1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,31,1,31,1,31,1, + 31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,32,1,32,1,32, + 1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,34,1,34,1,34,1,34, + 1,34,1,34,1,34,1,34,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1, + 35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35,1,35, + 1,35,1,35,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1,36,1, + 36,1,36,1,36,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,37, + 1,37,1,37,1,37,1,37,1,37,1,37,1,37,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1, + 38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,39,1,39, + 1,39,1,39,1,39,1,39,1,39,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,41,1, + 41,1,41,1,41,1,41,1,41,1,41,1,42,1,42,1,42,1,42,1,42,1,42,1,42,1,43,1,43, + 1,44,1,44,1,45,1,45,1,46,1,46,1,47,1,47,1,48,1,48,1,49,1,49,1,49,1,50,1, + 50,1,50,1,51,1,51,1,51,1,52,1,52,1,52,1,53,1,53,1,54,1,54,1,55,1,55,1,55, + 1,56,1,56,1,56,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,58,4,58,519, + 8,58,11,58,12,58,520,1,58,1,58,4,58,525,8,58,11,58,12,58,526,1,58,1,58, + 4,58,531,8,58,11,58,12,58,532,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1, + 59,3,59,544,8,59,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60, + 1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1, + 60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60, + 1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1,60,1, + 60,1,60,1,60,3,60,603,8,60,1,61,3,61,606,8,61,1,61,1,61,3,61,610,8,61,1, + 62,4,62,613,8,62,11,62,12,62,614,1,62,1,62,4,62,619,8,62,11,62,12,62,620, + 5,62,623,8,62,10,62,12,62,626,9,62,1,63,1,63,1,63,1,64,1,64,1,64,1,64,1, + 64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64, + 1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,3,64,660,8,64,1,65,1, + 65,1,65,1,65,1,65,1,65,1,66,1,66,1,66,1,66,1,66,1,66,1,66,1,66,1,66,1,66, + 1,66,3,66,679,8,66,1,67,1,67,5,67,683,8,67,10,67,12,67,686,9,67,1,68,1, + 68,1,68,1,68,5,68,692,8,68,10,68,12,68,695,9,68,1,68,1,68,1,68,1,68,1,68, + 5,68,702,8,68,10,68,12,68,705,9,68,1,68,3,68,708,8,68,1,69,1,69,1,69,1, + 69,1,69,1,69,1,69,1,69,1,69,1,70,1,70,1,70,5,70,722,8,70,10,70,12,70,725, + 9,70,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1, + 71,1,71,3,71,742,8,71,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72, 1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1, - 72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72, - 1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1, - 72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72, - 1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,3,72,878,8,72,1,73,1, - 73,5,73,882,8,73,10,73,12,73,885,9,73,1,74,4,74,888,8,74,11,74,12,74,889, - 1,74,1,74,1,75,1,75,1,75,1,75,5,75,898,8,75,10,75,12,75,901,9,75,1,75,1, - 75,1,75,1,75,1,75,1,76,1,76,1,76,1,76,5,76,912,8,76,10,76,12,76,915,9,76, - 1,76,1,76,3,683,693,899,0,77,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19, - 10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43, - 22,45,23,47,24,49,25,51,26,53,27,55,28,57,29,59,30,61,31,63,32,65,33,67, - 34,69,35,71,36,73,37,75,38,77,39,79,40,81,41,83,42,85,43,87,44,89,45,91, - 46,93,47,95,48,97,49,99,50,101,51,103,52,105,53,107,54,109,55,111,56,113, - 57,115,58,117,59,119,60,121,61,123,62,125,63,127,64,129,65,131,66,133,67, - 135,68,137,69,139,70,141,71,143,72,145,73,147,74,149,75,151,76,153,77,1, - 0,11,1,0,48,57,2,0,69,69,101,101,1,0,49,57,3,0,10,10,13,13,34,34,3,0,10, - 10,13,13,39,39,2,0,88,88,120,120,3,0,48,57,65,70,97,102,2,0,65,90,97,122, - 4,0,48,57,65,90,95,95,97,122,3,0,9,10,12,13,32,32,2,0,10,10,13,13,962,0, - 1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0, - 0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1, - 0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0, - 0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1, - 0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0, - 0,57,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1, - 0,0,0,0,69,1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0, - 0,79,1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,1, - 0,0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0,0,0, - 0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109,1,0,0,0,0, - 111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,0,0,0,0,119,1,0,0,0,0,121, - 1,0,0,0,0,123,1,0,0,0,0,125,1,0,0,0,0,127,1,0,0,0,0,129,1,0,0,0,0,131,1, - 0,0,0,0,133,1,0,0,0,0,135,1,0,0,0,0,137,1,0,0,0,0,139,1,0,0,0,0,141,1,0, - 0,0,0,143,1,0,0,0,0,145,1,0,0,0,0,147,1,0,0,0,0,149,1,0,0,0,0,151,1,0,0, - 0,0,153,1,0,0,0,1,155,1,0,0,0,3,162,1,0,0,0,5,164,1,0,0,0,7,175,1,0,0,0, - 9,177,1,0,0,0,11,179,1,0,0,0,13,182,1,0,0,0,15,184,1,0,0,0,17,186,1,0,0, - 0,19,189,1,0,0,0,21,191,1,0,0,0,23,200,1,0,0,0,25,202,1,0,0,0,27,204,1, - 0,0,0,29,213,1,0,0,0,31,215,1,0,0,0,33,217,1,0,0,0,35,219,1,0,0,0,37,227, - 1,0,0,0,39,239,1,0,0,0,41,242,1,0,0,0,43,247,1,0,0,0,45,250,1,0,0,0,47, - 256,1,0,0,0,49,260,1,0,0,0,51,264,1,0,0,0,53,266,1,0,0,0,55,268,1,0,0,0, - 57,279,1,0,0,0,59,286,1,0,0,0,61,303,1,0,0,0,63,318,1,0,0,0,65,333,1,0, - 0,0,67,346,1,0,0,0,69,356,1,0,0,0,71,381,1,0,0,0,73,396,1,0,0,0,75,415, - 1,0,0,0,77,431,1,0,0,0,79,442,1,0,0,0,81,450,1,0,0,0,83,457,1,0,0,0,85, - 464,1,0,0,0,87,466,1,0,0,0,89,468,1,0,0,0,91,470,1,0,0,0,93,472,1,0,0,0, - 95,474,1,0,0,0,97,476,1,0,0,0,99,479,1,0,0,0,101,482,1,0,0,0,103,485,1, - 0,0,0,105,488,1,0,0,0,107,490,1,0,0,0,109,492,1,0,0,0,111,495,1,0,0,0,113, - 498,1,0,0,0,115,508,1,0,0,0,117,533,1,0,0,0,119,592,1,0,0,0,121,595,1,0, - 0,0,123,602,1,0,0,0,125,617,1,0,0,0,127,649,1,0,0,0,129,651,1,0,0,0,131, - 668,1,0,0,0,133,670,1,0,0,0,135,697,1,0,0,0,137,699,1,0,0,0,139,708,1,0, - 0,0,141,731,1,0,0,0,143,781,1,0,0,0,145,877,1,0,0,0,147,879,1,0,0,0,149, - 887,1,0,0,0,151,893,1,0,0,0,153,907,1,0,0,0,155,156,5,112,0,0,156,157,5, - 114,0,0,157,158,5,97,0,0,158,159,5,103,0,0,159,160,5,109,0,0,160,161,5, - 97,0,0,161,2,1,0,0,0,162,163,5,59,0,0,163,4,1,0,0,0,164,165,5,99,0,0,165, - 166,5,97,0,0,166,167,5,115,0,0,167,168,5,104,0,0,168,169,5,115,0,0,169, - 170,5,99,0,0,170,171,5,114,0,0,171,172,5,105,0,0,172,173,5,112,0,0,173, - 174,5,116,0,0,174,6,1,0,0,0,175,176,5,94,0,0,176,8,1,0,0,0,177,178,5,126, - 0,0,178,10,1,0,0,0,179,180,5,62,0,0,180,181,5,61,0,0,181,12,1,0,0,0,182, - 183,5,62,0,0,183,14,1,0,0,0,184,185,5,60,0,0,185,16,1,0,0,0,186,187,5,60, - 0,0,187,188,5,61,0,0,188,18,1,0,0,0,189,190,5,61,0,0,190,20,1,0,0,0,191, - 192,5,99,0,0,192,193,5,111,0,0,193,194,5,110,0,0,194,195,5,116,0,0,195, - 196,5,114,0,0,196,197,5,97,0,0,197,198,5,99,0,0,198,199,5,116,0,0,199,22, - 1,0,0,0,200,201,5,123,0,0,201,24,1,0,0,0,202,203,5,125,0,0,203,26,1,0,0, - 0,204,205,5,102,0,0,205,206,5,117,0,0,206,207,5,110,0,0,207,208,5,99,0, - 0,208,209,5,116,0,0,209,210,5,105,0,0,210,211,5,111,0,0,211,212,5,110,0, - 0,212,28,1,0,0,0,213,214,5,40,0,0,214,30,1,0,0,0,215,216,5,44,0,0,216,32, - 1,0,0,0,217,218,5,41,0,0,218,34,1,0,0,0,219,220,5,114,0,0,220,221,5,101, - 0,0,221,222,5,113,0,0,222,223,5,117,0,0,223,224,5,105,0,0,224,225,5,114, - 0,0,225,226,5,101,0,0,226,36,1,0,0,0,227,228,5,99,0,0,228,229,5,111,0,0, - 229,230,5,110,0,0,230,231,5,115,0,0,231,232,5,111,0,0,232,233,5,108,0,0, - 233,234,5,101,0,0,234,235,5,46,0,0,235,236,5,108,0,0,236,237,5,111,0,0, - 237,238,5,103,0,0,238,38,1,0,0,0,239,240,5,105,0,0,240,241,5,102,0,0,241, - 40,1,0,0,0,242,243,5,101,0,0,243,244,5,108,0,0,244,245,5,115,0,0,245,246, - 5,101,0,0,246,42,1,0,0,0,247,248,5,100,0,0,248,249,5,111,0,0,249,44,1,0, - 0,0,250,251,5,119,0,0,251,252,5,104,0,0,252,253,5,105,0,0,253,254,5,108, - 0,0,254,255,5,101,0,0,255,46,1,0,0,0,256,257,5,102,0,0,257,258,5,111,0, - 0,258,259,5,114,0,0,259,48,1,0,0,0,260,261,5,110,0,0,261,262,5,101,0,0, - 262,263,5,119,0,0,263,50,1,0,0,0,264,265,5,91,0,0,265,52,1,0,0,0,266,267, - 5,93,0,0,267,54,1,0,0,0,268,269,5,116,0,0,269,270,5,120,0,0,270,271,5,46, - 0,0,271,272,5,111,0,0,272,273,5,117,0,0,273,274,5,116,0,0,274,275,5,112, - 0,0,275,276,5,117,0,0,276,277,5,116,0,0,277,278,5,115,0,0,278,56,1,0,0, - 0,279,280,5,46,0,0,280,281,5,118,0,0,281,282,5,97,0,0,282,283,5,108,0,0, - 283,284,5,117,0,0,284,285,5,101,0,0,285,58,1,0,0,0,286,287,5,46,0,0,287, - 288,5,108,0,0,288,289,5,111,0,0,289,290,5,99,0,0,290,291,5,107,0,0,291, - 292,5,105,0,0,292,293,5,110,0,0,293,294,5,103,0,0,294,295,5,66,0,0,295, - 296,5,121,0,0,296,297,5,116,0,0,297,298,5,101,0,0,298,299,5,99,0,0,299, - 300,5,111,0,0,300,301,5,100,0,0,301,302,5,101,0,0,302,60,1,0,0,0,303,304, - 5,46,0,0,304,305,5,116,0,0,305,306,5,111,0,0,306,307,5,107,0,0,307,308, - 5,101,0,0,308,309,5,110,0,0,309,310,5,67,0,0,310,311,5,97,0,0,311,312,5, - 116,0,0,312,313,5,101,0,0,313,314,5,103,0,0,314,315,5,111,0,0,315,316,5, - 114,0,0,316,317,5,121,0,0,317,62,1,0,0,0,318,319,5,46,0,0,319,320,5,110, - 0,0,320,321,5,102,0,0,321,322,5,116,0,0,322,323,5,67,0,0,323,324,5,111, - 0,0,324,325,5,109,0,0,325,326,5,109,0,0,326,327,5,105,0,0,327,328,5,116, - 0,0,328,329,5,109,0,0,329,330,5,101,0,0,330,331,5,110,0,0,331,332,5,116, - 0,0,332,64,1,0,0,0,333,334,5,46,0,0,334,335,5,116,0,0,335,336,5,111,0,0, - 336,337,5,107,0,0,337,338,5,101,0,0,338,339,5,110,0,0,339,340,5,65,0,0, - 340,341,5,109,0,0,341,342,5,111,0,0,342,343,5,117,0,0,343,344,5,110,0,0, - 344,345,5,116,0,0,345,66,1,0,0,0,346,347,5,116,0,0,347,348,5,120,0,0,348, - 349,5,46,0,0,349,350,5,105,0,0,350,351,5,110,0,0,351,352,5,112,0,0,352, - 353,5,117,0,0,353,354,5,116,0,0,354,355,5,115,0,0,355,68,1,0,0,0,356,357, - 5,46,0,0,357,358,5,111,0,0,358,359,5,117,0,0,359,360,5,116,0,0,360,361, - 5,112,0,0,361,362,5,111,0,0,362,363,5,105,0,0,363,364,5,110,0,0,364,365, - 5,116,0,0,365,366,5,84,0,0,366,367,5,114,0,0,367,368,5,97,0,0,368,369,5, - 110,0,0,369,370,5,115,0,0,370,371,5,97,0,0,371,372,5,99,0,0,372,373,5,116, - 0,0,373,374,5,105,0,0,374,375,5,111,0,0,375,376,5,110,0,0,376,377,5,72, - 0,0,377,378,5,97,0,0,378,379,5,115,0,0,379,380,5,104,0,0,380,70,1,0,0,0, - 381,382,5,46,0,0,382,383,5,111,0,0,383,384,5,117,0,0,384,385,5,116,0,0, - 385,386,5,112,0,0,386,387,5,111,0,0,387,388,5,105,0,0,388,389,5,110,0,0, - 389,390,5,116,0,0,390,391,5,73,0,0,391,392,5,110,0,0,392,393,5,100,0,0, - 393,394,5,101,0,0,394,395,5,120,0,0,395,72,1,0,0,0,396,397,5,46,0,0,397, - 398,5,117,0,0,398,399,5,110,0,0,399,400,5,108,0,0,400,401,5,111,0,0,401, - 402,5,99,0,0,402,403,5,107,0,0,403,404,5,105,0,0,404,405,5,110,0,0,405, - 406,5,103,0,0,406,407,5,66,0,0,407,408,5,121,0,0,408,409,5,116,0,0,409, - 410,5,101,0,0,410,411,5,99,0,0,411,412,5,111,0,0,412,413,5,100,0,0,413, - 414,5,101,0,0,414,74,1,0,0,0,415,416,5,46,0,0,416,417,5,115,0,0,417,418, - 5,101,0,0,418,419,5,113,0,0,419,420,5,117,0,0,420,421,5,101,0,0,421,422, - 5,110,0,0,422,423,5,99,0,0,423,424,5,101,0,0,424,425,5,78,0,0,425,426,5, - 117,0,0,426,427,5,109,0,0,427,428,5,98,0,0,428,429,5,101,0,0,429,430,5, - 114,0,0,430,76,1,0,0,0,431,432,5,46,0,0,432,433,5,114,0,0,433,434,5,101, - 0,0,434,435,5,118,0,0,435,436,5,101,0,0,436,437,5,114,0,0,437,438,5,115, - 0,0,438,439,5,101,0,0,439,440,5,40,0,0,440,441,5,41,0,0,441,78,1,0,0,0, - 442,443,5,46,0,0,443,444,5,108,0,0,444,445,5,101,0,0,445,446,5,110,0,0, - 446,447,5,103,0,0,447,448,5,116,0,0,448,449,5,104,0,0,449,80,1,0,0,0,450, - 451,5,46,0,0,451,452,5,115,0,0,452,453,5,112,0,0,453,454,5,108,0,0,454, - 455,5,105,0,0,455,456,5,116,0,0,456,82,1,0,0,0,457,458,5,46,0,0,458,459, - 5,115,0,0,459,460,5,108,0,0,460,461,5,105,0,0,461,462,5,99,0,0,462,463, - 5,101,0,0,463,84,1,0,0,0,464,465,5,33,0,0,465,86,1,0,0,0,466,467,5,45,0, - 0,467,88,1,0,0,0,468,469,5,42,0,0,469,90,1,0,0,0,470,471,5,47,0,0,471,92, - 1,0,0,0,472,473,5,37,0,0,473,94,1,0,0,0,474,475,5,43,0,0,475,96,1,0,0,0, - 476,477,5,62,0,0,477,478,5,62,0,0,478,98,1,0,0,0,479,480,5,60,0,0,480,481, - 5,60,0,0,481,100,1,0,0,0,482,483,5,61,0,0,483,484,5,61,0,0,484,102,1,0, - 0,0,485,486,5,33,0,0,486,487,5,61,0,0,487,104,1,0,0,0,488,489,5,38,0,0, - 489,106,1,0,0,0,490,491,5,124,0,0,491,108,1,0,0,0,492,493,5,38,0,0,493, - 494,5,38,0,0,494,110,1,0,0,0,495,496,5,124,0,0,496,497,5,124,0,0,497,112, - 1,0,0,0,498,499,5,99,0,0,499,500,5,111,0,0,500,501,5,110,0,0,501,502,5, - 115,0,0,502,503,5,116,0,0,503,504,5,97,0,0,504,505,5,110,0,0,505,506,5, - 116,0,0,506,114,1,0,0,0,507,509,7,0,0,0,508,507,1,0,0,0,509,510,1,0,0,0, - 510,508,1,0,0,0,510,511,1,0,0,0,511,512,1,0,0,0,512,514,5,46,0,0,513,515, - 7,0,0,0,514,513,1,0,0,0,515,516,1,0,0,0,516,514,1,0,0,0,516,517,1,0,0,0, - 517,518,1,0,0,0,518,520,5,46,0,0,519,521,7,0,0,0,520,519,1,0,0,0,521,522, - 1,0,0,0,522,520,1,0,0,0,522,523,1,0,0,0,523,116,1,0,0,0,524,525,5,116,0, - 0,525,526,5,114,0,0,526,527,5,117,0,0,527,534,5,101,0,0,528,529,5,102,0, - 0,529,530,5,97,0,0,530,531,5,108,0,0,531,532,5,115,0,0,532,534,5,101,0, - 0,533,524,1,0,0,0,533,528,1,0,0,0,534,118,1,0,0,0,535,536,5,115,0,0,536, - 537,5,97,0,0,537,538,5,116,0,0,538,539,5,111,0,0,539,540,5,115,0,0,540, - 541,5,104,0,0,541,542,5,105,0,0,542,593,5,115,0,0,543,544,5,115,0,0,544, - 545,5,97,0,0,545,546,5,116,0,0,546,593,5,115,0,0,547,548,5,102,0,0,548, - 549,5,105,0,0,549,550,5,110,0,0,550,551,5,110,0,0,551,552,5,101,0,0,552, - 593,5,121,0,0,553,554,5,98,0,0,554,555,5,105,0,0,555,556,5,116,0,0,556, - 593,5,115,0,0,557,558,5,98,0,0,558,559,5,105,0,0,559,560,5,116,0,0,560, - 561,5,99,0,0,561,562,5,111,0,0,562,563,5,105,0,0,563,593,5,110,0,0,564, - 565,5,115,0,0,565,566,5,101,0,0,566,567,5,99,0,0,567,568,5,111,0,0,568, - 569,5,110,0,0,569,570,5,100,0,0,570,593,5,115,0,0,571,572,5,109,0,0,572, - 573,5,105,0,0,573,574,5,110,0,0,574,575,5,117,0,0,575,576,5,116,0,0,576, - 577,5,101,0,0,577,593,5,115,0,0,578,579,5,104,0,0,579,580,5,111,0,0,580, - 581,5,117,0,0,581,582,5,114,0,0,582,593,5,115,0,0,583,584,5,100,0,0,584, - 585,5,97,0,0,585,586,5,121,0,0,586,593,5,115,0,0,587,588,5,119,0,0,588, - 589,5,101,0,0,589,590,5,101,0,0,590,591,5,107,0,0,591,593,5,115,0,0,592, - 535,1,0,0,0,592,543,1,0,0,0,592,547,1,0,0,0,592,553,1,0,0,0,592,557,1,0, - 0,0,592,564,1,0,0,0,592,571,1,0,0,0,592,578,1,0,0,0,592,583,1,0,0,0,592, - 587,1,0,0,0,593,120,1,0,0,0,594,596,5,45,0,0,595,594,1,0,0,0,595,596,1, - 0,0,0,596,597,1,0,0,0,597,599,3,123,61,0,598,600,3,125,62,0,599,598,1,0, - 0,0,599,600,1,0,0,0,600,122,1,0,0,0,601,603,7,0,0,0,602,601,1,0,0,0,603, - 604,1,0,0,0,604,602,1,0,0,0,604,605,1,0,0,0,605,614,1,0,0,0,606,608,5,95, - 0,0,607,609,7,0,0,0,608,607,1,0,0,0,609,610,1,0,0,0,610,608,1,0,0,0,610, - 611,1,0,0,0,611,613,1,0,0,0,612,606,1,0,0,0,613,616,1,0,0,0,614,612,1,0, - 0,0,614,615,1,0,0,0,615,124,1,0,0,0,616,614,1,0,0,0,617,618,7,1,0,0,618, - 619,3,123,61,0,619,126,1,0,0,0,620,621,5,105,0,0,621,622,5,110,0,0,622, - 650,5,116,0,0,623,624,5,98,0,0,624,625,5,111,0,0,625,626,5,111,0,0,626, - 650,5,108,0,0,627,628,5,115,0,0,628,629,5,116,0,0,629,630,5,114,0,0,630, - 631,5,105,0,0,631,632,5,110,0,0,632,650,5,103,0,0,633,634,5,112,0,0,634, - 635,5,117,0,0,635,636,5,98,0,0,636,637,5,107,0,0,637,638,5,101,0,0,638, - 650,5,121,0,0,639,640,5,115,0,0,640,641,5,105,0,0,641,650,5,103,0,0,642, - 643,5,100,0,0,643,644,5,97,0,0,644,645,5,116,0,0,645,646,5,97,0,0,646,647, - 5,115,0,0,647,648,5,105,0,0,648,650,5,103,0,0,649,620,1,0,0,0,649,623,1, - 0,0,0,649,627,1,0,0,0,649,633,1,0,0,0,649,639,1,0,0,0,649,642,1,0,0,0,650, - 128,1,0,0,0,651,652,5,98,0,0,652,653,5,121,0,0,653,654,5,116,0,0,654,655, - 5,101,0,0,655,656,5,115,0,0,656,130,1,0,0,0,657,658,5,98,0,0,658,659,5, - 121,0,0,659,660,5,116,0,0,660,661,5,101,0,0,661,662,5,115,0,0,662,663,1, - 0,0,0,663,669,3,133,66,0,664,665,5,98,0,0,665,666,5,121,0,0,666,667,5,116, - 0,0,667,669,5,101,0,0,668,657,1,0,0,0,668,664,1,0,0,0,669,132,1,0,0,0,670, - 674,7,2,0,0,671,673,7,0,0,0,672,671,1,0,0,0,673,676,1,0,0,0,674,672,1,0, - 0,0,674,675,1,0,0,0,675,134,1,0,0,0,676,674,1,0,0,0,677,683,5,34,0,0,678, - 679,5,92,0,0,679,682,5,34,0,0,680,682,8,3,0,0,681,678,1,0,0,0,681,680,1, - 0,0,0,682,685,1,0,0,0,683,684,1,0,0,0,683,681,1,0,0,0,684,686,1,0,0,0,685, - 683,1,0,0,0,686,698,5,34,0,0,687,693,5,39,0,0,688,689,5,92,0,0,689,692, - 5,39,0,0,690,692,8,4,0,0,691,688,1,0,0,0,691,690,1,0,0,0,692,695,1,0,0, - 0,693,694,1,0,0,0,693,691,1,0,0,0,694,696,1,0,0,0,695,693,1,0,0,0,696,698, - 5,39,0,0,697,677,1,0,0,0,697,687,1,0,0,0,698,136,1,0,0,0,699,700,5,100, - 0,0,700,701,5,97,0,0,701,702,5,116,0,0,702,703,5,101,0,0,703,704,5,40,0, - 0,704,705,1,0,0,0,705,706,3,135,67,0,706,707,5,41,0,0,707,138,1,0,0,0,708, - 709,5,48,0,0,709,713,7,5,0,0,710,712,7,6,0,0,711,710,1,0,0,0,712,715,1, - 0,0,0,713,711,1,0,0,0,713,714,1,0,0,0,714,140,1,0,0,0,715,713,1,0,0,0,716, - 717,5,116,0,0,717,718,5,104,0,0,718,719,5,105,0,0,719,720,5,115,0,0,720, - 721,5,46,0,0,721,722,5,97,0,0,722,723,5,103,0,0,723,732,5,101,0,0,724,725, - 5,116,0,0,725,726,5,120,0,0,726,727,5,46,0,0,727,728,5,116,0,0,728,729, - 5,105,0,0,729,730,5,109,0,0,730,732,5,101,0,0,731,716,1,0,0,0,731,724,1, - 0,0,0,732,142,1,0,0,0,733,734,5,117,0,0,734,735,5,110,0,0,735,736,5,115, - 0,0,736,737,5,97,0,0,737,738,5,102,0,0,738,739,5,101,0,0,739,740,5,95,0, - 0,740,741,5,105,0,0,741,742,5,110,0,0,742,782,5,116,0,0,743,744,5,117,0, - 0,744,745,5,110,0,0,745,746,5,115,0,0,746,747,5,97,0,0,747,748,5,102,0, - 0,748,749,5,101,0,0,749,750,5,95,0,0,750,751,5,98,0,0,751,752,5,111,0,0, - 752,753,5,111,0,0,753,782,5,108,0,0,754,755,5,117,0,0,755,756,5,110,0,0, - 756,757,5,115,0,0,757,758,5,97,0,0,758,759,5,102,0,0,759,760,5,101,0,0, - 760,761,5,95,0,0,761,762,5,98,0,0,762,763,5,121,0,0,763,764,5,116,0,0,764, - 765,5,101,0,0,765,766,5,115,0,0,766,768,1,0,0,0,767,769,3,133,66,0,768, - 767,1,0,0,0,768,769,1,0,0,0,769,782,1,0,0,0,770,771,5,117,0,0,771,772,5, - 110,0,0,772,773,5,115,0,0,773,774,5,97,0,0,774,775,5,102,0,0,775,776,5, - 101,0,0,776,777,5,95,0,0,777,778,5,98,0,0,778,779,5,121,0,0,779,780,5,116, - 0,0,780,782,5,101,0,0,781,733,1,0,0,0,781,743,1,0,0,0,781,754,1,0,0,0,781, - 770,1,0,0,0,782,144,1,0,0,0,783,784,5,116,0,0,784,785,5,104,0,0,785,786, - 5,105,0,0,786,787,5,115,0,0,787,788,5,46,0,0,788,789,5,97,0,0,789,790,5, - 99,0,0,790,791,5,116,0,0,791,792,5,105,0,0,792,793,5,118,0,0,793,794,5, - 101,0,0,794,795,5,73,0,0,795,796,5,110,0,0,796,797,5,112,0,0,797,798,5, - 117,0,0,798,799,5,116,0,0,799,800,5,73,0,0,800,801,5,110,0,0,801,802,5, - 100,0,0,802,803,5,101,0,0,803,878,5,120,0,0,804,805,5,116,0,0,805,806,5, - 104,0,0,806,807,5,105,0,0,807,808,5,115,0,0,808,809,5,46,0,0,809,810,5, - 97,0,0,810,811,5,99,0,0,811,812,5,116,0,0,812,813,5,105,0,0,813,814,5,118, - 0,0,814,815,5,101,0,0,815,816,5,66,0,0,816,817,5,121,0,0,817,818,5,116, - 0,0,818,819,5,101,0,0,819,820,5,99,0,0,820,821,5,111,0,0,821,822,5,100, - 0,0,822,878,5,101,0,0,823,824,5,116,0,0,824,825,5,120,0,0,825,826,5,46, - 0,0,826,827,5,105,0,0,827,828,5,110,0,0,828,829,5,112,0,0,829,830,5,117, - 0,0,830,831,5,116,0,0,831,832,5,115,0,0,832,833,5,46,0,0,833,834,5,108, - 0,0,834,835,5,101,0,0,835,836,5,110,0,0,836,837,5,103,0,0,837,838,5,116, - 0,0,838,878,5,104,0,0,839,840,5,116,0,0,840,841,5,120,0,0,841,842,5,46, - 0,0,842,843,5,111,0,0,843,844,5,117,0,0,844,845,5,116,0,0,845,846,5,112, - 0,0,846,847,5,117,0,0,847,848,5,116,0,0,848,849,5,115,0,0,849,850,5,46, - 0,0,850,851,5,108,0,0,851,852,5,101,0,0,852,853,5,110,0,0,853,854,5,103, - 0,0,854,855,5,116,0,0,855,878,5,104,0,0,856,857,5,116,0,0,857,858,5,120, - 0,0,858,859,5,46,0,0,859,860,5,118,0,0,860,861,5,101,0,0,861,862,5,114, - 0,0,862,863,5,115,0,0,863,864,5,105,0,0,864,865,5,111,0,0,865,878,5,110, - 0,0,866,867,5,116,0,0,867,868,5,120,0,0,868,869,5,46,0,0,869,870,5,108, - 0,0,870,871,5,111,0,0,871,872,5,99,0,0,872,873,5,107,0,0,873,874,5,116, - 0,0,874,875,5,105,0,0,875,876,5,109,0,0,876,878,5,101,0,0,877,783,1,0,0, - 0,877,804,1,0,0,0,877,823,1,0,0,0,877,839,1,0,0,0,877,856,1,0,0,0,877,866, - 1,0,0,0,878,146,1,0,0,0,879,883,7,7,0,0,880,882,7,8,0,0,881,880,1,0,0,0, - 882,885,1,0,0,0,883,881,1,0,0,0,883,884,1,0,0,0,884,148,1,0,0,0,885,883, - 1,0,0,0,886,888,7,9,0,0,887,886,1,0,0,0,888,889,1,0,0,0,889,887,1,0,0,0, - 889,890,1,0,0,0,890,891,1,0,0,0,891,892,6,74,0,0,892,150,1,0,0,0,893,894, - 5,47,0,0,894,895,5,42,0,0,895,899,1,0,0,0,896,898,9,0,0,0,897,896,1,0,0, - 0,898,901,1,0,0,0,899,900,1,0,0,0,899,897,1,0,0,0,900,902,1,0,0,0,901,899, - 1,0,0,0,902,903,5,42,0,0,903,904,5,47,0,0,904,905,1,0,0,0,905,906,6,75, - 1,0,906,152,1,0,0,0,907,908,5,47,0,0,908,909,5,47,0,0,909,913,1,0,0,0,910, - 912,8,10,0,0,911,910,1,0,0,0,912,915,1,0,0,0,913,911,1,0,0,0,913,914,1, - 0,0,0,914,916,1,0,0,0,915,913,1,0,0,0,916,917,6,76,1,0,917,154,1,0,0,0, - 28,0,510,516,522,533,592,595,599,604,610,614,649,668,674,681,683,691,693, - 697,713,731,768,781,877,883,889,899,913,2,6,0,0,0,1,0]; + 72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,3,72,779,8,72,1,72, + 1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,3,72,792,8,72,1,73,1, + 73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73, + 1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73, + 1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73, + 1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1,73,1, + 73,1,73,1,73,1,73,1,73,1,73,3,73,888,8,73,1,74,1,74,5,74,892,8,74,10,74, + 12,74,895,9,74,1,75,4,75,898,8,75,11,75,12,75,899,1,75,1,75,1,76,1,76,1, + 76,1,76,5,76,908,8,76,10,76,12,76,911,9,76,1,76,1,76,1,76,1,76,1,76,1,77, + 1,77,1,77,1,77,5,77,922,8,77,10,77,12,77,925,9,77,1,77,1,77,3,693,703,909, + 0,78,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27, + 14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51, + 26,53,27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71,36,73,37,75, + 38,77,39,79,40,81,41,83,42,85,43,87,44,89,45,91,46,93,47,95,48,97,49,99, + 50,101,51,103,52,105,53,107,54,109,55,111,56,113,57,115,58,117,59,119,60, + 121,61,123,62,125,63,127,64,129,65,131,66,133,67,135,68,137,69,139,70,141, + 71,143,72,145,73,147,74,149,75,151,76,153,77,155,78,1,0,11,1,0,48,57,2, + 0,69,69,101,101,1,0,49,57,3,0,10,10,13,13,34,34,3,0,10,10,13,13,39,39,2, + 0,88,88,120,120,3,0,48,57,65,70,97,102,2,0,65,90,97,122,4,0,48,57,65,90, + 95,95,97,122,3,0,9,10,12,13,32,32,2,0,10,10,13,13,972,0,1,1,0,0,0,0,3,1, + 0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15, + 1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0, + 0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37, + 1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0, + 0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59, + 1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0, + 0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,1,0,0,0,0,81, + 1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,1,0,0,0,0,91,1,0,0, + 0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0,0,0,0,101,1,0,0,0,0,103, + 1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109,1,0,0,0,0,111,1,0,0,0,0,113,1, + 0,0,0,0,115,1,0,0,0,0,117,1,0,0,0,0,119,1,0,0,0,0,121,1,0,0,0,0,123,1,0, + 0,0,0,125,1,0,0,0,0,127,1,0,0,0,0,129,1,0,0,0,0,131,1,0,0,0,0,133,1,0,0, + 0,0,135,1,0,0,0,0,137,1,0,0,0,0,139,1,0,0,0,0,141,1,0,0,0,0,143,1,0,0,0, + 0,145,1,0,0,0,0,147,1,0,0,0,0,149,1,0,0,0,0,151,1,0,0,0,0,153,1,0,0,0,0, + 155,1,0,0,0,1,157,1,0,0,0,3,164,1,0,0,0,5,166,1,0,0,0,7,177,1,0,0,0,9,179, + 1,0,0,0,11,181,1,0,0,0,13,184,1,0,0,0,15,186,1,0,0,0,17,188,1,0,0,0,19, + 191,1,0,0,0,21,193,1,0,0,0,23,202,1,0,0,0,25,204,1,0,0,0,27,206,1,0,0,0, + 29,214,1,0,0,0,31,223,1,0,0,0,33,225,1,0,0,0,35,227,1,0,0,0,37,229,1,0, + 0,0,39,237,1,0,0,0,41,249,1,0,0,0,43,252,1,0,0,0,45,257,1,0,0,0,47,260, + 1,0,0,0,49,266,1,0,0,0,51,270,1,0,0,0,53,274,1,0,0,0,55,276,1,0,0,0,57, + 278,1,0,0,0,59,289,1,0,0,0,61,296,1,0,0,0,63,313,1,0,0,0,65,328,1,0,0,0, + 67,343,1,0,0,0,69,356,1,0,0,0,71,366,1,0,0,0,73,391,1,0,0,0,75,406,1,0, + 0,0,77,425,1,0,0,0,79,441,1,0,0,0,81,452,1,0,0,0,83,460,1,0,0,0,85,467, + 1,0,0,0,87,474,1,0,0,0,89,476,1,0,0,0,91,478,1,0,0,0,93,480,1,0,0,0,95, + 482,1,0,0,0,97,484,1,0,0,0,99,486,1,0,0,0,101,489,1,0,0,0,103,492,1,0,0, + 0,105,495,1,0,0,0,107,498,1,0,0,0,109,500,1,0,0,0,111,502,1,0,0,0,113,505, + 1,0,0,0,115,508,1,0,0,0,117,518,1,0,0,0,119,543,1,0,0,0,121,602,1,0,0,0, + 123,605,1,0,0,0,125,612,1,0,0,0,127,627,1,0,0,0,129,659,1,0,0,0,131,661, + 1,0,0,0,133,678,1,0,0,0,135,680,1,0,0,0,137,707,1,0,0,0,139,709,1,0,0,0, + 141,718,1,0,0,0,143,741,1,0,0,0,145,791,1,0,0,0,147,887,1,0,0,0,149,889, + 1,0,0,0,151,897,1,0,0,0,153,903,1,0,0,0,155,917,1,0,0,0,157,158,5,112,0, + 0,158,159,5,114,0,0,159,160,5,97,0,0,160,161,5,103,0,0,161,162,5,109,0, + 0,162,163,5,97,0,0,163,2,1,0,0,0,164,165,5,59,0,0,165,4,1,0,0,0,166,167, + 5,99,0,0,167,168,5,97,0,0,168,169,5,115,0,0,169,170,5,104,0,0,170,171,5, + 115,0,0,171,172,5,99,0,0,172,173,5,114,0,0,173,174,5,105,0,0,174,175,5, + 112,0,0,175,176,5,116,0,0,176,6,1,0,0,0,177,178,5,94,0,0,178,8,1,0,0,0, + 179,180,5,126,0,0,180,10,1,0,0,0,181,182,5,62,0,0,182,183,5,61,0,0,183, + 12,1,0,0,0,184,185,5,62,0,0,185,14,1,0,0,0,186,187,5,60,0,0,187,16,1,0, + 0,0,188,189,5,60,0,0,189,190,5,61,0,0,190,18,1,0,0,0,191,192,5,61,0,0,192, + 20,1,0,0,0,193,194,5,99,0,0,194,195,5,111,0,0,195,196,5,110,0,0,196,197, + 5,116,0,0,197,198,5,114,0,0,198,199,5,97,0,0,199,200,5,99,0,0,200,201,5, + 116,0,0,201,22,1,0,0,0,202,203,5,123,0,0,203,24,1,0,0,0,204,205,5,125,0, + 0,205,26,1,0,0,0,206,207,5,108,0,0,207,208,5,105,0,0,208,209,5,98,0,0,209, + 210,5,114,0,0,210,211,5,97,0,0,211,212,5,114,0,0,212,213,5,121,0,0,213, + 28,1,0,0,0,214,215,5,102,0,0,215,216,5,117,0,0,216,217,5,110,0,0,217,218, + 5,99,0,0,218,219,5,116,0,0,219,220,5,105,0,0,220,221,5,111,0,0,221,222, + 5,110,0,0,222,30,1,0,0,0,223,224,5,40,0,0,224,32,1,0,0,0,225,226,5,44,0, + 0,226,34,1,0,0,0,227,228,5,41,0,0,228,36,1,0,0,0,229,230,5,114,0,0,230, + 231,5,101,0,0,231,232,5,113,0,0,232,233,5,117,0,0,233,234,5,105,0,0,234, + 235,5,114,0,0,235,236,5,101,0,0,236,38,1,0,0,0,237,238,5,99,0,0,238,239, + 5,111,0,0,239,240,5,110,0,0,240,241,5,115,0,0,241,242,5,111,0,0,242,243, + 5,108,0,0,243,244,5,101,0,0,244,245,5,46,0,0,245,246,5,108,0,0,246,247, + 5,111,0,0,247,248,5,103,0,0,248,40,1,0,0,0,249,250,5,105,0,0,250,251,5, + 102,0,0,251,42,1,0,0,0,252,253,5,101,0,0,253,254,5,108,0,0,254,255,5,115, + 0,0,255,256,5,101,0,0,256,44,1,0,0,0,257,258,5,100,0,0,258,259,5,111,0, + 0,259,46,1,0,0,0,260,261,5,119,0,0,261,262,5,104,0,0,262,263,5,105,0,0, + 263,264,5,108,0,0,264,265,5,101,0,0,265,48,1,0,0,0,266,267,5,102,0,0,267, + 268,5,111,0,0,268,269,5,114,0,0,269,50,1,0,0,0,270,271,5,110,0,0,271,272, + 5,101,0,0,272,273,5,119,0,0,273,52,1,0,0,0,274,275,5,91,0,0,275,54,1,0, + 0,0,276,277,5,93,0,0,277,56,1,0,0,0,278,279,5,116,0,0,279,280,5,120,0,0, + 280,281,5,46,0,0,281,282,5,111,0,0,282,283,5,117,0,0,283,284,5,116,0,0, + 284,285,5,112,0,0,285,286,5,117,0,0,286,287,5,116,0,0,287,288,5,115,0,0, + 288,58,1,0,0,0,289,290,5,46,0,0,290,291,5,118,0,0,291,292,5,97,0,0,292, + 293,5,108,0,0,293,294,5,117,0,0,294,295,5,101,0,0,295,60,1,0,0,0,296,297, + 5,46,0,0,297,298,5,108,0,0,298,299,5,111,0,0,299,300,5,99,0,0,300,301,5, + 107,0,0,301,302,5,105,0,0,302,303,5,110,0,0,303,304,5,103,0,0,304,305,5, + 66,0,0,305,306,5,121,0,0,306,307,5,116,0,0,307,308,5,101,0,0,308,309,5, + 99,0,0,309,310,5,111,0,0,310,311,5,100,0,0,311,312,5,101,0,0,312,62,1,0, + 0,0,313,314,5,46,0,0,314,315,5,116,0,0,315,316,5,111,0,0,316,317,5,107, + 0,0,317,318,5,101,0,0,318,319,5,110,0,0,319,320,5,67,0,0,320,321,5,97,0, + 0,321,322,5,116,0,0,322,323,5,101,0,0,323,324,5,103,0,0,324,325,5,111,0, + 0,325,326,5,114,0,0,326,327,5,121,0,0,327,64,1,0,0,0,328,329,5,46,0,0,329, + 330,5,110,0,0,330,331,5,102,0,0,331,332,5,116,0,0,332,333,5,67,0,0,333, + 334,5,111,0,0,334,335,5,109,0,0,335,336,5,109,0,0,336,337,5,105,0,0,337, + 338,5,116,0,0,338,339,5,109,0,0,339,340,5,101,0,0,340,341,5,110,0,0,341, + 342,5,116,0,0,342,66,1,0,0,0,343,344,5,46,0,0,344,345,5,116,0,0,345,346, + 5,111,0,0,346,347,5,107,0,0,347,348,5,101,0,0,348,349,5,110,0,0,349,350, + 5,65,0,0,350,351,5,109,0,0,351,352,5,111,0,0,352,353,5,117,0,0,353,354, + 5,110,0,0,354,355,5,116,0,0,355,68,1,0,0,0,356,357,5,116,0,0,357,358,5, + 120,0,0,358,359,5,46,0,0,359,360,5,105,0,0,360,361,5,110,0,0,361,362,5, + 112,0,0,362,363,5,117,0,0,363,364,5,116,0,0,364,365,5,115,0,0,365,70,1, + 0,0,0,366,367,5,46,0,0,367,368,5,111,0,0,368,369,5,117,0,0,369,370,5,116, + 0,0,370,371,5,112,0,0,371,372,5,111,0,0,372,373,5,105,0,0,373,374,5,110, + 0,0,374,375,5,116,0,0,375,376,5,84,0,0,376,377,5,114,0,0,377,378,5,97,0, + 0,378,379,5,110,0,0,379,380,5,115,0,0,380,381,5,97,0,0,381,382,5,99,0,0, + 382,383,5,116,0,0,383,384,5,105,0,0,384,385,5,111,0,0,385,386,5,110,0,0, + 386,387,5,72,0,0,387,388,5,97,0,0,388,389,5,115,0,0,389,390,5,104,0,0,390, + 72,1,0,0,0,391,392,5,46,0,0,392,393,5,111,0,0,393,394,5,117,0,0,394,395, + 5,116,0,0,395,396,5,112,0,0,396,397,5,111,0,0,397,398,5,105,0,0,398,399, + 5,110,0,0,399,400,5,116,0,0,400,401,5,73,0,0,401,402,5,110,0,0,402,403, + 5,100,0,0,403,404,5,101,0,0,404,405,5,120,0,0,405,74,1,0,0,0,406,407,5, + 46,0,0,407,408,5,117,0,0,408,409,5,110,0,0,409,410,5,108,0,0,410,411,5, + 111,0,0,411,412,5,99,0,0,412,413,5,107,0,0,413,414,5,105,0,0,414,415,5, + 110,0,0,415,416,5,103,0,0,416,417,5,66,0,0,417,418,5,121,0,0,418,419,5, + 116,0,0,419,420,5,101,0,0,420,421,5,99,0,0,421,422,5,111,0,0,422,423,5, + 100,0,0,423,424,5,101,0,0,424,76,1,0,0,0,425,426,5,46,0,0,426,427,5,115, + 0,0,427,428,5,101,0,0,428,429,5,113,0,0,429,430,5,117,0,0,430,431,5,101, + 0,0,431,432,5,110,0,0,432,433,5,99,0,0,433,434,5,101,0,0,434,435,5,78,0, + 0,435,436,5,117,0,0,436,437,5,109,0,0,437,438,5,98,0,0,438,439,5,101,0, + 0,439,440,5,114,0,0,440,78,1,0,0,0,441,442,5,46,0,0,442,443,5,114,0,0,443, + 444,5,101,0,0,444,445,5,118,0,0,445,446,5,101,0,0,446,447,5,114,0,0,447, + 448,5,115,0,0,448,449,5,101,0,0,449,450,5,40,0,0,450,451,5,41,0,0,451,80, + 1,0,0,0,452,453,5,46,0,0,453,454,5,108,0,0,454,455,5,101,0,0,455,456,5, + 110,0,0,456,457,5,103,0,0,457,458,5,116,0,0,458,459,5,104,0,0,459,82,1, + 0,0,0,460,461,5,46,0,0,461,462,5,115,0,0,462,463,5,112,0,0,463,464,5,108, + 0,0,464,465,5,105,0,0,465,466,5,116,0,0,466,84,1,0,0,0,467,468,5,46,0,0, + 468,469,5,115,0,0,469,470,5,108,0,0,470,471,5,105,0,0,471,472,5,99,0,0, + 472,473,5,101,0,0,473,86,1,0,0,0,474,475,5,33,0,0,475,88,1,0,0,0,476,477, + 5,45,0,0,477,90,1,0,0,0,478,479,5,42,0,0,479,92,1,0,0,0,480,481,5,47,0, + 0,481,94,1,0,0,0,482,483,5,37,0,0,483,96,1,0,0,0,484,485,5,43,0,0,485,98, + 1,0,0,0,486,487,5,62,0,0,487,488,5,62,0,0,488,100,1,0,0,0,489,490,5,60, + 0,0,490,491,5,60,0,0,491,102,1,0,0,0,492,493,5,61,0,0,493,494,5,61,0,0, + 494,104,1,0,0,0,495,496,5,33,0,0,496,497,5,61,0,0,497,106,1,0,0,0,498,499, + 5,38,0,0,499,108,1,0,0,0,500,501,5,124,0,0,501,110,1,0,0,0,502,503,5,38, + 0,0,503,504,5,38,0,0,504,112,1,0,0,0,505,506,5,124,0,0,506,507,5,124,0, + 0,507,114,1,0,0,0,508,509,5,99,0,0,509,510,5,111,0,0,510,511,5,110,0,0, + 511,512,5,115,0,0,512,513,5,116,0,0,513,514,5,97,0,0,514,515,5,110,0,0, + 515,516,5,116,0,0,516,116,1,0,0,0,517,519,7,0,0,0,518,517,1,0,0,0,519,520, + 1,0,0,0,520,518,1,0,0,0,520,521,1,0,0,0,521,522,1,0,0,0,522,524,5,46,0, + 0,523,525,7,0,0,0,524,523,1,0,0,0,525,526,1,0,0,0,526,524,1,0,0,0,526,527, + 1,0,0,0,527,528,1,0,0,0,528,530,5,46,0,0,529,531,7,0,0,0,530,529,1,0,0, + 0,531,532,1,0,0,0,532,530,1,0,0,0,532,533,1,0,0,0,533,118,1,0,0,0,534,535, + 5,116,0,0,535,536,5,114,0,0,536,537,5,117,0,0,537,544,5,101,0,0,538,539, + 5,102,0,0,539,540,5,97,0,0,540,541,5,108,0,0,541,542,5,115,0,0,542,544, + 5,101,0,0,543,534,1,0,0,0,543,538,1,0,0,0,544,120,1,0,0,0,545,546,5,115, + 0,0,546,547,5,97,0,0,547,548,5,116,0,0,548,549,5,111,0,0,549,550,5,115, + 0,0,550,551,5,104,0,0,551,552,5,105,0,0,552,603,5,115,0,0,553,554,5,115, + 0,0,554,555,5,97,0,0,555,556,5,116,0,0,556,603,5,115,0,0,557,558,5,102, + 0,0,558,559,5,105,0,0,559,560,5,110,0,0,560,561,5,110,0,0,561,562,5,101, + 0,0,562,603,5,121,0,0,563,564,5,98,0,0,564,565,5,105,0,0,565,566,5,116, + 0,0,566,603,5,115,0,0,567,568,5,98,0,0,568,569,5,105,0,0,569,570,5,116, + 0,0,570,571,5,99,0,0,571,572,5,111,0,0,572,573,5,105,0,0,573,603,5,110, + 0,0,574,575,5,115,0,0,575,576,5,101,0,0,576,577,5,99,0,0,577,578,5,111, + 0,0,578,579,5,110,0,0,579,580,5,100,0,0,580,603,5,115,0,0,581,582,5,109, + 0,0,582,583,5,105,0,0,583,584,5,110,0,0,584,585,5,117,0,0,585,586,5,116, + 0,0,586,587,5,101,0,0,587,603,5,115,0,0,588,589,5,104,0,0,589,590,5,111, + 0,0,590,591,5,117,0,0,591,592,5,114,0,0,592,603,5,115,0,0,593,594,5,100, + 0,0,594,595,5,97,0,0,595,596,5,121,0,0,596,603,5,115,0,0,597,598,5,119, + 0,0,598,599,5,101,0,0,599,600,5,101,0,0,600,601,5,107,0,0,601,603,5,115, + 0,0,602,545,1,0,0,0,602,553,1,0,0,0,602,557,1,0,0,0,602,563,1,0,0,0,602, + 567,1,0,0,0,602,574,1,0,0,0,602,581,1,0,0,0,602,588,1,0,0,0,602,593,1,0, + 0,0,602,597,1,0,0,0,603,122,1,0,0,0,604,606,5,45,0,0,605,604,1,0,0,0,605, + 606,1,0,0,0,606,607,1,0,0,0,607,609,3,125,62,0,608,610,3,127,63,0,609,608, + 1,0,0,0,609,610,1,0,0,0,610,124,1,0,0,0,611,613,7,0,0,0,612,611,1,0,0,0, + 613,614,1,0,0,0,614,612,1,0,0,0,614,615,1,0,0,0,615,624,1,0,0,0,616,618, + 5,95,0,0,617,619,7,0,0,0,618,617,1,0,0,0,619,620,1,0,0,0,620,618,1,0,0, + 0,620,621,1,0,0,0,621,623,1,0,0,0,622,616,1,0,0,0,623,626,1,0,0,0,624,622, + 1,0,0,0,624,625,1,0,0,0,625,126,1,0,0,0,626,624,1,0,0,0,627,628,7,1,0,0, + 628,629,3,125,62,0,629,128,1,0,0,0,630,631,5,105,0,0,631,632,5,110,0,0, + 632,660,5,116,0,0,633,634,5,98,0,0,634,635,5,111,0,0,635,636,5,111,0,0, + 636,660,5,108,0,0,637,638,5,115,0,0,638,639,5,116,0,0,639,640,5,114,0,0, + 640,641,5,105,0,0,641,642,5,110,0,0,642,660,5,103,0,0,643,644,5,112,0,0, + 644,645,5,117,0,0,645,646,5,98,0,0,646,647,5,107,0,0,647,648,5,101,0,0, + 648,660,5,121,0,0,649,650,5,115,0,0,650,651,5,105,0,0,651,660,5,103,0,0, + 652,653,5,100,0,0,653,654,5,97,0,0,654,655,5,116,0,0,655,656,5,97,0,0,656, + 657,5,115,0,0,657,658,5,105,0,0,658,660,5,103,0,0,659,630,1,0,0,0,659,633, + 1,0,0,0,659,637,1,0,0,0,659,643,1,0,0,0,659,649,1,0,0,0,659,652,1,0,0,0, + 660,130,1,0,0,0,661,662,5,98,0,0,662,663,5,121,0,0,663,664,5,116,0,0,664, + 665,5,101,0,0,665,666,5,115,0,0,666,132,1,0,0,0,667,668,5,98,0,0,668,669, + 5,121,0,0,669,670,5,116,0,0,670,671,5,101,0,0,671,672,5,115,0,0,672,673, + 1,0,0,0,673,679,3,135,67,0,674,675,5,98,0,0,675,676,5,121,0,0,676,677,5, + 116,0,0,677,679,5,101,0,0,678,667,1,0,0,0,678,674,1,0,0,0,679,134,1,0,0, + 0,680,684,7,2,0,0,681,683,7,0,0,0,682,681,1,0,0,0,683,686,1,0,0,0,684,682, + 1,0,0,0,684,685,1,0,0,0,685,136,1,0,0,0,686,684,1,0,0,0,687,693,5,34,0, + 0,688,689,5,92,0,0,689,692,5,34,0,0,690,692,8,3,0,0,691,688,1,0,0,0,691, + 690,1,0,0,0,692,695,1,0,0,0,693,694,1,0,0,0,693,691,1,0,0,0,694,696,1,0, + 0,0,695,693,1,0,0,0,696,708,5,34,0,0,697,703,5,39,0,0,698,699,5,92,0,0, + 699,702,5,39,0,0,700,702,8,4,0,0,701,698,1,0,0,0,701,700,1,0,0,0,702,705, + 1,0,0,0,703,704,1,0,0,0,703,701,1,0,0,0,704,706,1,0,0,0,705,703,1,0,0,0, + 706,708,5,39,0,0,707,687,1,0,0,0,707,697,1,0,0,0,708,138,1,0,0,0,709,710, + 5,100,0,0,710,711,5,97,0,0,711,712,5,116,0,0,712,713,5,101,0,0,713,714, + 5,40,0,0,714,715,1,0,0,0,715,716,3,137,68,0,716,717,5,41,0,0,717,140,1, + 0,0,0,718,719,5,48,0,0,719,723,7,5,0,0,720,722,7,6,0,0,721,720,1,0,0,0, + 722,725,1,0,0,0,723,721,1,0,0,0,723,724,1,0,0,0,724,142,1,0,0,0,725,723, + 1,0,0,0,726,727,5,116,0,0,727,728,5,104,0,0,728,729,5,105,0,0,729,730,5, + 115,0,0,730,731,5,46,0,0,731,732,5,97,0,0,732,733,5,103,0,0,733,742,5,101, + 0,0,734,735,5,116,0,0,735,736,5,120,0,0,736,737,5,46,0,0,737,738,5,116, + 0,0,738,739,5,105,0,0,739,740,5,109,0,0,740,742,5,101,0,0,741,726,1,0,0, + 0,741,734,1,0,0,0,742,144,1,0,0,0,743,744,5,117,0,0,744,745,5,110,0,0,745, + 746,5,115,0,0,746,747,5,97,0,0,747,748,5,102,0,0,748,749,5,101,0,0,749, + 750,5,95,0,0,750,751,5,105,0,0,751,752,5,110,0,0,752,792,5,116,0,0,753, + 754,5,117,0,0,754,755,5,110,0,0,755,756,5,115,0,0,756,757,5,97,0,0,757, + 758,5,102,0,0,758,759,5,101,0,0,759,760,5,95,0,0,760,761,5,98,0,0,761,762, + 5,111,0,0,762,763,5,111,0,0,763,792,5,108,0,0,764,765,5,117,0,0,765,766, + 5,110,0,0,766,767,5,115,0,0,767,768,5,97,0,0,768,769,5,102,0,0,769,770, + 5,101,0,0,770,771,5,95,0,0,771,772,5,98,0,0,772,773,5,121,0,0,773,774,5, + 116,0,0,774,775,5,101,0,0,775,776,5,115,0,0,776,778,1,0,0,0,777,779,3,135, + 67,0,778,777,1,0,0,0,778,779,1,0,0,0,779,792,1,0,0,0,780,781,5,117,0,0, + 781,782,5,110,0,0,782,783,5,115,0,0,783,784,5,97,0,0,784,785,5,102,0,0, + 785,786,5,101,0,0,786,787,5,95,0,0,787,788,5,98,0,0,788,789,5,121,0,0,789, + 790,5,116,0,0,790,792,5,101,0,0,791,743,1,0,0,0,791,753,1,0,0,0,791,764, + 1,0,0,0,791,780,1,0,0,0,792,146,1,0,0,0,793,794,5,116,0,0,794,795,5,104, + 0,0,795,796,5,105,0,0,796,797,5,115,0,0,797,798,5,46,0,0,798,799,5,97,0, + 0,799,800,5,99,0,0,800,801,5,116,0,0,801,802,5,105,0,0,802,803,5,118,0, + 0,803,804,5,101,0,0,804,805,5,73,0,0,805,806,5,110,0,0,806,807,5,112,0, + 0,807,808,5,117,0,0,808,809,5,116,0,0,809,810,5,73,0,0,810,811,5,110,0, + 0,811,812,5,100,0,0,812,813,5,101,0,0,813,888,5,120,0,0,814,815,5,116,0, + 0,815,816,5,104,0,0,816,817,5,105,0,0,817,818,5,115,0,0,818,819,5,46,0, + 0,819,820,5,97,0,0,820,821,5,99,0,0,821,822,5,116,0,0,822,823,5,105,0,0, + 823,824,5,118,0,0,824,825,5,101,0,0,825,826,5,66,0,0,826,827,5,121,0,0, + 827,828,5,116,0,0,828,829,5,101,0,0,829,830,5,99,0,0,830,831,5,111,0,0, + 831,832,5,100,0,0,832,888,5,101,0,0,833,834,5,116,0,0,834,835,5,120,0,0, + 835,836,5,46,0,0,836,837,5,105,0,0,837,838,5,110,0,0,838,839,5,112,0,0, + 839,840,5,117,0,0,840,841,5,116,0,0,841,842,5,115,0,0,842,843,5,46,0,0, + 843,844,5,108,0,0,844,845,5,101,0,0,845,846,5,110,0,0,846,847,5,103,0,0, + 847,848,5,116,0,0,848,888,5,104,0,0,849,850,5,116,0,0,850,851,5,120,0,0, + 851,852,5,46,0,0,852,853,5,111,0,0,853,854,5,117,0,0,854,855,5,116,0,0, + 855,856,5,112,0,0,856,857,5,117,0,0,857,858,5,116,0,0,858,859,5,115,0,0, + 859,860,5,46,0,0,860,861,5,108,0,0,861,862,5,101,0,0,862,863,5,110,0,0, + 863,864,5,103,0,0,864,865,5,116,0,0,865,888,5,104,0,0,866,867,5,116,0,0, + 867,868,5,120,0,0,868,869,5,46,0,0,869,870,5,118,0,0,870,871,5,101,0,0, + 871,872,5,114,0,0,872,873,5,115,0,0,873,874,5,105,0,0,874,875,5,111,0,0, + 875,888,5,110,0,0,876,877,5,116,0,0,877,878,5,120,0,0,878,879,5,46,0,0, + 879,880,5,108,0,0,880,881,5,111,0,0,881,882,5,99,0,0,882,883,5,107,0,0, + 883,884,5,116,0,0,884,885,5,105,0,0,885,886,5,109,0,0,886,888,5,101,0,0, + 887,793,1,0,0,0,887,814,1,0,0,0,887,833,1,0,0,0,887,849,1,0,0,0,887,866, + 1,0,0,0,887,876,1,0,0,0,888,148,1,0,0,0,889,893,7,7,0,0,890,892,7,8,0,0, + 891,890,1,0,0,0,892,895,1,0,0,0,893,891,1,0,0,0,893,894,1,0,0,0,894,150, + 1,0,0,0,895,893,1,0,0,0,896,898,7,9,0,0,897,896,1,0,0,0,898,899,1,0,0,0, + 899,897,1,0,0,0,899,900,1,0,0,0,900,901,1,0,0,0,901,902,6,75,0,0,902,152, + 1,0,0,0,903,904,5,47,0,0,904,905,5,42,0,0,905,909,1,0,0,0,906,908,9,0,0, + 0,907,906,1,0,0,0,908,911,1,0,0,0,909,910,1,0,0,0,909,907,1,0,0,0,910,912, + 1,0,0,0,911,909,1,0,0,0,912,913,5,42,0,0,913,914,5,47,0,0,914,915,1,0,0, + 0,915,916,6,76,1,0,916,154,1,0,0,0,917,918,5,47,0,0,918,919,5,47,0,0,919, + 923,1,0,0,0,920,922,8,10,0,0,921,920,1,0,0,0,922,925,1,0,0,0,923,921,1, + 0,0,0,923,924,1,0,0,0,924,926,1,0,0,0,925,923,1,0,0,0,926,927,6,77,1,0, + 927,156,1,0,0,0,28,0,520,526,532,543,602,605,609,614,620,624,659,678,684, + 691,693,701,703,707,723,741,778,791,887,893,899,909,923,2,6,0,0,0,1,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/packages/cashc/src/grammar/CashScriptParser.ts b/packages/cashc/src/grammar/CashScriptParser.ts index dc7f04a2..20d42f2c 100644 --- a/packages/cashc/src/grammar/CashScriptParser.ts +++ b/packages/cashc/src/grammar/CashScriptParser.ts @@ -1,4 +1,4 @@ -// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.1 +// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.2 // noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols import { @@ -75,64 +75,67 @@ export default class CashScriptParser extends Parser { public static readonly T__54 = 55; public static readonly T__55 = 56; public static readonly T__56 = 57; - public static readonly VersionLiteral = 58; - public static readonly BooleanLiteral = 59; - public static readonly NumberUnit = 60; - public static readonly NumberLiteral = 61; - public static readonly NumberPart = 62; - public static readonly ExponentPart = 63; - public static readonly PrimitiveType = 64; - public static readonly UnboundedBytes = 65; - public static readonly BoundedBytes = 66; - public static readonly Bound = 67; - public static readonly StringLiteral = 68; - public static readonly DateLiteral = 69; - public static readonly HexLiteral = 70; - public static readonly TxVar = 71; - public static readonly UnsafeCast = 72; - public static readonly NullaryOp = 73; - public static readonly Identifier = 74; - public static readonly WHITESPACE = 75; - public static readonly COMMENT = 76; - public static readonly LINE_COMMENT = 77; - public static readonly EOF = Token.EOF; + public static readonly T__57 = 58; + public static readonly VersionLiteral = 59; + public static readonly BooleanLiteral = 60; + public static readonly NumberUnit = 61; + public static readonly NumberLiteral = 62; + public static readonly NumberPart = 63; + public static readonly ExponentPart = 64; + public static readonly PrimitiveType = 65; + public static readonly UnboundedBytes = 66; + public static readonly BoundedBytes = 67; + public static readonly Bound = 68; + public static readonly StringLiteral = 69; + public static readonly DateLiteral = 70; + public static readonly HexLiteral = 71; + public static readonly TxVar = 72; + public static readonly UnsafeCast = 73; + public static readonly NullaryOp = 74; + public static readonly Identifier = 75; + public static readonly WHITESPACE = 76; + public static readonly COMMENT = 77; + public static readonly LINE_COMMENT = 78; + public static override readonly EOF = Token.EOF; public static readonly RULE_sourceFile = 0; - public static readonly RULE_pragmaDirective = 1; - public static readonly RULE_pragmaName = 2; - public static readonly RULE_pragmaValue = 3; - public static readonly RULE_versionConstraint = 4; - public static readonly RULE_versionOperator = 5; - public static readonly RULE_contractDefinition = 6; - public static readonly RULE_functionDefinition = 7; - public static readonly RULE_parameterList = 8; - public static readonly RULE_parameter = 9; - public static readonly RULE_block = 10; - public static readonly RULE_statement = 11; - public static readonly RULE_nonControlStatement = 12; - public static readonly RULE_controlStatement = 13; - public static readonly RULE_variableDefinition = 14; - public static readonly RULE_tupleAssignment = 15; - public static readonly RULE_assignStatement = 16; - public static readonly RULE_timeOpStatement = 17; - public static readonly RULE_requireStatement = 18; - public static readonly RULE_consoleStatement = 19; - public static readonly RULE_ifStatement = 20; - public static readonly RULE_loopStatement = 21; - public static readonly RULE_doWhileStatement = 22; - public static readonly RULE_whileStatement = 23; - public static readonly RULE_forStatement = 24; - public static readonly RULE_forInit = 25; - public static readonly RULE_requireMessage = 26; - public static readonly RULE_consoleParameter = 27; - public static readonly RULE_consoleParameterList = 28; - public static readonly RULE_functionCall = 29; - public static readonly RULE_expressionList = 30; - public static readonly RULE_expression = 31; - public static readonly RULE_modifier = 32; - public static readonly RULE_literal = 33; - public static readonly RULE_numberLiteral = 34; - public static readonly RULE_typeName = 35; - public static readonly RULE_typeCast = 36; + public static readonly RULE_topLevelDefinition = 1; + public static readonly RULE_pragmaDirective = 2; + public static readonly RULE_pragmaName = 3; + public static readonly RULE_pragmaValue = 4; + public static readonly RULE_versionConstraint = 5; + public static readonly RULE_versionOperator = 6; + public static readonly RULE_contractDefinition = 7; + public static readonly RULE_libraryDefinition = 8; + public static readonly RULE_functionDefinition = 9; + public static readonly RULE_parameterList = 10; + public static readonly RULE_parameter = 11; + public static readonly RULE_block = 12; + public static readonly RULE_statement = 13; + public static readonly RULE_nonControlStatement = 14; + public static readonly RULE_controlStatement = 15; + public static readonly RULE_variableDefinition = 16; + public static readonly RULE_tupleAssignment = 17; + public static readonly RULE_assignStatement = 18; + public static readonly RULE_timeOpStatement = 19; + public static readonly RULE_requireStatement = 20; + public static readonly RULE_consoleStatement = 21; + public static readonly RULE_ifStatement = 22; + public static readonly RULE_loopStatement = 23; + public static readonly RULE_doWhileStatement = 24; + public static readonly RULE_whileStatement = 25; + public static readonly RULE_forStatement = 26; + public static readonly RULE_forInit = 27; + public static readonly RULE_requireMessage = 28; + public static readonly RULE_consoleParameter = 29; + public static readonly RULE_consoleParameterList = 30; + public static readonly RULE_functionCall = 31; + public static readonly RULE_expressionList = 32; + public static readonly RULE_expression = 33; + public static readonly RULE_modifier = 34; + public static readonly RULE_literal = 35; + public static readonly RULE_numberLiteral = 36; + public static readonly RULE_typeName = 37; + public static readonly RULE_typeCast = 38; public static readonly literalNames: (string | null)[] = [ null, "'pragma'", "';'", "'cashscript'", "'^'", "'~'", @@ -140,6 +143,7 @@ export default class CashScriptParser extends Parser { "'<'", "'<='", "'='", "'contract'", "'{'", "'}'", + "'library'", "'function'", "'('", "','", "')'", "'require'", @@ -204,7 +208,7 @@ export default class CashScriptParser extends Parser { null, null, null, null, null, null, - "VersionLiteral", + null, "VersionLiteral", "BooleanLiteral", "NumberUnit", "NumberLiteral", @@ -224,14 +228,15 @@ export default class CashScriptParser extends Parser { "LINE_COMMENT" ]; // tslint:disable:no-trailing-whitespace public static readonly ruleNames: string[] = [ - "sourceFile", "pragmaDirective", "pragmaName", "pragmaValue", "versionConstraint", - "versionOperator", "contractDefinition", "functionDefinition", "parameterList", - "parameter", "block", "statement", "nonControlStatement", "controlStatement", - "variableDefinition", "tupleAssignment", "assignStatement", "timeOpStatement", - "requireStatement", "consoleStatement", "ifStatement", "loopStatement", - "doWhileStatement", "whileStatement", "forStatement", "forInit", "requireMessage", - "consoleParameter", "consoleParameterList", "functionCall", "expressionList", - "expression", "modifier", "literal", "numberLiteral", "typeName", "typeCast", + "sourceFile", "topLevelDefinition", "pragmaDirective", "pragmaName", "pragmaValue", + "versionConstraint", "versionOperator", "contractDefinition", "libraryDefinition", + "functionDefinition", "parameterList", "parameter", "block", "statement", + "nonControlStatement", "controlStatement", "variableDefinition", "tupleAssignment", + "assignStatement", "timeOpStatement", "requireStatement", "consoleStatement", + "ifStatement", "loopStatement", "doWhileStatement", "whileStatement", + "forStatement", "forInit", "requireMessage", "consoleParameter", "consoleParameterList", + "functionCall", "expressionList", "expression", "modifier", "literal", + "numberLiteral", "typeName", "typeCast", ]; public get grammarFileName(): string { return "CashScript.g4"; } public get literalNames(): (string | null)[] { return CashScriptParser.literalNames; } @@ -255,23 +260,23 @@ export default class CashScriptParser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 77; + this.state = 81; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===1) { { { - this.state = 74; + this.state = 78; this.pragmaDirective(); } } - this.state = 79; + this.state = 83; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 80; - this.contractDefinition(); - this.state = 81; + this.state = 84; + this.topLevelDefinition(); + this.state = 85; this.match(CashScriptParser.EOF); } } @@ -290,19 +295,59 @@ export default class CashScriptParser extends Parser { return localctx; } // @RuleVersion(0) + public topLevelDefinition(): TopLevelDefinitionContext { + let localctx: TopLevelDefinitionContext = new TopLevelDefinitionContext(this, this._ctx, this.state); + this.enterRule(localctx, 2, CashScriptParser.RULE_topLevelDefinition); + try { + this.state = 89; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case 11: + this.enterOuterAlt(localctx, 1); + { + this.state = 87; + this.contractDefinition(); + } + break; + case 14: + this.enterOuterAlt(localctx, 2); + { + this.state = 88; + this.libraryDefinition(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) public pragmaDirective(): PragmaDirectiveContext { let localctx: PragmaDirectiveContext = new PragmaDirectiveContext(this, this._ctx, this.state); - this.enterRule(localctx, 2, CashScriptParser.RULE_pragmaDirective); + this.enterRule(localctx, 4, CashScriptParser.RULE_pragmaDirective); try { this.enterOuterAlt(localctx, 1); { - this.state = 83; + this.state = 91; this.match(CashScriptParser.T__0); - this.state = 84; + this.state = 92; this.pragmaName(); - this.state = 85; + this.state = 93; this.pragmaValue(); - this.state = 86; + this.state = 94; this.match(CashScriptParser.T__1); } } @@ -323,11 +368,11 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public pragmaName(): PragmaNameContext { let localctx: PragmaNameContext = new PragmaNameContext(this, this._ctx, this.state); - this.enterRule(localctx, 4, CashScriptParser.RULE_pragmaName); + this.enterRule(localctx, 6, CashScriptParser.RULE_pragmaName); try { this.enterOuterAlt(localctx, 1); { - this.state = 88; + this.state = 96; this.match(CashScriptParser.T__2); } } @@ -348,19 +393,19 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public pragmaValue(): PragmaValueContext { let localctx: PragmaValueContext = new PragmaValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 6, CashScriptParser.RULE_pragmaValue); + this.enterRule(localctx, 8, CashScriptParser.RULE_pragmaValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 90; + this.state = 98; this.versionConstraint(); - this.state = 92; + this.state = 100; this._errHandler.sync(this); _la = this._input.LA(1); - if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 2032) !== 0) || _la===58) { + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 2032) !== 0) || _la===59) { { - this.state = 91; + this.state = 99; this.versionConstraint(); } } @@ -384,22 +429,22 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public versionConstraint(): VersionConstraintContext { let localctx: VersionConstraintContext = new VersionConstraintContext(this, this._ctx, this.state); - this.enterRule(localctx, 8, CashScriptParser.RULE_versionConstraint); + this.enterRule(localctx, 10, CashScriptParser.RULE_versionConstraint); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 95; + this.state = 103; this._errHandler.sync(this); _la = this._input.LA(1); if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 2032) !== 0)) { { - this.state = 94; + this.state = 102; this.versionOperator(); } } - this.state = 97; + this.state = 105; this.match(CashScriptParser.VersionLiteral); } } @@ -420,12 +465,12 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public versionOperator(): VersionOperatorContext { let localctx: VersionOperatorContext = new VersionOperatorContext(this, this._ctx, this.state); - this.enterRule(localctx, 10, CashScriptParser.RULE_versionOperator); + this.enterRule(localctx, 12, CashScriptParser.RULE_versionOperator); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 99; + this.state = 107; _la = this._input.LA(1); if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 2032) !== 0))) { this._errHandler.recoverInline(this); @@ -453,34 +498,80 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public contractDefinition(): ContractDefinitionContext { let localctx: ContractDefinitionContext = new ContractDefinitionContext(this, this._ctx, this.state); - this.enterRule(localctx, 12, CashScriptParser.RULE_contractDefinition); + this.enterRule(localctx, 14, CashScriptParser.RULE_contractDefinition); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 101; + this.state = 109; this.match(CashScriptParser.T__10); - this.state = 102; + this.state = 110; this.match(CashScriptParser.Identifier); - this.state = 103; + this.state = 111; this.parameterList(); - this.state = 104; + this.state = 112; this.match(CashScriptParser.T__11); - this.state = 108; + this.state = 116; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===14) { + while (_la===15) { { { - this.state = 105; + this.state = 113; this.functionDefinition(); } } - this.state = 110; + this.state = 118; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 111; + this.state = 119; + this.match(CashScriptParser.T__12); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public libraryDefinition(): LibraryDefinitionContext { + let localctx: LibraryDefinitionContext = new LibraryDefinitionContext(this, this._ctx, this.state); + this.enterRule(localctx, 16, CashScriptParser.RULE_libraryDefinition); + let _la: number; + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 121; + this.match(CashScriptParser.T__13); + this.state = 122; + this.match(CashScriptParser.Identifier); + this.state = 123; + this.match(CashScriptParser.T__11); + this.state = 127; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la===15) { + { + { + this.state = 124; + this.functionDefinition(); + } + } + this.state = 129; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + this.state = 130; this.match(CashScriptParser.T__12); } } @@ -501,34 +592,34 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public functionDefinition(): FunctionDefinitionContext { let localctx: FunctionDefinitionContext = new FunctionDefinitionContext(this, this._ctx, this.state); - this.enterRule(localctx, 14, CashScriptParser.RULE_functionDefinition); + this.enterRule(localctx, 18, CashScriptParser.RULE_functionDefinition); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 113; - this.match(CashScriptParser.T__13); - this.state = 114; + this.state = 132; + this.match(CashScriptParser.T__14); + this.state = 133; this.match(CashScriptParser.Identifier); - this.state = 115; + this.state = 134; this.parameterList(); - this.state = 116; + this.state = 135; this.match(CashScriptParser.T__11); - this.state = 120; + this.state = 139; this._errHandler.sync(this); _la = this._input.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 31195136) !== 0) || ((((_la - 64)) & ~0x1F) === 0 && ((1 << (_la - 64)) & 1031) !== 0)) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 62390272) !== 0) || ((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 1031) !== 0)) { { { - this.state = 117; + this.state = 136; this.statement(); } } - this.state = 122; + this.state = 141; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 123; + this.state = 142; this.match(CashScriptParser.T__12); } } @@ -549,54 +640,54 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public parameterList(): ParameterListContext { let localctx: ParameterListContext = new ParameterListContext(this, this._ctx, this.state); - this.enterRule(localctx, 16, CashScriptParser.RULE_parameterList); + this.enterRule(localctx, 20, CashScriptParser.RULE_parameterList); let _la: number; try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 125; - this.match(CashScriptParser.T__14); - this.state = 137; + this.state = 144; + this.match(CashScriptParser.T__15); + this.state = 156; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 64)) & ~0x1F) === 0 && ((1 << (_la - 64)) & 7) !== 0)) { + if (((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 7) !== 0)) { { - this.state = 126; + this.state = 145; this.parameter(); - this.state = 131; + this.state = 150; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 5, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 7, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 127; - this.match(CashScriptParser.T__15); - this.state = 128; + this.state = 146; + this.match(CashScriptParser.T__16); + this.state = 147; this.parameter(); } } } - this.state = 133; + this.state = 152; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 5, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 7, this._ctx); } - this.state = 135; + this.state = 154; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 134; - this.match(CashScriptParser.T__15); + this.state = 153; + this.match(CashScriptParser.T__16); } } } } - this.state = 139; - this.match(CashScriptParser.T__16); + this.state = 158; + this.match(CashScriptParser.T__17); } } catch (re) { @@ -616,13 +707,13 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public parameter(): ParameterContext { let localctx: ParameterContext = new ParameterContext(this, this._ctx, this.state); - this.enterRule(localctx, 18, CashScriptParser.RULE_parameter); + this.enterRule(localctx, 22, CashScriptParser.RULE_parameter); try { this.enterOuterAlt(localctx, 1); { - this.state = 141; + this.state = 160; this.typeName(); - this.state = 142; + this.state = 161; this.match(CashScriptParser.Identifier); } } @@ -643,48 +734,48 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public block(): BlockContext { let localctx: BlockContext = new BlockContext(this, this._ctx, this.state); - this.enterRule(localctx, 20, CashScriptParser.RULE_block); + this.enterRule(localctx, 24, CashScriptParser.RULE_block); let _la: number; try { - this.state = 153; + this.state = 172; this._errHandler.sync(this); switch (this._input.LA(1)) { case 12: this.enterOuterAlt(localctx, 1); { - this.state = 144; + this.state = 163; this.match(CashScriptParser.T__11); - this.state = 148; + this.state = 167; this._errHandler.sync(this); _la = this._input.LA(1); - while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 31195136) !== 0) || ((((_la - 64)) & ~0x1F) === 0 && ((1 << (_la - 64)) & 1031) !== 0)) { + while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 62390272) !== 0) || ((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 1031) !== 0)) { { { - this.state = 145; + this.state = 164; this.statement(); } } - this.state = 150; + this.state = 169; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 151; + this.state = 170; this.match(CashScriptParser.T__12); } break; - case 18: case 19: case 20: - case 22: + case 21: case 23: case 24: - case 64: + case 25: case 65: case 66: - case 74: + case 67: + case 75: this.enterOuterAlt(localctx, 2); { - this.state = 152; + this.state = 171; this.statement(); } break; @@ -709,32 +800,32 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public statement(): StatementContext { let localctx: StatementContext = new StatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 22, CashScriptParser.RULE_statement); + this.enterRule(localctx, 26, CashScriptParser.RULE_statement); try { - this.state = 159; + this.state = 178; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 20: - case 22: + case 21: case 23: case 24: + case 25: this.enterOuterAlt(localctx, 1); { - this.state = 155; + this.state = 174; this.controlStatement(); } break; - case 18: case 19: - case 64: + case 20: case 65: case 66: - case 74: + case 67: + case 75: this.enterOuterAlt(localctx, 2); { - this.state = 156; + this.state = 175; this.nonControlStatement(); - this.state = 157; + this.state = 176; this.match(CashScriptParser.T__1); } break; @@ -759,50 +850,50 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public nonControlStatement(): NonControlStatementContext { let localctx: NonControlStatementContext = new NonControlStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 24, CashScriptParser.RULE_nonControlStatement); + this.enterRule(localctx, 28, CashScriptParser.RULE_nonControlStatement); try { - this.state = 167; + this.state = 186; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 11, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 13, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 161; + this.state = 180; this.variableDefinition(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 162; + this.state = 181; this.tupleAssignment(); } break; case 3: this.enterOuterAlt(localctx, 3); { - this.state = 163; + this.state = 182; this.assignStatement(); } break; case 4: this.enterOuterAlt(localctx, 4); { - this.state = 164; + this.state = 183; this.timeOpStatement(); } break; case 5: this.enterOuterAlt(localctx, 5); { - this.state = 165; + this.state = 184; this.requireStatement(); } break; case 6: this.enterOuterAlt(localctx, 6); { - this.state = 166; + this.state = 185; this.consoleStatement(); } break; @@ -825,24 +916,24 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public controlStatement(): ControlStatementContext { let localctx: ControlStatementContext = new ControlStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 26, CashScriptParser.RULE_controlStatement); + this.enterRule(localctx, 30, CashScriptParser.RULE_controlStatement); try { - this.state = 171; + this.state = 190; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 20: + case 21: this.enterOuterAlt(localctx, 1); { - this.state = 169; + this.state = 188; this.ifStatement(); } break; - case 22: case 23: case 24: + case 25: this.enterOuterAlt(localctx, 2); { - this.state = 170; + this.state = 189; this.loopStatement(); } break; @@ -867,32 +958,32 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public variableDefinition(): VariableDefinitionContext { let localctx: VariableDefinitionContext = new VariableDefinitionContext(this, this._ctx, this.state); - this.enterRule(localctx, 28, CashScriptParser.RULE_variableDefinition); + this.enterRule(localctx, 32, CashScriptParser.RULE_variableDefinition); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 173; + this.state = 192; this.typeName(); - this.state = 177; + this.state = 196; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===57) { + while (_la===58) { { { - this.state = 174; + this.state = 193; this.modifier(); } } - this.state = 179; + this.state = 198; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 180; + this.state = 199; this.match(CashScriptParser.Identifier); - this.state = 181; + this.state = 200; this.match(CashScriptParser.T__9); - this.state = 182; + this.state = 201; this.expression(0); } } @@ -913,23 +1004,23 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public tupleAssignment(): TupleAssignmentContext { let localctx: TupleAssignmentContext = new TupleAssignmentContext(this, this._ctx, this.state); - this.enterRule(localctx, 30, CashScriptParser.RULE_tupleAssignment); + this.enterRule(localctx, 34, CashScriptParser.RULE_tupleAssignment); try { this.enterOuterAlt(localctx, 1); { - this.state = 184; + this.state = 203; this.typeName(); - this.state = 185; + this.state = 204; this.match(CashScriptParser.Identifier); - this.state = 186; - this.match(CashScriptParser.T__15); - this.state = 187; + this.state = 205; + this.match(CashScriptParser.T__16); + this.state = 206; this.typeName(); - this.state = 188; + this.state = 207; this.match(CashScriptParser.Identifier); - this.state = 189; + this.state = 208; this.match(CashScriptParser.T__9); - this.state = 190; + this.state = 209; this.expression(0); } } @@ -950,15 +1041,15 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public assignStatement(): AssignStatementContext { let localctx: AssignStatementContext = new AssignStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 32, CashScriptParser.RULE_assignStatement); + this.enterRule(localctx, 36, CashScriptParser.RULE_assignStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 192; + this.state = 211; this.match(CashScriptParser.Identifier); - this.state = 193; + this.state = 212; this.match(CashScriptParser.T__9); - this.state = 194; + this.state = 213; this.expression(0); } } @@ -979,35 +1070,35 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public timeOpStatement(): TimeOpStatementContext { let localctx: TimeOpStatementContext = new TimeOpStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 34, CashScriptParser.RULE_timeOpStatement); + this.enterRule(localctx, 38, CashScriptParser.RULE_timeOpStatement); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 196; - this.match(CashScriptParser.T__17); - this.state = 197; - this.match(CashScriptParser.T__14); - this.state = 198; + this.state = 215; + this.match(CashScriptParser.T__18); + this.state = 216; + this.match(CashScriptParser.T__15); + this.state = 217; this.match(CashScriptParser.TxVar); - this.state = 199; + this.state = 218; this.match(CashScriptParser.T__5); - this.state = 200; + this.state = 219; this.expression(0); - this.state = 203; + this.state = 222; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 201; - this.match(CashScriptParser.T__15); - this.state = 202; + this.state = 220; + this.match(CashScriptParser.T__16); + this.state = 221; this.requireMessage(); } } - this.state = 205; - this.match(CashScriptParser.T__16); + this.state = 224; + this.match(CashScriptParser.T__17); } } catch (re) { @@ -1027,31 +1118,31 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public requireStatement(): RequireStatementContext { let localctx: RequireStatementContext = new RequireStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 36, CashScriptParser.RULE_requireStatement); + this.enterRule(localctx, 40, CashScriptParser.RULE_requireStatement); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 207; - this.match(CashScriptParser.T__17); - this.state = 208; - this.match(CashScriptParser.T__14); - this.state = 209; + this.state = 226; + this.match(CashScriptParser.T__18); + this.state = 227; + this.match(CashScriptParser.T__15); + this.state = 228; this.expression(0); - this.state = 212; + this.state = 231; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 210; - this.match(CashScriptParser.T__15); - this.state = 211; + this.state = 229; + this.match(CashScriptParser.T__16); + this.state = 230; this.requireMessage(); } } - this.state = 214; - this.match(CashScriptParser.T__16); + this.state = 233; + this.match(CashScriptParser.T__17); } } catch (re) { @@ -1071,13 +1162,13 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public consoleStatement(): ConsoleStatementContext { let localctx: ConsoleStatementContext = new ConsoleStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 38, CashScriptParser.RULE_consoleStatement); + this.enterRule(localctx, 42, CashScriptParser.RULE_consoleStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 216; - this.match(CashScriptParser.T__18); - this.state = 217; + this.state = 235; + this.match(CashScriptParser.T__19); + this.state = 236; this.consoleParameterList(); } } @@ -1098,28 +1189,28 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public ifStatement(): IfStatementContext { let localctx: IfStatementContext = new IfStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 40, CashScriptParser.RULE_ifStatement); + this.enterRule(localctx, 44, CashScriptParser.RULE_ifStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 219; - this.match(CashScriptParser.T__19); - this.state = 220; - this.match(CashScriptParser.T__14); - this.state = 221; + this.state = 238; + this.match(CashScriptParser.T__20); + this.state = 239; + this.match(CashScriptParser.T__15); + this.state = 240; this.expression(0); - this.state = 222; - this.match(CashScriptParser.T__16); - this.state = 223; + this.state = 241; + this.match(CashScriptParser.T__17); + this.state = 242; localctx._ifBlock = this.block(); - this.state = 226; + this.state = 245; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 16, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 18, this._ctx) ) { case 1: { - this.state = 224; - this.match(CashScriptParser.T__20); - this.state = 225; + this.state = 243; + this.match(CashScriptParser.T__21); + this.state = 244; localctx._elseBlock = this.block(); } break; @@ -1143,29 +1234,29 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public loopStatement(): LoopStatementContext { let localctx: LoopStatementContext = new LoopStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 42, CashScriptParser.RULE_loopStatement); + this.enterRule(localctx, 46, CashScriptParser.RULE_loopStatement); try { - this.state = 231; + this.state = 250; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 22: + case 23: this.enterOuterAlt(localctx, 1); { - this.state = 228; + this.state = 247; this.doWhileStatement(); } break; - case 23: + case 24: this.enterOuterAlt(localctx, 2); { - this.state = 229; + this.state = 248; this.whileStatement(); } break; - case 24: + case 25: this.enterOuterAlt(localctx, 3); { - this.state = 230; + this.state = 249; this.forStatement(); } break; @@ -1190,23 +1281,23 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public doWhileStatement(): DoWhileStatementContext { let localctx: DoWhileStatementContext = new DoWhileStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 44, CashScriptParser.RULE_doWhileStatement); + this.enterRule(localctx, 48, CashScriptParser.RULE_doWhileStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 233; - this.match(CashScriptParser.T__21); - this.state = 234; - this.block(); - this.state = 235; + this.state = 252; this.match(CashScriptParser.T__22); - this.state = 236; - this.match(CashScriptParser.T__14); - this.state = 237; + this.state = 253; + this.block(); + this.state = 254; + this.match(CashScriptParser.T__23); + this.state = 255; + this.match(CashScriptParser.T__15); + this.state = 256; this.expression(0); - this.state = 238; - this.match(CashScriptParser.T__16); - this.state = 239; + this.state = 257; + this.match(CashScriptParser.T__17); + this.state = 258; this.match(CashScriptParser.T__1); } } @@ -1227,19 +1318,19 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public whileStatement(): WhileStatementContext { let localctx: WhileStatementContext = new WhileStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 46, CashScriptParser.RULE_whileStatement); + this.enterRule(localctx, 50, CashScriptParser.RULE_whileStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 241; - this.match(CashScriptParser.T__22); - this.state = 242; - this.match(CashScriptParser.T__14); - this.state = 243; + this.state = 260; + this.match(CashScriptParser.T__23); + this.state = 261; + this.match(CashScriptParser.T__15); + this.state = 262; this.expression(0); - this.state = 244; - this.match(CashScriptParser.T__16); - this.state = 245; + this.state = 263; + this.match(CashScriptParser.T__17); + this.state = 264; this.block(); } } @@ -1260,27 +1351,27 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public forStatement(): ForStatementContext { let localctx: ForStatementContext = new ForStatementContext(this, this._ctx, this.state); - this.enterRule(localctx, 48, CashScriptParser.RULE_forStatement); + this.enterRule(localctx, 52, CashScriptParser.RULE_forStatement); try { this.enterOuterAlt(localctx, 1); { - this.state = 247; - this.match(CashScriptParser.T__23); - this.state = 248; - this.match(CashScriptParser.T__14); - this.state = 249; + this.state = 266; + this.match(CashScriptParser.T__24); + this.state = 267; + this.match(CashScriptParser.T__15); + this.state = 268; this.forInit(); - this.state = 250; + this.state = 269; this.match(CashScriptParser.T__1); - this.state = 251; + this.state = 270; this.expression(0); - this.state = 252; + this.state = 271; this.match(CashScriptParser.T__1); - this.state = 253; + this.state = 272; this.assignStatement(); - this.state = 254; - this.match(CashScriptParser.T__16); - this.state = 255; + this.state = 273; + this.match(CashScriptParser.T__17); + this.state = 274; this.block(); } } @@ -1301,24 +1392,24 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public forInit(): ForInitContext { let localctx: ForInitContext = new ForInitContext(this, this._ctx, this.state); - this.enterRule(localctx, 50, CashScriptParser.RULE_forInit); + this.enterRule(localctx, 54, CashScriptParser.RULE_forInit); try { - this.state = 259; + this.state = 278; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 64: case 65: case 66: + case 67: this.enterOuterAlt(localctx, 1); { - this.state = 257; + this.state = 276; this.variableDefinition(); } break; - case 74: + case 75: this.enterOuterAlt(localctx, 2); { - this.state = 258; + this.state = 277; this.assignStatement(); } break; @@ -1343,11 +1434,11 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public requireMessage(): RequireMessageContext { let localctx: RequireMessageContext = new RequireMessageContext(this, this._ctx, this.state); - this.enterRule(localctx, 52, CashScriptParser.RULE_requireMessage); + this.enterRule(localctx, 56, CashScriptParser.RULE_requireMessage); try { this.enterOuterAlt(localctx, 1); { - this.state = 261; + this.state = 280; this.match(CashScriptParser.StringLiteral); } } @@ -1368,26 +1459,26 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public consoleParameter(): ConsoleParameterContext { let localctx: ConsoleParameterContext = new ConsoleParameterContext(this, this._ctx, this.state); - this.enterRule(localctx, 54, CashScriptParser.RULE_consoleParameter); + this.enterRule(localctx, 58, CashScriptParser.RULE_consoleParameter); try { - this.state = 265; + this.state = 284; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 74: + case 75: this.enterOuterAlt(localctx, 1); { - this.state = 263; + this.state = 282; this.match(CashScriptParser.Identifier); } break; - case 59: - case 61: - case 68: + case 60: + case 62: case 69: case 70: + case 71: this.enterOuterAlt(localctx, 2); { - this.state = 264; + this.state = 283; this.literal(); } break; @@ -1412,54 +1503,54 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public consoleParameterList(): ConsoleParameterListContext { let localctx: ConsoleParameterListContext = new ConsoleParameterListContext(this, this._ctx, this.state); - this.enterRule(localctx, 56, CashScriptParser.RULE_consoleParameterList); + this.enterRule(localctx, 60, CashScriptParser.RULE_consoleParameterList); let _la: number; try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 267; - this.match(CashScriptParser.T__14); - this.state = 279; + this.state = 286; + this.match(CashScriptParser.T__15); + this.state = 298; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 59)) & ~0x1F) === 0 && ((1 << (_la - 59)) & 36357) !== 0)) { + if (((((_la - 60)) & ~0x1F) === 0 && ((1 << (_la - 60)) & 36357) !== 0)) { { - this.state = 268; + this.state = 287; this.consoleParameter(); - this.state = 273; + this.state = 292; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 269; - this.match(CashScriptParser.T__15); - this.state = 270; + this.state = 288; + this.match(CashScriptParser.T__16); + this.state = 289; this.consoleParameter(); } } } - this.state = 275; + this.state = 294; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); } - this.state = 277; + this.state = 296; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 276; - this.match(CashScriptParser.T__15); + this.state = 295; + this.match(CashScriptParser.T__16); } } } } - this.state = 281; - this.match(CashScriptParser.T__16); + this.state = 300; + this.match(CashScriptParser.T__17); } } catch (re) { @@ -1479,13 +1570,13 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public functionCall(): FunctionCallContext { let localctx: FunctionCallContext = new FunctionCallContext(this, this._ctx, this.state); - this.enterRule(localctx, 58, CashScriptParser.RULE_functionCall); + this.enterRule(localctx, 62, CashScriptParser.RULE_functionCall); try { this.enterOuterAlt(localctx, 1); { - this.state = 283; + this.state = 302; this.match(CashScriptParser.Identifier); - this.state = 284; + this.state = 303; this.expressionList(); } } @@ -1506,54 +1597,54 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public expressionList(): ExpressionListContext { let localctx: ExpressionListContext = new ExpressionListContext(this, this._ctx, this.state); - this.enterRule(localctx, 60, CashScriptParser.RULE_expressionList); + this.enterRule(localctx, 64, CashScriptParser.RULE_expressionList); let _la: number; try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 286; - this.match(CashScriptParser.T__14); - this.state = 298; + this.state = 305; + this.match(CashScriptParser.T__15); + this.state = 317; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & 548406273) !== 0) || ((((_la - 43)) & ~0x1F) === 0 && ((1 << (_la - 43)) & 3999596547) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & 1096812545) !== 0) || ((((_la - 44)) & ~0x1F) === 0 && ((1 << (_la - 44)) & 3999596547) !== 0)) { { - this.state = 287; + this.state = 306; this.expression(0); - this.state = 292; + this.state = 311; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 23, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 25, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 288; - this.match(CashScriptParser.T__15); - this.state = 289; + this.state = 307; + this.match(CashScriptParser.T__16); + this.state = 308; this.expression(0); } } } - this.state = 294; + this.state = 313; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 23, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 25, this._ctx); } - this.state = 296; + this.state = 315; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 295; - this.match(CashScriptParser.T__15); + this.state = 314; + this.match(CashScriptParser.T__16); } } } } - this.state = 300; - this.match(CashScriptParser.T__16); + this.state = 319; + this.match(CashScriptParser.T__17); } } catch (re) { @@ -1583,28 +1674,28 @@ export default class CashScriptParser extends Parser { let _parentState: number = this.state; let localctx: ExpressionContext = new ExpressionContext(this, this._ctx, _parentState); let _prevctx: ExpressionContext = localctx; - let _startState: number = 62; - this.enterRecursionRule(localctx, 62, CashScriptParser.RULE_expression, _p); + let _startState: number = 66; + this.enterRecursionRule(localctx, 66, CashScriptParser.RULE_expression, _p); let _la: number; try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 351; + this.state = 370; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 32, this._ctx) ) { case 1: { localctx = new ParenthesisedContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 303; - this.match(CashScriptParser.T__14); - this.state = 304; + this.state = 322; + this.match(CashScriptParser.T__15); + this.state = 323; this.expression(0); - this.state = 305; - this.match(CashScriptParser.T__16); + this.state = 324; + this.match(CashScriptParser.T__17); } break; case 2: @@ -1612,24 +1703,24 @@ export default class CashScriptParser extends Parser { localctx = new CastContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 307; + this.state = 326; this.typeCast(); - this.state = 308; - this.match(CashScriptParser.T__14); - this.state = 309; + this.state = 327; + this.match(CashScriptParser.T__15); + this.state = 328; (localctx as CastContext)._castable = this.expression(0); - this.state = 311; + this.state = 330; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 310; - this.match(CashScriptParser.T__15); + this.state = 329; + this.match(CashScriptParser.T__16); } } - this.state = 313; - this.match(CashScriptParser.T__16); + this.state = 332; + this.match(CashScriptParser.T__17); } break; case 3: @@ -1637,7 +1728,7 @@ export default class CashScriptParser extends Parser { localctx = new FunctionCallExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 315; + this.state = 334; this.functionCall(); } break; @@ -1646,11 +1737,11 @@ export default class CashScriptParser extends Parser { localctx = new InstantiationContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 316; - this.match(CashScriptParser.T__24); - this.state = 317; + this.state = 335; + this.match(CashScriptParser.T__25); + this.state = 336; this.match(CashScriptParser.Identifier); - this.state = 318; + this.state = 337; this.expressionList(); } break; @@ -1659,18 +1750,18 @@ export default class CashScriptParser extends Parser { localctx = new UnaryIntrospectionOpContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 319; - (localctx as UnaryIntrospectionOpContext)._scope = this.match(CashScriptParser.T__27); - this.state = 320; - this.match(CashScriptParser.T__25); - this.state = 321; - this.expression(0); - this.state = 322; + this.state = 338; + (localctx as UnaryIntrospectionOpContext)._scope = this.match(CashScriptParser.T__28); + this.state = 339; this.match(CashScriptParser.T__26); - this.state = 323; + this.state = 340; + this.expression(0); + this.state = 341; + this.match(CashScriptParser.T__27); + this.state = 342; (localctx as UnaryIntrospectionOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 29)) & ~0x1F) === 0 && ((1 << (_la - 29)) & 31) !== 0))) { + if(!(((((_la - 30)) & ~0x1F) === 0 && ((1 << (_la - 30)) & 31) !== 0))) { (localctx as UnaryIntrospectionOpContext)._op = this._errHandler.recoverInline(this); } else { @@ -1684,18 +1775,18 @@ export default class CashScriptParser extends Parser { localctx = new UnaryIntrospectionOpContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 325; - (localctx as UnaryIntrospectionOpContext)._scope = this.match(CashScriptParser.T__33); - this.state = 326; - this.match(CashScriptParser.T__25); - this.state = 327; - this.expression(0); - this.state = 328; + this.state = 344; + (localctx as UnaryIntrospectionOpContext)._scope = this.match(CashScriptParser.T__34); + this.state = 345; this.match(CashScriptParser.T__26); - this.state = 329; + this.state = 346; + this.expression(0); + this.state = 347; + this.match(CashScriptParser.T__27); + this.state = 348; (localctx as UnaryIntrospectionOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 29)) & ~0x1F) === 0 && ((1 << (_la - 29)) & 991) !== 0))) { + if(!(((((_la - 30)) & ~0x1F) === 0 && ((1 << (_la - 30)) & 991) !== 0))) { (localctx as UnaryIntrospectionOpContext)._op = this._errHandler.recoverInline(this); } else { @@ -1709,17 +1800,17 @@ export default class CashScriptParser extends Parser { localctx = new UnaryOpContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 331; + this.state = 350; (localctx as UnaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===5 || _la===43 || _la===44)) { + if(!(_la===5 || _la===44 || _la===45)) { (localctx as UnaryOpContext)._op = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 332; + this.state = 351; this.expression(15); } break; @@ -1728,48 +1819,48 @@ export default class CashScriptParser extends Parser { localctx = new ArrayContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 333; - this.match(CashScriptParser.T__25); - this.state = 345; + this.state = 352; + this.match(CashScriptParser.T__26); + this.state = 364; this._errHandler.sync(this); _la = this._input.LA(1); - if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & 548406273) !== 0) || ((((_la - 43)) & ~0x1F) === 0 && ((1 << (_la - 43)) & 3999596547) !== 0)) { + if (((((_la - 5)) & ~0x1F) === 0 && ((1 << (_la - 5)) & 1096812545) !== 0) || ((((_la - 44)) & ~0x1F) === 0 && ((1 << (_la - 44)) & 3999596547) !== 0)) { { - this.state = 334; + this.state = 353; this.expression(0); - this.state = 339; + this.state = 358; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 29, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 335; - this.match(CashScriptParser.T__15); - this.state = 336; + this.state = 354; + this.match(CashScriptParser.T__16); + this.state = 355; this.expression(0); } } } - this.state = 341; + this.state = 360; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 29, this._ctx); } - this.state = 343; + this.state = 362; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===16) { + if (_la===17) { { - this.state = 342; - this.match(CashScriptParser.T__15); + this.state = 361; + this.match(CashScriptParser.T__16); } } } } - this.state = 347; - this.match(CashScriptParser.T__26); + this.state = 366; + this.match(CashScriptParser.T__27); } break; case 9: @@ -1777,7 +1868,7 @@ export default class CashScriptParser extends Parser { localctx = new NullaryOpContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 348; + this.state = 367; this.match(CashScriptParser.NullaryOp); } break; @@ -1786,7 +1877,7 @@ export default class CashScriptParser extends Parser { localctx = new IdentifierContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 349; + this.state = 368; this.match(CashScriptParser.Identifier); } break; @@ -1795,15 +1886,15 @@ export default class CashScriptParser extends Parser { localctx = new LiteralExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 350; + this.state = 369; this.literal(); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 405; + this.state = 424; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { if (this._parseListeners != null) { @@ -1811,29 +1902,29 @@ export default class CashScriptParser extends Parser { } _prevctx = localctx; { - this.state = 403; + this.state = 422; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 33, this._ctx) ) { case 1: { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 353; + this.state = 372; if (!(this.precpred(this._ctx, 14))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 14)"); } - this.state = 354; + this.state = 373; (localctx as BinaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 45)) & ~0x1F) === 0 && ((1 << (_la - 45)) & 7) !== 0))) { + if(!(((((_la - 46)) & ~0x1F) === 0 && ((1 << (_la - 46)) & 7) !== 0))) { (localctx as BinaryOpContext)._op = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 355; + this.state = 374; (localctx as BinaryOpContext)._right = this.expression(15); } break; @@ -1842,21 +1933,21 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 356; + this.state = 375; if (!(this.precpred(this._ctx, 13))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 13)"); } - this.state = 357; + this.state = 376; (localctx as BinaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===44 || _la===48)) { + if(!(_la===45 || _la===49)) { (localctx as BinaryOpContext)._op = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 358; + this.state = 377; (localctx as BinaryOpContext)._right = this.expression(14); } break; @@ -1865,21 +1956,21 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 359; + this.state = 378; if (!(this.precpred(this._ctx, 12))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 12)"); } - this.state = 360; + this.state = 379; (localctx as BinaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===49 || _la===50)) { + if(!(_la===50 || _la===51)) { (localctx as BinaryOpContext)._op = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 361; + this.state = 380; (localctx as BinaryOpContext)._right = this.expression(13); } break; @@ -1888,11 +1979,11 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 362; + this.state = 381; if (!(this.precpred(this._ctx, 11))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 11)"); } - this.state = 363; + this.state = 382; (localctx as BinaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 960) !== 0))) { @@ -1902,7 +1993,7 @@ export default class CashScriptParser extends Parser { this._errHandler.reportMatch(this); this.consume(); } - this.state = 364; + this.state = 383; (localctx as BinaryOpContext)._right = this.expression(12); } break; @@ -1911,21 +2002,21 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 365; + this.state = 384; if (!(this.precpred(this._ctx, 10))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 10)"); } - this.state = 366; + this.state = 385; (localctx as BinaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===51 || _la===52)) { + if(!(_la===52 || _la===53)) { (localctx as BinaryOpContext)._op = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 367; + this.state = 386; (localctx as BinaryOpContext)._right = this.expression(11); } break; @@ -1934,13 +2025,13 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 368; + this.state = 387; if (!(this.precpred(this._ctx, 9))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 9)"); } - this.state = 369; - (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__52); - this.state = 370; + this.state = 388; + (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__53); + this.state = 389; (localctx as BinaryOpContext)._right = this.expression(10); } break; @@ -1949,13 +2040,13 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 371; + this.state = 390; if (!(this.precpred(this._ctx, 8))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 8)"); } - this.state = 372; + this.state = 391; (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__3); - this.state = 373; + this.state = 392; (localctx as BinaryOpContext)._right = this.expression(9); } break; @@ -1964,13 +2055,13 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 374; + this.state = 393; if (!(this.precpred(this._ctx, 7))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 7)"); } - this.state = 375; - (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__53); - this.state = 376; + this.state = 394; + (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__54); + this.state = 395; (localctx as BinaryOpContext)._right = this.expression(8); } break; @@ -1979,13 +2070,13 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 377; + this.state = 396; if (!(this.precpred(this._ctx, 6))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 6)"); } - this.state = 378; - (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__54); - this.state = 379; + this.state = 397; + (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__55); + this.state = 398; (localctx as BinaryOpContext)._right = this.expression(7); } break; @@ -1994,13 +2085,13 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 380; + this.state = 399; if (!(this.precpred(this._ctx, 5))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 5)"); } - this.state = 381; - (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__55); - this.state = 382; + this.state = 400; + (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__56); + this.state = 401; (localctx as BinaryOpContext)._right = this.expression(6); } break; @@ -2008,30 +2099,30 @@ export default class CashScriptParser extends Parser { { localctx = new TupleIndexOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 383; + this.state = 402; if (!(this.precpred(this._ctx, 21))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 21)"); } - this.state = 384; - this.match(CashScriptParser.T__25); - this.state = 385; - (localctx as TupleIndexOpContext)._index = this.match(CashScriptParser.NumberLiteral); - this.state = 386; + this.state = 403; this.match(CashScriptParser.T__26); + this.state = 404; + (localctx as TupleIndexOpContext)._index = this.match(CashScriptParser.NumberLiteral); + this.state = 405; + this.match(CashScriptParser.T__27); } break; case 12: { localctx = new UnaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 387; + this.state = 406; if (!(this.precpred(this._ctx, 18))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 18)"); } - this.state = 388; + this.state = 407; (localctx as UnaryOpContext)._op = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===39 || _la===40)) { + if(!(_la===40 || _la===41)) { (localctx as UnaryOpContext)._op = this._errHandler.recoverInline(this); } else { @@ -2045,18 +2136,18 @@ export default class CashScriptParser extends Parser { localctx = new BinaryOpContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as BinaryOpContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 389; + this.state = 408; if (!(this.precpred(this._ctx, 17))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 17)"); } - this.state = 390; - (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__40); - this.state = 391; - this.match(CashScriptParser.T__14); - this.state = 392; + this.state = 409; + (localctx as BinaryOpContext)._op = this.match(CashScriptParser.T__41); + this.state = 410; + this.match(CashScriptParser.T__15); + this.state = 411; (localctx as BinaryOpContext)._right = this.expression(0); - this.state = 393; - this.match(CashScriptParser.T__16); + this.state = 412; + this.match(CashScriptParser.T__17); } break; case 14: @@ -2064,30 +2155,30 @@ export default class CashScriptParser extends Parser { localctx = new SliceContext(this, new ExpressionContext(this, _parentctx, _parentState)); (localctx as SliceContext)._element = _prevctx; this.pushNewRecursionContext(localctx, _startState, CashScriptParser.RULE_expression); - this.state = 395; + this.state = 414; if (!(this.precpred(this._ctx, 16))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 16)"); } - this.state = 396; - this.match(CashScriptParser.T__41); - this.state = 397; - this.match(CashScriptParser.T__14); - this.state = 398; - (localctx as SliceContext)._start = this.expression(0); - this.state = 399; + this.state = 415; + this.match(CashScriptParser.T__42); + this.state = 416; this.match(CashScriptParser.T__15); - this.state = 400; - (localctx as SliceContext)._end = this.expression(0); - this.state = 401; + this.state = 417; + (localctx as SliceContext)._start = this.expression(0); + this.state = 418; this.match(CashScriptParser.T__16); + this.state = 419; + (localctx as SliceContext)._end = this.expression(0); + this.state = 420; + this.match(CashScriptParser.T__17); } break; } } } - this.state = 407; + this.state = 426; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); } } } @@ -2108,12 +2199,12 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public modifier(): ModifierContext { let localctx: ModifierContext = new ModifierContext(this, this._ctx, this.state); - this.enterRule(localctx, 64, CashScriptParser.RULE_modifier); + this.enterRule(localctx, 68, CashScriptParser.RULE_modifier); try { this.enterOuterAlt(localctx, 1); { - this.state = 408; - this.match(CashScriptParser.T__56); + this.state = 427; + this.match(CashScriptParser.T__57); } } catch (re) { @@ -2133,43 +2224,43 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public literal(): LiteralContext { let localctx: LiteralContext = new LiteralContext(this, this._ctx, this.state); - this.enterRule(localctx, 66, CashScriptParser.RULE_literal); + this.enterRule(localctx, 70, CashScriptParser.RULE_literal); try { - this.state = 415; + this.state = 434; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 59: + case 60: this.enterOuterAlt(localctx, 1); { - this.state = 410; + this.state = 429; this.match(CashScriptParser.BooleanLiteral); } break; - case 61: + case 62: this.enterOuterAlt(localctx, 2); { - this.state = 411; + this.state = 430; this.numberLiteral(); } break; - case 68: + case 69: this.enterOuterAlt(localctx, 3); { - this.state = 412; + this.state = 431; this.match(CashScriptParser.StringLiteral); } break; - case 69: + case 70: this.enterOuterAlt(localctx, 4); { - this.state = 413; + this.state = 432; this.match(CashScriptParser.DateLiteral); } break; - case 70: + case 71: this.enterOuterAlt(localctx, 5); { - this.state = 414; + this.state = 433; this.match(CashScriptParser.HexLiteral); } break; @@ -2194,18 +2285,18 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public numberLiteral(): NumberLiteralContext { let localctx: NumberLiteralContext = new NumberLiteralContext(this, this._ctx, this.state); - this.enterRule(localctx, 68, CashScriptParser.RULE_numberLiteral); + this.enterRule(localctx, 72, CashScriptParser.RULE_numberLiteral); try { this.enterOuterAlt(localctx, 1); { - this.state = 417; + this.state = 436; this.match(CashScriptParser.NumberLiteral); - this.state = 419; + this.state = 438; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 34, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 36, this._ctx) ) { case 1: { - this.state = 418; + this.state = 437; this.match(CashScriptParser.NumberUnit); } break; @@ -2229,14 +2320,14 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public typeName(): TypeNameContext { let localctx: TypeNameContext = new TypeNameContext(this, this._ctx, this.state); - this.enterRule(localctx, 70, CashScriptParser.RULE_typeName); + this.enterRule(localctx, 74, CashScriptParser.RULE_typeName); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 421; + this.state = 440; _la = this._input.LA(1); - if(!(((((_la - 64)) & ~0x1F) === 0 && ((1 << (_la - 64)) & 7) !== 0))) { + if(!(((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 7) !== 0))) { this._errHandler.recoverInline(this); } else { @@ -2262,14 +2353,14 @@ export default class CashScriptParser extends Parser { // @RuleVersion(0) public typeCast(): TypeCastContext { let localctx: TypeCastContext = new TypeCastContext(this, this._ctx, this.state); - this.enterRule(localctx, 72, CashScriptParser.RULE_typeCast); + this.enterRule(localctx, 76, CashScriptParser.RULE_typeCast); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 423; + this.state = 442; _la = this._input.LA(1); - if(!(((((_la - 64)) & ~0x1F) === 0 && ((1 << (_la - 64)) & 259) !== 0))) { + if(!(((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 259) !== 0))) { this._errHandler.recoverInline(this); } else { @@ -2295,7 +2386,7 @@ export default class CashScriptParser extends Parser { public sempred(localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { switch (ruleIndex) { - case 31: + case 33: return this.expression_sempred(localctx as ExpressionContext, predIndex); } return true; @@ -2334,146 +2425,153 @@ export default class CashScriptParser extends Parser { return true; } - public static readonly _serializedATN: number[] = [4,1,77,426,2,0,7,0,2, + public static readonly _serializedATN: number[] = [4,1,78,445,2,0,7,0,2, 1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2, 10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17, 7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7, 24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31, - 2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,5,0,76,8,0,10,0,12, - 0,79,9,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,3,1,3,3,3,93,8,3,1,4, - 3,4,96,8,4,1,4,1,4,1,5,1,5,1,6,1,6,1,6,1,6,1,6,5,6,107,8,6,10,6,12,6,110, - 9,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,5,7,119,8,7,10,7,12,7,122,9,7,1,7,1,7,1, - 8,1,8,1,8,1,8,5,8,130,8,8,10,8,12,8,133,9,8,1,8,3,8,136,8,8,3,8,138,8,8, - 1,8,1,8,1,9,1,9,1,9,1,10,1,10,5,10,147,8,10,10,10,12,10,150,9,10,1,10,1, - 10,3,10,154,8,10,1,11,1,11,1,11,1,11,3,11,160,8,11,1,12,1,12,1,12,1,12, - 1,12,1,12,3,12,168,8,12,1,13,1,13,3,13,172,8,13,1,14,1,14,5,14,176,8,14, - 10,14,12,14,179,9,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15, - 1,15,1,15,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,204, - 8,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,3,18,213,8,18,1,18,1,18,1,19,1, - 19,1,19,1,20,1,20,1,20,1,20,1,20,1,20,1,20,3,20,227,8,20,1,21,1,21,1,21, - 3,21,232,8,21,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1, - 23,1,23,1,23,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,24,1,25,1,25, - 3,25,260,8,25,1,26,1,26,1,27,1,27,3,27,266,8,27,1,28,1,28,1,28,1,28,5,28, - 272,8,28,10,28,12,28,275,9,28,1,28,3,28,278,8,28,3,28,280,8,28,1,28,1,28, - 1,29,1,29,1,29,1,30,1,30,1,30,1,30,5,30,291,8,30,10,30,12,30,294,9,30,1, - 30,3,30,297,8,30,3,30,299,8,30,1,30,1,30,1,31,1,31,1,31,1,31,1,31,1,31, - 1,31,1,31,1,31,3,31,312,8,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1, - 31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31, - 1,31,5,31,338,8,31,10,31,12,31,341,9,31,1,31,3,31,344,8,31,3,31,346,8,31, - 1,31,1,31,1,31,1,31,3,31,352,8,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1, - 31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31, - 1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1, - 31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,1,31,5,31, - 404,8,31,10,31,12,31,407,9,31,1,32,1,32,1,33,1,33,1,33,1,33,1,33,3,33,416, - 8,33,1,34,1,34,3,34,420,8,34,1,35,1,35,1,36,1,36,1,36,0,1,62,37,0,2,4,6, - 8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54, - 56,58,60,62,64,66,68,70,72,0,12,1,0,4,10,1,0,29,33,2,0,29,33,35,38,2,0, - 5,5,43,44,1,0,45,47,2,0,44,44,48,48,1,0,49,50,1,0,6,9,1,0,51,52,1,0,39, - 40,1,0,64,66,2,0,64,65,72,72,452,0,77,1,0,0,0,2,83,1,0,0,0,4,88,1,0,0,0, - 6,90,1,0,0,0,8,95,1,0,0,0,10,99,1,0,0,0,12,101,1,0,0,0,14,113,1,0,0,0,16, - 125,1,0,0,0,18,141,1,0,0,0,20,153,1,0,0,0,22,159,1,0,0,0,24,167,1,0,0,0, - 26,171,1,0,0,0,28,173,1,0,0,0,30,184,1,0,0,0,32,192,1,0,0,0,34,196,1,0, - 0,0,36,207,1,0,0,0,38,216,1,0,0,0,40,219,1,0,0,0,42,231,1,0,0,0,44,233, - 1,0,0,0,46,241,1,0,0,0,48,247,1,0,0,0,50,259,1,0,0,0,52,261,1,0,0,0,54, - 265,1,0,0,0,56,267,1,0,0,0,58,283,1,0,0,0,60,286,1,0,0,0,62,351,1,0,0,0, - 64,408,1,0,0,0,66,415,1,0,0,0,68,417,1,0,0,0,70,421,1,0,0,0,72,423,1,0, - 0,0,74,76,3,2,1,0,75,74,1,0,0,0,76,79,1,0,0,0,77,75,1,0,0,0,77,78,1,0,0, - 0,78,80,1,0,0,0,79,77,1,0,0,0,80,81,3,12,6,0,81,82,5,0,0,1,82,1,1,0,0,0, - 83,84,5,1,0,0,84,85,3,4,2,0,85,86,3,6,3,0,86,87,5,2,0,0,87,3,1,0,0,0,88, - 89,5,3,0,0,89,5,1,0,0,0,90,92,3,8,4,0,91,93,3,8,4,0,92,91,1,0,0,0,92,93, - 1,0,0,0,93,7,1,0,0,0,94,96,3,10,5,0,95,94,1,0,0,0,95,96,1,0,0,0,96,97,1, - 0,0,0,97,98,5,58,0,0,98,9,1,0,0,0,99,100,7,0,0,0,100,11,1,0,0,0,101,102, - 5,11,0,0,102,103,5,74,0,0,103,104,3,16,8,0,104,108,5,12,0,0,105,107,3,14, - 7,0,106,105,1,0,0,0,107,110,1,0,0,0,108,106,1,0,0,0,108,109,1,0,0,0,109, - 111,1,0,0,0,110,108,1,0,0,0,111,112,5,13,0,0,112,13,1,0,0,0,113,114,5,14, - 0,0,114,115,5,74,0,0,115,116,3,16,8,0,116,120,5,12,0,0,117,119,3,22,11, - 0,118,117,1,0,0,0,119,122,1,0,0,0,120,118,1,0,0,0,120,121,1,0,0,0,121,123, - 1,0,0,0,122,120,1,0,0,0,123,124,5,13,0,0,124,15,1,0,0,0,125,137,5,15,0, - 0,126,131,3,18,9,0,127,128,5,16,0,0,128,130,3,18,9,0,129,127,1,0,0,0,130, - 133,1,0,0,0,131,129,1,0,0,0,131,132,1,0,0,0,132,135,1,0,0,0,133,131,1,0, - 0,0,134,136,5,16,0,0,135,134,1,0,0,0,135,136,1,0,0,0,136,138,1,0,0,0,137, - 126,1,0,0,0,137,138,1,0,0,0,138,139,1,0,0,0,139,140,5,17,0,0,140,17,1,0, - 0,0,141,142,3,70,35,0,142,143,5,74,0,0,143,19,1,0,0,0,144,148,5,12,0,0, - 145,147,3,22,11,0,146,145,1,0,0,0,147,150,1,0,0,0,148,146,1,0,0,0,148,149, - 1,0,0,0,149,151,1,0,0,0,150,148,1,0,0,0,151,154,5,13,0,0,152,154,3,22,11, - 0,153,144,1,0,0,0,153,152,1,0,0,0,154,21,1,0,0,0,155,160,3,26,13,0,156, - 157,3,24,12,0,157,158,5,2,0,0,158,160,1,0,0,0,159,155,1,0,0,0,159,156,1, - 0,0,0,160,23,1,0,0,0,161,168,3,28,14,0,162,168,3,30,15,0,163,168,3,32,16, - 0,164,168,3,34,17,0,165,168,3,36,18,0,166,168,3,38,19,0,167,161,1,0,0,0, - 167,162,1,0,0,0,167,163,1,0,0,0,167,164,1,0,0,0,167,165,1,0,0,0,167,166, - 1,0,0,0,168,25,1,0,0,0,169,172,3,40,20,0,170,172,3,42,21,0,171,169,1,0, - 0,0,171,170,1,0,0,0,172,27,1,0,0,0,173,177,3,70,35,0,174,176,3,64,32,0, - 175,174,1,0,0,0,176,179,1,0,0,0,177,175,1,0,0,0,177,178,1,0,0,0,178,180, - 1,0,0,0,179,177,1,0,0,0,180,181,5,74,0,0,181,182,5,10,0,0,182,183,3,62, - 31,0,183,29,1,0,0,0,184,185,3,70,35,0,185,186,5,74,0,0,186,187,5,16,0,0, - 187,188,3,70,35,0,188,189,5,74,0,0,189,190,5,10,0,0,190,191,3,62,31,0,191, - 31,1,0,0,0,192,193,5,74,0,0,193,194,5,10,0,0,194,195,3,62,31,0,195,33,1, - 0,0,0,196,197,5,18,0,0,197,198,5,15,0,0,198,199,5,71,0,0,199,200,5,6,0, - 0,200,203,3,62,31,0,201,202,5,16,0,0,202,204,3,52,26,0,203,201,1,0,0,0, - 203,204,1,0,0,0,204,205,1,0,0,0,205,206,5,17,0,0,206,35,1,0,0,0,207,208, - 5,18,0,0,208,209,5,15,0,0,209,212,3,62,31,0,210,211,5,16,0,0,211,213,3, - 52,26,0,212,210,1,0,0,0,212,213,1,0,0,0,213,214,1,0,0,0,214,215,5,17,0, - 0,215,37,1,0,0,0,216,217,5,19,0,0,217,218,3,56,28,0,218,39,1,0,0,0,219, - 220,5,20,0,0,220,221,5,15,0,0,221,222,3,62,31,0,222,223,5,17,0,0,223,226, - 3,20,10,0,224,225,5,21,0,0,225,227,3,20,10,0,226,224,1,0,0,0,226,227,1, - 0,0,0,227,41,1,0,0,0,228,232,3,44,22,0,229,232,3,46,23,0,230,232,3,48,24, - 0,231,228,1,0,0,0,231,229,1,0,0,0,231,230,1,0,0,0,232,43,1,0,0,0,233,234, - 5,22,0,0,234,235,3,20,10,0,235,236,5,23,0,0,236,237,5,15,0,0,237,238,3, - 62,31,0,238,239,5,17,0,0,239,240,5,2,0,0,240,45,1,0,0,0,241,242,5,23,0, - 0,242,243,5,15,0,0,243,244,3,62,31,0,244,245,5,17,0,0,245,246,3,20,10,0, - 246,47,1,0,0,0,247,248,5,24,0,0,248,249,5,15,0,0,249,250,3,50,25,0,250, - 251,5,2,0,0,251,252,3,62,31,0,252,253,5,2,0,0,253,254,3,32,16,0,254,255, - 5,17,0,0,255,256,3,20,10,0,256,49,1,0,0,0,257,260,3,28,14,0,258,260,3,32, - 16,0,259,257,1,0,0,0,259,258,1,0,0,0,260,51,1,0,0,0,261,262,5,68,0,0,262, - 53,1,0,0,0,263,266,5,74,0,0,264,266,3,66,33,0,265,263,1,0,0,0,265,264,1, - 0,0,0,266,55,1,0,0,0,267,279,5,15,0,0,268,273,3,54,27,0,269,270,5,16,0, - 0,270,272,3,54,27,0,271,269,1,0,0,0,272,275,1,0,0,0,273,271,1,0,0,0,273, - 274,1,0,0,0,274,277,1,0,0,0,275,273,1,0,0,0,276,278,5,16,0,0,277,276,1, - 0,0,0,277,278,1,0,0,0,278,280,1,0,0,0,279,268,1,0,0,0,279,280,1,0,0,0,280, - 281,1,0,0,0,281,282,5,17,0,0,282,57,1,0,0,0,283,284,5,74,0,0,284,285,3, - 60,30,0,285,59,1,0,0,0,286,298,5,15,0,0,287,292,3,62,31,0,288,289,5,16, - 0,0,289,291,3,62,31,0,290,288,1,0,0,0,291,294,1,0,0,0,292,290,1,0,0,0,292, - 293,1,0,0,0,293,296,1,0,0,0,294,292,1,0,0,0,295,297,5,16,0,0,296,295,1, + 2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,1, + 0,5,0,80,8,0,10,0,12,0,83,9,0,1,0,1,0,1,0,1,1,1,1,3,1,90,8,1,1,2,1,2,1, + 2,1,2,1,2,1,3,1,3,1,4,1,4,3,4,101,8,4,1,5,3,5,104,8,5,1,5,1,5,1,6,1,6,1, + 7,1,7,1,7,1,7,1,7,5,7,115,8,7,10,7,12,7,118,9,7,1,7,1,7,1,8,1,8,1,8,1,8, + 5,8,126,8,8,10,8,12,8,129,9,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,5,9,138,8,9,10, + 9,12,9,141,9,9,1,9,1,9,1,10,1,10,1,10,1,10,5,10,149,8,10,10,10,12,10,152, + 9,10,1,10,3,10,155,8,10,3,10,157,8,10,1,10,1,10,1,11,1,11,1,11,1,12,1,12, + 5,12,166,8,12,10,12,12,12,169,9,12,1,12,1,12,3,12,173,8,12,1,13,1,13,1, + 13,1,13,3,13,179,8,13,1,14,1,14,1,14,1,14,1,14,1,14,3,14,187,8,14,1,15, + 1,15,3,15,191,8,15,1,16,1,16,5,16,195,8,16,10,16,12,16,198,9,16,1,16,1, + 16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18, + 1,19,1,19,1,19,1,19,1,19,1,19,1,19,3,19,223,8,19,1,19,1,19,1,20,1,20,1, + 20,1,20,1,20,3,20,232,8,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,1,22, + 1,22,1,22,1,22,3,22,246,8,22,1,23,1,23,1,23,3,23,251,8,23,1,24,1,24,1,24, + 1,24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1, + 26,1,26,1,26,1,26,1,26,1,26,1,26,1,27,1,27,3,27,279,8,27,1,28,1,28,1,29, + 1,29,3,29,285,8,29,1,30,1,30,1,30,1,30,5,30,291,8,30,10,30,12,30,294,9, + 30,1,30,3,30,297,8,30,3,30,299,8,30,1,30,1,30,1,31,1,31,1,31,1,32,1,32, + 1,32,1,32,5,32,310,8,32,10,32,12,32,313,9,32,1,32,3,32,316,8,32,3,32,318, + 8,32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,3,33,331,8, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33, + 1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,5,33,357,8,33,10,33,12, + 33,360,9,33,1,33,3,33,363,8,33,3,33,365,8,33,1,33,1,33,1,33,1,33,3,33,371, + 8,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33, + 1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,33,1,33,5,33,423,8,33,10,33,12,33,426,9,33, + 1,34,1,34,1,35,1,35,1,35,1,35,1,35,3,35,435,8,35,1,36,1,36,3,36,439,8,36, + 1,37,1,37,1,38,1,38,1,38,0,1,66,39,0,2,4,6,8,10,12,14,16,18,20,22,24,26, + 28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74, + 76,0,12,1,0,4,10,1,0,30,34,2,0,30,34,36,39,2,0,5,5,44,45,1,0,46,48,2,0, + 45,45,49,49,1,0,50,51,1,0,6,9,1,0,52,53,1,0,40,41,1,0,65,67,2,0,65,66,73, + 73,471,0,81,1,0,0,0,2,89,1,0,0,0,4,91,1,0,0,0,6,96,1,0,0,0,8,98,1,0,0,0, + 10,103,1,0,0,0,12,107,1,0,0,0,14,109,1,0,0,0,16,121,1,0,0,0,18,132,1,0, + 0,0,20,144,1,0,0,0,22,160,1,0,0,0,24,172,1,0,0,0,26,178,1,0,0,0,28,186, + 1,0,0,0,30,190,1,0,0,0,32,192,1,0,0,0,34,203,1,0,0,0,36,211,1,0,0,0,38, + 215,1,0,0,0,40,226,1,0,0,0,42,235,1,0,0,0,44,238,1,0,0,0,46,250,1,0,0,0, + 48,252,1,0,0,0,50,260,1,0,0,0,52,266,1,0,0,0,54,278,1,0,0,0,56,280,1,0, + 0,0,58,284,1,0,0,0,60,286,1,0,0,0,62,302,1,0,0,0,64,305,1,0,0,0,66,370, + 1,0,0,0,68,427,1,0,0,0,70,434,1,0,0,0,72,436,1,0,0,0,74,440,1,0,0,0,76, + 442,1,0,0,0,78,80,3,4,2,0,79,78,1,0,0,0,80,83,1,0,0,0,81,79,1,0,0,0,81, + 82,1,0,0,0,82,84,1,0,0,0,83,81,1,0,0,0,84,85,3,2,1,0,85,86,5,0,0,1,86,1, + 1,0,0,0,87,90,3,14,7,0,88,90,3,16,8,0,89,87,1,0,0,0,89,88,1,0,0,0,90,3, + 1,0,0,0,91,92,5,1,0,0,92,93,3,6,3,0,93,94,3,8,4,0,94,95,5,2,0,0,95,5,1, + 0,0,0,96,97,5,3,0,0,97,7,1,0,0,0,98,100,3,10,5,0,99,101,3,10,5,0,100,99, + 1,0,0,0,100,101,1,0,0,0,101,9,1,0,0,0,102,104,3,12,6,0,103,102,1,0,0,0, + 103,104,1,0,0,0,104,105,1,0,0,0,105,106,5,59,0,0,106,11,1,0,0,0,107,108, + 7,0,0,0,108,13,1,0,0,0,109,110,5,11,0,0,110,111,5,75,0,0,111,112,3,20,10, + 0,112,116,5,12,0,0,113,115,3,18,9,0,114,113,1,0,0,0,115,118,1,0,0,0,116, + 114,1,0,0,0,116,117,1,0,0,0,117,119,1,0,0,0,118,116,1,0,0,0,119,120,5,13, + 0,0,120,15,1,0,0,0,121,122,5,14,0,0,122,123,5,75,0,0,123,127,5,12,0,0,124, + 126,3,18,9,0,125,124,1,0,0,0,126,129,1,0,0,0,127,125,1,0,0,0,127,128,1, + 0,0,0,128,130,1,0,0,0,129,127,1,0,0,0,130,131,5,13,0,0,131,17,1,0,0,0,132, + 133,5,15,0,0,133,134,5,75,0,0,134,135,3,20,10,0,135,139,5,12,0,0,136,138, + 3,26,13,0,137,136,1,0,0,0,138,141,1,0,0,0,139,137,1,0,0,0,139,140,1,0,0, + 0,140,142,1,0,0,0,141,139,1,0,0,0,142,143,5,13,0,0,143,19,1,0,0,0,144,156, + 5,16,0,0,145,150,3,22,11,0,146,147,5,17,0,0,147,149,3,22,11,0,148,146,1, + 0,0,0,149,152,1,0,0,0,150,148,1,0,0,0,150,151,1,0,0,0,151,154,1,0,0,0,152, + 150,1,0,0,0,153,155,5,17,0,0,154,153,1,0,0,0,154,155,1,0,0,0,155,157,1, + 0,0,0,156,145,1,0,0,0,156,157,1,0,0,0,157,158,1,0,0,0,158,159,5,18,0,0, + 159,21,1,0,0,0,160,161,3,74,37,0,161,162,5,75,0,0,162,23,1,0,0,0,163,167, + 5,12,0,0,164,166,3,26,13,0,165,164,1,0,0,0,166,169,1,0,0,0,167,165,1,0, + 0,0,167,168,1,0,0,0,168,170,1,0,0,0,169,167,1,0,0,0,170,173,5,13,0,0,171, + 173,3,26,13,0,172,163,1,0,0,0,172,171,1,0,0,0,173,25,1,0,0,0,174,179,3, + 30,15,0,175,176,3,28,14,0,176,177,5,2,0,0,177,179,1,0,0,0,178,174,1,0,0, + 0,178,175,1,0,0,0,179,27,1,0,0,0,180,187,3,32,16,0,181,187,3,34,17,0,182, + 187,3,36,18,0,183,187,3,38,19,0,184,187,3,40,20,0,185,187,3,42,21,0,186, + 180,1,0,0,0,186,181,1,0,0,0,186,182,1,0,0,0,186,183,1,0,0,0,186,184,1,0, + 0,0,186,185,1,0,0,0,187,29,1,0,0,0,188,191,3,44,22,0,189,191,3,46,23,0, + 190,188,1,0,0,0,190,189,1,0,0,0,191,31,1,0,0,0,192,196,3,74,37,0,193,195, + 3,68,34,0,194,193,1,0,0,0,195,198,1,0,0,0,196,194,1,0,0,0,196,197,1,0,0, + 0,197,199,1,0,0,0,198,196,1,0,0,0,199,200,5,75,0,0,200,201,5,10,0,0,201, + 202,3,66,33,0,202,33,1,0,0,0,203,204,3,74,37,0,204,205,5,75,0,0,205,206, + 5,17,0,0,206,207,3,74,37,0,207,208,5,75,0,0,208,209,5,10,0,0,209,210,3, + 66,33,0,210,35,1,0,0,0,211,212,5,75,0,0,212,213,5,10,0,0,213,214,3,66,33, + 0,214,37,1,0,0,0,215,216,5,19,0,0,216,217,5,16,0,0,217,218,5,72,0,0,218, + 219,5,6,0,0,219,222,3,66,33,0,220,221,5,17,0,0,221,223,3,56,28,0,222,220, + 1,0,0,0,222,223,1,0,0,0,223,224,1,0,0,0,224,225,5,18,0,0,225,39,1,0,0,0, + 226,227,5,19,0,0,227,228,5,16,0,0,228,231,3,66,33,0,229,230,5,17,0,0,230, + 232,3,56,28,0,231,229,1,0,0,0,231,232,1,0,0,0,232,233,1,0,0,0,233,234,5, + 18,0,0,234,41,1,0,0,0,235,236,5,20,0,0,236,237,3,60,30,0,237,43,1,0,0,0, + 238,239,5,21,0,0,239,240,5,16,0,0,240,241,3,66,33,0,241,242,5,18,0,0,242, + 245,3,24,12,0,243,244,5,22,0,0,244,246,3,24,12,0,245,243,1,0,0,0,245,246, + 1,0,0,0,246,45,1,0,0,0,247,251,3,48,24,0,248,251,3,50,25,0,249,251,3,52, + 26,0,250,247,1,0,0,0,250,248,1,0,0,0,250,249,1,0,0,0,251,47,1,0,0,0,252, + 253,5,23,0,0,253,254,3,24,12,0,254,255,5,24,0,0,255,256,5,16,0,0,256,257, + 3,66,33,0,257,258,5,18,0,0,258,259,5,2,0,0,259,49,1,0,0,0,260,261,5,24, + 0,0,261,262,5,16,0,0,262,263,3,66,33,0,263,264,5,18,0,0,264,265,3,24,12, + 0,265,51,1,0,0,0,266,267,5,25,0,0,267,268,5,16,0,0,268,269,3,54,27,0,269, + 270,5,2,0,0,270,271,3,66,33,0,271,272,5,2,0,0,272,273,3,36,18,0,273,274, + 5,18,0,0,274,275,3,24,12,0,275,53,1,0,0,0,276,279,3,32,16,0,277,279,3,36, + 18,0,278,276,1,0,0,0,278,277,1,0,0,0,279,55,1,0,0,0,280,281,5,69,0,0,281, + 57,1,0,0,0,282,285,5,75,0,0,283,285,3,70,35,0,284,282,1,0,0,0,284,283,1, + 0,0,0,285,59,1,0,0,0,286,298,5,16,0,0,287,292,3,58,29,0,288,289,5,17,0, + 0,289,291,3,58,29,0,290,288,1,0,0,0,291,294,1,0,0,0,292,290,1,0,0,0,292, + 293,1,0,0,0,293,296,1,0,0,0,294,292,1,0,0,0,295,297,5,17,0,0,296,295,1, 0,0,0,296,297,1,0,0,0,297,299,1,0,0,0,298,287,1,0,0,0,298,299,1,0,0,0,299, - 300,1,0,0,0,300,301,5,17,0,0,301,61,1,0,0,0,302,303,6,31,-1,0,303,304,5, - 15,0,0,304,305,3,62,31,0,305,306,5,17,0,0,306,352,1,0,0,0,307,308,3,72, - 36,0,308,309,5,15,0,0,309,311,3,62,31,0,310,312,5,16,0,0,311,310,1,0,0, - 0,311,312,1,0,0,0,312,313,1,0,0,0,313,314,5,17,0,0,314,352,1,0,0,0,315, - 352,3,58,29,0,316,317,5,25,0,0,317,318,5,74,0,0,318,352,3,60,30,0,319,320, - 5,28,0,0,320,321,5,26,0,0,321,322,3,62,31,0,322,323,5,27,0,0,323,324,7, - 1,0,0,324,352,1,0,0,0,325,326,5,34,0,0,326,327,5,26,0,0,327,328,3,62,31, - 0,328,329,5,27,0,0,329,330,7,2,0,0,330,352,1,0,0,0,331,332,7,3,0,0,332, - 352,3,62,31,15,333,345,5,26,0,0,334,339,3,62,31,0,335,336,5,16,0,0,336, - 338,3,62,31,0,337,335,1,0,0,0,338,341,1,0,0,0,339,337,1,0,0,0,339,340,1, - 0,0,0,340,343,1,0,0,0,341,339,1,0,0,0,342,344,5,16,0,0,343,342,1,0,0,0, - 343,344,1,0,0,0,344,346,1,0,0,0,345,334,1,0,0,0,345,346,1,0,0,0,346,347, - 1,0,0,0,347,352,5,27,0,0,348,352,5,73,0,0,349,352,5,74,0,0,350,352,3,66, - 33,0,351,302,1,0,0,0,351,307,1,0,0,0,351,315,1,0,0,0,351,316,1,0,0,0,351, - 319,1,0,0,0,351,325,1,0,0,0,351,331,1,0,0,0,351,333,1,0,0,0,351,348,1,0, - 0,0,351,349,1,0,0,0,351,350,1,0,0,0,352,405,1,0,0,0,353,354,10,14,0,0,354, - 355,7,4,0,0,355,404,3,62,31,15,356,357,10,13,0,0,357,358,7,5,0,0,358,404, - 3,62,31,14,359,360,10,12,0,0,360,361,7,6,0,0,361,404,3,62,31,13,362,363, - 10,11,0,0,363,364,7,7,0,0,364,404,3,62,31,12,365,366,10,10,0,0,366,367, - 7,8,0,0,367,404,3,62,31,11,368,369,10,9,0,0,369,370,5,53,0,0,370,404,3, - 62,31,10,371,372,10,8,0,0,372,373,5,4,0,0,373,404,3,62,31,9,374,375,10, - 7,0,0,375,376,5,54,0,0,376,404,3,62,31,8,377,378,10,6,0,0,378,379,5,55, - 0,0,379,404,3,62,31,7,380,381,10,5,0,0,381,382,5,56,0,0,382,404,3,62,31, - 6,383,384,10,21,0,0,384,385,5,26,0,0,385,386,5,61,0,0,386,404,5,27,0,0, - 387,388,10,18,0,0,388,404,7,9,0,0,389,390,10,17,0,0,390,391,5,41,0,0,391, - 392,5,15,0,0,392,393,3,62,31,0,393,394,5,17,0,0,394,404,1,0,0,0,395,396, - 10,16,0,0,396,397,5,42,0,0,397,398,5,15,0,0,398,399,3,62,31,0,399,400,5, - 16,0,0,400,401,3,62,31,0,401,402,5,17,0,0,402,404,1,0,0,0,403,353,1,0,0, - 0,403,356,1,0,0,0,403,359,1,0,0,0,403,362,1,0,0,0,403,365,1,0,0,0,403,368, - 1,0,0,0,403,371,1,0,0,0,403,374,1,0,0,0,403,377,1,0,0,0,403,380,1,0,0,0, - 403,383,1,0,0,0,403,387,1,0,0,0,403,389,1,0,0,0,403,395,1,0,0,0,404,407, - 1,0,0,0,405,403,1,0,0,0,405,406,1,0,0,0,406,63,1,0,0,0,407,405,1,0,0,0, - 408,409,5,57,0,0,409,65,1,0,0,0,410,416,5,59,0,0,411,416,3,68,34,0,412, - 416,5,68,0,0,413,416,5,69,0,0,414,416,5,70,0,0,415,410,1,0,0,0,415,411, - 1,0,0,0,415,412,1,0,0,0,415,413,1,0,0,0,415,414,1,0,0,0,416,67,1,0,0,0, - 417,419,5,61,0,0,418,420,5,60,0,0,419,418,1,0,0,0,419,420,1,0,0,0,420,69, - 1,0,0,0,421,422,7,10,0,0,422,71,1,0,0,0,423,424,7,11,0,0,424,73,1,0,0,0, - 35,77,92,95,108,120,131,135,137,148,153,159,167,171,177,203,212,226,231, - 259,265,273,277,279,292,296,298,311,339,343,345,351,403,405,415,419]; + 300,1,0,0,0,300,301,5,18,0,0,301,61,1,0,0,0,302,303,5,75,0,0,303,304,3, + 64,32,0,304,63,1,0,0,0,305,317,5,16,0,0,306,311,3,66,33,0,307,308,5,17, + 0,0,308,310,3,66,33,0,309,307,1,0,0,0,310,313,1,0,0,0,311,309,1,0,0,0,311, + 312,1,0,0,0,312,315,1,0,0,0,313,311,1,0,0,0,314,316,5,17,0,0,315,314,1, + 0,0,0,315,316,1,0,0,0,316,318,1,0,0,0,317,306,1,0,0,0,317,318,1,0,0,0,318, + 319,1,0,0,0,319,320,5,18,0,0,320,65,1,0,0,0,321,322,6,33,-1,0,322,323,5, + 16,0,0,323,324,3,66,33,0,324,325,5,18,0,0,325,371,1,0,0,0,326,327,3,76, + 38,0,327,328,5,16,0,0,328,330,3,66,33,0,329,331,5,17,0,0,330,329,1,0,0, + 0,330,331,1,0,0,0,331,332,1,0,0,0,332,333,5,18,0,0,333,371,1,0,0,0,334, + 371,3,62,31,0,335,336,5,26,0,0,336,337,5,75,0,0,337,371,3,64,32,0,338,339, + 5,29,0,0,339,340,5,27,0,0,340,341,3,66,33,0,341,342,5,28,0,0,342,343,7, + 1,0,0,343,371,1,0,0,0,344,345,5,35,0,0,345,346,5,27,0,0,346,347,3,66,33, + 0,347,348,5,28,0,0,348,349,7,2,0,0,349,371,1,0,0,0,350,351,7,3,0,0,351, + 371,3,66,33,15,352,364,5,27,0,0,353,358,3,66,33,0,354,355,5,17,0,0,355, + 357,3,66,33,0,356,354,1,0,0,0,357,360,1,0,0,0,358,356,1,0,0,0,358,359,1, + 0,0,0,359,362,1,0,0,0,360,358,1,0,0,0,361,363,5,17,0,0,362,361,1,0,0,0, + 362,363,1,0,0,0,363,365,1,0,0,0,364,353,1,0,0,0,364,365,1,0,0,0,365,366, + 1,0,0,0,366,371,5,28,0,0,367,371,5,74,0,0,368,371,5,75,0,0,369,371,3,70, + 35,0,370,321,1,0,0,0,370,326,1,0,0,0,370,334,1,0,0,0,370,335,1,0,0,0,370, + 338,1,0,0,0,370,344,1,0,0,0,370,350,1,0,0,0,370,352,1,0,0,0,370,367,1,0, + 0,0,370,368,1,0,0,0,370,369,1,0,0,0,371,424,1,0,0,0,372,373,10,14,0,0,373, + 374,7,4,0,0,374,423,3,66,33,15,375,376,10,13,0,0,376,377,7,5,0,0,377,423, + 3,66,33,14,378,379,10,12,0,0,379,380,7,6,0,0,380,423,3,66,33,13,381,382, + 10,11,0,0,382,383,7,7,0,0,383,423,3,66,33,12,384,385,10,10,0,0,385,386, + 7,8,0,0,386,423,3,66,33,11,387,388,10,9,0,0,388,389,5,54,0,0,389,423,3, + 66,33,10,390,391,10,8,0,0,391,392,5,4,0,0,392,423,3,66,33,9,393,394,10, + 7,0,0,394,395,5,55,0,0,395,423,3,66,33,8,396,397,10,6,0,0,397,398,5,56, + 0,0,398,423,3,66,33,7,399,400,10,5,0,0,400,401,5,57,0,0,401,423,3,66,33, + 6,402,403,10,21,0,0,403,404,5,27,0,0,404,405,5,62,0,0,405,423,5,28,0,0, + 406,407,10,18,0,0,407,423,7,9,0,0,408,409,10,17,0,0,409,410,5,42,0,0,410, + 411,5,16,0,0,411,412,3,66,33,0,412,413,5,18,0,0,413,423,1,0,0,0,414,415, + 10,16,0,0,415,416,5,43,0,0,416,417,5,16,0,0,417,418,3,66,33,0,418,419,5, + 17,0,0,419,420,3,66,33,0,420,421,5,18,0,0,421,423,1,0,0,0,422,372,1,0,0, + 0,422,375,1,0,0,0,422,378,1,0,0,0,422,381,1,0,0,0,422,384,1,0,0,0,422,387, + 1,0,0,0,422,390,1,0,0,0,422,393,1,0,0,0,422,396,1,0,0,0,422,399,1,0,0,0, + 422,402,1,0,0,0,422,406,1,0,0,0,422,408,1,0,0,0,422,414,1,0,0,0,423,426, + 1,0,0,0,424,422,1,0,0,0,424,425,1,0,0,0,425,67,1,0,0,0,426,424,1,0,0,0, + 427,428,5,58,0,0,428,69,1,0,0,0,429,435,5,60,0,0,430,435,3,72,36,0,431, + 435,5,69,0,0,432,435,5,70,0,0,433,435,5,71,0,0,434,429,1,0,0,0,434,430, + 1,0,0,0,434,431,1,0,0,0,434,432,1,0,0,0,434,433,1,0,0,0,435,71,1,0,0,0, + 436,438,5,62,0,0,437,439,5,61,0,0,438,437,1,0,0,0,438,439,1,0,0,0,439,73, + 1,0,0,0,440,441,7,10,0,0,441,75,1,0,0,0,442,443,7,11,0,0,443,77,1,0,0,0, + 37,81,89,100,103,116,127,139,150,154,156,167,172,178,186,190,196,222,231, + 245,250,278,284,292,296,298,311,315,317,330,358,362,364,370,422,424,434, + 438]; private static __ATN: ATN; public static get _ATN(): ATN { @@ -2494,8 +2592,8 @@ export class SourceFileContext extends ParserRuleContext { super(parent, invokingState); this.parser = parser; } - public contractDefinition(): ContractDefinitionContext { - return this.getTypedRuleContext(ContractDefinitionContext, 0) as ContractDefinitionContext; + public topLevelDefinition(): TopLevelDefinitionContext { + return this.getTypedRuleContext(TopLevelDefinitionContext, 0) as TopLevelDefinitionContext; } public EOF(): TerminalNode { return this.getToken(CashScriptParser.EOF, 0); @@ -2520,6 +2618,31 @@ export class SourceFileContext extends ParserRuleContext { } +export class TopLevelDefinitionContext extends ParserRuleContext { + constructor(parser?: CashScriptParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public contractDefinition(): ContractDefinitionContext { + return this.getTypedRuleContext(ContractDefinitionContext, 0) as ContractDefinitionContext; + } + public libraryDefinition(): LibraryDefinitionContext { + return this.getTypedRuleContext(LibraryDefinitionContext, 0) as LibraryDefinitionContext; + } + public get ruleIndex(): number { + return CashScriptParser.RULE_topLevelDefinition; + } + // @Override + public accept(visitor: CashScriptVisitor): Result { + if (visitor.visitTopLevelDefinition) { + return visitor.visitTopLevelDefinition(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class PragmaDirectiveContext extends ParserRuleContext { constructor(parser?: CashScriptParser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); @@ -2664,6 +2787,34 @@ export class ContractDefinitionContext extends ParserRuleContext { } +export class LibraryDefinitionContext extends ParserRuleContext { + constructor(parser?: CashScriptParser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public Identifier(): TerminalNode { + return this.getToken(CashScriptParser.Identifier, 0); + } + public functionDefinition_list(): FunctionDefinitionContext[] { + return this.getTypedRuleContexts(FunctionDefinitionContext) as FunctionDefinitionContext[]; + } + public functionDefinition(i: number): FunctionDefinitionContext { + return this.getTypedRuleContext(FunctionDefinitionContext, i) as FunctionDefinitionContext; + } + public get ruleIndex(): number { + return CashScriptParser.RULE_libraryDefinition; + } + // @Override + public accept(visitor: CashScriptVisitor): Result { + if (visitor.visitLibraryDefinition) { + return visitor.visitLibraryDefinition(this); + } else { + return visitor.visitChildren(this); + } + } +} + + export class FunctionDefinitionContext extends ParserRuleContext { constructor(parser?: CashScriptParser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); @@ -3319,7 +3470,7 @@ export class ExpressionContext extends ParserRuleContext { public get ruleIndex(): number { return CashScriptParser.RULE_expression; } - public copyFrom(ctx: ExpressionContext): void { + public override copyFrom(ctx: ExpressionContext): void { super.copyFrom(ctx); } } diff --git a/packages/cashc/src/grammar/CashScriptVisitor.ts b/packages/cashc/src/grammar/CashScriptVisitor.ts index c1d8ebfa..22e02762 100644 --- a/packages/cashc/src/grammar/CashScriptVisitor.ts +++ b/packages/cashc/src/grammar/CashScriptVisitor.ts @@ -1,15 +1,17 @@ -// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.1 +// Generated from src/grammar/CashScript.g4 by ANTLR 4.13.2 import {ParseTreeVisitor} from 'antlr4'; import { SourceFileContext } from "./CashScriptParser.js"; +import { TopLevelDefinitionContext } from "./CashScriptParser.js"; import { PragmaDirectiveContext } from "./CashScriptParser.js"; import { PragmaNameContext } from "./CashScriptParser.js"; import { PragmaValueContext } from "./CashScriptParser.js"; import { VersionConstraintContext } from "./CashScriptParser.js"; import { VersionOperatorContext } from "./CashScriptParser.js"; import { ContractDefinitionContext } from "./CashScriptParser.js"; +import { LibraryDefinitionContext } from "./CashScriptParser.js"; import { FunctionDefinitionContext } from "./CashScriptParser.js"; import { ParameterListContext } from "./CashScriptParser.js"; import { ParameterContext } from "./CashScriptParser.js"; @@ -68,6 +70,12 @@ export default class CashScriptVisitor extends ParseTreeVisitor * @return the visitor result */ visitSourceFile?: (ctx: SourceFileContext) => Result; + /** + * Visit a parse tree produced by `CashScriptParser.topLevelDefinition`. + * @param ctx the parse tree + * @return the visitor result + */ + visitTopLevelDefinition?: (ctx: TopLevelDefinitionContext) => Result; /** * Visit a parse tree produced by `CashScriptParser.pragmaDirective`. * @param ctx the parse tree @@ -104,6 +112,12 @@ export default class CashScriptVisitor extends ParseTreeVisitor * @return the visitor result */ visitContractDefinition?: (ctx: ContractDefinitionContext) => Result; + /** + * Visit a parse tree produced by `CashScriptParser.libraryDefinition`. + * @param ctx the parse tree + * @return the visitor result + */ + visitLibraryDefinition?: (ctx: LibraryDefinitionContext) => Result; /** * Visit a parse tree produced by `CashScriptParser.functionDefinition`. * @param ctx the parse tree diff --git a/packages/cashc/src/imports.ts b/packages/cashc/src/imports.ts index e8704ab3..c3f96a10 100644 --- a/packages/cashc/src/imports.ts +++ b/packages/cashc/src/imports.ts @@ -267,17 +267,17 @@ function transformLibrary( usedMangledNames: Set, ): LibraryTransformResult { const tokens = getVisibleTokens(library.body); - const functionNames = collectFunctionDefinitions(tokens); + const functionDefinitions = collectFunctionDefinitions(tokens); - if (functionNames.length === 0) { + if (functionDefinitions.length === 0) { throw new InvalidLibraryImportError(`Library '${library.name}' does not define any functions.`); } - const localFunctions = new Set(functionNames.map((definition) => definition.name)); + const localFunctions = new Set(functionDefinitions.map((definition) => definition.name)); const mangledNames = new Map(); const replacements: Replacement[] = []; - functionNames.forEach(({ name, token }) => { + functionDefinitions.forEach(({ name, nameToken, openBraceToken, visibility }) => { const mangledName = `${alias}_${name}`; if (usedMangledNames.has(mangledName)) { throw new InvalidLibraryImportError( @@ -287,7 +287,11 @@ function transformLibrary( usedMangledNames.add(mangledName); mangledNames.set(name, mangledName); - replacements.push({ start: token.start, stop: token.stop, text: mangledName }); + replacements.push({ start: nameToken.start, stop: nameToken.stop, text: mangledName }); + + if (visibility === 'omitted') { + replacements.push({ start: openBraceToken.start, stop: openBraceToken.stop, text: ' internal {' }); + } }); validateLibraryCalls(tokens, localFunctions, library.name); @@ -295,12 +299,22 @@ function transformLibrary( return { body: applyReplacements(library.body, replacements).trim(), - functions: new Set(functionNames.map((definition) => definition.name)), + functions: new Set(functionDefinitions.map((definition) => definition.name)), }; } -function collectFunctionDefinitions(tokens: Token[]): Array<{ name: string; token: Token }> { - const definitions: Array<{ name: string; token: Token }> = []; +function collectFunctionDefinitions(tokens: Token[]): Array<{ + name: string; + nameToken: Token; + openBraceToken: Token; + visibility: FunctionVisibility.INTERNAL | 'omitted'; +}> { + const definitions: Array<{ + name: string; + nameToken: Token; + openBraceToken: Token; + visibility: FunctionVisibility.INTERNAL | 'omitted'; + }> = []; for (let index = 0; index < tokens.length; index += 1) { if (tokens[index].text !== 'function') continue; @@ -320,13 +334,23 @@ function collectFunctionDefinitions(tokens: Token[]): Array<{ name: string; toke } const visibilityToken = tokens[cursor]; - if (visibilityToken?.text !== FunctionVisibility.INTERNAL) { + if (visibilityToken?.text === FunctionVisibility.PUBLIC) { throw new InvalidLibraryImportError( - `Imported library functions must be declared explicit internal. Offending function: '${nameToken.text}'.`, + `Imported library functions cannot be public. Offending function: '${nameToken.text}'.`, ); } - definitions.push({ name: nameToken.text, token: nameToken }); + const openBraceToken = visibilityToken?.text === FunctionVisibility.INTERNAL ? tokens[cursor + 1] : visibilityToken; + if (openBraceToken?.text !== '{') { + throw new InvalidLibraryImportError('Invalid function definition in imported library.'); + } + + definitions.push({ + name: nameToken.text, + nameToken, + openBraceToken, + visibility: visibilityToken?.text === FunctionVisibility.INTERNAL ? FunctionVisibility.INTERNAL : 'omitted', + }); } return definitions; diff --git a/packages/cashc/test/compiler/compiler.test.ts b/packages/cashc/test/compiler/compiler.test.ts index 81dd9a23..33ee97cd 100644 --- a/packages/cashc/test/compiler/compiler.test.ts +++ b/packages/cashc/test/compiler/compiler.test.ts @@ -257,8 +257,8 @@ contract Other() { })).toThrow(Errors.InvalidLibraryImportError); }); - it('should reject imported library functions that omit explicit internal visibility', () => { - expect(() => compileString(` + it('should treat omitted library visibility as helper-only when importing libraries', () => { + const artifact = compileString(` import "./math.cash" as Math; contract UsesLibrary() { @@ -275,7 +275,11 @@ library MathHelpers { } } `, - })).toThrow(Errors.InvalidLibraryImportError); + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); }); it('should reject imported libraries that reference non-library local functions', () => { From 363202d37c4bc05a09132dcb7be4f2353f741b3c Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 2 Apr 2026 20:49:12 -0400 Subject: [PATCH 7/9] Reuse shared frames for public helper calls --- .../src/generation/GenerateTargetTraversal.ts | 26 +++++++++++++-- .../cashc/test/generation/generation.test.ts | 32 +++++++++++++++++-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/cashc/src/generation/GenerateTargetTraversal.ts b/packages/cashc/src/generation/GenerateTargetTraversal.ts index 74eed8b2..554478f1 100644 --- a/packages/cashc/src/generation/GenerateTargetTraversal.ts +++ b/packages/cashc/src/generation/GenerateTargetTraversal.ts @@ -160,7 +160,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { } if (publicFunctions.length === 1) { - publicFunctions[0] = this.visit(publicFunctions[0]) as FunctionDefinitionNode; + publicFunctions[0] = this.emitPublicFunctionDispatch(publicFunctions[0]) as FunctionDefinitionNode; } else { this.pushToStack('$$', true); publicFunctions.forEach((f, i) => { @@ -187,7 +187,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { this.emit(Op.OP_VERIFY, locationData); } - this.visit(f); + this.emitPublicFunctionDispatch(f); if (i < publicFunctions.length - 1) { this.emit(Op.OP_ELSE, { ...locationData, positionHint: PositionHint.END }); @@ -204,6 +204,28 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { return node; } + private emitPublicFunctionDispatch(node: FunctionDefinitionNode): Node { + if (this.functionIndices.has(node.name)) { + node.parameters = this.visitList(node.parameters) as ParameterNode[]; + + if (this.compilerOptions.enforceFunctionParameterTypes) { + this.enforceFunctionParameterTypes(node); + } + + this.emit( + encodeInt(BigInt(this.functionIndices.get(node.name)!)), + { location: node.location, positionHint: PositionHint.START }, + ); + this.pushToStack('(value)'); + this.emit(Op.OP_INVOKE, { location: node.location, positionHint: PositionHint.END }); + this.popFromStack(node.parameters.length + 1); + this.pushToStack('(value)'); + return node; + } + + return this.visit(node); + } + visitFunctionDefinition(node: FunctionDefinitionNode): Node { this.currentFunction = node; diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index 040b2ae1..d44f0538 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -164,7 +164,7 @@ contract PublicCalls() { { name: 'validate', inputs: [{ name: 'value', type: 'int' }] }, ]); expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(1); - expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(1); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); expect(artifact.compiler.target).toBe('BCH_2026_05'); }); @@ -185,8 +185,34 @@ contract LiteralNames() { { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, { name: 'helper_check', inputs: [{ name: 'value', type: 'int' }] }, ]); - expect(artifact.bytecode).toContain('OP_DEFINE'); - expect(artifact.bytecode).toContain('OP_INVOKE'); + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(1); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + }); + + it('should reuse a shared frame when an internal helper invokes a public function', () => { + const artifact = compileString(` +contract InternalCallsPublic() { + function spend(int x) public { + require(route(x)); + } + + function route(int value) internal { + require(validate(value)); + } + + function validate(int value) public { + require(value == 11); + } +} +`); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'x', type: 'int' }] }, + { name: 'validate', inputs: [{ name: 'value', type: 'int' }] }, + ]); + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(2); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + expect(artifact.compiler.target).toBe('BCH_2026_05'); }); it('should reject explicit pre-2026 targets when internal-function opcodes are required', () => { From d2b6dca87dcece6b16fc16684cd4d6db01702670 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Thu, 2 Apr 2026 21:06:55 -0400 Subject: [PATCH 8/9] Harden library helper edge cases --- packages/cashc/src/imports.ts | 12 +++ .../src/print/OutputSourceCodeTraversal.ts | 15 ++- packages/cashc/test/ast/AST.test.ts | 21 +++++ packages/cashc/test/ast/Location.test.ts | 2 +- packages/cashc/test/compiler/compiler.test.ts | 91 +++++++++++++++++++ .../cashc/test/generation/generation.test.ts | 74 +++++++++++++++ 6 files changed, 211 insertions(+), 4 deletions(-) diff --git a/packages/cashc/src/imports.ts b/packages/cashc/src/imports.ts index c3f96a10..69db2049 100644 --- a/packages/cashc/src/imports.ts +++ b/packages/cashc/src/imports.ts @@ -362,6 +362,18 @@ function validateLibraryCalls(tokens: Token[], localFunctions: Set, libr const nextToken = tokens[index + 1]; const previousToken = tokens[index - 1]; + if ( + token.text?.match(IDENTIFIER_PATTERN) + && nextToken?.text === '.' + && tokens[index + 2]?.text?.match(IDENTIFIER_PATTERN) + && tokens[index + 3]?.text === '(' + && previousToken?.text !== '.' + ) { + throw new InvalidLibraryImportError( + `Library '${libraryName}' references external helper '${token.text}.${tokens[index + 2]!.text}'. Imported libraries may only call their own functions or built-ins.`, + ); + } + if (!token.text?.match(IDENTIFIER_PATTERN) || nextToken?.text !== '(') continue; if (previousToken?.text === 'function' || previousToken?.text === 'new' || previousToken?.text === '.') continue; diff --git a/packages/cashc/src/print/OutputSourceCodeTraversal.ts b/packages/cashc/src/print/OutputSourceCodeTraversal.ts index 7347cd16..56a6fca2 100644 --- a/packages/cashc/src/print/OutputSourceCodeTraversal.ts +++ b/packages/cashc/src/print/OutputSourceCodeTraversal.ts @@ -62,9 +62,18 @@ export default class OutputSourceCodeTraversal extends AstTraversal { } visitContract(node: ContractNode): Node { - this.addOutput(`${node.kind} ${node.name}(`, true); - node.parameters = this.visitCommaList(node.parameters) as ParameterNode[]; - this.addOutput(') {'); + this.addOutput(`${node.kind} ${node.name}`, true); + + if (node.kind === 'contract') { + this.addOutput('('); + node.parameters = this.visitCommaList(node.parameters) as ParameterNode[]; + this.addOutput(') '); + } else { + node.parameters = this.visitCommaList(node.parameters) as ParameterNode[]; + this.addOutput(' '); + } + + this.addOutput('{'); this.outputSymbolTable(node.symbolTable); this.addOutput('\n'); diff --git a/packages/cashc/test/ast/AST.test.ts b/packages/cashc/test/ast/AST.test.ts index 84bf6e04..7308df76 100644 --- a/packages/cashc/test/ast/AST.test.ts +++ b/packages/cashc/test/ast/AST.test.ts @@ -72,5 +72,26 @@ contract Test() { expect(initialOutput).toContain('function validate(int value) internal'); expect(rerunOutput).toEqual(initialOutput); }); + + it('should preserve library containers in AST source output', () => { + const input = ` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } + + function checkParity(int value) internal { + require(isEven(value)); + } +}`; + + const { sourceOutput: initialOutput } = setup(input); + const { sourceOutput: rerunOutput } = setup(initialOutput); + + expect(initialOutput).toContain('library MathHelpers {'); + expect(initialOutput).toContain('function isEven(int value) internal'); + expect(initialOutput).toContain('function checkParity(int value) internal'); + expect(rerunOutput).toEqual(initialOutput); + }); }); }); diff --git a/packages/cashc/test/ast/Location.test.ts b/packages/cashc/test/ast/Location.test.ts index 4f7d9418..e247559a 100644 --- a/packages/cashc/test/ast/Location.test.ts +++ b/packages/cashc/test/ast/Location.test.ts @@ -12,7 +12,7 @@ describe('Location', () => { const f = ast.contract.functions[0]; expect(f.location).toBeDefined(); - expect((f.location).text(code)).toEqual('function hello(sig s, pubkey pk) {\n require(checkSig(s, pk));\n }'); + expect((f.location).text(code)).toEqual('function hello(sig s, pubkey pk) public {\n require(checkSig(s, pk));\n }'); }); const wrap = (code: string): string => { diff --git a/packages/cashc/test/compiler/compiler.test.ts b/packages/cashc/test/compiler/compiler.test.ts index 33ee97cd..dbcffb43 100644 --- a/packages/cashc/test/compiler/compiler.test.ts +++ b/packages/cashc/test/compiler/compiler.test.ts @@ -340,5 +340,96 @@ library CoreHelpers { }, })).toThrow(Errors.InvalidLibraryImportError); }); + + it('should reject duplicate import aliases', () => { + expect(() => compileString(` +import "./math.cash" as Helpers; +import "./bits.cash" as Helpers; + +contract UsesLibraries() { + function spend(int value) public { + require(Helpers.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library Helpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`, + })).toThrow(Errors.InvalidImportDirectiveError); + }); + + it('should reject calls to missing imported library functions', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isOdd(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`, + })).toThrow(Errors.InvalidImportDirectiveError); + }); + + it('should reject imported libraries that attempt namespaced external helper calls', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) { + require(Other.check(value)); + } +} +`, + })).toThrow(Errors.InvalidLibraryImportError); + }); + + it('should not rewrite imported helper references inside comments or strings', () => { + const artifact = compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + console.log("Math.isEven(value) should stay literal"); + // Math.isEven(value) should stay in the comment too + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + }); }); }); diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index d44f0538..4545432f 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -325,4 +325,78 @@ library MathHelpers { expect(artifact.bytecode).toContain('OP_INVOKE'); expect(artifact.compiler.target).toBe('BCH_2026_05'); }); + + it('should support multiple imported libraries with overlapping helper names', () => { + const artifact = compileString(` +import "./math.cash" as Math; +import "./bits.cash" as Bits; + +contract UsesLibraries() { + function spend(int value) public { + require(Math.isEven(value)); + require(Bits.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + if (specifier === './math.cash') { + return ` +library MathHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`; + } + + return ` +library BitHelpers { + function isEven(int value) { + require((value % 4) == 0 || (value % 4) == 2); + } +} +`; + }, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(2); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + expect(artifact.compiler.target).toBe('BCH_2026_05'); + }); + + it('should emit only reachable imported helper call chains', () => { + const artifact = compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: () => ` +library MathHelpers { + function isEven(int value) { + require(checkParity(value)); + } + + function checkParity(int value) { + require(value % 2 == 0); + } + + function unused(int value) { + require(value == 123); + } +} +`, + }); + + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(2); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(1); + }); }); From 45b92104673631d0b33b45ebc4e8200f2a2cb121 Mon Sep 17 00:00:00 2001 From: lightswarm124 Date: Fri, 3 Apr 2026 13:21:15 -0400 Subject: [PATCH 9/9] Finalize BCH functions and library helper review branch --- docs/proposals/bch-functions-team-review.md | 286 +++----------- .../functions-implementation-spec.md | 295 +++++++++++++++ .../functions-security-and-release-review.md | 206 ++++++++++ docs/proposals/functions-vs-next-review.md | 136 +++++++ docs/proposals/internal-functions-review.md | 36 +- examples/README.md | 6 +- examples/imported-helper-functions.cash | 13 + examples/imported-helper-functions.ts | 24 ++ examples/math-helpers.cash | 10 + examples/parity-helpers.cash | 7 + packages/cashc/src/ast/AST.ts | 4 + packages/cashc/src/ast/Location.ts | 4 +- packages/cashc/src/compiler.ts | 144 ++++++- .../src/generation/GenerateTargetTraversal.ts | 45 ++- packages/cashc/src/imports.ts | 357 +++++++++++++----- packages/cashc/test/ast/AST.test.ts | 2 +- packages/cashc/test/compiler/compiler.test.ts | 154 +++++++- .../cashc/test/generation/generation.test.ts | 154 ++++++-- packages/cashscript/src/Errors.ts | 117 ++++-- packages/cashscript/src/debugging.ts | 159 +++++--- packages/cashscript/test/debugging.test.ts | 28 ++ packages/utils/src/artifact.ts | 15 + packages/utils/src/types.ts | 1 + 23 files changed, 1705 insertions(+), 498 deletions(-) create mode 100644 docs/proposals/functions-implementation-spec.md create mode 100644 docs/proposals/functions-security-and-release-review.md create mode 100644 docs/proposals/functions-vs-next-review.md create mode 100644 examples/imported-helper-functions.cash create mode 100644 examples/imported-helper-functions.ts create mode 100644 examples/math-helpers.cash create mode 100644 examples/parity-helpers.cash diff --git a/docs/proposals/bch-functions-team-review.md b/docs/proposals/bch-functions-team-review.md index 61bab144..40cc2427 100644 --- a/docs/proposals/bch-functions-team-review.md +++ b/docs/proposals/bch-functions-team-review.md @@ -1,255 +1,79 @@ -# BCH Functions Proposal Review +# BCH Functions Team Review -This document summarizes the current working-tree patch on top of `next`. It is intended as a maintainer-facing review aid for the CashScript team. +This directory now contains the maintainer review packet for the BCH functions work. -The goal of this patch is to add safe user-defined internal functions to CashScript using BCH `OP_DEFINE` / `OP_INVOKE`, while keeping the language model understandable for developers and conservative enough for real integrations. +Recommended reading order: -## Scope +1. [functions-vs-next-review.md](./functions-vs-next-review.md) +2. [functions-implementation-spec.md](./functions-implementation-spec.md) +3. [functions-security-and-release-review.md](./functions-security-and-release-review.md) -This patch adds: +Historical/internal notes remain available if needed: -- explicit function visibility syntax: `public` and `internal` -- user-defined contract-function calls compiled to BCH function opcodes -- ABI filtering so only `public` functions are externally callable -- reachability-based function-table emission from public entrypoints -- compiler restrictions for patterns the current model does not safely support -- artifact/debug/runtime metadata updates needed for BCH 2026 function semantics -- expanded compiler, SDK, debugging, testing-suite, and documentation coverage +- [library-integration-plan.md](./library-integration-plan.md) +- [internal-functions-review.md](./internal-functions-review.md) -This patch intentionally does not add: +## What This Packet Covers -- signature checks inside internally invoked functions -- constructor-parameter capture inside internally invoked functions -- recursion or mutual recursion in invoked function graphs -- mandatory explicit visibility on all functions yet -- arbitrary typed internal return values beyond the current boolean-style model +The current branch implements: -## High-Level Model +- `public` and `internal` functions inside `contract` +- `library` as a non-spendable helper container +- BCH `OP_DEFINE` / `OP_INVOKE` lowering for local and imported helpers +- transitive `contract -> library -> library` imports +- helper-aware debug metadata and nested-frame debugging behavior -CashScript functions now have two roles: - -- `public`: appears in the artifact ABI and is exposed by the SDK -- `internal`: callable only from other contract functions and lowered to BCH function opcodes - -If visibility is omitted, the compiler currently defaults to `public` and emits a warning. That keeps existing contracts source-compatible while nudging authors toward explicit visibility. - -Public functions may also call other public functions. In that case, the called function remains in the ABI and is also emitted into the BCH function table if it is invoked internally. - -## Why This Approach - -The BCH functions CHIP makes internal code reuse possible at the script level, but CashScript and the SDK previously assumed: - -- ABI functions are the externally visible boundary -- most execution/debug reasoning happens within a single active bytecode body -- visibility is not yet enforced as a mandatory source-level concept - -So the patch takes a conservative path: - -1. Make visibility explicit instead of relying on naming conventions. -2. Keep omitted visibility backward-compatible for now. -3. Compile only the subset we can model safely today. -4. Reject unsupported patterns at compile time rather than risk silent miscompilation. -5. Carry enough artifact/runtime/debug metadata to make BCH-function contracts inspectable and testable. - -## Language And Compiler Model - -### Visibility - -Examples: - -```solidity -contract Example() { - function spend(int x) public { - require(validate(x)); - } - - function validate(int value) internal { - require(value == 7); - } -} -``` - -Effects: - -- `public` functions remain ABI entrypoints -- `internal` functions are hidden from the ABI -- internal/public classification no longer depends on prefixes or suffixes - -### Reachability - -The compiler computes the invoked-function closure starting from public entrypoints only. - -That means: - -- reachable internal/public invocations are compiled into the BCH function table -- dead internal-only call chains are ignored -- helper reachability is anchored to real external entrypoints, not arbitrary dead call edges - -### Code generation - -Lowering works like this: - -1. Public ABI dispatch stays on the existing CashScript path. -2. Reachable invoked functions are compiled into separate bytecode fragments. -3. Those fragments are registered using `OP_DEFINE`. -4. User-defined calls emit the function index plus `OP_INVOKE`. - -## Safety Boundaries - -The implementation intentionally rejects patterns that are not sound under the current execution/signing/debug model. - -### Disallowed in internally invoked functions - -- `checkSig()` -- `checkMultiSig()` -- `checkDataSig()` -- references to constructor parameters -- direct recursion -- mutual recursion - -### Why those restrictions exist - -These are conservative safety/tooling restrictions, not BCH opcode impossibilities. - -- Signature operations are blocked because nested invocation frames complicate signing/debug/template attribution, and the current SDK/compiler pipeline should not claim stronger guarantees than it can model. -- Constructor-parameter capture is blocked because it creates closure-like hidden dependencies on outer contract state, which is easy to misunderstand and harder to audit safely. -- Recursion is blocked because the current function-table model is intentionally acyclic and bounded. - -The design principle is straightforward: if the compiler cannot prove the lowering is safe and predictable, it rejects the pattern. - -## Artifact, Target, And SDK Integration - -### Artifact behavior - -Artifacts now: - -- include only `public` functions in the ABI -- record BCH-function contracts with `compiler.target` at least `BCH_2026_05` -- reject explicit lower targets when function opcodes are required -- still allow explicit `BCH_SPEC` - -This prevents artifacts from claiming an older VM target while containing BCH 2026 function opcodes. - -### SDK/runtime behavior - -The SDK validates target compatibility against provider VM-target metadata and continues to expose only public ABI methods. - -This means: - -- internal functions are not available as unlock methods -- BCH-function artifacts fail fast if paired with incompatible provider expectations -- mixed-target local testing is harder to misconfigure silently - -## Debugging And Tooling - -Nested invoked-function debugging is materially better than the initial branch state. - -The current patch adds: - -- frame-aware debug metadata on logs, stack items, and require statements -- frame-aware matching in SDK debug decoding -- better root-locking-script slicing when nested invoked frames are present -- fallback attribution for nested internal-function require failures -- explicit source locations for require statements - -Practically, this means `console.log` and failing `require(...)` inside internally invoked functions can now be surfaced with the internal frame’s source line and statement, rather than collapsing everything onto the public wrapper. - -## Return-Value Semantics - -Internal functions are currently modeled as boolean-style reusable subroutines. - -In practice: - -- user-defined calls are used naturally in boolean positions like `require(validate(x));` -- invoked functions currently compile to leave a single success value on the stack - -So this is “general internal functions” in visibility/reuse terms, but not yet a full arbitrary typed-return function system. - -## Tests And Validation - -Coverage was expanded in several layers. - -### Compiler / parser / codegen - -- explicit `public` / `internal` parsing -- comments/newlines around visibility -- no more underscore-based visibility inference -- omitted-visibility warning behavior -- ABI filtering for internal functions -- reachability-based `OP_DEFINE` emission -- rejection of pre-2026 explicit targets for BCH-function contracts -- debug metadata coverage for frame bytecode and require locations - -### SDK / runtime / debugging - -- internal-function contracts execute under `BCH_2026_05` -- nested internal call chains work across public entrypoints -- zero-argument invoked functions work -- public-to-public internal invocation preserves ABI visibility -- nested internal logs are attributed to the internal source line -- nested internal require failures are attributed to the internal failing statement - -### Example project coverage - -The testing-suite example now includes a dedicated internal-functions contract and test. Existing generic example contracts were intentionally left in their original form so this patch stays scoped to the new functions feature rather than rewriting the broader examples set before acceptance. - -### Validation run status - -The working tree has been validated with: - -- `cd packages/utils && yarn run build` -- `cd packages/cashc && yarn run build` -- `cd packages/cashscript && yarn run build` -- `yarn vitest run packages/cashc/test/compiler/compiler.test.ts` -- `cd packages/cashc && yarn vitest run test/generation/generation.test.ts` -- `yarn vitest run packages/cashc/test/ast/AST.test.ts` -- `yarn vitest run packages/cashscript/test/debugging.test.ts` -- `yarn vitest run packages/cashscript/test/Contract.test.ts` -- `cd examples/testing-suite && yarn test` - -## Documentation And DX Changes +## Review Focus -Docs were updated to reflect the current branch rather than the earlier helper-naming model. +The highest-value review questions for the CashScript team are: -The user-facing story is now: +1. Is the `contract` / `library` split the right long-term source model? +2. Are the helper restrictions conservative enough for a release branch? +3. Is canonicalization by resolved library identity the right import behavior? +4. Is the helper-frame debug model sufficiently explicit and trustworthy? +5. Is the branch scoped and shaped correctly relative to the current `next` baseline? -- explicit `public` / `internal` -- omitted visibility remains backward-compatible but warns -- BCH 2026 is required for function-opcode contracts -- internal-function restrictions are documented explicitly +## Code Areas To Review -Functions-specific examples and happy-path fixtures were updated to use explicit visibility where helpful, while older generic example contracts were intentionally left alone to keep branch scope tight. +The most important implementation files are: -## Remaining Tradeoffs And Caveats +- `packages/cashc/src/compiler.ts` +- `packages/cashc/src/imports.ts` +- `packages/cashc/src/generation/GenerateTargetTraversal.ts` +- `packages/cashscript/src/debugging.ts` +- `packages/cashscript/src/Errors.ts` +- `packages/utils/src/artifact.ts` -This patch looks production-ready enough for maintainer review, but there are still some intentional transitional choices: +The most important tests are: -- omitted visibility still defaults to `public` -- omitted-visibility warnings are transitional rather than the final UX -- some older compiler error fixtures still trigger warnings because they intentionally preserve legacy-style source -- visibility parsing still uses compiler preprocessing rather than being grammar-native +- `packages/cashc/test/compiler/compiler.test.ts` +- `packages/cashc/test/generation/generation.test.ts` +- `packages/cashscript/test/debugging.test.ts` -These are mostly rollout and ergonomics concerns, not correctness/safety blockers. +## Validation Summary -## Review Focus +This branch was brought to a clean state with: -The highest-value maintainer questions are: +- root `yarn test` +- root `yarn build` +- root `yarn lint` -1. Is explicit visibility the right language direction versus naming conventions? -2. Are the current internal-function restrictions conservative enough for merge? -3. Is the BCH 2026 target enforcement strict enough and correctly placed? -4. Is the boolean-style internal return model acceptable for this iteration? -5. Is the preprocessing-based visibility parser acceptable for now, or should grammar-native syntax be required before merge? +It also includes adversarial regressions around: -## Bottom Line +- nested helper failure attribution +- ambiguous helper-frame identity +- shared transitive import graphs +- duplicate helper names across imported libraries -The patch is now much stronger than the earlier helper-function branch state: +## Showcase Examples -- visibility is explicit -- ABI boundaries are coherent -- reachability is correct -- target metadata cannot lie -- nested debug attribution is materially improved -- examples/tests/docs are aligned with the intended design +For maintainers reviewing the feature with concrete source examples: -The remaining concerns are mostly about rollout polish and syntax implementation strategy, not about core semantic safety. +- local internal helpers: + - `examples/helper-functions.cash` + - `examples/helper-functions.ts` +- imported helper libraries: + - `examples/imported-helper-functions.cash` + - `examples/math-helpers.cash` + - `examples/parity-helpers.cash` + - `examples/imported-helper-functions.ts` diff --git a/docs/proposals/functions-implementation-spec.md b/docs/proposals/functions-implementation-spec.md new file mode 100644 index 00000000..cb73af9f --- /dev/null +++ b/docs/proposals/functions-implementation-spec.md @@ -0,0 +1,295 @@ +# BCH Functions Implementation Spec + +## Purpose + +This document describes the current implementation of BCH function support on top of the current `next` branch. + +It is written as an implementation/spec handoff for maintainers who want to review: + +- source-level language semantics +- compiler lowering strategy +- helper import behavior +- debug/runtime metadata behavior +- safety boundaries and explicit non-goals + +This document describes the implementation as it exists in the branch, not just an aspirational plan. + +## Scope + +Relative to current `next`, the implementation adds: + +- explicit function visibility inside `contract`: `public` and `internal` +- first-class lowering of helper functions to BCH `OP_DEFINE` / `OP_INVOKE` +- `library` as a first-class non-spendable top-level container +- support for local helper functions and imported helper functions +- transitive `contract -> library -> library` imports +- helper-aware debug metadata and nested-frame attribution in the SDK + +The implementation intentionally does not add: + +- standalone spendable imported modules +- imports of `contract` files +- recursive helper call graphs +- signature-check builtins inside internally invoked helpers +- contract-constructor capture inside helper functions + +## Source-Level Model + +### `contract` + +A `contract` is the spendable top-level unit. + +A contract may contain: + +- `public` functions +- `internal` functions + +`public` functions: + +- appear in the ABI +- are exposed through the SDK as unlock methods +- act as external spend entrypoints + +`internal` functions: + +- do not appear in the ABI +- are not directly exposed as unlock methods +- are compiled as helper frames and may be invoked from other functions + +Inside a `contract`, omitted visibility is still accepted and treated as `public`. + +### `library` + +A `library` is a non-spendable top-level unit. + +A library: + +- cannot declare constructor parameters +- cannot compile to a spendable artifact +- cannot define public entrypoints +- may only define `internal` helper functions + +This is the main container-level guardrail that prevents imported helper files from becoming independent spend surfaces. + +## Import Model + +### Allowed forms + +Supported forms are: + +- `contract -> library` +- `library -> library` + +Imports are: + +- compile-time only +- relative-path only +- namespace-qualified at source level + +Example: + +```cash +import "./math.cash" as Math; + +contract Vault() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +``` + +Imported library: + +```cash +library MathHelpers { + function isEven(int value) internal { + require(value % 2 == 0); + } +} +``` + +### Forbidden forms + +The compiler rejects: + +- importing a `contract` +- importing a library with `public` functions +- duplicate aliases at the same scope +- circular transitive library imports +- namespaced external helper references from inside a library +- library references to non-library local functions + +### Canonicalization strategy + +Transitive libraries are canonicalized by resolved file identity rather than duplicated per alias path. + +This matters for diamond graphs and shared leaves: + +- the same resolved library file is compiled once +- helper names are mangled from canonical library identity, not alias-path repetition +- shared leaves are not duplicated just because they are reachable from multiple branches + +That decision was made to avoid hidden bytecode/opcount inflation in reusable helper graphs. + +## Compiler Model + +### Entrypoints vs helper frames + +The compiler distinguishes between: + +- public dispatcher entrypoints +- helper frames + +Helper frames may come from: + +- local `internal` functions +- local `public` functions that are also invoked internally +- imported library `internal` functions + +### Reachability + +Reachability is computed from public entrypoints. + +This means: + +- unreachable internal-only call chains are not emitted +- unreachable imported helper chains are not emitted +- the BCH function table is driven by actual entrypoint reachability rather than by all syntactically declared helpers + +### Lowering + +Reachable helper functions are compiled to separate bytecode fragments and emitted with: + +- `OP_DEFINE` +- `OP_INVOKE` + +Public dispatcher flow remains on the normal ABI dispatch path. + +Invoked public functions reuse the same helper-frame body rather than duplicating a second compiled body just for internal invocation. + +### VM target enforcement + +If helper opcodes are required, the compiler: + +- upgrades inferred target requirements to at least `BCH_2026_05` +- rejects explicitly lower targets +- still permits `BCH_SPEC` + +This prevents artifacts from claiming an older VM target while containing BCH function opcodes. That enforcement is part of the functions delta, not baseline `next` behavior. + +## Safety Restrictions + +### Helper-function restrictions + +The implementation intentionally rejects: + +- direct recursion +- mutual recursion +- signature-check builtins inside internally invoked functions +- direct constructor-parameter capture inside helper functions + +These are conservative restrictions introduced by the functions implementation because the current compiler/runtime/debugging model can defend these boundaries cleanly. + +### Why these restrictions exist + +The guiding rule is: + +- if the compiler/runtime cannot model a helper pattern safely and inspectably, it should reject it rather than guess + +Specific reasons: + +- recursion complicates bounded codegen and debug semantics +- signature-check builtins in nested helper frames complicate signing and runtime attribution +- constructor capture makes helper behavior less explicit and less auditable + +## Debugging And Artifact Model + +### Frame-aware metadata + +Artifacts now carry richer debug metadata for helper-aware execution. + +The implementation includes: + +- root-frame debug metadata +- helper frame metadata in `debug.frames` +- per-log frame metadata +- per-stack-item frame metadata +- per-require frame metadata +- source provenance for imported helper frames + +Developer-facing debug output prefers readable source basenames, while internal metadata keeps full path provenance where available. + +### Frame identity + +Helper frames are identified by explicit frame ids plus frame bytecode. + +The implementation also includes a compiler safeguard: + +- if two distinct helper functions would compile to identical helper bytecode, compilation fails + +That guard exists because runtime frame attribution is unsafe if two helper frames are bytecode-identical. + +### Nested failure attribution + +The SDK now resolves helper logs and helper failures against the active frame. + +Implemented behavior: + +- internal helper logs resolve to the helper source line +- helper `require(...)` failures resolve to the helper source line and statement +- nested non-`require` helper failures resolve as evaluation failures, not as guessed require failures +- final verify failures caused by a nested helper final `require(...)` are attributed back to that helper require when this can be proven safely + +The important audit constraint is: + +- the SDK should not speculate broadly about require attribution +- it should only attribute a nested require when there is a safe frame-local match + +## Important Edge Cases Covered + +The branch includes coverage for: + +- local internal helpers reachable from public functions +- unreachable helper chains not being emitted +- public-to-public invocation while preserving ABI exposure +- shared helper-frame reuse for invoked public functions +- imported library helpers lowering to BCH function opcodes +- transitive imports +- shared-leaf canonicalization in diamond-style graphs +- overlapping helper names across imported libraries +- internal helper logs +- internal helper require failures +- nested non-require helper failures not being misattributed to earlier require statements +- ambiguous identical helper bytecode rejection + +## Behavior That Is Intentionally Transitional + +For `library`, the implementation is strict: + +- explicit `internal` is required +- `public` is rejected + +## Reviewer Checklist + +The highest-value review questions for the main CashScript team are: + +1. Is `contract` + `library` the right long-term source model? +2. Are the current helper restrictions conservative enough for merge? +3. Is explicit `internal` in `library` the right strictness level? +4. Is canonicalization by resolved library identity the right import strategy? +5. Is the helper debug model sufficiently explicit and stable for external users? +6. Is the current `contract` visibility behavior acceptable for the first functions release line? + +## Bottom Line + +The current implementation treats BCH function opcodes as first-class compiler/runtime behavior rather than an afterthought. + +The central semantics are: + +- spend surface belongs to `contract` +- reusable helper surface belongs to `internal` functions +- imported helper code belongs to `library` +- helper execution is unified whether the helper is local or imported +- imported libraries cannot become independent spend paths + +That is the design this branch implements and the basis on which it should be reviewed. diff --git a/docs/proposals/functions-security-and-release-review.md b/docs/proposals/functions-security-and-release-review.md new file mode 100644 index 00000000..d6b2630d --- /dev/null +++ b/docs/proposals/functions-security-and-release-review.md @@ -0,0 +1,206 @@ +# BCH Functions Security And Release Review + +## Purpose + +This document records the security posture, review findings addressed in the branch, and the production-readiness checks that were used to harden the BCH functions implementation. + +It is intended for maintainers doing scrutiny before merge or release. + +## Threat Model + +The implementation was reviewed against these failure classes: + +- accidental creation of new spend paths +- imported helpers behaving like independent contracts +- helper-frame debugging ambiguity +- wrong source attribution for nested helper failures +- hidden bytecode growth from transitive imports +- helper-name collisions across import graphs +- dead helper code being emitted unnecessarily + +## Main Guardrails + +### Spend-surface guardrails + +Implemented constraints: + +- only `contract` is spendable +- `library` cannot compile as an artifact +- `library` cannot declare constructor parameters +- `library` cannot expose `public` functions +- imported `contract` files are rejected + +These are the primary protections against imported helpers becoming independent authorization surfaces. + +### Helper-safety guardrails + +Implemented constraints: + +- helper functions cannot recurse +- helper functions cannot mutually recurse +- helper functions cannot use signature-check builtins +- helper functions cannot capture contract constructor parameters + +These are conservative restrictions intended to keep helper execution explicit and auditable. + +### Debugging-integrity guardrails + +Implemented protections: + +- helper frames carry explicit frame ids and frame bytecode +- compiler rejects contracts whose helper frames would be bytecode-identical +- nested helper logs and helper requires resolve against helper-frame metadata +- nested non-require failures are not broadly guessed as require failures + +This matters because ambiguous debugging in covenant tooling is not just a DX issue; it can hide real behavioral faults during review. + +## Audit Findings Addressed + +### 1. Ambiguous helper-frame identity + +Original concern: + +- helper-frame identity was too dependent on frame bytecode +- two different helpers with identical bytecode could cause incorrect log/error attribution + +Resolution: + +- helper debug metadata now includes explicit frame ids +- runtime matching uses helper-frame metadata +- compiler rejects helper-frame bytecode collisions across distinct helpers + +Result: + +- helper attribution is no longer allowed to proceed in an ambiguous bytecode-collision state + +### 2. Shared transitive-library duplication + +Original concern: + +- the same imported shared leaf could be duplicated per import path +- that would inflate bytecode and opcount unexpectedly in diamond graphs + +Resolution: + +- transitive libraries are canonicalized by resolved file identity +- shared leaves are compiled once and reused + +Result: + +- reusable helper graphs do not silently grow script size just because they are reached through more than one alias path + +### 3. Misattribution of nested non-require failures + +Original concern: + +- nested helper failures could be misreported as if an earlier helper `require(...)` failed + +Resolution: + +- speculative broad fallback was removed +- nested helper final-verify attribution now only maps to a helper require when that mapping can be made safely +- explicit regression coverage now checks that a later non-require helper failure is not blamed on an earlier helper require + +Result: + +- nested failure reporting is more honest and safer for auditing + +## Adversarial Cases Covered + +The branch now includes targeted coverage for failure-oriented cases, not only happy paths. + +### Import graph adversarial cases + +- duplicate top-level aliases rejected +- duplicate helper names across imported libraries supported safely +- circular transitive imports rejected +- shared-leaf transitive libraries canonicalized +- imported namespaced external helper references rejected +- imported references to non-library local functions rejected + +### Helper execution adversarial cases + +- unreachable helper chains not emitted +- ambiguous identical helper bytecode rejected at compile time +- internal helper logs attributed to helper source line +- internal helper require failures attributed to helper source line and statement +- nested non-require helper failures stay evaluation failures + +### Container and visibility adversarial cases + +- omitted library visibility is rejected +- public functions inside a library are rejected +- imported contracts are rejected + +## Residual Tradeoffs + +These are not open bugs in the branch, but they are still design choices worth explicit maintainer review. + +### Explicit `internal` in `library` + +Current behavior: + +- `library` functions must explicitly declare `internal` + +Tradeoff: + +- stricter and clearer for review +- slightly more verbose than treating `library` as implicitly helper-only + +### Helper restrictions + +Current behavior: + +- helper restrictions are intentionally conservative + +Tradeoff: + +- safer for release +- narrower than everything BCH opcodes could theoretically support + +## Production-Readiness Verification + +The branch was validated with: + +- full root `yarn test` +- full root `yarn build` +- full root `yarn lint` + +It was also hardened with focused suites around: + +- compiler semantics +- generation +- AST/source round-tripping +- helper-aware debugging + +## Recommended Maintainer Review Focus + +If the main CashScript team wants to review this efficiently, the highest-value areas are: + +1. `packages/cashc/src/compiler.ts` +2. `packages/cashc/src/imports.ts` +3. `packages/cashc/src/generation/GenerateTargetTraversal.ts` +4. `packages/cashscript/src/debugging.ts` +5. `packages/cashscript/src/Errors.ts` +6. `packages/utils/src/artifact.ts` +7. `packages/cashscript/test/fixture/debugging/debugging_contracts.ts` + +And the highest-value tests to inspect are: + +1. `packages/cashc/test/compiler/compiler.test.ts` +2. `packages/cashc/test/generation/generation.test.ts` +3. `packages/cashscript/test/debugging.test.ts` + +## Bottom Line + +This branch is not just a feature spike anymore. + +It has: + +- a defined spend-surface model +- explicit imported-helper constraints +- canonical transitive imports +- nested helper-aware debugging +- adversarial tests aimed at ways to break helper attribution and helper import safety + +That is the implementation state maintainers should review. diff --git a/docs/proposals/functions-vs-next-review.md b/docs/proposals/functions-vs-next-review.md new file mode 100644 index 00000000..bc8d6538 --- /dev/null +++ b/docs/proposals/functions-vs-next-review.md @@ -0,0 +1,136 @@ +# BCH Functions Vs Current `next` + +## Purpose + +This document summarizes what this branch adds relative to the current team-maintained `next` branch, and how those additions relate to the team’s current direction. + +It is intentionally scoped to the delta that matters for review. + +## Current Base + +Current reviewed base: + +- `next` at `84d858f` + +This branch is built on top of that base through the existing merge into `feature/functions-only`, then the later `library` and hardening work. + +## What `next` Already Has + +Current `next` already includes: + +- the newer loop implementation work +- the current SDK/network changes from the team +- the baseline parser/compiler/runtime behavior without BCH helper functions + +That means this branch should be reviewed as: + +- functions work layered on top of current `next` +- not as an alternative to the team’s loop work + +## What This Branch Adds Beyond `next` + +### 1. Contract-local helper functions + +This branch adds explicit function classification inside contracts: + +- `public` +- `internal` + +And lowers reachable helper calls through: + +- `OP_DEFINE` +- `OP_INVOKE` + +This is the core language/compiler change. + +### 2. Non-spendable `library` containers + +This branch adds a second top-level container: + +- `library` + +`library` exists to make imported helper code non-spendable by construction. + +That is a deliberate design choice from our local work: + +- imported helper files should complement a contract +- imported helper files should not define their own spend surface + +### 3. Transitive helper imports + +This branch adds: + +- `contract -> library` +- `library -> library` + +with: + +- cycle rejection +- alias checks +- canonicalization by resolved file identity + +### 4. Helper-aware debugging and artifact metadata + +This branch adds helper-frame metadata and SDK-side helper-aware attribution for: + +- logs +- require failures +- nested helper evaluation failures + +This matters because helper functions without usable debugging are not reviewable or production-ready enough for other teams. + +### 5. Conservative helper restrictions + +This branch explicitly rejects helper patterns that are currently too risky or too underspecified: + +- recursion +- mutual recursion +- signature-check builtins in helpers +- contract-constructor capture in helpers + +## How This Fits The Team’s Current Direction + +### Loops + +The main team is already carrying the loop work on `next`. + +Our branch does not try to compete with that work. It assumes those loop changes are the baseline and focuses on the other major BCH 2026 opcode area: + +- user-defined function frames + +### Compiler direction + +The branch follows the same broader philosophy we discussed locally: + +- BCH opcodes should be first-class compiler targets +- source constructs should map cleanly onto those opcodes +- the compiler should make spend surfaces and helper surfaces explicit + +In that sense, this work is aligned with the team’s broader compiler direction rather than being an unrelated experiment. + +### Safety posture + +The strongest local design decision was: + +- helper reuse should be enabled +- hidden new spend surfaces should not be enabled + +That is why `library` is non-spendable and why helper restrictions are deliberately conservative. + +## Main Things The Team Should Scrutinize + +1. Whether `library` is the right source-level container for imported helper code. +2. Whether explicit `public` / `internal` is the right contract-level function model. +3. Whether canonicalizing shared transitive libraries by resolved file identity is the right import strategy. +4. Whether the current helper restrictions are conservative in the right places. +5. Whether the helper debug metadata is stable enough for downstream tooling and users. + +## Bottom Line + +Relative to current `next`, this branch is primarily: + +- BCH helper-function support +- non-spendable imported helper libraries +- helper-aware debugging and hardening + +It is not a competing loops branch. It is the local functions track, updated to fit the team’s current `next` baseline. diff --git a/docs/proposals/internal-functions-review.md b/docs/proposals/internal-functions-review.md index edfa20ae..14832245 100644 --- a/docs/proposals/internal-functions-review.md +++ b/docs/proposals/internal-functions-review.md @@ -53,37 +53,7 @@ The practical implication is: ## Findings -### 1. Omitted visibility defaults to `public` - -Severity: High - -Relevant code: - -- `packages/cashc/src/compiler.ts` -- `packages/cashc/test/compiler/compiler.test.ts` - -The current preprocessing logic treats omitted visibility as `public` and only emits a warning. - -That is much riskier now that helper functions exist. A developer who forgets to mark a helper as `internal` accidentally creates a new externally callable entrypoint. - -Why this matters: - -- the helper may bypass assumptions enforced only in the intended top-level function -- the helper may become a user-spend path even though it was written as support logic -- warnings are too weak for consensus-critical contract code - -Current design decision: - -- do not force an immediate explicit-visibility migration for all older contracts -- instead, use first-class `library` containers as the stronger boundary for imported helper files - -Recommendation: - -- keep this as a known hardening issue for normal contracts -- do not rely on omitted-visibility behavior for imported helper code -- consider a stricter compiler mode later for contracts that want explicit visibility guarantees - -### 2. Nested-frame debugging is still incomplete for non-`require` failures +### 1. Nested-frame debugging is still incomplete for non-`require` failures Severity: Medium @@ -108,7 +78,7 @@ Recommendation: - add frame-aware source resolution rather than only frame-aware `require`/log matching - add tests for internal helper failures that are not ordinary `require(...)` failures -### 3. Locking-script start detection in debugging is brittle +### 2. Locking-script start detection in debugging is brittle Severity: Medium @@ -132,7 +102,7 @@ Recommendation: - keep the heuristic fallback only as a last resort - add tests where nested invoked frames also begin at `ip === 0` -### 4. Public-to-public invocation duplicates function bodies +### 3. Public-to-public invocation duplicates function bodies Severity: Medium diff --git a/examples/README.md b/examples/README.md index d4e5f9f0..61493359 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,7 +3,11 @@ This folder contains a number of example CashScript contracts to show off its fu The "Hello World" of cash contracts is defining the P2PKH pattern inside a cash contract, which can be found under [`p2pkh.cash`](/examples/p2pkh.cash). Its usage can be found under [`p2pkh.ts`](/examples/p2pkh.ts). -For BCH internal functions compiled via `OP_DEFINE` / `OP_INVOKE`, see [`helper-functions.cash`](/examples/helper-functions.cash) and [`helper-functions.ts`](/examples/helper-functions.ts). This example is specific to the BCH functions proposal and configures the mock provider for `BCH_2026_05`. +For BCH internal helper functions compiled via `OP_DEFINE` / `OP_INVOKE`, see [`helper-functions.cash`](/examples/helper-functions.cash) and [`helper-functions.ts`](/examples/helper-functions.ts). + +For imported helper libraries, including a transitive `library -> library` helper chain, see [`imported-helper-functions.cash`](/examples/imported-helper-functions.cash), [`math-helpers.cash`](/examples/math-helpers.cash), [`parity-helpers.cash`](/examples/parity-helpers.cash), and [`imported-helper-functions.ts`](/examples/imported-helper-functions.ts). + +Both functions examples are specific to the BCH functions proposal and configure the mock provider for `BCH_2026_05`. ## Running the examples To run the examples, clone this repository and navigate to the `examples/` directory. Since the examples depend on the SDK, be sure to run `yarn` inside the `examples/` directory, which installs all required packages. diff --git a/examples/imported-helper-functions.cash b/examples/imported-helper-functions.cash new file mode 100644 index 00000000..e7fc5cc2 --- /dev/null +++ b/examples/imported-helper-functions.cash @@ -0,0 +1,13 @@ +pragma cashscript ^0.13.0; + +import "./math-helpers.cash" as Math; + +contract ImportedHelperFunctions() { + function spend(int value) public { + require(Math.isPositiveEven(value)); + } + + function spendPlusTwo(int value) public { + require(Math.isPositiveEven(value + 2)); + } +} diff --git a/examples/imported-helper-functions.ts b/examples/imported-helper-functions.ts new file mode 100644 index 00000000..6b19450a --- /dev/null +++ b/examples/imported-helper-functions.ts @@ -0,0 +1,24 @@ +import { stringify } from '@bitauth/libauth'; +import { compileFile } from 'cashc'; +import { Contract, MockNetworkProvider, randomUtxo, TransactionBuilder, VmTarget } from 'cashscript'; +import { URL } from 'url'; + +const artifact = compileFile(new URL('imported-helper-functions.cash', import.meta.url)); +const provider = new MockNetworkProvider({ vmTarget: VmTarget.BCH_2026_05 }); +const contract = new Contract(artifact, [], { provider }); + +provider.addUtxo(contract.address, randomUtxo()); + +const [contractUtxo] = await contract.getUtxos(); +if (!contractUtxo) throw new Error('No contract UTXO found'); + +console.log('contract address:', contract.address); +console.log('public ABI functions:', artifact.abi.map((func) => func.name)); +console.log('compiler target:', artifact.compiler.target); + +const tx = await new TransactionBuilder({ provider }) + .addInput(contractUtxo, contract.unlock.spend(8n)) + .addOutput({ to: contract.address, amount: 10_000n }) + .send(); + +console.log('transaction details:', stringify(tx)); diff --git a/examples/math-helpers.cash b/examples/math-helpers.cash new file mode 100644 index 00000000..18110ce3 --- /dev/null +++ b/examples/math-helpers.cash @@ -0,0 +1,10 @@ +pragma cashscript ^0.13.0; + +import "./parity-helpers.cash" as Parity; + +library MathHelpers { + function isPositiveEven(int value) internal { + require(value > 0); + require(Parity.isEven(value)); + } +} diff --git a/examples/parity-helpers.cash b/examples/parity-helpers.cash new file mode 100644 index 00000000..2a032490 --- /dev/null +++ b/examples/parity-helpers.cash @@ -0,0 +1,7 @@ +pragma cashscript ^0.13.0; + +library ParityHelpers { + function isEven(int value) internal { + require(value % 2 == 0); + } +} diff --git a/packages/cashc/src/ast/AST.ts b/packages/cashc/src/ast/AST.ts index e845f6d7..bc2504f4 100644 --- a/packages/cashc/src/ast/AST.ts +++ b/packages/cashc/src/ast/AST.ts @@ -37,6 +37,8 @@ export class SourceFileNode extends Node { export class ContractNode extends Node implements Named { symbolTable?: SymbolTable; + sourceCode?: string; + sourceFile?: string; constructor( public name: string, @@ -56,6 +58,8 @@ export class FunctionDefinitionNode extends Node implements Named { symbolTable?: SymbolTable; opRolls: Map = new Map(); calledFunctions: Set = new Set(); + sourceCode?: string; + sourceFile?: string; constructor( public name: string, diff --git a/packages/cashc/src/ast/Location.ts b/packages/cashc/src/ast/Location.ts index a3054606..90b2afd7 100644 --- a/packages/cashc/src/ast/Location.ts +++ b/packages/cashc/src/ast/Location.ts @@ -2,7 +2,7 @@ import type { ParserRuleContext, Token } from 'antlr4'; import { LocationI } from '@cashscript/utils'; export class Location implements LocationI { - constructor(public start: Point, public end: Point) {} + constructor(public start: Point, public end: Point, public sourceFile?: string) {} static fromCtx(ctx: ParserRuleContext): Location { const stop = ctx.stop?.text ? ctx.stop : ctx.start; @@ -27,7 +27,7 @@ export class Location implements LocationI { const start = new Point(object.start.line, object.start.column); const end = new Point(object.end.line, object.end.column); - return new Location(start, end); + return new Location(start, end, object.sourceFile); } text(code: string): string { diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index 9310f3a4..25022ffb 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -1,11 +1,12 @@ -import { CharStream, CommonTokenStream } from 'antlr4'; +import { CharStream, CommonTokenStream, Token } from 'antlr4'; import { binToHex } from '@bitauth/libauth'; import { Artifact, CompilerOptions, generateSourceMap, generateSourceTags, optimiseBytecode, optimiseBytecodeOld, scriptToAsm, scriptToBytecode, sourceMapToLocationData } from '@cashscript/utils'; import fs, { PathLike } from 'fs'; import { fileURLToPath } from 'url'; import { generateArtifact } from './artifact/Artifact.js'; -import { Ast } from './ast/AST.js'; +import { Ast, ContractNode, FunctionDefinitionNode, Node } from './ast/AST.js'; import AstBuilder from './ast/AstBuilder.js'; +import AstTraversal from './ast/AstTraversal.js'; import ThrowingErrorListener from './ast/ThrowingErrorListener.js'; import GenerateTargetTraversal from './generation/GenerateTargetTraversal.js'; import CashScriptLexer from './grammar/CashScriptLexer.js'; @@ -16,8 +17,9 @@ import EnsureFinalRequireTraversal from './semantic/EnsureFinalRequireTraversal. import EnsureInvokedFunctionsSafeTraversal from './semantic/EnsureInvokedFunctionsSafeTraversal.js'; import EnsureContainerSemanticsTraversal from './semantic/EnsureContainerSemanticsTraversal.js'; import { FunctionVisibility } from './ast/Globals.js'; +import { Point } from './ast/Location.js'; import { NonSpendableCompilationError, ParseError } from './Errors.js'; -import { ImportResolver, preprocessImports } from './imports.js'; +import { ImportResolver, ImportedFunctionProvenance, preprocessImports } from './imports.js'; export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = { enforceFunctionParameterTypes: true, @@ -41,11 +43,15 @@ export function compileString(code: string, compilerOptions: CompileOptions = {} const { sourcePath, resolveImport, ...serialisableCompilerOptions } = compilerOptions; const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...serialisableCompilerOptions }; const importedCode = preprocessImports(code, { sourcePath, resolveImport }); - const preprocessed = preprocessFunctionVisibility(importedCode.code); + const preprocessed = preprocessFunctionVisibility(importedCode.code, mergedCompilerOptions); emitVisibilityWarnings(preprocessed.omittedPublicFunctions); // Lexing + parsing let ast = parseCodeFromPreprocessed(preprocessed); + ast = applySourceProvenance(ast, importedCode.functionProvenance, { + source: code, + ...(sourcePath ? { sourceFile: normaliseSourcePath(sourcePath) } : {}), + }); // Semantic analysis ast = ast.accept(new SymbolTableTraversal()) as Ast; @@ -61,6 +67,7 @@ export function compileString(code: string, compilerOptions: CompileOptions = {} // Code generation const traversal = new GenerateTargetTraversal(mergedCompilerOptions); ast = ast.accept(traversal) as Ast; + assertDistinctHelperFrameBytecode(traversal); const constructorParamLength = ast.contract.parameters.length; @@ -90,16 +97,34 @@ export function compileString(code: string, compilerOptions: CompileOptions = {} logs: optimisationResult.logs.map((log) => ({ ...log, frameBytecode: log.frameBytecode ?? rootFrameBytecode, + frameId: log.frameId ?? '__root__', + sourceFile: log.sourceFile ?? ast.contract.sourceFile, data: log.data.map((entry) => ( typeof entry === 'string' ? entry - : { ...entry, frameBytecode: entry.frameBytecode ?? log.frameBytecode ?? rootFrameBytecode } + : { + ...entry, + frameBytecode: entry.frameBytecode ?? log.frameBytecode ?? rootFrameBytecode, + frameId: entry.frameId ?? log.frameId ?? '__root__', + } )), })), requires: optimisationResult.requires.map((require) => ({ ...require, frameBytecode: require.frameBytecode ?? rootFrameBytecode, + frameId: require.frameId ?? '__root__', + sourceFile: require.sourceFile ?? ast.contract.sourceFile, })), + frames: [ + { + id: '__root__', + bytecode: rootFrameBytecode, + sourceMap: generateSourceMap(optimisationResult.locationData), + source: ast.contract.sourceCode ?? code, + ...(ast.contract.sourceFile ? { sourceFile: ast.contract.sourceFile } : {}), + }, + ...traversal.frames, + ], ...(sourceTags ? { sourceTags } : {}), }; @@ -114,7 +139,10 @@ export function compileFile(codeFile: PathLike, compilerOptions: CompileOptions export function parseCode(code: string, compilerOptions: Pick = {}): Ast { const importedCode = preprocessImports(code, compilerOptions); const preprocessed = preprocessFunctionVisibility(importedCode.code); - return parseCodeFromPreprocessed(preprocessed); + return applySourceProvenance(parseCodeFromPreprocessed(preprocessed), importedCode.functionProvenance, { + source: code, + ...(compilerOptions.sourcePath ? { sourceFile: normaliseSourcePath(compilerOptions.sourcePath) } : {}), + }); } function parseCodeFromPreprocessed(preprocessed: PreprocessedVisibilityResult): Ast { @@ -137,7 +165,7 @@ function parseCodeFromPreprocessed(preprocessed: PreprocessedVisibilityResult): return ast; } -function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResult { +function preprocessFunctionVisibility(code: string, compilerOptions: CompilerOptions = {}): PreprocessedVisibilityResult { const containerKind = getTopLevelContainerKind(code); const inputStream = new CharStream(code); const lexer = new CashScriptLexer(inputStream); @@ -166,6 +194,13 @@ function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResul const visibilityToken = visibleTokens[cursor]; if (visibilityToken?.text === FunctionVisibility.INTERNAL || visibilityToken?.text === FunctionVisibility.PUBLIC) { + if (containerKind === 'library' && visibilityToken.text === FunctionVisibility.PUBLIC) { + throw new ParseError( + `Library function '${functionNameToken.text}' must declare internal visibility`, + new Point(visibilityToken.line, visibilityToken.column), + ); + } + functionVisibilities.push(visibilityToken.text as FunctionVisibility); for (let index = visibilityToken.start; index <= visibilityToken.stop; index += 1) { mutableCode[index] = ' '; @@ -173,7 +208,21 @@ function preprocessFunctionVisibility(code: string): PreprocessedVisibilityResul continue; } - const defaultVisibility = containerKind === 'library' ? FunctionVisibility.INTERNAL : FunctionVisibility.PUBLIC; + if (containerKind === 'library') { + throw new ParseError( + `Library function '${functionNameToken.text}' must declare internal visibility`, + new Point(functionNameToken.line, functionNameToken.column), + ); + } + + if (compilerOptions.requireExplicitFunctionVisibility) { + throw new ParseError( + `Function '${functionNameToken.text}' must declare explicit visibility (public or internal)`, + new Point(functionNameToken.line, functionNameToken.column), + ); + } + + const defaultVisibility = FunctionVisibility.PUBLIC; functionVisibilities.push(defaultVisibility); if (defaultVisibility === FunctionVisibility.PUBLIC) { omittedPublicFunctions.push({ @@ -223,7 +272,84 @@ function emitVisibilityWarnings(omittedPublicFunctions: Array<{ name: string; li ); } -function getVisibleTokens(code: string) { +class SourceProvenanceTraversal extends AstTraversal { + private currentSource = this.rootSource; + private currentLineDelta = 0; + private currentColumnDelta = 0; + + constructor( + private functionProvenance: Map, + private rootSource: { source: string; sourceFile?: string }, + ) { + super(); + } + + visit(node: Node): Node { + if (node.location) { + node.location.start.line += this.currentLineDelta; + node.location.end.line += this.currentLineDelta; + node.location.start.column += this.currentColumnDelta; + node.location.end.column += this.currentColumnDelta; + node.location.sourceFile = this.currentSource.sourceFile; + } + + return super.visit(node); + } + + visitContract(node: ContractNode): Node { + node.sourceCode = this.rootSource.source; + node.sourceFile = this.rootSource.sourceFile; + return super.visitContract(node); + } + + visitFunctionDefinition(node: FunctionDefinitionNode): Node { + const previousSource = this.currentSource; + const previousLineDelta = this.currentLineDelta; + const previousColumnDelta = this.currentColumnDelta; + const provenance = this.functionProvenance.get(node.name); + + this.currentSource = provenance + ? { source: provenance.source, sourceFile: provenance.sourceFile } + : this.rootSource; + this.currentLineDelta = provenance ? provenance.originalStartLine - node.location.start.line : 0; + this.currentColumnDelta = provenance ? provenance.originalStartColumn - provenance.generatedStartColumn : 0; + node.sourceCode = this.currentSource.source; + node.sourceFile = this.currentSource.sourceFile; + + const result = super.visitFunctionDefinition(node); + + this.currentSource = previousSource; + this.currentLineDelta = previousLineDelta; + this.currentColumnDelta = previousColumnDelta; + return result; + } +} + +function applySourceProvenance( + ast: Ast, + functionProvenance: ImportedFunctionProvenance[], + rootSource: { source: string; sourceFile?: string }, +): Ast { + const provenanceMap = new Map(functionProvenance.map((entry) => [entry.mangledName, entry])); + return ast.accept(new SourceProvenanceTraversal(provenanceMap, rootSource)) as Ast; +} + +function assertDistinctHelperFrameBytecode(traversal: GenerateTargetTraversal): void { + const frameIdsByBytecode = new Map(); + + traversal.frames.forEach((frame) => { + const previousFrameId = frameIdsByBytecode.get(frame.bytecode); + if (previousFrameId && previousFrameId !== frame.id) { + throw new Error( + `Cannot compile helper functions '${previousFrameId}' and '${frame.id}' because they produce identical helper bytecode, which would make runtime debugging ambiguous.`, + ); + } + + frameIdsByBytecode.set(frame.bytecode, frame.id); + }); +} + +function getVisibleTokens(code: string): Token[] { const inputStream = new CharStream(code); const lexer = new CashScriptLexer(inputStream); const tokenStream = new CommonTokenStream(lexer); diff --git a/packages/cashc/src/generation/GenerateTargetTraversal.ts b/packages/cashc/src/generation/GenerateTargetTraversal.ts index 554478f1..5588bb44 100644 --- a/packages/cashc/src/generation/GenerateTargetTraversal.ts +++ b/packages/cashc/src/generation/GenerateTargetTraversal.ts @@ -75,6 +75,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { stack: string[] = []; consoleLogs: LogEntry[] = []; requires: RequireStatement[] = []; + frames: Array<{ id: string; bytecode: string; sourceMap: string; source: string; sourceFile?: string }> = []; sourceTags: SourceTagEntry[] = []; finalStackUsage: Record = {}; @@ -265,26 +266,46 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { traversal.output = asmToScript(scriptToAsm(traversal.output)); const frameBytecode = scriptToBytecode(traversal.output); const frameBytecodeHex = binToHex(frameBytecode); - traversal.annotateFrameDebugMetadata(frameBytecodeHex); + const frameId = `fn:${this.functionIndices.get(node.name)!}`; + traversal.annotateFrameDebugMetadata(frameBytecodeHex, frameId, node.sourceFile); + this.frames.push(...traversal.frames); + this.frames.push({ + id: frameId, + bytecode: frameBytecodeHex, + sourceMap: generateSourceMap(traversal.locationData), + source: node.sourceCode ?? '', + ...(node.sourceFile ? { sourceFile: node.sourceFile } : {}), + }); this.consoleLogs.push(...traversal.consoleLogs); this.requires.push(...traversal.requires); return frameBytecode; } - annotateFrameDebugMetadata(frameBytecode: string): void { + annotateFrameDebugMetadata(frameBytecode: string, frameId: string, sourceFile?: string): void { this.finalStackUsage = Object.fromEntries( - Object.entries(this.finalStackUsage).map(([name, usage]) => [name, { ...usage, frameBytecode }]), + Object.entries(this.finalStackUsage).map(([name, usage]) => [name, { + ...usage, + frameBytecode, + frameId, + }]), ); this.consoleLogs = this.consoleLogs.map((log) => ({ ...log, frameBytecode, + frameId, + ...(sourceFile ? { sourceFile } : {}), data: log.data.map((entry) => ( - typeof entry === 'string' ? entry : { ...entry, frameBytecode } + typeof entry === 'string' ? entry : { ...entry, frameBytecode, frameId } )), })); - this.requires = this.requires.map((require) => ({ ...require, frameBytecode })); + this.requires = this.requires.map((require) => ({ + ...require, + frameBytecode, + frameId, + ...(sourceFile ? { sourceFile } : {}), + })); } removeFinalVerifyFromFunction(functionBodyNode: Node): void { @@ -430,6 +451,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { line: node.location.start.line, message: node.message, location: node.location, + ...(this.currentFunction.sourceFile ? { sourceFile: this.currentFunction.sourceFile } : {}), }); this.popFromStack(); @@ -446,6 +468,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { line: node.location.start.line, message: node.message, location: node.location, + ...(this.currentFunction.sourceFile ? { sourceFile: this.currentFunction.sourceFile } : {}), }); this.popFromStack(); @@ -479,7 +502,12 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { return parameter.toString(); }); - this.consoleLogs.push({ ip, line, data }); + this.consoleLogs.push({ + ip, + line, + data, + ...(this.currentFunction.sourceFile ? { sourceFile: this.currentFunction.sourceFile } : {}), + }); return node; } @@ -628,7 +656,10 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal { node.parameters = this.visitList(node.parameters); if (node.identifier.definition?.definition instanceof FunctionDefinitionNode) { - this.emit(encodeInt(BigInt(this.functionIndices.get(node.identifier.name)!)), { location: node.location, positionHint: PositionHint.START }); + this.emit( + encodeInt(BigInt(this.functionIndices.get(node.identifier.name)!)), + { location: node.location, positionHint: PositionHint.START }, + ); this.pushToStack('(value)'); this.emit(Op.OP_INVOKE, { location: node.location, positionHint: PositionHint.END }); this.popFromStack(node.parameters.length + 1); diff --git a/packages/cashc/src/imports.ts b/packages/cashc/src/imports.ts index 69db2049..9a7150fe 100644 --- a/packages/cashc/src/imports.ts +++ b/packages/cashc/src/imports.ts @@ -25,8 +25,20 @@ export interface ImportPreprocessOptions { resolveImport?: ImportResolver; } +export interface ImportedFunctionProvenance { + mangledName: string; + generatedStartLine: number; + originalStartLine: number; + generatedStartColumn: number; + originalStartColumn: number; + source: string; + sourceFile?: string; +} + export interface ImportPreprocessResult { code: string; + sources: Array<{ source: string; sourceFile?: string }>; + functionProvenance: ImportedFunctionProvenance[]; } interface ImportDirective { @@ -39,14 +51,13 @@ interface ImportDirective { interface LibraryDefinition { name: string; body: string; -} - -interface LibraryTransformResult { - body: string; - functions: Set; + bodyStartLine: number; + source: string; + sourceFile?: string; } interface ParsedLibraryFile { + imports: ImportDirective[]; library: LibraryDefinition; pragmaConstraints: string[]; } @@ -61,6 +72,22 @@ interface Replacement { text: string; } +interface CanonicalLibraryRecord { + sourceId: string; + mangledPrefix: string; + body: string; + functions: Set; + provenance: ImportedFunctionProvenance[]; + sources: Array<{ source: string; sourceFile?: string }>; +} + +interface ImportContext { + orderedLibraries: CanonicalLibraryRecord[]; + canonicalLibraries: Map; + usedMangledNames: Set; + nextLibraryId: number; +} + const IDENTIFIER_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/; const BUILT_IN_STATEMENTS = new Set(['require']); const GLOBAL_FUNCTIONS = new Set( @@ -72,17 +99,27 @@ const GLOBAL_CLASSES = new Set(Object.values(Class)); export function preprocessImports(code: string, options: ImportPreprocessOptions = {}): ImportPreprocessResult { const importDirectives = parseTopLevelImports(code); + const rootSourceFile = options.sourcePath ? normaliseFilesystemPath(options.sourcePath) : undefined; + if (importDirectives.length === 0) { - return { code }; + return { + code, + sources: [{ source: code, ...(rootSourceFile ? { sourceFile: rootSourceFile } : {}) }], + functionProvenance: [], + }; } const mutableCode = code.split(''); - const transformedLibraries: string[] = []; const rootFunctionNames = collectFunctionNames(code); const usedAliases = new Set(); - const usedMangledNames = new Set(rootFunctionNames); - const transformedLibrariesByAlias = new Map(); + const context: ImportContext = { + orderedLibraries: [], + canonicalLibraries: new Map(), + usedMangledNames: new Set(rootFunctionNames), + nextLibraryId: 0, + }; + const transformedLibrariesByAlias = new Map(); for (const directive of importDirectives) { if (usedAliases.has(directive.alias)) { throw new InvalidImportDirectiveError(`Duplicate import alias '${directive.alias}' is not allowed.`); @@ -94,39 +131,108 @@ export function preprocessImports(code: string, options: ImportPreprocessOptions const transformedLibrary = preprocessImportedLibrary( directive, options, - usedMangledNames, + context, options.sourcePath, + [], ); transformedLibrariesByAlias.set(directive.alias, transformedLibrary); - transformedLibraries.push( - `\n // Imported from ${JSON.stringify(directive.specifier)} as ${directive.alias}\n${indentLibraryBody(transformedLibrary.body)}\n`, - ); } let flattenedCode = mutableCode.join(''); transformedLibrariesByAlias.forEach((library, alias) => { - flattenedCode = rewriteNamespacedCalls(flattenedCode, alias, library.functions); + flattenedCode = rewriteNamespacedCalls(flattenedCode, alias, library.functions, library.mangledPrefix); }); const contractCloseIndex = findRootCloseIndex(flattenedCode); - const injectedCode = transformedLibraries.join('\n'); + const injectedSections = context.orderedLibraries.map((library) => `\n${indentLibraryBody(library.body)}\n`); + const insertionBaseLine = countLines(flattenedCode.slice(0, contractCloseIndex)); + + let injectedLineOffset = 0; + const functionProvenance = context.orderedLibraries.flatMap((library, index) => { + const section = injectedSections[index]!; + const resolved = library.provenance.map((entry) => ({ + ...entry, + generatedStartLine: insertionBaseLine + injectedLineOffset + getFunctionGeneratedLine(entry.mangledName, section), + })); + injectedLineOffset += countLineBreaks(section) + 1; + return resolved; + }); + + const sources = dedupeSources([ + { source: code, ...(rootSourceFile ? { sourceFile: rootSourceFile } : {}) }, + ...context.orderedLibraries.flatMap((library) => library.sources), + ]); return { - code: `${flattenedCode.slice(0, contractCloseIndex)}${injectedCode}${flattenedCode.slice(contractCloseIndex)}`, + code: `${flattenedCode.slice(0, contractCloseIndex)}${injectedSections.join('\n')}${flattenedCode.slice(contractCloseIndex)}`, + sources, + functionProvenance, }; } function preprocessImportedLibrary( directive: ImportDirective, options: ImportPreprocessOptions, - usedMangledNames: Set, + context: ImportContext, fromPath?: string, -): LibraryTransformResult { + ancestry: string[] = [], +): CanonicalLibraryRecord { const resolvedImport = resolveImportSource(directive.specifier, options, fromPath); + const sourceId = getImportIdentity(resolvedImport, directive.specifier, fromPath); + if (ancestry.includes(sourceId)) { + throw new InvalidLibraryImportError( + `Circular library import detected: ${[...ancestry, sourceId].join(' -> ')}`, + ); + } + + const cached = context.canonicalLibraries.get(sourceId); + if (cached) { + return cached; + } + const parsedLibrary = parseLibraryFileWithPragmas(resolvedImport.source, resolvedImport.path ?? directive.specifier); validateLibraryPragmas(parsedLibrary.pragmaConstraints); - return transformLibrary(parsedLibrary.library, directive.alias, usedMangledNames); + const nestedLibrariesByAlias = new Map(); + const usedNestedAliases = new Set(); + for (const nestedImport of parsedLibrary.imports) { + if (usedNestedAliases.has(nestedImport.alias)) { + throw new InvalidImportDirectiveError(`Duplicate import alias '${nestedImport.alias}' is not allowed.`); + } + usedNestedAliases.add(nestedImport.alias); + nestedLibrariesByAlias.set( + nestedImport.alias, + preprocessImportedLibrary( + nestedImport, + options, + context, + resolvedImport.path ?? fromPath, + [...ancestry, sourceId], + ), + ); + } + + const mangledPrefix = `lib${context.nextLibraryId}`; + context.nextLibraryId += 1; + const transformed = transformLibrary( + parsedLibrary.library, + mangledPrefix, + context.usedMangledNames, + nestedLibrariesByAlias, + ); + + const record: CanonicalLibraryRecord = { + sourceId, + mangledPrefix, + body: transformed.body, + functions: transformed.functions, + provenance: transformed.provenance, + sources: transformed.sources, + }; + + context.canonicalLibraries.set(sourceId, record); + context.orderedLibraries.push(record); + return record; } function resolveImportSource(specifier: string, options: ImportPreprocessOptions, fromPath?: string): ResolvedImport { @@ -183,8 +289,13 @@ function parseTopLevelImports(code: string): ImportDirective[] { const aliasToken = tokens[cursor + 3]; const semicolonToken = tokens[cursor + 4]; - if (!specifierToken?.text || !isStringLiteral(specifierToken.text) || asToken?.text !== 'as' - || !aliasToken?.text?.match(IDENTIFIER_PATTERN) || semicolonToken?.text !== ';') { + if ( + !specifierToken?.text + || !isStringLiteral(specifierToken.text) + || asToken?.text !== 'as' + || !aliasToken?.text?.match(IDENTIFIER_PATTERN) + || semicolonToken?.text !== ';' + ) { throw new InvalidImportDirectiveError( 'Import directives must use the form import "./helpers.cash" as Helpers;', ); @@ -227,10 +338,8 @@ function parseLibraryFileWithPragmas(code: string, sourceLabel: string): ParsedL } const nestedImports = parseTopLevelImports(code); - if (nestedImports.length > 0) { - throw new InvalidLibraryImportError( - `Imported library '${sourceLabel}' may not import other libraries in the current MVP.`, - ); + while (cursor < tokens.length && tokens[cursor].text === 'import') { + cursor = advanceToSemicolon(tokens, cursor + 1); } const libraryToken = tokens[cursor]; @@ -253,9 +362,13 @@ function parseLibraryFileWithPragmas(code: string, sourceLabel: string): ParsedL } return { + imports: nestedImports, library: { name: nameToken.text, body: code.slice(openBraceToken.stop + 1, closeBraceToken.start), + bodyStartLine: openBraceToken.line, + source: code, + ...(sourceLabel ? { sourceFile: sourceLabel } : {}), }, pragmaConstraints, }; @@ -263,10 +376,32 @@ function parseLibraryFileWithPragmas(code: string, sourceLabel: string): ParsedL function transformLibrary( library: LibraryDefinition, - alias: string, + mangledPrefix: string, usedMangledNames: Set, -): LibraryTransformResult { - const tokens = getVisibleTokens(library.body); + nestedLibrariesByAlias: Map, +): { + body: string; + functions: Set; + provenance: ImportedFunctionProvenance[]; + sources: Array<{ source: string; sourceFile?: string }>; + } { + let rewrittenBody = library.body; + const nestedAccessibleFunctions = new Set(); + + nestedLibrariesByAlias.forEach((nestedLibrary, nestedAlias) => { + rewrittenBody = rewriteNamespacedCalls( + rewrittenBody, + nestedAlias, + nestedLibrary.functions, + nestedLibrary.mangledPrefix, + ); + + nestedLibrary.functions.forEach((functionName) => { + nestedAccessibleFunctions.add(`${nestedLibrary.mangledPrefix}_${functionName}`); + }); + }); + + const tokens = getVisibleTokens(rewrittenBody); const functionDefinitions = collectFunctionDefinitions(tokens); if (functionDefinitions.length === 0) { @@ -277,43 +412,52 @@ function transformLibrary( const mangledNames = new Map(); const replacements: Replacement[] = []; - functionDefinitions.forEach(({ name, nameToken, openBraceToken, visibility }) => { - const mangledName = `${alias}_${name}`; + functionDefinitions.forEach(({ name, nameToken }) => { + const mangledName = `${mangledPrefix}_${name}`; if (usedMangledNames.has(mangledName)) { throw new InvalidLibraryImportError( - `Imported function '${alias}.${name}' conflicts with an existing function named '${mangledName}'.`, + `Imported function '${library.name}.${name}' conflicts with an existing function named '${mangledName}'.`, ); } usedMangledNames.add(mangledName); mangledNames.set(name, mangledName); replacements.push({ start: nameToken.start, stop: nameToken.stop, text: mangledName }); - - if (visibility === 'omitted') { - replacements.push({ start: openBraceToken.start, stop: openBraceToken.stop, text: ' internal {' }); - } }); - validateLibraryCalls(tokens, localFunctions, library.name); + validateLibraryCalls(tokens, new Set([...localFunctions, ...nestedAccessibleFunctions]), library.name); replacements.push(...collectLocalFunctionCallReplacements(tokens, mangledNames)); + const body = applyReplacements(rewrittenBody, replacements).trim(); return { - body: applyReplacements(library.body, replacements).trim(), + body, functions: new Set(functionDefinitions.map((definition) => definition.name)), + provenance: functionDefinitions.map((definition) => ({ + mangledName: mangledNames.get(definition.name)!, + generatedStartLine: 0, + originalStartLine: library.bodyStartLine + definition.line - 1, + generatedStartColumn: definition.functionToken.column + 2, + originalStartColumn: definition.functionToken.column, + source: library.source, + ...(library.sourceFile ? { sourceFile: library.sourceFile } : {}), + })), + sources: [{ source: library.source, ...(library.sourceFile ? { sourceFile: library.sourceFile } : {}) }], }; } function collectFunctionDefinitions(tokens: Token[]): Array<{ + functionToken: Token; name: string; nameToken: Token; - openBraceToken: Token; - visibility: FunctionVisibility.INTERNAL | 'omitted'; + visibility: FunctionVisibility.INTERNAL; + line: number; }> { const definitions: Array<{ + functionToken: Token; name: string; nameToken: Token; - openBraceToken: Token; - visibility: FunctionVisibility.INTERNAL | 'omitted'; + visibility: FunctionVisibility.INTERNAL; + line: number; }> = []; for (let index = 0; index < tokens.length; index += 1) { @@ -334,29 +478,30 @@ function collectFunctionDefinitions(tokens: Token[]): Array<{ } const visibilityToken = tokens[cursor]; - if (visibilityToken?.text === FunctionVisibility.PUBLIC) { + if (visibilityToken?.text !== FunctionVisibility.INTERNAL) { throw new InvalidLibraryImportError( - `Imported library functions cannot be public. Offending function: '${nameToken.text}'.`, + `Imported library functions must declare internal visibility. Offending function: '${nameToken.text}'.`, ); } - const openBraceToken = visibilityToken?.text === FunctionVisibility.INTERNAL ? tokens[cursor + 1] : visibilityToken; + const openBraceToken = tokens[cursor + 1]; if (openBraceToken?.text !== '{') { throw new InvalidLibraryImportError('Invalid function definition in imported library.'); } definitions.push({ + functionToken: tokens[index], name: nameToken.text, nameToken, - openBraceToken, - visibility: visibilityToken?.text === FunctionVisibility.INTERNAL ? FunctionVisibility.INTERNAL : 'omitted', + visibility: FunctionVisibility.INTERNAL, + line: nameToken.line, }); } return definitions; } -function validateLibraryCalls(tokens: Token[], localFunctions: Set, libraryName: string): void { +function validateLibraryCalls(tokens: Token[], accessibleFunctions: Set, libraryName: string): void { for (let index = 0; index < tokens.length - 1; index += 1) { const token = tokens[index]; const nextToken = tokens[index + 1]; @@ -370,7 +515,7 @@ function validateLibraryCalls(tokens: Token[], localFunctions: Set, libr && previousToken?.text !== '.' ) { throw new InvalidLibraryImportError( - `Library '${libraryName}' references external helper '${token.text}.${tokens[index + 2]!.text}'. Imported libraries may only call their own functions or built-ins.`, + `Library '${libraryName}' references external helper '${token.text}.${tokens[index + 2]!.text}'. Imported libraries may only call imported or local helper functions.`, ); } @@ -378,7 +523,7 @@ function validateLibraryCalls(tokens: Token[], localFunctions: Set, libr if (previousToken?.text === 'function' || previousToken?.text === 'new' || previousToken?.text === '.') continue; if ( - localFunctions.has(token.text) + accessibleFunctions.has(token.text) || GLOBAL_FUNCTIONS.has(token.text) || GLOBAL_CLASSES.has(token.text as Class) || BUILT_IN_STATEMENTS.has(token.text) @@ -387,7 +532,7 @@ function validateLibraryCalls(tokens: Token[], localFunctions: Set, libr } throw new InvalidLibraryImportError( - `Library '${libraryName}' references non-library function '${token.text}'. Imported libraries may only call their own functions or built-ins.`, + `Library '${libraryName}' references non-library function '${token.text}'. Imported libraries may only call imported or local helper functions.`, ); } } @@ -495,6 +640,40 @@ function indentLibraryBody(body: string): string { .join('\n'); } +function countLineBreaks(text: string): number { + return (text.match(/\n/g) ?? []).length; +} + +function countLines(text: string): number { + return countLineBreaks(text) + 1; +} + +function getFunctionGeneratedLine(mangledName: string, section: string): number { + const index = section.indexOf(mangledName); + if (index === -1) { + throw new InvalidLibraryImportError(`Could not map imported helper '${mangledName}' back to generated source.`); + } + + return countLineBreaks(section.slice(0, index)) + 1; +} + +function dedupeSources( + sources: Array<{ source: string; sourceFile?: string }>, +): Array<{ source: string; sourceFile?: string }> { + const seen = new Set(); + return sources.filter((entry) => { + const key = `${entry.sourceFile ?? ''}\u0000${entry.source}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); +} + +function getImportIdentity(resolvedImport: ResolvedImport, specifier: string, fromPath?: string): string { + if (resolvedImport.path) return normaliseFilesystemPath(resolvedImport.path); + return `${fromPath ?? ''}::${specifier}`; +} + function findMatchingBrace(tokens: Token[], openBraceIndex: number): number { let depth = 0; for (let index = openBraceIndex; index < tokens.length; index += 1) { @@ -612,87 +791,81 @@ function findLineCommentEnd(code: string, index: number): number { } function findBlockCommentEnd(code: string, index: number): number { - const closingIndex = code.indexOf('*/', index + 2); - return closingIndex === -1 ? code.length : closingIndex + 2; + let cursor = index + 2; + while (cursor < code.length && !(code[cursor] === '*' && code[cursor + 1] === '/')) { + cursor += 1; + } + return cursor < code.length ? cursor + 2 : code.length; } function findStringEnd(code: string, index: number): number { const quote = code[index]; let cursor = index + 1; - while (cursor < code.length) { if (code[cursor] === '\\') { cursor += 2; continue; } - if (code[cursor] === quote) { return cursor + 1; } - cursor += 1; } - return code.length; } -function isIdentifierBoundary(character: string | undefined): boolean { - return !character || !/[a-zA-Z0-9_]/.test(character); +function isIdentifierBoundary(char: string | undefined): boolean { + return char === undefined || !/[a-zA-Z0-9_]/.test(char); } -function isStringLiteral(text: string): boolean { - return (text.startsWith('"') && text.endsWith('"')) || (text.startsWith('\'') && text.endsWith('\'')); +function isStringLiteral(value: string): boolean { + return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')); } -function parseStringLiteral(text: string): string { - return text.slice(1, -1); +function parseStringLiteral(value: string): string { + return JSON.parse(value.replace(/^'/, '"').replace(/'$/, '"')); } function readPragmaConstraints(tokens: Token[], pragmaIndex: number, sourceLabel: string): string[] { - const pragmaName = tokens[pragmaIndex + 1]; - if (pragmaName?.text !== 'cashscript') { - throw new InvalidLibraryImportError( - `Imported library '${sourceLabel}' uses unsupported pragma '${pragmaName?.text ?? ''}'.`, - ); + const nameToken = tokens[pragmaIndex + 1]; + if (nameToken?.text !== 'cashscript') { + throw new VersionError(sourceLabel, 'pragma cashscript ...'); } const constraints: string[] = []; let cursor = pragmaIndex + 2; - while (cursor < tokens.length && tokens[cursor].text !== ';') { - const current = tokens[cursor]; - const next = tokens[cursor + 1]; + while (tokens[cursor]?.text !== ';') { + const operator = tokens[cursor]?.text?.match(/^[\^~><=]+$/) ? tokens[cursor]!.text : ''; + if (operator) cursor += 1; - if (!current?.text) break; - - if (/^[\^~><=]/.test(current.text) && next?.text && semver.valid(next.text)) { - constraints.push(`${current.text}${next.text}`); - cursor += 2; - continue; + const versionToken = tokens[cursor]; + if (!versionToken?.text) { + throw new VersionError(sourceLabel, 'valid pragma cashscript version constraint'); } - if (semver.valid(current.text)) { - constraints.push(current.text); - cursor += 1; - continue; - } - - throw new InvalidLibraryImportError( - `Imported library '${sourceLabel}' has an invalid pragma constraint near '${current.text}'.`, - ); + constraints.push(`${operator}${versionToken.text}`); + cursor += 1; } return constraints; } -function validateLibraryPragmas(pragmaConstraints: string[]): void { - const actualVersion = version.replace(/-.*/g, ''); - pragmaConstraints.forEach((constraint) => { - if (!semver.satisfies(actualVersion, constraint)) { - throw new VersionError(actualVersion, constraint); +function validateLibraryPragmas(constraints: string[]): void { + constraints.forEach((constraint) => { + if (!semver.satisfies(version, constraint, { includePrerelease: true })) { + throw new VersionError(version, constraint); } }); } -function normaliseFilesystemPath(sourcePath: string): string { - return sourcePath.startsWith('file://') ? fileURLToPath(sourcePath) : sourcePath; +function normaliseFilesystemPath(codeFile: string | URL): string { + if (codeFile instanceof URL) { + return fileURLToPath(codeFile); + } + + if (codeFile.startsWith('file://')) { + return fileURLToPath(codeFile); + } + + return codeFile; } diff --git a/packages/cashc/test/ast/AST.test.ts b/packages/cashc/test/ast/AST.test.ts index 7308df76..18a3bcdd 100644 --- a/packages/cashc/test/ast/AST.test.ts +++ b/packages/cashc/test/ast/AST.test.ts @@ -76,7 +76,7 @@ contract Test() { it('should preserve library containers in AST source output', () => { const input = ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } diff --git a/packages/cashc/test/compiler/compiler.test.ts b/packages/cashc/test/compiler/compiler.test.ts index dbcffb43..646a76ce 100644 --- a/packages/cashc/test/compiler/compiler.test.ts +++ b/packages/cashc/test/compiler/compiler.test.ts @@ -177,7 +177,7 @@ contract Test() { it('should parse top-level libraries as non-spendable helper containers', () => { const ast = parseCode(` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -191,7 +191,7 @@ library MathHelpers { it('should reject compiling a top-level library to an artifact', () => { expect(() => compileString(` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -205,7 +205,17 @@ library BadHelpers { require(value % 2 == 0); } } -`)).toThrow(Errors.LibraryPublicFunctionError); +`)).toThrow(Errors.ParseError); + }); + + it('should require internal functions inside a library', () => { + expect(() => compileString(` +library BadHelpers { + function isEven(int value) { + require(value % 2 == 0); + } +} +`)).toThrow(Errors.ParseError); }); it('should compile contracts that import helper libraries', () => { @@ -257,8 +267,8 @@ contract Other() { })).toThrow(Errors.InvalidLibraryImportError); }); - it('should treat omitted library visibility as helper-only when importing libraries', () => { - const artifact = compileString(` + it('should reject imported libraries with omitted visibility', () => { + expect(() => compileString(` import "./math.cash" as Math; contract UsesLibrary() { @@ -275,11 +285,7 @@ library MathHelpers { } } `, - }); - - expect(artifact.abi).toEqual([ - { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, - ]); + })).toThrow(Errors.InvalidLibraryImportError); }); it('should reject imported libraries that reference non-library local functions', () => { @@ -303,8 +309,8 @@ library MathHelpers { })).toThrow(Errors.InvalidLibraryImportError); }); - it('should reject nested library imports in the current MVP', () => { - expect(() => compileString(` + it('should support transitive library imports', () => { + const artifact = compileString(` import "./math.cash" as Math; contract UsesLibrary() { @@ -330,13 +336,63 @@ library MathHelpers { }; } - return ` + return { + path: '/contracts/core.cash', + source: ` library CoreHelpers { function isZero(int value) internal { require(value == 0); } } -`; +`, + }; + }, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + }); + + it('should reject circular transitive library imports', () => { + expect(() => compileString(` +import "./math.cash" as Math; + +contract UsesLibrary() { + function spend(int value) public { + require(Math.isEven(value)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + if (specifier === './math.cash') { + return { + path: '/contracts/math.cash', + source: ` +import "./core.cash" as Core; + +library MathHelpers { + function isEven(int value) internal { + require(Core.isZero(value % 2)); + } +} +`, + }; + } + + return { + path: '/contracts/core.cash', + source: ` +import "./math.cash" as Math; + +library CoreHelpers { + function isZero(int value) internal { + require(Math.isEven(value)); + } +} +`, + }; }, })).toThrow(Errors.InvalidLibraryImportError); }); @@ -355,7 +411,7 @@ contract UsesLibraries() { sourcePath: '/contracts/main.cash', resolveImport: () => ` library Helpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -376,7 +432,7 @@ contract UsesLibrary() { sourcePath: '/contracts/main.cash', resolveImport: () => ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -397,7 +453,7 @@ contract UsesLibrary() { sourcePath: '/contracts/main.cash', resolveImport: () => ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(Other.check(value)); } } @@ -420,7 +476,7 @@ contract UsesLibrary() { sourcePath: '/contracts/main.cash', resolveImport: () => ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -431,5 +487,67 @@ library MathHelpers { { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, ]); }); + + it('should canonicalise shared transitive libraries by resolved file identity', () => { + const artifact = compileString(` +import "./math.cash" as Math; +import "./bits.cash" as Bits; + +contract UsesLibraries() { + function spend(int value) public { + require(Math.isEven(value)); + require(Bits.isOdd(value + 1)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + if (specifier === './math.cash') { + return { + path: '/contracts/math.cash', + source: ` +import "./shared.cash" as Shared; + +library MathHelpers { + function isEven(int value) internal { + require(Shared.isParity(value, 0)); + } +} +`, + }; + } + + if (specifier === './bits.cash') { + return { + path: '/contracts/bits.cash', + source: ` +import "./shared.cash" as Shared; + +library BitHelpers { + function isOdd(int value) internal { + require(Shared.isParity(value, 1)); + } +} +`, + }; + } + + return { + path: '/contracts/shared.cash', + source: ` +library SharedHelpers { + function isParity(int value, int parity) internal { + require(value % 2 == parity); + } +} +`, + }; + }, + }); + + expect(artifact.abi).toEqual([ + { name: 'spend', inputs: [{ name: 'value', type: 'int' }] }, + ]); + }); }); }); diff --git a/packages/cashc/test/generation/generation.test.ts b/packages/cashc/test/generation/generation.test.ts index 4545432f..72695063 100644 --- a/packages/cashc/test/generation/generation.test.ts +++ b/packages/cashc/test/generation/generation.test.ts @@ -21,24 +21,49 @@ const stripFrameBytecodeFromLogData = (entry: LogData): LogData => { return stackItem; }; -const stripExtendedDebugMetadata = (artifact: Artifact): Artifact => ({ - ...artifact, - ...(artifact.debug ? { - debug: { - ...artifact.debug, - logs: artifact.debug.logs.map((log): LogEntry => ({ - ip: log.ip, - line: log.line, - data: log.data.map(stripFrameBytecodeFromLogData), - })), - requires: artifact.debug.requires.map((requireStatement): RequireStatement => ({ - ip: requireStatement.ip, - line: requireStatement.line, - ...(requireStatement.message ? { message: requireStatement.message } : {}), - })), - }, - } : {}), -}); +const stripFrameMetadataFromArtifact = (artifact: Artifact): Artifact => { + if (!artifact.debug) { + return artifact; + } + + const debugWithoutFrames = Object.fromEntries( + Object.entries(artifact.debug).filter(([key]) => key !== 'frames'), + ) as typeof artifact.debug; + const strippedDebug = { + ...debugWithoutFrames, + logs: artifact.debug.logs.map((log): LogEntry => ({ + ip: log.ip, + line: log.line, + data: log.data.map(stripFrameBytecodeFromLogData), + })), + requires: artifact.debug.requires.map((requireStatement): RequireStatement => ({ + ip: requireStatement.ip, + line: requireStatement.line, + ...(requireStatement.message ? { message: requireStatement.message } : {}), + })), + }; + + return { + ...artifact, + debug: strippedDebug, + }; +}; + +const stripExtendedDebugMetadata = (artifact: Artifact): Artifact => { + const strippedArtifact = stripFrameMetadataFromArtifact(artifact); + return { + ...strippedArtifact, + ...(strippedArtifact.debug ? { + debug: { + ...strippedArtifact.debug, + bytecode: strippedArtifact.debug.bytecode, + sourceMap: strippedArtifact.debug.sourceMap, + logs: strippedArtifact.debug.logs, + requires: strippedArtifact.debug.requires, + }, + } : {}), + }; +}; describe('Code generation & target code optimisation', () => { fixtures.forEach((fixture) => { @@ -326,6 +351,89 @@ library MathHelpers { expect(artifact.compiler.target).toBe('BCH_2026_05'); }); + it('should lower transitive imported library helpers without duplicating shared leaves', () => { + const artifact = compileString(` +import "./math.cash" as Math; +import "./bits.cash" as Bits; + +contract UsesLibraries() { + function spend(int value) public { + require(Math.isEven(value)); + require(Bits.isOdd(value + 1)); + } +} +`, { + sourcePath: '/contracts/main.cash', + resolveImport: (specifier) => { + if (specifier === './math.cash') { + return { + path: '/contracts/math.cash', + source: ` +import "./shared.cash" as Shared; + +library MathHelpers { + function isEven(int value) internal { + require(Shared.isParity(value, 0)); + } +} +`, + }; + } + + if (specifier === './bits.cash') { + return { + path: '/contracts/bits.cash', + source: ` +import "./shared.cash" as Shared; + +library BitHelpers { + function isOdd(int value) internal { + require(Shared.isParity(value, 1)); + } +} +`, + }; + } + + return { + path: '/contracts/shared.cash', + source: ` +library SharedHelpers { + function isParity(int value, int parity) internal { + require(value % 2 == parity); + } +} +`, + }; + }, + }); + + expect(artifact.bytecode.match(/OP_DEFINE/g)).toHaveLength(3); + expect(artifact.bytecode.match(/OP_INVOKE/g)).toHaveLength(2); + }); + + it('should reject contracts whose helper frames would have ambiguous identical bytecode', () => { + expect(() => compileString(` +contract AmbiguousHelpers() { + function spendA() public { + require(checkA()); + } + + function spendB() public { + require(checkB()); + } + + function checkA() internal { + require(true); + } + + function checkB() internal { + require(true); + } +} +`)).toThrow(/identical helper bytecode/); + }); + it('should support multiple imported libraries with overlapping helper names', () => { const artifact = compileString(` import "./math.cash" as Math; @@ -343,7 +451,7 @@ contract UsesLibraries() { if (specifier === './math.cash') { return ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(value % 2 == 0); } } @@ -352,7 +460,7 @@ library MathHelpers { return ` library BitHelpers { - function isEven(int value) { + function isEven(int value) internal { require((value % 4) == 0 || (value % 4) == 2); } } @@ -381,15 +489,15 @@ contract UsesLibrary() { sourcePath: '/contracts/main.cash', resolveImport: () => ` library MathHelpers { - function isEven(int value) { + function isEven(int value) internal { require(checkParity(value)); } - function checkParity(int value) { + function checkParity(int value) internal { require(value % 2 == 0); } - function unused(int value) { + function unused(int value) internal { require(value == 123); } } diff --git a/packages/cashscript/src/Errors.ts b/packages/cashscript/src/Errors.ts index 9ca18469..323c9e3c 100644 --- a/packages/cashscript/src/Errors.ts +++ b/packages/cashscript/src/Errors.ts @@ -1,4 +1,4 @@ -import { Artifact, RequireStatement, sourceMapToLocationData, Type } from '@cashscript/utils'; +import { Artifact, DebugFrame, RequireStatement, sourceMapToLocationData, Type } from '@cashscript/utils'; export class TypeError extends Error { constructor(actual: string, expected: Type) { @@ -68,12 +68,19 @@ export class FailedTransactionEvaluationError extends FailedTransactionError { public inputIndex: number, public bitauthUri: string, public libauthErrorMessage: string, + public frameId?: string, + public frameBytecode?: string, ) { let message = `${artifact.contractName}.cash Error in transaction at input ${inputIndex} in contract ${artifact.contractName}.cash.\nReason: ${libauthErrorMessage}`; if (artifact.debug) { - const { statement, lineNumber } = getLocationDataForInstructionPointer(artifact, failingInstructionPointer); - message = `${artifact.contractName}.cash:${lineNumber} Error in transaction at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}.\nReason: ${libauthErrorMessage}\nFailing statement: ${statement}`; + const { statement, lineNumber, sourceName } = getLocationDataForInstructionPointer( + artifact, + failingInstructionPointer, + frameId, + frameBytecode, + ); + message = `${sourceName}:${lineNumber} Error in transaction at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}.\nReason: ${libauthErrorMessage}\nFailing statement: ${statement}`; } super(message, bitauthUri); @@ -89,11 +96,22 @@ export class FailedRequireError extends FailedTransactionError { public bitauthUri: string, public libauthErrorMessage?: string, ) { - const { statement, lineNumber } = requireStatement.location - ? getLocationDataForLocation(artifact, requireStatement.location) - : getLocationDataForInstructionPointer(artifact, failingInstructionPointer); - - const baseMessage = `${artifact.contractName}.cash:${lineNumber} Require statement failed at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}`; + const { statement, lineNumber, sourceName } = requireStatement.location + ? getLocationDataForLocation( + artifact, + requireStatement.location, + requireStatement.frameId, + requireStatement.frameBytecode, + requireStatement.sourceFile, + ) + : getLocationDataForInstructionPointer( + artifact, + failingInstructionPointer, + requireStatement.frameId, + requireStatement.frameBytecode, + ); + + const baseMessage = `${sourceName}:${lineNumber} Require statement failed at input ${inputIndex} in contract ${artifact.contractName}.cash at line ${lineNumber}`; const baseMessageWithRequireMessage = `${baseMessage} with the following message: ${requireStatement.message}`; const fullMessage = `${requireStatement.message ? baseMessageWithRequireMessage : baseMessage}.\nFailing statement: ${statement}`; @@ -104,33 +122,38 @@ export class FailedRequireError extends FailedTransactionError { const getLocationDataForInstructionPointer = ( artifact: Artifact, instructionPointer: number, -): { lineNumber: number, statement: string } => { - const locationData = sourceMapToLocationData(artifact.debug!.sourceMap); - - // We subtract the constructor inputs because these are present in the evaluation (and thus the instruction pointer) - // but they are not present in the source code (and thus the location data) - const modifiedInstructionPointer = instructionPointer - artifact.constructorInputs.length; + frameId?: string, + frameBytecode?: string, +): { lineNumber: number; statement: string; sourceName: string } => { + const frame = getDebugFrame(artifact, frameId, frameBytecode); + const locationData = sourceMapToLocationData(frame.sourceMap); + const modifiedInstructionPointer = instructionPointer - ( + frame.id === '__root__' ? artifact.constructorInputs.length : 0 + ); const { location } = locationData[modifiedInstructionPointer]; + const source = frame.source; + const failingLines = source.split('\n').slice(location.start.line - 1, location.end.line); - const failingLines = artifact.source.split('\n').slice(location.start.line - 1, location.end.line); - - // Slice off the start and end of the statement's start and end lines to only return the failing part - // Note that we first slice off the end, to avoid shifting the end column index failingLines[failingLines.length - 1] = failingLines[failingLines.length - 1].slice(0, location.end.column); failingLines[0] = failingLines[0].slice(location.start.column); - const statement = failingLines.join('\n'); - const lineNumber = location.start.line; - - return { statement, lineNumber }; + return { + statement: failingLines.join('\n'), + lineNumber: location.start.line, + sourceName: getSourceName(frame.sourceFile, artifact), + }; }; const getLocationDataForLocation = ( artifact: Artifact, location: NonNullable, -): { lineNumber: number, statement: string } => { - const failingLines = artifact.source.split('\n').slice(location.start.line - 1, location.end.line); + frameId?: string, + frameBytecode?: string, + sourceFile?: string, +): { lineNumber: number; statement: string; sourceName: string } => { + const frame = getDebugFrame(artifact, frameId, frameBytecode, sourceFile); + const failingLines = frame.source.split('\n').slice(location.start.line - 1, location.end.line); failingLines[failingLines.length - 1] = failingLines[failingLines.length - 1].slice(0, location.end.column); failingLines[0] = failingLines[0].slice(location.start.column); @@ -138,5 +161,51 @@ const getLocationDataForLocation = ( return { lineNumber: location.start.line, statement: failingLines.join('\n'), + sourceName: getSourceName(sourceFile ?? frame.sourceFile, artifact), }; }; + +function getDebugFrame( + artifact: Artifact, + frameId?: string, + frameBytecode?: string, + sourceFile?: string, +): DebugFrame { + const rootFrame: DebugFrame = { + id: '__root__', + bytecode: artifact.debug!.bytecode, + sourceMap: artifact.debug!.sourceMap, + source: artifact.source, + }; + + const frames = artifact.debug?.frames ?? []; + + if (frameId) { + const matchingFrame = frames.find((frame) => frame.id === frameId); + if (matchingFrame) return matchingFrame; + if (frameId === '__root__') return rootFrame; + } + + if (frameBytecode) { + const matchingFrame = frames.find((frame) => ( + frame.bytecode === frameBytecode || frame.bytecode.endsWith(frameBytecode) + )); + if (matchingFrame) return matchingFrame; + if (rootFrame.bytecode === frameBytecode || rootFrame.bytecode.endsWith(frameBytecode)) return rootFrame; + } + + if (sourceFile) { + const matchingFrame = frames.find((frame) => frame.sourceFile === sourceFile); + if (matchingFrame) return matchingFrame; + } + + return rootFrame; +} + +function getSourceName(sourceFile: string | undefined, artifact: Artifact): string { + return sourceFile ? basename(sourceFile) : `${artifact.contractName}.cash`; +} + +function basename(sourceFile: string): string { + return sourceFile.split(/[/\\]/).at(-1) ?? sourceFile; +} diff --git a/packages/cashscript/src/debugging.ts b/packages/cashscript/src/debugging.ts index b9ee2691..b0f5bf42 100644 --- a/packages/cashscript/src/debugging.ts +++ b/packages/cashscript/src/debugging.ts @@ -1,5 +1,5 @@ import { AuthenticationErrorCommon, AuthenticationInstruction, AuthenticationProgramCommon, AuthenticationProgramStateCommon, AuthenticationVirtualMachine, ResolvedTransactionCommon, WalletTemplate, WalletTemplateScriptUnlocking, binToHex, createCompiler, createVirtualMachineBch2023, createVirtualMachineBch2025, createVirtualMachineBch2026, createVirtualMachineBchSpec, decodeAuthenticationInstructions, encodeAuthenticationInstruction, walletTemplateToCompilerConfiguration } from '@bitauth/libauth'; -import { Artifact, LogData, LogEntry, Op, PrimitiveType, StackItem, asmToBytecode, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils'; +import { Artifact, LogData, LogEntry, Op, PrimitiveType, RequireStatement, StackItem, asmToBytecode, bytecodeToAsm, decodeBool, decodeInt, decodeString } from '@cashscript/utils'; import { findLastIndex, toRegExp } from './utils.js'; import { FailedRequireError, FailedTransactionError, FailedTransactionEvaluationError } from './Errors.js'; import { getBitauthUri } from './libauth-template/LibauthTemplate.js'; @@ -95,7 +95,7 @@ const debugSingleScenario = ( .flatMap((debugStep, index) => { const logEntries = artifact.debug?.logs?.filter((log) => ( log.ip === debugStep.ip - && matchesFrameBytecode(debugStep, log.frameBytecode, frameBytecodeCache) + && matchesDebugFrame(debugStep, log.frameId, log.frameBytecode, frameBytecodeCache) )); if (!logEntries || logEntries.length === 0) return []; @@ -103,14 +103,21 @@ const debugSingleScenario = ( return logEntries.map((logEntry) => { const decodedLogData = logEntry.data - .map((dataEntry) => decodeLogDataEntry(dataEntry, reversedPriorDebugSteps, vm, logEntry.frameBytecode, frameBytecodeCache)); + .map((dataEntry) => decodeLogDataEntry( + dataEntry, + reversedPriorDebugSteps, + vm, + logEntry.frameId, + logEntry.frameBytecode, + frameBytecodeCache, + )); return { logEntry, decodedLogData }; }); }); for (const { logEntry, decodedLogData } of executedLogs) { const inputIndex = extractInputIndexFromScenario(scenarioId); - logConsoleLogStatement(logEntry, decodedLogData, artifact.contractName, inputIndex); + logConsoleLogStatement(logEntry, decodedLogData, artifact, inputIndex); } } @@ -145,7 +152,7 @@ const debugSingleScenario = ( const requireStatement = (artifact.debug?.requires ?? []) .find((statement) => ( statement.ip === requireStatementIp - && matchesFrameBytecode(lastExecutedDebugStep, statement.frameBytecode, frameBytecodeCache) + && matchesDebugFrame(lastExecutedDebugStep, statement.frameId, statement.frameBytecode, frameBytecodeCache) )); if (requireStatement) { @@ -157,7 +164,13 @@ const debugSingleScenario = ( // Note that we use failingIp here rather than requireStatementIp, see comment above throw new FailedTransactionEvaluationError( - artifact, failingIp, inputIndex, getBitauthUri(template), error, + artifact, + failingIp, + inputIndex, + getBitauthUri(template), + error, + getFrameId(lastExecutedDebugStep), + getFrameBytecode(lastExecutedDebugStep, frameBytecodeCache), ); } @@ -188,7 +201,7 @@ const debugSingleScenario = ( const requireStatement = (artifact.debug?.requires ?? []) .find((message) => ( message.ip === finalExecutedVerifyIp - && matchesFrameBytecode(lastExecutedDebugStep, message.frameBytecode, frameBytecodeCache) + && matchesDebugFrame(lastExecutedDebugStep, message.frameId, message.frameBytecode, frameBytecodeCache) )); if (requireStatement) { @@ -197,16 +210,16 @@ const debugSingleScenario = ( ); } - const nestedRequireStatement = findMostRecentExecutedRequireStatement( + const nestedRequireStatement = findNestedFinalRequireBeforeFinalVerify( executedDebugSteps, - artifact, + artifact.debug?.requires ?? [], frameBytecodeCache, ); if (nestedRequireStatement) { throw new FailedRequireError( artifact, - nestedRequireStatement.ip, + sourcemapInstructionPointer, nestedRequireStatement, inputIndex, getBitauthUri(template), @@ -214,7 +227,13 @@ const debugSingleScenario = ( } throw new FailedTransactionEvaluationError( - artifact, sourcemapInstructionPointer, inputIndex, getBitauthUri(template), evaluationResult, + artifact, + sourcemapInstructionPointer, + inputIndex, + getBitauthUri(template), + evaluationResult, + getFrameId(lastExecutedDebugStep), + getFrameBytecode(lastExecutedDebugStep, frameBytecodeCache), ); } @@ -295,16 +314,20 @@ const createProgram = (template: WalletTemplate, unlockingScriptId: string, scen const logConsoleLogStatement = ( log: LogEntry, decodedLogData: Array, - contractName: string, + artifact: Artifact, inputIndex: number, ): void => { - console.log(`[Input #${inputIndex}] ${contractName}.cash:${log.line} ${decodedLogData.join(' ')}`); + const sourceName = log.sourceFile + ? basename(log.sourceFile) + : getFrameSourceName(artifact, log.frameId, log.frameBytecode); + console.log(`[Input #${inputIndex}] ${sourceName}:${log.line} ${decodedLogData.join(' ')}`); }; const decodeLogDataEntry = ( dataEntry: LogData, reversedPriorDebugSteps: AuthenticationProgramStateCommon[], vm: VM, + fallbackFrameId: string | undefined, fallbackFrameBytecode: string | undefined, frameBytecodeCache: WeakMap, ): string | bigint | boolean => { @@ -312,7 +335,12 @@ const decodeLogDataEntry = ( const dataEntryDebugStep = reversedPriorDebugSteps.find((step) => ( step.ip === dataEntry.ip - && matchesFrameBytecode(step, dataEntry.frameBytecode ?? fallbackFrameBytecode, frameBytecodeCache) + && matchesDebugFrame( + step, + dataEntry.frameId ?? fallbackFrameId, + dataEntry.frameBytecode ?? fallbackFrameBytecode, + frameBytecodeCache, + ) )); if (!dataEntryDebugStep) { @@ -323,78 +351,95 @@ const decodeLogDataEntry = ( return decodeStackItem(dataEntry, transformedDebugStep.stack); }; -const matchesFrameBytecode = ( +const matchesDebugFrame = ( debugStep: AuthenticationProgramStateCommon, + expectedFrameId: string | undefined, expectedFrameBytecode: string | undefined, cache: WeakMap, ): boolean => { + if (expectedFrameId && !expectedFrameBytecode) { + return getFrameId(debugStep) === expectedFrameId; + } + + const actualFrameBytecode = getFrameBytecode(debugStep, cache); if (!expectedFrameBytecode) return true; + return actualFrameBytecode === expectedFrameBytecode || actualFrameBytecode.endsWith(expectedFrameBytecode); +}; + +const getFrameBytecode = ( + debugStep: AuthenticationProgramStateCommon, + cache: WeakMap, +): string => { let actualFrameBytecode = cache.get(debugStep.instructions); if (!actualFrameBytecode) { actualFrameBytecode = instructionsToBytecodeHex(debugStep.instructions); cache.set(debugStep.instructions, actualFrameBytecode); } - return actualFrameBytecode === expectedFrameBytecode || actualFrameBytecode.endsWith(expectedFrameBytecode); + return actualFrameBytecode; }; -const findMostRecentExecutedRequireStatement = ( - executedDebugSteps: AuthenticationProgramStateCommon[], - artifact: Artifact, - frameBytecodeCache: WeakMap, -) => { - const matchingRequireStatements = executedDebugSteps - .slice() - .reverse() - .flatMap((debugStep) => ( - (artifact.debug?.requires ?? []).filter((requireStatement) => ( - requireStatement.ip === debugStep.ip - && matchesFrameBytecode(debugStep, requireStatement.frameBytecode, frameBytecodeCache) - )) +const getFrameId = (debugStep: AuthenticationProgramStateCommon): string => { + const activeBytecode = binToHex(Uint8Array.from(debugStep.instructions.flatMap((instruction) => ( + Array.from(encodeAuthenticationInstruction(instruction)) + )))); + const matchingEntry = Object.entries(debugStep.functionTable ?? {}).find(([, functionBody]) => ( + binToHex(functionBody) === activeBytecode )); - const nestedRequireStatement = matchingRequireStatements.find((requireStatement) => ( - requireStatement.frameBytecode !== undefined - && requireStatement.frameBytecode !== artifact.debug?.bytecode - )); + return matchingEntry ? `fn:${matchingEntry[0]}` : '__root__'; +}; - if (nestedRequireStatement) { - return nestedRequireStatement; - } +const findNestedFinalRequireBeforeFinalVerify = ( + executedDebugSteps: AuthenticationProgramStateCommon[], + requireStatements: readonly RequireStatement[], + cache: WeakMap, +): RequireStatement | undefined => { + const finalStep = executedDebugSteps[executedDebugSteps.length - 1]; + if (!finalStep) return undefined; + + const finalFrameId = getFrameId(finalStep); + const finalFrameBytecode = getFrameBytecode(finalStep, cache); - const nestedFrameBytecode = executedDebugSteps - .slice() - .reverse() - .map((debugStep) => getFrameBytecode(debugStep, frameBytecodeCache)) - .find((frameBytecode) => frameBytecode !== artifact.debug?.bytecode); + for (let index = executedDebugSteps.length - 2; index >= 0; index -= 1) { + const step = executedDebugSteps[index]!; + if (matchesDebugFrame(step, finalFrameId, finalFrameBytecode, cache)) continue; - if (nestedFrameBytecode) { - const nestedFrameRequireStatement = (artifact.debug?.requires ?? []) - .filter((requireStatement) => requireStatement.frameBytecode === nestedFrameBytecode) - .at(-1); + const matchingRequire = requireStatements.find((statement) => ( + (statement.ip === step.ip || statement.ip === step.ip + 1) + && matchesDebugFrame(step, statement.frameId, statement.frameBytecode, cache) + )); + + if (!matchingRequire) continue; + + const returnedToFinalFrameOnly = executedDebugSteps + .slice(index + 1) + .every((laterStep) => matchesDebugFrame(laterStep, finalFrameId, finalFrameBytecode, cache)); - if (nestedFrameRequireStatement) { - return nestedFrameRequireStatement; + if (returnedToFinalFrameOnly) { + return matchingRequire; } } - return matchingRequireStatements.at(0); + return undefined; }; -const getFrameBytecode = ( - debugStep: AuthenticationProgramStateCommon, - cache: WeakMap, -): string => { - let actualFrameBytecode = cache.get(debugStep.instructions); - if (!actualFrameBytecode) { - actualFrameBytecode = instructionsToBytecodeHex(debugStep.instructions); - cache.set(debugStep.instructions, actualFrameBytecode); +const getFrameSourceName = (artifact: Artifact, frameId?: string, frameBytecode?: string): string => { + const matchingFrame = artifact.debug?.frames?.find((frame) => ( + (frameId && frame.id === frameId) + || (frameBytecode && (frame.bytecode === frameBytecode || frame.bytecode.endsWith(frameBytecode))) + )); + + if (matchingFrame?.sourceFile) { + return basename(matchingFrame.sourceFile); } - return actualFrameBytecode; + return `${artifact.contractName}.cash`; }; +const basename = (sourceFile: string): string => sourceFile.split(/[/\\]/).at(-1) ?? sourceFile; + const applyStackItemTransformations = ( element: StackItem, debugStep: AuthenticationProgramStateCommon, diff --git a/packages/cashscript/test/debugging.test.ts b/packages/cashscript/test/debugging.test.ts index ddd37053..f4fdcd0e 100644 --- a/packages/cashscript/test/debugging.test.ts +++ b/packages/cashscript/test/debugging.test.ts @@ -1,4 +1,5 @@ import { Contract, FailedTransactionError, MockNetworkProvider, SignatureAlgorithm, SignatureTemplate, TransactionBuilder, VmTarget } from '../src/index.js'; +import { compileString } from 'cashc'; import { DEFAULT_VM_TARGET } from '../src/libauth-template/utils.js'; import { aliceAddress, alicePriv, alicePub, bobPriv, bobPub } from './fixture/vars.js'; import { randomUtxo } from '../src/utils.js'; @@ -289,6 +290,33 @@ describe('Debugging tests', () => { expect(transaction).toFailRequireWith('Failing statement: require(value == 1, \'internal value should be 1\')'); }); + it('should not misattribute a nested non-require helper failure to an earlier helper require', async () => { + const artifact = compileString(` +contract Test() { + function trigger() public { + require(runInternalFailure()); + } + + function runInternalFailure() internal { + require(true, 'earlier require should not be reported'); + int value = tx.inputs[5].value; + require(value == 1); + } +} +`); + const contract = new Contract(artifact, [], { provider }); + const utxo = randomUtxo(); + provider.addUtxo(contract.address, utxo); + + const transaction = new TransactionBuilder({ provider }) + .addInput(utxo, contract.unlock.trigger()) + .addOutput({ to: contract.address, amount: 1000n }); + + expect(() => transaction.debug()).toThrow(`Reason: ${AuthenticationErrorCommon.invalidTransactionUtxoIndex}`); + expect(() => transaction.debug()).toThrow('Failing statement: tx.inputs[5].value'); + expect(() => transaction.debug()).not.toThrow(/earlier require should not be reported/); + }); + // test_multiple_require_statements_final_fails it('it should only fail with correct error message when there are multiple require statements where the final statement fails', async () => { const transaction = new TransactionBuilder({ provider }) diff --git a/packages/utils/src/artifact.ts b/packages/utils/src/artifact.ts index 04aef5e7..8620f462 100644 --- a/packages/utils/src/artifact.ts +++ b/packages/utils/src/artifact.ts @@ -8,6 +8,7 @@ export type VmTarget = export interface CompilerOptions { enforceFunctionParameterTypes?: boolean; + requireExplicitFunctionVisibility?: boolean; target?: VmTarget; } @@ -26,14 +27,25 @@ export interface DebugInformation { sourceMap: string; // see documentation for `generateSourceMap` logs: readonly LogEntry[]; // log entries generated from `console.log` statements requires: readonly RequireStatement[]; // messages for failing `require` statements + frames?: readonly DebugFrame[]; // source/provenance details for the root frame and helper frames sourceTags?: string; // semantic tags for opcodes (e.g. loop update/condition ranges) } +export interface DebugFrame { + id: string; + bytecode: string; + sourceMap: string; + source: string; + sourceFile?: string; +} + export interface LogEntry { ip: number; // instruction pointer line: number; // line in the source code data: readonly LogData[]; // data to be logged frameBytecode?: string; // active bytecode frame in which this log executes + frameId?: string; // compiler-assigned helper/root frame identifier + sourceFile?: string; // source file in which this log executes } export interface StackItem { @@ -44,6 +56,7 @@ export interface StackItem { // Instruction pointer at which we can access the logged variable ip: number; frameBytecode?: string; // active bytecode frame in which this stack item is available + frameId?: string; // compiler-assigned helper/root frame identifier // Operations to apply to the debug state at the specified instruction pointer to make sure that the variable is // on the correct position on the stack. This is used when we're optimising bytecode where the logged variable is // an intermediate result that existed in the unoptimised bytecode, but no longer exists in the optimised bytecode. @@ -57,7 +70,9 @@ export interface RequireStatement { line: number; // line in the source code message?: string; // custom message for failing `require` statement frameBytecode?: string; // active bytecode frame in which this require executes + frameId?: string; // compiler-assigned helper/root frame identifier location?: LocationI; // source location of the full require statement + sourceFile?: string; // source file in which this require executes } export interface Artifact { diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index d5a6e52e..5c71e173 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -201,6 +201,7 @@ export interface LocationI { line: number, column: number }; + sourceFile?: string; } export type SingleLocationData = {