Repository navigation
Expand file tree
/
Copy pathcompiler.ts
More file actions
123 lines (108 loc) · 5.1 KB
/
Copy pathcompiler.ts
File metadata and controls
123 lines (108 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { binToHex } from '@bitauth/libauth';
import {
Artifact,
CompilerOptions,
computeBytecodeFingerprintWithConstructorArgs,
generateSourceMap,
generateSourceTags,
optimiseBytecode,
optimiseBytecodeOld,
scriptToAsm,
scriptToBytecode,
sourceMapToLocationData,
} from '@cashscript/utils';
import fs, { PathLike } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { generateArtifact } from './artifact/Artifact.js';
import { Ast } from './ast/AST.js';
import { CashScriptErrorListener } from './ast/error-listeners.js';
import { MissingContractError } from './Errors.js';
import { parseCode } from './parser.js';
import { resolveDependencies } from './dependency-resolution.js';
import GenerateTargetTraversal from './generation/GenerateTargetTraversal.js';
import SymbolTableTraversal from './semantic/SymbolTableTraversal.js';
import TypeCheckTraversal from './semantic/TypeCheckTraversal.js';
import EnsureFinalRequireTraversal from './semantic/EnsureFinalRequireTraversal.js';
import EnsureFunctionsSafeTraversal from './semantic/EnsureFunctionsSafeTraversal.js';
import InjectLocktimeGuardTraversal from './semantic/InjectLocktimeGuardTraversal.js';
import DeadCodeEliminationTraversal from './semantic/DeadCodeEliminationTraversal.js';
export const DEFAULT_COMPILER_OPTIONS: CompilerOptions = {
enforceFunctionParameterTypes: true,
enforceLocktimeGuard: true,
};
export interface CompileOptions extends CompilerOptions {
errorListener?: CashScriptErrorListener;
basePath?: string;
}
/**
* Compile a CashScript source string to an {@link Artifact}.
*
* @param code - The CashScript source code to compile.
* @param compilerOptions - Optional compiler options that override the defaults.
* @returns The compiled CashScript artifact, including ABI, bytecode and debug information.
* @throws If the source code contains a syntax, semantic, or type error.
*/
export function compileString(code: string, compilerOptions: CompileOptions = {}): Artifact {
const { errorListener, basePath, ...artifactCompilerOptions } = compilerOptions;
const mergedCompilerOptions = { ...DEFAULT_COMPILER_OPTIONS, ...artifactCompilerOptions };
// Lexing + parsing
let ast = parseCode(code, errorListener);
ast = resolveDependencies(ast, { basePath, errorListener }) as Ast;
if (!ast.contract) throw new MissingContractError();
const constructorParamLength = ast.contract.parameters.length;
// Semantic analysis
ast = ast.accept(new SymbolTableTraversal()) as Ast;
ast = ast.accept(new TypeCheckTraversal()) as Ast;
ast = ast.accept(new EnsureFunctionsSafeTraversal()) as Ast;
ast = ast.accept(new EnsureFinalRequireTraversal()) as Ast;
if (mergedCompilerOptions.enforceLocktimeGuard) {
ast = ast.accept(new InjectLocktimeGuardTraversal()) as Ast;
}
// Dead-code elimination: drop global functions that are never invoked before code generation
ast = ast.accept(new DeadCodeEliminationTraversal()) as Ast;
// Code generation
const traversal = new GenerateTargetTraversal(mergedCompilerOptions);
ast = ast.accept(traversal) as Ast;
// Bytecode optimisation
const optimisedBytecodeOld = optimiseBytecodeOld(traversal.output);
const optimisationResult = optimiseBytecode(
traversal.output,
sourceMapToLocationData(traversal.sourceMap),
traversal.consoleLogs,
traversal.requires,
traversal.sourceTags,
constructorParamLength,
);
if (scriptToAsm(optimisedBytecodeOld) !== scriptToAsm(optimisationResult.script)) {
console.error(scriptToAsm(optimisedBytecodeOld));
console.error(scriptToAsm(optimisationResult.script));
throw new Error('New bytecode optimisation is not backwards compatible, please report this issue to the CashScript team');
}
// Attach debug information
const sourceTags = generateSourceTags(optimisationResult.sourceTags);
const debug = {
bytecode: binToHex(scriptToBytecode(optimisationResult.script)),
sourceMap: generateSourceMap(optimisationResult.locationData),
logs: optimisationResult.logs,
requires: optimisationResult.requires,
...(sourceTags ? { sourceTags } : {}),
...(traversal.frames.length > 0 ? { functions: traversal.frames } : {}),
};
const fingerprint = computeBytecodeFingerprintWithConstructorArgs(optimisationResult.script, constructorParamLength);
return generateArtifact(ast, optimisationResult.script, code, debug, mergedCompilerOptions, fingerprint);
}
/**
* Read a `.cash` source file from disk and compile it to an `Artifact`.
*
* @param codeFile - The path to the `.cash` source file.
* @param compilerOptions - Optional compiler options that override the defaults.
* @returns The compiled CashScript artifact.
* @throws If the file cannot be read, or if the source contains a compilation error.
*/
export function compileFile(codeFile: PathLike, compilerOptions: CompileOptions = {}): Artifact {
const filePath = codeFile instanceof URL ? fileURLToPath(codeFile) : codeFile.toString();
const code = fs.readFileSync(filePath, { encoding: 'utf-8' });
const basePath = compilerOptions.basePath ?? path.dirname(filePath);
return compileString(code, { ...compilerOptions, basePath });
}