diff --git a/src/attrstore.nim b/src/attrstore.nim
index 89a02f3..01ebc72 100644
--- a/src/attrstore.nim
+++ b/src/attrstore.nim
@@ -160,11 +160,14 @@ proc set*(ctx: RuntimeState, key: string, value: pointer, tid: TypeId,
if curOpt.isSome():
curInfo = curOpt.get()
+ newInfo.moduleLock = curInfo.moduleLock
if newInfo.override and not override:
return true # Pretend it was successful.
- if curInfo.locked:
+ if curInfo.locked or
+ (curInfo.moduleLock != 0 and
+ curInfo.moduleLock != ctx.curModule.moduleId):
if not override:
if not call_eq(value, curInfo.contents, curInfo.tid):
return false
@@ -179,6 +182,8 @@ proc set*(ctx: RuntimeState, key: string, value: pointer, tid: TypeId,
if ctx.running:
newInfo.lastset = addr ctx.curModule.instructions[ctx.ip]
+ if ctx.module_lock_stack.len() != 0:
+ newInfo.moduleLock = ctx.module_lock_stack[^1]
# Don't trigger write lock if we're setting a default (i.e., internal is set).
if (lock or wlock) and not internal:
diff --git a/src/codegen.nim b/src/codegen.nim
index a6828d6..97750b6 100644
--- a/src/codegen.nim
+++ b/src/codegen.nim
@@ -18,7 +18,7 @@
# defer typing things at the top-level; this will be a subtlety we
# deal with when we get to doing the REPL.
-import "."/irgen
+import "."/[irgen, compile]
import ztypes/api
proc findAndLoadModule(ctx: CompileCtx, location, fname, ext: string):
@@ -174,6 +174,8 @@ proc rawReprInstructions*(module: ZModuleInfo, ctx: ZObjectFile, fn = 0): Rope =
of ZTCall:
arg1 = text(hex(item.arg))
lbl = em(tCallReverseMap[item.arg])
+ of ZCallModule:
+ arg1 = text($(item.arg))
of Z0Call:
# Look up the symbol name by moduleId. It's always going to be a module,
# so it's really about indexing into the right module in the
@@ -308,10 +310,11 @@ proc rawReprInstructions*(module: ZModuleInfo, ctx: ZObjectFile, fn = 0): Rope =
proc disassembly*(ctx: ZObjectFile, skip_nops = true): Rope =
for item in ctx.moduleContents:
- if item.instructions.len() == 3 and skip_nops:
+ if item.instructions.len() == 4 and skip_nops:
if item.instructions[0].op == ZNop and
- item.instructions[1].op == ZModuleRet and
- item.instructions[2].op == ZNop:
+ item.instructions[1].op == ZModuleEnter and
+ item.instructions[2].op == ZModuleRet and
+ item.instructions[3].op == ZNop:
continue
result = result + rawReprInstructions(item, ctx)
@@ -886,6 +889,7 @@ proc genExternCall(ctx: CodeGenState, cur: IrContents) =
if tid != TVoid:
ctx.emitInstruction(ZPushRes, tid = tid)
+
proc genRunCallback(ctx: CodeGenState, cur: IrContents) =
var
i = cur.actuals.len()
@@ -1197,9 +1201,6 @@ proc oneIrNode(ctx: CodeGenState, n: IrNode) =
ctx.curNode = saved
-proc addParameter(ctx: CodeGenState, symbol: SymbolInfo) =
- discard
-
proc stashSymbolInfo(scope: Scope, d: var Dict[int, string],
t: var seq[(int, TypeId)]) =
for (name, sym) in scope.table.items(sort=true):
@@ -1233,6 +1234,74 @@ proc genFunction(ctx: CodeGenState, fn: FuncInfo) =
ctx.mcur.objInfo.codesyms[zinfo.offset] = fullname
+proc stashParam(ctx: CodeGenState, m: Module, info: ParamInfo) =
+
+ var zparam = ZParamInfo(shortdoc: info.shortdoc,
+ longdoc: info.longdoc)
+
+ 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
+ zparam.haveDefault = true
+ zparam.default = info.defaultIr.get().value
+
+ zparam.tid = info.sym.tid
+
+ if info.validatorIr.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))
+
+ 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
+
+ offset += 1
+ m.objinfo.instructions[offset].typeInfo = cb.tid
+
+ else:
+ zparam.funcIx = -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 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)
+
proc genModule(ctx: CodeGenState, m: Module) =
let curMod = ctx.minfo[m.key]
if curMod.processed:
@@ -1241,10 +1310,10 @@ proc genModule(ctx: CodeGenState, m: Module) =
ctx.mcur = curMod
ctx.genLabel("Module '" & m.modname & "' :")
+ ctx.emitInstruction(ZModuleEnter, arg = m.params.len())
- for (_, sym) in m.moduleScope.table.items(sort=true):
- if sym.pInfo != nil:
- ctx.addParameter(sym)
+ if m.params.len() != 0:
+ ctx.genParamChecks(m)
ctx.oneIrNode(m.ir)
ctx.emitInstruction(ZModuleRet)
@@ -1258,6 +1327,9 @@ proc genModule(ctx: CodeGenState, m: Module) =
continue
ctx.genFunction(fn)
+ for param in m.params:
+ ctx.stashParam(m, param)
+
curMod.processed = true
let deps = ctx.minfo.items(sort = true)
@@ -1289,7 +1361,7 @@ proc applyArenaId(ctx: CodeGenState, scope: Scope, moduleId: int,
proc applyArenasToGloballyVisibleFuncs(ctx: CodeGenState, scope: Scope,
curFnId: var int) =
- for (name, sym) in scope.table.items(sort=true):
+ for (name, sym) in scope.table.items(sort = true):
for fn in sym.fimpls:
if fn.externInfo != nil:
continue
@@ -1299,7 +1371,7 @@ proc applyArenasToGloballyVisibleFuncs(ctx: CodeGenState, scope: Scope,
mid = fn.defModule.objInfo.moduleId
fi = ZFnInfo(funcname: name, mid: int32(mid), tid: fn.tid,
offset: int32(fn.codeOffset))
- ctx.zobj.funcInfo.adD(fi)
+ ctx.zobj.funcInfo.add(fi)
fi.syms.initDict()
fn.fnScope.stashSymbolInfo(fi.syms, fi.symTypes)
curFnId += 1
@@ -1401,6 +1473,27 @@ proc extractSectionDocs(ctx: CodeGenState,
let docObj = AttrDocs(shortdoc: node.shortdoc, longdoc: node.longdoc)
d[sec] = docObj
+template setupOneModule() =
+ var mi = ZModuleInfo(moduleId: curArena, modname: module.modname,
+ key: module.key, ext: module.ext, url: module.url,
+ moduleVarSize: module.moduleScope.scopeSize)
+ module.objInfo = mi
+ ctx.applyArenaId(module.moduleScope, curArena, curFnId)
+ curArena = curArena + 1
+ ctx.minfo[module.key] = module
+ ctx.zobj.moduleContents.add(mi)
+ mi.codesyms.initDict()
+ mi.datasyms.initDict()
+
+ # TODO: I have disabled eliding source code until incremental
+ # compilation is properly done.
+
+ #if not module.system:
+ mi.source = $(module.s.runes)
+
+ module.moduleScope.stashSymbolInfo(mi.datasyms, mi.symTypes)
+ mi.codesyms[0] = module.modname & ".__mod_run__()"
+
proc setupModules(ctx: CodeGenState, d: Dict[string, AttrDocs]) =
# This call applies a unique number to each module
# and then applies a unique number to each function.
@@ -1417,32 +1510,29 @@ proc setupModules(ctx: CodeGenState, d: Dict[string, AttrDocs]) =
ctx.cc.globalScope.stashSymbolInfo(ctx.zobj.globals, ctx.zobj.symTypes)
ctx.zobj.globalScopeSz = ctx.cc.globalScope.scopeSize
- for (_, module) in ctx.cc.modules.items(sort=true):
+ for (_, module) in ctx.cc.modules.items(sort = true):
ctx.addFfiInfo(module)
ctx.extractSectionDocs(module, d)
- for (_, module) in ctx.cc.modules.items(sort=true):
- var mi = ZModuleInfo(moduleId: curArena, modname: module.modname,
- moduleVarSize: module.moduleScope.scopeSize)
- module.objInfo = mi
- ctx.applyArenaId(module.moduleScope, curArena, curFnId)
- curArena = curArena + 1
- ctx.minfo[module.key] = module
- ctx.zobj.moduleContents.add(mi)
- mi.codesyms.initDict()
- mi.datasyms.initDict()
-
- if not module.system:
- mi.source = $(module.s.runes)
-
- module.moduleScope.stashSymbolInfo(mi.datasyms, mi.symTypes)
- mi.codesyms[0] = module.modname & ".__mod_run__()"
-
+ for (_, module) in ctx.cc.modules.items(sort = true):
+ setupOneModule()
# Finally, apply module IDs to the functions that are globally visible
# so we can look up their addresses.
ctx.applyArenasToGloballyVisibleFuncs(ctx.cc.globalScope, curFnId)
+proc setupNewModules(ctx: CodeGenState, d: Dict[string, AttrDocs]) =
+ # The incremental version.
+ var
+ curArena = ctx.zobj.moduleContents[^1].moduleId + 1
+ curFnId = ctx.zobj.funcInfo.len() + 1
+
+ for (_, module) in ctx.cc.modules.items(sort = true):
+ if module.objInfo != nil:
+ continue
+
+ setupOneModule()
+
proc fillCallBackpatches(ctx: CodeGenState) =
for (fn, dstmodule, patchloc) in ctx.callBackpatches:
var cur = dstmodule.objInfo.instructions[patchloc]
@@ -1455,8 +1545,7 @@ proc cacheAllTypeInfo(ctx: CodeGenState) =
# output, we go ahead and keep any type that does not forward. This
# is far less error prone than trying to make sure we always
# remember to save off type info, but it might end up causing too
- # much bloat. I doubt it, though! However, we definitely should
- # collect metrics on this.
+ # much bloat. I doubt it, though!
for (id, tObj) in typeStore.items(sort=true):
if tObj.typeId != id:
@@ -1468,12 +1557,7 @@ proc cacheAllTypeInfo(ctx: CodeGenState) =
discard ctx.zobj.tInfo.add(id, loc)
- var typeCache: seq[string]
- for (k, v) in ctx.zobj.tInfo.items(sort=true):
- typeCache.add($(cast[int](k)))
- typeCache.add($(cast[cstring](addr ctx.zobj.staticdata[v])))
-
-proc generateCode*(cc: CompileCtx, nextId = -1): RuntimeState =
+proc generateInitialCodeObject*(cc: CompileCtx, nextId = -1): RuntimeState =
var ctx = CodeGenState()
result = RuntimeState()
@@ -1497,5 +1581,99 @@ proc generateCode*(cc: CompileCtx, nextId = -1): RuntimeState =
result.obj.nextEntrypoint = int32(nextId)
result.obj.spec = ctx.cc.attrSpec
+when false:
+ proc setup_incremental_compile*(ctx: RuntimeState): CompileCtx =
+ result = ctx.newCompileContext(ctx.obj.spec)
+ result.incremental = true
+
+ for (attr, contents) in ctx.attrs:
+ var sym = SymbolInfo(name: attr, tid: contents.tid,
+ isAttr: true, inObj: true)
+ result.usedAttrs.table[attr] = sym
+
+ for (offset, name) in ctx.obj.globals.items():
+ var sym = SymbolInfo(name: name)
+
+ sym.tid = x
+ sym.constValue = x
+ sym.declaredType = x
+ sym.immutable = x
+ sym.global = true
+ sym.inObj = true
+ sym.offset = offset
+ sym.moduleId = 0
+
+ result.globalScope.table[name] = sym
+
+ for zmod in ctx.obj.moduleContents:
+ var module = Module(loaded: true, system: true, modname: zmod.modname,
+ where: zmod.location, ext: zmod.ext, key: zmod.key,
+ exitNode: CfgNode(), processed: true,
+ objinfo: zmod)
+ result.modules[module.key] = module
+
+ # global functions
+ # ffi functions
+ # module stubs
+else:
+ proc setup_incremental_compile*(ctx: RuntimeState): CompileCtx =
+ # Assume this all is going to just work...
+ result = CompileCtx()
+
+ result.globalScope = initScope()
+ result.usedAttrs = initScope()
+ result.modules.initDict()
+
+ let entrypointix = ctx.obj.entrypoint
+ var entry: Module
+
+ var modlist: seq[Module]
+
+ for zmod in ctx.obj.moduleContents:
+ let m = newModuleObj(result, zmod.source, zmod.modname, zmod.location,
+ zmod.ext, zmod.url, zmod.key)
+ if zmod.moduleId == entrypointix:
+ entry = m
+
+ m.objInfo = zmod
+ result.modules[zmod.key] = m
+
+ modlist.add(m)
+
+ for m in modlist:
+ m.system = true
+ result.loadModule(m)
+
+ discard result.build_program(entry)
+
+ for m in modlist:
+ m.processed = true
+
+proc generate_incremental_code*(rt: RuntimeState, cc: CompileCtx,
+ newEntry: Module) =
+ # TODO -- Rebuild the string cache?
+ let start = rt.obj.moduleContents.len()
+ var ctx = CodeGenState()
+ ctx.cc = cc
+ ctx.zobj = rt.obj
+ ctx.memos = Memos()
+ rt.obj.spec = cc.attrSpec
+ ctx.minfo = cc.modules
+
+ ctx.strCache.initDict()
+ ctx.memos.map.initDict()
+ ctx.cacheAllTypeInfo()
+ ctx.setupNewModules(rt.sectionDocs)
+ ctx.genModule(newEntry)
+ ctx.fillCallBackpatches()
+
+ rt.obj.entrypoint = int32(newEntry.objInfo.moduleId)
+
+ for i, item in rt.obj.moduleContents:
+ if i < start:
+ continue
+ var moduleAllocation = newSeq[pointer](item.moduleVarSize * 2)
+ rt.moduleAllocations.add(moduleAllocation)
+
# TODO -- pushTypeOf needs to handle attributes.
# TODO -- varargs for func entry.
diff --git a/src/commands/objdump.nim b/src/commands/objdump.nim
index 7987b2a..125c8f0 100644
--- a/src/commands/objdump.nim
+++ b/src/commands/objdump.nim
@@ -19,6 +19,33 @@ proc get_basic_object_info*(obj: ZObjectFile): Rope =
result = cells.quickTable(title = "Basic object info", verticalHeaders = true,
caption = config_args[0].resolvePath())
+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"]]
+ for item in params:
+ var row: seq[string] = @[]
+ if item.attr != "":
+ echo "Attr = ", item.attr
+ row.add(item.attr)
+ else:
+ echo "offset = ", item.offset
+ row.add($item.offset)
+ row.add(item.tid.toString())
+ if item.native:
+ row.add("✓")
+ else:
+ row.add("✗")
+ row.add($item.funcIx)
+ row.add(item.shortdoc & " ")
+ row.add(item.longdoc & " ")
+ cells.add(row)
+
+ return cells.quickTable(title = "Module param info",
+ caption = "Sorry, haven't had the dump look up " &
+ "local names or functions yet")
+
proc get_per_module_info*(obj: ZObjectFile): Rope =
var cells: seq[seq[string]]
@@ -76,6 +103,11 @@ proc get_per_module_info*(obj: ZObjectFile): Rope =
result += h2(text("Source code for ") + em(item.modname))
result += item.source.pretty()
+ if item.parameters.len() == 0:
+ result += h2("No stored parameters.")
+ else:
+ result += obj.format_module_params(item.parameters)
+
result += h2(text("Disassembly for ") + em(item.modname))
result += item.rawReprInstructions(obj)
diff --git a/src/commands/run.nim b/src/commands/run.nim
index 742b285..c630f2b 100644
--- a/src/commands/run.nim
+++ b/src/commands/run.nim
@@ -110,9 +110,8 @@ proc cmd_resume*() =
rt.run_object(fname, resumed = true)
-proc base_compile*(ctx: CompileCtx): RuntimeState =
+proc base_compile(ctx: CompileCtx, entryname: string): bool =
let
- entryname = config_args[0]
can_proceed = ctx.buildFromEntryPoint(entryname)
entry = ctx.entrypoint
@@ -130,19 +129,19 @@ proc base_compile*(ctx: CompileCtx): RuntimeState =
if config_debug:
if entry.tokens.len() == 0:
- return nil
+ return false
print(h1("Tokens for module '" & entry.modname & "'"))
entry.printTokens()
if entry.root == nil:
- return nil
+ return false
print(h1("Parse tree for module '" & entry.modname & "'"))
entry.printParseTree()
if entry.ir == nil:
- return nil
+ return false
print(h1("IR for module '" & entry.modname & "'"))
entry.printIr()
@@ -174,13 +173,47 @@ proc base_compile*(ctx: CompileCtx): RuntimeState =
print(h1("Global CFG"))
ctx.printProgramCfg()
- if not ctx.printErrors():
+ return can_proceed
+
+proc base_initial_compile*(ctx: CompileCtx): RuntimeState =
+ if not ctx.base_compile(config_args[0]) or not ctx.printErrors():
return nil
- return ctx.generateCode()
+ return ctx.generateInitialCodeObject()
+
+proc cmd_add*() =
+ if config_args[1].endswith(obj_file_extension):
+ print(fgcolor("error: ", "red") +
+ text("Cannot compile object file ") + em(config_args[1]) +
+ text(". Please specify a module entry point."))
+ quit(-4)
+
+ config_save_object = true
+
+ var
+ obj = config_args[0]
+ module = config_args[1]
+ rt = obj.load_object_file()
+ cc = rt.setup_incremental_compile()
+
+ if cc.base_compile(module) and cc.printErrors():
+ rt.generate_incremental_code(cc, cc.entryPoint)
+
+ if config_debug:
+ print rt.obj.disassembly()
+
+ rt.save_object_to_disk(cc.entrypoint.modname)
proc cmd_compile*(ctx: CompileCtx) =
- var rt = ctx.base_compile()
+ if config_args[0].endswith(obj_file_extension):
+ print(fgcolor("error: ", "red") +
+ text("Cannot compile object file ") + em(config_args[0]) +
+ text(". Please specify a module entry point."))
+ quit(-4)
+
+ config_save_object = true
+
+ var rt = ctx.base_initial_compile()
if rt == nil or rt.obj == nil:
quit(-1)
@@ -197,7 +230,7 @@ proc cmd_compile*(ctx: CompileCtx) =
proc cmd_run*(ctx: CompileCtx) =
var
- rt = ctx.base_compile()
+ rt = ctx.base_initial_compile()
if rt == nil or rt.obj == nil:
quit(-1)
diff --git a/src/commands/spec.c4m b/src/commands/spec.c4m
index 31a348a..20ab39c 100644
--- a/src/commands/spec.c4m
+++ b/src/commands/spec.c4m
@@ -115,6 +115,29 @@ Future runs can save a new entry point via the `--entry` flag.
args: (1, 1)
}
+ command add {
+ " Add a new module into an object."
+"""
+## Incrementally compiles a new module, updating the object file.
+
+This command will compile the new module and any dependencies
+necessary that aren't already in the object file, updating the object
+file, and setting the new module as the entry point to run.
+
+#### Parameters
+
+1. The object file
+2. The new entry point module to add.
+
+#### Output
+
+The new object does *not* overwrite the original object file. Instead,
+a new object file (.0c00l extension) will get added, using the name
+of your new entrypoint.
+"""
+ args: (2, 2)
+ }
+
command check {
"Parse and validate con4m code"
diff --git a/src/common.nim b/src/common.nim
index 867a2c2..f0da563 100644
--- a/src/common.nim
+++ b/src/common.nim
@@ -398,11 +398,43 @@ type
ParamInfo* = ref object
## Module parameters.
- shortdoc*: Option[string]
- doc*: Option[string]
- validator*: Option[Callback]
+ sym*: SymbolInfo
+ shortdoc*: string
+ longdoc*: string
+ validatorIr*: Option[IrNode]
defaultParse*: Option[ParseNode]
defaultIr*: Option[IrNode]
+ backpatchLoc*: 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
+
+ ZParamExport* = ref object
+ ## What we pass when asked what parameters need to be provided.
+ ## Currently, con4m is NOT storing these parameters itself between
+ ## runs, but if the modules holding the parameter gets called,
+ ## there will be a value set, and we'll report that it's got
+ ## a default value.
+ name*: string
+ modname*: string
+ shortdoc*: string
+ longdoc*: string
+ modid*: int32
+ paramid*: int32
+ tid*: TypeId
+ havedefault*: bool
+ default*: pointer
SymbolInfo* = ref object
name*: string
@@ -411,7 +443,6 @@ type
uses*: seq[IrNode]
defs*: seq[IrNode]
fimpls*: seq[FuncInfo]
- pInfo*: ParamInfo
defaultVal*: pointer
constValue*: pointer
isFunc*: bool
@@ -425,6 +456,9 @@ type
err*: bool
formal*: bool
declNode*: ParseNode
+ pinfo*: ParamInfo
+ inObj*: bool
+
# heapalloc is only true for global variables and
# indicates that the offset generated won't be relative to the
# stack, but from some start offset associated with the module
@@ -591,9 +625,9 @@ type
# available (stashed in attrSpec).
# First two fields are refs to the one in the compile context.
- globalScope*: Scope
- attrSpec*: ValidationSpec
- declaredSpec*: ValidationSpec
+ globalScope*: Scope
+ attrSpec*: ValidationSpec
+ declaredSpec*: ValidationSpec
moduleScope*: Scope
funcScope*: Scope
usedAttrs*: Scope
@@ -628,6 +662,7 @@ type
attrContext*: bool # True when we are def processing an attr.
ambigAssign*: bool
secDefContext*: bool
+ inParamCtx*: bool
curSym*: SymbolInfo
usedModules*: seq[(string, string)]
funcsToResolve*: seq[(IrNode, TypeId, string)]
@@ -636,20 +671,11 @@ type
curSecSpec*: SectionSpec
didFoldingPass*: bool # Might not be using this anymore.
maxOffset*: int
+ params*: seq[ParamInfo]
# These help us to fold.
branchOrLoopDepth*: int
- # We currently lift out any attrs set from a module if they are
- # not nested in any control flow or function, if the values are
- # constant, and there are no uses earlier in the file, or in a
- # function.
- #
- # This is particularly useful for being able to statically apply
- # specs.
-
- staticAttrs*: Dict[string, AttrContents]
-
# Used in code generation.
processed*: bool
loopLocs*: seq[(IrNode, int)]
@@ -658,17 +684,19 @@ type
secDocNodes*: seq[IRNode]
CompileCtx* = ref object
- modules*: Dict[string, Module]
- defaultExt*: string = ".c4m"
- attrSpec*: ValidationSpec
- globalScope*: Scope
- usedAttrs*: Scope
- entrypoint*: Module
- fatal*: bool
- topExitNode*: CfgNode # Used when building CFG
- modulePath*: seq[string] = @[".", "https://chalkdust.io/"]
- sysdir*: string
- errors*: seq[Con4mError]
+ incremental*: bool
+ modules*: Dict[string, Module]
+ defaultExt*: string = ".c4m"
+ attrSpec*: ValidationSpec
+ globalScope*: Scope
+ usedAttrs*: Scope
+ entrypoint*: Module
+ fatal*: bool
+ topExitNode*: CfgNode # Used when building CFG
+ modulePath*: seq[string] = @[".", "https://chalkdust.io/"]
+ sysdir*: string
+ errors*: seq[Con4mError]
+
# The remaining data structures are used in code generation and / or
# runtime. Any type here with a Z in front is intended to get
@@ -836,6 +864,15 @@ type
ZModuleRet = 0x81,
ZHalt = 0x82,
+ # Generated on module entry. If the module takes parameters,
+ # the argument is non-zero, and
+ # are checked and validated on entry to the module.
+ ZModuleEnter = 0x83,
+
+ # Checks a single parameter once its validator has run, popping
+ # the string result off the stack, and erroring if it's not null.
+ ZParamCheck = 0x84,
+
# SetRet should be called by any function before exiting, where
# that function is non-void. It moves the value at fp - 2
# into the return register.
@@ -971,6 +1008,9 @@ type
ZModuleInfo* = ref object
modname*: string
location*: string
+ key*: string
+ ext*: string
+ url*: string
version*: string
symTypes*: seq[(int, TypeId)]
codesyms*: Dict[int, string]
@@ -979,6 +1019,7 @@ type
moduleId*: int
moduleVarSize*: int
initSize*: int # size of init code before functions begin.
+ parameters*: seq[ZParamInfo]
instructions*: seq[ZInstruction]
# Used in vm.nim
@@ -994,6 +1035,7 @@ type
isSet*: bool
locked*: bool
lockOnWrite*: bool
+ moduleLock*: int32
override*: bool
contents*: pointer
lastset*: ptr ZInstruction # (not marhshaled)
@@ -1031,7 +1073,7 @@ type
running*: bool
memos*: Memos
cmdline_info*: ArgResult
-
+ module_lock_stack*: seq[int32]
MixedObj* = object
t*: TypeId
diff --git a/src/compile.nim b/src/compile.nim
index d549346..d2878ba 100644
--- a/src/compile.nim
+++ b/src/compile.nim
@@ -7,7 +7,7 @@ proc getRootSection(spec: ValidationSpec): SectionSpec {.importc, cdecl.}
proc mergeStaticSpec(m: Module) {.cdecl, importc.}
proc findAndLoadFromUrl(ctx: CompileCtx, url: string): Option[Module] {.exportc,
cdecl.}
-proc loadModule(ctx: CompileCtx, module: Module)
+proc loadModule*(ctx: CompileCtx, module: Module)
@@ -125,7 +125,7 @@ proc findAndLoadFromUrl(ctx: CompileCtx, url: string): Option[Module] =
let (loc, name, ext) = url.splitFile()
return ctx.findAndLoadModule(loc, name, ext)
-proc loadModule(ctx: CompileCtx, module: Module) =
+proc loadModule*(ctx: CompileCtx, module: Module) =
# This is the thing that actually orchestrates the load once found.
#
# The module's global scope is for its own view on what symbols
diff --git a/src/con4m.nim b/src/con4m.nim
index 3e48085..54e5728 100644
--- a/src/con4m.nim
+++ b/src/con4m.nim
@@ -58,18 +58,12 @@ when isMainModule:
session.cmd_pretty()
elif config_cmd == "resume":
cmd_resume()
- elif config_cmd == "help":
- rt.cmd_help()
+ elif config_cmd == "add":
+ cmd_add()
elif config_cmd == "compile":
- if config_args[0].endswith(obj_file_extension):
- print(fgcolor("error: ", "red") +
- text("Cannot compile object file ") + em(config_args[0]) +
- text(". Please specify a module entry point."))
- quit(-4)
-
- config_save_object = true
-
session.cmd_compile()
+ elif config_cmd == "help":
+ rt.cmd_help()
elif config_cmd == "run":
if config_args[0].endswith(obj_file_extension):
cmd_resume()
diff --git a/src/err.nim b/src/err.nim
index a5d8bc0..9079bb4 100644
--- a/src/err.nim
+++ b/src/err.nim
@@ -405,6 +405,24 @@ const errorMsgs = [
("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.
@@ -786,7 +804,8 @@ proc runtimeIssue(ctx: RuntimeState, err: string, args: seq[string],
let
instr = addr ctx.curModule.instructions[ctx.ip]
(m, l) = ctx.location_from_instruction(instr)
- loc = "When executing " & m & ":" & $(l)
+ extra = if l == -1: "" else: ":" & $(l)
+ loc = "When executing " & m & extra
print(err.formatLateError(severity, loc, args), file = stderr)
@@ -802,7 +821,11 @@ proc runtimeError*(ctx: RuntimeState, err: string, args: seq[string] = @[]) =
proc runtimeError*(ctx: RuntimeState, err: string, file: ZModuleInfo, line: int,
args: seq[string] = @[]) =
- let loc = "When executing " & file.modname & ":" & $(line)
+ 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)
diff --git a/src/getopts.nim b/src/getopts.nim
index cd7d297..7a06170 100644
--- a/src/getopts.nim
+++ b/src/getopts.nim
@@ -1417,7 +1417,7 @@ proc parse_command_line*(code: string, refname = "c4m_getopt"): RuntimeState =
quit(-4)
var
- rt = ctx.generateCode()
+ rt = ctx.generateInitialCodeObject()
exit = rt.executeObject()
params = commandLineParams()
diff --git a/src/irgen.nim b/src/irgen.nim
index f121837..ea0ace3 100644
--- a/src/irgen.nim
+++ b/src/irgen.nim
@@ -199,12 +199,25 @@ proc extractShortDoc(n: ParseNode): Rope =
else:
return doc_extract(n.docNodes.children[0].getText())
+proc extractShortDocPlain(n: ParseNode): string =
+ if n == nil or n.docNodes == nil:
+ return ""
+ else:
+ return n.docNodes.children[0].getText()
+
proc extractLongDoc(n: ParseNode): Rope =
if n == nil or n.docNodes == nil or len(n.docNodes.children) < 2:
return nil
else:
return doc_extract(n.docNodes.children[1].getText())
+proc extractLongDocPlain(n: ParseNode): string =
+ if n == nil or n.docNodes == nil or len(n.docNodes.children) < 2:
+ return ""
+ else:
+ return n.docNodes.children[1].getText()
+
+
proc fmt(s: string, x = "", y = "", t = TBottom): Rope =
result = atom(s).fgColor("atomiclime")
if x != "":
@@ -601,7 +614,7 @@ proc extractSymInfo(ctx: Module, scope: Scope, isConst = false,
for i, (name, tid, kid) in toAdd:
let symOpt = ctx.scopeDeclare(scope, name, false, tid.getTid(), isConst,
- declnode = kid)
+ declnode = kid, inparam = ctx.inParamCtx)
if i == toAdd.len() - 1 and symOpt.isSome():
result = symOpt.get()
@@ -648,8 +661,9 @@ proc convertConstStmt(ctx: Module) =
proc convertParamBody(ctx: Module, sym: var SymbolInfo) =
var
- gotShort, gotLong, gotValid, gotDefault: bool
- paramInfo = ParamInfo()
+ gotValid, gotDefault: bool
+ paramInfo = ParamInfo(shortdoc: ctx.pt.extractShortDocPlain(),
+ longdoc: ctx.pt.extractLongDocPlain())
independentSubtree:
for i in 0 ..< ctx.numKids():
@@ -658,67 +672,80 @@ proc convertParamBody(ctx: Module, sym: var SymbolInfo) =
continue
let propname = ctx.getText(i, 0)
case propname
- of "shortdoc":
- if gotShort:
- ctx.irError("DupeParamProp", @["shortdoc"], ctx.pt.children[i])
- continue
- if ctx.kidKind(i, 1) != NodeStringLit:
- ctx.irError("BadParamProp", @["shortdoc", "string literal"],
- ctx.pt.children[i])
- continue
- paramInfo.shortdoc = some(ctx.getText(i, 1))
- gotShort = true
- of "doc":
- if gotLong:
- ctx.irError("DupeParamProp", @["doc"], ctx.pt.children[i])
- continue
- if ctx.kidKind(i, 1) != NodeStringLit:
- ctx.irError("BadParamProp", @["doc", "string literal"],
- ctx.pt.children[i])
- continue
- paramInfo.doc = some(ctx.getText(i, 1))
- gotLong = true
of "validator":
- if ctx.kidKind(i, 1) != NodeCallbackLit:
+ let
+ irNode = ctx.downNode(i, 1)
+ to = irNode.tid.idToTypeRef()
+
+ if to.kind != C4Func:
ctx.irError("ParamType", @["validator", "callback"],
ctx.pt.children[i])
- let irNode = ctx.downNode(i, 1)
- ctx.typeCheck(irNode.tid, tFunc(@[sym.tid, TRich]))
- let cb = extractRef[Callback](irNode.value)
- paramInfo.validator = some(cb)
- gotValid = true
+ elif to.items.len() != 0:
+ if to.items.len() != 2:
+ ctx.irError("ParamValNArgs")
+ else:
+ if unify(sym.tid, to.items[0]) == TBottom:
+ ctx.irError("ParamValParTy", @[sym.tid.toString(),
+ irnode.tid.toString()],
+ w = irNode.parseNode)
+
+ elif unify(irNode.tid, tFunc(@[sym.tid, TString])) == TBottom:
+ ctx.irError("ParamValRetTy")
+
+ paramInfo.validatorIr = some(irNode)
+ gotValid = true
of "default":
if gotDefault:
ctx.irError("DupeParamProp", @["default"], ctx.pt.children[i])
continue
+ let irNode = ctx.downNode(i, 1)
paramInfo.defaultParse = some(ctx.pt.children[i].children[1])
gotDefault = true
+ ctx.typeCheck(irNode.tid, sym.tid, where = irNode.parseNode)
else:
ctx.irError("BadParamProp", @[propname], ctx.pt.children[i])
continue
- sym.pinfo = paramInfo
+ paramInfo.sym = sym
+ sym.pinfo = paramInfo
+
+ ctx.params.add(paramInfo)
proc convertParamBlock(ctx: Module) =
var sym: SymbolInfo
+ var ir: IrNode
+
+ ctx.inParamCtx = true
+
if ctx.pt.children[0].kind == NodeMember:
independentSubtree:
- let memberIr = ctx.downNode(0)
- sym = ctx.scopeDeclare(ctx.usedAttrs, memberIr.contents.name, false,
+ ir = ctx.downNode(0)
+ sym = ctx.scopeDeclare(ctx.usedAttrs, ir.contents.name, false,
tVar()).getOrElse(nil)
- else: # will be something we can call extractSymInfo on.
- var savedPt = ctx.pt
- ctx.pt = ctx.pt.children[0]
- sym = ctx.extractSymInfo(ctx.moduleScope, checkdollar = true)
- ctx.pt = savedPt
+ else:
+ let
+ ir = IrNode()
+ n = ctx.pt.children[0].children[0]
+ name = n.getText()
+ opt = ctx.scopeDeclare(ctx.moduleScope, name, false, tid = tVar(),
+ declNode = n, inparam = true)
+
+ if opt.isSome():
+ sym = opt.get()
+ else:
+ unreachable # TODO, make sure this is true
+
+ sym.defs.add(ir)
var savedPt = ctx.pt
ctx.pt = ctx.pt.children[1]
ctx.convertParamBody(sym)
ctx.pt = savedPt
+ ctx.inParamCtx = false
+
proc extractCTypes(ctx: Module, n: ParseNode, fn: var FuncInfo) =
var
info = ExternFnInfo()
@@ -1371,7 +1398,7 @@ proc findDeclarations(ctx: Module, n: ParseNode) =
for kid in n.children:
ctx.pt = kid
case kid.kind
- of NodeFuncDef:
+ of NodeFuncDef, NodeParamBlock:
continue
of NodeVarStmt:
ctx.convertVarStmt()
@@ -1379,8 +1406,6 @@ proc findDeclarations(ctx: Module, n: ParseNode) =
ctx.convertGlobalStmt()
of NodeConstStmt:
ctx.convertConstStmt()
- of NodeParamBlock:
- ctx.convertParamBlock()
of NodeExternBlock:
ctx.convertExternBlock()
of NodeConfSpec:
@@ -1411,6 +1436,10 @@ proc findDeclarations*(ctx: Module) =
ctx.pt = item
ctx.convertFuncDefinition()
continue
+ of NodeParamBlock:
+ ctx.pt = item
+ ctx.convertParamBlock()
+ continue
else:
ctx.findDeclarations(item)
@@ -2407,15 +2436,22 @@ proc resolveDeferredSymbols*(ctx: CompileCtx, m: Module) =
n.contents.toCall = matches[0]
continue
elif matches.len() == 0:
-
- let info = showCallMistakes(n.contents.fname, fails, t)
- m.irError("BadSig", n, @[n.contents.fname, t.toString(), "call"],
- detail = info)
+ if n.contents.kind == IrLit:
+ m.irError("NoCbMatch", w = n, @[name, t.toString])
+ else:
+ let info = showCallMistakes(n.contents.fname, fails, t)
+ m.irError("BadSig", n, @[n.contents.fname, t.toString(), "call"],
+ detail = info)
else:
- let info = fmtImplementationList(n.contents.fname,
- matches, t).quicktable()
- m.irError("CallAmbig", n, @[n.contents.fname, t.toString(), "call"],
- detail = info)
+ if n.contents.kind == IrLit:
+ let info = fmtImplementationList(name, matches, t).quicktable()
+ m.irError("CallAmbig", w = n, @[name, t.toString(), "callback"],
+ detail = info)
+ else:
+ let info = fmtImplementationList(n.contents.fname,
+ matches, t).quicktable()
+ m.irError("CallAmbig", n, @[n.contents.fname, t.toString(), "call"],
+ detail = info)
m.funcsToResolve = @[]
diff --git a/src/modparams.nim b/src/modparams.nim
new file mode 100644
index 0000000..72c7f92
--- /dev/null
+++ b/src/modparams.nim
@@ -0,0 +1,95 @@
+import "."/[stchecks, attrstore]
+
+proc get_parameter_info*(ctx: RuntimeState): seq[ZParamExport] =
+ ## Pass back the big list of all the parameters we know about, with
+ ## status information.
+ for module in ctx.obj.moduleContents:
+ for i, param in module.parameters:
+ var
+ data = ZParamExport(modid: int32(module.moduleId),
+ modname: module.modname,
+ paramid: int32(i),
+ shortdoc: param.shortdoc,
+ longdoc: param.longdoc,
+ tid: param.tid)
+ default: pointer
+
+ if param.attr != "":
+ data.name = param.attr
+ var
+ err: bool
+ tid: TypeId
+
+ let val = ctx.get(data.name, err, addr tid)
+ if not err:
+ data.havedefault = true
+ data.default = val
+ data.tid = tid
+ continue
+ else:
+ data.name = ctx.curModule.datasyms[param.offset]
+ let
+ p = param.offset
+ dataaddr = addr ctx.moduleAllocations[module.moduleId][p]
+ typeaddr = cast[ptr TypeId](cast[uint](dataaddr) +
+ uint(sizeof(pointer)))
+ if typeaddr[] != TBottom:
+ data.havedefault = true
+ data.default = dataaddr[]
+ data.tid = typeaddr[]
+ continue
+
+ if param.havedefault:
+ data.havedefault = true
+ data.default = param.default
+
+proc get_current_instruction(ctx: RuntimeState):
+ ptr ZInstruction {.importc, cdecl.}
+
+proc run_param_validator*(ctx: RuntimeState, p: ZParamInfo,
+ val: pointer, t: TypeId): string =
+
+ 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 not p.native:
+ cb.ffi = true
+
+ let s = ctx.run_callback_internal(addr cb, [(val, p.tid)])
+
+ if s != nil and s.len() != 0:
+ return s.toNimStr()
+
+proc set_user_param*(ctx: RuntimeState, mid: int, paramix: int,
+ value: pointer, t: TypeId): string {.exportc, cdecl.} =
+ ## Sets the user parameter, and runs the validator. If it returns
+ ## an error message, then it's feedback for the user, and try
+ ## again. Otherwise, it succeeded.
+
+ let
+ module = ctx.obj.moduleContents[mid - 1]
+ param = module.parameters[paramix]
+
+ result = ctx.run_param_validator(param, value, t)
+
+ param.userType = t
+ param.userparam = value
+
+proc get_param_value*(ctx: RuntimeState, param: ZParamInfo): (pointer, TypeId) =
+ if param.userType != TBottom:
+ return (param.userparam, param.userType)
+
+ if param.haveDefault:
+ return (param.default, param.tid)
+
+ var name: string
+
+ if param.attr != "":
+ name = param.attr
+ else:
+ name = ctx.curModule.datasyms[param.offset]
+
+ ctx.runtimeError("ParamNotSet", @[name, ctx.curModule.modName])
diff --git a/src/rtmarshal.nim b/src/rtmarshal.nim
index d0f1847..f88a9c9 100644
--- a/src/rtmarshal.nim
+++ b/src/rtmarshal.nim
@@ -7,7 +7,7 @@ proc marshal_int_list[T](l: seq[T]): C4Str =
proc unmarshal_int_list(p: var cstring): seq[int] =
list_unmarshal_helper(p):
- let i = cast[int](p.unmarshal_64_bit_value())
+ let i = cast[int](p.unmarshal_64_bit_value())
result.add(i)
proc marshal_ffi_arg_info(args: seq[ZFFiArgInfo]): C4Str =
@@ -117,10 +117,43 @@ proc unmarshal_byte_code(p: var cstring): seq[ZInstruction] =
instr.typeInfo = cast[TypeId](p.unmarshal_64_bit_value())
result.add(instr)
+proc marshal_mod_params(rt: RuntimeState, params: seq[ZParamInfo]): C4Str =
+ list_marshal_helper(params):
+ toAdd.add(item.attr.marshal_nim_string())
+ 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(cast[pointer](item.tid.followForwards()).marshal_64_bit_value())
+ toAdd.add(item.funcIx.marshal_32_bit_value())
+ toAdd.add(item.haveDefault.marshal_bool())
+ if item.haveDefault:
+ toAdd.add(marshal(item.default, item.tid, rt.memos))
+
+proc unmarshal_mod_params(rt: RuntimeState, p: var cstring): seq[ZParamInfo] =
+ list_unmarshal_helper(p):
+ var param = ZParamInfo()
+
+ param.attr = p.unmarshal_nim_string()
+ 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.tid = cast[TypeId](p.unmarshal_64_bit_value())
+ param.funcIx = p.unmarshal_32_bit_value()
+ param.haveDefault = p.unmarshal_bool()
+ if param.haveDefault:
+ param.default = p.unmarshal(param.tid, rt.memos)
+
+ result.add(param)
+
proc marshal_one_module(rt: RuntimeState, m: ZModuleInfo): C4Str =
basic_marshal_helper:
toAdd.add(m.modname.marshal_nim_string())
toAdd.add(m.location.marshal_nim_string())
+ toAdd.add(m.key.marshal_nim_string())
+ toAdd.add(m.ext.marshal_nim_string())
+ toAdd.add(m.url.marshal_nim_string())
toAdd.add(m.version.marshal_nim_string())
toAdd.add(m.symTypes.marshal_sym_types())
toAdd.add(m.codesyms.marshal_sym_names())
@@ -130,6 +163,7 @@ proc marshal_one_module(rt: RuntimeState, m: ZModuleInfo): C4Str =
toAdd.add(cast[pointer](m.moduleId).marshal_64_bit_value())
toAdd.add(cast[pointer](m.moduleVarSize).marshal_64_bit_value())
toAdd.add(cast[pointer](m.initSize).marshal_64_bit_value())
+ toAdd.add(rt.marshal_mod_params(m.parameters))
toAdd.add(m.instructions.marshal_byte_code())
proc unmarshal_one_module(rt: RuntimeState, p: var cstring): ZModuleInfo =
@@ -137,6 +171,9 @@ proc unmarshal_one_module(rt: RuntimeState, p: var cstring): ZModuleInfo =
result.modname = p.unmarshal_nim_string()
result.location = p.unmarshal_nim_string()
+ result.key = p.unmarshal_nim_string()
+ result.ext = p.unmarshal_nim_string()
+ result.url = p.unmarshal_nim_string()
result.version = p.unmarshal_nim_string()
result.symTypes = p.unmarshal_sym_types()
result.codeSyms = p.unmarshal_sym_names()
@@ -145,6 +182,7 @@ proc unmarshal_one_module(rt: RuntimeState, p: var cstring): ZModuleInfo =
result.moduleId = int(p.unmarshal_64_bit_value())
result.moduleVarSize = int(p.unmarshal_64_bit_value())
result.initSize = int(p.unmarshal_64_bit_value())
+ result.parameters = rt.unmarshal_mod_params(p)
result.instructions = p.unmarshal_byte_code()
proc marshal_validators(rt: RuntimeState, vlist: seq[Validator]): C4Str =
@@ -311,6 +349,11 @@ proc unmarshal_types(p: var cstring): Dict[TypeId, int] =
result[k] = v
proc marshal_object(rt: RuntimeState, nextmid: int32): C4Str =
+ var nextmid = nextmid
+
+ if nextmid == 0 and rt.obj.nextEntrypoint != 0:
+ nextmid = rt.obj.nextEntrypoint
+
basic_marshal_helper:
toAdd.add(cast[pointer](rt.obj.zeroMagic).marshal_64_bit_value())
toAdd.add(rt.obj.zcObjectVers.marshal_16_bit_value())
diff --git a/src/scope.nim b/src/scope.nim
index c1c1d1a..87bb8f9 100644
--- a/src/scope.nim
+++ b/src/scope.nim
@@ -65,7 +65,7 @@ proc lookupOrAdd(ctx: Module, scope: Scope, n: string,
proc scopeDeclare*(ctx: Module, scope: Scope, n: string,
isfunc: bool, tid = TBottom, immutable = false,
- declnode = ctx.pt):
+ declnode = ctx.pt, inparam = false):
Option[SymbolInfo] =
# This is meant for things that should be declared in a specific scope,
# like enums, functions and explicitly declared variables.
@@ -79,8 +79,10 @@ proc scopeDeclare*(ctx: Module, scope: Scope, n: string,
var sym = result.get()
# Funcs can have multiple declarations as long as signatures are disjoint.
- if sym.declaredType and not isFunc:
- ctx.irWarn("VarRedef")
+ # Similarly, we ignore this in param blocks.
+ if sym.declaredType and not isFunc and not inparam:
+ echo getStackTrace()
+ ctx.irWarn("VarRedef", @[sym.name])
else:
sym.declNode = declnode
diff --git a/src/vm.nim b/src/vm.nim
index efd38ac..aacad78 100644
--- a/src/vm.nim
+++ b/src/vm.nim
@@ -1,6 +1,6 @@
# This is just a basic runtime with no thought to performance; we'll
# do one more focused on performance later, probably directly in C.
-import "."/[stchecks, attrstore]
+import "."/[stchecks, attrstore, modparams]
export attrstore
proc load_spec() {.cdecl, importc.}
@@ -114,6 +114,10 @@ proc getSourceLoc*(ctx: RuntimeState): string =
else:
return ""
+proc get_current_instruction*(ctx: RuntimeState):
+ ptr ZInstruction {.exportc, cdecl.} =
+ return addr ctx.curModule.instructions[ctx.ip]
+
proc getStackTrace*(ctx: RuntimeState): Rope {.exportc, cdecl.} =
var cells: seq[seq[string]] = @[@["Caller module", "Line #",
"Call target"]]
@@ -707,9 +711,66 @@ proc runMainExecutionLoop(ctx: RuntimeState): int =
ctx.run_callback_internal(cbObj)
of ZRet:
leaveFrame()
+ of ZModuleEnter:
+ if instr.arg != 0:
+ for i, param in ctx.curModule.parameters:
+ var
+ val: pointer
+ t: TypeId
+
+ ## Fill in all parameter values now. If there's a validator,
+ ## it will get called after this loop, along w/ a call to
+ ## ZParamCheck.
+
+ if param.attr != "":
+ let attropt = ctx.attrs.lookup(param.attr)
+ if attropt.isNone():
+ (val, t) = ctx.get_param_value(param)
+ discard ctx.set(param.attr, val, param.tid, lock = true,
+ internal = true)
+ else:
+ let
+ p = param.offset
+ id = ctx.curModule.moduleId
+ a = addr ctx.moduleAllocations[id][p * 2]
+ address = cast[ptr pointer](a)
+ typeaddr = cast[ptr pointer](cast[uint](address) +
+ uint(sizeof(pointer)))
+ if typeaddr[] == nil: # TBottom
+ (val, t) = ctx.get_param_value(param)
+ address[] = val
+ typeaddr[] = cast[pointer](t)
+
+ ctx.module_lock_stack.add(int32(ctx.curModule.moduleId))
+ else:
+ if ctx.module_lock_stack.len() == 0:
+ ctx.module_lock_stack.add(0)
+ else:
+ ctx.module_lock_stack.add(ctx.module_lock_stack[^1])
+
+ of ZParamCheck:
+ let s = cast[C4Str](ctx.stack[ctx.sp])
+
+ ctx.sp += 2
+
+ if s != nil and s.len() != 0:
+ var
+ param = ctx.curModule.parameters[instr.arg]
+ modname = ctx.curModule.modname
+ err = s.toNimStr()
+ name: string
+
+ if param.attr != "":
+ name = param.attr
+ else:
+ name = ctx.curModule.datasyms[param.offset]
+
+ ctx.runtimeError("ParamNotValid", @[name, modname, err])
+
of ZModuleRet:
if ctx.numFrames <= 2:
return
+ discard ctx.module_lock_stack.pop()
leaveFrame()
of ZFFICall:
ctx.z_ffi_call(instr.arg)
@@ -799,8 +860,8 @@ proc setupArena(ctx: RuntimeState,
# 128 bits per item.
var moduleAllocation = newSeq[pointer](moduleIdSz * 2)
- for (offset, tid) in typeInfo:
- moduleAllocation[offset*2 + 1] = cast[pointer](tid)
+ #for (offset, tid) in typeInfo:
+ # moduleAllocation[offset*2 + 1] = cast[pointer](tid)
ctx.moduleAllocations.add(moduleAllocation)
@@ -877,6 +938,7 @@ proc setup_first_execution*(ctx: var RuntimeState) {.exportc, cdecl.} =
for i, item in ctx.obj.moduleContents:
ctx.setupArena(item.symTypes, item.moduleVarSize)
+
proc execute_object*(ctx: var RuntimeState): int {.exportc, cdecl.} =
## This call is intended for first execution, not for save /
## resumption.
diff --git a/src/ztypes/function.nim b/src/ztypes/function.nim
index 3832b37..768ab37 100644
--- a/src/ztypes/function.nim
+++ b/src/ztypes/function.nim
@@ -92,8 +92,8 @@ template initForeignModule(): ZModuleInfo =
proc run_validator(ctx: RuntimeState, startswith: string):
FlexArray[pointer] {.cdecl, importc.}
-proc foreign_z_call(ctx: RuntimeState, funcid: int):
- pointer {.exportc, cdecl.} =
+proc foreign_z_call*(ctx: RuntimeState, funcid: int):
+ pointer {.exportc, cdecl.} =
# We use a dummy module with two instructions, a call and a halt,
# then we re-start the VM using that as an entry point.
# Any result is left in the return register.
@@ -209,10 +209,10 @@ proc find_string_at(mem: string, offset: int): string {.importc, cdecl.}
proc baseunify(id1, id2: TypeId): TypeId {.importc, cdecl.}
proc run_callback*(ctx: RuntimeState,
- name: string,
- t: TypeId,
- args: openarray[(pointer, TypeId)] = [],
- ffi = false): pointer {.discardable, exportc, cdecl.} =
+ name: string,
+ t: TypeId,
+ args: openarray[(pointer, TypeId)] = [],
+ ffi = false): pointer {.discardable, exportc, cdecl.} =
## This version of run_callback finds the function to call, and
## creates the callback pointer for you, based on the name and
## signature.
diff --git a/src/ztypes/marshal.nim b/src/ztypes/marshal.nim
index 1d096c5..01ab59c 100644
--- a/src/ztypes/marshal.nim
+++ b/src/ztypes/marshal.nim
@@ -111,9 +111,9 @@ proc unmarshal_nim_string*(s: var cstring): string {.exportc, cdecl.} =
l = s.unmarshal_32_bit_value()
if l == 0:
- result = newString(1)
+ result = ""
else:
- result = newString(l + 1)
+ result = newString(l)
for i in 0 ..< l:
result[i] = s[i]
diff --git a/tests/modparam.c4m b/tests/modparam.c4m
new file mode 100644
index 0000000..0d52a3c
--- /dev/null
+++ b/tests/modparam.c4m
@@ -0,0 +1,26 @@
+func example_checker(x) {
+ result = ""
+
+ if (x % 2) != 0 {
+ result = "Parameter value must be even."
+ }
+}
+
+parameter var example1 {
+ "This should be some documentation."
+ "Also this."
+ default: 100
+ validator: func example_checker(int) -> string
+}
+
+parameter var example2 {
+ "This should be some documentation."
+ "Also this."
+ default: 101
+ validator: func example_checker(int) -> string
+}
+
+# Neither of these should happen.
+print(example1)
+print(example2)
+assert false
\ No newline at end of file
diff --git a/tests/modparam.kat b/tests/modparam.kat
new file mode 100644
index 0000000..548691e
--- /dev/null
+++ b/tests/modparam.kat
@@ -0,0 +1,2 @@
+fail
+ParamNotValid