Created
June 13, 2015 06:22
-
-
Save fowlmouth/665bedf61e6f85ae7673 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## this is an component composition framework, it is a basic object model | |
## founded on composition rather than inheritance. | |
## | |
## changes since entoody: | |
## * removed unicast/multicast messages | |
## send() sends a message to the first component that applies, | |
## multicast() is an iterator that sends a message to all components that apply | |
## | |
## * removed static component ordering. | |
## order matters! higher components can intercept messages before | |
## lower ordered components | |
## | |
## * components are geared towards behavior, nice data structs have _accessors_ | |
## | |
## What the hell is this though? | |
## * components are chunks of data and/or behavior. isolation of the two is preferred | |
## except when the data is a primitive (nim type) | |
## components can be primitive data or dynamic slotted objects | |
## their memory layout is final after creation | |
## * aggregates are collections of components, new behavior is bolted on and overrides | |
## old behavior. aggregate types may not add data after they are created, this would | |
## invalidate all of their children. a way around this is shown in stdobj.nim | |
## * objects are instances of aggregates, components here can be added and removed at | |
## will only require reallocation if data components are added/removed | |
## | |
## Example components: | |
## Position, Rotation, | |
## TextureRef, | |
## HealthPoints, Energy, MaxLifetime | |
## | |
## | |
## | |
## | |
## | |
## | |
import typetraits,tables,macros,strutils | |
export macros | |
type | |
NimDataTypeID* = int | |
ComponentKind* {.pure.}= enum | |
Static, Dynamic | |
Component* = ref object | |
bytes*: int | |
name*: string | |
messages*: Table[string,Object] | |
case kind*: ComponentKind | |
of ComponentKind.Static: | |
nimType*: NimDataTypeID | |
destructor, initializer: ObjectDestructor | |
aggr*: AggregateType | |
of ComponentKind.Dynamic: | |
slots*: seq[string] | |
AggregateType* = ref object | |
bytes*: int | |
components*: seq[(int,Component)] | |
UncheckedArray* {.unchecked.}[T] = array[1,T] | |
Object* = ref object | |
ty*: AggregateType | |
dat*: UncheckedArray[byte] | |
ObjectDestructor* = proc(self:Object, componentData: pointer){.nimcall.} | |
var data_type_counter{.global.} = 1 | |
var staticComponents{.global.}: seq[Component] = @[] | |
proc castTo (fro,to:NimNode): NimNode {.compileTime.}= | |
newTree(nnkCast, to, fro) | |
proc genDecrefStmts (ty:NimNode, data:NimNode): NimNode {.compileTime.} = | |
result = newStmtList() | |
let stmts = newStmtList() | |
var skipCast = false | |
case ty.typekind | |
of ntyInt: | |
skipCast = true | |
of ntyString: | |
let data = data.castTo(newTree(nnkPtrTy, ty)) | |
result = quote do: | |
`data`[] = nil | |
# let `strdata` = | |
# if not `data`[].isNil: GCunref(`data`[]) | |
of ntyObject: | |
let data = data.castTo(newTree(nnkPtrTy, ty)) | |
result = quote do: | |
reset `data`[] | |
of ntyRef, ntySequence: | |
let data = data.castTo(newTree(nnkPtrTy, ty[0])) | |
let dsym = genSym(nskLet, "data") | |
return quote do: | |
let `dsym` = `data` | |
if not `dsym`[].isNil: GCunref(`dsym`[]) | |
of ntyProc: | |
# stmts.add(quote do: `data`[] = nil) | |
# ty_sym = ty[0] | |
let data = data.castTo(newTree(nnkPtrTy, ty[0])) | |
return quote do: | |
`data`[] = nil | |
of ntyDistinct: | |
# TODO fix later, remove the need for an extra function call | |
echo treerepr ty | |
let ty = if ty.kind == nnkSym: getType(ty) else: ty | |
let subt = ty[1] | |
return genDecrefStmts(subt, data) | |
of ntyCstring: | |
discard | |
else: | |
echo "what do for ", ty.typekind, "???" | |
echo treerepr ty | |
echo treerepr(getType(ty)) | |
quit 0 | |
proc destroyComponent* [t: any] (self:Object; data:pointer) {.nimcall.} = | |
# todo gcunref all non-nil refs (provide a default destructor) | |
macro decRefs : stmt = | |
let x: NimNode = (quote do: t)[0] | |
echo x.treerepr | |
var ty = getType(x) | |
var tyk = ty.typekind | |
if tyk == ntyTypedesc: | |
ty = ty[1] | |
tyk = ty.typekind | |
var ty_sym = ty | |
var stmts = genDecrefStmts(ty_sym, ident"data") | |
result = stmts | |
# if skipCast: | |
# result = stmts | |
# else: | |
# let pt = newTree(nnkPtrTy, ty_sym) | |
# echo repr pt | |
# result = newStmtList(quote do: | |
# let `data` = cast[`pt`](`data`)) | |
# result.add stmts | |
when defined(debug): | |
echo "destroyComponent macro result: " , repr result | |
when defined(debug): | |
echo "destroyComponent invoked ", name(t) | |
static: | |
echo name( t) | |
decRefs() | |
proc next_type_id (t:typedesc): NimDataTypeID = | |
#mixin destroyComponent | |
result = data_type_counter.NimDataTypeID | |
var c = Component( | |
name: name(t), | |
bytes: sizeof(t), | |
messages: initTable[string,Object](4), | |
kind: ComponentKind.Static, | |
nimType: result, | |
destructor: ObjectDestructor(destroyComponent[t]) | |
) | |
if staticComponents.len < c.nimType+1: | |
staticComponents.setLen c.nimType+1 | |
staticComponents[c.nimType] = c | |
data_type_counter += 1 | |
proc data_type_id* (t:typedesc): NimDataTypeID = | |
## accessor used to check if a component is a data type | |
# static: echo name(t) | |
# echo name(t) | |
var id{.global.} = next_type_id(t) | |
return id | |
#proc gt*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} | |
#template getType* (t:typedesc): NimNode = getType(t) | |
proc dataComponent* (i:int): Component = | |
staticComponents[i] | |
proc typeComponent* (t:typedesc): Component = | |
dataComponent(dataTypeID(t)) | |
proc `$` (co: Component): string = | |
"(Component $#:$#)".format( | |
if co.nimType == 0.NimDataTypeID: "0x"& $toHex(cast[int](co), sizeof(int)*2) else: $co.nimType, | |
co.name | |
) | |
## aggregate | |
proc aggregate* (cos: varargs[Component]): AggregateType = | |
# create a new type out of multiple components | |
result = AggregateType(components: newSeq[(int,Component)](cos.len)) | |
var bytes = 0 | |
for i in 0 .. high(cos): | |
let c = cos[high(cos)-i] | |
result.components[i] = (bytes,c) | |
bytes += c.bytes | |
result.bytes = bytes | |
proc rfind* (a: seq, b: any): int {.inline.}= | |
result = high(a) | |
while result > low(a): | |
if a[result] == b: return | |
dec result | |
proc findComponentIndex* (ty: AggregateType; co: Component): int = | |
proc `==` (a: (int,Component), b: Component): bool = | |
a[1] == b | |
ty.components.rfind(co) | |
proc findComponentOffset* (ty: AggregateType; co: Component): int = | |
result = ty.findComponentIndex co | |
if result == -1: return | |
result = ty.components[result][0] | |
## Object | |
var | |
aggx_undef*: AggregateType # global aggregate type used for nil | |
# todo add some safety that no components on here have data. 'twould be bad | |
template safeType* (obj: Object): AggregateType = | |
(if obj.isNil: aggx_undef else: obj.ty) | |
proc printComponentNames* (ty: AggregateType; sep = ", "): string = | |
result = "" | |
for i in countdown(ty.components.high, 0, 1): | |
result.add ty.components[i][1].name | |
if i > 0: | |
result.add sep | |
when defined(Debug): | |
var objBeingFreed*: proc(o:Object) | |
proc freeObject* (obj: Object) = | |
#obj.sendMessage("beingFreed") | |
echo "Object free'd: 0x",strutils.tohex(cast[int](obj), sizeof(pointer)*2) | |
echo " (", printComponentNames(obj.ty), ")" | |
when defined(Debug): | |
if not objBeingFreed.isNil: | |
objBeingFreed(obj) | |
for ofs,component in obj.ty.components.items: | |
case component.kind | |
of ComponentKind.Static: | |
if not component.destructor.isNil: | |
let component_data = obj.dat[ofs].addr | |
component.destructor obj, component_data | |
of ComponentKind.Dynamic: | |
let my_data = cast[ptr UncheckedArray[Object]](obj.dat[ofs].addr) | |
for idx in 0 .. high(component.slots): | |
template slot: Object = my_data[idx] | |
if not slot.isNil: | |
echo "slot ", component.slots[idx], " ref count: ", slot.getRefCount | |
GCunref slot | |
obj.ty = nil | |
when true or defined(nim_fix_unsaferef_free): | |
proc x = | |
var e: Object | |
new e, freeObject | |
e.ty = AggregateType(components: @[]) | |
block: x() | |
GC_fullcollect() | |
proc instantiate* (ty: AggregateType): Object = | |
unsafeNew result, ty.bytes + sizeof(AggregateType) | |
result.ty = ty | |
# todo run initializers | |
proc dataPtr* (obj:Object; ty:typedesc): ptr ty = | |
let idx = obj.safeType.findComponentOffset(typeComponent(ty)) | |
if idx == -1: return | |
return cast[ptr ty](obj.dat[idx].addr) | |
proc dataVar* (obj:Object; ty:typedesc): var ty = | |
obj.findData(ty)[] | |
proc findData* (obj:Object; ty:typedesc): ptr ty = | |
dataPtr(obj,ty) | |
proc findDataM* (obj:Object; ty:typedesc): var ty = | |
dataVar(obj,ty) | |
template printComponents* (e:Object): stmt = | |
let ty = e.safeType | |
for offs,c in items(ty.components): | |
echo " ", c.name | |
for k in keys(c.messages): | |
stdout.write k | |
stdout.write ", " | |
stdout.write '\L' | |
stdout.flushfile | |
echo ty.components.len , " total" | |
# proc send* (co:Component; msg:string; args:varargs[Object]): Object = | |
# let m = co.messages[msg] | |
# if not m.isNil: | |
# var bm: BoundComponent | |
# bm.comp = co | |
# bm.offs = -1 | |
# result = m(bm, args) | |
## dynamic components | |
# proc readSlot (idx:int): MessageImpl = | |
# return proc(this:BoundComponent; args:varargs[Object]):Object = | |
# let offs = this.offs + (idx * sizeof(pointer)) | |
# return cast[ptr Object](this.self.dat[offs].addr)[] | |
# proc writeSlot (idx:int): MessageImpl = | |
# return proc(this:BoundComponent; args:varargs[Object]):Object = | |
# let offs = this.offs + (idx * sizeof(pointer)) | |
# cast[var Object](this.self.dat[offs].addr) = args[0] | |
# proc dynaComponent* (name: string; slots: varargs[string]): Component = | |
# result = Component( | |
# bytes: slots.len * sizeof(pointer), | |
# name: name, | |
# messages: initTable[string,MessageImpl](), | |
# kind: ComponentKind.Dynamic, | |
# slots: @slots | |
# ) | |
# for i in 0 .. high(slots): | |
# let | |
# m_reader = slots[i] | |
# m_writer = m_reader&":" | |
# result.rawDefine m_reader, readSlot(i) | |
# result.rawDefine m_writer, writeSlot(i) | |
## aggregate behavior modifying | |
proc dup* (ty:AggregateType): AggregateType = | |
AggregateType(bytes: ty.bytes, components: ty.components) | |
proc isBehavior* (co:Component): bool = co.bytes == 0 | |
## component stores no state | |
proc addBehavior* (ty:AggregateType; behav:Component): bool = | |
## modify an aggregate type by adding behavior, all instances of | |
## the type will be affected! | |
result = behav.isBehavior | |
if not result: return false | |
ty.components.add((0,behav)) | |
proc addBehavior* (obj:Object; behav:Component): bool = | |
## derive a new aggregate type for obj with behav added. | |
## no other instances of the current type will be affected. | |
result = behav.isBehavior | |
if not result: return false | |
let new_ty = obj.ty.dup | |
new_ty.components.add((-1,behav)) | |
obj.ty = new_ty | |
proc dropBehavior* (ty:AggregateType; behav:Component): bool = | |
## modifies an aggregate type, all instances will be affected! | |
result = behav.isBehavior | |
if not result: return false | |
let idx = ty.findComponentIndex(behav) | |
if idx == -1: return false | |
ty.components.delete idx | |
proc dropBehavior* (obj:Object; behav:Component): bool = | |
## derive a new type for the object without `behav`. this does not | |
## modify other objects, instead the object is assigned a new copy of type. | |
result = behav.isBehavior | |
if not result: return false | |
let idx = obj.ty.findComponentIndex(behav) | |
if idx == -1: return false | |
let new_ty = obj.ty.dup | |
new_ty.components.delete idx | |
obj.ty = new_ty | |
proc insertBehavior* (ty:AggregateType; behavior:Component; index:Natural): bool= | |
## insert a behavior into a specific slot | |
## index should range from 0 .. high(ty.components) | |
## index 0 inserts at the end of the components since dispatch looks in reverse | |
result = behavior.isBehavior | |
if not result: return | |
ty.components.insert( | |
(-1,behavior), | |
ty.components.len - index) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tables, strutils, sequtils | |
import glossolalia | |
type NK* {.pure.} = enum | |
Ident, Symbol, | |
IntLiteral, | |
Message, Array, Block | |
type | |
Node* = ref NodeObj | |
NodeObj* {.acyclic.} = object | |
case kind*: NK | |
of NK.Ident, NK.Symbol: str*: string | |
of NK.IntLiteral: i*: int | |
of NK.Message .. NK.Array: | |
sub*: seq[Node] | |
of NK.Block: | |
args*,locals*:seq[string] | |
stmts*: seq[Node] | |
const valid_operator = {'+','-','/','*','&','|','%','$','@','='} | |
proc `[]`* (n:Node; idx:Positive): Node = | |
n.sub[idx] | |
proc `$` * (n:Node): string = | |
case n.kind | |
of NK.Message: | |
assert n.sub.len >= 2 | |
result = "(" | |
result.add($n.sub[0].str) | |
for i in 1 .. high(n.sub): | |
result.add ' ' | |
result.add($n.sub[i]) | |
result.add ')' | |
of NK.Block: | |
result = "[" | |
for arg in n.args: | |
result.add ':' | |
result.add arg | |
result.add ' ' | |
for local in n.locals: | |
result.add local | |
result.add ' ' | |
result.add '|' | |
for i,statement in n.stmts: | |
result.add "\n" | |
result.add($statement) | |
if i < high(n.stmts): | |
result.add '.' | |
result.add ']' | |
of NK.Array: | |
result = "{" | |
for idx,node in pairs(n.sub): | |
result.add($node) | |
if idx < n.sub.high: | |
result.add ". " | |
result.add '}' | |
of NK.IntLiteral: return $n.i | |
of NK.Ident: return n.str | |
of NK.Symbol: return "#"&n.str | |
proc parens* (n: Rule[Node]): Rule[Node] = | |
charMatcher[Node]({'('}) and n and charMatcher[Node]({')'}) | |
proc Ident* (m:string): Node = Node(kind:NK.Ident, str:m) | |
proc save_expr_keyword (nodes: seq[Node]): Node = | |
var ident = Node(kind:NK.Ident, str:"") | |
result = Node(kind: NK.Message, sub: @[ident]) | |
var i = 0 | |
if (nodes.len and 1) != 0: | |
result.sub.add nodes[0] | |
i = 1 | |
else: | |
result.sub.add Ident("thisContext") | |
for j in countup(i,high(nodes),2): | |
ident.str.add nodes[j].str | |
result.sub.add nodes[j+1] | |
proc save_expr_binary (nodes: seq[Node]): Node = | |
result = nodes[0] | |
for i in countup(1, nodes.high, 2): | |
result = Node(kind: NK.Message, sub: @[nodes[i], result, nodes[i+1]]) | |
proc UnaryExpr* (recv:Node; msgs:seq[Node]): Node = | |
result = recv | |
for i in 0 .. high(msgs): | |
result = Node(kind: NK.Message, sub: @[msgs[i], result]) | |
# result = Node(kind: NK.Message, sub: @[recv]) | |
# result.sub.add msgs | |
proc p (r:Rule[Node]):Rule[Node] = | |
Rule[Node]( | |
m: proc(input:var InputState): Match[Node] = | |
result = r.m(input) | |
echo input.pos | |
) | |
proc echoP ():Rule[Node] = | |
Rule[Node]( | |
m: proc(input:var InputState): Match[Node] = | |
echo input.str[input.pos] | |
result = Match[Node](kind:mUnrefined, pos:input.pos,len: -1) | |
) | |
proc Expression* : Rule[Node] = | |
grammar(Node): | |
ws := +(newline or chr({' ','\t'})) | |
newline := str("\r\L") or chr({'\L'}) | |
colon := chr(':') | |
ident := ident_str.save(Ident) | |
ident_str := chr({'A'..'Z', 'a'..'z', '_'}) and *chr({'A'..'Z','a'..'z','0'..'9','_'}) | |
keywd := keyword_str.save(Ident) | |
keyword_str := ident_str and colon | |
symbol := chr('#') and (+keyword_str or ident_str).save do (str: string)->Node: Node(kind:NK.Symbol, str:str) | |
binary_op := | |
(+chr(valid_operator)).save(Ident) | |
literal_int := chr(strutils.Digits).repeat(1).save do(m:string)->Node: Node(kind:NK.IntLiteral, i:parseInt(m)) | |
stmt_separator := | |
?ws and chr('.') and ?ws | |
argument := colon and ident | |
anyChar := chr({char.low .. char.high}) | |
proc saveArrBlank (r:Rule[Node]):Rule[Node] = | |
(r.save do (ns:seq[Node])->Node: Node(kind:NK.Array, sub:ns)) | |
.saveBlank do ->Node: Node(kind:NK.Array, sub:nil) | |
arg_list := | |
(argument.join(?ws) or anyChar.present).saveArrBlank and | |
?ws and | |
(ident.join(ws) or anyChar.present).saveArrBlank and | |
chr('|') | |
# ( colon and ?ws and ident.join(?ws) and ?ws and chr('|') and ?ws | |
# ).repeat(0,1) | |
# .save((ns:seq[Node])->Node=> Node(kind:NK.Array, sub:ns)) | |
# .save((start:cstring,len:int)->Node=> Node(kind:NK.Array, sub: @[])) | |
block_literal := | |
( chr('[') and ?ws and ?(arg_list and ?ws) and | |
expr_keyword.join(stmt_separator).saveArrBlank and | |
?ws and chr(']') | |
).save do (nodes:seq[Node])->Node: | |
result = Node( | |
kind: NK.Block) | |
if nodes.len == 1: | |
result.stmts = nodes[0].sub | |
result.args = @[] | |
result.locals = @[] | |
else: | |
template strs (n): seq[string] = | |
(if n.kind == NK.Array: (if n.sub.isNil: @[] else: n.sub.mapIt(string, it.str)) else: @[]) | |
result.args = nodes[0].strs | |
result.locals= nodes[1].strs | |
result.stmts = nodes[2].sub | |
expr_terminal := | |
(literal_int and absent(chr({'A'..'Z','a'..'z'}))) or | |
block_literal or | |
parens(?ws and expr_keyword and ?ws) | |
expr_unary := | |
((expr_terminal and +(ws and ident and colon.absent)).save do (ns:seq[Node])->Node: | |
UnaryExpr(ns[0], ns[1 .. ^1]) | |
) or | |
((ident and colon.absent).join(ws).save do (ns: seq[Node])->Node: | |
UnaryExpr(Ident("thisContext"), ns) | |
) or | |
expr_terminal | |
expr_binary := | |
( expr_unary.join(?ws and binary_op and ?ws) | |
).save(save_expr_binary) or | |
expr_unary | |
expr_keyword := | |
( | |
(expr_binary and +(?ws and keywd and ?ws and expr_binary)) or | |
(keywd and ?ws and expr_binary).join(?ws) | |
).save(save_expr_keyword) or | |
expr_binary | |
result = expr_keyword | |
export glossolalia | |
when isMainModule: | |
template test(str,rule): stmt = | |
do_assert rule.match(str), "[FAIL] "& astToStr(rule) &" for "&str | |
test "1", expr_unary | |
test "1 + 1", expr_binary | |
test "1 at: 2 put: 3", expr_keyword | |
test "1+2 at: 1", expr_keyword | |
test "[1]", block_literal | |
test "[1+2]", block_literal | |
test "[ 1+2 at: 1 ]", block_literal | |
test "[a b]", Expression | |
echo Expression.match("[false. true]").nodes[0] | |
echo Expression.match("[:n a b| n send: a with: b ]") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cmodel2,macros | |
proc slotsComponent* (name: string; slots: varargs[string]): Component | |
## depends on opcodes | |
var | |
cxObj* = slotsComponent("Object") | |
cxTrue* = slotsComponent("True") | |
obj_true* = aggregate(cxTrue, cxObj).instantiate | |
cxFalse* = slotsComponent("False") | |
obj_false* = aggregate(cxFalse, cxObj).instantiate | |
cxUndef* = slotsComponent("Undef") | |
cmodel2.aggxUndef = aggregate(cxUndef, cxObj) | |
type | |
NimTerminalObject* = concept X | |
asObject(X) is Object | |
template defPrimitiveComponent* (n:untyped, comp:typed): stmt = | |
{.line: instantiationInfo().}: | |
static: assert `comp` isNot NimTerminalObject | |
let `cx n`* {.inject} = typeComponent(comp) | |
let `aggx n`* {.inject} = aggregate(typeComponent(comp), cx_obj) | |
`cx n`.aggr = `aggx n` | |
`cx n`.name = astToStr(n) | |
proc `obj n`* (some: `comp`): Object = | |
result = `aggx n`.instantiate | |
result.dataVar(comp) = some | |
proc asObject* (some: `comp`): Object{.inline.}= | |
`obj n`(some) | |
static: assert `comp` is NimTerminalObject | |
defineMessage(`cx n`, "as"&astToStr(n)) | |
do: return self | |
proc `as n`* (some:Object): var `comp` {.inline.} = | |
let dp = some.dataPtr(`comp`) | |
if not dp.isNil: return dp[] | |
# try to call asX on the object | |
let o = some.send("as"&astToStr(n)) | |
let dp2 = o.dataPtr(`comp`) | |
if not dp2.isNil: return dp2[] | |
## message sending | |
type | |
BoundComponent* = object | |
self*: Object | |
comp*: Component | |
idx*: int | |
## BoundComponent | |
## contains three pieces needed to access an instance of a component: | |
## the object that owns it, the component itself and the data offset in object. | |
## BoundComponent is valid so long as you do not prepend the object type | |
proc asPtr* (c:BoundComponent; t:typedesc): ptr t = | |
# take unsafe reference to component data. | |
do_assert c.comp.nimType == data_type_id(t) | |
let offs = c.self.ty.components[c.idx][0] | |
cast[ptr t](c.self.dat[offs].addr) | |
proc asVar* (c:BoundComponent; t:typedesc): var t = | |
c.asPtr(t)[] | |
proc getComponent* (some: Object; comp: Component): BoundComponent {.inline.} = | |
BoundComponent( | |
self: some, | |
comp: comp, | |
idx: some.safeType.findComponentIndex(comp) | |
) | |
proc getComponent* (some: Object; n: int): BoundComponent {.inline.} = | |
do_assert n in 0 .. some.safeType.components.high | |
BoundComponent(self:some, idx:n, comp:some.safeType.components[n][1]) | |
proc isValid* (bc: BoundComponent): bool = bc.idx > -1 | |
proc slotPtr* (bc: BoundComponent; idx: int): ptr Object = | |
let offs = bc.self.safeType.components[bc.idx][0] | |
return | |
addr cast[ptr UncheckedArray[Object]](bc.self.dat[offs].addr)[idx] | |
proc slotVar* (bc: BoundComponent; idx: int): var Object = | |
bc.slotPtr(idx)[] | |
type | |
MessageSearch* = object | |
continueFrom*: int | |
msg*: Object | |
bound*: BoundComponent | |
import tables | |
proc findMessage* (ty: AggregateType; msg: string; res: var MessageSearch): bool = | |
for i in countdown(res.continueFrom, 0, 1): | |
let | |
c = ty.components[i] | |
m = c[1].messages[msg] | |
if not m.isNil: | |
res.continueFrom = i-1 | |
res.msg = m #cast[MessageImpl](m) | |
res.bound.comp = c[1] | |
res.bound.idx = i | |
result = true | |
return | |
# proc bindMessage* (e:Object; msg:string): Object = | |
# # creates a context | |
# let ty = e.safeType | |
# var ms: MessageSearch | |
# ms.continueFrom = high(ty.components) | |
# if ty.findMessage(msg, ms): | |
# ms.bound.self = e | |
# # create its context | |
# result = createContext(ms.msg, ms.bound) | |
# #result = ms.msg(ms.bound, args) | |
# else: | |
# echo "failed to find `",msg.repr,"` on entity ", cast[int](e) | |
# print_components(e) | |
proc findMessage* (obj:Object; msg:string): (BoundComponent,Object) = | |
let ty = obj.safeType | |
var ms: MessageSearch | |
ms.continueFrom = high(ty.components) | |
if ty.findMessage(msg, ms): | |
ms.bound.self = obj | |
result = (ms.bound, ms.msg) | |
return | |
result[0].idx = -1 | |
let | |
cxBoundComponent* = typeComponent(BoundComponent) | |
type | |
Block* = object | |
ipStart*, ipEnd*: int | |
meth*: Object | |
let cxBlock* = typeComponent(Block) | |
type | |
CompiledMethod* = object | |
bytecode*: seq[byte] | |
args,locals: seq[string] | |
contextCreator*: Component ## component with slots for args and locals to hold state in the context object | |
let | |
cxCompiledMethod* = typeComponent(CompiledMethod) | |
aggxCompiledMethod* = aggregate(cxCompiledMethod, cxObj) | |
proc initCompiledMethod* (bytecode:seq[byte]; args,locals:openarray[string]=[]): CompiledMethod = | |
result = CompiledMethod( | |
bytecode:bytecode, | |
args: @args, | |
locals: @locals | |
) | |
result.contextCreator = slotsComponent("locals", result.args & result.locals) | |
type | |
PrimitiveCB = proc(context: Object; this: BoundComponent): Object{.nimcall.} | |
PrimitiveMethod* = object | |
fn*: PrimitiveCB | |
code*,name*: string | |
let | |
cxPrimitiveMethod* = typeComponent(PrimitiveMethod) | |
aggxPrimitiveMethod* = aggregate(cxPrimitiveMethod, cxCompiledMethod, cxObj) | |
cxPrimitiveMethod.aggr = aggxPrimitiveMethod | |
proc newPrimitiveMessage* (args:openarray[string]; name,src:string; fn:PrimitiveCB): Object | |
proc rawDefine* (co:Component; msg:string; obj:Object) = | |
co.messages[msg] = obj | |
macro defineMessage* (co,msg,body:untyped):stmt = | |
#co:Component; msg:string; body:untyped{nkDo} | |
let cs = callsite() | |
# let co = cs[1] | |
# let msg = cs[2] | |
# let body = cs[3] | |
let params = [ | |
ident"Object", | |
newIdentDefs(ident"context", ident"Object"), | |
newIdentDefs(ident"this", ident"BoundComponent") | |
] | |
## the body param is a nnkDo, lets scan the params and turn them into | |
## templates. | |
let new_body = newStmtList() | |
var arg_names = newseq[NimNode]() | |
new_body.add quote do: | |
template self(): Object = this.self | |
template thisComponent(): Component = this.comp | |
var arg_idx = 0 | |
for fp1 in 1 .. len(body.params)-1: | |
let p = body.params[fp1] | |
for fp2 in 0 .. len(p)-3: | |
let name = p[fp2] | |
let strname = repr(name) | |
new_body.add(quote do: | |
let `name` = context.send(`strname`) | |
#template `name`: Object = args[`arg_idx`]) | |
) | |
arg_idx += 1 | |
arg_names.add(newLit($name)) | |
## copy the rest of the function into the new stmt list | |
body.body.copyChildrenTo new_body | |
let new_proc = newProc( | |
params = params, | |
body = new_body, | |
proc_type = nnkLambda) | |
new_proc.pragma = newTree(nnkPragma, ident"nimcall") | |
var co_safe = co | |
# if co_safe.typekind == ntyTypedesc: | |
# co_safe = quote do: typeComponent(`co`) | |
## call rawDefine() to store it | |
let argNamesNode = newTree(nnkBracket, arg_names) | |
# let src = repr(callsite()) | |
# echo src | |
echo treerepr cs | |
result = quote do: | |
let m = newPrimitiveMessage(`arg_names_node`, `msg`, "", `new_proc`) | |
rawDefine(`co_safe`, `msg`, m) | |
when defined(Debug): | |
echo repr result | |
# template defineMessage* (co:Component; msg:string; body:untyped):stmt {.immediate,dirty.} = | |
# getAst(defineMessage2(co,msg,body)) | |
proc send* (recv:Object; msg:string; args:varargs[Object]): Object | |
# not used in the vm | |
defPrimitiveComponent(Int, int) | |
defPrimitiveComponent(String, string) | |
import tables | |
type | |
Instr* {.pure.} = enum | |
NOP, PushNIL, PushPOD, PushBLOCK, PushThisContext, | |
Pop, Dup, Send, GetSlot, SetSlot, ExecPrimitive, Return | |
iseq = seq[byte] | |
InstrBuilder* = object | |
iset*: iseq | |
index*: int | |
labels*: Table[string,int] | |
proc initInstrBuilder* : InstrBuilder = | |
newSeq result.iset, 0 | |
result.labels = initTable[string,int]() | |
type | |
RawBytes* = cstring | |
Serializable* = concept obj | |
var builder: InstrBuilder | |
serialize(obj, builder) | |
var source: RawBytes | |
unserialize(obj, source) | |
proc ensureLen* (some: var seq; len: int) {.inline.}= | |
if some.len < len: some.setLen len | |
template addByte* (i: var InstrBuilder; b: untyped): stmt = | |
let byt = byte(b) | |
i.iset.ensureLen i.index+1 | |
i.iset[i.index] = byt | |
i.index += 1 | |
template addNullBytes* (i: var InstrBuilder; n: int): stmt = | |
i.iset.ensureLen i.index+n | |
i.index += n | |
proc pushPOD* (i: var InstrBuilder; obj: Serializable) = | |
mixin serialize | |
let ty = typeComponent(type(obj)) | |
let id = ty.nim_type | |
do_assert id < 127, "FIX ASAP" # make this two bytes if it gets big | |
do_assert id > 0, "invalid id "& $id & "::"& ty.name | |
i.addByte Instr.PushPOD | |
i.addByte id | |
serialize(obj, i) | |
import endians | |
proc pushBlock* (i:var InstrBuilder; args,locals:openarray[string]; iseq: var iseq) = | |
i.addByte Instr.PushBLOCK | |
i.addByte args.len | |
for idx in 0 .. high(args): | |
let start = i.index | |
i.addNullBytes args[idx].len+1 | |
copyMem i.iset[start].addr, args[idx].cstring, args[idx].len | |
i.addByte locals.len | |
for idx in 0 .. high(locals): | |
let start = i.index | |
i.addNullBytes locals[idx].len+1 | |
copyMem i.iset[start].addr, locals[idx].cstring, locals[idx].len | |
block: | |
let start = i.index | |
i.addNullBytes 4 | |
var len = iseq.len.uint32 | |
bigEndian32(i.iset[start].addr, len.addr) | |
let instrs_start = i.index | |
i.addNullBytes len.int | |
copyMem i.iset[instrs_start].addr, iseq[0].addr, len | |
proc pushThisContext* (i: var InstrBuilder) = | |
## stack effect ( -- object ) | |
i.addByte Instr.PushThisContext | |
proc pushNIL* (i: var InstrBuilder) = | |
## stack effect ( -- nil ) | |
i.addByte Instr.PushNIL | |
proc dup* (i: var InstrBuilder) = | |
## stack effect ( object -- object object ) | |
i.addByte Instr.Dup | |
proc pop* (i: var InstrBuilder) = | |
## stack effect ( object -- ) | |
i.addByte Instr.Pop | |
proc send* (i: var InstrBuilder; msg:string; args:int) = | |
let L = msg.len | |
i.addByte Instr.Send | |
i.addByte args | |
i.addByte L | |
let IDX = i.index | |
i.addNullBytes L | |
copyMem i.iset[IDX].addr, msg.cstring, L | |
proc getSlot* (i:var InstrBuilder; slot:int) = | |
## stack effect ( -- object ) | |
assert slot in 0 .. 127 # arbitrary limit | |
i.addByte Instr.GetSlot | |
i.addByte slot | |
proc setSlot* (i:var InstrBuilder; slot:int) = | |
## stack effect ( object -- ) | |
assert slot in 0 .. 127 | |
i.addByte Instr.SetSlot | |
i.addByte slot | |
proc execPrimitive* (i:var InstrBuilder) = | |
i.addByte Instr.ExecPrimitive | |
proc done* (i: var InstrBuilder): seq[byte] = | |
i.iset.setLen i.index | |
result = i.iset | |
proc newPrimitiveMessage* (args:openarray[string]; name,src:string; fn:PrimitiveCB): Object = | |
result = aggxPrimitiveMethod.instantiate | |
result.dataPtr(PrimitiveMethod).fn = fn | |
result.dataPtr(PrimitiveMethod).code = src | |
result.dataPtr(PrimitiveMethod).name = name | |
var ibuilder = initInstrBuilder() | |
ibuilder.execPrimitive | |
let cm = result.dataPtr(CompiledMethod) | |
cm[] = initCompiledMethod(ibuilder.done, args=args, locals=[]) | |
## dynamic components | |
var readers: seq[Object] = @[] | |
proc readSlot (idx:int): Object = | |
# returns a CompiledMethod to read slot idx from a component instance | |
if idx < 0: return | |
if idx < readers.len: | |
result = readers[idx] | |
if not result.isNil: return | |
elif readers.high < idx: | |
readers.setLen idx+1 | |
result = aggxCompiledMethod.instantiate | |
readers[idx] = result | |
var ibuilder = initInstrBuilder() | |
ibuilder.getSlot(idx) | |
result.dataVar(CompiledMethod) = | |
initCompiledMethod( | |
ibuilder.done, | |
args=[] ) | |
var writers: seq[Object] = @[] | |
proc writeSlot (idx:int): Object = | |
# return proc(this:BoundComponent; args:varargs[Object]):Object = | |
# let offs = this.offs + (idx * sizeof(pointer)) | |
# cast[var Object](this.self.dat[offs].addr) = args[0] | |
if idx < 0: return | |
if idx < writers.len: | |
result = writers[idx] | |
if not result.isNil: return | |
elif writers.high < idx: | |
writers.setLen idx+1 | |
result = aggxCompiledMethod.instantiate | |
writers[idx] = result | |
var ib = initInstrBuilder() | |
ib.pushThisContext | |
ib.send "val", 0 | |
ib.setSlot(idx) | |
result.dataVar(CompiledMethod) = | |
initCompiledMethod( | |
ib.done, args=["val"] ) | |
proc slotsComponent (name: string; slots: varargs[string]): Component = | |
result = Component( | |
bytes: slots.len * sizeof(pointer), | |
name: name, | |
messages: initTable[string,Object](), | |
kind: ComponentKind.Dynamic, | |
slots: @slots | |
) | |
for i in 0 .. high(slots): | |
let | |
m_reader = slots[i] | |
m_writer = m_reader&":" | |
result.rawDefine m_reader, readSlot(i) | |
result.rawDefine m_writer, writeSlot(i) | |
type Stack* = distinct seq[Object] | |
let cxStack* = typeComponent(Stack) | |
let aggxStack = aggregate(cxStack, cxObj) | |
proc len* (some: ptr Stack): int = | |
result = | |
if some.isNil: 0 | |
elif seq[Object](some[]).isNil: 0 | |
else: seq[Object](some[]).len | |
proc pop* (some: ptr Stack): Object = | |
result = | |
if some.len == 0: nil | |
else: seq[Object](some[]).pop | |
proc push* (some: ptr Stack; val:Object) = | |
if some.isNil: return | |
if seq[Object](some[]).isNil: newSeq seq[Object](some[]), 0 | |
seq[Object](some[]).add val | |
proc top* (some: ptr Stack): Object = | |
let some = (ptr seq[Object])(some) | |
if some.isNil or some[].isNil or some[].len == 0: return | |
return some[][some[].high] | |
type | |
Context* = object | |
parent*: Object # lexical parent context (where the block was instantiated from) | |
# for methods this may be a link to global state? not sure yet | |
caller*: Object # | |
instrs*: Object ## CompiledMethod or Block | |
exec*: Object | |
ip*, highIP*: int | |
let cxContext* = typeComponent(Context) | |
proc createContext* (compiledMethod:Object; bound:BoundComponent): Object = | |
## allocates a context for a method | |
## does not set Context.caller or .parent | |
let cm = compiledMethod.dataPtr(CompiledMethod) | |
#echo cm.contextCreator.isNil | |
result = instantiate aggregate( | |
cm.contextCreator, | |
cxBoundComponent, | |
cxStack, cxContext | |
) | |
result.dataVar(Context).highIP = cm.bytecode.high | |
result.dataVar(Context).instrs = compiledMethod | |
result.dataVar(BoundComponent) = bound | |
proc createBlockContext* (): Object = | |
nil | |
# defineMessage(cxCompiledMethod, "createContext") do: | |
# let cm = this.asPtr(CompiledMethod) | |
# if cm.contextCreator.isNil: return | |
# result = aggregate(cxBoundComponent, cm.contextCreator, cxContextInstance) | |
# .instantiate | |
# discard context.send(result, "parent:", context) | |
type Exec* = object | |
activeContext*, rootContext*: Object | |
bytePtr*: Object | |
result*: Object | |
let cxExec* = typeComponent(Exec) | |
let aggxExec* = aggregate(cxExec, cxObj) | |
proc isActive* (some: ptr Exec): bool = | |
not some.activeContext.isNil | |
proc contextIsFinished* (some: ptr Exec): bool = | |
let ac = some.activeContext | |
if ac.isNil: return true | |
let ctx = ac.dataPtr(Context) | |
if ctx.isNil: return true | |
if ctx.ip > ctx.highIP: | |
return true | |
proc ptrToBytecode* (some: ptr Exec): ptr UncheckedArray[byte] = | |
let ctx = some.activeContext.dataPtr(Context) | |
let cm = ctx.instrs | |
# result = cast[ptr UncheckedArray[byte]]( | |
# cm.dataPtr(CompiledMethod).bytecode[ctx.ip].addr | |
# ) | |
result = cast[ptr UncheckedArray[byte]]( | |
cm.dataPtr(CompiledMethod).bytecode[0].addr | |
) | |
proc setActiveContext* (someExec, ctx: Object) = | |
someExec.dataVar(Exec).activeContext = ctx | |
ctx.dataVar(Context).exec = someExec | |
let cxRawBytes* = typeComponent(cstring) | |
let aggxRawBytes* = aggregate(cxRawBytes, cxObj) | |
import strutils | |
template echoCode* (xpr:expr): stmt = | |
echo astToStr(xpr),": ",xpr | |
template echoCodeI* (i=2; xpr:expr): stmt = | |
echo repeat(' ',i), astToStr(xpr), ": ", xpr | |
template wdd * (body:stmt):stmt = | |
when defined(Debug): body | |
const ShowInstruction = | |
defined(Debug) or defined(ShowInstruction) | |
proc tick* (self: Object) = | |
let exe = self.dataPtr(Exec) | |
assert(not exe.activeContext.isNil) | |
let activeContext = exe.activeContext | |
let thisContext = activeContext.dataPtr(Context) | |
let thisStack = activeContext.dataPtr(Stack) | |
wdd: | |
echo thisContext.ip, "/", thisContext.highIP | |
if exe.contextIsFinished: | |
wdd: echo "Leaving context - finished" | |
let val = thisStack.pop | |
let next = exe.activeContext.dataPtr(Context).caller | |
if next.isNil or next.dataPtr(Context).exec == self: | |
exe.result = val | |
exe.activeContext = nil | |
return | |
next.dataPtr(Stack).push val | |
self.setActiveContext next | |
return | |
# exe.activeContext = next | |
# if not next.isNil: | |
# next.dataPtr(Stack).push val | |
# else: | |
# exe.result = val | |
# return | |
template top (): expr = | |
thisStack.top | |
template push (o): stmt = | |
thisStack.push o | |
template pop (): expr = | |
thisStack.pop | |
let iset = exe.ptrToBytecode | |
var idx = thisContext.ip | |
let op = iset[idx] | |
when ShowInstruction: | |
echo op.Instr ," @ ", thisContext.ip,"/",thisContext.highIP | |
case op.Instr | |
of Instr.NOP: | |
idx += 1 | |
of Instr.Dup: | |
idx += 1 | |
push top() | |
of Instr.Pop: | |
idx += 1 | |
discard pop() | |
of Instr.PushNIL: | |
idx += 1 | |
push nil.Object | |
of Instr.PushThisContext: | |
idx += 1 | |
push activeContext | |
of Instr.ExecPrimitive: | |
idx += 1 | |
# execute the primitive attached to the currently running context | |
let pm = thisContext.instrs.dataPtr(PrimitiveMethod) | |
when ShowInstruction: | |
echo " ExecPrimitive($#)".format(pm.name) | |
if not pm.isNil: | |
let bc = activeContext.dataPtr(BoundComponent) | |
if bc.isNil: | |
echo "NO BOUND COMPONENT!" | |
push nil | |
else: | |
if pm.fn.isNil: | |
echo "PRIMITIVE IS NIL??" | |
#activeContext.printcomponents | |
push nil | |
else: | |
let res = pm.fn(activeContext, bc[]) | |
push res | |
else: | |
push nil | |
of Instr.GetSlot: | |
idx += 1 | |
let slot = iset[idx] | |
when ShowInstruction: | |
echo "GetSlot(", slot, ")" | |
idx += 1 | |
let bm = activeContext.dataPtr(BoundComponent) | |
if not bm.isNil: | |
# found bound method, trying to get slot "slot" | |
do_assert slot.int in 0 .. high(bm.comp.slots) | |
let obj = bm[].slotVar(slot.int) | |
wdd: obj.printcomponents | |
push obj | |
# let offs = bm.self.ty.components[bm.idx][0] | |
# do_assert offs != -1 | |
# let dp = cast[ptr UncheckedArray[Object]](bm.self.dat[offs].addr) | |
# push dp[slot] | |
else: | |
echo "BoundComponent not found!" | |
push nil.Object | |
of Instr.SetSlot: | |
idx += 1 | |
let slot = iset[idx] | |
idx += 1 | |
when ShowInstruction: | |
echo "SetSlot(", slot, ")" | |
let val = pop() | |
let bc = activeContext.dataPtr(BoundComponent) | |
if not bc.isNil: | |
do_assert slot.int in 0 .. high(bm.comp.slots) | |
do_assert bm[].isValid | |
bc[].slotVar(slot.int) = val | |
# let offs = bm.self.ty.components[bm.idx][0] | |
# do_assert offs != -1 | |
# let dp = cast[ptr UncheckedArray[Object]](bm.self.dat[offs].addr) | |
# dp[slot] = val | |
else: | |
echo "BoundComponent not found for set slot!!!" | |
of Instr.PushPOD: | |
idx += 1 | |
let id = iset[idx].int | |
let ty = dataComponent(id) | |
idx += 1 | |
let cstr = cast[cstring](iset[idx].addr) | |
#let src = objRawBytes(cstr) | |
if exe.bytePtr.isNil: | |
exe.bytePtr = aggxRawBytes.instantiate | |
let src = exe.bytePtr | |
src.dataVar(RawBytes) = cstr | |
when defined(Debug): | |
echoCodeI 2, ty.name | |
let obj = ty.aggr.instantiate() | |
if obj.isNil: | |
echo "NEW POD ", ty.name, " FAILED TO LOAD" | |
else: | |
wdd: obj.printcomponents | |
let o2 = obj.send("loadFromRaw:", src) | |
let L = o2.dataPtr(int) | |
if L.isNil: echo "LOADFROMRAW: RESULT IS NOT INT!" | |
else: | |
idx += L[] | |
push obj | |
echo " PushPOD($#)" % obj.send("print").asString | |
of Instr.PushBLOCK: | |
idx += 1 | |
let nArgs = iset[idx].int | |
var args = newSeq[string](nArgs) | |
idx += 1 | |
for arg in 0 .. <nArgs: | |
var start = idx | |
var str: array[129,char] | |
for i in 0 .. 128: | |
str[i] = iset[start+i].char | |
if str[i] == '\00': | |
idx += i | |
break | |
args[arg] = $ str[0].addr.cstring | |
idx += 1 | |
let nLocals = iset[idx].int | |
var locals = newSeq[string](nLocals) | |
idx += 1 | |
for arg in 0 .. <nLocals: | |
var start = idx | |
var str: array[129,char] | |
for i in 0 .. 128: | |
str[i] = iset[start+i].char | |
if str[i] == '\00': | |
idx += i | |
break | |
locals[arg] = $ str[0].addr.cstring | |
idx += 1 | |
var bytecode_len: uint32 | |
bigEndian32(bytecode_len.addr, iset[idx].addr) | |
idx += 4 | |
let bc_start = idx | |
idx += bytecode_len.int | |
let bc_end = idx | |
## create an object that can create this block's context | |
let obj = aggregate(cxBlock, cxObj).instantiate | |
let bp = obj.dataPtr(Block) | |
bp.ipStart = bc_start | |
bp.ipEnd = bc_end | |
bp.meth = thisContext.instrs | |
push obj | |
echo " * bytecode_len = ", bytecode_len | |
echo " * end up at ", idx | |
of Instr.Send: | |
idx += 1 | |
let argN = iset[idx].int | |
idx += 1 | |
let L = iset[idx].int | |
idx += 1 | |
var str = newString(L) | |
copyMem str[0].addr, iset[idx].addr, L | |
idx += L | |
when ShowInstruction: | |
echo "Send(", str, ", ", argN, ")" | |
echo "-- entering ", str | |
var args = newSeq[Object](argN) | |
#echoCode str | |
let H = <argN | |
for i in 0 .. H: | |
args[H-i] = pop() | |
let recv = pop() | |
#echoCode frame.sp | |
let (bc,msg) = recv.findMessage(str) | |
if msg.isNil: | |
recv.printcomponents | |
echo "msg is nil ($#)" % str | |
else: | |
## create the context for the message, fill in its arguments | |
## link the context to the current context, make the new context | |
## active | |
## when the new context exits it will return to this context | |
## and push its result on the stack | |
let ctx = createContext(msg, bc) | |
ctx.dataPtr(Context).caller = activeContext | |
let bc = ctx.getComponent(ctx.ty.components.high) | |
for i in 0 .. H: | |
bc.slotVar(i) = args[i] | |
# let arg_locals = cast[ptr UncheckedArray[Object]]( | |
# ctx.dat[ctx.ty.components[ctx.ty.components.high][0]].addr | |
# ) | |
# for i in 0 .. H: | |
# arg_locals[i] = args[i] | |
self.setActiveContext ctx | |
else: | |
echo "unknown opcode ", op.ord, " (", op.Instr , ")" | |
quit 1 | |
thisContext.ip = idx | |
echo op.Instr, " next IP here: ", thisContext.ip, "/", thisContext.highIP | |
when defined(Debug) or defined(ShowStack): | |
echo "stack.len = ", thisStack.len | |
proc executorForContext* (ctx:Object): Object = | |
result = aggxExec.instantiate | |
result.dataPtr(Exec).rootContext = ctx | |
result.dataPtr(Exec).activeContext = ctx | |
proc send* (recv:Object; msg:string; args:varargs[Object]): Object = | |
let (bc,msg) = recv.findMessage(msg) | |
if not bc.isValid: return | |
# validate that msg has args.len args | |
if msg.dataPtr(CompiledMethod).args.len != args.len: | |
return nil | |
# instantiate the context | |
let ctx = createContext(msg, bc) | |
if ctx.isNil: | |
echo "ctx is nil??" | |
# set the argument in the slots of the context ._. | |
if args.len > 0: | |
let idx = ctx.ty.components.high | |
let (offs, comp) = ctx.ty.components[idx] | |
let nSlots = comp.slots.len | |
if nSlots < args.len: return nil | |
# not enough slots to hold arguments? check here could be smarter | |
let slots = cast[ptr UncheckedArray[Object]](ctx.dat[offs].addr) | |
for i in 0 .. args.high: | |
slots[i] = args[i] | |
# instantiate and execute a vm | |
var ticks = 0 | |
let o = executorForContext(ctx) | |
let exe = o.dataPtr(Exec) | |
while exe.isActive: | |
o.tick | |
ticks += 1 | |
result = exe.result | |
wdd: echo "TICKS: ", ticks | |
defineMessage(cxInt, "+") do (other): | |
let other_int = other.dataVar(int) | |
result = asObject(this.asVar(int) + other_int) | |
defineMessage(cxInt, "print") do: | |
result = asObject($ this.asVar(int)) | |
defineMessage(cxString, "print") do: | |
result = self | |
defineMessage(cxObj, "asString") do: | |
result = self.send("print") | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cmodel2, stdobj2 | |
defineMessage(cxExec, "tick") do: | |
# let exe = this.asPtr(Exec) | |
# exe.tick | |
tick(self) | |
## wrap InstrBuilder here | |
type Compiler* = object | |
let | |
cxCompiler* = typeComponent(Compiler) | |
aggxCompiler* = aggregate(cxCompiler, cxObj) | |
cxCompiler.aggr = aggxCompiler | |
defineMessage(cxCompiler, "compile:") do (src): | |
discard | |
import endians | |
proc serialize* (some:int; builder:var InstrBuilder) = | |
let i = builder.index | |
builder.addNullBytes sizeof(some) | |
let dest = builder.iset[i].addr | |
var some = some | |
when sizeof(some) == 4: | |
bigEndian32(dest, some.addr) | |
elif sizeof(some) == 8: | |
bigEndian64(dest, some.addr) | |
else: | |
static: | |
assert false, "wat the size of int is on your masheen omg fix" | |
proc unserialize* (some:var int; source:RawBytes): int = | |
when sizeof(some) == 4: | |
bigEndian32(some.addr, source) | |
result = 4 | |
elif sizeof(some) == 8: | |
bigEndian64(some.addr, source) | |
result = 8 | |
defineMessage(cxInt, "loadFromRaw:") | |
do (byteSrc): | |
let dat = byteSrc.dataPtr(RawBytes) | |
let my_int = this.asPtr(int) | |
let len_read = my_int[].unserialize dat[] | |
assert len_read == sizeof(int) | |
result = objInt(len_read) | |
import kwdgrammar | |
defPrimitiveComponent(ASTNode,Node) | |
defineMessage(cxASTNode, "print") | |
do: | |
asObject($ this.asVar(Node)) | |
proc compileNode (builder: var InstrBuilder; node: Node) = | |
case node.kind | |
of NK.IntLiteral: | |
builder.pushPOD node.i | |
of NK.Message: | |
# (msg recv arg0 arg1 ...) | |
let msg = node.sub[0].str | |
# recv | |
builder.compileNode node.sub[1] | |
for i in 2 .. high(node.sub): | |
# args | |
builder.compileNode node.sub[i] | |
builder.send msg, len(node.sub)-2 | |
of NK.Block: | |
var new_builder = initInstrBuilder() | |
for i,s in node.stmts: | |
new_builder.compileNode s | |
if i < high(node.stmts): new_builder.pop | |
var iseq = new_builder.done | |
builder.pushBlock node.args, node.locals, iseq | |
else: | |
echo "not ready to compile ", node | |
quit 1 | |
proc compileExpr* (str:string): Object = | |
let match = Expression().match(str) | |
if match.kind != mNodes: | |
echo match | |
return | |
assert match.nodes.len == 1 | |
let node = match.nodes[0] | |
var builder = initInstrBuilder() | |
builder.compileNode node | |
# creates a CompiledMethod ready to be executed | |
result = aggxCompiledMethod.instantiate | |
result.dataVar(CompiledMethod) = initCompiledMethod(builder.done, args=[], locals=[]) | |
# result = aggregate( | |
# dynaComponent("context", "parent", "code"), | |
# cxBlock, cxObj | |
# ).instantiate | |
# result.dataVar(Block).iset = iset | |
# discard result.send("code:", asObject(node)) | |
proc execute* (expresion:string): Object = | |
let meth = compileExpr(expresion) | |
if meth.isNil: | |
echo "failed to compile ", expresion | |
return | |
let ctx = createContext(meth, BoundComponent(self: nil, idx: 1)) | |
let o_exe = executorForContext(ctx) | |
let exe = o_exe .dataPtr(Exec) | |
while exe.isActive: | |
#exe.tick | |
tick(o_exe) | |
result = exe.result | |
proc createBlockContext (blck, caller:Object): Object = | |
result = instantiate aggregate( | |
cxStack, cxContext | |
) | |
let bl = blck.dataPtr(Block) | |
let ctx = result.dataPtr(Context) | |
ctx.caller = caller | |
ctx.ip = bl.ipStart | |
ctx.highIP = bl.ipEnd | |
ctx.instrs = bl.meth | |
#blck.printComponents | |
defineMessage(cxBlock, "value") do: | |
let ctx = createBlockContext(self, context) | |
# let ctx = createContext(msg, bc) | |
context.dataPtr(Context).exec.setActiveContext(ctx) | |
# type | |
# Context* = object | |
# sender*, pc*,sp*: Object | |
# blck*: Object # compiledmethod or block | |
## pc points to bytecode inside the enclosing method | |
## limit is the highest bytecode | |
# type | |
# Frame* = object | |
# context*: Object | |
# blck*: Object | |
# ip, sp*: int | |
# entry*: Object | |
# defPrimitiveComponent Frame,Frame | |
# type | |
# Process* = object | |
# #stack*: seq[Object] | |
# callstack*: seq[Object] | |
# let cxProcess* = typeComponent(Process) | |
# let aggxProcess* = aggregate(cxProcess, cxObj) | |
# defineMessage(cxProcess, "init") do: | |
# this.asVar(Process).stack.newSeq 0 | |
# this.asVar(Process).callstack.newSeq 0 | |
# proc activeFrame* (some: Process): Object = some.callstack[^1] | |
# proc safeActiveFrame* (some: Process): Object = | |
# (if some.callstack.len > 0: some.callstack[some.callstack.high] else: nil) | |
# defineMessage(cxProcess, "send:to:with:") | |
# do (msg,obj,args): | |
# defineMessage(cxProcess, "activeFrame") do: | |
# this.asVar(Process).safeActiveFrame | |
# defineMessage(cxProcess, "activateBlock:") do (blck): | |
# let context = blck.send("instantiate") | |
# let parent = self.asVar(Process).safeActiveFrame | |
# var fr = Frame( | |
# context: parent, | |
# ip: 0, | |
# sp: if parent.isNil: 0 else: parent.asVar(Frame).sp | |
# ) | |
# defineMessage(cxProcess, "pushFrame:") do (ctx): | |
# this.asVar(Process).callstack.add fr.asObject | |
# proc newProcessFromBlock* (blck:Object):Object = | |
# result = aggxProcess.instantiate | |
# discard result.send("init") | |
# discard result.send("activateBlock:", blck) | |
# var ms: MessageSearch | |
# let ty = e.safeType | |
# ms.continueFrom = high(ty.components) | |
# if ty.findMessage(msg, ms): | |
# ms.bound.self = e | |
# result = ms.msg(ms.bound, args) | |
# else: | |
# echo "failed to find `",msg.repr,"` on entity ", cast[int](e) | |
# print_components(e) | |
# iterator multicast* (e:Object; msg:string; args:varargs[Object]): Object = | |
# var ms: MessageSearch | |
# let ty = e.safeType | |
# ms.continueFrom = high(ty.components) | |
# while ty.findMessage(msg, ms): | |
# ms.bound.self = e | |
# yield ms.msg(ms.bound, args) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cmodel2, vm2, stdobj2, tables | |
import kwdgrammar | |
const test2 = "[1] value" | |
let ob = execute(test2) | |
ob.printcomponents | |
echo "---------------------------------" | |
let ob2 = ob.send("print") | |
ob2.printcomponents |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment