import { isFunction, isVariable, Symbol, SYMBOL_FLAG_CONVERT_INSTANCE_TO_GLOBAL, SYMBOL_FLAG_IS_BINARY_OPERATOR, SYMBOL_FLAG_IS_GENERIC, SYMBOL_FLAG_IS_REFERENCE, SYMBOL_FLAG_IS_TEMPLATE, SYMBOL_FLAG_IS_UNARY_OPERATOR, SYMBOL_FLAG_IS_UNSIGNED, SYMBOL_FLAG_NATIVE_DOUBLE, SYMBOL_FLAG_NATIVE_FLOAT, SYMBOL_FLAG_NATIVE_INTEGER, SYMBOL_FLAG_NATIVE_LONG, SYMBOL_FLAG_USED, SymbolKind, SymbolState } from "../core/symbol"; import {ConversionKind, Type} from "../core/type"; import { createboolean, createCall, createDouble, createFloat, createInt, createLong, createMemberReference, createName, createNull, createReturn, createSymbolReference, createType, createVariable, createVariables, isBinary, isExpression, isUnary, Node, NODE_FLAG_DECLARE, NODE_FLAG_EXPORT, NODE_FLAG_GENERIC, NODE_FLAG_GET, NODE_FLAG_LIBRARY, NODE_FLAG_PRIVATE, NODE_FLAG_PROTECTED, NODE_FLAG_PUBLIC, NODE_FLAG_SET, NODE_FLAG_UNSIGNED_OPERATOR, NodeKind, rangeForFlag } from "../core/node"; import {CompileTarget} from "../compile-target"; import {Log, SourceRange, spanRanges} from "../../utils/log"; import {FindNested, Scope, ScopeHint} from "../core/scope"; import {alignToNextMultipleOf, isPositivePowerOf2} from "../../utils/utils"; import {MAX_INT32_VALUE, MAX_UINT32_VALUE, MIN_INT32_VALUE} from "../const"; import {assert} from "../../utils/assert"; import {Compiler} from "../compiler"; import {Terminal} from "../../utils/terminal"; /** * Author : Nidin Vinayakan */ export class CheckContext { log: Log; target: CompileTarget; pointerByteSize: int32; isUnsafeAllowed: boolean; enclosingModule: Symbol; enclosingClass: Symbol; currentReturnType: Type; nextGlobalVariableOffset: int32; //Foreign type anyType: Type; // Native types booleanType: Type; int8Type: Type; errorType: Type; int32Type: Type; int64Type: Type; float32Type: Type; float64Type: Type; nullType: Type; undefinedType: Type; int16Type: Type; stringType: Type; uint8Type: Type; uint32Type: Type; uint64Type: Type; uint16Type: Type; voidType: Type; allocateGlobalVariableOffset(sizeOf: int32, alignmentOf: int32): int32 { let offset = alignToNextMultipleOf(this.nextGlobalVariableOffset, alignmentOf); this.nextGlobalVariableOffset = offset + sizeOf; return offset; } } export function addScopeToSymbol(symbol: Symbol, parentScope: Scope): void { let scope = new Scope(); scope.parent = parentScope; scope.symbol = symbol; symbol.scope = scope; } export function linkSymbolToNode(symbol: Symbol, node: Node): void { node.symbol = symbol; node.scope = symbol.scope; symbol.range = node.internalRange != null ? node.internalRange : node.range; symbol.node = node; } export enum CheckMode { NORMAL, INITIALIZE, } export function initialize(context: CheckContext, node: Node, parentScope: Scope, mode: CheckMode): void { let kind = node.kind; if (node.parent != null) { let parentKind = node.parent.kind; // Validate node placement if (kind != NodeKind.IMPORTS && kind != NodeKind.VARIABLE && kind != NodeKind.VARIABLES && (kind != NodeKind.FUNCTION || parentKind != NodeKind.CLASS) && (parentKind == NodeKind.FILE || parentKind == NodeKind.GLOBAL) != ( parentKind == NodeKind.MODULE || kind == NodeKind.MODULE || kind == NodeKind.CLASS || kind == NodeKind.ENUM || kind == NodeKind.FUNCTION || kind == NodeKind.CONSTANTS ) ) { context.log.error(node.range, "This statement is not allowed here"); } } // Module if (kind == NodeKind.MODULE) { assert(node.symbol == null); let symbol = new Symbol(); symbol.kind = SymbolKind.TYPE_MODULE; symbol.name = node.stringValue; symbol.resolvedType = new Type(); symbol.resolvedType.symbol = symbol; symbol.flags = SYMBOL_FLAG_IS_REFERENCE; addScopeToSymbol(symbol, parentScope); linkSymbolToNode(symbol, node); parentScope.define(context.log, symbol, ScopeHint.NORMAL); parentScope = symbol.scope; } // Class if (kind == NodeKind.CLASS || kind == NodeKind.ENUM) { assert(node.symbol == null); let symbol = new Symbol(); symbol.kind = kind == NodeKind.CLASS ? SymbolKind.TYPE_CLASS : SymbolKind.TYPE_ENUM; symbol.name = node.stringValue; symbol.resolvedType = new Type(); symbol.resolvedType.symbol = symbol; symbol.flags = SYMBOL_FLAG_IS_REFERENCE; addScopeToSymbol(symbol, parentScope); linkSymbolToNode(symbol, node); parentScope.define(context.log, symbol, ScopeHint.NORMAL); parentScope = symbol.scope; if (node.parameterCount() > 0) { //Class has generic parameters. convert it to class template symbol.kind = SymbolKind.TYPE_TEMPLATE; symbol.flags |= SYMBOL_FLAG_IS_TEMPLATE; //TODO: Lift generic parameter limit from 1 to many let genericType = node.firstGenericType(); let genericSymbol = new Symbol(); genericSymbol.kind = SymbolKind.TYPE_GENERIC; genericSymbol.name = genericType.stringValue; genericSymbol.resolvedType = new Type(); genericSymbol.resolvedType.symbol = genericSymbol; genericSymbol.flags = SYMBOL_FLAG_IS_GENERIC; genericType.flags = NODE_FLAG_GENERIC; addScopeToSymbol(genericSymbol, parentScope); linkSymbolToNode(genericSymbol, genericType); parentScope.define(context.log, genericSymbol, ScopeHint.NORMAL); } } // Function else if (kind == NodeKind.FUNCTION) { assert(node.symbol == null); let symbol = new Symbol(); symbol.kind = node.parent.kind == NodeKind.CLASS ? SymbolKind.FUNCTION_INSTANCE : SymbolKind.FUNCTION_GLOBAL; symbol.name = node.stringValue; if (node.isOperator()) { if (symbol.name == "+" || symbol.name == "-") { if (node.functionFirstArgument() == node.functionReturnType()) { symbol.flags = SYMBOL_FLAG_IS_UNARY_OPERATOR; symbol.rename = symbol.name == "+" ? "op_positive" : "op_negative"; } else { symbol.flags = SYMBOL_FLAG_IS_BINARY_OPERATOR; symbol.rename = symbol.name == "+" ? "op_add" : "op_subtract"; } } else { symbol.rename = symbol.name == "%" ? "op_remainder" : symbol.name == "&" ? "op_and" : symbol.name == "*" ? "op_multiply" : symbol.name == "**" ? "op_exponent" : symbol.name == "++" ? "op_increment" : symbol.name == "--" ? "op_decrement" : symbol.name == "/" ? "op_divide" : symbol.name == "<" ? "op_lessThan" : symbol.name == "<<" ? "op_shiftLeft" : symbol.name == "==" ? "op_equals" : symbol.name == ">" ? "op_greaterThan" : symbol.name == ">>" ? "op_shiftRight" : symbol.name == "[]" ? "op_get" : symbol.name == "[]=" ? "op_set" : symbol.name == "^" ? "op_xor" : symbol.name == "|" ? "op_or" : symbol.name == "~" ? "op_complement" : null; } } if (symbol.name == "constructor") { symbol.rename = "_ctr"; } addScopeToSymbol(symbol, parentScope); linkSymbolToNode(symbol, node); parentScope.define(context.log, symbol, symbol.isSetter() ? ScopeHint.NOT_GETTER : symbol.isGetter() ? ScopeHint.NOT_SETTER : symbol.isBinaryOperator() ? ScopeHint.NOT_UNARY : symbol.isUnaryOperator() ? ScopeHint.NOT_BINARY : ScopeHint.NORMAL); parentScope = symbol.scope; // All instance functions have a special "this" type if (symbol.kind == SymbolKind.FUNCTION_INSTANCE) { let parent = symbol.parent(); initializeSymbol(context, parent); if (symbol.name == "constructor") { let body = node.functionBody(); if (body !== null) { let variablesNode = body.firstChild; if (variablesNode === undefined) { let _variablesNode = createVariables(); body.appendChild(_variablesNode); variablesNode = _variablesNode; } else if (variablesNode.kind !== NodeKind.VARIABLES) { let _variablesNode = createVariables(); body.insertChildBefore(variablesNode, _variablesNode); variablesNode = _variablesNode; } let firstVariable = variablesNode.firstChild; if (firstVariable !== undefined) { if (firstVariable.stringValue !== "this") { variablesNode.insertChildBefore(firstVariable, createVariable("this", createType(parent.resolvedType), null)); } else if (firstVariable.stringValue === "this" && firstVariable.firstChild.resolvedType === undefined) { firstVariable.firstChild.resolvedType = parent.resolvedType; } } else { variablesNode.appendChild(createVariable("this", createType(parent.resolvedType), null)); } // All constructors have special return "this" type let returnNode: Node = createReturn(createName("this")); if (node.lastChild.lastChild && node.lastChild.lastChild.kind == NodeKind.RETURN) { node.lastChild.lastChild.remove(); } node.lastChild.appendChild(returnNode); } } else { let firstArgument = node.functionFirstArgument(); if (firstArgument.stringValue !== "this") { node.insertChildBefore(firstArgument, createVariable("this", createType(parent.resolvedType), null)); } else if (firstArgument.stringValue === "this" && firstArgument.firstChild.resolvedType === undefined) { firstArgument.firstChild.resolvedType = parent.resolvedType; } } } } // Variable else if (kind == NodeKind.VARIABLE) { assert(node.symbol == null); let symbol = new Symbol(); symbol.kind = node.parent.kind == NodeKind.CLASS ? SymbolKind.VARIABLE_INSTANCE : node.parent.kind == NodeKind.FUNCTION ? SymbolKind.VARIABLE_ARGUMENT : node.parent.kind == NodeKind.CONSTANTS || node.parent.kind == NodeKind.ENUM ? SymbolKind.VARIABLE_CONSTANT : node.parent.kind == NodeKind.VARIABLES && node.parent.parent.kind == NodeKind.FILE ? SymbolKind.VARIABLE_GLOBAL : SymbolKind.VARIABLE_LOCAL; symbol.name = node.stringValue; symbol.scope = parentScope; linkSymbolToNode(symbol, node); parentScope.define(context.log, symbol, ScopeHint.NORMAL); } // Block else if (kind == NodeKind.BLOCK) { if (node.parent.kind != NodeKind.FUNCTION) { let scope = new Scope(); scope.parent = parentScope; parentScope = scope; } node.scope = parentScope; } // Children let child = node.firstChild; while (child != null) { if (mode == CheckMode.INITIALIZE) { child.flags |= NODE_FLAG_LIBRARY; } initialize(context, child, parentScope, mode); child = child.nextSibling; } if (kind == NodeKind.FILE && mode == CheckMode.INITIALIZE) { context.booleanType = parentScope.findLocal("boolean", ScopeHint.NORMAL).resolvedType; context.uint8Type = parentScope.findLocal("uint8", ScopeHint.NORMAL).resolvedType; context.int32Type = parentScope.findLocal("int32", ScopeHint.NORMAL).resolvedType; context.int64Type = parentScope.findLocal("int64", ScopeHint.NORMAL).resolvedType; context.int8Type = parentScope.findLocal("int8", ScopeHint.NORMAL).resolvedType; context.int16Type = parentScope.findLocal("int16", ScopeHint.NORMAL).resolvedType; context.stringType = parentScope.findLocal("string", ScopeHint.NORMAL).resolvedType; context.uint32Type = parentScope.findLocal("uint32", ScopeHint.NORMAL).resolvedType; context.uint64Type = parentScope.findLocal("uint64", ScopeHint.NORMAL).resolvedType; context.uint16Type = parentScope.findLocal("uint16", ScopeHint.NORMAL).resolvedType; context.float32Type = parentScope.findLocal("float32", ScopeHint.NORMAL).resolvedType; context.float64Type = parentScope.findLocal("float64", ScopeHint.NORMAL).resolvedType; prepareNativeType(context.booleanType, 1, 0); prepareNativeType(context.uint8Type, 1, SYMBOL_FLAG_NATIVE_INTEGER | SYMBOL_FLAG_IS_UNSIGNED); prepareNativeType(context.int8Type, 1, SYMBOL_FLAG_NATIVE_INTEGER); prepareNativeType(context.int16Type, 2, SYMBOL_FLAG_NATIVE_INTEGER); prepareNativeType(context.uint16Type, 2, SYMBOL_FLAG_NATIVE_INTEGER | SYMBOL_FLAG_IS_UNSIGNED); prepareNativeType(context.int32Type, 4, SYMBOL_FLAG_NATIVE_INTEGER); prepareNativeType(context.int64Type, 8, SYMBOL_FLAG_NATIVE_LONG); prepareNativeType(context.uint32Type, 4, SYMBOL_FLAG_NATIVE_INTEGER | SYMBOL_FLAG_IS_UNSIGNED); prepareNativeType(context.uint64Type, 8, SYMBOL_FLAG_NATIVE_LONG | SYMBOL_FLAG_IS_UNSIGNED); prepareNativeType(context.stringType, 4, SYMBOL_FLAG_IS_REFERENCE); prepareNativeType(context.float32Type, 4, SYMBOL_FLAG_NATIVE_FLOAT); prepareNativeType(context.float64Type, 8, SYMBOL_FLAG_NATIVE_DOUBLE); } } function prepareNativeType(type: Type, byteSizeAndMaxAlignment: int32, flags: int32): void { let symbol = type.symbol; symbol.kind = SymbolKind.TYPE_NATIVE; symbol.byteSize = byteSizeAndMaxAlignment; symbol.maxAlignment = byteSizeAndMaxAlignment; symbol.flags = flags; } export function forbidFlag(context: CheckContext, node: Node, flag: int32, text: string): void { if ((node.flags & flag) != 0) { let range = rangeForFlag(node.firstFlag, flag); if (range != null) { node.flags = node.flags & ~flag; context.log.error(range, text); } } } export function requireFlag(context: CheckContext, node: Node, flag: int32, text: string): void { if ((node.flags & flag) == 0) { node.flags = node.flags | flag; context.log.error(node.range, text); } } export function initializeSymbol(context: CheckContext, symbol: Symbol): void { if (symbol.state == SymbolState.INITIALIZED) { assert(symbol.resolvedType != null); return; } assert(symbol.state == SymbolState.UNINITIALIZED); symbol.state = SymbolState.INITIALIZING; // Most flags aren't supported yet let node = symbol.node; // forbidFlag(context, node, NODE_FLAG_EXPORT, "Unsupported flag 'export'"); forbidFlag(context, node, NODE_FLAG_PROTECTED, "Unsupported flag 'protected'"); //forbidFlag(context, node, NODE_FLAG_STATIC, "Unsupported flag 'static'"); // Module if (symbol.kind == SymbolKind.TYPE_MODULE) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' on a namespace"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' on a namespace"); forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' on a namespace"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' on a namespace"); } // Class else if (symbol.kind == SymbolKind.TYPE_CLASS || symbol.kind == SymbolKind.TYPE_NATIVE || symbol.kind == SymbolKind.TYPE_GENERIC || symbol.kind == SymbolKind.TYPE_TEMPLATE) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' on a class"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' on a class"); forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' on a class"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' on a class"); } // Interface else if (symbol.kind == SymbolKind.TYPE_INTERFACE) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' on a interface"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' on a interface"); forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' on a interface"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' on a interface"); } // Enum else if (symbol.kind == SymbolKind.TYPE_ENUM) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' on an enum"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' on an enum"); forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' on an enum"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' on an enum"); symbol.resolvedType = new Type(); symbol.resolvedType.symbol = symbol; let underlyingSymbol = symbol.resolvedType.underlyingType(context).symbol; symbol.byteSize = underlyingSymbol.byteSize; symbol.maxAlignment = underlyingSymbol.maxAlignment; } // Function else if (isFunction(symbol.kind)) { let body = node.functionBody(); let returnType = node.functionReturnType(); let oldUnsafeAllowed = context.isUnsafeAllowed; context.isUnsafeAllowed = node.isUnsafe(); resolveAsType(context, returnType, symbol.scope.parent); if (returnType.resolvedType.isClass() && returnType.hasParameters() && node.parent != returnType.resolvedType.symbol.node) { deriveConcreteClass(context, returnType, [returnType.firstChild.firstChild], returnType.resolvedType.symbol.scope); } let argumentCount = 0; let child = node.functionFirstArgument(); while (child != returnType) { assert(child.kind == NodeKind.VARIABLE); assert(child.symbol.kind == SymbolKind.VARIABLE_ARGUMENT); initializeSymbol(context, child.symbol); child.symbol.offset = argumentCount; argumentCount = argumentCount + 1; child = child.nextSibling; } if (symbol.kind != SymbolKind.FUNCTION_INSTANCE) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' here"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' here"); forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' here"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' here"); } else if (node.isGet()) { forbidFlag(context, node, NODE_FLAG_SET, "Cannot use both 'get' and 'set'"); // Validate argument count including "this" if (argumentCount != 1) { context.log.error(symbol.range, "Getters must not have any argumentVariables"); } } else if (node.isSet()) { symbol.rename = `set_${symbol.name}`; // Validate argument count including "this" if (argumentCount != 2) { context.log.error(symbol.range, "Setters must have exactly one argument"); } } // Validate operator argument counts including "this" else if (node.isOperator()) { if (symbol.name == "~" || symbol.name == "++" || symbol.name == "--") { if (argumentCount != 1) { context.log.error(symbol.range, `Operator '${symbol.name}' must not have any arguments`); } } else if (symbol.name == "+" || symbol.name == "-") { if (argumentCount > 2) { context.log.error(symbol.range, `Operator '${symbol.name}' must have at most one argument`); } } else if (symbol.name == "[]=") { if (argumentCount < 2) { context.log.error(symbol.range, "Operator '[]=' must have at least one argument"); } } else if (argumentCount != 2) { context.log.error(symbol.range, `Operator '${symbol.name}' must have exactly one argument`); } } symbol.resolvedType = new Type(); symbol.resolvedType.symbol = symbol; if (symbol.kind == SymbolKind.FUNCTION_INSTANCE) { let parent = symbol.parent(); let shouldConvertInstanceToGlobal = false; forbidFlag(context, node, NODE_FLAG_EXPORT, "Cannot use 'export' on an instance function"); forbidFlag(context, node, NODE_FLAG_DECLARE, "Cannot use 'declare' on an instance function"); // Functions inside declared classes are automatically declared if (parent.node.isDeclare()) { if (body == null) { node.flags = node.flags | NODE_FLAG_DECLARE; } else { shouldConvertInstanceToGlobal = true; } } // Require implementations for functions not on declared classes else { if (body == null) { context.log.error(node.lastChild.range, "Must implement this function"); } // Functions inside export classes are automatically export if (parent.node.isExport()) { node.flags = node.flags | NODE_FLAG_EXPORT; } } // Rewrite this symbol as a global function instead of an instance function if (shouldConvertInstanceToGlobal) { symbol.kind = SymbolKind.FUNCTION_GLOBAL; symbol.flags = symbol.flags | SYMBOL_FLAG_CONVERT_INSTANCE_TO_GLOBAL; symbol.rename = `${parent.name}_${symbol.rename != null ? symbol.rename : symbol.name}`; let argument = node.functionFirstArgument(); assert(argument.symbol.name == "this"); argument.symbol.rename = "__this"; } } // Imported functions require a modifier for consistency with TypeScript else if (body == null) { forbidFlag(context, node, NODE_FLAG_EXPORT, "Cannot use 'export' on an unimplemented function"); if (!node.parent || !node.parent.isDeclare()) { requireFlag(context, node, NODE_FLAG_DECLARE, "Declared functions must be prefixed with 'declare'"); } } else { forbidFlag(context, node, NODE_FLAG_DECLARE, "Cannot use 'declare' on a function with an implementation"); } context.isUnsafeAllowed = oldUnsafeAllowed; } // Variable else if (isVariable(symbol.kind)) { forbidFlag(context, node, NODE_FLAG_GET, "Cannot use 'get' on a variable"); forbidFlag(context, node, NODE_FLAG_SET, "Cannot use 'set' on a variable"); let type = node.variableType(); let value = node.variableValue(); let oldUnsafeAllowed = context.isUnsafeAllowed; context.isUnsafeAllowed = context.isUnsafeAllowed || node.isUnsafe(); if (symbol.kind != SymbolKind.VARIABLE_INSTANCE) { forbidFlag(context, node, NODE_FLAG_PUBLIC, "Cannot use 'public' here"); forbidFlag(context, node, NODE_FLAG_PRIVATE, "Cannot use 'private' here"); } if (type != null) { resolveAsType(context, type, symbol.scope); if (type.resolvedType.isTemplate() && type.hasParameters() && node.parent != type.resolvedType.symbol.node) { deriveConcreteClass(context, type, [type.firstChild.firstChild], type.resolvedType.symbol.scope); } symbol.resolvedType = type.resolvedType; } else if (value != null) { resolveAsExpression(context, value, symbol.scope); if (value.resolvedType.isTemplate() && value.hasParameters() && node.parent != value.resolvedType.symbol.node) { deriveConcreteClass(context, value, [value.firstChild.firstChild], value.resolvedType.symbol.scope); } symbol.resolvedType = value.resolvedType; } else { context.log.error(node.internalRange, "Cannot create untyped variables"); symbol.resolvedType = context.errorType; } // Validate the variable type if (symbol.resolvedType == context.voidType || symbol.resolvedType == context.nullType) { context.log.error(node.internalRange, `Cannot create a variable with type '${symbol.resolvedType.toString()}'`); symbol.resolvedType = context.errorType; } // Resolve constant values at initialization time if (symbol.kind == SymbolKind.VARIABLE_CONSTANT) { if (value != null) { resolveAsExpression(context, value, symbol.scope); checkConversion(context, value, symbol.resolvedTypeUnderlyingIfEnumValue(context), ConversionKind.IMPLICIT); if (value.kind == NodeKind.INT32 || value.kind == NodeKind.INT64 || value.kind == NodeKind.BOOLEAN) { symbol.offset = value.intValue; } else if (value.kind == NodeKind.FLOAT32 || value.kind == NodeKind.FLOAT64) { symbol.offset = value.floatValue; } else if (value.resolvedType != context.errorType) { context.log.error(value.range, "Invalid constant initializer"); symbol.resolvedType = context.errorType; } } // Automatically initialize enum values using the previous enum else if (symbol.isEnumValue()) { if (node.previousSibling != null) { let previousSymbol = node.previousSibling.symbol; initializeSymbol(context, previousSymbol); symbol.offset = previousSymbol.offset + 1; } else { symbol.offset = 0; } } else { context.log.error(node.internalRange, "Constants must be initialized"); } } // Disallow shadowing at function scope if (symbol.scope.symbol == null) { let scope = symbol.scope.parent; while (scope != null) { let shadowed = scope.findLocal(symbol.name, ScopeHint.NORMAL); if (shadowed != null) { context.log.error( node.internalRange, `The symbol '${symbol.name}' shadows another symbol with the same name in a parent scope` ); break; } // Stop when we pass through a function scope if (scope.symbol != null) { break; } scope = scope.parent; } } context.isUnsafeAllowed = oldUnsafeAllowed; } else { assert(false); } assert(symbol.resolvedType != null); symbol.state = SymbolState.INITIALIZED; } /** * Derive a concrete class from class template type * @param context * @param type * @param parameters * @param scope * @returns {Symbol} */ function deriveConcreteClass(context: CheckContext, type: Node, parameters: any[], scope: Scope) { let templateNode: Node = type.resolvedType.pointerTo ? type.resolvedType.pointerTo.symbol.node : type.resolvedType.symbol.node; let templateName = templateNode.stringValue; let typeName = templateNode.stringValue + `<${parameters[0].stringValue}>`; let rename = templateNode.stringValue + `_${parameters[0].stringValue}`; let symbol = scope.parent.findNested(typeName, ScopeHint.NORMAL, FindNested.NORMAL); if (symbol) { // resolve(context, type.firstChild.firstChild, scope.parent); let genericSymbol = scope.parent.findNested(type.firstChild.firstChild.stringValue, ScopeHint.NORMAL, FindNested.NORMAL); type.firstChild.firstChild.symbol = genericSymbol; if (genericSymbol.resolvedType.pointerTo) { type.firstChild.firstChild.resolvedType = genericSymbol.resolvedType.pointerType(); } else { type.firstChild.firstChild.resolvedType = genericSymbol.resolvedType; } type.symbol = symbol; if (type.resolvedType.pointerTo) { type.resolvedType = symbol.resolvedType.pointerType(); } else { type.resolvedType = symbol.resolvedType; } return; } let node: Node = templateNode.clone(); // node.parent = templateNode.parent; node.stringValue = typeName; cloneChildren(templateNode.firstChild.nextSibling, node, parameters, templateName, typeName); node.offset = null;//FIXME: we cannot take offset from class template node initialize(context, node, scope.parent, CheckMode.NORMAL); resolve(context, node, scope.parent); node.symbol.flags |= SYMBOL_FLAG_USED; node.constructorFunctionNode.symbol.flags |= SYMBOL_FLAG_USED; type.symbol = node.symbol; node.symbol.rename = rename; if (type.resolvedType.pointerTo) { type.resolvedType = node.symbol.resolvedType.pointerType(); } else { type.resolvedType = node.symbol.resolvedType; } if (templateNode.parent) { templateNode.replaceWith(node); } else { let prevNode = templateNode.derivedNodes[templateNode.derivedNodes.length - 1]; prevNode.parent.insertChildAfter(prevNode, node); } if (templateNode.derivedNodes === undefined) { templateNode.derivedNodes = []; } templateNode.derivedNodes.push(node); //Leave the parameter for the emitter to identify the type type.firstChild.firstChild.kind = NodeKind.NAME; resolve(context, type.firstChild.firstChild, scope.parent); type.stringValue = node.symbol.name; return; } function cloneChildren(child: Node, parentNode: Node, parameters: any[], templateName: string, typeName: string): void { let firstChildNode: Node = null; let lastChildNode: Node = null; while (child) { if (child.stringValue == "this" && child.parent.symbol && child.parent.symbol.kind == SymbolKind.FUNCTION_INSTANCE && child.kind == NodeKind.TYPE) { child = child.nextSibling; continue; } let childNode: Node; if (child.kind == NodeKind.PARAMETERS || child.kind == NodeKind.PARAMETER) { child = child.nextSibling; continue; } if (child.isGeneric()) { let offset = child.offset; if (child.resolvedType) { offset = child.resolvedType.pointerTo ? child.resolvedType.pointerTo.symbol.node.offset : child.resolvedType.symbol.node.offset; } if (child.symbol && isVariable(child.symbol.kind)) { childNode = child.clone(); } else { childNode = parameters[offset].clone(); } childNode.kind = NodeKind.NAME; } else { if (child.stringValue == "T") { Terminal.write("Generic type escaped!"); Terminal.write(child); } childNode = child.clone(); if (childNode.stringValue == templateName) { childNode.stringValue = typeName; } } childNode.parent = parentNode; if (childNode.stringValue == "constructor" && childNode.parent.kind == NodeKind.CLASS) { childNode.parent.constructorFunctionNode = childNode; } if (!firstChildNode) { firstChildNode = childNode; } if (lastChildNode) { lastChildNode.nextSibling = childNode; childNode.previousSibling = lastChildNode; } if (child.firstChild) { cloneChildren(child.firstChild, childNode, parameters, templateName, typeName); } lastChildNode = childNode; child = child.nextSibling; } if (firstChildNode != null) parentNode.firstChild = firstChildNode; if (lastChildNode != null) parentNode.lastChild = lastChildNode; } export function resolveChildren(context: CheckContext, node: Node, parentScope: Scope): void { let child = node.firstChild; while (child != null) { resolve(context, child, parentScope); assert(child.resolvedType != null); child = child.nextSibling; } } export function resolveChildrenAsExpressions(context: CheckContext, node: Node, parentScope: Scope): void { let child = node.firstChild; while (child != null) { resolveAsExpression(context, child, parentScope); child = child.nextSibling; } } export function resolveAsExpression(context: CheckContext, node: Node, parentScope: Scope): void { assert(isExpression(node)); resolve(context, node, parentScope); assert(node.resolvedType != null); if (node.resolvedType != context.errorType) { if (node.isType()) { context.log.error(node.range, "Expected expression but found type"); node.resolvedType = context.errorType; } else if (node.resolvedType == context.voidType && node.parent.kind != NodeKind.EXPRESSION) { context.log.error(node.range, "This expression does not return a value"); node.resolvedType = context.errorType; } } } export function resolveAsType(context: CheckContext, node: Node, parentScope: Scope): void { assert(isExpression(node)); resolve(context, node, parentScope); assert(node.resolvedType != null); if (node.resolvedType != context.errorType && !node.isType()) { context.log.error(node.range, "Expected type but found expression"); node.resolvedType = context.errorType; } } export function canConvert(context: CheckContext, node: Node, to: Type, kind: ConversionKind): boolean { let from = node.resolvedType; assert(isExpression(node)); assert(from != null); assert(to != null); //Generic always accept any types if (from.isGeneric() || to.isGeneric()) { return true; } // Early-out if the types are identical or errors if (from == to || from == context.errorType || to == context.errorType) { return true; } // Allow conversions from null else if (from == context.nullType/* && to.isReference()*/) { return true; } // Allow explicit conversions between references in unsafe mode else if (/*context.isUnsafeAllowed && */(from.isReference() || to.isReference())) { if (kind == ConversionKind.EXPLICIT) { return true; } } // Allow conversions from boolean else if (from == context.booleanType) { return true; } // Check integer conversions else if (from.isInteger() && to.isInteger()) { let mask = to.integerBitMask(context); if (from.isUnsigned() && to.isUnsigned()) { return true; } // Allow implicit conversions between enums and int32 if (from.isEnum() && to == from.underlyingType(context)) { return true; } if (!node.intValue) { return true; } // Only allow lossless conversions implicitly if (kind == ConversionKind.EXPLICIT || from.symbol.byteSize < to.symbol.byteSize || node.kind == NodeKind.INT32 && (to.isUnsigned() ? node.intValue >= 0 && node.intValue <= MAX_UINT32_VALUE : node.intValue >= MIN_INT32_VALUE && node.intValue <= MAX_INT32_VALUE)) { return true; } return false; } else if ( from.isInteger() && to.isFloat() || from.isInteger() && to.isDouble() || from.isLong() && to.isInteger() || from.isLong() && to.isFloat() || from.isLong() && to.isDouble() || from.isFloat() && to.isInteger() || from.isFloat() && to.isLong() || from.isDouble() && to.isInteger() || from.isDouble() && to.isLong() || from.isDouble() && to.isFloat() ) { if (kind == ConversionKind.IMPLICIT) { return false; } return true; } else if ( from.isInteger() && to.isLong() || from.isFloat() && to.isDouble() || from.isFloat() && to.isFloat() || from.isDouble() && to.isDouble() ) { return true; } return false; } export function checkConversion(context: CheckContext, node: Node, to: Type, kind: ConversionKind): void { if (!canConvert(context, node, to, kind)) { context.log.error(node.range, `Cannot convert from type '${node.resolvedType.toString()}' to type '${to.toString()}' ${ kind == ConversionKind.IMPLICIT && canConvert(context, node, to, ConversionKind.EXPLICIT) ? "without a cast" : ""}` ); node.resolvedType = context.errorType; } } export function checkStorage(context: CheckContext, target: Node): void { assert(isExpression(target)); if (target.resolvedType != context.errorType && target.kind != NodeKind.INDEX && target.kind != NodeKind.POINTER_INDEX && target.kind != NodeKind.DEREFERENCE && ( target.kind != NodeKind.NAME && target.kind != NodeKind.DOT || target.symbol != null && ( !isVariable(target.symbol.kind) || target.symbol.kind == SymbolKind.VARIABLE_CONSTANT ) ) ) { context.log.error(target.range, "Cannot store to this location"); target.resolvedType = context.errorType; } } export function createDefaultValueForType(context: CheckContext, type: Type): Node { if (type.isLong()) { return createLong(0); } else if (type.isInteger()) { return createInt(0); } else if (type.isDouble()) { return createDouble(0); } else if (type.isFloat()) { return createFloat(0); } if (type == context.booleanType) { return createboolean(false); } if (type.isClass()) { return createNull(); } if (type.isGeneric()) { return createNull(); } assert(type.isReference()); return createNull(); } export function simplifyBinary(node: Node): void { let left = node.binaryLeft(); let right = node.binaryRight(); // Canonicalize commutative operators if ((node.kind == NodeKind.ADD || node.kind == NodeKind.MULTIPLY || node.kind == NodeKind.BITWISE_AND || node.kind == NodeKind.BITWISE_OR || node.kind == NodeKind.BITWISE_XOR) && left.kind == NodeKind.INT32 && right.kind != NodeKind.INT32) { node.appendChild(left.remove()); left = node.binaryLeft(); right = node.binaryRight(); } // Convert multiplication or division by a power of 2 into a shift if ((node.kind == NodeKind.MULTIPLY || (node.kind == NodeKind.DIVIDE || node.kind == NodeKind.REMAINDER) && node.resolvedType.isUnsigned()) && right.kind == NodeKind.INT32 && isPositivePowerOf2(right.intValue)) { // Extract the shift from the value let shift = -1; let value = right.intValue; while (value != 0) { value = value >> 1; shift = shift + 1; } // "x * 16" => "x << 4" if (node.kind == NodeKind.MULTIPLY) { node.kind = NodeKind.SHIFT_LEFT; right.intValue = shift; } // "x / 16" => "x >> 4" when x is unsigned else if (node.kind == NodeKind.DIVIDE) { node.kind = NodeKind.SHIFT_RIGHT; right.intValue = shift; } // "x % 16" => "x & 15" when x is unsigned else if (node.kind == NodeKind.REMAINDER) { node.kind = NodeKind.BITWISE_AND; right.intValue = right.intValue - 1; } else { assert(false); } } // Flip addition with negation into subtraction else if (node.kind == NodeKind.ADD && right.kind == NodeKind.NEGATIVE) { node.kind = NodeKind.SUBTRACT; right.replaceWith(right.unaryValue().remove()); } // Flip addition with negative constants into subtraction else if (node.kind == NodeKind.ADD && right.isNegativeInteger()) { node.kind = NodeKind.SUBTRACT; right.intValue = -right.intValue; } } export function binaryHasUnsignedArguments(node: Node): boolean { let left = node.binaryLeft(); let right = node.binaryRight(); let leftType = left.resolvedType; let rightType = right.resolvedType; return leftType.isUnsigned() && rightType.isUnsigned() || leftType.isUnsigned() && right.isNonNegativeInteger() || left.isNonNegativeInteger() && rightType.isUnsigned(); } export function isBinaryLong(node: Node): boolean { let left = node.binaryLeft(); let right = node.binaryRight(); let leftType = left.resolvedType; let rightType = right.resolvedType; return leftType.isLong() || rightType.isLong(); } export function isBinaryDouble(node: Node): boolean { let left = node.binaryLeft(); let right = node.binaryRight(); let leftType = left.resolvedType; let rightType = right.resolvedType; return leftType.isDouble() || rightType.isDouble(); } export function isSymbolAccessAllowed(context: CheckContext, symbol: Symbol, node: Node, range: SourceRange): boolean { if (symbol.isUnsafe() && !context.isUnsafeAllowed) { context.log.error(range, `Cannot use symbol '${symbol.name}' outside an 'unsafe' block`); return false; } if (symbol.node != null && symbol.node.isPrivate()) { let parent = symbol.parent(); if (parent != null && context.enclosingClass != parent) { context.log.error(range, `Cannot access private symbol '${symbol.name}' here`); return false; } } if (isFunction(symbol.kind) && (symbol.isSetter() ? !node.isAssignTarget() : !node.isCallValue())) { if (symbol.isSetter()) { context.log.error(range, `Cannot use setter '${symbol.name}' here`); } else { context.log.error(range, `Must call function '${symbol.name}'`); } return false; } return true; } export function resolve(context: CheckContext, node: Node, parentScope: Scope): void { let kind = node.kind; assert(kind == NodeKind.FILE || parentScope != null); if (node.resolvedType != null) { return; } node.resolvedType = context.errorType; if (kind == NodeKind.FILE || kind == NodeKind.GLOBAL) { resolveChildren(context, node, parentScope); } else if (kind == NodeKind.MODULE) { let oldEnclosingModule = context.enclosingModule; initializeSymbol(context, node.symbol); context.enclosingModule = node.symbol; resolveChildren(context, node, node.scope); context.enclosingModule = oldEnclosingModule; } else if (kind == NodeKind.IMPORT || kind == NodeKind.IMPORT_FROM) { //ignore imports } else if (kind == NodeKind.CLASS) { let oldEnclosingClass = context.enclosingClass; initializeSymbol(context, node.symbol); context.enclosingClass = node.symbol; resolveChildren(context, node, node.scope); if (!node.isDeclare() && node.constructorFunctionNode === undefined) { node.constructorFunctionNode = node.createEmptyConstructor(); node.appendChild(node.constructorFunctionNode); initialize(context, node.constructorFunctionNode, node.scope, CheckMode.NORMAL); // let firstFunction = node.firstInstanceFunction(); // if(firstFunction === undefined){ // node.insertChildBefore(firstFunction, node.constructorFunctionNode); // } else { // node.insertChildBefore(firstFunction, node.constructorFunctionNode); // } resolve(context, node.constructorFunctionNode, node.scope); } if (node.symbol.kind == SymbolKind.TYPE_CLASS) { node.symbol.determineClassLayout(context); } context.enclosingClass = oldEnclosingClass; } else if (kind == NodeKind.ENUM) { initializeSymbol(context, node.symbol); resolveChildren(context, node, node.scope); } else if (kind == NodeKind.FUNCTION) { let body = node.functionBody(); initializeSymbol(context, node.symbol); if (node.stringValue == "constructor" && node.parent.kind == NodeKind.CLASS) { node.parent.constructorFunctionNode = node; } if (body != null) { let oldReturnType = context.currentReturnType; let oldUnsafeAllowed = context.isUnsafeAllowed; let returnType = node.functionReturnType(); if (returnType.resolvedType.isTemplate() && returnType.hasParameters() && node.parent != returnType.resolvedType.symbol.node) { deriveConcreteClass(context, returnType, [returnType.firstChild.firstChild], returnType.resolvedType.symbol.scope); } context.currentReturnType = returnType.resolvedType; context.isUnsafeAllowed = node.isUnsafe(); resolveChildren(context, body, node.scope); if (oldReturnType && oldReturnType.isTemplate() && returnType.hasParameters() && node.parent != oldReturnType.symbol.node) { deriveConcreteClass(context, returnType, [returnType.firstChild.firstChild], oldReturnType.symbol.scope); } // if (oldReturnType && oldReturnType.isTemplate() && !oldReturnType.symbol.node.hasParameters()) { // deriveConcreteClass(context, oldReturnType.symbol.node, [oldReturnType.symbol.node.firstChild], oldReturnType.symbol.scope); // } context.currentReturnType = oldReturnType; context.isUnsafeAllowed = oldUnsafeAllowed; } } else if (kind == NodeKind.PARAMETER) { let symbol = node.symbol; } else if (kind == NodeKind.VARIABLE) { let symbol = node.symbol; initializeSymbol(context, symbol); let oldUnsafeAllowed = context.isUnsafeAllowed; context.isUnsafeAllowed = context.isUnsafeAllowed || node.isUnsafe(); let value = node.variableValue(); if (value != null) { resolveAsExpression(context, value, parentScope); checkConversion(context, value, symbol.resolvedTypeUnderlyingIfEnumValue(context), ConversionKind.IMPLICIT); if (symbol.resolvedType != value.resolvedType) { value.becomeValueTypeOf(symbol, context); } // Variable initializers must be compile-time constants if (symbol.kind == SymbolKind.VARIABLE_GLOBAL && value.kind != NodeKind.INT32 && value.kind != NodeKind.BOOLEAN && value.kind != NodeKind.NULL) { //context.log.error(value.range, "Global initializers must be compile-time constants"); } } else if (symbol.resolvedType != context.errorType) { value = createDefaultValueForType(context, symbol.resolvedType); resolveAsExpression(context, value, parentScope); node.appendChild(value); } // Allocate global variables if (symbol.kind == SymbolKind.VARIABLE_GLOBAL && symbol.resolvedType != context.errorType) { symbol.offset = context.allocateGlobalVariableOffset(symbol.resolvedType.variableSizeOf(context), symbol.resolvedType.variableAlignmentOf(context)); } context.isUnsafeAllowed = oldUnsafeAllowed; } else if (kind == NodeKind.BREAK || kind == NodeKind.CONTINUE) { let found = false; let n = node; while (n != null) { if (n.kind == NodeKind.WHILE) { found = true; break; } n = n.parent; } if (!found) { context.log.error(node.range, "Cannot use this statement outside of a loop"); } } else if (kind == NodeKind.BLOCK) { let oldUnsafeAllowed = context.isUnsafeAllowed; if (node.isUnsafe()) context.isUnsafeAllowed = true; resolveChildren(context, node, node.scope); context.isUnsafeAllowed = oldUnsafeAllowed; } else if (kind == NodeKind.IMPORTS || kind == NodeKind.CONSTANTS || kind == NodeKind.VARIABLES) { resolveChildren(context, node, parentScope); } else if (kind == NodeKind.ANY) { //imported functions have anyType node.kind = NodeKind.TYPE; node.resolvedType = context.anyType; } else if (kind == NodeKind.INT32) { // Use the positive flag to differentiate between -2147483648 and 2147483648 node.resolvedType = node.intValue < 0 && !node.isPositive() ? context.uint32Type : context.int32Type; } else if (kind == NodeKind.INT64) { node.resolvedType = node.intValue < 0 && !node.isPositive() ? context.uint64Type : context.int64Type; } else if (kind == NodeKind.FLOAT32) { node.resolvedType = context.float32Type; } else if (kind == NodeKind.FLOAT64) { node.resolvedType = context.float64Type; } else if (kind == NodeKind.STRING) { node.resolvedType = context.stringType; } else if (kind == NodeKind.BOOLEAN) { node.resolvedType = context.booleanType; } else if (kind == NodeKind.NULL) { node.resolvedType = context.nullType; } else if (kind == NodeKind.INDEX) { resolveChildrenAsExpressions(context, node, parentScope); let target = node.indexTarget(); let type = target.resolvedType; if (type != context.errorType) { let symbol = type.hasInstanceMembers() ? type.findMember("[]", ScopeHint.NORMAL) : null; if (symbol == null) { if (target.resolvedType.pointerTo !== undefined) { // convert index to pinter index node.kind = NodeKind.POINTER_INDEX; node.resolvedType = target.resolvedType.pointerTo.symbol.resolvedType; } else { context.log.error(node.internalRange, `Cannot index into type '${target.resolvedType.toString()}'`); } } else { assert(symbol.kind == SymbolKind.FUNCTION_INSTANCE || symbol.kind == SymbolKind.FUNCTION_GLOBAL && symbol.shouldConvertInstanceToGlobal()); // Convert to a regular function call and resolve that instead node.kind = NodeKind.CALL; target.remove(); node.insertChildBefore(node.firstChild, createMemberReference(target, symbol)); node.resolvedType = null; resolveAsExpression(context, node, parentScope); } } } else if (kind == NodeKind.ALIGN_OF) { let type = node.alignOfType(); resolveAsType(context, type, parentScope); node.resolvedType = context.int32Type; if (type.resolvedType != context.errorType) { node.becomeIntegerConstant(type.resolvedType.allocationAlignmentOf(context)); } } else if (kind == NodeKind.SIZE_OF) { let type = node.sizeOfType(); resolveAsType(context, type, parentScope); node.resolvedType = context.int32Type; if (type.resolvedType != context.errorType) { node.becomeIntegerConstant(type.resolvedType.allocationSizeOf(context)); } } else if (kind == NodeKind.THIS) { let symbol = parentScope.findNested("this", ScopeHint.NORMAL, FindNested.NORMAL); if (symbol == null) { context.log.error(node.range, "Cannot use 'this' here"); } else { node.becomeSymbolReference(symbol); } } else if (kind == NodeKind.PARSE_ERROR) { node.resolvedType = context.errorType; } else if (kind == NodeKind.NAME) { let name = node.stringValue; let symbol = parentScope.findNested(name, ScopeHint.NORMAL, FindNested.NORMAL); if (symbol == null) { let errorMessage = `No symbol named '${name}' here`; // In JavaScript, "this." before instance symbols is required symbol = parentScope.findNested(name, ScopeHint.NORMAL, FindNested.ALLOW_INSTANCE_ERRORS); if (symbol != null) { errorMessage += `, did you mean 'this.${symbol.name}'?`; } // People may try to use types from TypeScript else if (name == "number") { // TODO: convert to float64 automatically errorMessage += ", you cannot use generic number type from TypeScript!"; } else if (name == "bool") { errorMessage += ", did you mean 'boolean'?"; } context.log.error(node.range, errorMessage); } else if (symbol.state == SymbolState.INITIALIZING) { context.log.error(node.range, `Cyclic reference to symbol '${name}' here`); } else if (isSymbolAccessAllowed(context, symbol, node, node.range)) { initializeSymbol(context, symbol); node.symbol = symbol; node.resolvedType = symbol.resolvedType; if (node.resolvedType.isGeneric()) { node.flags |= NODE_FLAG_GENERIC; } // Inline constants if (symbol.kind == SymbolKind.VARIABLE_CONSTANT) { if (symbol.resolvedType == context.booleanType) { node.becomeBooleanConstant(symbol.offset != 0); } else if (symbol.resolvedType == context.float32Type) { node.becomeFloatConstant(symbol.offset); } else if (symbol.resolvedType == context.float64Type) { node.becomeDoubleConstant(symbol.offset); } else if (symbol.resolvedType == context.int64Type) { node.becomeLongConstant(symbol.offset); } else { node.becomeIntegerConstant(symbol.offset); } } } } else if (kind == NodeKind.CAST) { let value = node.castValue(); let type = node.castType(); resolveAsExpression(context, value, parentScope); resolveAsType(context, type, parentScope); let castedType = type.resolvedType; checkConversion(context, value, castedType, ConversionKind.EXPLICIT); node.resolvedType = castedType; // Automatically fold constants if (value.kind == NodeKind.INT32 && castedType.isInteger()) { let result = value.intValue; let shift = 32 - castedType.integerBitCount(context); node.becomeIntegerConstant(castedType.isUnsigned() ? castedType.integerBitMask(context) & result : result << shift >> shift); } //i32 to f32 else if (value.kind == NodeKind.INT32 && castedType.isFloat()) { node.becomeFloatConstant(value.intValue); } //i32 to f64 else if (value.kind == NodeKind.INT32 && castedType.isDouble()) { node.becomeDoubleConstant(value.intValue); } //f32 to i32 else if (value.kind == NodeKind.FLOAT32 && castedType.isInteger()) { node.becomeIntegerConstant(Math.round(value.floatValue)); } } else if (kind == NodeKind.DOT) { let target = node.dotTarget(); resolve(context, target, parentScope); if (target.resolvedType != context.errorType) { if (target.isType() && (target.resolvedType.isEnum() || target.resolvedType.hasInstanceMembers()) || !target.isType() && target.resolvedType.hasInstanceMembers()) { let name = node.stringValue; // Empty names are left over from parse errors that have already been reported if (name.length > 0) { let symbol = target.resolvedType.findMember(name, node.isAssignTarget() ? ScopeHint.PREFER_SETTER : ScopeHint.PREFER_GETTER); if (symbol == null) { context.log.error( node.internalRange, `No member named '${name}' on type '${target.resolvedType.toString()}'` ); } // Automatically call getters else if (symbol.isGetter()) { if (node.parent.stringValue === node.stringValue && node.parent.kind === NodeKind.CALL) { node.parent.resolvedType = null; node.symbol = symbol; node.resolvedType = symbol.resolvedType; resolveAsExpression(context, node.parent, parentScope); } else { node.kind = NodeKind.CALL; node.appendChild(createMemberReference(target.remove(), symbol)); node.resolvedType = null; resolveAsExpression(context, node, parentScope); } return; } else if (isSymbolAccessAllowed(context, symbol, node, node.internalRange)) { initializeSymbol(context, symbol); node.symbol = symbol; node.resolvedType = symbol.resolvedType; // Inline constants if (symbol.kind == SymbolKind.VARIABLE_CONSTANT) { node.becomeIntegerConstant(symbol.offset); } } } } else { context.log.error( node.internalRange, `The type '${target.resolvedType.toString()}' has no members` ); } } } else if (kind == NodeKind.CALL) { let value = node.callValue(); resolveAsExpression(context, value, parentScope); if (value.resolvedType != context.errorType) { let symbol = value.symbol; // Only functions are callable if (symbol == null || !isFunction(symbol.kind)) { context.log.error( value.range, `Cannot call value of type '${value.resolvedType.toString()}'`); } else { initializeSymbol(context, symbol); if (symbol.shouldConvertInstanceToGlobal()) { let name = createSymbolReference(symbol); node.insertChildBefore(value, name.withRange(value.internalRange)); node.insertChildBefore(value, value.dotTarget().remove()); value.remove(); value = name; } if (symbol.name === "malloc") { Compiler.mallocRequired = true; } let returnType = symbol.node.functionReturnType(); let argumentVariable = symbol.node.functionFirstArgumentIgnoringThis(); let argumentValue = value.nextSibling; // Match argument values with variables while (argumentVariable != returnType && argumentValue != null) { resolveAsExpression(context, argumentValue, parentScope); checkConversion(context, argumentValue, argumentVariable.symbol.resolvedType, ConversionKind.IMPLICIT); argumentVariable = argumentVariable.nextSibling; argumentValue = argumentValue.nextSibling; } // Not enough argumentVariables? if (returnType.resolvedType != context.anyType) { if (argumentVariable != returnType && !argumentVariable.hasVariableValue()) { context.log.error( node.internalRange, `Not enough arguments for function '${symbol.name}'`); } // Too many argumentVariables? else if (argumentValue != null) { while (argumentValue != null) { resolveAsExpression(context, argumentValue, parentScope); argumentValue = argumentValue.nextSibling; } context.log.error( node.internalRange, `Too many arguments for function '${symbol.name}'`); } } if (returnType.resolvedType.isArray()) { Terminal.write(returnType); } // Pass the return type along node.resolvedType = returnType.resolvedType; } } } else if (kind == NodeKind.DELETE) { let value = node.deleteType(); if (value != null) { resolveAsExpression(context, value, parentScope); if (value.resolvedType == null || value.resolvedType == context.voidType) { context.log.error(value.range, "Unexpected delete value 'void'"); } } else { context.log.error( node.range, `Expected delete value '${context.currentReturnType.toString()}'` ); } } else if (kind == NodeKind.RETURN) { let value = node.returnValue(); if (value != null) { resolveAsExpression(context, value, parentScope); if (context.currentReturnType != null) { if (context.currentReturnType != context.voidType) { if (value.resolvedType.isTemplate() && value.hasParameters() && node.parent != value.resolvedType.symbol.node) { deriveConcreteClass(context, value, [value.firstChild.firstChild], value.resolvedType.symbol.scope); } checkConversion(context, value, context.currentReturnType, ConversionKind.IMPLICIT); } else { context.log.error(value.range, "Unexpected return value in function returning 'void'"); } } node.parent.returnNode = node; } else if (context.currentReturnType != null && context.currentReturnType != context.voidType) { context.log.error( node.range, `Expected return value in function returning '${context.currentReturnType.toString()}'` ); } } else if (kind == NodeKind.EMPTY) { } else if (kind == NodeKind.PARAMETERS) { // resolveAsType(context, node.genericType(), parentScope); // resolveAsExpression(context, node.expressionValue(), parentScope); // context.log.error(node.range, "Generics are not implemented yet"); } else if (kind == NodeKind.EXTENDS) { resolveAsType(context, node.extendsType(), parentScope); //context.log.error(node.range, "Subclassing is not implemented yet"); } else if (kind == NodeKind.IMPLEMENTS) { let child = node.firstChild; while (child != null) { resolveAsType(context, child, parentScope); child = child.nextSibling; } context.log.error(node.range, "Interfaces are not implemented yet"); } else if (kind == NodeKind.EXPRESSIONS) { let child = node.firstChild; while (child) { resolveAsExpression(context, child.expressionValue(), parentScope); child = child.nextSibling; } } else if (kind == NodeKind.EXPRESSION) { resolveAsExpression(context, node.expressionValue(), parentScope); } else if (kind == NodeKind.WHILE) { let value = node.whileValue(); let body = node.whileBody(); resolveAsExpression(context, value, parentScope); checkConversion(context, value, context.booleanType, ConversionKind.IMPLICIT); resolve(context, body, parentScope); } else if (kind == NodeKind.FOR) { let initializationStmt = node.forInitializationStatement(); let terminationStmt = node.forTerminationStatement(); let updateStmts = node.forUpdateStatements(); let body = node.forBody(); resolve(context, initializationStmt, parentScope); resolveAsExpression(context, terminationStmt, parentScope); resolve(context, updateStmts, parentScope); checkConversion(context, terminationStmt, context.booleanType, ConversionKind.IMPLICIT); resolve(context, body, parentScope); } else if (kind == NodeKind.IF) { let value = node.ifValue(); let yes = node.ifTrue(); let no = node.ifFalse(); resolveAsExpression(context, value, parentScope); checkConversion(context, value, context.booleanType, ConversionKind.IMPLICIT); resolve(context, yes, parentScope); if (no != null) { resolve(context, no, parentScope); } } else if (kind == NodeKind.HOOK) { let value = node.hookValue(); let yes = node.hookTrue(); let no = node.hookFalse(); resolveAsExpression(context, value, parentScope); checkConversion(context, value, context.booleanType, ConversionKind.IMPLICIT); resolve(context, yes, parentScope); resolve(context, no, parentScope); checkConversion(context, yes, no.resolvedType, ConversionKind.IMPLICIT); let commonType = (yes.resolvedType == context.nullType ? no : yes).resolvedType; if (yes.resolvedType != commonType && (yes.resolvedType != context.nullType || !commonType.isReference()) && no.resolvedType != commonType && (no.resolvedType != context.nullType || !commonType.isReference())) { context.log.error( spanRanges(yes.range, no.range), `Type '${yes.resolvedType.toString()}' is not the same as type '${no.resolvedType.toString()}'` ); } node.resolvedType = commonType; } else if (kind == NodeKind.ASSIGN) { let left = node.binaryLeft(); let right = node.binaryRight(); if (left.kind == NodeKind.INDEX) { resolveChildrenAsExpressions(context, left, parentScope); let target = left.indexTarget(); let type = target.resolvedType; if (type != context.errorType) { let symbol = type.hasInstanceMembers() ? type.findMember("[]=", ScopeHint.NORMAL) : null; if (symbol == null) { if (target.resolvedType.pointerTo != undefined) { left.kind = NodeKind.POINTER_INDEX; left.resolvedType = target.resolvedType.pointerTo.symbol.resolvedType; } else { context.log.error( left.internalRange, `Cannot index into type '${target.resolvedType.toString()}'` ); } } else { assert(symbol.kind == SymbolKind.FUNCTION_INSTANCE); // Convert to a regular function call and resolve that instead node.kind = NodeKind.CALL; target.remove(); left.remove(); while (left.lastChild != null) { node.insertChildBefore(node.firstChild, left.lastChild.remove()); } node.insertChildBefore(node.firstChild, createMemberReference(target, symbol)); node.internalRange = spanRanges(left.internalRange, right.range); node.resolvedType = null; resolveAsExpression(context, node, parentScope); return; } } } if (!left.resolvedType) { resolveAsExpression(context, left, parentScope); } // Automatically call setters if (left.symbol != null && left.symbol.isSetter()) { node.kind = NodeKind.CALL; node.internalRange = left.internalRange; node.resolvedType = null; resolveAsExpression(context, node, parentScope); return; } resolveAsExpression(context, right, parentScope); checkConversion(context, right, left.resolvedType, ConversionKind.IMPLICIT); checkStorage(context, left); node.resolvedType = left.resolvedType; } else if (kind == NodeKind.NEW) { Compiler.mallocRequired = true; let type = node.newType(); resolveAsType(context, type, parentScope); if (type.resolvedType.isTemplate() && type.hasParameters() && node.parent != type.resolvedType.symbol.node) { deriveConcreteClass(context, type, [type.firstChild.firstChild], type.resolvedType.symbol.scope); } if (type.resolvedType != context.errorType) { if (!type.resolvedType.isClass()) { context.log.error( type.range, `Cannot construct type '${type.resolvedType.toString()}'` ); } else { node.resolvedType = type.resolvedType; } } //Constructors argumentVariables let child = type.nextSibling; let constructorNode = node.constructorNode(); if (constructorNode !== undefined) { let argumentVariable = constructorNode.functionFirstArgument(); while (child != null) { resolveAsExpression(context, child, parentScope); checkConversion(context, child, argumentVariable.symbol.resolvedType, ConversionKind.IMPLICIT); child = child.nextSibling; argumentVariable = argumentVariable.nextSibling; } } // Match argument values with variables // while (argumentVariable != returnType && argumentValue != null) { // resolveAsExpression(context, argumentValue, parentScope); // checkConversion(context, argumentValue, argumentVariable.symbol.resolvedType, ConversionKind.IMPLICIT); // argumentVariable = argumentVariable.nextSibling; // argumentValue = argumentValue.nextSibling; // } } else if (kind == NodeKind.POINTER_TYPE) { let value = node.unaryValue(); resolveAsType(context, value, parentScope); if (context.target == CompileTarget.JAVASCRIPT) { context.log.error(node.internalRange, "Cannot use pointers when compiling to JavaScript"); } /*else if (!context.isUnsafeAllowed) { context.log.error(node.internalRange, "Cannot use pointers outside an 'unsafe' block"); }*/ else { let type = value.resolvedType; if (type != context.errorType) { node.resolvedType = type.pointerType(); } } } else if (kind == NodeKind.POINTER_INDEX) { debugger } else if (kind == NodeKind.DEREFERENCE) { let value = node.unaryValue(); resolveAsExpression(context, value, parentScope); let type = value.resolvedType; if (type != context.errorType) { if (type.pointerTo == null) { context.log.error(node.internalRange, `Cannot dereference type '${type.toString()}'`); } else { node.resolvedType = type.pointerTo; } } } else if (kind == NodeKind.ADDRESS_OF) { let value = node.unaryValue(); resolveAsExpression(context, value, parentScope); context.log.error(node.internalRange, "The address-of operator is not supported"); } else if (isUnary(kind)) { let value = node.unaryValue(); resolveAsExpression(context, value, parentScope); // Operator "!" is hard-coded if (kind == NodeKind.NOT) { checkConversion(context, value, context.booleanType, ConversionKind.IMPLICIT); node.resolvedType = context.booleanType; } // Special-case long types else if (value.resolvedType.isLong()) { if (value.resolvedType.isUnsigned()) { node.flags = node.flags | NODE_FLAG_UNSIGNED_OPERATOR; node.resolvedType = context.uint64Type; } else { node.resolvedType = context.int64Type; } // Automatically fold constants if (value.kind == NodeKind.INT64) { let input = value.longValue; let output = input; if (kind == NodeKind.COMPLEMENT) output = ~input; else if (kind == NodeKind.NEGATIVE) output = -input; node.becomeLongConstant(output); } } // Special-case integer types else if (value.resolvedType.isInteger()) { if (value.resolvedType.isUnsigned()) { node.flags = node.flags | NODE_FLAG_UNSIGNED_OPERATOR; node.resolvedType = context.uint32Type; } else { node.resolvedType = context.int32Type; } // Automatically fold constants if (value.kind == NodeKind.INT32) { let input = value.intValue; let output = input; if (kind == NodeKind.COMPLEMENT) output = ~input; else if (kind == NodeKind.NEGATIVE) output = -input; node.becomeIntegerConstant(output); } } // Special-case double types else if (value.resolvedType.isDouble()) { node.resolvedType = context.float64Type; // Automatically fold constants if (value.kind == NodeKind.FLOAT64) { let input = value.doubleValue; let output = input; if (kind == NodeKind.COMPLEMENT) output = ~input; else if (kind == NodeKind.NEGATIVE) output = -input; node.becomeDoubleConstant(output); } } // Special-case float types else if (value.resolvedType.isFloat()) { node.resolvedType = context.float32Type; // Automatically fold constants if (value.kind == NodeKind.FLOAT32) { let input = value.floatValue; let output = input; if (kind == NodeKind.COMPLEMENT) output = ~input; else if (kind == NodeKind.NEGATIVE) output = -input; node.becomeFloatConstant(output); } } // Support custom operators else if (value.resolvedType != context.errorType) { let name = node.internalRange.toString(); let symbol = value.resolvedType.findMember(name, ScopeHint.NOT_BINARY); // Automatically call the function if (symbol != null) { node.appendChild(createMemberReference(value.remove(), symbol).withRange(node.range).withInternalRange(node.internalRange)); node.kind = NodeKind.CALL; node.resolvedType = null; resolveAsExpression(context, node, parentScope); } else { context.log.error(node.internalRange, `Cannot use unary operator '${name}' with type '${value.resolvedType.toString()}'`); } } } else if (isBinary(kind)) { let left = node.binaryLeft(); let right = node.binaryRight(); resolveAsExpression(context, left, parentScope); resolveAsExpression(context, right, parentScope); let leftType = left.resolvedType; if ((leftType.isDouble() && right.resolvedType.isFloat()) || (leftType.isLong() && right.resolvedType.isInteger())) { right.becomeTypeOf(left, context); } let rightType = right.resolvedType; // Operators "&&" and "||" are hard-coded if (kind == NodeKind.LOGICAL_OR || kind == NodeKind.LOGICAL_AND) { checkConversion(context, left, context.booleanType, ConversionKind.IMPLICIT); checkConversion(context, right, context.booleanType, ConversionKind.IMPLICIT); node.resolvedType = context.booleanType; } // Special-case pointer types (it's the emitter's job to scale the integer delta by the size of the pointer target) else if (kind == NodeKind.ADD && leftType.pointerTo != null && rightType.isInteger()) { node.resolvedType = leftType; } // Special-case pointer types else if ((kind == NodeKind.LESS_THAN || kind == NodeKind.LESS_THAN_EQUAL || kind == NodeKind.GREATER_THAN || kind == NodeKind.GREATER_THAN_EQUAL) && ( leftType.pointerTo != null || rightType.pointerTo != null)) { node.resolvedType = context.booleanType; // Both pointer types must be exactly the same if (leftType != rightType) { context.log.error(node.internalRange, `Cannot compare type '${leftType.toString()}' with type '${rightType.toString()}'`); } } // Operators for integers and floats are hard-coded else if ( ( leftType.isInteger() || leftType.isLong() || leftType.isFloat() || leftType.isDouble() || (leftType.isGeneric() && rightType.isGeneric()) ) && kind != NodeKind.EQUAL && kind != NodeKind.NOT_EQUAL) { let isFloat: boolean = false; let isFloat64: boolean = false; if (leftType.isFloat() || leftType.isDouble()) { isFloat = true; isFloat64 = leftType.isDouble(); } let isUnsigned = binaryHasUnsignedArguments(node); // Arithmetic operators if (kind == NodeKind.ADD || kind == NodeKind.SUBTRACT || kind == NodeKind.MULTIPLY || kind == NodeKind.DIVIDE || kind == NodeKind.REMAINDER || kind == NodeKind.BITWISE_AND || kind == NodeKind.BITWISE_OR || kind == NodeKind.BITWISE_XOR || kind == NodeKind.SHIFT_LEFT || kind == NodeKind.SHIFT_RIGHT) { let isLong = isBinaryLong(node); let commonType; if (isFloat) { commonType = isBinaryDouble(node) ? context.float64Type : context.float32Type; } else { commonType = isUnsigned ? (isLong ? context.uint64Type : context.uint32Type) : (isLong ? context.int64Type : context.int32Type); } if (isUnsigned) { node.flags = node.flags | NODE_FLAG_UNSIGNED_OPERATOR; } checkConversion(context, left, commonType, ConversionKind.IMPLICIT); checkConversion(context, right, commonType, ConversionKind.IMPLICIT); node.resolvedType = commonType; // Signature conversion if (commonType == context.int64Type) { if (left.kind == NodeKind.INT32) { left.kind = NodeKind.INT64; left.resolvedType = context.int64Type; } else if (right.kind == NodeKind.INT32) { right.kind = NodeKind.INT64; right.resolvedType = context.int64Type; } } // Automatically fold constants if ((left.kind == NodeKind.INT32 || left.kind == NodeKind.INT64) && (right.kind == NodeKind.INT32 || right.kind == NodeKind.INT64)) { let inputLeft = left.intValue; let inputRight = right.intValue; let output = 0; if (kind == NodeKind.ADD) output = inputLeft + inputRight; else if (kind == NodeKind.BITWISE_AND) output = inputLeft & inputRight; else if (kind == NodeKind.BITWISE_OR) output = inputLeft | inputRight; else if (kind == NodeKind.BITWISE_XOR) output = inputLeft ^ inputRight; else if (kind == NodeKind.DIVIDE) output = inputLeft / inputRight; else if (kind == NodeKind.MULTIPLY) output = inputLeft * inputRight; else if (kind == NodeKind.REMAINDER) output = inputLeft % inputRight; else if (kind == NodeKind.SHIFT_LEFT) output = inputLeft << inputRight; else if (kind == NodeKind.SHIFT_RIGHT) output = isUnsigned ? ((inputLeft) >> (inputRight)) : inputLeft >> inputRight; else if (kind == NodeKind.SUBTRACT) output = inputLeft - inputRight; else return; if (left.kind == NodeKind.INT32) { node.becomeIntegerConstant(output); } else { node.becomeLongConstant(output); } } else if ((left.kind == NodeKind.FLOAT32 || left.kind == NodeKind.FLOAT64) && (right.kind == NodeKind.FLOAT32 || right.kind == NodeKind.FLOAT64)) { let inputLeft = left.floatValue; let inputRight = right.floatValue; let output = 0; if (kind == NodeKind.ADD) output = inputLeft + inputRight; else if (kind == NodeKind.BITWISE_AND) output = inputLeft & inputRight; else if (kind == NodeKind.BITWISE_OR) output = inputLeft | inputRight; else if (kind == NodeKind.BITWISE_XOR) output = inputLeft ^ inputRight; else if (kind == NodeKind.DIVIDE) output = inputLeft / inputRight; else if (kind == NodeKind.MULTIPLY) output = inputLeft * inputRight; else if (kind == NodeKind.REMAINDER) output = inputLeft % inputRight; else if (kind == NodeKind.SHIFT_LEFT) output = inputLeft << inputRight; else if (kind == NodeKind.SHIFT_RIGHT) output = inputLeft >> inputRight; else if (kind == NodeKind.SUBTRACT) output = inputLeft - inputRight; else return; if (left.kind == NodeKind.FLOAT32) { node.becomeFloatConstant(output); } else { node.becomeDoubleConstant(output); } } else { simplifyBinary(node); } } // Comparison operators else if ( kind == NodeKind.LESS_THAN || kind == NodeKind.LESS_THAN_EQUAL || kind == NodeKind.GREATER_THAN || kind == NodeKind.GREATER_THAN_EQUAL) { let expectedType = isFloat ? (isFloat64 ? context.float64Type : context.float32Type) : (isUnsigned ? context.uint32Type : context.int32Type); if (isUnsigned) { node.flags = node.flags | NODE_FLAG_UNSIGNED_OPERATOR; } if (leftType != rightType) { checkConversion(context, left, expectedType, ConversionKind.IMPLICIT); checkConversion(context, right, expectedType, ConversionKind.IMPLICIT); } node.resolvedType = context.booleanType; } else { context.log.error(node.internalRange, "This operator is not currently supported"); } } // Support custom operators else if (leftType != context.errorType) { let name = node.internalRange.toString(); let symbol = leftType.findMember( kind == NodeKind.NOT_EQUAL ? "==" : kind == NodeKind.LESS_THAN_EQUAL ? ">" : kind == NodeKind.GREATER_THAN_EQUAL ? "<" : name, ScopeHint.NOT_UNARY); // Automatically call the function if (symbol != null) { left = createMemberReference(left.remove(), symbol).withRange(node.range).withInternalRange(node.internalRange); right.remove(); if (kind == NodeKind.NOT_EQUAL || kind == NodeKind.LESS_THAN_EQUAL || kind == NodeKind.GREATER_THAN_EQUAL) { let call = createCall(left); call.appendChild(right); node.kind = NodeKind.NOT; node.appendChild(call.withRange(node.range).withInternalRange(node.range)); } else { node.appendChild(left); node.appendChild(right); node.kind = NodeKind.CALL; } node.resolvedType = null; resolveAsExpression(context, node, parentScope); } // Automatically implement equality operators else if (kind == NodeKind.EQUAL || kind == NodeKind.NOT_EQUAL) { node.resolvedType = context.booleanType; if ( leftType != context.errorType && rightType != context.errorType && leftType != rightType && !canConvert(context, right, leftType, ConversionKind.IMPLICIT) && !canConvert(context, left, rightType, ConversionKind.IMPLICIT) ) { context.log.error(node.internalRange, `Cannot compare type '${leftType.toString()}' with type '${rightType.toString()}'`); } } else { context.log.error(node.internalRange, `Cannot use binary operator '${name}' with type '${leftType.toString()}'`); } } } else if (kind == NodeKind.TYPE) { //ignore types } else { Terminal.error(`Unexpected kind: ${NodeKind[kind]}`); assert(false); } }