UNPKG

motoko

Version:

Compile and run Motoko smart contracts in Node.js or the browser.

336 lines (325 loc) 11.2 kB
import { CompilerNode, Node, RawScope, simplifyAST, setRootScope, getScope, getRawExp, AST, } from './ast'; import { Scope, file } from './file'; import { Package, PackageInfo, fetchPackage, installPackages, validatePackage, } from './package'; import { asciiToUtf8 } from './utils/asciiToUtf8'; import { resolveLib, resolveMain } from './utils/resolveEntryPoint'; export type Motoko = ReturnType<typeof wrapMotoko>; type Compiler = any; // TODO: generate from `js_of_ocaml`? export type Diagnostic = { source: string; range: { start: { line: number; character: number }; end: { line: number; character: number }; }; severity: number; code: string; category: string; message: string; }; export type WasmMode = 'ic' | 'wasi'; export type Result = { value?: any; error: { message?: string; } | null; }; export default function wrapMotoko(compiler: Compiler) { const version = compiler.version || '(unknown)'; const debug = require('debug')(`motoko:${version}`); const invoke = (key: string, unwrap: boolean, args: any[]) => { if (typeof compiler[key] !== 'function') { throw new Error(`Unknown compiler function: '${key}'`); } let result; try { result = compiler[key](...args); } catch (err) { if (err instanceof Error) { throw err; } throw new Error( `Unable to execute ${key}(${[...args] .map((x) => typeof x) .join(', ')}):\n${JSON.stringify(err)}`, ); } if (!unwrap) { return result; } if (!result.code) { throw new Error( result.diagnostics ? result.diagnostics .map(({ message }: Diagnostic) => message) .join('; ') : '(no diagnostics)', ); } return result.code; }; /* Note: [parseMotokoTyped] is an old API which doesn't support scope cache and recovery */ // Function signatures for `mo.parseMotokoTyped()` type ParseMotokoTypedResult = { ast: Node; type: Node }; function parseMotokoTyped(paths: string): ParseMotokoTypedResult; function parseMotokoTyped(paths: string[]): ParseMotokoTypedResult[]; function parseMotokoTyped( paths: string | string[], ): ParseMotokoTypedResult | ParseMotokoTypedResult[] { if (typeof paths === 'string') { return mo.parseMotokoTyped([paths])[0]; } return invoke('parseMotokoTyped', true, [paths]).map( ({ ast, typ }: { ast: CompilerNode; typ: CompilerNode }) => { return { ast: simplifyAST(ast), type: simplifyAST(typ), }; }, ); } // Function signatures for `mo.parseMotokoTypedWithScopeCache()` type ParseMotokoTypedWithScopeCacheResult = { ast: Node; type: Node; immediateImports: string[]; }; function parseMotokoTypedWithScopeCache( paths: string, scopeCache: Map<string, Scope>, enableRecovery?: boolean, ): [ParseMotokoTypedWithScopeCacheResult, Map<string, Scope>]; function parseMotokoTypedWithScopeCache( paths: string[], scopeCache: Map<string, Scope>, enableRecovery?: boolean, ): [ParseMotokoTypedWithScopeCacheResult[], Map<string, Scope>]; function parseMotokoTypedWithScopeCache( paths: string | string[], scopeCache: Map<string, Scope>, enableRecovery?: boolean, ): [ ( | ParseMotokoTypedWithScopeCacheResult | ParseMotokoTypedWithScopeCacheResult[] ), Map<string, Scope>, ] { if (enableRecovery === undefined) { enableRecovery = false; } if (typeof paths === 'string') { const [progs, outCache] = mo.parseMotokoTypedWithScopeCache( [paths], scopeCache, enableRecovery, ); return [progs[0], outCache]; } const [progs, outCache] = invoke( 'parseMotokoTypedWithScopeCache', true, [enableRecovery, paths, scopeCache], ); return [ progs.map( ({ ast, typ, immediateImports, scope, }: { ast: CompilerNode; typ: CompilerNode; immediateImports: string[]; scope: RawScope; }) => { const simplifiedAst = simplifyAST(ast); setRootScope(simplifiedAst, scope); return { ast: simplifiedAst, type: simplifyAST(typ), immediateImports, }; }, ), outCache, ]; } const mo = { version, compiler, file(path: string) { return file(mo, path); }, read(path: string): string { return invoke('readFile', false, [path]); }, write(path: string, content: string = '') { if (typeof content !== 'string') { throw new Error('Non-string file content'); } debug('+file', path); invoke('saveFile', false, [path, content]); }, rename(path: string, newPath: string) { invoke('renameFile', false, [path, newPath]); }, delete(path: string) { debug('-file', path); invoke('removeFile', false, [path]); }, list(directory: string): string[] { return invoke('readDir', false, [directory]); }, async fetchPackage(name: string, info: string | PackageInfo) { if (!info) { throw new Error('Please specify both a name and source'); } return fetchPackage(name, info); }, async installPackages(packages: Record<string, string | PackageInfo>) { return installPackages(mo, packages); }, loadPackage(pkg: Package) { debug('+package', pkg.name); mo.validatePackage(pkg); const directory = `.node-motoko/${pkg.name}/${pkg.version}`; Object.entries(pkg.files).forEach(([path, file]) => { mo.write(`${directory}/${path}`, file.content); }); mo.usePackage(pkg.name, directory); }, usePackage(name: string, directory: string) { debug('@package', name, directory); invoke('addPackage', false, [name, directory]); }, clearPackages() { debug('-packages'); invoke('clearPackage', false, []); }, validatePackage(pkg: Package) { validatePackage(pkg); }, setAliases(directory: string, aliases: Record<string, string>) { debug('aliases', directory, aliases); invoke('setCandidPath', false, [directory]); invoke('setActorAliases', false, [Object.entries(aliases)]); }, setPublicMetadata(values: string[]) { invoke('setPublicMetadata', false, [values]); }, setRunStepLimit(limit: number) { invoke('setRunStepLimit', false, [limit]); }, setTypecheckerCombineSrcs(combineSrcs: boolean) { invoke('setTypecheckerCombineSrcs', false, [combineSrcs]); }, setExtraFlags(argv: string[]) { if (!Array.isArray(argv)) { throw new Error('extra flags must be an array of strings'); } invoke('setExtraFlags', false, [argv]); }, check(path: string): Diagnostic[] { const result = invoke('check', false, [path]); return result.diagnostics; }, checkWithScopeCache( path: string, scopeCache: Map<string, Scope>, ): { diagnostics: Diagnostic[]; scopeCache: Map<string, Scope> | null; } { return invoke('checkWithScopeCache', false, [path, scopeCache]); }, run( path: string, libPaths?: string[] | undefined, ): { stdout: string; stderr: string; result: Result } { const run = invoke('run', false, [libPaths || [], path]); run.stdout = asciiToUtf8(run.stdout); run.stderr = asciiToUtf8(run.stderr); return run; }, candid(path: string): string { return invoke('candid', true, [path]); }, wasm(path: string, mode: WasmMode) { if (!mode) { mode = 'ic'; } else if (mode !== 'ic' && mode !== 'wasi') { throw new Error(`Invalid WASM format: ${mode}`); } return invoke('compileWasm', true, [mode, path]); }, parseCandid(content: string): object { return invoke('parseCandid', true, [content]); }, parseMotoko(content: string, enableRecovery = false): Node { const ast = invoke('parseMotoko', true, [enableRecovery, content]); return simplifyAST(ast); }, parseMotokoWithDeps( path: string, content: string, enableRecovery = false, ): { ast: Node; immediateImports: string[] } { const { ast, immediateImports } = invoke( 'parseMotokoWithDeps', true, [enableRecovery, path, content], ); return { ast: simplifyAST(ast), immediateImports }; }, parseMotokoTyped, parseMotokoTypedWithScopeCache, contextualDotSuggestions( node: Node, program: { ast: AST }, ): | { moduleUri: string; funcName: string; funcType: string; }[] | undefined { const rawExp = getRawExp(node); const scope = getScope(program.ast); if (!rawExp || !scope) return undefined; return invoke('contextualDotSuggestions', false, [scope, rawExp]); }, contextualDotModule( node: Node, ): { moduleNameOrUri: string; funcName: string } | undefined { const rawExp = getRawExp(node); if (!rawExp) return undefined; return invoke('contextualDotModule', false, [rawExp]) ?? undefined; }, resolveMain(directory: string = ''): string | undefined { return resolveMain(mo, directory); }, resolveLib(directory: string = ''): string | undefined { return resolveLib(mo, directory); }, }; // @ts-ignore mo.default = mo; return mo; }