Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.1",
"version": "0.2",
"language": "en-GB",
"words": [
"aave",
Expand All @@ -11,6 +11,7 @@
"authchain",
"anyhedge",
"anyonecanpay",
"backpointer",
"badlength",
"bchjs",
"bchreg",
Expand All @@ -31,6 +32,7 @@
"boolor",
"bytecode",
"bytesize",
"callees",
"cashaddress",
"cashc",
"cashproof",
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CLAUDE.md

NEVER stage changes, just leave them in the working directory.
NEVER stage or unstage changes, just leave them in the working directory.

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Expand Down
43 changes: 15 additions & 28 deletions packages/cashc/src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import {
IdentifierNode,
ImportNode,
FunctionDefinitionNode,
ConstantDefinitionNode,
VariableDefinitionNode,
ParameterNode,
Node,
FunctionCallNode,
BinaryOpNode,
UnaryOpNode,
TimeOpNode,
CastNode,
AssignNode,
BranchNode,
ArrayNode,
TupleIndexOpNode,
RequireNode,
Expand Down Expand Up @@ -73,13 +72,12 @@ export class InvalidSymbolTypeError extends CashScriptError {
}
}

export class RedefinitionError extends CashScriptError { }

export class FunctionRedefinitionError extends RedefinitionError {
export class RedefinitionError extends CashScriptError {
constructor(
public node: FunctionDefinitionNode,
public node: Node,
public identifier: string,
) {
super(node, `Redefinition of function ${node.name}`);
super(node, `Redefinition of identifier ${identifier}`);
}
}

Expand All @@ -99,14 +97,6 @@ export class ImportResolutionError extends CashScriptError {
}
}

export class VariableRedefinitionError extends RedefinitionError {
constructor(
public node: VariableDefinitionNode | ParameterNode,
) {
super(node, `Redefinition of variable ${node.name}`);
}
}

export class UnusedVariableError extends CashScriptError {
constructor(
public symbol: Symbol,
Expand Down Expand Up @@ -264,27 +254,24 @@ export class CastTypeError extends TypeError {

export class AssignTypeError extends TypeError {
constructor(
node: AssignNode | VariableDefinitionNode,
node: AssignNode | VariableDefinitionNode | ConstantDefinitionNode,
) {
const expected = node instanceof AssignNode ? node.identifier.type : node.type;
super(node, node.expression.type, expected, `Type '${node.expression.type}' can not be assigned to variable of type '${expected}'`);
}
}

export class ConstantConditionError extends CashScriptError {
constructor(
node: BranchNode | RequireNode,
res: boolean,
) {
super(node, `Condition always evaluates to ${res}`);
const expression = node instanceof ConstantDefinitionNode ? node.value : node.expression;
const target = node instanceof ConstantDefinitionNode ? `constant '${node.name}'` : 'variable';
super(node, expression.type, expected, `Type '${expression.type}' can not be assigned to ${target} of type '${expected}'`);
}
}

export class ConstantModificationError extends CashScriptError {
constructor(node: VariableDefinitionNode | ConstantDefinitionNode);
constructor(node: Node, name: string);
constructor(
node: VariableDefinitionNode,
node: Node,
name?: string,
) {
super(node, `Tried to modify immutable variable '${node.name}'`);
const constantName = name ?? (node as VariableDefinitionNode | ConstantDefinitionNode).name;
super(node, `Tried to modify immutable variable '${constantName}'`);
}
}

Expand Down
32 changes: 30 additions & 2 deletions packages/cashc/src/ast/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ export enum FunctionKind {
}

export class SourceFileNode extends Node {
// The source file's scope: the table of global functions (each symbol carries its VM function-table id).
// The source file's scope: the table of global definitions (shared definitions carry a VM function-table id).
symbolTable?: SymbolTable;

constructor(
public contract?: ContractNode,
public functions: FunctionDefinitionNode[] = [],
public constants: ConstantDefinitionNode[] = [],
public imports: ImportNode[] = [],
public pragmas: string[] = [],
) {
Expand All @@ -44,6 +45,24 @@ export class SourceFileNode extends Node {
}
}

export class ConstantDefinitionNode extends Node implements Named, Typed {
// Source provenance for debugging. Set on imported constants, left undefined for constants in the contract's own file.
sourceCode?: string;
sourceFile?: string;

constructor(
public type: Type,
public name: string,
public value: LiteralNode,
) {
super();
}

accept<T>(visitor: AstVisitor<T>): T {
return visitor.visitConstantDefinition(this);
}
}

export class ImportNode extends Node {
constructor(
public path: string,
Expand Down Expand Up @@ -76,6 +95,9 @@ export class FunctionDefinitionNode extends Node implements Named {
symbolTable?: SymbolTable;
opRolls: Map<string, IdentifierNode> = new Map();

// Set when this is the synthetic zero-argument function used to lower a global constant.
constant?: ConstantDefinitionNode;

// Source provenance for debugging. Set on imported functions, left undefined for functions in the contract's own file.
sourceCode?: string;
sourceFile?: string;
Expand All @@ -98,6 +120,7 @@ export class FunctionDefinitionNode extends Node implements Named {
export class ParameterNode extends Node implements Named, Typed {
constructor(
public type: Type,
public modifiers: string[],
public name: string,
) {
super();
Expand All @@ -108,14 +131,16 @@ export class ParameterNode extends Node implements Named, Typed {
}
}

export type DefinitionNode = VariableDefinitionNode | ConstantDefinitionNode | FunctionDefinitionNode | ParameterNode;

export abstract class StatementNode extends Node { }
export abstract class ControlStatementNode extends StatementNode { }
export abstract class NonControlStatementNode extends StatementNode { }

export class VariableDefinitionNode extends NonControlStatementNode implements Named, Typed {
constructor(
public type: Type,
public modifier: string[],
public modifiers: string[],
public name: string,
public expression: ExpressionNode,
) {
Expand Down Expand Up @@ -435,6 +460,9 @@ export class IdentifierNode extends ExpressionNode implements Named {
export abstract class LiteralNode<T = any> extends ExpressionNode {
public value: T;

// Set when this is the synthetic literal node used to represent a global constant
constant?: ConstantDefinitionNode;

toString(): string {
return `${this.value}`;
}
Expand Down
18 changes: 16 additions & 2 deletions packages/cashc/src/ast/AstBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ParameterNode,
VariableDefinitionNode,
FunctionDefinitionNode,
ConstantDefinitionNode,
FunctionKind,
AssignNode,
IdentifierNode,
Expand Down Expand Up @@ -47,6 +48,7 @@ import type {
ContractDefinitionContext,
ContractFunctionDefinitionContext,
GlobalFunctionDefinitionContext,
ConstantDefinitionContext,
ReturnStatementContext,
FunctionCallStatementContext,
VariableDefinitionContext,
Expand Down Expand Up @@ -117,11 +119,14 @@ export default class AstBuilder
const imports = ctx.importDirective_list().map((directive) => this.visit(directive) as ImportNode);

const functions: FunctionDefinitionNode[] = [];
const constants: ConstantDefinitionNode[] = [];
let contract: ContractNode | undefined;

ctx.topLevelDefinition_list().forEach((def) => {
if (def.globalFunctionDefinition()) {
functions.push(this.visit(def.globalFunctionDefinition()) as FunctionDefinitionNode);
} else if (def.constantDefinition()) {
constants.push(this.visit(def.constantDefinition()) as ConstantDefinitionNode);
} else if (def.contractDefinition()) {
if (contract) {
throw new ParseError('A source file may define at most one contract', Location.fromCtx(def.contractDefinition()));
Expand All @@ -130,11 +135,20 @@ export default class AstBuilder
}
});

const sourceFileNode = new SourceFileNode(contract, functions, imports, pragmas);
const sourceFileNode = new SourceFileNode(contract, functions, constants, imports, pragmas);
sourceFileNode.location = Location.fromCtx(ctx);
return sourceFileNode;
}

visitConstantDefinition(ctx: ConstantDefinitionContext): ConstantDefinitionNode {
const type = parseType(ctx.typeName().getText());
const name = ctx.Identifier().getText();
const value = this.createLiteral(ctx.literal());
const constantDefinition = new ConstantDefinitionNode(type, name, value);
constantDefinition.location = Location.fromCtx(ctx);
return constantDefinition;
}

visitImportDirective(ctx: ImportDirectiveContext): ImportNode {
const raw = ctx.StringLiteral().getText();
const importNode = new ImportNode(raw.substring(1, raw.length - 1));
Expand Down Expand Up @@ -196,7 +210,7 @@ export default class AstBuilder
visitParameter(ctx: ParameterContext): ParameterNode {
const type = parseType(ctx.typeName().getText());
const name = ctx.Identifier().getText();
const parameter = new ParameterNode(type, name);
const parameter = new ParameterNode(type, [], name);
parameter.location = Location.fromCtx(ctx);
return parameter;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/cashc/src/ast/AstTraversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ParameterNode,
VariableDefinitionNode,
FunctionDefinitionNode,
ConstantDefinitionNode,
AssignNode,
IdentifierNode,
BranchNode,
Expand All @@ -29,6 +30,7 @@ import {
NullaryOpNode,
ConsoleStatementNode,
ConsoleParameterNode,
LiteralNode,
FunctionCallStatementNode,
SliceNode,
DoWhileNode,
Expand All @@ -39,6 +41,7 @@ import AstVisitor from './AstVisitor.js';

export default class AstTraversal extends AstVisitor<Node> {
visitSourceFile(node: SourceFileNode): Node {
node.constants = this.visitList(node.constants) as ConstantDefinitionNode[];
node.functions = this.visitList(node.functions) as FunctionDefinitionNode[];
node.contract = this.visitOptional(node.contract) as ContractNode | undefined;
return node;
Expand All @@ -60,6 +63,11 @@ export default class AstTraversal extends AstVisitor<Node> {
return node;
}

visitConstantDefinition(node: ConstantDefinitionNode): Node {
node.value = this.visit(node.value) as LiteralNode;
return node;
}

visitParameter(node: ParameterNode): Node {
return node;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/cashc/src/ast/AstVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ParameterNode,
VariableDefinitionNode,
FunctionDefinitionNode,
ConstantDefinitionNode,
AssignNode,
IdentifierNode,
BranchNode,
Expand Down Expand Up @@ -39,6 +40,7 @@ export default abstract class AstVisitor<T> {
abstract visitImport(node: ImportNode): T;
abstract visitContract(node: ContractNode): T;
abstract visitFunctionDefinition(node: FunctionDefinitionNode): T;
abstract visitConstantDefinition(node: ConstantDefinitionNode): T;
abstract visitParameter(node: ParameterNode): T;
abstract visitVariableDefinition(node: VariableDefinitionNode): T;
abstract visitTupleAssignment(node: TupleAssignmentNode): T;
Expand Down
15 changes: 9 additions & 6 deletions packages/cashc/src/ast/SymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import {
VariableDefinitionNode,
ParameterNode,
FunctionDefinitionNode,
ConstantDefinitionNode,
IdentifierNode,
Node,
DefinitionNode,
} from './AST.js';
import { functionReturnType } from '../utils.js';

Expand All @@ -15,7 +16,7 @@ export class Symbol {
public name: string,
public type: Type,
public symbolType: SymbolType,
public definition?: Node,
public definition?: DefinitionNode,
public parameters?: Type[],
public bytecode?: Script,
public functionId?: number,
Expand All @@ -25,6 +26,10 @@ export class Symbol {
return new Symbol(node.name, node.type, SymbolType.VARIABLE, node);
}

static constant(node: ConstantDefinitionNode): Symbol {
return new Symbol(node.name, node.type, SymbolType.VARIABLE, node);
}

static global(name: string, type: Type): Symbol {
return new Symbol(name, type, SymbolType.VARIABLE);
}
Expand All @@ -33,11 +38,9 @@ export class Symbol {
return new Symbol(name, returnType, SymbolType.FUNCTION, undefined, parameters, bytecode);
}

static userFunction(node: FunctionDefinitionNode, functionId: number): Symbol {
static userFunction(node: FunctionDefinitionNode): Symbol {
const parameterTypes = node.parameters.map((parameter) => parameter.type);
const symbol = new Symbol(node.name, functionReturnType(node.returnTypes), SymbolType.FUNCTION, node, parameterTypes);
symbol.setFunctionId(functionId);
return symbol;
return new Symbol(node.name, functionReturnType(node.returnTypes), SymbolType.FUNCTION, node, parameterTypes);
}

setFunctionId(functionId: number): void {
Expand Down
Loading
Loading