UNPKG

walt-compiler

Version:

Alternative syntax for WebAssembly text format

250 lines (235 loc) 7.63 kB
// @flow // AST Validator import Syntax from 'walt-syntax'; import walkNode from 'walt-parser-tools/walk-node'; import error from '../utils/generate-error'; import { isBuiltinType } from '../generator/utils'; import { TYPE_CONST, AST_METADATA } from '../semantics/metadata'; import { typeWeight } from '../types'; import type { NodeType } from '../flow/types'; const GLOBAL_LABEL = 'global'; // We walk the the entire tree and perform syntax validation before we continue // onto the generator. This may throw sometimes export default function validate( ast: NodeType, { filename, }: { filename: string, } ) { const metadata = ast.meta[AST_METADATA]; if (metadata == null) { throw new Error('Missing AST metadata!'); } const { types, functions, userTypes } = metadata; const problems = []; walkNode({ [Syntax.Import]: (importNode, _) => { walkNode({ [Syntax.BinaryExpression]: (binary, __) => { const [start, end] = binary.range; problems.push( error( "Using an 'as' import without a type.", 'A type for original import ' + binary.params[0].value + ' is not defined nor could it be inferred.', { start, end }, filename, GLOBAL_LABEL ) ); }, [Syntax.Identifier]: (identifier, __) => { const [start, end] = identifier.range; problems.push( error( 'Infered type not supplied.', "Looks like you'd like to infer a type, but it was never provided by a linker. Non-concrete types cannot be compiled.", { start, end }, filename, GLOBAL_LABEL ) ); }, [Syntax.Pair]: (pair, __) => { const type = pair.params[1]; if (!isBuiltinType(type.value) && types[type.value] == null) { const [start, end] = type.range; problems.push( error( `Undefined Type ${type.value}`, `Invalid Import. ${type.value} type does not exist`, { start, end }, filename, GLOBAL_LABEL ) ); } }, })(importNode); }, // All of the validators below need to be implemented [Syntax.Struct]: (_, __) => {}, [Syntax.ImmutableDeclaration]: (_, __) => {}, [Syntax.Declaration]: (decl, _validator) => { const [start, end] = decl.range; if ( !isBuiltinType(decl.type) && !types[decl.type] && !userTypes[decl.type] ) { problems.push( error( 'Unknown type used in a declaration, ' + `"${String(decl.type)}"`, 'Variables must be assigned with a known type.', { start, end }, filename, GLOBAL_LABEL ) ); } }, [Syntax.FunctionDeclaration]: (func, __) => { const functionName = `${func.value}()`; walkNode({ [Syntax.Declaration]: (node, _validator) => { const [start, end] = node.range; if ( !isBuiltinType(node.type) && !types[node.type] && !userTypes[node.type] ) { problems.push( error( 'Unknown type used in a declartion, ' + `"${String(node.type)}"`, 'Variables must be assigned with a known type.', { start, end }, filename, functionName ) ); } }, [Syntax.Assignment]: node => { const [identifier] = node.params; const [start, end] = node.range; const isConst = identifier.meta[TYPE_CONST]; if (isConst) { problems.push( error( `Cannot reassign a const variable ${identifier.value}`, 'const variables cannot be reassigned, use let instead.', { start, end }, filename, functionName ) ); } }, [Syntax.ArraySubscript]: node => { const [target] = node.params; const [start, end] = node.range; problems.push( error( 'Invalid subscript target', `Expected array type for ${target.value}, received ${ target.type }`, { start, end }, filename, functionName ) ); }, [Syntax.NativeMethod]: (node, _validator) => { const { value } = node; const [offset = {}, rhs] = node.params; const [start, end] = node.range; if ( !( offset.value === 'unreachable' && (value.includes('store') || value.includes('load')) ) ) { return; } problems.push( error( 'Cannot generate property access', `Cannot assign "${rhs.value}". Key is "${offset.value}"`, { start, end }, filename, functionName ) ); }, [Syntax.ReturnStatement]: (node, validator) => { node.params.map(validator); if (func.type == null) { return; } const [expression] = node.params; const [start] = node.range; const end = expression != null ? expression.range[1] : node.range[1]; const type = node.type; if (typeWeight(type) !== typeWeight(func.type)) { problems.push( error( 'Missing return value', 'Inconsistent return value. Expected ' + func.type + ' received ' + String(type), { start, end }, filename, functionName ) ); } }, [Syntax.FunctionCall]: (node, _validator) => { if (functions[node.value] == null) { const [start, end] = node.range; problems.push( error( 'Undefined function reference', `${node.value} is not defined.`, { start, end }, filename, functionName ) ); } }, [Syntax.IndirectFunctionCall]: (node, _validator) => { const identifier = node.params[node.params.length - 1]; const type = types[identifier.type]; if (!isBuiltinType(identifier.type) && type == null) { const [start, end] = node.range; problems.push( error( 'Cannot make an indirect call without a valid function type', `${identifier.value} has type ${String( identifier.type )} which is not defined. Indirect calls must have pre-defined types.`, { start, end }, filename, functionName ) ); } }, })(func); }, })(ast); const problemCount = problems.length; if (problemCount > 0) { const errorString = problems.reduce((acc, value) => { return acc + '\n' + `${value}\n`; }, `Cannot generate WebAssembly for ${filename}. ${problemCount} problems.\n`); throw new Error(errorString); } }