diff --git a/src/codegen.nim b/src/codegen.nim index 8391b8f..92f2451 100644 --- a/src/codegen.nim +++ b/src/codegen.nim @@ -516,8 +516,8 @@ proc genContainerIndex(ctx: CodeGenState, sym: SymbolInfo = nil, else: ctx.genTCall(FIndex) -proc genPushStaticString(ctx: CodeGenState, name: string) = - ctx.emitInstruction(ZPushStaticPtr, ctx.addStaticObject(name), tid = TString) +proc genPushStaticString(ctx: CodeGenState, s: string) = + ctx.emitInstruction(ZPushStaticPtr, ctx.addStaticObject(s), tid = TString) proc genLoadAttr(ctx: CodeGenState, tid: TypeId, mode = 0) = ctx.emitInstruction(ZLoadFromAttr, tid = tid, arg = mode) @@ -1234,73 +1234,115 @@ proc genFunction(ctx: CodeGenState, fn: FuncInfo) = ctx.mcur.objInfo.codesyms[zinfo.offset] = fullname +# Returns true if native. +proc backpatch_callback(ctx: CodeGenState, patchloc: int, + node: IrNode): (int32, bool) = + ## m.instructions[offset] is ZPushVmPtr, which needs to be patched. + ## next one is ZRunCallback + var + m = ctx.mcur + offset = patchloc + cb = cast[ptr Callback](node.value) + fnid: int32 + native: bool + + if cb.impl.externInfo != nil: + # TODO Shouldn't the offset already be in cb.impl? + # Not sure we need to loop; just copying from above. + native = false + + for i, item in ctx.zobj.ffiInfo: + let + p = cast[pointer](addr ctx.zobj.staticData[item.nameOffset]) + s = $(cast[cstring](p)) + + if s == cb.impl.externName: + let to = cb.tid.idToTypeRef() + fnid = int32(i) + m.objinfo.instructions[offset].arg = fnid + m.objinfo.instructions[offset].typeInfo = to.items[^1] + break + else: + fnid = int32(cb.impl.internalId) + native = true + m.objinfo.instructions[offset].arg = fnid + + offset += 1 + m.objinfo.instructions[offset].typeInfo = cb.tid + + return (fnid, native) + proc stashParam(ctx: CodeGenState, m: Module, info: ParamInfo) = var zparam = ZParamInfo(shortdoc: info.shortdoc, - longdoc: info.longdoc) - + longdoc: info.longdoc, + private: info.private) if info.sym.isAttr: zparam.attr = info.sym.name else: zparam.offset = info.sym.offset if info.defaultIr.isSome(): - # TODO: do I need to check if this is constant? Don't remember + # TODO: I think may need to ensure const? zparam.haveDefault = true - zparam.default = info.defaultIr.get().value + zparam.default = info.defaultIr.get().value zparam.tid = info.sym.tid - if info.validatorIr.isSome(): + if info.initializeIr.isSome(): var - offset = info.backpatchLoc - ### m.instructions[offset] is ZPushVmPtr - ### next one is ZRunCallback - litnode = info.validatorIr.get() - cb = cast[ptr Callback](litnode.value) - - if cb.impl.externInfo != nil: - # TODO Shouldn't the offset already be in cb.impl? - # Not sure we need to loop; just copying from above. - zparam.native = false - for i, item in ctx.zobj.ffiInfo: - let - p = cast[pointer](addr ctx.zobj.staticData[item.nameOffset]) - s = $(cast[cstring](p)) + offset = info.iPatchLoc + litnode = info.initializeIr.get() - if s == cb.impl.externName: - zparam.funcIx = int32(i) - m.objinfo.instructions[offset].arg = zparam.funcIx - m.objinfo.instructions[offset].typeInfo = zparam.tid - break - else: - zparam.native = true - zparam.funcIx = int32(cb.impl.internalId) - m.objinfo.instructions[offset].arg = zparam.funcIx + (zparam.iFnIx, zparam.iNative) = ctx.backpatch_callback(offset, litnode) + else: + zparam.iFnIx = -1 - offset += 1 - m.objinfo.instructions[offset].typeInfo = cb.tid + if info.validatorIr.isSome(): + var + offset = info.vPatchLoc + litnode = info.validatorIr.get() + (zparam.vFnIx, zparam.iNative) = ctx.backpatch_callback(offset, litnode) else: - zparam.funcIx = -1 - + zparam.vFnIx = -1 m.objInfo.parameters.add(zparam) -proc placeholdParamValidation(ctx: CodeGenState, m: Module, i: int) = - m.params[i].backpatchLoc = m.objInfo.instructions.len() - ctx.emitInstruction(ZPushVmPtr) - ctx.emitInstruction(ZRunCallback, arg = 1) - ctx.emitInstruction(ZMoveSp, -1) - ctx.emitInstruction(ZPushRes) +proc emitBail(ctx: CodeGenState, msg: string) = + ctx.genPushStaticString(msg) + ctx.emitInstruction(ZBail) proc genParamChecks(ctx: CodeGenState, m: Module) = for i, param in m.params: - if param.validatorIr.isNone(): - continue - ctx.genLoadSymbol(param.sym) - ctx.placeholdParamValidation(m, i) - ctx.emitInstruction(ZParamCheck, arg = i) + ctx.genPushTypeOf(param.sym) + let patchloc = m.objInfo.instructions.len() + ctx.startJnz() + ctx.emitBail("Parameter " & param.sym.name & " was not set when " & + "entering module " & m.modname) + ctx.backpatch(patchloc) + + if param.validatorIr.isSome(): + ctx.genLoadSymbol(param.sym) + m.params[i].vPatchLoc = m.objInfo.instructions.len() + ctx.emitInstruction(ZPushVmPtr) # Will overwrite this later. + ctx.emitInstruction(ZRunCallback, arg = 1) + ctx.emitInstruction(ZMoveSp, -1) + ctx.emitInstruction(ZPushRes) + ctx.emitInstruction(ZParamCheck, arg = i) + +proc genInitializer(ctx: CodeGenState, p: ParamInfo) = + ctx.genPushTypeOf(p.sym) + let patchloc = ctx.mcur.objInfo.instructions.len() + ctx.startJnz() + # Not sure what kind of callback we're pushing yet. + # We'll come back to the next instruction we emit later. + p.iPatchloc = ctx.mcur.objInfo.instructions.len() + ctx.emitInstruction(ZPushVmPtr) + ctx.emitInstruction(ZRunCallback, arg = -1) + ctx.emitInstruction(ZPushRes) + ctx.genStore(p.sym) + ctx.backPatch(patchloc) proc genModule(ctx: CodeGenState, m: Module) = let curMod = ctx.minfo[m.key] @@ -1315,6 +1357,10 @@ proc genModule(ctx: CodeGenState, m: Module) = curMod.longdoc = m.longdoc ctx.genLabel("Module '" & m.modname & "' :") + for param in m.params: + if param.initializeIr.isSome(): + ctx.genInitializer(param) + ctx.emitInstruction(ZModuleEnter, arg = m.params.len()) if m.params.len() != 0: diff --git a/src/commands/objdump.nim b/src/commands/objdump.nim index c4cfdd6..eb4cf05 100644 --- a/src/commands/objdump.nim +++ b/src/commands/objdump.nim @@ -22,8 +22,8 @@ proc get_basic_object_info*(obj: ZObjectFile): Rope = proc format_module_params*(obj: ZObjectFile, params: seq[ZParamInfo]): Rope = var cells: seq[seq[string]] = @[@["Attr or offset", "Type", - "Native func?", - "Function index", "Short Doc", "Long Doc"]] + "Native Validator?", + "Validator index", "Short Doc", "Long Doc"]] for item in params: var row: seq[string] = @[] if item.attr != "": @@ -33,11 +33,11 @@ proc format_module_params*(obj: ZObjectFile, params: seq[ZParamInfo]): Rope = echo "offset = ", item.offset row.add($item.offset) row.add(item.tid.toString()) - if item.native: + if item.vnative: row.add("✓") else: row.add("✗") - row.add($item.funcIx) + row.add($item.vfnIx) row.add(item.shortdoc & " ") row.add(item.longdoc & " ") cells.add(row) diff --git a/src/common.nim b/src/common.nim index dc24a64..4dbc5e7 100644 --- a/src/common.nim +++ b/src/common.nim @@ -402,23 +402,29 @@ type shortdoc*: string longdoc*: string validatorIr*: Option[IrNode] + initializeIr*: Option[IrNode] defaultParse*: Option[ParseNode] defaultIr*: Option[IrNode] - backpatchLoc*: int + private*: bool + iPatchLoc*: int + vPatchLoc*: int ZParamInfo* = ref object ## What we stick in the obj file. - attr*: string # Either it's an attribute... - offset*: int # or in theerr current module. - default*: pointer - tid*: TypeId - haveDefault*: bool - native*: bool - funcIx*: int32 - userparam*: pointer - userType*: TypeId - shortdoc*: string - longdoc*: string + attr*: string # Either it's an attribute... + offset*: int # or in theerr current module. + default*: pointer + tid*: TypeId + haveDefault*: bool + private*: bool + vFnIx*: int32 + vNative*: bool # Whether the validation fn is native. + iFnIx*: int32 + iNative*: bool # Whether the validation fn is native. + userparam*: pointer + userType*: TypeId + shortdoc*: string + longdoc*: string ZParamExport* = ref object ## What we pass when asked what parameters need to be provided. @@ -433,6 +439,7 @@ type modid*: int32 paramid*: int32 tid*: TypeId + private*: bool havedefault*: bool default*: pointer @@ -897,6 +904,9 @@ type # through a ZAssignAttr. ZLockOnWrite = 0xb0, + # Print an error message and abort. + ZBail = 0xee, + # A no-op. These double as labels for disassembly too. ZNop = 0xff @@ -1218,8 +1228,6 @@ template debug*(s: string, s2: string, moreargs: varargs[string]) = debug(cells.quickTable(verticalHeaders = true)) - - # The current runtime, so that builtin functions can access the state. # Was having a weird link error so moved this here. var diff --git a/src/err.nim b/src/err.nim index 3c18719..58787ca 100644 --- a/src/err.nim +++ b/src/err.nim @@ -1,907 +1,2 @@ -import std/[posix, terminal] -import "."/common - -## If we have to bail on control flow, we'll do so here. -proc newCon4mException*(errs: seq[Con4mError] = @[]): Con4mException = - result = new(Con4mException) - result.errors = errs - -proc newCon4mLongJmp*(msg = ""): Con4mLongJmp = - result = new(Con4mLongJmp) - result.msg = msg - -template con4mLongJmp*(msg = "") = - raise newCon4mLongJmp(msg) - -const errorMsgs = [ - ("FileNotFound", "Module could not be located."), - ("ModuleNotFound", "Module $1 could not be located."), - ("StrayCr", "Carriage return (\r) without newline."), - ("BadEscape", "Unterminated escape sequence in literal."), - ("UnicodeEscape", "Invalid unicode escape character found in literal."), - ("LitModExpected", "Expected a valid literal modifier"), - ("CommentTerm", "Unterminated C-style long comment."), - ("BadFloat", "Invalid character in float literal"), - ("MissingDigits", "Must have digits after the dot in a valid float literal"), - ("BadExponent", "Float exponent specified, with no exponent " & - "value provided"), - ("BadChar", "Invalid character literal"), - ("StringNl", "Unterminated single-quoted string."), - ("StringTerm", "Unterminated string literal"), - ("CharTerm", "Unterminated character literal"), - ("OtherTerm", "Unterminated literal"), - ("BadChar", "Invalid character found in token"), - ("StmtEnd", "Expected end of a statement after $1."), - ("NotALit", "Not a literal; literal modifier not allowed here."), - ("BadLitMod", "Unknown literal modifier $1 for $2" & - " syntax."), - ("LitModTypeErr", "Literal modifier $1 can't be used with " & - "$2 literals"), - ("MissingTok", "Expected $1 here."), - ("MemberSpec", "'.' operator must have an identifier on " & - "both sides."), - ("ItemExpected", "Expected either another item or '$1'"), - ("BadTuple", "Tuple types must contain two or more items."), - ("AccessExpr", "Expected either an identifier or paren here."), - ("ExprStart", "Expected the start of an expression here, but got $1."), - ("BinaryNot", "'not' operator takes only one operand."), - ("LitExpected", "Expected a literal value here."), - ("ForFromIx", "for ... from loops must have a single index " & - "variable."), - ("NameInTypeSpec", "'$1' is not a builtin type. " & - "Use struct[typename] for user types."), - ("BadOneOf", "OneOf types must have multiple type options that " & - "are more constrained than a generic type variable."), - ("BadObjType", "Object types must provide either a name or a type " & - "variable if specifying an object where no fields " & - "will be referenced."), - ("BadTypeDecl", "Invalid syntax inside a type declaration."), - ("BadFormalEnd", "Expected either a closing parenthesis (')'" & - " or an additional parameter."), - ("BadLock", "Attribute Lock operator (~) must be " & - "followed either by an assignment statement or " & - "an attribute to lock."), - ("BadUseSyntax", "'use' statement requires a string literal " & - " after 'from'"), - ("BadParamName", "'parameter' keyword must be followed by " & - "an attribute (which can be dotted), or the " & - "'var' keyword followed by a local variable " & - "name. The variable can be optionally typed."), - ("BadExternField", "Bad field for 'extern' block."), - ("NeedSig", "Field requires a function type signature."), - ("BadRCParam", "Reference counting specs must either be identifiers " & - "that match the names given in the function signature, " & - "or the special value return (for incref only)"), - ("BadCType", "Invalid C type for external parameter: $1"), - ("PureBool", "The 'pure' property for external functions " & - "must be a boolean value (true or " & - "false)"), - ("DupeExtField", "Duplicate field $1 provided for an " & - "extern block."), - ("DupeDllName", "Duplicate DLL name $1 provided for an " & - "external function (ignored)."), - ("DupeVal", "Duplicate parameter value $1 in the " & - "$2 property of the extern spec."), - ("ExtNotSpecd", "None of the external function parameters were given" & - " the name $1"), - ("ExtAllocNHold", "Extern function parameter $1 cannot be " & - "spec'd to have the external function hold memory " & - "we pass, and to allocate that memory."), - ("NoMemMan", "Since $1 is not a pointer or array type, " & - "memory management will not be performend, and this " & - "annotation will be ignored."), - ("WontLink", "Was not able to locate the external symbol $1" & - ", which may be required for running."), - ("MissingSym", "Was not able to locate the external symbol $1" & - "; program will crash if it is accessed, unless " & - "it is dynamically loaded first."), - ("PurePlz", "Please provide a value for the pure property " & - "for extern functions. Pure functions always " & - "return the same output for the same input, and do " & - "not do any I/O, allowing us to pre-execute."), - ("EofInBlock", "Block was not closed when end-of-file was found."), - ("TopLevelOnly", "'$1' is only allowed at the top-level of a " & - "module."), - ("TopLevelPlural", "$1 are only allowed at the top-level of a module."), - ("InLoopsOnly", "'$1' is only allowed inside loops."), - ("RetOutOfFunc", "'return' is only allowed inside functions."), - ("UToSSmaller", "Conversion of unsigned value to a smaller signed type" & - "can lead to both sign changes and truncated values."), - ("UToSSameSz", "Conversion of same-typed unsized values" & - "will turn large numbers negative. Explicitly cast " & - "to a bigger type if possible to avoid this scenario."), - ("CanTruncate", "Conversion to a smaller type can result in values " & - "getting truncated."), - ("StoU", "Conversion of signed integer to unsigned type turns " & - "negative values into larger integers."), - ("SToSmallerU", "Conversion of signed integer to unsigned type turns " & - "negative values into larger integers, and the " & - "smaller type may truncate."), - ("HexTooLarge", "Hex number is too large for type $1"), - ("IntTooLarge", "Integer literal is too large for type $1"), - ("FloatTooLarge", "Integer portion of float is too large; " & - "use 'e' notation."), - ("ExpTooLarge", "Exponent portion of float is too large."), - ("BadHex", "Invalid hex literal"), - ("BadInt", "Invalid int literal"), - ("BadBool", "Invalid bool literal."), - ("BadByte", "Invalid value for a byte (cannot be above 0xff)"), - ("BadCodepoint", "Invalid character; unicode values may not be " & - "above U+10FFFF."), - ("BadCP2", "Invalid character; the unicode standard does not " & - "allow values from U+D800 to U+DFFF"), - ("LoseFormat", "Conversion discards string formatting information."), - ("TypeNotSigned", "Literal is negative, but the specified type " & - "is unsigned."), - ("TypeMismatch", "$1 and $2 are incompatible types."), - ("LoopVarAssign", "Cannot assign to (or re-declare) loop iteration " & - "variables."), - ("AlreadyAFunc", "Variable names cannot have names that are identical to " & - "functions named in the same module's top-level."), - ("AlreadyAVar", "Already found a top-level variable with the same " & - "name as this function; this is not allowed within a " & - "module."), - ("VarRedef", "Variable $1 declared multiple times in " & - "the same scope."), - ("LabelDupe", "Nested loops have the same label ($1)."), - ("LabelLoc", "label statement must come immediately before " & - "either a for loop or a while loop."), - ("DeadCode", "Dead code after $1."), - ("ElifLoc", "elif statement must follow either an " & - "if block, or another elif block."), - ("ElseLoc", "else statement must follow either an " & - "if block or another elif block."), - ("BadLoopExit", "$1 to label $2 is invalid, because " & - "it is not contained inside a loop that have that label."), - ("DupeParamProp", "Duplicate parameter property for $1"), - ("ParamType", "Parameter property $1 must be a $2."), - ("BadParamProp", "Invalid property for a parameter: $1"), - ("EnumDeclConst", "Enum value assignments must be constants"), - ("EnumInt", "Currently, enum values may only be int types"), - ("EnumReuse", "Value $1 has already been used in this enum."), - ("BinaryOpCompat", "LHS and RHS do not have compatable types " & - "(lhs: $1; rhs: $2)."), - ("NoBoolCast", "The condition cannot be automatically converted to " & - "a true / false value; provide a specific check. " & - "Type of condition is: $1"), - ("CannotCast", "Cannot convert from type $1 to " & - "type $2"), - ("BoolAutoCast", "This condition (of type $1) is not a " & - "boolean value, but is being auto-cast."), - ("TyDiffListItem", "List item type is not consistent with other items (" & - "Previous items were $1; this is a $2)"), - ("TyDiffKey", "Key type is not consistent with other keys (" & - "Previous keys were $1; this is a $2)"), - ("TyDiffValue", "Value type is not consistent with other values (" & - "Previous values were $1; this is a " & - "$2)"), - ("VarInSecDef", "In explicit section declarations, the `=` is " & - "expecting valid attributes only on the LHS, but " & - "$1 is not allowed as a top-level attribute." & - "Assuming this is a variable, not an attribute. " & - "If it should be an attribute, fix the specification. " & - "Otherwise, to get rid of this warning, either move " & - "the assignment outside the section block, or " & - "Use the = assignment operator, which " & - "forces variable assignment."), - ("TryVarAssign", "The attribute specifcation doesn't allow this field. " & - "If you'd like a variable with this name, you can " & - "use the = operator, which creates a" & - "variable, even when there's an attribute of the " & - "same name."), - ("AssignToSec", "Cannot assign directly to $1; it is a " & - "section that supports multiple instances, not a " & - "field within a section."), - ("AsgnSingleton", "Cannot assign directly to $1; it is a single" & - "section that contains fields, not a field within " & - "a section."), - ("AsgnInstance", "Cannot assign directly to $1; the parent " & - "section supports multiple instances, so this name " & - "would be an instance, to which you can then add fields."), - ("SecUnderField", "Cannot assign; $1 is a field, so may not " & - "contain sub-fields."), - ("SectionNoSpec", "While $1 is a valid section, there is no " & - "known section type named $2."), - ("RootNoSec", "There is no allowed section or attribute named: " & - "$1"), - ("SectionDenied", "While $2 is a known section type, it is not " & - "permitted within $1."), - ("RootSecDenied", "The root attribute scope doesn't allow $1 " & - "sections."), - ("AttrNotSpecd", "$1 is not an allowed attribute name."), - ("BadSectionType", "There isn't an allowed section type named $1."), - ("SecNotAllowed", "A $1 section is not allowed from within $2."), - ("NotASingleton", "The $1 section expects an instance name."), - ("IsASingleton", "A $1 section does not allow named instances;" & - " there is only one unnamed section."), - ("TypeVsSpec", "The type of this use ($1) is not compatible " & - "with the specified type ($2)"), - ("UnsignedUMinus", "Unary minus would turn an unsigned value to a signed " & - "value; cast either to the same size (which you " & - "shouldn't do if the int value might be larger than the " & - "highest signed value), or cast to the next size up if " & - "possible."), - ("128BitLimit", "$1 is not currently supported for 128-bit integers."), - ("U64Div", "Division producing a float isn't currently defined for " & - "unsigned 64-bit integers."), - ("TupleLhs", "When unpacking a tuple, all items on the left hand " & - "side of the assignment must be valid variables, and " & - "cannot currently be attributes."), - ("MemberTop", "Attribute member access (.) can only be applied " & - "to attributes, not to values."), - ("TupleConstIx", "When indexing a tuple, the index must evaluate to " & - "a constant integer."), - ("TupleIxBound", "Constant index is out of bounds for the tuple being " & - "indexed."), - ("ContainerType", "Cannot distinguish what kind of container type this " & - "is (could be a dict, list, tuple, etc.) " & - " Please explicitly declare this type."), - ("BadUrl", "Invalid URL for loading con4m source code."), - ("InsecureUrl", "Warning: loading file from an insecure URL. " & - "The contents could be injected by an attacker."), - ("NoImpl", "Could not find any function implementations in scope " & - "named $1. Full signature: $1$2"), - ("NotAFunc", "There is a variable named $1, but there was " & - "Not a function in scope with the signature: " & - "$1$2"), - ("BadSig", "No implementation of $1 matched this $3. " & - "The $3 had the type: $1$2"), - ("CallAmbig", "Found multiple functions matching $1$2" & - " Please disambiguate."), - ("NotIndexible", "Type $1 is not indexible."), - ("CantLiftFunc", "Function $1 has the same name as a " & - "global variable. Currently, other modules will have to " & - "explicitly qualify the module to call this function."), - ("DoesntExit", "Control does not reach the end of this $1."), - ("ExprResult", "Result of expression is unused. Expression result is " & - "of type $1. Please assign to _ to discard."), - ("InfLoop", "While loop does not exit."), - ("UseBeforeDef", "Likely use of $1 before assignment;" & - " If you think this is wrong, set a default value " & - "at the top of this scope."), - ("ConstReassign", "This variable has been declared constant, and was " & - "previously assigned."), - ("Immutable", "Cannot modify immutable values."), - ("DefWoUse", "Variable $1 is defined, but never used."), - ("UseWoDef", "Definite use of $1 without assignment."), - ("ConstNotSet", "Constant $1 was declared, but no value was " & - "set."), - ("$assign", "Variables starting with $ are set by the " & - "system, and cannot be otherwise assigned."), - ("SigOverlap", "In this module, for the function name $1, " & - "implementations have overlapping signatures:
" & - "2. $3
" & - "1. $2 (line $4)."), - ("NextCase", "Statement seemed to end, and was expecting " & - "another case branch, an else, " & - " or } to end the cases."), - ("CaseBodyStart", "Case bodies may either be regular blocks (i.e., " & - "{ ... }) or can be a colon followed by a list " & - "of statements."), - ("DeadTypeCase", "Variable can never be of type $1; case cannot " & - "be taken."), - ("BadIPv4", "Invalid IPv4 address."), - ("BadPort", "Invalid port number."), - ("BadDuration", "Invalid duration literal."), - ("BadSize", "Invalid size literal."), - ("BadUrl", "Invalid URL literal."), - ("BadDateTime", "Invalid literal value for datetime type."), - ("BadDate", "Invalid literal value for date type."), - ("BadTime", "Invalid literal value for time type."), - ("InvalidOther", "Invalid literal, did not match any known literal type."), - ("OtherLit", "Inferred type of literal is $1. If incorrect, " & - "place in quotes and provide an explicit literal " & - "modifier after (e.g., \"2 gb\"'size for " & - "a size literal.)"), - ("OtherQuotes", "The := operator is used for values of special " & - "types, where the system takes all input till the end " & - " of the line, then tries to treat it as a primitive " & - "type. The quotes here are treated like they're inside " & - "the data you're providing, which may not be what you " & - "want."), - ("OtherBrak", "The := operator does not process brackets " & - "or parentheses of any kind; this will be treated as " & - "a single string."), - ("OtherNum", "The := operator does not result in numeric " & - "types; it asks the system to guess from a number of " & - "specialized types. Use = or add a modifier " & - "that's appropriate for the type of value you're trying " & - "to create."), - ("AttrUse", "Attempted to use an attribute $1 that has not " & - "been set."), - ("RT_BadTOp", "Unknown function id for internal type API call " & - "($1)"), - ("NeedExName", "Local function specs must include names for every " & - "parameter."), - ("DupeExParam", "Duplicate parameter name for local function: " & - "$1"), - ("ExternZArgCt", "Local function spec has more arguments than external " & - "function for $1, which is currently not " & - "allowed. C function has $3 args, local has $2 ($4)."), - ("ExternCArgCt", "External function spec has more arguments than local " & - "function for $1, which is currently not " & - "allowed. C function has $3 args, local has $2."), - ("ArrayIxErr", "Array index $1 is not in bounds."), - ("DictKeyErr", "Could not find dictionary key: $1."), - ("LockedAttr", "Attempted to set attribute $1, which is " & - "locked. Current value: $2 Attempted value: " & - "$3"), - ("AlreadyLocked", "Tried to set lock-on-write for attribute $1, " & - "but it is already locked."), - ("DupeSection", "Section $1 cannot be specified twice"), - ("SpecFieldType", "Specification field $1 was expected to be " & - "of type $3, but was of type $2"), - ("RequiredProp", "Specified field $1 does not have the " & - "required property $2."), - ("SpecLock", "Cannot make changes to the attribute specification; " & - "it is locked."), - ("NoSecSpec", "When processing $1, there is no section " & - "specification for $2"), - ("MissingField", "Attribute $1 must be set, but was not " & - "provided."), - ("NoSecType", "In the top level of a confspec block, got " & - "$1 but only allowed contents are: " & - "root, singleton or named" & - " blocks."), - ("InstSecStart", "Was expecting this to start an instantiation " & - "of a $1 section named $2, but " & - "would need a { here."), - ("RootOverwrite", "Overwriting an existing root $1."), - ("MissingSec", "Spec tries to $2 a section of type $1" & - ", but no section named $1 has been spec'd yet."), - - ("BadRange", "When validating $1, an invalid value, " & - "$2, was chosen. But the value must be no less " & - "than $3 and no greater than $4."), - ("BadChoice", "When validating $1, an invalid value, " & - "$2, was chosen. Valid choices are: $3"), - ("MissingSection", "When validating $1, expected a required " & - "section named $2, which was not provided."), - ("MissingField", "When validating $1, expected a required " & - "field named $2, but it was not found."), - ("BadSection", "When validating $1, the section $2 " & - "is not allowed."), - ("NotTSpec", "When attempting to determine the type of the " & - "value $1$2, the field $3 was " & - "expected to specify the value's type, but " & - "that field did not contain a type specification."), - ("BadField", "When validating $1, found the field " & - "$2, which is not a valid field name for a " & - "$3 section."), - ("FieldMutex", "When validating $1, found the field " & - "$2, which is specified to not be able " & - "to appear together in a section with the field " & - "$3 (which was also present)."), - ("ExternVarargs", "External variable argument functions are not yet " & - " supported (external function $1)"), - ("StackOverflow", "Exceeded the maximum stack depth."), - ("NoSpecForSec", "Used a section $1, which is not defined " & - "by the attribute specification."), - ("InvalidStart", "Found the start of an attribute assignment that " & - "is invalid according to the spec, at: $1"), - ("NoInstance", "This section must have an instance, meaning " & - "you must specify named sections underneath it."), - ("DupeProp", "Property $1 cannot appear twice in one " & - "item spec"), - ("NotBool", "Property $1 is reuquired to be bool, " & - "but here is $2)"), - ("RangeAndChoice", "Cannot have both range and choice " & - "constraints for the same field."), - ("TyDiffListItem", "Inconsistent item type for choice. Previously " & - "it type was $1, but here it was $2"), - ("BadRangeSpec", "Start value for a range must be less than the end " & - "value."), - ("SpecWhenLocked", "Cannot add confspec fields; the " & - "specification has already been locked."), - ("DupeSpecField", "Duplicate specification for field $1."), - ("TCaseOverlap", "Type cases have overlapping types."), - ("DupeExclusion", "Exclusion duplicated for $1."), - ("DupeAllow", "Section $1 appears multiple times in " & - "allow list"), - ("AllowInReq", "Section $1 appears in both require" & - "and allow; suggest removing from allow"), - ("DupeRequire", "Section $1 appears multiple times in " & - "require list"), - ("DupeRootSpec", "Should not have duplicate root section" & - "in one module"), - ("NotConst", "Value must be a constant at compile time."), - ("ParamValParTy", "The validation function for this parameter takes an " & - "argument that is inconsistent with the type we have " & - "for the parameter. Previously, we had $1, " & - "But the function takes a $2."), - ("ParamValNArgs", "Validation functions for parameters must take a " & - "single argument, where a value to validate will be " & - "passed. It must return a string; the empty string " & - "indicates no validation error. Otherwise, the return " & - "value should be an error message."), - ("ParamValRetTy", "Validation functions for parameters must return " & - "a string, which represents any error message to " & - "give as feedback."), - ("NoCbMatch", "Could not find a function to match to the callback " & - "$1$2"), - ("ParamNotSet", "Module parameter $1 was not set when entering " & - "module $2."), - ("ParamNotValid", "Module parameter $1 was set when entering " & - "module $2, but was not valid: $3"), - - ] -# "CustomValidator" is possible, but not looked up in this array. - -proc baseError*(list: var seq[Con4mError], code: string, cursor: StringCursor, - modname: string, line: int, lineOffset: int, - phase: Con4mErrPhase, severity = LlFatal, - extraContents: seq[string] = @[], detail: Rope = nil, - trace: string = "", ii: Option[InstantiationInfo] = - none(InstantiationInfo)) = - if severity < config_log_level: - return - var err = Con4mError(phase: phase, severity: severity, code: code, - cursor: cursor, modname: modname, line: line, - offset: lineOffset, extra: extraContents, detail: detail) - - when not defined(release): - err.trace = trace - if ii.isSome(): - err.ii = ii.get() - - var foundCode = false - for (k, v) in errorMsgs: - if code == k: - foundCode = true - break - if not foundCode: - print fgColor("error: ", "red") + text("Error code '") + em(code) + - text("' was not found.") - - list.add(err) - -proc baseError*(list: var seq[Con4mError], code: string, tok: Con4mToken, - modname: string, phase: Con4mErrPhase, severity = LlFatal, - extra: seq[string] = @[], detail: Rope = nil, trace = "", - ii = none(InstantiationInfo)) = - list.baseError(code, tok.cursor, modname, tok.lineNo, tok.lineOffset, - phase, severity, extra, detail, trace, ii) - -proc lexBaseError*(ctx: Module, basemsg: string, t: Con4mToken = nil, - subs: seq[string] = @[]) = - var t = t - - if t == nil: - t = ctx.tokens[^1] - - ctx.errors.baseError(basemsg, t.cursor, ctx.modname & ctx.ext, t.lineNo, - t.lineOffset, ErrLex, LlFatal, subs) - -proc lexFatal*(ctx: Module, basemsg: string, t: Con4mToken = nil) = - ctx.lexBaseError(basemsg, t) - raise newCon4mException() - -template lexError*(msg: string, t: Con4mToken = nil) = - ctx.lexFatal(msg, t) - -proc baseError*(list: var seq[Con4mError], code: string, node: ParseNode, - modname: string, phase: Con4mErrPhase, severity = LlFatal, - extra = seq[string](@[]), detail: Rope = nil, trace = "", - ii = none(InstantiationInfo)) = - if node == nil: - list.baseError(code, nil, modname, 0, 0, phase, severity, extra, - detail, trace, ii) - return - - if node.err and severity != LlInfo: - return - if severity in [LlErr, LlFatal]: - node.err = true - list.baseError(code, node.token, modname, phase, severity, extra, - detail, trace, ii) - -template irError*(ctx: Module, msg: string, extra: seq[string] = @[], - w = ParseNode(nil), detail = Rope(nil)) = - var where = if w == nil: ctx.pt else: w - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlFatal, - extra, detail) - -template irError*(ctx: Module, msg: string, w: IrNode, - extra: seq[string] = @[], detail: Rope = nil) = - var where = if w == nil: ctx.pt else: w.parseNode - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlFatal, - extra, detail) - -template irNonFatal*(ctx: Module, msg: string, extra: seq[string] = @[], - w = ParseNode(nil)) = - # Things we consider errors, but we may end up allowing. Currently, this - # is just for use-before-def errors. - var where = if w == nil: ctx.pt else: w - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlErr, - extra) - -template irNonFatal*(ctx: Module, msg: string, w: IrNode, - extra: seq[string] = @[]) = - # Things we consider errors, but we may end up allowing. Currently, this - # is just for use-before-def errors. - var where = if w == nil: ctx.pt else: w.parseNode - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlErr, - extra) - -template irWarn*(ctx: Module, msg: string, extra: seq[string] = @[], - w = ParseNode(nil)) = - var where = if w == nil: ctx.pt else: w - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlWarn, - extra) - -template irWarn*(ctx: Module, msg: string, w: IrNode, - extra: seq[string] = @[]) = - var where = if w == nil: ctx.pt else: w.parseNode - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlWarn, - extra) - -template irInfo*(ctx: Module, msg: string, extra: seq[string] = @[], - w = ParseNode(nil)) = - var where = if w == nil: ctx.pt else: w - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlInfo, - extra) - -template irInfo*(ctx: Module, msg: string, w: IrNode, - extra: seq[string] = @[]) = - var where = if w == nil: ctx.pt else: w.parseNode - ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlInfo, - extra) - -template loadError*(ctx: CompileCtx, msg: string, modname: string, - extra: seq[string] = @[]) = - # TODO: don't hardcode the extension. - ctx.errors.baseError(msg, ParseNode(nil), modname & ".c4m", ErrLoad, - LlFatal, extra) - -template loadWarn*(ctx: CompileCtx, msg: string, modname: string, - extra: seq[string] = @[]) = - ctx.errors.baseError(msg, ParseNode(nil), modname & ".c4m", ErrLoad, LlWarn, - extra) - -proc canProceed*(errs: seq[Con4mError]): bool = - for err in errs: - if err.severity in [LlErr, LlFatal]: - - return false - return true - -template canProceed*(ctx: CompileCtx): bool = - ctx.errors.canProceed() - -proc lookupMsg(code: string): string = - for (k, v) in errorMsgs: - if k == code: - return v - - return "Unknown error code: " & code - -proc performSubs(extra: seq[string], s: var string) = - for i, item in extra: - s = s.replace("$" & `$`(i + 1), item) - -proc oneErrToRopeList(err: Con4mError, s: string): seq[Rope] = - case err.severity - of LlErr, LlFatal: - result.add(fgColor("error:", "red").td().overflow(OTruncate)) - of LlWarn: - result.add(fgColor("warn:", "yellow").td().overflow(OTruncate)) - of LLInfo: - result.add(fgColor("info:", "atomiclime").td().overflow(OTruncate)) - of LlNone: - unreachable - - if err.modname.len() != 0: - let modname = fgColor(err.modname, "jazzberry") + text(":") - result.add(modname.overflow(OTruncate)) - else: - result.add(text("")) - if err.line >= 1: - let offset = td(text(`$`(err.line) & ":" & `$`(err.offset + 1) & ":")) - result.add(offset.overflow(OTruncate)) - else: - result.add(text("")) - - result.add(s.htmlStringToRope(markdown = false, add_div = false)) - -proc getVerboseInfo(err: Con4mError): Rope = - var - noLoc = false - - if err.cursor == nil: - return nil - - elif err.offset < 0: - noLoc = true - - let - src = $(err.cursor.runes) - lines = src.split("\n") - - if lines.len() == 0 or err.line <= 0: - return nil - - result = text(lines[err.line - 1]) + newBreak() - - if not noLoc: - result += em(repeat((' '), err.offset) & "^") + newBreak() - - if err.detail != nil: - result = result + err.detail - -proc getLocWidth(errs: seq[Con4mError]): int = - for err in errs: - let r = 2 + `$`(err.line).len() + `$`(err.offset + 1).len() - - if r > result: - result = r - -proc getModuleWidth(errs: seq[Con4mError]): int = - for err in errs: - let l = err.modname.len() - if l != 0 and (l + 1) > result: - result = l + 1 - -proc dupeLocationCheck(err: Con4mError, locs: var seq[(int, int)]): bool = - for (l, c) in locs: - if err.line == l and err.offset == c: - return true - - locs.add((err.line, err.offset)) - -proc formatErrors*(errs: seq[Con4mError], verbose = true): Rope = - var - errList: seq[seq[Rope]] - locs: seq[(int, int)] - - let - mw = errs.getModuleWidth() + 1 - lw = errs.getLocWidth() + 1 - - for i, error in errs: - if error.dupeLocationCheck(locs): - continue - if i == 30: # TODO; make this a configurable limit. - break - var msg = error.code.lookupMsg() & " (" & error.code & ")" - error.extra.performSubs(msg) - errList.add(error.oneErrToRopeList(msg)) - - if not verbose: - let table = quickTable[Rope](errList, noHeaders = true, - borders = BorderNone) - var one: Rope - result = table.colWidths([(7, true), (mw, true), (lw, true), (0, false)]) - result = result.lpad(0, true).rpad(0, true) - result = result.bpad(0, true).tpad(0, true) - else: - for i, item in errlist: - var table = quickTable(@[item], noHeaders = true, borders = BorderNone) - table = table.colWidths([(7, true), (mw, true), (lw, true), (0, false)]) - table = table.lpad(0, true).rpad(0, true).bpad(0, true).tpad(0, true) - var one = table + container(errs[i].getVerboseInfo()) - result += one - -proc get_con4m_stack_trace(ctx: RuntimeState): Rope {.importc, cdecl.} -proc find_string_at(mem: string, offset: int): string {.importc, cdecl.} -proc toString(x: TypeId): string {.importc, cdecl.} - -proc location_from_instruction*(ctx: RuntimeState, - ins: ptr ZInstruction): (string, int) = - return (ctx.obj.moduleContents[ins.moduleId - 1].modname, - int(ins.lineno)) - -proc print_con4m_trace*(ctx: RuntimeState) {.exportc, cdecl.} = - print(ctx.get_con4m_stack_trace(), file = stderr) - -proc formatLateError(err: string, severity: Con4mSeverity, location: string, - args: seq[string], verbose = true): Rope = - # `location` should be an indication of the instruction if we are executing, - # attribute information if we're validating, and whatever is appropriate - # if it's some other error. - var - msg = lookupMsg(err) - row: seq[Rope] - - performSubs(args, msg) - - case severity - of LlErr, LlFatal: - row.add(fgColor("error: ", "red").td().lpad(0)) - of LlWarn: - row.add(fgColor("warn: ", "yellow").td().overflow(OTruncate)) - of LLInfo: - row.add(fgColor("info: ", "atomiclime").td().overflow(OTruncate)) - of LlNone: - unreachable - - row.add(italic(location & ": ")) - row.add(markdown(msg)) - row.add(italic("(" & err & ")")) - - var - width_used = 11 + location.len() + err.len() - remains = terminalWidth() - width_used - msg_width = row[2].runeLength() + 1 - msg_col: int - - if msg_width < remains: - msg_col = msg_width - else: - msg_col = remains - - result = @[row].quickTable(noheaders = true, borders = BorderNone) - result.colWidths([(7, true), (location.len() + 2, true), - (msg_col, true), (err.len() + 2, true)]) - result.lpad(0, true).rpad(0, true).bpad(0, true).tpad(0, true) - -proc assemble_validation_msg(ctx: RuntimeState, path: string, msg: Rope, - code: string, other: Rope = nil): Rope = - var - nim_path = path - last_touch: Rope - - if nim_path.len() != 0: - if nim_path[0] == '.': - nim_path = nim_path[1 .. ^1] - - if nim_path[^1] == '.': - nim_path = nim_path[0 ..< ^1] - - let attrOpt = ctx.attrs.lookup(nim_path) - if attrOpt.isSome(): - let record = attrOpt.get() - if record.lastset == nil: - last_touch = text("Attribute has not been set this execution.") - else: - let (module, line) = ctx.location_from_instruction(record.lastset) - - last_touch = text("Attribute last set at: ") + - em(module & ":" & $line) - - if nim_path.len() == 0: - nim_path = "root attribute section" - - result = text("Validation for ") + em(nim_path) + text(" failed: ") - result += italic(msg) - result += last_touch - - if other != nil: - result += text(" ") + other - - result += italic(" (" & code & ")") - GC_ref(result) - -proc formatValidationError*(ctx: RuntimeState, attr: string, err: string, - args: seq[string]): Rope = - var msg = err.lookupMsg() - performSubs(args, msg) - - let asRope = htmlStringToRope(msg, markdown = false, add_div = false) - - return ctx.assemble_validation_msg(attr, asRope, err) - -proc formatValidationError*(ctx: RuntimeState, attr: C4Str, err: string, - args: seq[string]): Rope = - return ctx.formatValidationError(attr.toNimStr(), err, args) - -proc customValidationError*(ctx: RuntimeState, path: C4Str, usrmsg: C4Str, - cb: ptr ZCallback): Rope = - let - asRope = htmlStringToRope(usrmsg.toNimStr(), markdown = false, - add_div = false) - cbName = find_string_at(ctx.obj.staticData, cb.nameOffset) - validator_info = text("Validation function: ") + - em(cbname & cb.tid.toString()) - - return ctx.assemble_validation_msg(path.toNimStr(), asRope, - "CustomValidator", validator_info) - -proc runtimeIssue(ctx: RuntimeState, err: string, args: seq[string], - severity = LLFatal) = - if severity < config_log_level: - return - - let - instr = addr ctx.curModule.instructions[ctx.ip] - (m, l) = ctx.location_from_instruction(instr) - extra = if l == -1: "" else: ":" & $(l) - loc = "When executing " & m & extra - - print(err.formatLateError(severity, loc, args), file = stderr) - -proc runtimeWarn*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = - if config_debug: - ctx.print_con4m_trace() - ctx.runtimeIssue(err, args, LlWarn) - -proc runtimeError*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = - ctx.print_con4m_trace() - ctx.runtimeIssue(err, args) - quit(-1) - -proc runtimeError*(ctx: RuntimeState, err: string, file: ZModuleInfo, line: int, - args: seq[string] = @[]) = - var extra: string = "" - if line != -1: - extra = ":" & $(line) - - let loc = "When executing " & file.modname & extra - - ctx.print_con4m_trace() - print(err.formatLateError(LlErr, loc, args), file = stderr) - quit(-1) - -proc codeGenError*(err: string, args: seq[string] = @[]) = - # TODO: the module / function info needs to show up here. - print(err.formatLateError(LlErr, "When generating code", args), - file = stderr) - quit(-1) - -proc objLoadWarn*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = - if config_log_level > LlWarn: - return - - # TODO: the module / function info needs to show up here. - print(err.formatLateError(LlWarn, "When loading object file", args), - file = stderr) - quit(-1) - -let sigNameMap = { 1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", - 6: "SIGABRT",7: "SIGBUS", 9: "SIGKILL", 11: "SIGSEGV", - 15: "SIGTERM" }.toDict() -var - LC_ALL {.importc, header: "".}: cint - savedTermState: Termcap - -proc restoreTerminal() {.noconv.} = - tcSetAttr(cint(1), TcsaConst.TCSAFLUSH, savedTermState) - -proc regularTerminationSignal(signal: cint) {.noconv.} = - let pid = getpid() - - print(h5("pid: " & $(pid) & " - Aborting due to signal: " & - sigNameMap[signal] & "(" & $(signal) & ")"), file = stderr) - - let rt = getCon4mRuntime() - - if rt != nil and rt.running: - print(getCon4mRuntime().get_con4m_stack_trace(), file = stderr) - else: - print(h2(text("Program was ") + em("NOT") + - text(" executing when we crashed."))) - - when defined(debug): - print(h4("Nim stack trace:")) - echo getStackTrace() - else: - print(h4(text("Nim stack trace is unavailable " & - "(must compile w/ ") + strong("-d:debug") + text(" for traces)"))) - var sigset: SigSet - - discard sigemptyset(sigset) - - for signal in [SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGKILL, - SIGSEGV, SIGTERM]: - discard sigaddset(sigset, signal) - discard sigprocmask(SIG_SETMASK, sigset, sigset) - - - exitnow(signal + 128) - -proc setlocale(category: cint, locale: cstring): cstring {. importc, cdecl, - nodecl, header: "", discardable .} - -proc setupTerminal*() = - setlocale(LC_ALL, cstring("")) - tcGetAttr(cint(1), savedTermState) - addQuitProc(restoreTerminal) - -proc setupSignalHandlers*() = - var handler: SigAction - - handler.sa_handler = regularTerminationSignal - handler.sa_flags = 0 - - for signal in [SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGKILL, - SIGSEGV, SIGTERM]: - discard sigaction(signal, handler, nil) +import "err"/[errbase, output, backtrace, signal] +export errbase, output, backtrace, signal diff --git a/src/err/backtrace.nim b/src/err/backtrace.nim new file mode 100644 index 0000000..f14afb9 --- /dev/null +++ b/src/err/backtrace.nim @@ -0,0 +1,59 @@ +import ".."/common + +template getModName*(ctx: RuntimeState): string = + ctx.curModule.modName & ".c4m" + +template getLineNo*(ctx: RuntimeState): int = + ctx.curModule.instructions[ctx.ip].lineNo + +proc storageAddr*(ctx: RuntimeState, x: ZInstruction, + p: int64): ptr pointer = + if x.moduleId == -1: + result = addr ctx.stack[ctx.fp - (p * 2)] + elif x.moduleId == -2: + # Static data, no type info assumed. + result = cast[ptr pointer](addr ctx.obj.staticData[p]) + else: + result = addr ctx.moduleAllocations[x.moduleId][p * 2] + +proc getSourceLoc*(ctx: RuntimeState): string = + ## Decode the source location of the current runtime state from + ## the current instruction. + let line = ctx.getLineNo() + if line != -1: + return ctx.getModName() & " (line #" & $(ctx.getLineNo()) & ")" + else: + return "" + +proc get_stack_trace*(ctx: RuntimeState): Rope {.exportc, cdecl.} = + var cells: seq[seq[string]] = @[@["Caller module", "Line #", + "Call target"]] + + for i in 1 ..< ctx.numFrames: + var + frame = ctx.frameInfo[i] + row: seq[string] + + row.add(frame.callModule.modname) + if i == ctx.numFrames - 1: + row.add($(ctx.getLineNo())) + else: + row.add($(frame.calllineno)) + + if frame.targetfunc == nil: + row.add(frame.targetmodule.modname & ".__mod_run__") + else: + row.add(frame.targetmodule.modname & "." & frame.targetfunc.funcname) + + cells.add(row) + + let loc = ctx.getSourceLoc() + if loc != "": + result = cells.quicktable(title = "Stack trace", + caption = "Source location: " & + ctx.getSourceLoc()) + else: + result = cells.quicktable(title = "Stack trace") + + result.colWidths([(17, true), (15, true), (20, true)]) + result.tpad(0, true).bpad(0, true) diff --git a/src/err/errbase.nim b/src/err/errbase.nim new file mode 100644 index 0000000..1695722 --- /dev/null +++ b/src/err/errbase.nim @@ -0,0 +1,155 @@ +import ".."/common +import "."/messages + +## If we have to bail on control flow, we'll do so here. +proc newCon4mException*(errs: seq[Con4mError] = @[]): Con4mException = + result = new(Con4mException) + result.errors = errs + +proc newCon4mLongJmp*(msg = ""): Con4mLongJmp = + result = new(Con4mLongJmp) + result.msg = msg + +template con4mLongJmp*(msg = "") = + raise newCon4mLongJmp(msg) + +proc baseError*(list: var seq[Con4mError], code: string, cursor: StringCursor, + modname: string, line: int, lineOffset: int, + phase: Con4mErrPhase, severity = LlFatal, + extraContents: seq[string] = @[], detail: Rope = nil, + trace: string = "", ii: Option[InstantiationInfo] = + none(InstantiationInfo)) = + if severity < config_log_level: + return + var err = Con4mError(phase: phase, severity: severity, code: code, + cursor: cursor, modname: modname, line: line, + offset: lineOffset, extra: extraContents, detail: detail) + + when not defined(release): + err.trace = trace + if ii.isSome(): + err.ii = ii.get() + + var foundCode = false + for (k, v) in errorMsgs: + if code == k: + foundCode = true + break + if not foundCode: + print fgColor("error: ", "red") + text("Error code '") + em(code) + + text("' was not found.") + + list.add(err) + +proc baseError*(list: var seq[Con4mError], code: string, tok: Con4mToken, + modname: string, phase: Con4mErrPhase, severity = LlFatal, + extra: seq[string] = @[], detail: Rope = nil, trace = "", + ii = none(InstantiationInfo)) = + list.baseError(code, tok.cursor, modname, tok.lineNo, tok.lineOffset, + phase, severity, extra, detail, trace, ii) + +proc lexBaseError*(ctx: Module, basemsg: string, t: Con4mToken = nil, + subs: seq[string] = @[]) = + var t = t + + if t == nil: + t = ctx.tokens[^1] + + ctx.errors.baseError(basemsg, t.cursor, ctx.modname & ctx.ext, t.lineNo, + t.lineOffset, ErrLex, LlFatal, subs) + +proc lexFatal*(ctx: Module, basemsg: string, t: Con4mToken = nil) = + ctx.lexBaseError(basemsg, t) + raise newCon4mException() + +template lexError*(msg: string, t: Con4mToken = nil) = + ctx.lexFatal(msg, t) + +proc baseError*(list: var seq[Con4mError], code: string, node: ParseNode, + modname: string, phase: Con4mErrPhase, severity = LlFatal, + extra = seq[string](@[]), detail: Rope = nil, trace = "", + ii = none(InstantiationInfo)) = + if node == nil: + list.baseError(code, nil, modname, 0, 0, phase, severity, extra, + detail, trace, ii) + return + + if node.err and severity != LlInfo: + return + if severity in [LlErr, LlFatal]: + node.err = true + list.baseError(code, node.token, modname, phase, severity, extra, + detail, trace, ii) + +template irError*(ctx: Module, msg: string, extra: seq[string] = @[], + w = ParseNode(nil), detail = Rope(nil)) = + var where = if w == nil: ctx.pt else: w + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlFatal, + extra, detail) + +template irError*(ctx: Module, msg: string, w: IrNode, + extra: seq[string] = @[], detail: Rope = nil) = + var where = if w == nil: ctx.pt else: w.parseNode + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlFatal, + extra, detail) + +template irNonFatal*(ctx: Module, msg: string, extra: seq[string] = @[], + w = ParseNode(nil)) = + # Things we consider errors, but we may end up allowing. Currently, this + # is just for use-before-def errors. + var where = if w == nil: ctx.pt else: w + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlErr, + extra) + +template irNonFatal*(ctx: Module, msg: string, w: IrNode, + extra: seq[string] = @[]) = + # Things we consider errors, but we may end up allowing. Currently, this + # is just for use-before-def errors. + var where = if w == nil: ctx.pt else: w.parseNode + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlErr, + extra) + +template irWarn*(ctx: Module, msg: string, extra: seq[string] = @[], + w = ParseNode(nil)) = + var where = if w == nil: ctx.pt else: w + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlWarn, + extra) + +template irWarn*(ctx: Module, msg: string, w: IrNode, + extra: seq[string] = @[]) = + var where = if w == nil: ctx.pt else: w.parseNode + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlWarn, + extra) + +template irInfo*(ctx: Module, msg: string, extra: seq[string] = @[], + w = ParseNode(nil)) = + var where = if w == nil: ctx.pt else: w + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlInfo, + extra) + +template irInfo*(ctx: Module, msg: string, w: IrNode, + extra: seq[string] = @[]) = + var where = if w == nil: ctx.pt else: w.parseNode + ctx.errors.baseError(msg, where, ctx.modname & ctx.ext, ErrIrGen, LlInfo, + extra) + +template loadError*(ctx: CompileCtx, msg: string, modname: string, + extra: seq[string] = @[]) = + # TODO: don't hardcode the extension. + ctx.errors.baseError(msg, ParseNode(nil), modname & ".c4m", ErrLoad, + LlFatal, extra) + +template loadWarn*(ctx: CompileCtx, msg: string, modname: string, + extra: seq[string] = @[]) = + ctx.errors.baseError(msg, ParseNode(nil), modname & ".c4m", ErrLoad, LlWarn, + extra) + +proc canProceed*(errs: seq[Con4mError]): bool = + for err in errs: + if err.severity in [LlErr, LlFatal]: + + return false + return true + +template canProceed*(ctx: CompileCtx): bool = + ctx.errors.canProceed() diff --git a/src/err/messages.nim b/src/err/messages.nim new file mode 100644 index 0000000..a42de95 --- /dev/null +++ b/src/err/messages.nim @@ -0,0 +1,425 @@ +const errorMsgs* = [ + ("FileNotFound", "Module could not be located."), + ("ModuleNotFound", "Module $1 could not be located."), + ("StrayCr", "Carriage return (\r) without newline."), + ("BadEscape", "Unterminated escape sequence in literal."), + ("UnicodeEscape", "Invalid unicode escape character found in literal."), + ("LitModExpected", "Expected a valid literal modifier"), + ("CommentTerm", "Unterminated C-style long comment."), + ("BadFloat", "Invalid character in float literal"), + ("MissingDigits", "Must have digits after the dot in a valid float literal"), + ("BadExponent", "Float exponent specified, with no exponent " & + "value provided"), + ("BadChar", "Invalid character literal"), + ("StringNl", "Unterminated single-quoted string."), + ("StringTerm", "Unterminated string literal"), + ("CharTerm", "Unterminated character literal"), + ("OtherTerm", "Unterminated literal"), + ("BadChar", "Invalid character found in token"), + ("StmtEnd", "Expected end of a statement after $1."), + ("NotALit", "Not a literal; literal modifier not allowed here."), + ("BadLitMod", "Unknown literal modifier $1 for $2" & + " syntax."), + ("LitModTypeErr", "Literal modifier $1 can't be used with " & + "$2 literals"), + ("MissingTok", "Expected $1 here."), + ("MemberSpec", "'.' operator must have an identifier on " & + "both sides."), + ("ItemExpected", "Expected either another item or '$1'"), + ("BadTuple", "Tuple types must contain two or more items."), + ("AccessExpr", "Expected either an identifier or paren here."), + ("ExprStart", "Expected the start of an expression here, but got $1."), + ("BinaryNot", "'not' operator takes only one operand."), + ("LitExpected", "Expected a literal value here."), + ("ForFromIx", "for ... from loops must have a single index " & + "variable."), + ("NameInTypeSpec", "'$1' is not a builtin type. " & + "Use struct[typename] for user types."), + ("BadOneOf", "OneOf types must have multiple type options that " & + "are more constrained than a generic type variable."), + ("BadObjType", "Object types must provide either a name or a type " & + "variable if specifying an object where no fields " & + "will be referenced."), + ("BadTypeDecl", "Invalid syntax inside a type declaration."), + ("BadFormalEnd", "Expected either a closing parenthesis (')'" & + " or an additional parameter."), + ("BadLock", "Attribute Lock operator (~) must be " & + "followed either by an assignment statement or " & + "an attribute to lock."), + ("BadUseSyntax", "'use' statement requires a string literal " & + " after 'from'"), + ("BadParamName", "'parameter' keyword must be followed by " & + "an attribute (which can be dotted), or the " & + "'var' keyword followed by a local variable " & + "name. The variable can be optionally typed."), + ("BadExternField", "Bad field for 'extern' block."), + ("NeedSig", "Field requires a function type signature."), + ("BadRCParam", "Reference counting specs must either be identifiers " & + "that match the names given in the function signature, " & + "or the special value return (for incref only)"), + ("BadCType", "Invalid C type for external parameter: $1"), + ("PureBool", "The 'pure' property for external functions " & + "must be a boolean value (true or " & + "false)"), + ("DupeExtField", "Duplicate field $1 provided for an " & + "extern block."), + ("DupeDllName", "Duplicate DLL name $1 provided for an " & + "external function (ignored)."), + ("DupeVal", "Duplicate parameter value $1 in the " & + "$2 property of the extern spec."), + ("ExtNotSpecd", "None of the external function parameters were given" & + " the name $1"), + ("ExtAllocNHold", "Extern function parameter $1 cannot be " & + "spec'd to have the external function hold memory " & + "we pass, and to allocate that memory."), + ("NoMemMan", "Since $1 is not a pointer or array type, " & + "memory management will not be performend, and this " & + "annotation will be ignored."), + ("WontLink", "Was not able to locate the external symbol $1" & + ", which may be required for running."), + ("MissingSym", "Was not able to locate the external symbol $1" & + "; program will crash if it is accessed, unless " & + "it is dynamically loaded first."), + ("PurePlz", "Please provide a value for the pure property " & + "for extern functions. Pure functions always " & + "return the same output for the same input, and do " & + "not do any I/O, allowing us to pre-execute."), + ("EofInBlock", "Block was not closed when end-of-file was found."), + ("TopLevelOnly", "'$1' is only allowed at the top-level of a " & + "module."), + ("TopLevelPlural", "$1 are only allowed at the top-level of a module."), + ("InLoopsOnly", "'$1' is only allowed inside loops."), + ("RetOutOfFunc", "'return' is only allowed inside functions."), + ("UToSSmaller", "Conversion of unsigned value to a smaller signed type" & + "can lead to both sign changes and truncated values."), + ("UToSSameSz", "Conversion of same-typed unsized values" & + "will turn large numbers negative. Explicitly cast " & + "to a bigger type if possible to avoid this scenario."), + ("CanTruncate", "Conversion to a smaller type can result in values " & + "getting truncated."), + ("StoU", "Conversion of signed integer to unsigned type turns " & + "negative values into larger integers."), + ("SToSmallerU", "Conversion of signed integer to unsigned type turns " & + "negative values into larger integers, and the " & + "smaller type may truncate."), + ("HexTooLarge", "Hex number is too large for type $1"), + ("IntTooLarge", "Integer literal is too large for type $1"), + ("FloatTooLarge", "Integer portion of float is too large; " & + "use 'e' notation."), + ("ExpTooLarge", "Exponent portion of float is too large."), + ("BadHex", "Invalid hex literal"), + ("BadInt", "Invalid int literal"), + ("BadBool", "Invalid bool literal."), + ("BadByte", "Invalid value for a byte (cannot be above 0xff)"), + ("BadCodepoint", "Invalid character; unicode values may not be " & + "above U+10FFFF."), + ("BadCP2", "Invalid character; the unicode standard does not " & + "allow values from U+D800 to U+DFFF"), + ("LoseFormat", "Conversion discards string formatting information."), + ("TypeNotSigned", "Literal is negative, but the specified type " & + "is unsigned."), + ("TypeMismatch", "$1 and $2 are incompatible types."), + ("LoopVarAssign", "Cannot assign to (or re-declare) loop iteration " & + "variables."), + ("AlreadyAFunc", "Variable names cannot have names that are identical to " & + "functions named in the same module's top-level."), + ("AlreadyAVar", "Already found a top-level variable with the same " & + "name as this function; this is not allowed within a " & + "module."), + ("VarRedef", "Variable $1 declared multiple times in " & + "the same scope."), + ("LabelDupe", "Nested loops have the same label ($1)."), + ("LabelLoc", "label statement must come immediately before " & + "either a for loop or a while loop."), + ("DeadCode", "Dead code after $1."), + ("ElifLoc", "elif statement must follow either an " & + "if block, or another elif block."), + ("ElseLoc", "else statement must follow either an " & + "if block or another elif block."), + ("BadLoopExit", "$1 to label $2 is invalid, because " & + "it is not contained inside a loop that have that label."), + ("DupeParamProp", "Duplicate parameter property for $1"), + ("ParamType", "Parameter property $1 must be a $2."), + ("BadParamProp", "Invalid property for a parameter: $1"), + ("EnumDeclConst", "Enum value assignments must be constants"), + ("EnumInt", "Currently, enum values may only be int types"), + ("EnumReuse", "Value $1 has already been used in this enum."), + ("BinaryOpCompat", "LHS and RHS do not have compatable types " & + "(lhs: $1; rhs: $2)."), + ("NoBoolCast", "The condition cannot be automatically converted to " & + "a true / false value; provide a specific check. " & + "Type of condition is: $1"), + ("CannotCast", "Cannot convert from type $1 to " & + "type $2"), + ("BoolAutoCast", "This condition (of type $1) is not a " & + "boolean value, but is being auto-cast."), + ("TyDiffListItem", "List item type is not consistent with other items (" & + "Previous items were $1; this is a $2)"), + ("TyDiffKey", "Key type is not consistent with other keys (" & + "Previous keys were $1; this is a $2)"), + ("TyDiffValue", "Value type is not consistent with other values (" & + "Previous values were $1; this is a " & + "$2)"), + ("VarInSecDef", "In explicit section declarations, the `=` is " & + "expecting valid attributes only on the LHS, but " & + "$1 is not allowed as a top-level attribute." & + "Assuming this is a variable, not an attribute. " & + "If it should be an attribute, fix the specification. " & + "Otherwise, to get rid of this warning, either move " & + "the assignment outside the section block, or " & + "Use the = assignment operator, which " & + "forces variable assignment."), + ("TryVarAssign", "The attribute specifcation doesn't allow this field. " & + "If you'd like a variable with this name, you can " & + "use the = operator, which creates a" & + "variable, even when there's an attribute of the " & + "same name."), + ("AssignToSec", "Cannot assign directly to $1; it is a " & + "section that supports multiple instances, not a " & + "field within a section."), + ("AsgnSingleton", "Cannot assign directly to $1; it is a single" & + "section that contains fields, not a field within " & + "a section."), + ("AsgnInstance", "Cannot assign directly to $1; the parent " & + "section supports multiple instances, so this name " & + "would be an instance, to which you can then add fields."), + ("SecUnderField", "Cannot assign; $1 is a field, so may not " & + "contain sub-fields."), + ("SectionNoSpec", "While $1 is a valid section, there is no " & + "known section type named $2."), + ("RootNoSec", "There is no allowed section or attribute named: " & + "$1"), + ("SectionDenied", "While $2 is a known section type, it is not " & + "permitted within $1."), + ("RootSecDenied", "The root attribute scope doesn't allow $1 " & + "sections."), + ("AttrNotSpecd", "$1 is not an allowed attribute name."), + ("BadSectionType", "There isn't an allowed section type named $1."), + ("SecNotAllowed", "A $1 section is not allowed from within $2."), + ("NotASingleton", "The $1 section expects an instance name."), + ("IsASingleton", "A $1 section does not allow named instances;" & + " there is only one unnamed section."), + ("TypeVsSpec", "The type of this use ($1) is not compatible " & + "with the specified type ($2)"), + ("UnsignedUMinus", "Unary minus would turn an unsigned value to a signed " & + "value; cast either to the same size (which you " & + "shouldn't do if the int value might be larger than the " & + "highest signed value), or cast to the next size up if " & + "possible."), + ("128BitLimit", "$1 is not currently supported for 128-bit integers."), + ("U64Div", "Division producing a float isn't currently defined for " & + "unsigned 64-bit integers."), + ("TupleLhs", "When unpacking a tuple, all items on the left hand " & + "side of the assignment must be valid variables, and " & + "cannot currently be attributes."), + ("MemberTop", "Attribute member access (.) can only be applied " & + "to attributes, not to values."), + ("TupleConstIx", "When indexing a tuple, the index must evaluate to " & + "a constant integer."), + ("TupleIxBound", "Constant index is out of bounds for the tuple being " & + "indexed."), + ("ContainerType", "Cannot distinguish what kind of container type this " & + "is (could be a dict, list, tuple, etc.) " & + " Please explicitly declare this type."), + ("BadUrl", "Invalid URL for loading con4m source code."), + ("InsecureUrl", "Warning: loading file from an insecure URL. " & + "The contents could be injected by an attacker."), + ("NoImpl", "Could not find any function implementations in scope " & + "named $1. Full signature: $1$2"), + ("NotAFunc", "There is a variable named $1, but there was " & + "Not a function in scope with the signature: " & + "$1$2"), + ("BadSig", "No implementation of $1 matched this $3. " & + "The $3 had the type: $1$2"), + ("CallAmbig", "Found multiple functions matching $1$2" & + " Please disambiguate."), + ("NotIndexible", "Type $1 is not indexible."), + ("CantLiftFunc", "Function $1 has the same name as a " & + "global variable. Currently, other modules will have to " & + "explicitly qualify the module to call this function."), + ("DoesntExit", "Control does not reach the end of this $1."), + ("ExprResult", "Result of expression is unused. Expression result is " & + "of type $1. Please assign to _ to discard."), + ("InfLoop", "While loop does not exit."), + ("UseBeforeDef", "Likely use of $1 before assignment;" & + " If you think this is wrong, set a default value " & + "at the top of this scope."), + ("ConstReassign", "This variable has been declared constant, and was " & + "previously assigned."), + ("Immutable", "Cannot modify immutable values."), + ("DefWoUse", "Variable $1 is defined, but never used."), + ("UseWoDef", "Definite use of $1 without assignment."), + ("ConstNotSet", "Constant $1 was declared, but no value was " & + "set."), + ("$assign", "Variables starting with $ are set by the " & + "system, and cannot be otherwise assigned."), + ("SigOverlap", "In this module, for the function name $1, " & + "implementations have overlapping signatures:
" & + "2. $3
" & + "1. $2 (line $4)."), + ("NextCase", "Statement seemed to end, and was expecting " & + "another case branch, an else, " & + " or } to end the cases."), + ("CaseBodyStart", "Case bodies may either be regular blocks (i.e., " & + "{ ... }) or can be a colon followed by a list " & + "of statements."), + ("DeadTypeCase", "Variable can never be of type $1; case cannot " & + "be taken."), + ("BadIPv4", "Invalid IPv4 address."), + ("BadPort", "Invalid port number."), + ("BadDuration", "Invalid duration literal."), + ("BadSize", "Invalid size literal."), + ("BadUrl", "Invalid URL literal."), + ("BadDateTime", "Invalid literal value for datetime type."), + ("BadDate", "Invalid literal value for date type."), + ("BadTime", "Invalid literal value for time type."), + ("InvalidOther", "Invalid literal, did not match any known literal type."), + ("OtherLit", "Inferred type of literal is $1. If incorrect, " & + "place in quotes and provide an explicit literal " & + "modifier after (e.g., \"2 gb\"'size for " & + "a size literal.)"), + ("OtherQuotes", "The := operator is used for values of special " & + "types, where the system takes all input till the end " & + " of the line, then tries to treat it as a primitive " & + "type. The quotes here are treated like they're inside " & + "the data you're providing, which may not be what you " & + "want."), + ("OtherBrak", "The := operator does not process brackets " & + "or parentheses of any kind; this will be treated as " & + "a single string."), + ("OtherNum", "The := operator does not result in numeric " & + "types; it asks the system to guess from a number of " & + "specialized types. Use = or add a modifier " & + "that's appropriate for the type of value you're trying " & + "to create."), + ("AttrUse", "Attempted to use an attribute $1 that has not " & + "been set."), + ("RT_BadTOp", "Unknown function id for internal type API call " & + "($1)"), + ("NeedExName", "Local function specs must include names for every " & + "parameter."), + ("DupeExParam", "Duplicate parameter name for local function: " & + "$1"), + ("ExternZArgCt", "Local function spec has more arguments than external " & + "function for $1, which is currently not " & + "allowed. C function has $3 args, local has $2 ($4)."), + ("ExternCArgCt", "External function spec has more arguments than local " & + "function for $1, which is currently not " & + "allowed. C function has $3 args, local has $2."), + ("ArrayIxErr", "Array index $1 is not in bounds."), + ("DictKeyErr", "Could not find dictionary key: $1."), + ("LockedAttr", "Attempted to set attribute $1, which is " & + "locked. Current value: $2 Attempted value: " & + "$3"), + ("AlreadyLocked", "Tried to set lock-on-write for attribute $1, " & + "but it is already locked."), + ("DupeSection", "Section $1 cannot be specified twice"), + ("SpecFieldType", "Specification field $1 was expected to be " & + "of type $3, but was of type $2"), + ("RequiredProp", "Specified field $1 does not have the " & + "required property $2."), + ("SpecLock", "Cannot make changes to the attribute specification; " & + "it is locked."), + ("NoSecSpec", "When processing $1, there is no section " & + "specification for $2"), + ("MissingField", "Attribute $1 must be set, but was not " & + "provided."), + ("NoSecType", "In the top level of a confspec block, got " & + "$1 but only allowed contents are: " & + "root, singleton or named" & + " blocks."), + ("InstSecStart", "Was expecting this to start an instantiation " & + "of a $1 section named $2, but " & + "would need a { here."), + ("RootOverwrite", "Overwriting an existing root $1."), + ("MissingSec", "Spec tries to $2 a section of type $1" & + ", but no section named $1 has been spec'd yet."), + + ("BadRange", "When validating $1, an invalid value, " & + "$2, was chosen. But the value must be no less " & + "than $3 and no greater than $4."), + ("BadChoice", "When validating $1, an invalid value, " & + "$2, was chosen. Valid choices are: $3"), + ("MissingSection", "When validating $1, expected a required " & + "section named $2, which was not provided."), + ("MissingField", "When validating $1, expected a required " & + "field named $2, but it was not found."), + ("BadSection", "When validating $1, the section $2 " & + "is not allowed."), + ("NotTSpec", "When attempting to determine the type of the " & + "value $1$2, the field $3 was " & + "expected to specify the value's type, but " & + "that field did not contain a type specification."), + ("BadField", "When validating $1, found the field " & + "$2, which is not a valid field name for a " & + "$3 section."), + ("FieldMutex", "When validating $1, found the field " & + "$2, which is specified to not be able " & + "to appear together in a section with the field " & + "$3 (which was also present)."), + ("ExternVarargs", "External variable argument functions are not yet " & + " supported (external function $1)"), + ("StackOverflow", "Exceeded the maximum stack depth."), + ("NoSpecForSec", "Used a section $1, which is not defined " & + "by the attribute specification."), + ("InvalidStart", "Found the start of an attribute assignment that " & + "is invalid according to the spec, at: $1"), + ("NoInstance", "This section must have an instance, meaning " & + "you must specify named sections underneath it."), + ("DupeProp", "Property $1 cannot appear twice in one " & + "item spec"), + ("NotBool", "Property $1 is reuquired to be bool, " & + "but here is $2)"), + ("RangeAndChoice", "Cannot have both range and choice " & + "constraints for the same field."), + ("TyDiffListItem", "Inconsistent item type for choice. Previously " & + "it type was $1, but here it was $2"), + ("BadRangeSpec", "Start value for a range must be less than the end " & + "value."), + ("SpecWhenLocked", "Cannot add confspec fields; the " & + "specification has already been locked."), + ("DupeSpecField", "Duplicate specification for field $1."), + ("TCaseOverlap", "Type cases have overlapping types."), + ("DupeExclusion", "Exclusion duplicated for $1."), + ("DupeAllow", "Section $1 appears multiple times in " & + "allow list"), + ("AllowInReq", "Section $1 appears in both require" & + "and allow; suggest removing from allow"), + ("DupeRequire", "Section $1 appears multiple times in " & + "require list"), + ("DupeRootSpec", "Should not have duplicate root section" & + "in one module"), + ("NotConst", "Value must be a constant at compile time."), + ("ParamValParTy", "The validation function for this parameter takes an " & + "argument that is inconsistent with the type we have " & + "for the parameter. Previously, we had $1, " & + "But the function takes a $2."), + ("ParamValNArgs", "Validation functions for parameters must take a " & + "single argument, where a value to validate will be " & + "passed. It must return a string; the empty string " & + "indicates no validation error. Otherwise, the return " & + "value should be an error message."), + ("ParamValRetTy", "Validation functions for parameters must return " & + "a string, which represents any error message to " & + "give as feedback."), + ("NoCbMatch", "Could not find a function to match to the callback " & + "$1$2"), + ("ParamNotSet", "Module parameter $1 was not set when entering " & + "module $2."), + ("ParamNotValid", "Module parameter $1 was set when entering " & + "module $2, but was not valid: $3"), + ("DefaultMutex", "Module parameters cannot provide both a " & + "default value and an initialize. " & + "The initializer is intended for computing a default " & + "value when needed."), + ("InitArg", "Parameter initializer callbacks do not take take " & + "any arguments; they only return a value used to " & + "initialize the parameter."), + ("LitRequired", "Currently, $1 must be a $2 literal value, " & + "and cannot be computed or taken from a variable."), + ("CantInitialize", "Parameter cannot be initialized if it is private, " & + "because no default value has been set, and no " & + "initialize callback has been set. If this " & + "value is going to be initialized every time in " & + "the module, then it shouldn't be a parameter."), + ] diff --git a/src/err/output.nim b/src/err/output.nim new file mode 100644 index 0000000..1956448 --- /dev/null +++ b/src/err/output.nim @@ -0,0 +1,280 @@ +import "std"/terminal +import ".."/common +import "."/[messages, backtrace] + +proc lookupMsg(code: string): string = + for (k, v) in errorMsgs: + if k == code: + return v + + return "Unknown error code: " & code + +proc performSubs(extra: seq[string], s: var string) = + for i, item in extra: + s = s.replace("$" & `$`(i + 1), item) + +proc oneErrToRopeList(err: Con4mError, s: string): seq[Rope] = + case err.severity + of LlErr, LlFatal: + result.add(fgColor("error:", "red").td().overflow(OTruncate)) + of LlWarn: + result.add(fgColor("warn:", "yellow").td().overflow(OTruncate)) + of LLInfo: + result.add(fgColor("info:", "atomiclime").td().overflow(OTruncate)) + of LlNone: + unreachable + + if err.modname.len() != 0: + let modname = fgColor(err.modname, "jazzberry") + text(":") + result.add(modname.overflow(OTruncate)) + else: + result.add(text("")) + if err.line >= 1: + let offset = td(text(`$`(err.line) & ":" & `$`(err.offset + 1) & ":")) + result.add(offset.overflow(OTruncate)) + else: + result.add(text("")) + + result.add(s.htmlStringToRope(markdown = false, add_div = false)) + +proc getVerboseInfo(err: Con4mError): Rope = + var + noLoc = false + + if err.cursor == nil: + return nil + + elif err.offset < 0: + noLoc = true + + let + src = $(err.cursor.runes) + lines = src.split("\n") + + if lines.len() == 0 or err.line <= 0: + return nil + + result = text(lines[err.line - 1]) + newBreak() + + if not noLoc: + result += em(repeat((' '), err.offset) & "^") + newBreak() + + if err.detail != nil: + result = result + err.detail + +proc getLocWidth(errs: seq[Con4mError]): int = + for err in errs: + let r = 2 + `$`(err.line).len() + `$`(err.offset + 1).len() + + if r > result: + result = r + +proc getModuleWidth(errs: seq[Con4mError]): int = + for err in errs: + let l = err.modname.len() + if l != 0 and (l + 1) > result: + result = l + 1 + +proc dupeLocationCheck(err: Con4mError, locs: var seq[(int, int)]): bool = + for (l, c) in locs: + if err.line == l and err.offset == c: + return true + + locs.add((err.line, err.offset)) + +proc formatErrors*(errs: seq[Con4mError], verbose = true): Rope = + var + errList: seq[seq[Rope]] + locs: seq[(int, int)] + + let + mw = errs.getModuleWidth() + 1 + lw = errs.getLocWidth() + 1 + + for i, error in errs: + if error.dupeLocationCheck(locs): + continue + if i == 30: # TODO; make this a configurable limit. + break + var msg = error.code.lookupMsg() & " (" & error.code & ")" + error.extra.performSubs(msg) + errList.add(error.oneErrToRopeList(msg)) + + if not verbose: + let table = quickTable[Rope](errList, noHeaders = true, + borders = BorderNone) + var one: Rope + result = table.colWidths([(7, true), (mw, true), (lw, true), (0, false)]) + result = result.lpad(0, true).rpad(0, true) + result = result.bpad(0, true).tpad(0, true) + else: + for i, item in errlist: + var table = quickTable(@[item], noHeaders = true, borders = BorderNone) + table = table.colWidths([(7, true), (mw, true), (lw, true), (0, false)]) + table = table.lpad(0, true).rpad(0, true).bpad(0, true).tpad(0, true) + var one = table + container(errs[i].getVerboseInfo()) + result += one + +proc find_string_at(mem: string, offset: int): string {.importc, cdecl.} +proc toString(x: TypeId): string {.importc, cdecl.} + +proc location_from_instruction*(ctx: RuntimeState, + ins: ptr ZInstruction): (string, int) = + return (ctx.obj.moduleContents[ins.moduleId - 1].modname, + int(ins.lineno)) + +proc print_con4m_trace*(ctx: RuntimeState) {.exportc, cdecl.} = + print(ctx.get_stack_trace(), file = stderr) + +proc formatLateError(err: string, severity: Con4mSeverity, location: string, + args: seq[string], verbose = true): Rope = + # `location` should be an indication of the instruction if we are executing, + # attribute information if we're validating, and whatever is appropriate + # if it's some other error. + var + msg = lookupMsg(err) + row: seq[Rope] + + performSubs(args, msg) + + case severity + of LlErr, LlFatal: + row.add(fgColor("error: ", "red").td().lpad(0)) + of LlWarn: + row.add(fgColor("warn: ", "yellow").td().overflow(OTruncate)) + of LLInfo: + row.add(fgColor("info: ", "atomiclime").td().overflow(OTruncate)) + of LlNone: + unreachable + + row.add(italic(location & ": ")) + row.add(markdown(msg)) + row.add(italic("(" & err & ")")) + + var + width_used = 11 + location.len() + err.len() + remains = terminalWidth() - width_used + msg_width = row[2].runeLength() + 1 + msg_col: int + + if msg_width < remains: + msg_col = msg_width + else: + msg_col = remains + + result = @[row].quickTable(noheaders = true, borders = BorderNone) + result.colWidths([(7, true), (location.len() + 2, true), + (msg_col, true), (err.len() + 2, true)]) + result.lpad(0, true).rpad(0, true).bpad(0, true).tpad(0, true) + +proc assemble_validation_msg(ctx: RuntimeState, path: string, msg: Rope, + code: string, other: Rope = nil): Rope = + var + nim_path = path + last_touch: Rope + + if nim_path.len() != 0: + if nim_path[0] == '.': + nim_path = nim_path[1 .. ^1] + + if nim_path[^1] == '.': + nim_path = nim_path[0 ..< ^1] + + let attrOpt = ctx.attrs.lookup(nim_path) + if attrOpt.isSome(): + let record = attrOpt.get() + if record.lastset == nil: + last_touch = text("Attribute has not been set this execution.") + else: + let (module, line) = ctx.location_from_instruction(record.lastset) + + last_touch = text("Attribute last set at: ") + + em(module & ":" & $line) + + if nim_path.len() == 0: + nim_path = "root attribute section" + + result = text("Validation for ") + em(nim_path) + text(" failed: ") + result += italic(msg) + result += last_touch + + if other != nil: + result += text(" ") + other + + result += italic(" (" & code & ")") + GC_ref(result) + +proc formatValidationError*(ctx: RuntimeState, attr: string, err: string, + args: seq[string]): Rope = + var msg = err.lookupMsg() + performSubs(args, msg) + + let asRope = htmlStringToRope(msg, markdown = false, add_div = false) + + return ctx.assemble_validation_msg(attr, asRope, err) + +proc formatValidationError*(ctx: RuntimeState, attr: C4Str, err: string, + args: seq[string]): Rope = + return ctx.formatValidationError(attr.toNimStr(), err, args) + +proc customValidationError*(ctx: RuntimeState, path: C4Str, usrmsg: C4Str, + cb: ptr ZCallback): Rope = + let + asRope = htmlStringToRope(usrmsg.toNimStr(), markdown = false, + add_div = false) + cbName = find_string_at(ctx.obj.staticData, cb.nameOffset) + validator_info = text("Validation function: ") + + em(cbname & cb.tid.toString()) + + return ctx.assemble_validation_msg(path.toNimStr(), asRope, + "CustomValidator", validator_info) + +proc runtimeIssue(ctx: RuntimeState, err: string, args: seq[string], + severity = LLFatal) = + if severity < config_log_level: + return + + let + instr = addr ctx.curModule.instructions[ctx.ip] + (m, l) = ctx.location_from_instruction(instr) + extra = if l == -1: "" else: ":" & $(l) + loc = "When executing " & m & extra + + print(err.formatLateError(severity, loc, args), file = stderr) + +proc runtimeWarn*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = + if config_debug: + ctx.print_con4m_trace() + ctx.runtimeIssue(err, args, LlWarn) + +proc runtimeError*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = + ctx.print_con4m_trace() + ctx.runtimeIssue(err, args) + quit(-1) + +proc runtimeError*(ctx: RuntimeState, err: string, file: ZModuleInfo, line: int, + args: seq[string] = @[]) = + var extra: string = "" + if line != -1: + extra = ":" & $(line) + + let loc = "When executing " & file.modname & extra + + ctx.print_con4m_trace() + print(err.formatLateError(LlErr, loc, args), file = stderr) + quit(-1) + +proc codeGenError*(err: string, args: seq[string] = @[]) = + # TODO: the module / function info needs to show up here. + print(err.formatLateError(LlErr, "When generating code", args), + file = stderr) + quit(-1) + +proc objLoadWarn*(ctx: RuntimeState, err: string, args: seq[string] = @[]) = + if config_log_level > LlWarn: + return + + # TODO: the module / function info needs to show up here. + print(err.formatLateError(LlWarn, "When loading object file", args), + file = stderr) + quit(-1) diff --git a/src/err/signal.nim b/src/err/signal.nim new file mode 100644 index 0000000..4a448b3 --- /dev/null +++ b/src/err/signal.nim @@ -0,0 +1,63 @@ +import std/posix +import ".."/common +import "."/backtrace + +let sigNameMap = { 1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", + 6: "SIGABRT",7: "SIGBUS", 9: "SIGKILL", 11: "SIGSEGV", + 15: "SIGTERM" }.toDict() +var + LC_ALL {.importc, header: "".}: cint + savedTermState: Termcap + +proc restoreTerminal() {.noconv.} = + tcSetAttr(cint(1), TcsaConst.TCSAFLUSH, savedTermState) + +proc regularTerminationSignal(signal: cint) {.noconv.} = + let pid = getpid() + + print(h5("pid: " & $(pid) & " - Aborting due to signal: " & + sigNameMap[signal] & "(" & $(signal) & ")"), file = stderr) + + let rt = getCon4mRuntime() + + if rt != nil and rt.running: + print(getCon4mRuntime().get_stack_trace(), file = stderr) + else: + print(h2(text("Program was ") + em("NOT") + + text(" executing when we crashed."))) + + when defined(debug): + print(h4("Nim stack trace:")) + echo getStackTrace() + else: + print(h4(text("Nim stack trace is unavailable " & + "(must compile w/ ") + strong("-d:debug") + text(" for traces)"))) + var sigset: SigSet + + discard sigemptyset(sigset) + + for signal in [SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGKILL, + SIGSEGV, SIGTERM]: + discard sigaddset(sigset, signal) + discard sigprocmask(SIG_SETMASK, sigset, sigset) + + + exitnow(signal + 128) + +proc setlocale(category: cint, locale: cstring): cstring {.importc, cdecl, + nodecl, header: "", discardable .} + +proc setupTerminal*() = + setlocale(LC_ALL, cstring("")) + tcGetAttr(cint(1), savedTermState) + addQuitProc(restoreTerminal) + +proc setupSignalHandlers*() = + var handler: SigAction + + handler.sa_handler = regularTerminationSignal + handler.sa_flags = 0 + + for signal in [SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGKILL, + SIGSEGV, SIGTERM]: + discard sigaction(signal, handler, nil) diff --git a/src/irgen.nim b/src/irgen.nim index d49eb55..65196d6 100644 --- a/src/irgen.nim +++ b/src/irgen.nim @@ -661,7 +661,7 @@ proc convertConstStmt(ctx: Module) = proc convertParamBody(ctx: Module, sym: var SymbolInfo) = var - gotValid, gotDefault: bool + gotValid, gotDefault, gotInitialize, gotPrivate, isPrivate: bool paramInfo = ParamInfo(shortdoc: ctx.pt.extractShortDocPlain(), longdoc: ctx.pt.extractLongDocPlain()) @@ -672,6 +672,28 @@ proc convertParamBody(ctx: Module, sym: var SymbolInfo) = continue let propname = ctx.getText(i, 0) case propname + of "initialize": + if gotDefault: + ctx.irError("DefaultMutex", @[], ctx.pt.children[i]) + if gotInitialize: + ctx.irError("DupeParamProp", @["initialize"], ctx.pt.children[i]) + let + irNode = ctx.downNode(i, 1) + to = irNode.tid.idToTypeRef() + + if to.kind != C4Func: + ctx.irError("ParamType", @["initializor", "callback"], + ctx.pt.children[i]) + elif to.items.len() != 0: + if to.items.len() != 1: + ctx.irError("InitArg", @[], ctx.pt.children[i]) + else: + if unify(tFunc(@[sym.tid]), irNode.tid) == TBottom: + ctx.irError("InitArg", @[], ctx.pt.children[i]) + + paramInfo.initializeIr = some(irNode) + gotInitialize = true + of "validator": let irNode = ctx.downNode(i, 1) @@ -682,7 +704,7 @@ proc convertParamBody(ctx: Module, sym: var SymbolInfo) = ctx.pt.children[i]) elif to.items.len() != 0: if to.items.len() != 2: - ctx.irError("ParamValNArgs") + ctx.irError("ParamValNArgs", @[], ctx.pt.children[i]) else: if unify(sym.tid, to.items[0]) == TBottom: ctx.irError("ParamValParTy", @[sym.tid.toString(), @@ -694,7 +716,22 @@ proc convertParamBody(ctx: Module, sym: var SymbolInfo) = paramInfo.validatorIr = some(irNode) gotValid = true + + of "private": + if gotPrivate: + ctx.irError("DupeParamProp", @["private"], ctx.pt.children[i]) + else: + gotPrivate = true + let irNode = ctx.downNode(i, 1) + ctx.typeCheck(irNode.tid, TBool, where = irNode.parseNode) + if irNode.contents.kind != IrLit: + ctx.irError("LitRequired", + @["private field for parameters", "bool"], + ctx.pt.children[i]) + isPrivate = cast[bool](irNode.value) of "default": + if gotInitialize: + ctx.irError("DefaultMutex", @[], ctx.pt.children[i]) if gotDefault: ctx.irError("DupeParamProp", @["default"], ctx.pt.children[i]) continue @@ -706,8 +743,12 @@ proc convertParamBody(ctx: Module, sym: var SymbolInfo) = ctx.irError("BadParamProp", @[propname], ctx.pt.children[i]) continue - paramInfo.sym = sym - sym.pinfo = paramInfo + paramInfo.private = isPrivate + paramInfo.sym = sym + sym.pinfo = paramInfo + + if isPrivate and not gotDefault and not gotInitialize: + ctx.irError("CantInitialize") ctx.params.add(paramInfo) diff --git a/src/modparams.nim b/src/modparams.nim index 99ef782..64b7393 100644 --- a/src/modparams.nim +++ b/src/modparams.nim @@ -9,10 +9,10 @@ proc get_parameter_info*(ctx: RuntimeState): seq[ZParamExport] = data = ZParamExport(modid: int32(module.moduleId), modname: module.modname, paramid: int32(i), + private: param.private, shortdoc: param.shortdoc, longdoc: param.longdoc, tid: param.tid) - default: pointer if param.attr != "": data.name = param.attr @@ -43,6 +43,15 @@ proc get_parameter_info*(ctx: RuntimeState): seq[ZParamExport] = data.havedefault = true data.default = param.default + elif param.iFnIx != -1: + if param.iNative: + ctx.z_ffi_call(cast[int](param.iFnIx)) + data.default = ctx.returnRegister + data.tid = cast[TypeId](ctx.rrType) + else: + data.default = ctx.foreign_z_call(cast[int](param.iFnIx)) + data.tid = cast[TypeId](ctx.rrType) + proc get_current_instruction(ctx: RuntimeState): ptr ZInstruction {.importc, cdecl.} @@ -52,10 +61,10 @@ proc run_param_validator*(ctx: RuntimeState, p: ZParamInfo, if p.tid.tCopy().unify(t) == TBottom: return "Specified type for parameter was not compatable with the " & "stored type (" & t.toString() & ")" - if p.funcIx != -1: - var cb = ZCallback(impl: cast[pointer](int64(p.funcIx))) + if p.vFnIx != -1: + var cb = ZCallback(impl: cast[pointer](int64(p.vFnIx))) - if not p.native: + if not p.vNative: cb.ffi = true let s = ctx.run_callback_internal(addr cb, [(val, p.tid)]) diff --git a/src/parse.nim b/src/parse.nim index fa375c3..fa10d95 100644 --- a/src/parse.nim +++ b/src/parse.nim @@ -2301,7 +2301,10 @@ proc buildType*(n: ParseNode, tvars: Dict[string, TypeId]): TypeId = va = true items.add(kid.children[0].buildType(tvars)) elif kid.kind == NodeReturnType: - items.add(kid.children[0].buildType(tvars)) + if kid.children.len() == 0: + items.add(tVar()) + else: + items.add(kid.children[0].buildType(tvars)) else: items.add(kid.buildType(tvars)) return newFuncType(items, va).typeId diff --git a/src/rtmarshal.nim b/src/rtmarshal.nim index abb5000..3f0bb10 100644 --- a/src/rtmarshal.nim +++ b/src/rtmarshal.nim @@ -135,9 +135,12 @@ proc marshal_mod_params(rt: RuntimeState, params: seq[ZParamInfo]): C4Str = toAdd.add(item.shortdoc.marshal_nim_string()) toAdd.add(item.longdoc.marshal_nim_string()) toAdd.add(item.offset.int32().marshal_32_bit_value()) - toAdd.add(item.native.marshal_bool()) + toAdd.add(item.private.marshal_bool()) toAdd.add(cast[pointer](item.tid.followForwards()).marshal_64_bit_value()) - toAdd.add(item.funcIx.marshal_32_bit_value()) + toAdd.add(item.vFnIx.marshal_32_bit_value()) + toAdd.add(item.iFnIx.marshal_32_bit_value()) + toAdd.add(item.vNative.marshal_bool()) + toAdd.add(item.iNative.marshal_bool()) toAdd.add(item.haveDefault.marshal_bool()) if item.haveDefault: toAdd.add(marshal(item.default, item.tid, rt.memos)) @@ -150,9 +153,12 @@ proc unmarshal_mod_params(rt: RuntimeState, p: var cstring): seq[ZParamInfo] = param.shortdoc = p.unmarshal_nim_string() param.longdoc = p.unmarshal_nim_string() param.offset = int(p.unmarshal_32_bit_value()) - param.native = p.unmarshal_bool() + param.private = p.unmarshal_bool() param.tid = cast[TypeId](p.unmarshal_64_bit_value()) - param.funcIx = p.unmarshal_32_bit_value() + param.vFnIx = p.unmarshal_32_bit_value() + param.iFnIx = p.unmarshal_32_bit_value() + param.vNative = p.unmarshal_bool() + param.iNative = p.unmarshal_bool() param.haveDefault = p.unmarshal_bool() if param.haveDefault: param.default = p.unmarshal(param.tid, rt.memos) diff --git a/src/vm.nim b/src/vm.nim index 01d4cd9..0949a44 100644 --- a/src/vm.nim +++ b/src/vm.nim @@ -8,6 +8,22 @@ proc using_spec(ctx: RuntimeState): bool {.importc, cdecl.} const sz = sizeof(ZInstruction) + +proc bailHere*(ctx: RuntimeState, errCode: string, extra: seq[string] = @[]) = + var + errList: seq[Con4mError] + runes = ctx.curModule.source.toRunes() + + errList.baseError(code = errCode, + cursor = StringCursor(runes: runes), + modname = ctx.getModName(), + line = ctx.getLineNo(), lineOffset = -1, + phase = ErrRuntime, severity = LlFatal, + extraContents = extra) + print errlist.formatErrors() + ctx.print_con4m_trace() + quit(-1) + when USE_TRACE: import codegen # For instr.toString() var trace_on = true @@ -99,85 +115,10 @@ else: template traceExecution(ctx: RuntimeState, instr: ZInstruction) = discard -template getModName(ctx: RuntimeState): string = - ctx.curModule.modName & ".c4m" - -template getLineNo(ctx: RuntimeState): int = - ctx.curModule.instructions[ctx.ip].lineNo - -proc getSourceLoc*(ctx: RuntimeState): string = - ## Decode the source location of the current runtime state from - ## the current instruction. - let line = ctx.getLineNo() - if line != -1: - return ctx.getModName() & " (line #" & $(ctx.getLineNo()) & ")" - else: - return "" - proc get_current_instruction*(ctx: RuntimeState): ptr ZInstruction {.exportc, cdecl.} = return addr ctx.curModule.instructions[ctx.ip] -proc get_con4m_stack_trace*(ctx: RuntimeState): Rope {.exportc, cdecl.} = - var cells: seq[seq[string]] = @[@["Caller module", "Line #", - "Call target"]] - - for i in 1 ..< ctx.numFrames: - var - frame = ctx.frameInfo[i] - row: seq[string] - - row.add(frame.callModule.modname) - if i == ctx.numFrames - 1: - row.add($(ctx.getLineNo())) - else: - row.add($(frame.calllineno)) - - if frame.targetfunc == nil: - row.add(frame.targetmodule.modname & ".__mod_run__") - else: - row.add(frame.targetmodule.modname & "." & frame.targetfunc.funcname) - - cells.add(row) - - let loc = ctx.getSourceLoc() - if loc != "": - result = cells.quicktable(title = "Stack trace", - caption = "Source location: " & - ctx.getSourceLoc()) - else: - result = cells.quicktable(title = "Stack trace") - - result.colWidths([(17, true), (15, true), (20, true)]) - result.tpad(0, true).bpad(0, true) - -proc printStackTrace*(ctx: RuntimeState) = - print ctx.get_con4m_stack_trace() - -proc bailHere(ctx: RuntimeState, errCode: string, extra: seq[string] = @[]) = - var - errList: seq[Con4mError] - runes = ctx.curModule.source.toRunes() - - errList.baseError(code = errCode, - cursor = StringCursor(runes: runes), - modname = ctx.getModName(), - line = ctx.getLineNo(), lineOffset = -1, - phase = ErrRuntime, severity = LlFatal, - extraContents = extra) - print errlist.formatErrors() - ctx.printStackTrace() - quit(-1) - -proc storageAddr(ctx: RuntimeState, x: ZInstruction, - p: int64): ptr pointer = - if x.moduleId == -1: - result = addr ctx.stack[ctx.fp - (p * 2)] - elif x.moduleId == -2: - # Static data, no type info assumed. - result = cast[ptr pointer](addr ctx.obj.staticData[p]) - else: - result = addr ctx.moduleAllocations[x.moduleId][p * 2] template storageAddr(ctx: RuntimeState, x: ZInstruction): ptr pointer = ctx.storageAddr(x, int64(x.arg)) @@ -238,7 +179,7 @@ proc runMainExecutionLoop(ctx: RuntimeState): int = ctx.stack[ctx.sp] = cast[pointer](instr.immediate) of ZPushRes: ctx.sp -= 1 - ctx.stack[ctx.sp] = cast[pointer](instr.typeInfo) + ctx.stack[ctx.sp] = cast[pointer](ctx.rrType) ctx.sp -= 1 ctx.stack[ctx.sp] = ctx.returnRegister of ZSetRes: @@ -769,7 +710,7 @@ proc runMainExecutionLoop(ctx: RuntimeState): int = of ZModuleRet: if ctx.numFrames <= 2: - return + return 0 discard ctx.module_lock_stack.pop() leaveFrame() of ZFFICall: @@ -829,6 +770,7 @@ proc runMainExecutionLoop(ctx: RuntimeState): int = ctx.tupleStash = cast[Con4mTuple](ctx.stack[ctx.sp]) ctx.stashType = ctx.stack[ctx.sp + 1] ctx.sp += 2 + of ZUnpack: let n = instr.arg @@ -852,6 +794,11 @@ proc runMainExecutionLoop(ctx: RuntimeState): int = address[] = valaddr typeaddr[] = cast[pointer](itemType) + of ZBail: + let s = cast[cstring](ctx.stack[ctx.sp]) + print(fgColor("error: ", "red") + text($s)) + return -1 + ctx.ip += 1 proc setupArena(ctx: RuntimeState, diff --git a/tests/modparam.c4m b/tests/modparam.c4m index ec2cd4c..4dc1adb 100644 --- a/tests/modparam.c4m +++ b/tests/modparam.c4m @@ -1,3 +1,7 @@ +func value_provider() { + return 101 +} + func example_checker(x) { result = "" @@ -16,7 +20,7 @@ parameter var example1 { parameter var example2 { "This should be some documentation." "Also this." - default: 101 + initialize: func value_provider() validator: func example_checker(int) -> string }