Skip to content
Merged
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
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"lshift",
"LSHIFTNUM",
"LSHIFTBIN",
"math",
"mecenas",
"meep",
"minimaldata",
Expand Down
66 changes: 65 additions & 1 deletion packages/cashc/src/Errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Type, PrimitiveType } from '@cashscript/utils';
import {
IdentifierNode,
ImportNode,
FunctionDefinitionNode,
VariableDefinitionNode,
ParameterNode,
Expand Down Expand Up @@ -69,7 +70,7 @@ export class InvalidSymbolTypeError extends CashScriptError {
public node: IdentifierNode,
public expected: SymbolType,
) {
super(node, `Found symbol ${node.name} with type ${node.definition?.symbolType} where type ${expected} was expected`);
super(node, `Found symbol ${node.name} with type ${node.symbol?.symbolType} where type ${expected} was expected`);
}
}

Expand All @@ -83,6 +84,22 @@ export class FunctionRedefinitionError extends RedefinitionError {
}
}

export class MissingContractError extends Error {
constructor() {
super('Source file does not contain a contract definition');
this.name = this.constructor.name;
}
}

export class ImportResolutionError extends CashScriptError {
constructor(
public node: ImportNode,
message: string,
) {
super(node, message);
}
}

export class VariableRedefinitionError extends RedefinitionError {
constructor(
public node: VariableDefinitionNode | ParameterNode,
Expand Down Expand Up @@ -123,6 +140,43 @@ export class FinalRequireStatementError extends CashScriptError {
}
}

export class UnusedFunctionReturnError extends CashScriptError {
constructor(
public node: FunctionCallNode,
) {
super(node, `Return value of ${node.identifier.name} must be used; only void functions may be called as a statement`);
}
}

export class MissingReturnError extends CashScriptError {
constructor(
public node: Node,
) {
super(node, 'A value-returning function must end with a return statement');
}
}

export class MisplacedReturnError extends CashScriptError {
constructor(
public node: StatementNode,
) {
super(node, 'A return statement is only allowed as the final statement of a function body');
}
}

export class UnsafeFunctionOperationError extends CashScriptError {
constructor(
node: Node,
operation: string,
) {
super(
node,
`'${operation}' cannot be used inside a user-defined function. Use it directly in a `
+ 'contract function instead, or pass the resulting value into the function as a parameter',
);
}
}

export class TypeError extends CashScriptError {
constructor(
node: Node,
Expand All @@ -134,6 +188,16 @@ export class TypeError extends CashScriptError {
}
}

export class ReturnTypeError extends TypeError {
constructor(
node: Node,
actual?: Type,
expected?: Type,
) {
super(node, actual, expected, `Cannot return type '${actual}' from a function with return type '${expected}'`);
}
}

export class InvalidParameterTypeError extends TypeError {
constructor(
node: FunctionCallNode | RequireNode | InstantiationNode,
Expand Down
1 change: 1 addition & 0 deletions packages/cashc/src/artifact/Artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function generateArtifact(
fingerprint: string,
): Artifact {
const { contract } = ast;
if (!contract) throw new Error('Internal error: cannot generate an artifact for a source file with no contract');

const constructorInputs = contract.parameters
.map((parameter) => ({ name: parameter.name, type: parameter.type.toString() }));
Expand Down
52 changes: 50 additions & 2 deletions packages/cashc/src/ast/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ export interface Typed {
type: Type;
}

export enum FunctionKind {
CONTRACT = 'contract',
GLOBAL = 'global',
}

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

constructor(
public contract: ContractNode,
public contract?: ContractNode,
public functions: FunctionDefinitionNode[] = [],
public imports: ImportNode[] = [],
) {
super();
}
Expand All @@ -33,6 +43,18 @@ export class SourceFileNode extends Node {
}
}

export class ImportNode extends Node {
constructor(
public path: string,
) {
super();
}

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

export class ContractNode extends Node implements Named {
symbolTable?: SymbolTable;

Expand All @@ -54,9 +76,11 @@ export class FunctionDefinitionNode extends Node implements Named {
opRolls: Map<string, IdentifierNode> = new Map();

constructor(
public kind: FunctionKind,
public name: string,
public parameters: ParameterNode[],
public body: BlockNode,
public returnType?: Type,
) {
super();
}
Expand Down Expand Up @@ -168,6 +192,30 @@ export class ConsoleStatementNode extends NonControlStatementNode {
}
}

export class FunctionCallStatementNode extends NonControlStatementNode {
constructor(
public functionCall: FunctionCallNode,
) {
super();
}

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

export class ReturnNode extends NonControlStatementNode {
constructor(
public expression: ExpressionNode,
) {
super();
}

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

export class BranchNode extends ControlStatementNode {
constructor(
public condition: ExpressionNode,
Expand Down Expand Up @@ -362,7 +410,7 @@ export class ArrayNode extends ExpressionNode {
}

export class IdentifierNode extends ExpressionNode implements Named {
definition?: Symbol;
symbol?: Symbol;

constructor(
public name: string,
Expand Down
74 changes: 66 additions & 8 deletions packages/cashc/src/ast/AstBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ParseTree, ParseTreeVisitor } from 'antlr4';
import { hexToBin } from '@bitauth/libauth';
import { parseType } from '@cashscript/utils';
import { parseType, Type } from '@cashscript/utils';
import semver from 'semver';
import {
Node,
SourceFileNode,
ImportNode,
ContractNode,
ParameterNode,
VariableDefinitionNode,
FunctionDefinitionNode,
FunctionKind,
AssignNode,
IdentifierNode,
BranchNode,
Expand All @@ -28,20 +30,26 @@ import {
ArrayNode,
TupleIndexOpNode,
RequireNode,
ReturnNode,
InstantiationNode,
TupleAssignmentNode,
NullaryOpNode,
ConsoleStatementNode,
ConsoleParameterNode,
FunctionCallStatementNode,
SliceNode,
DoWhileNode,
WhileNode,
ForNode,
} from './AST.js';
import { UnaryOperator, BinaryOperator, NullaryOperator } from './Operator.js';
import type {
ImportDirectiveContext,
ContractDefinitionContext,
FunctionDefinitionContext,
ContractFunctionDefinitionContext,
GlobalFunctionDefinitionContext,
ReturnStatementContext,
FunctionCallStatementContext,
VariableDefinitionContext,
TupleAssignmentContext,
ParameterContext,
Expand Down Expand Up @@ -110,12 +118,34 @@ export default class AstBuilder
this.processPragma(pragma);
});

const contract = this.visit(ctx.contractDefinition()) as ContractNode;
const sourceFileNode = new SourceFileNode(contract);
const imports = ctx.importDirective_list().map((directive) => this.visit(directive) as ImportNode);

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

ctx.topLevelDefinition_list().forEach((def) => {
if (def.globalFunctionDefinition()) {
functions.push(this.visit(def.globalFunctionDefinition()) as FunctionDefinitionNode);
} else if (def.contractDefinition()) {
if (contract) {
throw new ParseError('A source file may define at most one contract', Location.fromCtx(def.contractDefinition()));
}
contract = this.visit(def.contractDefinition()) as ContractNode;
}
});

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

visitImportDirective(ctx: ImportDirectiveContext): ImportNode {
const raw = ctx.StringLiteral().getText();
const importNode = new ImportNode(raw.substring(1, raw.length - 1));
importNode.location = Location.fromCtx(ctx);
return importNode;
}

processPragma(ctx: PragmaDirectiveContext): void {
const pragmaName = getPragmaName(ctx.pragmaName().getText());
if (pragmaName !== PragmaName.CASHSCRIPT) throw new Error(); // Shouldn't happen
Expand All @@ -135,17 +165,31 @@ export default class AstBuilder
visitContractDefinition(ctx: ContractDefinitionContext): ContractNode {
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 functions = ctx.contractFunctionDefinition_list()
.map((f) => this.visit(f) as FunctionDefinitionNode);
const contract = new ContractNode(name, parameters, functions);
contract.location = Location.fromCtx(ctx);
return contract;
}

visitFunctionDefinition(ctx: FunctionDefinitionContext): FunctionDefinitionNode {
visitContractFunctionDefinition(ctx: ContractFunctionDefinitionContext): FunctionDefinitionNode {
return this.buildFunctionDefinition(ctx, FunctionKind.CONTRACT);
}

visitGlobalFunctionDefinition(ctx: GlobalFunctionDefinitionContext): FunctionDefinitionNode {
const returnType = ctx.typeName() ? parseType(ctx.typeName().getText()) : undefined;
return this.buildFunctionDefinition(ctx, FunctionKind.GLOBAL, returnType);
}

private buildFunctionDefinition(
ctx: ContractFunctionDefinitionContext | GlobalFunctionDefinitionContext,
kind: FunctionKind,
returnType?: Type,
): FunctionDefinitionNode {
const name = ctx.Identifier().getText();
const parameters = ctx.parameterList().parameter_list().map((p) => this.visit(p) as ParameterNode);
const body = this.visit(ctx.functionBody());
const functionDefinition = new FunctionDefinitionNode(name, parameters, body);
const body = this.visit(ctx.functionBody()) as BlockNode;
const functionDefinition = new FunctionDefinitionNode(kind, name, parameters, body, returnType);
functionDefinition.location = Location.fromCtx(ctx);
return functionDefinition;
}
Expand Down Expand Up @@ -260,6 +304,20 @@ export default class AstBuilder
return require;
}

visitReturnStatement(ctx: ReturnStatementContext): ReturnNode {
const expression = this.visit(ctx.expression());
const returnNode = new ReturnNode(expression);
returnNode.location = Location.fromCtx(ctx);
return returnNode;
}

visitFunctionCallStatement(ctx: FunctionCallStatementContext): FunctionCallStatementNode {
const functionCall = this.visit(ctx.functionCall()) as FunctionCallNode;
const node = new FunctionCallStatementNode(functionCall);
node.location = Location.fromCtx(ctx);
return node;
}

visitIfStatement(ctx: IfStatementContext): BranchNode {
const condition = this.visit(ctx.expression());
const ifBlock = this.visit(ctx._ifBlock) as StatementNode;
Expand Down
Loading
Loading