cashc 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Errors.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import { Type } from '@cashscript/utils';
2
2
  import { IdentifierNode, FunctionDefinitionNode, VariableDefinitionNode, ParameterNode, Node, FunctionCallNode, BinaryOpNode, UnaryOpNode, TimeOpNode, CastNode, AssignNode, BranchNode, ArrayNode, TupleIndexOpNode, RequireNode, InstantiationNode, StatementNode, ContractNode, ExpressionNode, SliceNode } from './ast/AST.js';
3
3
  import { Symbol, SymbolType } from './ast/SymbolTable.js';
4
- import { Location, Point } from './ast/Location.js';
4
+ import { Location } from './ast/Location.js';
5
5
  export declare class CashScriptError extends Error {
6
6
  node: Node;
7
7
  constructor(node: Node, message: string);
8
8
  }
9
9
  export declare class ParseError extends Error {
10
- constructor(message: string, location?: Point | Location);
10
+ location: Location;
11
+ constructor(message: string, location: Location);
11
12
  }
12
13
  export declare class UndefinedReferenceError extends CashScriptError {
13
14
  node: IdentifierNode;
package/dist/Errors.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { PrimitiveType } from '@cashscript/utils';
2
2
  import { BinaryOpNode, UnaryOpNode, TimeOpNode, AssignNode, TupleIndexOpNode, RequireNode, SliceNode, IntLiteralNode, } from './ast/AST.js';
3
- import { Point } from './ast/Location.js';
4
3
  import { BinaryOperator } from './ast/Operator.js';
5
4
  export class CashScriptError extends Error {
6
5
  constructor(node, message) {
@@ -14,12 +13,11 @@ export class CashScriptError extends Error {
14
13
  }
15
14
  export class ParseError extends Error {
16
15
  constructor(message, location) {
17
- const start = location instanceof Point ? location : location?.start;
18
- if (start) {
19
- message += ` at ${start}`;
20
- }
16
+ message += ` at ${location.start}`;
21
17
  super(message);
18
+ this.location = location;
22
19
  this.name = this.constructor.name;
20
+ this.location = location;
23
21
  }
24
22
  }
25
23
  export class UndefinedReferenceError extends CashScriptError {
@@ -0,0 +1,22 @@
1
+ import { ErrorListener, RecognitionException, Recognizer } from 'antlr4';
2
+ export interface CashScriptErrorListener {
3
+ syntaxError(recognizer: unknown, offendingSymbol: unknown, line: number, charPositionInLine: number, message: string, e?: unknown): void;
4
+ }
5
+ /**
6
+ * Error listener that forwards syntax errors to another listener and stores the first error.
7
+ */
8
+ export declare class ForwardingErrorListener extends ErrorListener<unknown> implements CashScriptErrorListener {
9
+ private readonly errorListener;
10
+ private firstError?;
11
+ constructor(errorListener: CashScriptErrorListener);
12
+ syntaxError(recognizer: unknown, offendingSymbol: unknown, line: number, charPositionInLine: number, message: string, e?: unknown): void;
13
+ throwFirstError(): void;
14
+ }
15
+ /**
16
+ * ANTLR Error Listener that immediately throws on error. This is used so that
17
+ * ANTLR doesn't attempt any error recovery during lexing/parsing and fails early.
18
+ */
19
+ export declare class ThrowingErrorListener<TSymbol> extends ErrorListener<TSymbol> {
20
+ static readonly INSTANCE: ThrowingErrorListener<unknown>;
21
+ syntaxError(_recognizer: Recognizer<TSymbol>, offendingSymbol: TSymbol, line: number, charPositionInLine: number, message: string, _e?: RecognitionException): void;
22
+ }
@@ -0,0 +1,91 @@
1
+ import { ErrorListener, Token } from 'antlr4';
2
+ import { ParseError } from '../Errors.js';
3
+ import { Location, Point } from './Location.js';
4
+ /**
5
+ * Error listener that forwards syntax errors to another listener and stores the first error.
6
+ */
7
+ export class ForwardingErrorListener extends ErrorListener {
8
+ constructor(errorListener) {
9
+ super();
10
+ this.errorListener = errorListener;
11
+ }
12
+ syntaxError(recognizer, offendingSymbol, line, charPositionInLine, message, e) {
13
+ const normalisedMessage = normaliseSyntaxErrorMessage(message, offendingSymbol);
14
+ this.firstError ??= createParseError(line, charPositionInLine, normalisedMessage, offendingSymbol);
15
+ this.errorListener.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, normalisedMessage, e);
16
+ }
17
+ throwFirstError() {
18
+ if (this.firstError)
19
+ throw this.firstError;
20
+ }
21
+ }
22
+ /**
23
+ * ANTLR Error Listener that immediately throws on error. This is used so that
24
+ * ANTLR doesn't attempt any error recovery during lexing/parsing and fails early.
25
+ */
26
+ export class ThrowingErrorListener extends ErrorListener {
27
+ syntaxError(_recognizer, offendingSymbol, line, charPositionInLine, message, _e) {
28
+ const normalisedMessage = normaliseSyntaxErrorMessage(message, offendingSymbol);
29
+ throw createParseError(line, charPositionInLine, normalisedMessage, offendingSymbol);
30
+ }
31
+ }
32
+ ThrowingErrorListener.INSTANCE = new ThrowingErrorListener();
33
+ function createParseError(line, charPositionInLine, message, offendingSymbol) {
34
+ const capitalisedMessage = message.charAt(0).toUpperCase() + message.slice(1);
35
+ const token = getToken(offendingSymbol);
36
+ const location = getTokenLocation(token) ?? createEmptyLocation(line, charPositionInLine);
37
+ return new ParseError(capitalisedMessage, location);
38
+ }
39
+ function normaliseSyntaxErrorMessage(message, offendingSymbol) {
40
+ const token = getToken(offendingSymbol);
41
+ const tokenText = getTokenText(token);
42
+ if (!tokenText)
43
+ return message;
44
+ // There are 2 common error messages that we need to normalise:
45
+ const noViableAlternativeInput = getNoViableAlternativeInput(message);
46
+ const extraneousInput = isExtraneousInput(message, tokenText);
47
+ if (isBoundedBytesExpressionError(message, tokenText, noViableAlternativeInput)) {
48
+ return boundedBytesCastMessage(tokenText);
49
+ }
50
+ if (noViableAlternativeInput !== undefined || extraneousInput) {
51
+ return `Unexpected token '${tokenText}'`;
52
+ }
53
+ return message;
54
+ }
55
+ function isBoundedBytesExpressionError(message, tokenText, noViableAlternativeInput) {
56
+ if (!isBoundedBytesToken(tokenText))
57
+ return false;
58
+ return noViableAlternativeInput?.includes(`(${tokenText}`) || isExtraneousInput(message, tokenText);
59
+ }
60
+ function isBoundedBytesToken(tokenText) {
61
+ return tokenText === 'byte' || /^bytes[1-9][0-9]*$/.test(tokenText);
62
+ }
63
+ function boundedBytesCastMessage(typeName) {
64
+ const bound = typeName === 'byte' ? 1 : Number(typeName.slice('bytes'.length));
65
+ const unsafeCast = typeName === 'byte' ? 'unsafe_byte' : `unsafe_${typeName}`;
66
+ return `Invalid bounded bytes cast '${typeName}(...)'. Use 'toPaddedBytes(value, ${bound})' to convert an int or '${unsafeCast}(value)' for semantic bytes casts`;
67
+ }
68
+ function getNoViableAlternativeInput(message) {
69
+ return message.match(/^no viable alternative at input '([\s\S]*)'$/)?.[1];
70
+ }
71
+ function isExtraneousInput(message, tokenText) {
72
+ return message.startsWith(`extraneous input '${tokenText}'`);
73
+ }
74
+ function getToken(offendingSymbol) {
75
+ return offendingSymbol instanceof Token ? offendingSymbol : undefined;
76
+ }
77
+ function getTokenText(token) {
78
+ if (!token || token.type === Token.EOF || typeof token.text !== 'string' || token.text === '<EOF>') {
79
+ return undefined;
80
+ }
81
+ return token.text;
82
+ }
83
+ function getTokenLocation(token) {
84
+ if (!token || !getTokenText(token))
85
+ return undefined;
86
+ return Location.fromToken(token);
87
+ }
88
+ function createEmptyLocation(line, column) {
89
+ return new Location(new Point(line, column), new Point(line, column));
90
+ }
91
+ //# sourceMappingURL=error-listeners.js.map
@@ -1,7 +1,11 @@
1
1
  import { Artifact, CompilerOptions } from '@cashscript/utils';
2
2
  import { PathLike } from 'fs';
3
3
  import { Ast } from './ast/AST.js';
4
+ import { CashScriptErrorListener } from './ast/error-listeners.js';
4
5
  export declare const DEFAULT_COMPILER_OPTIONS: CompilerOptions;
6
+ export interface CompileOptions extends CompilerOptions {
7
+ errorListener?: CashScriptErrorListener;
8
+ }
5
9
  /**
6
10
  * Compile a CashScript source string to an {@link Artifact}.
7
11
  *
@@ -10,7 +14,7 @@ export declare const DEFAULT_COMPILER_OPTIONS: CompilerOptions;
10
14
  * @returns The compiled CashScript artifact, including ABI, bytecode and debug information.
11
15
  * @throws If the source code contains a syntax, semantic, or type error.
12
16
  */
13
- export declare function compileString(code: string, compilerOptions?: CompilerOptions): Artifact;
17
+ export declare function compileString(code: string, compilerOptions?: CompileOptions): Artifact;
14
18
  /**
15
19
  * Read a `.cash` source file from disk and compile it to an `Artifact`.
16
20
  *
@@ -19,5 +23,5 @@ export declare function compileString(code: string, compilerOptions?: CompilerOp
19
23
  * @returns The compiled CashScript artifact.
20
24
  * @throws If the file cannot be read, or if the source contains a compilation error.
21
25
  */
22
- export declare function compileFile(codeFile: PathLike, compilerOptions?: CompilerOptions): Artifact;
23
- export declare function parseCode(code: string): Ast;
26
+ export declare function compileFile(codeFile: PathLike, compilerOptions?: CompileOptions): Artifact;
27
+ export declare function parseCode(code: string, errorListener?: CashScriptErrorListener): Ast;
package/dist/compiler.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { CharStream, CommonTokenStream } from 'antlr4';
2
2
  import { binToHex } from '@bitauth/libauth';
3
- import { computeBytecodeFingerprintWithConstructorArgs, generateSourceMap, generateSourceTags, optimiseBytecode, optimiseBytecodeOld, scriptToAsm, scriptToBytecode, sourceMapToLocationData } from '@cashscript/utils';
3
+ import { computeBytecodeFingerprintWithConstructorArgs, generateSourceMap, generateSourceTags, optimiseBytecode, optimiseBytecodeOld, scriptToAsm, scriptToBytecode, sourceMapToLocationData, } from '@cashscript/utils';
4
4
  import fs from 'fs';
5
5
  import { generateArtifact } from './artifact/Artifact.js';
6
6
  import AstBuilder from './ast/AstBuilder.js';
7
- import ThrowingErrorListener from './ast/ThrowingErrorListener.js';
7
+ import { ThrowingErrorListener, ForwardingErrorListener } from './ast/error-listeners.js';
8
8
  import GenerateTargetTraversal from './generation/GenerateTargetTraversal.js';
9
9
  import CashScriptLexer from './grammar/CashScriptLexer.js';
10
10
  import CashScriptParser from './grammar/CashScriptParser.js';
@@ -25,9 +25,10 @@ export const DEFAULT_COMPILER_OPTIONS = {
25
25
  * @throws If the source code contains a syntax, semantic, or type error.
26
26
  */
27
27
  export function compileString(code, compilerOptions = {}) {
28
- const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptions };
28
+ const { errorListener, ...artifactCompilerOptions } = compilerOptions;
29
+ const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...artifactCompilerOptions };
29
30
  // Lexing + parsing
30
- let ast = parseCode(code);
31
+ let ast = parseCode(code, errorListener);
31
32
  // Semantic analysis
32
33
  ast = ast.accept(new SymbolTableTraversal());
33
34
  ast = ast.accept(new TypeCheckTraversal());
@@ -71,18 +72,20 @@ export function compileFile(codeFile, compilerOptions = {}) {
71
72
  const code = fs.readFileSync(codeFile, { encoding: 'utf-8' });
72
73
  return compileString(code, compilerOptions);
73
74
  }
74
- export function parseCode(code) {
75
+ export function parseCode(code, errorListener = ThrowingErrorListener.INSTANCE) {
76
+ const syntaxErrorListener = new ForwardingErrorListener(errorListener);
75
77
  // Lexing (throwing on errors)
76
78
  const inputStream = new CharStream(code);
77
79
  const lexer = new CashScriptLexer(inputStream);
78
80
  lexer.removeErrorListeners();
79
- lexer.addErrorListener(ThrowingErrorListener.INSTANCE);
81
+ lexer.addErrorListener(syntaxErrorListener);
80
82
  const tokenStream = new CommonTokenStream(lexer);
81
83
  // Parsing (throwing on errors)
82
84
  const parser = new CashScriptParser(tokenStream);
83
85
  parser.removeErrorListeners();
84
- parser.addErrorListener(ThrowingErrorListener.INSTANCE);
86
+ parser.addErrorListener(syntaxErrorListener);
85
87
  const parseTree = parser.sourceFile();
88
+ syntaxErrorListener.throwFirstError();
86
89
  // AST building
87
90
  const ast = new AstBuilder(parseTree).build();
88
91
  return ast;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './Errors.js';
2
2
  export * as utils from '@cashscript/utils';
3
- export { compileFile, compileString } from './compiler.js';
4
- export declare const version = "0.13.0";
3
+ export { compileFile, compileString, type CompileOptions } from './compiler.js';
4
+ export * from './ast/Location.js';
5
+ export * from './ast/error-listeners.js';
6
+ export declare const version = "0.13.1";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './Errors.js';
2
2
  export * as utils from '@cashscript/utils';
3
3
  export { compileFile, compileString } from './compiler.js';
4
- export const version = '0.13.0';
4
+ export * from './ast/Location.js';
5
+ export * from './ast/error-listeners.js';
6
+ export const version = '0.13.1';
5
7
  //# sourceMappingURL=index.js.map
@@ -94,11 +94,13 @@ export default class SymbolTableTraversal extends AstTraversal {
94
94
  return node;
95
95
  }
96
96
  visitTupleAssignment(node) {
97
- [node.left, node.right].forEach(({ name, type }) => {
97
+ [node.left, node.right].forEach((variable) => {
98
+ const definition = createTupleVariableDefinition(node, variable);
99
+ const { name } = variable;
98
100
  if (this.symbolTables[0].get(name)) {
99
- throw new VariableRedefinitionError(new VariableDefinitionNode(type, [], name, node.tuple));
101
+ throw new VariableRedefinitionError(definition);
100
102
  }
101
- this.symbolTables[0].set(Symbol.variable(new VariableDefinitionNode(type, [], name, node.tuple)));
103
+ this.symbolTables[0].set(Symbol.variable(definition));
102
104
  });
103
105
  node.tuple = this.visit(node.tuple);
104
106
  return node;
@@ -141,4 +143,9 @@ export default class SymbolTableTraversal extends AstTraversal {
141
143
  return node;
142
144
  }
143
145
  }
146
+ function createTupleVariableDefinition(node, variable) {
147
+ const definition = new VariableDefinitionNode(variable.type, [], variable.name, node.tuple);
148
+ definition.location = node.location;
149
+ return definition;
150
+ }
144
151
  //# sourceMappingURL=SymbolTableTraversal.js.map
@@ -18,7 +18,9 @@ export default class TypeCheckTraversal extends AstTraversal {
18
18
  }
19
19
  const assignmentType = new TupleType(node.left.type, node.right.type);
20
20
  if (!implicitlyCastable(node.tuple.type, assignmentType)) {
21
- throw new AssignTypeError(new VariableDefinitionNode(assignmentType, [], node.left.name, node.tuple));
21
+ const syntheticAssignment = new VariableDefinitionNode(assignmentType, [], node.left.name, node.tuple);
22
+ syntheticAssignment.location = node.location;
23
+ throw new AssignTypeError(syntheticAssignment);
22
24
  }
23
25
  return node;
24
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashc",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Compile Bitcoin Cash contracts to Bitcoin Cash Script or artifacts",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@bitauth/libauth": "^3.1.0-next.8",
51
- "@cashscript/utils": "^0.13.0",
51
+ "@cashscript/utils": "^0.13.1",
52
52
  "antlr4": "^4.13.2",
53
53
  "commander": "^14.0.0",
54
54
  "semver": "^7.7.2"
@@ -64,5 +64,5 @@
64
64
  "typescript": "^5.9.2",
65
65
  "vitest": "^4.0.15"
66
66
  },
67
- "gitHead": "a3461662016a2f3dad36431ba5a2b148bcc4dc75"
67
+ "gitHead": "b5248b51a2ef93d750dcf916743eac9b79f62b5b"
68
68
  }
@@ -1,9 +0,0 @@
1
- import { ErrorListener, RecognitionException, Recognizer } from 'antlr4';
2
- /**
3
- * ANTLR Error Listener that immediately throws on error. This is used so that
4
- * ANTLR doesn't attempt any error recovery during lexing/parsing and fails early.
5
- */
6
- export default class ThrowingErrorListener<TSymbol> extends ErrorListener<TSymbol> {
7
- static readonly INSTANCE: ThrowingErrorListener<unknown>;
8
- syntaxError(recognizer: Recognizer<TSymbol>, offendingSymbol: TSymbol, line: number, charPositionInLine: number, message: string, e?: RecognitionException): void;
9
- }
@@ -1,17 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { ErrorListener } from 'antlr4';
3
- import { ParseError } from '../Errors.js';
4
- import { Point } from './Location.js';
5
- /**
6
- * ANTLR Error Listener that immediately throws on error. This is used so that
7
- * ANTLR doesn't attempt any error recovery during lexing/parsing and fails early.
8
- */
9
- class ThrowingErrorListener extends ErrorListener {
10
- syntaxError(recognizer, offendingSymbol, line, charPositionInLine, message, e) {
11
- const capitalisedMessage = message.charAt(0).toUpperCase() + message.slice(1);
12
- throw new ParseError(capitalisedMessage, new Point(line, charPositionInLine));
13
- }
14
- }
15
- ThrowingErrorListener.INSTANCE = new ThrowingErrorListener();
16
- export default ThrowingErrorListener;
17
- //# sourceMappingURL=ThrowingErrorListener.js.map