diff --git a/mast/capn/deCapnKit.mt b/mast/capn/deCapnKit.mt new file mode 100644 index 00000000..d99f6289 --- /dev/null +++ b/mast/capn/deCapnKit.mt @@ -0,0 +1,138 @@ +import "lib/capn" =~ [=> makeMessageReader :DeepFrozen] +import "lib/serial/DEBuilderOf" =~ [=> DEBuilderOf :DeepFrozen] +import "capn/montevalue" =~ [=> reader, => makeWriter] + +exports (deCapnKit) + + +def Node :DeepFrozen := Map[Str, Any] # Named arguments to makeDataExpr +def Root :DeepFrozen := Bytes # capn message + +def Int32 :DeepFrozen := -(2 ** 31)..!2 ** 31 + + +object deCapnKit as DeepFrozen: + to makeBuilder(): + + def Literal := Any[Int, Double, Str, Char] + + var nextTemp :Int := 0 + var varReused := [].asMap().diverge() + def w := makeWriter() + + def expr(nargs :Map): + return M.call(w, "makeDataExpr", [], nargs) + + return object deCapnBuilder implements DEBuilderOf[Node, Root]: + method getNodeType() :Near: + Node + method getRootType() :Near: + Root + + to buildRoot(root :Node) :Bytes: + return w.dump(expr(root)) + + to buildLiteral(it :Literal) :Node: + def lit := switch (it) { + match i :Int32 { ["int" => w.makeInteger("int32" => i)] } + match i :Int { ["int" => w.makeInteger("bigint" => `${i}`)] } + match x :Double { ["double" => x] } + match s :Str { ["str" => s] } + match ch: Char { ["char" => ch.asInteger()] } + match bs: Bytes { ["bytes" => bs] } + } + return ["literal" => lit] + + to buildImport(varName :Str) :Node: + return ["noun" => varName] + + to buildIbid(tempIndex :Int) :Node: + if (! (tempIndex < nextTemp)): + throw(`assertion failure: $tempIndex < $nextTemp`) + varReused[tempIndex] := true + # traceln(`buildIbid: $tempIndex marked reused.`) + return ["ibid" => tempIndex] + + to buildCall(rec :Node, verb :Str, args :List[Node], nargs :Map[Str, Node]) :Node: + def message := ["verb" => verb, + "args" => args, + "namedArgs" => [for k => v in (nargs) ["key" => k, "value" => v]] + ] + return ["call" => ["receiver" => expr(rec), "message" => message]] + + to buildDefine(rValue :Node) :Pair[Node, Int]: + def tempIndex := nextTemp + nextTemp += 1 + varReused[tempIndex] := false + def defNode := ["defExpr" => ["index" => tempIndex, "rValue" => expr(rValue)]] + return [defNode, tempIndex] + + to buildPromise() :Int: + def promIndex := nextTemp + nextTemp += 2 + varReused[promIndex] := false + varReused[promIndex + 1] := false + return promIndex + + to buildDefrec(resIndex :Int, rValue :Node) :Node: + def promIndex := resIndex - 1 + # traceln(`buildDefrec: $promIndex reused? ${varReused[promIndex]}.`) + return if (varReused[promIndex]): + # We have a cycle + ["defRec" => ["promIndex" => promIndex, "rValue" => expr(rValue)]] + else: + # No cycle + ["defExpr" => ["index" => promIndex, "rValue" => expr(rValue)]] + + to recognize(msg :Root, builder) :(def _Root := builder.getRootType()): + def Node := builder.getNodeType() + + def build(expr): + return switch (expr._which()) { + match ==0 { # literal + def lit := expr.literal() + switch (lit._which()) { + match ==0 { + def litInt := lit.int() + switch (litInt._which()) { + match ==0 { litInt.int32() } + match ==1 { _makeInt(litInt.bigint()) } + } + } + match ==1 { lit.double() } + match ==2 { lit.str() } + match ==3 { '@' - 64 + lit.char() } + match ==4 { lit.bytes() } + } + } + match ==1 { + builder.buildImport(expr.noun()) + } + match ==2 { + builder.buildIbid(expr.ibid()) + } + match ==3 { + def call := expr.call() + def msg := call.message() + def args := [for arg in (msg.args()) build(arg)] + def nargs := [for n => arg in (msg.namedArgs()) n => build(arg)] + builder.buildCall(build(call.receiver()), msg.verb(), args, nargs) + } + match ==4 { # defExpr + # ISSUE: we're not using the de.index() field. Is it needed? + def de := expr.defExpr() + def [val, _tempIndex] := builder.buildDefine(build(de.rValue())) + val + } + match ==5 { # defRec + # ISSUE: we're not using the dr.promIndex() field. Is it needed? + def dr := expr.defRec() + def promIndex := builder.buildPromise() + return builder.buildDefrec(promIndex + 1, build(dr.rValue())) + } + match other { throw(`not implemented: ${other}`) } + } + + def expr := reader.DataExpr(makeMessageReader(msg).getRoot()) + + return builder.buildRoot(build(expr)) diff --git a/mast/capn/montevalue.capnp b/mast/capn/montevalue.capnp index e87c9323..16fc1f18 100644 --- a/mast/capn/montevalue.capnp +++ b/mast/capn/montevalue.capnp @@ -1,23 +1,47 @@ @0xe2597668ccd0fb6a; -struct MonteValue { - null @0 :Void; - bool @1 :Bool; - int @2 :Int32; - bigint @3 :Data; - double @4 :Float64; - bytes @5 :Data; - text @6 :Text; - list @7 :List(MonteValue); -} +struct DataExpr { + struct Integer { + union { + int32 @0 :Int32; + bigint @1 :Text; + } + } -struct NamedArg { - key @0 :Text; - value @1 :MonteValue; -} + struct NamedArg { + key @0 :Text; + value @1 :DataExpr; + } + + union { + literal :union { + int @0 :Integer; + double @1 :Float64; + str @2 :Text; + char @3 :Int32; + bytes @4 :Data; + } + + noun @5 :Text; + ibid @6 :Int32; + + call :group { + receiver @7 :DataExpr; + message :group { + verb @8 :Text; + args @9 :List(DataExpr); + namedArgs @10 :List(NamedArg); + } + } + + defExpr :group { + index @11 :Int32; + rValue @12 :DataExpr; + } -struct MonteMessage { - verb @0 :Text; - args @1 :List(MonteValue); - namedArgs @2 :List(NamedArg); + defRec :group { + promIndex @13 :Int32; + rValue @14 :DataExpr; + } + } } diff --git a/mast/fun/uneval.mt b/mast/fun/uneval.mt new file mode 100644 index 00000000..f6f4100e --- /dev/null +++ b/mast/fun/uneval.mt @@ -0,0 +1,51 @@ +import "lib/serial/deSubgraphKit" =~ [=>deSubgraphKit :DeepFrozen] +import "lib/serial/deMNodeKit" =~ [=>deMNodeKit :DeepFrozen] +import "lib/serial/deSrcKit" =~ [=>deSrcKit :DeepFrozen] +import "capn/deCapnKit" =~ [=>deCapnKit :DeepFrozen] +exports (main) + +def test(actual, expected) as DeepFrozen: + if (actual == expected): + trace(".") + else: + traceln("") + traceln(`want: $expected`) + traceln(` got: $actual`) + + +def main(_argv) :Vow[Int] as DeepFrozen: + def s := M.toString + + def data := [null, true, false, 1, 2 ** 32 + 123, + # 2.54 messageWriter lacks writeFloat64 + # https://github.com/monte-language/typhon/issues/206 + "abc",'A', + # b`hello` TODO: Problem: Can't uneval + ] + def dataMsg := deSubgraphKit.recognize(data, + deCapnKit.makeBuilder()) + def data2 := deCapnKit.recognize(dataMsg, deSubgraphKit.makeBuilder()) + test(data2, data) + + def x := [1, x, 3] + test(s(x), "[1, <**CYCLE**>, 3]") + + test(s(deSubgraphKit.recognize(x, deSrcKit.makeBuilder())), + "def t_0 := [def t_2 := 1, t_0, def t_4 := 3]") + + def ast := deSubgraphKit.recognize(x, deMNodeKit.makeBuilder()).canonical() + # TODO: def makeKernelECopyVisitor := elang_uriGetter("visitors.KernelECopyVisitor") + test(ast, m`def [t_0 :Any, t_1 :Any] := Ref.promise();$\ + t_1.resolve(_makeList.run(def t_2 :Any := 1, t_0, def t_4 :Any := 3));$\ + t_0`.canonical() :(astBuilder.getAstGuard())) + + def output := deSubgraphKit.recognize(x, deCapnKit.makeBuilder()) + + def xx := deCapnKit.recognize(output, deSubgraphKit.makeBuilder()) + test(s(xx), "[1, <**CYCLE**>, 3]") + + test(output.size(), 472) + test(output.contains(b`_makeList`), true) + # def stdout := stdio.stdout() + # return when (stdout(output), stdout<-complete()) -> { 0 } + return 0 diff --git a/mast/lib/serial/DEBuilderOf.mt b/mast/lib/serial/DEBuilderOf.mt new file mode 100755 index 00000000..b5de787e --- /dev/null +++ b/mast/lib/serial/DEBuilderOf.mt @@ -0,0 +1,104 @@ +#!/usr/bin/env rune + +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +# module "org.erights.e.elib.serial.DEBuilderOf" +import "lib/serial/guards" =~ [=>Guard :DeepFrozen] +exports (DEBuilderOf) + +# /** +# * Data-E is the subset of E used for serializing a subgraph by unevaling to an +# * expression. +# * +# * @see Unserialization as Expression Evaluation. +# * @author Mark S. Miller +# */ +def DEBuilderOf.get(Node :Guard, _Root :Guard) :Guard as DeepFrozen { + + interface _DEBuilder { + + # /** + # * What's the actual type corresponding to the Node type parameter? + # */ + to getNodeType() :Guard + + # /** + # * What's the actual type corresponding to the Root type parameter? + # */ + to getRootType() :Guard + + # /** + # * An opportunity to do some post-optimizations, writing out trailers, + # * and closing. + # *

+ # * [root] => buildRoot() + # *

+ # * This must appear exactly once at the end. + # */ + to buildRoot(root :Node) :Node + + # /** + # * For literal values -- ints, float64s, chars, or bare Strings. + # *

+ # * [] => buildLiteral(value) => [value] + # */ + to buildLiteral(value :Any[Int, Double, Char, Str]) :Node + + # /** + # * Generates a use-occurrence of a named variable. + # *

+ # * [] => buildImport(varName) => [value] + # *

+ # * Load the value of the named variable from the scope. + # */ + to buildImport(varName :Str) :Node + + # /** + # * Generates a use-occurrence of an temp variable. + # *

+ # * [] => buildIbid(tempIndex) => [value] + # *

+ # * Load the value of the temp variable at that index. + # */ + to buildIbid(tempIndex :Int) :Node + + # /** + # * Generates a call-expression. + # *

+ # * [rec, arg0,...] => buildCall(verb,arity) => [result] + # */ + to buildCall(rec :Node, verb :Str, args :List[Node]) :Node + + # /** + # * Allocates the next tempIndex, defines it to hold the value of + # * rValue, and return a pair of the generated definition and the index + # * of the new temp variable. + # *

+ # * [rValue] => buildDefine() => [rValue] + # *

+ # * If rValue needs to use the new variable, use + # * buildPromise/buildDefrec instead. + # */ + to buildDefine(rValue :Node) :Pair[Node, Int] + + # /** + # * Like a forward variable declaration in E (def varName). + # *

+ # * [] => buildPromise() => [] + # *

+ # * Allocates the next two temp variables. Defines them to hold a + # * promise and its Resolver, respectively. + # */ + to buildPromise() :Int + + # /** + # * Resolves a promise to the value of rValue. + # *

+ # * [rValue] => buildDefrec(resolverIndex) => [rValue] + # */ + to buildDefrec(resolverIndex :Int, rValue :Node) :Node + } +} diff --git a/mast/lib/serial/deASTKit.emaker b/mast/lib/serial/deASTKit.emaker new file mode 100755 index 00000000..4709e5c2 --- /dev/null +++ b/mast/lib/serial/deASTKit.emaker @@ -0,0 +1,214 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +def Term := +def DEBuilderOf := + +/** + * A Data-E AST is (XXX need explanation) + * + * @author Mark S. Miller + */ +def deASTKit { + + /** + * Uses deASTKit as a filter (in the Unix pipe sense) to make a + * builder like wrappedBuilder, but in which deASTKit's simplifications + * are applied first. + */ + to wrap(wrappedBuilder) :near { + def WrappedRoot := wrappedBuilder.getRootType() + + def wrapper extends deASTKit.makeBuilder() { + to getRootType() :near { WrappedRoot } + to buildRoot(root :Term) :WrappedRoot { + def astRoot := super.buildRoot(root) + deASTKit.recognize(astRoot, wrappedBuilder) + } + } + } + + /** + * Makes a simplified Data-E representation. + *

+ * By "simplified", we mean that some optimization occur at build time, so + * the result of recognizing what's built may be smaller and simpler than + * the original. In particular, unused temp variables are removed, and + * Defrec expressions where there isn't actually a cyclic use are turned + * into Define expressions. The final simplification step -- repacking the + * remaining temporary indicies to remove gaps -- happens during + * recognition rather than building, since the builder we're calling is + * responsible for generating packed temp indices anyway. + *

+ * Builds a tree by argument passing, rather than using a stack. But still + * relies on post-order in order to notice variable usage. + */ + to makeBuilder() :near { + + # The index of the next temp variable + var nextTemp := 0 + + # Which temp variables have been reused? + def varReused := [].diverge(boolean) + + def deASTBuilder implements DEBuilderOf(Term, Term) { + + to getNodeType() :near { Term } + to getRootType() :near { Term } + + /** + * Return the result after some simplifying transformations. + *

+ * As we've been building the argument root, we kept track of + * which temp variables are actually used. For those that were + * defined but not actually used, remove the definition leaving + * the simplified rValue. + *

+ * We can ignore defrec(..) and only need to look for define(..) + * since, if the variable isn't used, the defrec(..) would already + * have been simplified into a define(..). + */ + to buildRoot(root :Term) :Term { + def simplify(ast :Term) :Term { + if (ast =~ term`define(@tempI, @rValue)`) { + if (! varReused[tempI.getOptData()]) { + return simplify(rValue) + } + } + # generic recursive case + def term`@tag(@args*)` := ast + var simpArgs := [] + for arg in args { + simpArgs with= simplify(arg) + } + term`$tag($simpArgs*)` + } + simplify(root) + } + + to buildLiteral(value) :Term { + if (value =~ str :String) { + term`.String.$str` + } else { + term`$value` + } + } + + to buildImport(varName :String) :Term { + term`import(.String.$varName)` + } + + to buildIbid(tempIndex :int) :Term { + require(tempIndex < nextTemp, thunk { + `internal: $tempIndex must be < $nextTemp` + }) + varReused[tempIndex] := true + term`ibid($tempIndex)` + } + + to buildCall(rec :Term, verb :String, args :Term[]) :Term { + term`call($rec, .String.$verb, [$args*])` + } + + to buildDefine(rValue :Term) :[Term, int] { + def tempIndex := nextTemp + nextTemp += 1 + varReused[tempIndex] := false + def defExpr := term`define($tempIndex, $rValue)` + [defExpr, tempIndex] + } + + to buildPromise() :int { + def promIndex := nextTemp + nextTemp += 2 + varReused[promIndex] := false + varReused[promIndex+1] := false + promIndex + } + + /** + * If the temp variable wasn't actually used during the building + * of rValue, build a define(..) instead of a defrec(..). + */ + to buildDefrec(resIndex :int, rValue :Term) :Term { + def promIndex := resIndex-1 + + if (varReused[promIndex]) { + # We have a cycle + term`defrec($promIndex, $rValue)` + } else { + # No cycle + term`define($promIndex, $rValue)` + } + } + } + } + + + to recognize(ast :Term, builder) :(def Root := builder.getRootType()) { + + def Node := builder.getNodeType() + + # Maps from represented (unpacked) to reported (packed) temporary + # variable indicies. + def tempIndices := [].asMap().diverge() + + def subRecognize(sub :Term) :Node { + + switch (sub) { + + match term`.int.@i` { + builder.buildLiteral(i.getOptData()) + } + match term`.float64.@f` { + builder.buildLiteral(f.getOptData()) + } + match term`.char.@c` { + builder.buildLiteral(c.getOptData()) + } + match term`.String.@str` { + builder.buildLiteral(str.getOptString()) + } + + match term`import(@varName)` { + builder.buildImport(varName.getOptString()) + } + match term`ibid(@tempI)` { + def newTempIndex := tempIndices[tempI.getOptData()] + builder.buildIbid(newTempIndex) + } + match term`call(@rec, @verb, [@args*])` { + def recNode := subRecognize(rec) + var argNodes := [] + for arg in args { + argNodes with= subRecognize(arg) + } + builder.buildCall(recNode, verb.getOptString(), argNodes) + } + match term`define(@tempI, @rValue)` { + def rValueNode := subRecognize(rValue) + def [resultNode, newTempIndex] := + builder.buildDefine(rValueNode) + tempIndices.put(tempI.getOptData(), + newTempIndex, + true) + resultNode + } + match term`defrec(@promI, @rValue)` { + def promIndex := promI.getOptData() + def resIndex := promIndex+1 + def newPromIndex := builder.buildPromise() + def newResIndex := newPromIndex+1 + tempIndices.put(promIndex, newPromIndex, true) + tempIndices.put(resIndex, newResIndex, true) + + def rValueNode := subRecognize(rValue) + builder.buildDefrec(newResIndex, rValueNode) + } + } + } + builder.buildRoot(subRecognize(ast)) + } +} diff --git a/mast/lib/serial/deAssemblyKit.emaker b/mast/lib/serial/deAssemblyKit.emaker new file mode 100755 index 00000000..fedb6094 --- /dev/null +++ b/mast/lib/serial/deAssemblyKit.emaker @@ -0,0 +1,196 @@ +#!/usr/bin/env rune + +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +def makeTextWriter := +def Iteratable := +def makeTermParser := +def StringBuffer := + +def DEBuilderOf := +def DEStringBuilder := DEBuilderOf(void, String) +def DEStreamBuilder := DEBuilderOf(void, void) + +/** + * DataECode assembly code is the readable form of the DataECode instruction + * set. + *

+ * A kit is a set of utilities for manipulating things of a particular kind. + * The deAssemblyKit will provide for recognition (assembly) of this + * assembly code, and does provide for building (disassembly) for producing + * assembly listings. + * + * @see Unserialization as Expression Evaluation. + * @author Mark S. Miller + */ +def deAssemblyKit { + + /** + * Makes a builder whose output (the result of buildRoot()) is a string + * in DataECode assembly code. + *

+ * This is conventionally known as "disassembly". + * Note that each line is written in term-tree syntax, and encodes one + * instruction. + */ + to makeBuilder() :DEStringBuilder { + + def [out :TextWriter, sb :StringBuffer] := + makeTextWriter.makeBufferingPair() + + def subBuilder := deAssemblyKit.makeStreamBuilder(out) + + def wrappingBuilder extends subBuilder implements DEStringBuilder { + + to getRootType() :near { String } + + to buildRoot(root) :String { + super.buildRoot(root) + out.close() + sb.snapshot() + } + } + } + + /** + * Makes a builder writing the same assembly code, but writing into the + * provided stream rather than returning a result. + */ + to makeStreamBuilder(out :TextWriter) :DEStreamBuilder { + + # The index of the next temp variable + var nextTemp := 0 + + def deAssemblyBuilder implements DEStreamBuilder { + + to getNodeType() :near { void } + to getRootType() :near { void } + + to buildRoot(_) { + out.println(`OP_ROOT`) + out.flush() + } + + to buildLiteral(value) { + switch (value) { + match i :int { + if (i >= 0) { + out.println(`OP_LIT_WHOLENUM($i)`) + } else { + out.println(`OP_LIT_NEGINT(${-i})`) + } + } + match f :float64 { + out.println(`OP_LIT_FLOAT64($f)`) + } + match c :char { + out.println(`OP_LIT_CHAR(${E.toQuote(c)})`) + } + match str :String { + out.println(`OP_LIT_STRING(${E.toQuote(str)})`) + } + } + } + + to buildImport(varName :String) { + out.println(`OP_IMPORT(${E.toQuote(varName)})`) + } + + to buildIbid(tempIndex :int) { + out.println(`OP_IBID($tempIndex)`) + } + + to buildCall(_, verb :String, args :void[]) { + out.println(`OP_CALL(${E.toQuote(verb)}, ${args.size()})`) + } + + to buildDefine(_) :[void, int] { + def tempIndex := nextTemp + nextTemp += 1 + out.println(`OP_DEFINE # t_$tempIndex`) + [null, tempIndex] + } + + to buildPromise() :int { + def promIndex := nextTemp + def resIndex := promIndex + 1 + nextTemp += 2 + out.println(`OP_PROMISE # [t_$promIndex, t_$resIndex]`) + promIndex + } + + to buildDefrec(resIndex :int, _) { + out.println(`OP_DEFREC($resIndex)`) + } + } + } + + /** + * Parse an assembly listing, and invoke a DEBuilder to convert to some + * other representation. + */ + to recognize(listing :String, builder) :(builder.getRootType()) { + deAssemblyKit.recognizeStream(listing.split("\n"), builder) + } + + /** + * + */ + to recognizeStream(inp :Iteratable, builder) :(builder.getRootType()) { + + def stack := [].diverge() + + for line in inp { + switch (makeTermParser(line)) { + match term`OP_ROOT` { + return builder.buildRoot(stack.pop()) + } + match term`OP_LIT_WHOLENUM(.int.@i)` { + stack.push(builder.buildLiteral(i.getOptData())) + } + match term`OP_LIT_NEGINT(.int.@i)` { + stack.push(builder.buildLiteral(-(i.getOptData()))) + } + match term`OP_LIT_FLOAT64(.float64.@f)` { + stack.push(builder.buildLiteral(f.getOutData())) + } + match term`OP_LIT_CHAR(.char.@c)` { + stack.push(builder.buildLiteral(c.getOptData())) + } + match term`OP_LIT_STRING(.String.@str)` { + stack.push(builder.buildLiteral(str.getOptString())) + } + match term`OP_IMPORT(.String.@varName)` { + stack.push(builder.buildImport(varName.getOptString())) + } + match term`OP_IBID(.int.@tempIndex)` { + stack push(builder.buildIbid(tempIndex.getOptData())) + } + match term`OP_CALL(.String.@verb, .int.@arity)` { + def stackSize := stack.size() + def firstArgIndex := stackSize - arity.getOptData() + def args := stack.removeRun(firstArgIndex, stackSize) + def rec := stack.pop() + stack.push(builder.buildCall(rec, + verb.getOptString(), + args)) + } + match term`OP_DEFINE` { + # buildDefine normally hands back its argument, in which + # case this code does not effect the stack. + stack.push(builder.buildDefine(stack.pop())[0]) + } + match term`OP_PROMISE` { + builder.buildPromise() + } + match term`OP_DEFREC(.int.@resIndex)` { + stack.push(builder.buildDefrec(resIndex.getOptData(), + stack.pop())) + } + } + } + } +} diff --git a/mast/lib/serial/deBytecodeKit.emaker b/mast/lib/serial/deBytecodeKit.emaker new file mode 100755 index 00000000..4663cff3 --- /dev/null +++ b/mast/lib/serial/deBytecodeKit.emaker @@ -0,0 +1,215 @@ +#!/usr/bin/env rune + +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +def DEBuilderOf := + +def DataOutput := +def makeDataOutputStream := +def makeByteArrayOutputStream := + +def DataInput := +def makeDataInputStream := +def makeByteArrayInputStream := + +def byte := .getTYPE() + +def OP_ROOT := 1 +def OP_LIT_WHOLENUM := 2 +def OP_LIT_NEGINT := 3 +def OP_LIT_FLOAT64 := 4 +def OP_LIT_CHAR := 5 +def OP_LIT_STRING := 6 +def OP_IMPORT := 7 +def OP_IBID := 8 +def OP_CALL := 9 +def OP_DEFINE := 10 +def OP_PROMISE := 11 +def OP_DEFREC := 12 + +/** + * DataECode bytecodes is the compact fast form of the DataECode instruction + * set. + *

+ * A kit is a set of utilities for manipulating things of a particular kind. + * The deBytecodeKit provides for recognition (dispatch) and building + * (code generation) of this instruction set. + * + * @see Unserialization as Expression Evaluation. + * @author Mark S. Miller + */ +def deBytecodeKit { + + /** + * Makes a builder whose output (the result of buildRoot()) is a byte + * array holding a well-formed sequence of instructions. + */ + to makeBuilder() :near { + + def baos := makeByteArrayOutputStream() + def dos := makeDataOutputStream(baos) + + def subBuilder := deBytecodeKit.makeStreamBuilder(dos) + + def wrappingBuilder extends subBuilder \ + implements DEBuilderOf(void, byte[]) { + + to getRootType() :near { byte[] } + + to buildRoot(root) :byte[] { + super.buildRoot(root) + dos.close() + baos.toByteArray() + } + } + } + + to makeStreamBuilder(dos :DataOutput) :near { + + # The index of the next temp variable + var nextTemp := 0 + + def deBytecodeBuilder implements DEBuilderOf(void, void) { + + to getNodeType() :near { void } + to getRootType() :near { void } + + to buildRoot(_) :any { + dos.writeByte(OP_ROOT) + dos.flush() + } + + to buildLiteral(value) { + switch (value) { + match i :int { + if (i >= 0) { + dos.writeByte(OP_LIT_WHOLENUM) + dos.writeWholeNum(i) + } else { + dos.writeByte(OP_LIT_NEGINT) + dos.writeWholeNum(-i) + } + } + match f :float64 { + dos.writeByte(OP_LIT_FLOAT64) + dos.writeDouble(f) + } + match c :char { + dos.writeByte(OP_LIT_CHAR) + dos.writeChar(c) + } + match str :String { + dos.writeByte(OP_LIT_STRING) + dos.writeUTF(str) + } + } + } + + to buildImport(varName :String) { + dos.writeByte(OP_IMPORT) + dos.writeUTF(varName) + } + + to buildIbid(tempIndex :int) { + dos.writeByte(OP_IBID) + dos.writeWholeNum(tempIndex) + } + + to buildCall(_, verb :String, args :void[]) { + dos.writeByte(OP_CALL) + dos.writeUTF(verb) + dos.writeWholeNum(args.size()) + } + + to buildDefine(_) :[void, int] { + def tempIndex := nextTemp + nextTemp += 1 + dos.writeByte(OP_DEFINE) + [null, tempIndex] + } + + to buildPromise() :int { + def promIndex := nextTemp + def resIndex := promIndex + 1 + nextTemp += 2 + dos.writeByte(OP_PROMISE) + promIndex + } + + to buildDefrec(resIndex :int, _) { + dos.writeByte(OP_DEFREC) + dos.writeWholeNum(resIndex) + } + } + } + + /** + * + */ + to recognize(code :byte[], builder) :builder.getRootType() { + def bais := makeByteArrayInputStream(code) + def dis := makeDataInputStream(bais) + deBytecodeKit.recognizeStream(dis, builder) + } + + /** + * + */ + to recognizeStream(dis :DataInput, builder) :builder.getRootType() { + + def stack := [].diverge() + + while (true) { + switch (dis.readByte()) { + match ==OP_ROOT { + return builder.buildRoot(stack.pop()) + } + match ==OP_LIT_WHOLENUM { + stack.push(builder.buildLiteral(dis.readWholeNum())) + } + match ==OP_LIT_NEGINT { + stack.push(builder.buildLiteral(-(dis.readWholeNum()))) + } + match ==OP_LIT_FLOAT64 { + stack.push(builder.buildLiteral(dis.readDouble())) + } + match ==OP_LIT_CHAR { + stack.push(builder.buildLiteral(dis.readChar())) + } + match ==OP_LIT_STRING { + stack.push(builder.buildLiteral(dis.readUTF())) + } + match ==OP_IMPORT { + stack.push(builder.buildImport(dis.readUTF())) + } + match ==OP_IBID { + stack.push(builder.buildIbid(dis.readWholeNum())) + } + match ==OP_CALL { + def verb := dis.readUTF() + def arity := dis.readWholeNum() + def stackSize := stack.size() + def firstArgIndex := stackSize - arity + def args := stack.removeRun(firstArgIndex, stackSize) + def rec := stack.pop() + stack.push(builder.buildCall(rec, verb, args)) + } + match ==OP_DEFINE { + # buildDefine normally hands back its argument, in which + # case this code does not effect the stack. + stack.push(builder.buildDefine(stack.pop())[0]) + } + match ==OP_PROMISE { + builder.buildPromise() + } + match ==OP_DEFREC { + stack.push(builder.buildDefrec(dis.readWholeNum(), + stack.pop())) + } + } + } + } +} diff --git a/mast/lib/serial/deJSONKit.mt b/mast/lib/serial/deJSONKit.mt new file mode 100644 index 00000000..1f17bcd4 --- /dev/null +++ b/mast/lib/serial/deJSONKit.mt @@ -0,0 +1,76 @@ +import "guards" =~ [=>Tuple :DeepFrozen] +import "./elib/serial/DEBuilderOf" =~ [=> DEBuilderOf :DeepFrozen] +exports (deJSONKit) + +object deJSONKit as DeepFrozen { + to makeBuilder() { + # Fundamental Data-E Constructs + # as JSON-happy data structures + # http://wiki.erights.org/wiki/Data-E_in_JSON + def Expr + def Literal := Any[Int, Double, Str, Pair[Same["char"], Str]] + def Noun := Pair[Same["import"], Str] + def Ibid := Pair[Same["ibid"], Int] + # We'll leave the recursive bits to dynamic checks. + # Tuple[Same["call"], Expr, Str, List[Expr], Map[Str, Expr]] + def Call := Tuple[Same["call"], Any, Str, List , Map[Str, Any]] + # Tuple[Same["define"], Int, Expr] + def DefExpr := Tuple[Same["define"], Int, Any] + # Tuple[Same["defrec"], Int, Expr] + def DefRec := Tuple[Same["defrec"], Int, Any] + bind Expr := Any[Literal, Noun, Ibid, Call, DefExpr, DefRec] + + var nextTemp :Int := 0 + var varReused := [].asMap().diverge() + + return object deJSONBuilder implements DEBuilderOf[Expr, Expr] { + method getNodeType() :Near { Expr } + method getRootType() :Near { Expr } + + to buildRoot(root :Expr) :Expr { return root } + to buildLiteral(it :Literal) :Expr { return it } + to buildImport(varName :Str) :Expr { return ["import", varName] } + to buildIbid(tempIndex :Int) :Expr { + if (! (tempIndex < nextTemp)) { throw(`assertion failure: $tempIndex < $nextTemp`) } + varReused[tempIndex] := true + # traceln(`buildIbid: $tempIndex marked reused.`) + return ["ibid", tempIndex] + } + to buildCall(rec :Expr, verb :Str, args :List[Expr], nargs :Map[Str, Expr]) :Expr { + return ["call", rec, verb, args, nargs] + } + to buildDefine(rValue :Expr) :Pair[Expr, Int] { + def tempIndex := nextTemp + nextTemp += 1 + varReused[tempIndex] := false + def tempName := ["ibid", tempIndex] + # hmm... can we make this optimization locally? + def defExpr := if (rValue =~ Literal) { rValue } else { ["define", tempIndex, rValue] } + return [defExpr, tempIndex] + } + to buildPromise() :Int { + def promIndex := nextTemp + nextTemp += 2 + varReused[promIndex] := false + varReused[promIndex + 1] := false + return promIndex + } + to buildDefrec(resIndex :Int, rValue :Expr) :Expr { + def promIndex := resIndex - 1 + # traceln(`buildDefrec: $promIndex reused? ${varReused[promIndex]}.`) + return if (varReused[promIndex]) { + # We have a cycle + ["defrec", promIndex, rValue] + } else { + # No cycle + ["define", promIndex, rValue] + } + } + } + } + + to recognize() { + throw("@@not implemented") + #@@Char + } +} diff --git a/mast/lib/serial/deMNodeKit.mt b/mast/lib/serial/deMNodeKit.mt new file mode 100755 index 00000000..57b798e2 --- /dev/null +++ b/mast/lib/serial/deMNodeKit.mt @@ -0,0 +1,276 @@ +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +import "lib/serial/DEBuilderOf" =~ [=>DEBuilderOf :DeepFrozen] +exports (deMNodeKit) + +def EExpr :DeepFrozen := astBuilder.getExprGuard() +def FinalPattern :DeepFrozen := astBuilder.getPatternGuard() + +# TODO: def makeKernelECopyVisitor := elang_uriGetter("visitors.KernelECopyVisitor") + +def ANY :DeepFrozen := m`Any` + +object require as DeepFrozen { + to run (cond :Bool) { return require.run(cond, fn { null }) } + to run (cond :Bool, lose) { + if (! cond) { + throw(lose()) + } + } +} + +# /** +# * Data-E ENodes are the subset of possible Kernel-E ENode ASTs which could +# * result from applying the conventional E-to-Kernel-E expansion to the Data-E +# * subset of E. +# * +# * @author Mark S. Miller +# */ +object deMNodeKit as DeepFrozen { + + # /** + # * Makes a shrunk Kernel-E AST as the Data-E representation. + # *

+ # * By "shrunk", we mean that some optimization occur at build time, so that + # * the result of recognizing what's built may be smaller and simpler than + # * the original. In particular, unused temp variables are removed, and + # * Defrec expressions where there isn't actually a cyclic use are turned + # * into Define expressions. + # *

+ # * Builds a tree by argument passing, rather than using a stack. But still + # * relies on post-order in order to notice variable usage. + # */ + method makeBuilder() :Near { + + # The index of the next temp variable + var nextTemp := 0 + + # Which temp variables have been reused? + def varReused := [].diverge() # [].diverge(Bool) + + def asPattern(name :Str, "guard" => guard :EExpr := ANY) :FinalPattern { + return astBuilder.FinalPattern(astBuilder.NounExpr(name, null), guard, null) + } + + object deASTBuilder implements DEBuilderOf[EExpr, EExpr] { + + method getNodeType() :Near { EExpr } + method getRootType() :Near { EExpr } + + # /** + # * Return the result after some optimizing transformations. + # *

+ # * As we've been building the argument root, we kept track of + # * which variables are actually used. For those that were defined + # * by buildDef but not actually used, remove the definition + # * leaving the rValue. + # */ + method buildRoot(root :EExpr) :EExpr { + # which variables haven't been reused? + var badNames := [].asSet().diverge() + for tempIndex => wasReused in (varReused) { + if (! wasReused) { + badNames.include(`t_$tempIndex`) + } + } + badNames := badNames.snapshot() + + # remove definitions of non-reused variables + # TODO: when we have makeKernelECopyVisitor + # object simplify extends makeKernelECopyVisitor(simplify) { + # method visitDefineExpr(optOriginal, patt, rValue) :Any { + # if (patt =~ fp :FinalPattern) { + # def name := fp.optName() + # if (badNames.contains(name)) { + # return simplify(rValue) + # } + # } + # super.visitDefineExpr(optOriginal, patt, rValue) + # } + # } + + # simplify(root) + root + } + + method buildLiteral(value) :EExpr { + astBuilder.LiteralExpr(value, null) + } + + method buildImport(varName :Str) :EExpr { + astBuilder.NounExpr(varName, null) + } + + method buildIbid(tempIndex :Int) :EExpr { + require(tempIndex < nextTemp, fn { + `internal: $tempIndex must be < $nextTemp` + }) + varReused[tempIndex] := true + astBuilder.NounExpr(`t_$tempIndex`, null) + } + + method buildCall(rec :EExpr, verb :Str, args :List[EExpr], nargs :Map[Str, EExpr]) :EExpr { + def namedArgs := [for key => value in (nargs) ["key" => key, "value" => value]] + astBuilder.MethodCallExpr(rec, verb, args, namedArgs, null) + } + + method buildDefine(rValue :EExpr) :Pair[EExpr, Int] { + def tempIndex := nextTemp + nextTemp += 1 + varReused.push(false) + def tempPatt := asPattern(`t_$tempIndex`) + def defExpr := astBuilder.DefExpr(tempPatt, null, rValue, null) + [defExpr, tempIndex] + } + + method buildPromise() :Int { + def promIndex := nextTemp + nextTemp += 2 + varReused.push(false) + varReused.push(false) + promIndex + } + + # /** + # * If the temp variable wasn't actually used, build a define + # * instead. + # */ + method buildDefrec(resIndex :Int, rValue :EExpr) :EExpr { + def promIndex := resIndex-1 + def promPatt := asPattern(`t_$promIndex`) + + if (varReused[promIndex]) { + # We have a cycle + def promNoun := astBuilder.NounExpr(`t_$promIndex`, null) + def resPatt := asPattern(`t_$resIndex`) + def resNoun := astBuilder.NounExpr(`t_$resIndex`, null) + + # XXX Should we instead generate the same expansion + # generarated by the E parser? This would remove a + # recognizion case below. + m`def [$promPatt, $resPatt] := Ref.promise();$\ + $resNoun.resolve($rValue);$\ + $promNoun` + + } else { + # No cycle + astBuilder.DefExpr(promPatt, null, rValue, null) + } + } + } + } + + + method recognize(ast :EExpr, builder) :(def _Root := builder.getRootType()) { + + def Node := builder.getNodeType() + + def isTempName(varName :Str) :Bool { + if (varName =~ `t_@digits` && digits.size() >= 1) { + for digit in (digits) { + if (digit < '0' || digit > '9') { + return false + } + } + true + } else { + false + } + } + + def tempIndices := [].asMap().diverge() + + object visitor { + + method visitLiteralExpr(_, value) :Node { + builder.buildLiteral(value) + } + + method visitNounExpr(_, varName :Str) :Node { + if (isTempName(varName)) { + builder.buildIbid(tempIndices[varName]) + } else { + builder.buildImport(varName) + } + } + + method visitCallExpr(_, + rec :EExpr, verb :Str, args :EExpr[]) :Node { + def recNode := rec.welcome(visitor) + var argNodes := [] + for arg in (args) { + argNodes with= (arg.welcome(visitor)) + } + builder.buildCall(recNode, verb, argNodes) + } + + # /** + # * Kernel-E guarantees that rValue does not use the variables + # * defined by patt. + # */ + method visitDefineExpr(_, patt :FinalPattern, rValue :EExpr) :Node { + def varName :Str := patt.optName() + require(isTempName(varName)) + + def rValueNode := rValue.welcome(visitor) + def [resultNode, tempIndex] := builder.buildDefine(rValueNode) + tempIndices.put(varName, tempIndex, true) + resultNode + } + + # /** + # * The only use Data-E makes of this is for a defrec, so that's the + # * only case we need to recognize. + # */ + method visitSeqExpr(_, subs) :Node { + if (subs =~ [sub0, sub1, sub2]) { + # Recognize the cycle code we generate + + def m`def [@varPatt, @resPatt] := Ref.promise()` := sub0 + def m`@resNoun.resolve(@rightExpr)` := sub1 + def m`@varNoun` := sub2 + def varName := varNoun.name() + def resName := resNoun.name() + require(varPatt.optName() == varName) + require(resPatt.optName() == resName) + require(isTempName(varName)) + require(isTempName(resName)) + + def varIndex := builder.buildPromise() + def resIndex := varIndex +1 + tempIndices.put(varName, varIndex, true) + + def rValueNode := rightExpr.welcome(visitor) + builder.buildDefrec(resIndex, rValueNode) + + } else if (subs =~ [sub0, sub1, sub2, sub3]) { + # Recognize the cycle code generated by the E parser + + def m`def [@varPatt, @resPatt] := Ref.promise()` := sub0 + def m`def @rPatt := def @oPatt := @rightExpr` := sub1 + def m`@resNoun.resolve(@oNoun)` := sub2 + def m`@rNoun` := sub3 + def varName := varPatt.optName() + def resName := resNoun.name() + def rName := rNoun.name() + def oName := oNoun.name() + require(resPatt.optName() == resName) + require(rPatt.optName() == rName) + require(oPatt.optName() == oName) + require(isTempName(oName), fn{`unrecognized: $oName`}) + + def varIndex := builder.buildPromise() + def resIndex := varIndex +1 + tempIndices.put(varName, varIndex, true) + + def rValueNode := rightExpr.welcome(visitor) + builder.buildDefrec(resIndex, rValueNode) + } else { + throw(`unrecognized: $subs`) + } + } + } + builder.buildRoot(ast.welcome(visitor)) + } +} diff --git a/mast/lib/serial/deSrcKit.mt b/mast/lib/serial/deSrcKit.mt new file mode 100755 index 00000000..12e7f7ca --- /dev/null +++ b/mast/lib/serial/deSrcKit.mt @@ -0,0 +1,115 @@ +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +# def makeURIKit := +import "lib/serial/DEBuilderOf" =~ [=> DEBuilderOf :DeepFrozen] +import "lib/serial/deMNodeKit" =~ [=> deMNodeKit :DeepFrozen] +exports (deSrcKit) + +# /** +# * Prints a Kernel-Monte AST (@@??with some syntactic shorthands restored). +# *

+# * This is currently used only for printing the output of uneval, so it +# * currently handles the AST cases generated by uneval, but few if any others. +# * +# * @author Mark S. Miller +# * ported to Monte by Dan Connolly +# */ +object deSrcKit as DeepFrozen { + + method makeBuilder() :Near { + deSrcKit.makeBuilder(79, 1) + } + + method makeBuilder(wrapColumn :Int, sugarLevel :Int) :Near { + + # The index of the next temp variable + var nextTemp := 0 + + object deSrcBuilder implements DEBuilderOf[Str, Str] { + + to getNodeType() :Near { Str } + to getRootType() :Near { Str } + + to buildRoot(root :Str) :Str { + def result := M.toString(::"m``".fromStr(root) :(astBuilder.getAstGuard())) + # Remove terminal newline + # ugh: Slice stop cannot be negative + return if (result.endsWith("\n")) { + result.slice(0, result.size() - 1) + } else { + result + } + } + + to buildLiteral(value) :Str { + return M.toQuote(value) + } + + method buildImport(varName :Str) :Str { varName } + method buildIbid(tempIndex :Int) :Str { `t_$tempIndex` } + + to buildCall(var rec :Str, + verb :Str, + args :List[Str], + nargs :Map[Str, Str]) :Str { + if (rec =~ `def t_@_`) { + # the result would otherwise misparse. + rec := `($rec)` + } + var argList := ", ".join(args) + if (rec.size() + argList.size() > wrapColumn - 20) { + argList := ",\n".rjoin(args) + } + if (args.size() >= 1 && + rec.size() + args[0].size() > wrapColumn - 20) { + + argList := "\n" + argList + "\n" + } + argList += ", ".join([for n => v in (nargs) `$n => v`]) + + if (sugarLevel <= 0) { + return `$rec.$verb($argList)` + } + + return switch ([rec, verb, args]) { + match [`_makeList`, `run`, _] { + `[$argList]` + } + match [_, `run`, _] { + `$rec($argList)` + } + match [_, `negate`, []] { + `-$rec` + } + match _ { + `$rec.$verb($argList)` + } + } + } + + method buildDefine(rValue :Str) :Pair[Str, Int] { + def tempIndex := nextTemp + nextTemp += 1 + [`def t_$tempIndex := $rValue`, tempIndex] + } + + method buildPromise() :Int { + def promIndex := nextTemp + nextTemp += 2 + promIndex + } + + method buildDefrec(resIndex :Int, rValue :Str) :Str { + def promIndex := resIndex -1 + `def t_$promIndex := $rValue` + } + } + } + + method recognize(src :Str, builder) :(builder.getRootType()) { + var ast := ::"m``".fromStr(src) + # repair circular definition form + deMNodeKit.recognize(ast, builder) + } +} diff --git a/mast/lib/serial/deSubgraphKit.mt b/mast/lib/serial/deSubgraphKit.mt new file mode 100755 index 00000000..2d51cff2 --- /dev/null +++ b/mast/lib/serial/deSubgraphKit.mt @@ -0,0 +1,315 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +# module "org.erights.e.elib.serial.deSubgraphKit" + +# TODO: import "serial.deASTKit" =~ [=>deASTKit :DeepFrozen] +import "lib/serial/DEBuilderOf" =~ [=>DEBuilderOf :DeepFrozen] +# TODO: import "serial.deSrcKit" =~ [=>deSrcKit :DeepFrozen] +import "lib/tables/makeCycleBreaker" =~ [=>makeCycleBreaker :DeepFrozen] +import "lib/serial/makeUncaller" =~ [=>makeUncaller :DeepFrozen, =>Uncaller :DeepFrozen] +exports (makeUnevaler, deSubgraphKit) + +def defaultUncallers :DeepFrozen := makeUncaller.getDefaultUncallers() + +# See comment on getMinimalScope() below. +def minimalScope :DeepFrozen := [ + "null" => null, + "false" => false, + "true" => true, + "NaN" => NaN, + "Infinity" => Infinity, + "_makeList" => _makeList, +# "__identityFunc" => __identityFunc, + "_makeInt" => _makeInt, +# "import__uriGetter" => import__uriGetter +] + +def defaultScope :DeepFrozen := minimalScope +def toScalpel(scope) as DeepFrozen: + return makeCycleBreaker.byInverting(scope) + + +# /** +# * Serialize by generating an expression whose evaluation would produce a +# * reconstruction resembling the original object. +# * +# * @param uncallerList A list of {@link Uncaller}s used as a search path. For a +# * given object, calculates what call (if any) would create +# * it. Each uncaller is asked until one gives an answer or +# * the list is exhausted. Should the list be exhasted, +# * recognition terminates with a throw. +# *

+# * uncallerList can be any kind of list -- Flex, Const, RO +# * -- since the unevaler snapshot()s it at the beginning of +# * each recognize(..). +# * @param scalpelMap The value => variable-name associations we use as an +# * "unscope", the inverse of a scope. Given a value, what's +# * the name of the variable (if any) that currently has that +# * value? The scalpelMap should have at least the inverse of +# * the bindings defined in the minimalScope. +# *

+# * Since the scalpelMap generally should be able to map from +# * unresolved references (Promises) as keys, it would +# * normally be a {@link makeCycleBreaker CycleBreaker}. The +# * scalpelMap can be any kind of CycleBreaker -- Flex, Const, +# * RO -- since the unevaler diverge()s it at the beginning of +# * each recognize(..). +# * @author Mark S. Miller +# */ +def makeUnevaler(uncallerList, scalpelMap) :Near as DeepFrozen { + + # /** + # * + # */ + return object unevaler { + # /** + # * + # */ + to recognize(root, builder) :(def _Root := builder.getRootType()) { + + def Node := builder.getNodeType() + + def uncallers := uncallerList.snapshot() + + # We will identify temp variables by storing their index (ints) + # rather than their name (Strings) as scalpel-values. + def scalpel := scalpelMap.diverge() + + def generate + + # /** + # * traverse an uncall portrayal + # */ + def genCall(rec, verb :Str, args :List[Any], nargs :Map[Str, Any]) :Node { + return builder.buildCall( + generate(rec), verb, + [for arg in (args) generate(arg)], + [for name => arg in (nargs) name => generate(arg)]) + } + + # /** + # * When we're past all the variable manipulation. + # */ + def genObject(obj) :Node { + # scalars are transparent, but can't be uncalled. + # They are instead translated to literal expressions. + # The scalars null, true, and false should have already + # been picked up by the scalpel -- they should be in the + # provided scalpelMap. + if (obj =~ i :Int) { return builder.buildLiteral(i) } + if (obj =~ f :Double) { return builder.buildLiteral(f) } + if (obj =~ c :Char) { return builder.buildLiteral(c) } + if (obj =~ s :Str) { return builder.buildLiteral(s) } + + # Bare strings are transparent and aren't scalars, but + # still can't be uncalled. Instead, they are also + # translated into literal expressions + # TODO: when/if monte gets Twine + # if (obj =~ twine :Twine && twine.isBare()) { + # return builder.buildLiteral(twine) + # } + + for uncaller in (uncallers) { + if (uncaller.optUncall(obj) =~ [rec, verb, args, nargs]) { + return genCall(rec, verb, args, nargs) + } + } + throw(`Can't uneval ${M.toQuote(obj)}`) + } + + # /** Build a use-occurrence of a variable. */ + def genVarUse(varID :Any[Str, Int]) :Node { + return if (varID =~ varName :Str) { + builder.buildImport(varName) + } else { + builder.buildIbid(varID) + } + } + + # /** + # * The internal recursive routine that will traverse the + # * subgraph and build a Data-E Node while manipulating the + # * above state. + # */ + bind generate(obj) :Node { + escape notSeen { + return genVarUse(scalpel.fetch(obj, notSeen)) + } + def promIndex := builder.buildPromise() + scalpel[obj] := promIndex + def rValue := genObject(obj) + return builder.buildDefrec(promIndex+1, rValue) + } + + return builder.buildRoot(generate(root)) + } + + # /** + # * A printFunc can be used as an argument in + # *

    interp.setPrintFunc(..)
+ # * to be used as the 'print' part of that read-eval-print loop. + # * When using an unevalers printFunc for this purpose, we have instead + # * a read-eval-uneval loop. + # */ + # TODO: to makePrintFunc() :Near { + # def printFunc(value, out :TextWriter) :Void { + # def builder := deASTKit.wrap(deSrcKit.makeBuilder()) + # out.print(unevaler.recognize(value, builder)) + # } + # } + + to _muteSMO() {} + } +} + + +# /** +# * Unserializes/evals by building a subgraph of objects, or serializes/unevals +# * by recognizing/traversing a subgraph of objects. +# * +# * @author Mark S. Miller +# */ +object deSubgraphKit as DeepFrozen { + + # /** + # * This is the default scope used for recognizing/serializing/unevaling and + # * for building/unserializing/evaling. + # *

+ # * The minimal scope only has bindings for

+ # */ + method getMinimalScope() :Near { minimalScope } + + # /** + # * XXX For now, it's the same as the minimalScope, but we expect to add + # * more bindings from the safeScope; possibly all of them. + # */ + method getDefaultScope() :Near { defaultScope } + + # /** + # * + # */ + method getMinimalScalpel() :Near { toScalpel(minimalScope) } + + # /** + # * XXX For now, it's the same as the minimalScalpel, but we expect to add + # * more bindings from the safeScope; possibly all of them. + # */ + method getDefaultScalpel() :Near { toScalpel(defaultScope) } + + # /** + # * + # */ + method getDefaultUncallers() :List[Uncaller] { defaultUncallers } + + # /** + # * Makes a builder which evaluates a Data-E tree in the default scope to a + # * value. + # * + # * @see #getMinimalScope + # */ + method makeBuilder() :Near { + deSubgraphKit.makeBuilder(defaultScope) + } + + # /** + # * Makes a builder which evaluates a Data-E tree in a scope to a value. + # *

+ # * This is Data-E Unserialization. It is also a subset of E + # * evaluation. + # */ + to makeBuilder(scope) :Near { + + # The frame of temp variables + def temps := [].diverge() + + def Node := Any + def Root := Any + + return object deSubgraphBuilder implements DEBuilderOf[Node, Root] { + method getNodeType() :Near { Node } + method getRootType() :Near { Root } + + method buildRoot(root :Node) :Root { root } + method buildLiteral(value) :Node { value } + method buildImport(varName :Str) :Node { scope[varName] } + method buildIbid(tempIndex :Int) :Node { temps[tempIndex] } + + method buildCall(rec :Node, verb :Str, args :List[Node], nargs :Map[Str, Node]) :Node { + M.call(rec, verb, args, nargs) + } + + method buildDefine(rValue :Node) :Pair[Node, Int] { + def tempIndex := temps.size() + temps.push(rValue) + [rValue, tempIndex] + } + + method buildPromise() :Int { + def promIndex := temps.size() + def [prom,res] := Ref.promise() + temps.push(prom) + temps.push(res) + promIndex + } + + method buildDefrec(resIndex :Int, rValue :Node) :Node { + temps[resIndex].resolve(rValue) + rValue + } + } + } + + # /** + # * + # */ + to getDefaultRecognizer() :Near { + return makeUnevaler(defaultUncallers, toScalpel(defaultScope)) + } + + # /** + # * + # */ + method makeRecognizer(optUncallers, optScalpel) :Near { + def uncallers := if (null == optUncallers) { + defaultUncallers + } else { + optUncallers + } + def scalpel := if (null == optScalpel) { + toScalpel(defaultScope) + } else { + optScalpel + } + makeUnevaler(uncallers, scalpel) + } + + # /** + # * Uses the default recognizer + # */ + method recognize(root, builder) :(def _Root := builder.getRootType()) { + def defaultRecognizer := deSubgraphKit.getDefaultRecognizer() + defaultRecognizer.recognize(root, builder) + } +} diff --git a/mast/lib/serial/guards.mt b/mast/lib/serial/guards.mt new file mode 100644 index 00000000..8f65d463 --- /dev/null +++ b/mast/lib/serial/guards.mt @@ -0,0 +1,8 @@ +exports (NotNull, Guard) + +def NotNull.coerce(specimen, ej) as DeepFrozen: + if (specimen == null): + throw.eject(ej, "null not allowed") + return specimen + +def Guard :DeepFrozen := Any # TODO? diff --git a/mast/lib/serial/makeSurgeon.emaker b/mast/lib/serial/makeSurgeon.emaker new file mode 100755 index 00000000..cdc0d319 --- /dev/null +++ b/mast/lib/serial/makeSurgeon.emaker @@ -0,0 +1,374 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +module "org.erights.e.elib.serial.makeSurgeon" + +def deSubgraphKit := +def deASTKit := +def deBytecodeKit := +def deSrcKit := + +/** + * + */ +def argValue(optArg, func) :any { + if (null == optArg) { + func() + } else { + optArg + } +} + +/** + * Makes a map like the argument, but which returns a broken reference rather + * than throwing an exception on a .get(key) when the key is not + * found. + */ +def makeForgivingMap(map) :near { + def forgivingMap extends map { + to get(key) :any { + if (super.maps(key)) { + super.get(key) + } else { + Ref.broken(`There is no $key`) + } + } + to snapshot() :near { makeForgivingMap(super.snapshot()) } + to diverge() :near { makeForgivingMap(super.diverge()) } + to readOnly() :near { makeForgivingMap(super.readOnly()) } + } +} + +/** + * + * + * @author Mark S. Miller + */ +def makeSurgeon { + + /** + * Everything defaults + */ + to run() :near { + makeSurgeon(null, null, null, + null, + null, null) + } + + /** + * + */ + to withSrcKit(optPrefix :nullOk(String)) :near { + makeSurgeon(null, null, deSrcKit, + optPrefix, + deSrcKit, null) + } + + /** + * + * + * @param optUncallers Defaults to + * deSubgraphKit.getDefaultUncallers().diverge(). + * The search path used to find a portrayal for + * traversing each node of the subgraph. + * @param optScalpel Defaults to + * deSubgraphKit.getDefaultScalpel().diverge(). + * Cuts off outgoing references, replacing them with + * named exit points to be reconnected. + * @param optDepictionBuilderMaker Defaults to deBytecodeKit. + * Used to make the builder which will make + * the depiction. + * @param optPrefix Does not default. If provided, then the + * optDepictionBuilderMaker and optDepictionRecognizer + * must be for depictions which are strings. + * If provided, then it is prepended to the depiction to + * create the serialized form, and is stripped from the + * beginning of the depiction prior to serialization. + * @param optDepictionRecognizer Defaults to deBytecodeKit. + * Used to recognize the depiction built + * according to optDepictionBuilderMaker. + * @param optScope Defaults to deSubgraphKit.getDefaultScope().diverge(). + * Used to reconnect the named exit points. + */ + to run(optUncallers, + optScalpel, + optDepictionBuilderMaker, + + optPrefix :nullOk(String), + + optDepictionRecognizer, + optScope) :near { + + def uncallers := argValue(optUncallers, thunk{ + deSubgraphKit.getDefaultUncallers().diverge()}) + def scalpel := argValue(optScalpel, thunk{ + deSubgraphKit.getDefaultScalpel().diverge()}) + def depictionBuilderMaker := argValue(optDepictionBuilderMaker, thunk{ + deBytecodeKit}) + + def depictionRecognizer := argValue(optDepictionRecognizer, thunk{ + deBytecodeKit}) + var scope := argValue(optScope, thunk{ + deSubgraphKit.getDefaultScope().diverge()}) + + def subgraphRecognizer := deSubgraphKit.makeRecognizer(uncallers, + scalpel) + + def surgeon { + to makeDepictionBuilder() :near { + depictionBuilderMaker.makeBuilder() + } + to getOptPrefix() :nullOk(String) { optPrefix } + to getDepictionRecognzer() :near { depictionRecognizer } + to getSubgraphRecognizer() :near { subgraphRecognizer } + to makeSubgraphBuilder() :near { + deSubgraphKit.makeBuilder(scope.snapshot()) + } + + to snapshot() :near { + makeSurgeon(uncallers.snapshot(), + scalpel.snapshot(), + depictionBuilderMaker, + optPrefix, + depictionRecognizer, + scope.snapshot()) + } + + to diverge() :near { + makeSurgeon(uncallers.diverge(), + scalpel.diverge(), + depictionBuilderMaker, + optPrefix, + depictionRecognizer, + scope.diverge()) + } + + to serialize(root) :any { + def ast := subgraphRecognizer.recognize(root, + deASTKit.makeBuilder()) + def depictionBuilder := surgeon.makeDepictionBuilder() + def depiction := deASTKit.recognize(ast, depictionBuilder) + if (null == optPrefix) { + depiction + } else { + optPrefix + depiction + } + } + + to unserialize(var depiction) :any { + if (null != optPrefix) { + def `$optPrefix@rest` := depiction + depiction := rest + } + def subgraphBuilder := surgeon.makeSubgraphBuilder() + depictionRecognizer.recognize(depiction, subgraphBuilder) + } + + to addExit(value, exitName :String) :void { + scalpel[value] := exitName + scope[exitName] := value + } + + to addUncaller(uncaller) :void { + uncallers(0,0) := [uncaller] + } + + to addLastUncaller(uncaller) :void { + uncallers.push(uncaller) + } + + to addLoader(loader, exitName :String) :void { + surgeon.addExit(loader, exitName) + surgeon.addUncaller(loader) + } + + to beForgiving() :void { + surgeon.addExit(opaque__uriGetter, "opaque__uriGetter") + # Add at end instead of beginning + surgeon.addLastUncaller(opaque__uriGetter) + scope := makeForgivingMap(scope) + } + + /** + * Names which either aren't found or map to null aren't added. + */ + to addFromScope(scope, exitNames, loaderNames) :void { + for name in exitNames { + if (scope.get(name, null) =~ value :notNull) { + surgeon.addExit(value, name) + } else { + throw.breakpoint(`no exit: $name`) + } + } + for name in loaderNames { + if (scope.get(name, null) =~ value :notNull) { + surgeon.addLoader(value, name) + } else { + throw.breakpoint(`no loader: $name`) + } + } + } + + /** + * The defaultScope / defaultScalpel already has bindings for + * "null", "false", "true", "NaN", "Infinity", "__makeList", + * "__identityFunc", "__makeInt", and "import__uriGetter", so + * addFromSafeScope() assumes these are already present and does + * not add them. Similarly, the defaultUncallers already has the + * import__uriGetter, so this is not added as a loader. + *

+ * For different reasons, the opaque__uriGetter is not added by + * addFromSafeScope() -- we leave its addition as a separate policy + * decision, especially since it needs to be added to the end, + * not the beginning, of the uncallers list. + */ + to addFromSafeScope() :void { + surgeon.addFromScope(safeScope, [ + + # Keep the following lists in the same order as in + # ScopeSetup.java, and maintain these lists jointly. + + # "null", already in default scope / scalpel + # "false", already in default scope / scalpel + # "true", already in default scope / scalpel + "throw", # A strange but useful thing to include, so a + # depiction can force unserialization to fail. + "__loop", # Probably not useful + + # "__makeList", already in default scope / scalpel + "__makeMap", + "__makeProtocolDesc", + "__makeMessageDesc", + "__makeParamDesc", + + "settable", + "__defineSlot", # Probably not useful + "any", + "void", + + "boolean", + "__makeOrderedSpace", + + # "NaN", already in default scope / scalpel + # "Infinity", already in default scope / scalpel + # "__identityFunc", already in default scope / scalpel + # "__makeInt", already in default scope / scalpel + + "__makeTwine", + "__makeSourceSpan", + + "Guard", + "__auditedBy", + "near", + "pbc", + "PassByCopy", + "DeepFrozen", + "DeepPassByCopy", + "PersistentAuditor", + + "int", + "float64", + "char", + + "String", + "Twine", + "TextWriter", + + "require", + + "nullOk", + "vow", + "rcvr", + "sturdy", + "simple__quasiParser", + "rx__quasiParser", + "e__quasiParser", + "sml__quasiParser", + "term__quasiParser", + + # universals above. Below is only safe. + + "__equalizer", + "__comparer", + "Ref", + + "E", + "promiseAllFulfilled", + + "EIO", + "help", + "safeScope", + + "resource__uriGetter" # Should be in other list + ], [ + # "resource__uriGetter", Uncalling not yet implemented + + "type__uriGetter", + # "import__uriGetter", already in default scope, scalpel + # and uncallers + "elib__uriGetter", + "elang__uriGetter" + # "opaque__uriGetter" separate policy decision to include + ]) + } + + /** + * Starts by doing an addFromSafeScope() + */ + to addFromPrivScope(privScope) :void { + surgeon.addFromSafeScope() + surgeon.addFromScope(privScope, [ + "makeCommand", + + "stdout", + "stderr", + "print", + "println", + "interp", + + "entropy", + "timer", + "introducer", + "identityMgr", + + # "getPrivilegedJOSSSuture", to be replaced + "Persistent", + "makeSturdyRef", + "timeMachine", + + "currentVat", + "rune", + + # "awt__uriGetter", validity is runner dependent + # "swing__uriGetter", validity is runner dependent + # "JPanel__quasiParser", validity is runner dependent + + # "swt__uriGetter", validity is runner dependent + # "currentDisplay", validity is runner dependent + # "swtGrid__quasiParser", validity is runner dependent + + "privilegedScope", + + "unsafe__uriGetter" # it's a loader, but special + ], [ + # "unsafe__uriGetter", as loader, handled separately below + + "file__uriGetter", + "fileURL__uriGetter", + "http__uriGetter", + "ftp__uriGetter", + "gopher__uriGetter", + "news__uriGetter", + + "cap__uriGetter" + ]) + + # Insert after the import__uriGetter, so it has lower priority + def i := uncallers.indexOf1(import__uriGetter) + 1 + uncallers(i,i) := [privScope["unsafe__uriGetter"]] + } + } + } +} diff --git a/mast/lib/serial/makeUncaller.mt b/mast/lib/serial/makeUncaller.mt new file mode 100755 index 00000000..57b484e4 --- /dev/null +++ b/mast/lib/serial/makeUncaller.mt @@ -0,0 +1,122 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +# import "serial.RemoteCall" =~ [=>makeRemoteCall :DeepFrozen] +import "lib/serial/guards" =~ [=>NotNull :DeepFrozen] # =>DFTuple :DeepFrozen +exports (makeUncaller, Uncaller) + +def Portrayal :DeepFrozen := List # Tuple[Any, Str, List, Map[Str, Any]] + +interface Uncaller :DeepFrozen: + to optUncall(obj) :NullOk[Portrayal] + +object minimalUncaller as DeepFrozen implements Uncaller { + method optUncall(obj) :NullOk[Portrayal] { + if (Ref.isNear(obj)) { + obj._uncall() + } else if (Ref.isBroken(obj)) { + [Ref, "broken", [Ref.optProblem(obj)], [].asMap()] + } else { + throw("TODO: makeRemoteCall.optUncall(obj)") + } + } + to _muteSMO() {} +} + +def minimalUncallers :DeepFrozen := [minimalUncaller] # TODO: , import__uriGetter + +def defaultUncallers :DeepFrozen := minimalUncallers + +# /** +# * Makes an uncall function that, when applied to a transparent-enough object, +# * will return the ingredients of a call expression that, when performed, will +# * reconstruct a new object adequately similar to the original. +# *

+# * An uncall function is used as a component in making a subgraph recognizer, +# * ie, an uneval function. +# * +# * @author Mark S. Miller +# */ +object makeUncaller as DeepFrozen { + + # /** + # * + # */ + method getMinimalUncallers() :List[Uncaller] { minimalUncallers } + + # /** + # * XXX For now it's the same as minimalUncallers, but we expect to add + # * the other uriGetters from the safeScope. + # */ + method getDefaultUncallers() :List[Uncaller] { defaultUncallers } + + # /** + # * Makes an amplifyingUncall to implement selective transparency. + # *

+ # * A object that isn't objectively transparent is transparent to the + # * ampliedUncall if it responds to + # * __optSealedDispatch(unsealer.getBrand()) with a sealed + # * box, sealed by the corresponding sealer, containing the kind of three + # * element list an uncall function needs to return. This list should + # * is the elements of a call that, if performed, should create an + # * object that resembles the original object. + # * + # * @param baseUncall This is tried first, and if it succeeds, we're done. + # * @param unsealer If baseUncall fails, we use this unsealer to try to + # * access the object's private state by rights + # * amplification. + # * Currently, this can only be an individual Unsealer, + # * but we should create something like the KeyKOS + # * CanOpener composed of a searchpath of Unsealers. + # * @return null, or the kind of three + # * element list an uncall function needs to return, consisting of:

+ # */ + method makeAmplifier(unsealer) :Uncaller { + object amplifier { + to optUncall(obj) :NullOk[Portrayal] { + + if (unsealer.amplify(obj) =~ [result]) { + result + } else { + null + } + } + to _muteSMO() {} + } + } + + # /** + # * Make an onlySelflessUncaller by wrapping baseUncallers with a + # * pre-condition that accepts only Selfless objects. + # *

+ # * uncall on a Selfless object has all the guarantees explained at + # * {@link org.erights.e.elib.prim.MirandaMethods#__optUncall}. + # * An onlySelflessUncaller is for the purpose of restricting uncall to + # * those cases where these strong guarantees apply. + # */ + to onlySelfless(baseUncallers) :Uncaller { + + object onlySelflessUncaller { + method optUncall(obj) :NullOk[Portrayal] { + if (Ref.isSelfless(obj)) { + for baseUncaller in (baseUncallers) { + if (baseUncaller.optUncall(obj) =~ + result :NotNull) { + + return result + } + } + } + null + } + to _muteSMO() {} + } + } +} + diff --git a/mast/lib/serial/markupKit.mt b/mast/lib/serial/markupKit.mt new file mode 100644 index 00000000..07d198e8 --- /dev/null +++ b/mast/lib/serial/markupKit.mt @@ -0,0 +1,552 @@ +import "unittest" =~ [=> unittest] +import "lib/codec/utf8" =~ [=>UTF8 :DeepFrozen] +import "guards" =~ [=>Tuple :DeepFrozen] +import "./elib/serial/DEBuilderOf" =~ [=> DEBuilderOf :DeepFrozen] +exports (main, Name, nameStart, nameChar, Element, Text, deMarkupKit) + + +def oneOf(chars :DeepFrozen) as DeepFrozen: + "String matching guard builder with utilities. + + chars may be anything with a .contains(ch :Char) method. + " + return object oneOf as DeepFrozen: + to coerce(specimen, ej): + if (!chars.contains(specimen)): + ej(`found $specimen when expecting $chars`) + return specimen + + to add(rest :DeepFrozen): + "Prefix this guard to a string guard. + + e.g. oneOf(nameStart) + oneOf(nameChar).star() + " + return def concat.coerce(specimen, ej) as DeepFrozen: + Str.coerce(specimen, ej) + oneOf.coerce(specimen[0], ej) + rest.coerce(specimen.slice(1), ej) + return specimen + + to star(): + "Repeat any nuber of times (Kleene-star). + " + return def repeat.coerce(specimen, ej) as DeepFrozen: + for ch in (specimen): + oneOf.coerce(ch, ej) + return specimen + + to plus(): + "One or more repetitions. + + def word := oneOf('a'..'z').plus() + " + return oneOf + oneOf.star() + + to findFirst(s :Str) :Tuple[Str, NullOk[Char], Str]: + "Find the first of these characters to occur. + " + var out :Tuple[Str, NullOk[Char], Str] := [s, null, ""] + + for delim in (chars): + if (s.split(delim.asString(), 1) =~ [pre, post] && pre.size() < out[0].size()): + out := [pre, delim, post] + return out + + to split(s :Str, "max"=>max :NullOk[Int>0] := null) :List[Str]: + "(split is dead code, but it's tested, working dead code!) + " + def out := [].diverge() + var piece :Int := 0 + var sep :Int := -1 + for ix => ch in (s): + switch ([chars.contains(ch), sep > piece]) { + match ==[true, true] { } + match ==[false, false] { } + match [==true, _] { + out.push(s.slice(piece, ix)) + sep := ix + } + match [_, ==true] { + piece := ix + if (max != null && out.size() >= max) { + break + } + sep := -1 + } + } + if (piece > sep) { + out.push(s.slice(piece)) + } + return out.snapshot() + +def oneOfTest(assert) as DeepFrozen: + for [s, delim, m, expected] in ([ + ["", [], null, [""]], + ["a b c", [' '], null, ["a", "b", "c"]], + ["a \n b c", [' ', '\n'], null, ["a", "b", "c"]], + ["a b c", [' '], 1, ["a", "b c"]] + ]): + assert.equal(oneOf(delim).split(s, "max"=>m), expected) + + for [s, delim, expected] in ([ + ["blah bla spif", ['<', '&'], ["blah bla ", '<', "b>spif"]], + ["blah &bla spif", ['<', '&'], ["blah ", '&', "bla spif"]], + ["blah &spif", ['<', '&'], ["blah ", '<', "bla &spif"]], + ["", [], ["", null, ""]], + ]): + assert.equal(oneOf(delim).findFirst(s), expected) + + +######## +# A Name is a Str consisting of a nameStart followed by any number of nameChar. + +# TODO: non-ASCII stuff from https://www.w3.org/TR/xml/#NT-Name +# [4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | +# [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | +# [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | +# [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] +# [4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] +# [5] Name ::= NameStartChar (NameChar)* + +def nameStart :DeepFrozen := ':'..':' | 'A'..'Z' | '_'..'_' | 'a'..'z' +def nameChar :DeepFrozen := nameStart | '-'..'-' | '.'..'.' | '0'..'9' + + +def Name :DeepFrozen := oneOf(nameStart) + oneOf(nameChar).star() + +def nameTests(assert) as DeepFrozen: + for n in (["p", "h1"]): + assert.equal(n :Name, n) + for s in (["x y", "", "23", "

", "<"]): + assert.throws(fn { s :Name }) + + +# hmm... some characters not allowed, right? +def Text :DeepFrozen := Str + + +########## +# MLReader cf. SAX + +def makeMLReader(handler, + =>ws := [' ', '\n'], + =>selfClosing := [ + # HTML 4 + "area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", + # HTML 5 + "command", "keygen", "source"], + =>entities := ["amp"=>"&", "lt"=>"<", "gt"=>">", "quot"=>"\"", "apos"=>"'"]) as DeepFrozen { + def Numeral := oneOf('0'..'9').plus() + + def tryEntity(ampPost :Text, thunk) :Int { + return if (ampPost =~ `#@{charCode :Numeral};@_rest`) { + def ch := ('\x00' + _makeInt(charCode)).asString() + thunk(ch) + charCode.size() + 2 + } else if (ampPost =~ `@{entityName :Name ? (entities.contains(entityName))};@_rest`) { + thunk(entities[entityName]) + entityName.size() + 2 + } else { + thunk("&") + 0 + } + } + + def makeTag(ltPost :Text) { + var ix :Int := 0 + var ch := null + def advance() { + return if (ix < ltPost.size()) { + ch := ltPost[ix] + ix += 1 + true + } else { + ch := null + false + } + } + advance() + + def skipSpace() { + while (ws.contains(ch) && advance()) { } + } + def expect(fail, options) { + if (options.contains(ch)) { + advance() + } else { + traceln(`expect($options) got $ch`) + fail(ix) + } + } + def name(fail) { + def start := ix - 1 + expect(fail, nameStart) + while(nameChar.contains(ch) && advance()) { } + def out := ltPost.slice(start, ix - 1) + skipSpace() + # traceln(`name: ltPost[$start..$ix] = $out`) + return out + } + def attribute(fail) { + def attrName := name(fail) + skipSpace() + expect(fail, ['=']) + skipSpace() + def delim := ch + expect(fail, ['\'', '"']) + var value :Str := "" + while (ch != delim) { + if (ch == '&') { + ix += tryEntity(ltPost.slice(ix), fn s { value += s}) - 1 + } else { + value += ch.asString() + } + if (!advance()) { break } + } + advance() + skipSpace() + return [attrName, value] + } + + return object tag { + to start() :Text { + escape badTag { + def tagName := name(badTag) + def attrs := [].diverge() + while (nameStart.contains(ch)) { + attrs.push(attribute(badTag)) + } + handler.startElement(tagName, _makeMap.fromPairs(attrs)) + # XML-ish mode + if (selfClosing.size() == 0) { + if (ch == '/') { + expect(badTag, ['>']) + handler.endElement(tagName) + } else { + expect(badTag, ['>']) + } + } else { + if (ch == '/') { advance() } + expect(badTag, ['>']) + # traceln(`got tag to $ix: $tagName $attrs`) + if (selfClosing.contains(tagName)) { + handler.endElement(tagName) + } + } + } catch errIx { + handler.error([ltPost, errIx]) + } + + return ltPost.slice(ix - 1) + } + to end() :Text { + escape badTag { + expect(badTag, ['/']) + def tagName := name(badTag) + expect(badTag, ['>']) + handler.endElement(tagName) + } catch errIx { + handler.error([ltPost, errIx]) + } + + return ltPost.slice(ix - 1) + } + } + } + + return def XMLReader.parse(var markup :Text) :Void { + while(markup.size() > 0) { + def [text, delim, rest] := oneOf(['<', '^']).findFirst(markup) + if (text.size() > 0) { + handler.characters(text) + } + + # traceln(`M:${markup.slice(0, 4)}... => ${[text, delim, rest]}`) + switch (delim) { + match ==null { markup := "" } + match =='&' { markup := rest.slice(tryEntity(rest, handler.characters)) } + match =='<' { + markup := if (rest == "") { + handler.characters("<") + "" + } else if (rest[0] == '/') { + makeTag(rest).end() + } else if (nameStart.contains(rest[0])) { + makeTag(rest).start() + } else if (rest =~ `!--@comment-->@more`) { + handler.comment(comment) + more + } else if (rest.slice(0, "!doctype".size()).toLowerCase() == "!doctype" && + (def after := rest.slice("!doctype".size())).contains(">")) { + def `@decl>@more` := after + handler.doctype(decl) + more + } else { + handler.characters("<") + rest + } + } + } + } + } +} + + +### +# Element data + +# TODO. postponed due to: +# ~ Problem: Message refused: ().getNodeName() +# ~ .getNodeName() +# deep inside .convertFromKernel() +# def [makerAuditor :DeepFrozen, &&Element, &&serializer] := Transparent.makeAuditorKit() + +interface Element :DeepFrozen {} + +def Content :DeepFrozen := Any[Element, Text, List[Any[Element, Text]]] + +def makeElement(tag :Name, + "attrs"=>attrs :Map[Name, Str] := [].asMap(), + "children"=>children :NullOk[List[Content]] := []) as DeepFrozen: + return object element implements Selfless, Element: + to _uncall(): + # return serializer(makeElement, tag, "attrs"=>attrs, "children"=>children) + return [makeElement, "run", [tag], ["attrs"=>attrs, "children"=>children]] + + to getAttr(name :Name, =>FAIL): + return attrs.fetch(name, FAIL) + + to _printOn(p): + p.print(`<$tag`) + for n => v in (attrs): + p.print(` $n="$v"`) #@@ TODO: fix markup in v + if (children == null): + p.print(" />") + else: + p.print(">") + for ch in (children): + ch._printOn(p) + p.print(``) + + +object deMarkupKit as DeepFrozen { + to makeBuilder() { + return object deMarkupBuilder implements DEBuilderOf[Str, Str] { + to buildRoot(root :Str) :Str { return root } + to buildLiteral(text :Text) :Content { return text } + to buildImport(varName :Str) :Str { + return makeElement("a", "attrs"=>["href"=>varName, "class"=>"import"], + "children"=>[varName]) + } + to buildIbid(tempIndex :Int) :Str { + throw("TODO Markup ibitd") + } + to buildCall(rec :Str, verb :Str, args :List[Element], nargs :Map[Str, Element]) :Str { + throw("TODO markup call") + } + to buildDefine(rValue :Str) :Pair[Element, Int] { + throw("TODO markup define") + } + to buildPromise() :Int { + throw("TODO markup promise") + } + to buildDefrec(resIndex :Int, rValue :Str) :Str { + throw("TODO markup defrec") + } + } + } + + to recognize(markup :Str, builder) :(def _Root := builder.getRootType()) { + + var pendingTags :List[Tuple[Name, Map[Str, Text], List[Content]]] := [] + def root + + def addChild(c :Content) { + if (pendingTags.size() == 0) { + if (c =~ text :Text) { + builder.buildLiteral(text) + } else { + bind root := builder.buildRoot(c) + } + } else { + def [[n, a, children]] + rest := pendingTags + pendingTags := [[n, a, children + [c]]] + rest + } + } + + object handler { + to doctype(decl) { traceln(`TODO: build `) } + to comment(txt) { traceln(`TODO: build `) } + to startElement(name :Name, attrs: Map[Str, Text]) { + pendingTags := [[name, attrs, []]] + pendingTags + } + to characters(chars :Text) { addChild(chars) } + to endElement(endTagName :Name) { + def [[startTagName, attrs, children]] + rest := pendingTags + if (endTagName != startTagName) { + throw(`@@TODO: $endTagName != $startTagName ($pendingTags)`) + } + def elt := builder.buildCall(makeElement, "run", [startTagName], + ["attrs"=>attrs, "children"=>children]) + pendingTags := rest + addChild(elt) + } + to error([s :Str, ix :Int]) { + traceln(`@@got ERROR! ${s.slice(ix - 20, ix)}**${s.slice(ix, ix + 1)}**${s.slice(ix + 1, ix+20)}...`) + } + } + + def parser := makeMLReader(handler) + parser.parse(markup) + return root + } +} + + +object deMLNodeKit as DeepFrozen { + to makeBuilder() { + var nextTemp :Int := 0 + var varReused := [].asMap().diverge() + + return object deMarkupBuilder implements DEBuilderOf[Content, Element] { + method getNodeType() :Near { Content } + method getRootType() :Near { Element } + + to buildRoot(root :Element) :Element { return root } + to buildLiteral(text :Text) :Content { return text } + to buildImport(varName :Str) :Element { + return makeElement("a", "attrs"=>["href"=>varName, "class"=>"import"], + "children"=>[varName]) + } + to buildIbid(tempIndex :Int) :Element { + if (! (tempIndex < nextTemp)) { throw(`assertion failure: $tempIndex < $nextTemp`) } + varReused[tempIndex] := true + # traceln(`buildIbid: $tempIndex marked reused.`) + # Represent ibid as intra-document link. + return makeElement("a", "attrs"=>["href"=>`#ibid$tempIndex`, "class"=>"ibid"], + "children"=>[`[$tempIndex]`]) + } + to buildCall(==makeElement, =="run", [tagName :Name], + ["attrs"=>attrs :Map[Name, Str] := [].asMap(), + "children"=>children :List[Content] := []]) :Element { + return makeElement(tagName, "attrs"=>attrs, "children"=>children) + } + to buildDefine(rValue :Element) :Pair[Element, Int] { + throw("TODO: buildDefine") + + def tempIndex := nextTemp + nextTemp += 1 + varReused[tempIndex] := false + def tempName := ["ibid", tempIndex] + # hmm... can we make this optimization locally? + def defElement := if (rValue =~ Literal) { rValue } else { ["define", tempIndex, rValue] } + return [defElement, tempIndex] + } + to buildPromise() :Int { + throw("TODO: buildPromise") + + def promIndex := nextTemp + nextTemp += 2 + varReused[promIndex] := false + varReused[promIndex + 1] := false + return promIndex + } + to buildDefrec(resIndex :Int, rValue :Element) :Element { + throw("TODO: buildDefrec") + + def promIndex := resIndex - 1 + # traceln(`buildDefrec: $promIndex reused? ${varReused[promIndex]}.`) + return if (varReused[promIndex]) { + # We have a cycle + ["defrec", promIndex, rValue] + } else { + # No cycle + ["define", promIndex, rValue] + } + } + } + } + + to recognize() { + throw("@@not implemented") + #@@Char + } +} + + +def kitTest(assert) as DeepFrozen: + def parse := fn m { deMarkupKit.recognize(m, deMLNodeKit.makeBuilder()) } + for [m, rootTag] in ([ + ["

", "p"], + ["
", "br"], + ["

AT&T

", "h2"], + ["

AT & T

", "h2"], + ["

AT A T

", "h2"], + ["

a < b

", "p"], + ["

..

", "p"], + ["...", "html"], + ["

", "p"], + ["

sdlkfWHEE!...

", "p"], + ["
...sdlfkj
", "div"], + ["
...sdlfkj
", "div"], + ["<

hi

", "p"], + ["...", "my-stuff"] + ]): + # traceln(`markup: $m`) + def doc := parse(m) + # traceln(`parsed doc: $doc`) + def [_rx, _verb, [actual], _nargs] := doc._uncall() + assert.equal(actual, rootTag) + + assert.equal(parse("...").getAttr("href"), "AT&T") + + +unittest([ + oneOfTest, + nameTests, + kitTest +]) + + +def main(args :List[Str], =>makeFileResource) :Vow[Int] as DeepFrozen: + def runTests(): + var successes :Int := 0 + var failures :Int := 0 + object assert: + to equal(l, r): + if (l == r) { + successes += 1 + } else { + traceln(`failed: $l == $r`) + failures += 1 + } + to throws(f): + try: + f() + traceln(`did not throw: $f`) + failures += 1 + catch _: + successes += 1 + + oneOfTest(assert) + nameTests(assert) + kitTest(assert) + traceln(["success" => successes, "failure" => failures]) + return 0 + + if (args =~ [_eval, _script, fname] + _): + traceln(`$fname: reading...`) + return when (def bs := makeFileResource(fname) <- getContents()) -> + traceln(`$fname: UTF8 decoding ${bs.size()} bytes...`) + def markup := UTF8.decode(bs, throw) + traceln(`$fname: parsing ${markup.size()} chars...`) + def doc := deMarkupKit.recognize(markup, deMLNodeKit.makeBuilder()) + def [_rx, _verb, [root], _nargs] := doc._uncall() + traceln(`root: $root`) + 0 + catch oops: + traceln.exception(oops) + 1 + else: + return runTests() + diff --git a/mast/lib/serial/opaque__uriGetter.emaker b/mast/lib/serial/opaque__uriGetter.emaker new file mode 100755 index 00000000..f60af907 --- /dev/null +++ b/mast/lib/serial/opaque__uriGetter.emaker @@ -0,0 +1,45 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + +def Uncaller := + +/** + * Used to represent the failure to serialize an object. + *

+ * "opaque" because it represents objects that weren't transparent to the + * serializer in question. + * + * @author Mark S. Miller + */ +def opaque__uriGetter0 implements Uncaller { + + /** + * Always succeeds, returning a "<opaque:...>" expression that shows + * what presumably could not be serialized. + *

+ * Since this always succeeds, it should only appear last in any uncaller + * chain. + * + * @return Notice the lack of nullOk in the result guard, since this always + * succeeds. + */ + to optUncall(obj) :[any, String, any[]] { + + var str := E.toString(obj) + if (str.startsWith("<") && str.endsWith(">")) { + # Strip off outer angle brackets + str := str(1,str.size()-1) + } + [opaque__uriGetter0, "get", [str]] + } + + /** + * If the uncall is actually performed, this returns a broken reference + * whose problem shows what presumably could not be serialized. + */ + to get(str :String) :any { + Ref.broken(`opaque:$str`) + } +} diff --git a/mast/lib/serial/surgeon.updoc b/mast/lib/serial/surgeon.updoc new file mode 100755 index 00000000..c1a22bfe --- /dev/null +++ b/mast/lib/serial/surgeon.updoc @@ -0,0 +1,82 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + + +? def makeSurgeon := +# value: + +? def surgeon := makeSurgeon.withSrcKit("de: ") +# value: + +? def src := surgeon.serialize([false, 3]) +# value: "de: [false, 3]" + +? surgeon.unserialize(src) +# value: [false, 3] + +? surgeon.serialize() +# problem: Can't uneval +# + +? surgeon.addFromPrivScope(privilegedScope) + +? def src := surgeon.serialize() +# value: "de: " + +? def x := surgeon.unserialize(src) +# value: + +? introducer.onTheAir() +# value: ["3DES_SDH_M2", "3DES_SDH_M"] + +? def forever := .getMAX_VALUE() +# value: 9223372036854775807 + +? def sr := identityMgr.makeSturdyRef(3,identityMgr.nextSwiss(),forever) +# value: + +? def src := surgeon.serialize(sr) + +? def sr2 := surgeon.unserialize(src) +# value: + +? sr == sr2 +# value: true + +? surgeon.unserialize("de: spoon") +# problem: + +? def foo{} +# value: + +? surgeon.serialize(foo) +# problem: Can't uneval + +? surgeon.beForgiving() + +? surgeon.unserialize("de: spoon") +# value: + +? def src := surgeon.serialize(foo) +# value: "de: " + +? surgeon.unserialize(src) +# value: + +? def mvi := +# value: + +? def kp := mvi.generateKeyPair(entropy) +# value: + +? def src := surgeon.serialize(kp) + +? def kp2 := surgeon.unserialize(src) +# value: + +? def src2 := surgeon.serialize(kp2) + +? src == src2 +# value: true diff --git a/mast/lib/serial/uncall.updoc b/mast/lib/serial/uncall.updoc new file mode 100755 index 00000000..b98341d4 --- /dev/null +++ b/mast/lib/serial/uncall.updoc @@ -0,0 +1,84 @@ +#!/usr/bin/env rune + +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + + ? def makeUncall := + # value: + + ? def loaders := [, , unsafe__uriGetter] + # value: [, , ] + + ? def uncall := makeUncall(loaders) + # value: + + ? uncall(__makeList) + # value: [, "get", ["e.elib.tables.ConstList"]] + + ? uncall([3, 'c']) + # value: [, "run", [3, 'c']] + +Testing object-defined transparency + + ? def selfish { + > to __optUncall() :[any, String, any[]] { + > [37, "whatMeWorry", [2, 'x']] + > } + > } + # value: + + ? uncall(selfish) + # value: [37, "whatMeWorry", [2, 'x']] + +Testing cauterizer + + ? def testObj {} + # value: + + ? uncall(testObj) == null + # value: true + + ? def opaque__uriGetter {} + # value: + + ? def cauterizingUncall := makeUncall.cauterizer(uncall, opaque__uriGetter) + # value: + + ? cauterizingUncall(testObj) + # value: [, "get", ["testObj"]] + +Testing amplifier + + ? def [xSealer,xUnsealer] := ("X") + # value: [, ] + + ? def forXsEyesOnly { + > to __optSealedDispatch(brand) :near { + > if (xSealer.getBrand() == brand) { + > xSealer.seal([3, "add", [4]]) + > } else { + > null + > } + > } + > } + # value: + + ? uncall(forXsEyesOnly) == null + # value: true + + ? def amplifyingUncall := makeUncall.amplifier(uncall, xUnsealer) + # value: + + ? amplifyingUncall(forXsEyesOnly) + # value: [3, "add", [4]] + +Testing onlySelfless + + ? def onlySelflessUncall := makeUncall.onlySelfless(uncall) + # value: + + ? onlySelflessUncall([3, 'c']) + # value: [, "run", [3, 'c']] + + ? onlySelflessUncall(selfish) == null + # value: true diff --git a/mast/lib/serial/uneval.updoc b/mast/lib/serial/uneval.updoc new file mode 100755 index 00000000..fd56749c --- /dev/null +++ b/mast/lib/serial/uneval.updoc @@ -0,0 +1,165 @@ +#!/usr/bin/env rune + +# Copyright 2002 Combex, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ + + ? def deSubgraphKit := + # value + + ? def x := [1, x, 3] + # value: [1, ***CYCLE***, 3] + + ? def deSrcKit := + # value: + + ? deSubgraphKit.recognize(x, deSrcKit.makeBuilder()) + # value: "def t_0 := [def t_2 := 1, t_0, def t_4 := 3] + # " + + ? def deAssemblyKit := + # value + + ? deSubgraphKit.recognize(x, deAssemblyKit.makeBuilder()) + # value: "OP_PROMISE # [t_0, t_1] + # OP_IMPORT(\"__makeList\") + # OP_PROMISE # [t_2, t_3] + # OP_LIT_WHOLENUM(1) + # OP_DEFREC(3) + # OP_IBID(0) + # OP_PROMISE # [t_4, t_5] + # OP_LIT_WHOLENUM(3) + # OP_DEFREC(5) + # OP_CALL(\"run\", 3) + # OP_DEFREC(1) + # OP_ROOT + # " + + ? def deENodeKit := + # value + + ? def ast := deSubgraphKit.recognize(x, deENodeKit.makeBuilder()) + # value: e`def [t_0 :any, t_1 :any] := Ref.promise() + # t_1.resolve(__makeList.run(1, t_0, 3)) + # t_0` + + ? deSubgraphKit.recognize(x, deSrcKit.makeBuilder()) + # value: "def t_0 := [def t_2 := 1, t_0, def t_4 := 3] + # " + + ? deENodeKit.recognize(ast, deSrcKit.makeBuilder()) + # value: "def t_0 := [1, t_0, 3] + # " + + ? deENodeKit.recognize(ast, deAssemblyKit.makeBuilder()) + # value: "OP_PROMISE # [t_0, t_1] + # OP_IMPORT(\"__makeList\") + # OP_LIT_WHOLENUM(1) + # OP_IBID(0) + # OP_LIT_WHOLENUM(3) + # OP_CALL(\"run\", 3) + # OP_DEFREC(1) + # OP_ROOT + # " + +OP_PROMISE # [t_0, t_1] +OP_IMPORT(\"__makeList\") # [__makeList] +OP_LIT_WHOLENUM(1) # [__makeList, 1] +OP_IBID(0) # [__makeList, 1, t_0] +OP_LIT_WHOLENUM(3) # [__makeList, 1, t_0, 3] +OP_CALL(\"run\", 3) # [[1, t_0, 3]] +OP_DEFREC(1) # def t_0 := TOS +OP_ROOT # return TOS + + ? def deBytecodeKit := + # value: + + ? def code := deENodeKit.recognize(ast, deBytecodeKit.makeBuilder()) + > code.size() + # value: 30 + + ? deBytecodeKit.recognize(code, deAssemblyKit.makeBuilder()) + # value: "OP_PROMISE # [t_0, t_1] + # OP_IMPORT(\"__makeList\") + # OP_LIT_WHOLENUM(1) + # OP_IBID(0) + # OP_LIT_WHOLENUM(3) + # OP_CALL(\"run\", 3) + # OP_DEFREC(1) + # OP_ROOT + # " + + ? deSubgraphKit.recognize(x, deSubgraphKit.makeBuilder()) + # value: [1, ***CYCLE***, 3] + + ? deBytecodeKit.recognize(code, deSubgraphKit.makeBuilder()) + # value: [1, ***CYCLE***, 3] + + ? def unQuote := + # value: + + ? def testUneval(obj) :near { + > def ast := deSubgraphKit.recognize(obj, deENodeKit.makeBuilder()) + > def src := deENodeKit.recognize(ast, deSrcKit.makeBuilder()) + > unQuote(src.trim()) + > } + # value: + + ? testUneval(x) + # value: def t_0 := [1, t_0, 3] + + ? deSubgraphKit.recognize(introducer.getNetConfig(), + > deENodeKit.makeBuilder()) + # value: e`import__uriGetter.get("net.vattp.data.NetConfig").\ + # run(def t_6 :any := __makeList.run(), t_6, t_6)` + + ? testUneval(introducer.getNetConfig()) + # value: (def t_0 := [], t_0, t_0) + + ? def makeRemoteCall := + # value: + + ? def rc := makeRemoteCall(3, "add", [5]) + # value: + + ? testUneval(rc) + # value: 3.add(5) + + ? def opaque__uriGetter { + > to get(str :String) :any { throw(`couldn't serialize: $str`) } + > to getOptNameFor(obj) :nullOk(String) { null } + > } + # value: + + ? def makeUncall := + # value: + + ? def uncall := makeUncall() + # value: + + ? def uncall2 := makeUncall.cauterizer(uncall, opaque__uriGetter) + # value: + + ? def makeUneval := + # value: + + ? def scope2 := deSubgraphKit.getMinimalScope().with("opaque__uriGetter", + > opaque__uriGetter) + + ? def uneval2 := makeUneval(null, uncall2, scope2) + # value: + + ? def testUneval2(obj) :near { + > def ast := uneval2(obj, deENodeKit.makeBuilder()) + > def src := deENodeKit.recognize(ast, deSrcKit.makeBuilder()) + > unQuote(src.trim()) + > } + # value: + + ? def xxx := thunk{} + # value: <...__main$1> + + ? testUneval([xxx]) + # problem: Can't uneval <...__main$1> + + ? testUneval2([xxx]) + # value: [] diff --git a/mast/lib/tables/makeCycleBreaker.mt b/mast/lib/tables/makeCycleBreaker.mt new file mode 100755 index 00000000..a09b3f0e --- /dev/null +++ b/mast/lib/tables/makeCycleBreaker.mt @@ -0,0 +1,128 @@ +#!/usr/bin/env rune + +# Copyright 2003 Hewlett Packard, Inc. under the terms of the MIT X license +# found at http://www.opensource.org/licenses/mit-license.html ................ +exports (makeCycleBreaker) + +def makeTraversalKey :DeepFrozen := _equalizer.makeTraversalKey + +def readOnly(m) as DeepFrozen: + return if (m =~ _:Map): + m + else: + object ro extends m: + to put(_k, _v): + throw("read only") + + +object it as DeepFrozen { + +# /** +# * Provides CycleBeaker equivalents to any of the operations defined by +# * {@link EMap}. +# *

+# * This used as the super-object for wrapping an EMap independent of +# * whether the original is a ConstMap or a FlexMap. Because these are exactly +# * the read-only operations, this is also used directly as the object that +# * corresponds to an {@link ROMap} (a Read-Only EMap). +# * +# * @param roPMap Should either be a {@link ROMap} or a {@link ConstMap}, ie, a +# * valid response from {@link EMap#readOnly()}. This should be a +# * PowerMap, ie, all the keys in this map should be +# * {@link TraversalKey}s. +# * @author Mark S. Miller +# */ +method makeROCycleBreaker(roPMap) :Near { + object readOnlyCycleBreaker { + + method diverge() :Near { it.makeFlexCycleBreaker(roPMap.diverge()) } + method snapshot() :Near { it.makeConstCycleBreaker(roPMap.snapshot()) } + # The following implementation technique is only possible because we're + # using delegation rather than inheritance. + method readOnly() :Near { readOnlyCycleBreaker } + + method maps(key) :Bool { roPMap.maps(makeTraversalKey(key)) } + method get(key) :Any { roPMap[makeTraversalKey(key)] } + method fetch(key, instead) :Any { roPMap.fetch(makeTraversalKey(key),instead) } + + method with(key, val) :Near { + it.makeConstCycleBreaker(roPMap.with(makeTraversalKey(key), val)) + } + method without(key) :Near { + it.makeConstCycleBreaker(roPMap.without(makeTraversalKey(key))) + } + + method getPowerMap() :Near { readOnly(roPMap) } + } +} + +# /** +# * +# * +# * @author Mark S. Miller +# */ +method makeFlexCycleBreaker(flexPMap) :Near { + # Note that this is just delegation, not inheritance, in that we are not + # initializing the template with flexCycleBreaker. By the same token, + # the template makes no reference to self. + object flexCycleBreaker extends it.makeROCycleBreaker(readOnly(flexPMap)) { + + to put(key, value) :Void { flexPMap[makeTraversalKey(key)] := value } + + method getPowerMap() :Near { flexPMap } + + method removeKey(key) :Void { flexPMap.removeKey(makeTraversalKey(key)) } + } +} + +# /** +# * +# * +# * @author Mark S. Miller +# */ +method makeConstCycleBreaker(constPMap) :Near { + object constCycleBreaker extends it.makeROCycleBreaker(readOnly(constPMap)) { + + method getPowerMap() :Near { constPMap.snapshot() } + } +} + +method EMPTYConstCycleBreaker() { it.makeConstCycleBreaker([].asMap()) } + +} + +# /** +# * A CycleBreaker is like an EMap except that it accepts unsettled +# * references as keys. +# *

+# * This has somewhat counter-intuitive results, as is to be documented at +# * Reference Sameness. +# *

+# * With a CycleBreaker, one can write alorithms to finitely walk infinite +# * partial structures like +# *

    def x := [x, p]
+# * even when p is an unresolved Promise. Without CycleBreaker (or +# * rather, without the primitive it uses, {@link TraversalKey}) this does not +# * otherwise seem possible. +# * +# * @author Mark S. Miller +# */ +object makeCycleBreaker as DeepFrozen { + + # /** + # * + # */ + method run() :Near { it.EMPTYConstCycleBreaker() } + + # /** + # * + # */ + to byInverting(map) :Near { + def result := it.EMPTYConstCycleBreaker().diverge() + for key => value in (map) { + result[value] := key + } + return result.snapshot() + } +} diff --git a/mast/tools/capnpc.mt b/mast/tools/capnpc.mt index c43e7e73..cee1d6d8 100644 --- a/mast/tools/capnpc.mt +++ b/mast/tools/capnpc.mt @@ -339,7 +339,7 @@ def fieldWriter(nodeMap, node, _, f, fname) as DeepFrozen: astBuilder.MapPatternAssoc( L(f.name()), astBuilder.FinalPattern(N(name), null, null), - N("absent"), + if (f._which() == 1) { m`[].asMap()` } else { N("absent") }, null)], null, null) def structWriterCall := astBuilder.MethodCallExpr(m`structWriter`, innerStructWriter, [m`listPos + (i * $structSize * 8)`, m`builder`] + [for name => _ in (fields) N(name)], [], null)