From 3f28c60ab8cc340910fdb9b2deef2149270bf2e8 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 26 Aug 2021 21:25:39 -0600 Subject: [PATCH] Remove a bunch of old code, update the README --- BUILD | 5 - NOTES | 431 ----------------------------------- README.md | 132 ++--------- expr/build.go | 219 ------------------ expr/build_test.go | 99 -------- expr/ctx.go | 72 ------ expr/expr.go | 210 ----------------- expr/parse.go | 299 ------------------------- expr/parse_test.go | 149 ------------ expr/util.go | 40 ---- graph/graph.go | 522 ------------------------------------------- graph/graph_test.go | 386 -------------------------------- lang/lang.go | 118 ---------- lang/match.go | 54 ----- lang/match_test.go | 66 ------ main.go | 47 ---- sandbox/list/list.go | 39 ---- vm/cmds.go | 280 ----------------------- vm/vm.go | 129 ----------- vm/vm_test.go | 84 ------- 20 files changed, 21 insertions(+), 3360 deletions(-) delete mode 100644 BUILD delete mode 100644 NOTES delete mode 100644 expr/build.go delete mode 100644 expr/build_test.go delete mode 100644 expr/ctx.go delete mode 100644 expr/expr.go delete mode 100644 expr/parse.go delete mode 100644 expr/parse_test.go delete mode 100644 expr/util.go delete mode 100644 graph/graph.go delete mode 100644 graph/graph_test.go delete mode 100644 lang/lang.go delete mode 100644 lang/match.go delete mode 100644 lang/match_test.go delete mode 100644 main.go delete mode 100644 sandbox/list/list.go delete mode 100644 vm/cmds.go delete mode 100644 vm/vm.go delete mode 100644 vm/vm_test.go diff --git a/BUILD b/BUILD deleted file mode 100644 index 1c02dec..0000000 --- a/BUILD +++ /dev/null @@ -1,5 +0,0 @@ -RELEASE=RELEASE_381 # this may have to be changed based on llvm version -svn co https://llvm.org/svn/llvm-project/llvm/tags/$RELEASE/final $GOPATH/src/llvm.org/llvm -cd $GOPATH/src/llvm.org/llvm/bindings/go -./build.sh -go install llvm.org/llvm/bindings/go/llvm diff --git a/NOTES b/NOTES deleted file mode 100644 index 02cdd7f..0000000 --- a/NOTES +++ /dev/null @@ -1,431 +0,0 @@ -Been thinking about the stack and heap a lot. It would be possible, though -possibly painful, to enforce a language with no global heap. The question really -is: what are the principles which give reason to do so? What are the principles -of this language, period? The principles are different than the use-cases. They -don't need to be logically rigorous (at first anyway). - -########## - -I need to prioritize the future of this project a bit more. I've been thinking -I'm going to figure this thing out at this level, but I shouldn't even be -working here without a higher level view. - -I can't finish this project without financial help. I don't think I can get a v0 -up without financial help. What this means at minimum, no matter what, I'm going -to have to: - -- Develop a full concept of the language that can get it to where I want to go - - Figure out where I want it to go -- Write the concept into a manifesto of the language -- Write the concept into a proposal for course of action to take in developing - the language further - -I'm unsure about what this language actually is, or is actually going to look -like, but I'm sure of those things. So those are the lowest hanging fruit, and I -should start working on them pronto. It's likely I'll need to experiment with -some ideas which will require coding, and maybe even some big ideas, but those -should all be done under the auspices of developing the concepts of the -language, and not the compiler of the language itself. - -######### - -Elemental types: - -* Tuples -* Arrays -* Integers - -######### - -Been doing thinking and research on ginger's elemental types and what their -properties should be. Ran into roadblock where I was asking myself these -questions: - -* Can I do this without atoms? -* What are different ways atoms can be encoded? -* Can I define language types (elementals) without defining an encoding for - them? - -I also came up with two new possible types: - -* Stream, effectively an interface which produces discreet packets (each has a - length), where the production of one packet indicates the size of the next one - at the same time. -* Tagged, sort of like a stream, effectively a type which says "We don't know - what this will be at compile-time, but we know it will be prefixed with some - kind of tag indicating its type and size. - * Maybe only the size is important - * Maybe precludes user defined types that aren't composites of the - elementals? Maybe that's ok? - -Ran into this: -https://www.ps.uni-saarland.de/~duchier/python/continuations.htm://www.ps.uni-saarland.de/~duchier/python/continuations.html -https://en.wikipedia.org/wiki/Continuation#First-class_continuations - -which is interesting. A lot of my problems now are derived from stack-based -systems and their need for knowing the size input and output data, continuations -seem to be an alternative system? - -I found this: - -http://lambda-the-ultimate.org/node/4512 - -I don't understand any of it, I should definitely learn feather - -I should finish reading this: -http://www.blackhat.com/presentations/bh-usa-07/Ferguson/Whitepaper/bh-usa-07-ferguson-WP.pdf - -######### - -Ok, so I'm back at this for the first time in a while, and I've got a good thing -going. The vm package is working out well, Using tuples and atoms as the basis -of a language is pretty effective (thanks erlang!). I've got basic variable -assignment working as well. No functions yet. Here's the things I still need to -figure out or implement: - -* lang - * constant size arrays - * using them for a "do" macro - * figure out constant, string, int, etc... look at what erlang's actual - primitive types are for a hint - * figure out all needed macros for creating and working with lang types -* vm - * figure out the differentiation between compiler macros and runtime calls - * probably separate the two into two separate call systems - * the current use of varCtx is still pretty ugly, the do macro might help - clean it up -* functions - * are they a primitive? I guess so.... - * declaration and type - * variable deconstruction - * scoping/closures -* compiler macros, need vm's Run to output a lang.Term -* need to learn about linking - * figure out how to include llvm library in compiled binary and make it - callable. runtime macros will come from this - * linking in of other ginger code? or how to import in general -* comiler, a general purpose binary for taking ginger code and turning it - into machine code using the vm package - * swappable syntax, including syntax-dependent macros -* close the loop? - -############ - -I really want contexts to work. They _feel_ right, as far as abstractions go. -And they're clean, if I can work out the details. - -Just had a stupid idea, might as well write it down though. - -Similar to how the DNA and RNA in our cells work, each Context is created with -some starting set of data on it. This will be the initial protein block. Based -on the data there some set of Statements (the RNA) will "latch" on and do -whatever work they're programmed to do. That work could include making new -Contexts and "releasing" them into the ether, where they would get latched onto -(or not). - -There's so many problems with this idea, it's not even a little viable. But here -goes: - -* Order of execution becomes super duper fuzzy. It would be really difficult to - think about how your program is actually going to work. - -* Having Statement sets just latch onto Contexts is super janky. They would get - registered I guess, and it would be pretty straightforward to differentiate - one Context from another, but what about conflicts? If two Statements want to - latch onto the same Context then what? If we wanted to keep the metaphor one - would just get randomly chosen over the other, but obviously that's insane. - -############ - -I explained some of this to ibrahim already, but I might as well get it all -down, cause I've expanded on it a bit since. - -Basically, ops (functions) are fucking everything up. The biggest reason for -this is that they are really really hard to implement without a type annotation -system. The previous big braindump is about that, but basically I can't figure -out a way that feels clean and good enough to be called a "solution" to type -inference. I really don't want to have to add type annotations just to support -functions, at least not until I explore all of my options. - -The only other option I've come up with so far is the context thing. It's nice -because it covers a lot of ground without adding a lot of complexity. Really the -biggest problem with it is it doesn't allow for creating new things which look -like operations. Instead, everything is done with the %do operator, which feels -janky. - -One solution I just thought of is to get rid of the %do operator and simply make -it so that a list of Statements can be used as the operator in another -Statement. This would _probably_ allow for everything that I want to do. One -outstanding problem I'm facing is figuring out if all Statements should take a -Context or not. - -* If they did it would be a lot more explicit what's going on. There wouldn't be - an ethereal "this context" that would need to be managed and thought about. It - would also make things like using a set of Statements as an operator a lot - more straightforward, since without Contexts in the Statement it'll be weird - to "do" a set of Statements in another Context. - -* On the other hand, it's quite a bit more boilerplate. For the most part most - Statements are going to want to be run in "this" context. Also this wouldn't - really decrease the number of necessary macros, since one would still be - needed in order to retrieve the "root" Context. - -* One option would be for a Statement's Context to be optional. I don't really - like this option, it makes a very fundamental datatype (a Statement) a bit - fuzzier. - -* Another thing to think about is that I might just rethink how %bind works so - that it doesn't operate on an ethereal "this" Context. %ctxbind is one attempt - at this, but there's probably other ways. - -* One issue I just thought of with having a set of Statements be used as an - operator is that the argument to that Statement becomes.... weird. What even - is it? Something the set of Statements can access somehow? Then we still need - something like the %in operator. - -Let me backtrack a bit. What's the actual problem? The actual thing I'm -struggling with is allowing for code re-use, specifically pure functions. I -don't think there's any way anyone could argue that pure functions are not an -effective building block in all of programming, so I think I can make that my -statement of faith: pure functions are good and worthwhile, impure functions -are.... fine. - -Implementing them, however, is quite difficult. Moreso than I thought it would -be. The big inhibitor is the method by which I actually pass input data into the -function's body. From an implementation standpoint it's difficult because I -*need* to know how many bytes on the stack the arguments take up. From a syntax -standpoint this is difficult without a type annotation system. And from a -usability standpoint this is difficult because it's a task the programmer has to -do which doesn't really have to do with the actual purpose or content of the -function, it's just a book-keeping exercise. - -So the stack is what's screwing us over here. It's a nice idea, but ultimately -makes what we're trying to do difficult. I'm not sure if there's ever going to -be a method of implementing pure functions that doesn't involve argument/return -value copying though, and therefore which doesn't involve knowing the byte size -of your arguments ahead of time. - -It's probably not worth backtracking this much either. For starters, cpus are -heavily optimized for stack based operations, and much of the way we currently -think about programming is also based on the stack. It would take a lot of -backtracking if we ever moved to something else, if there even is anything else -worth moving to. - -If that's the case, how is the stack actually used then? - -* There's a stack pointer which points at an address on the stack, the stack - being a contiguous range of memory addresses. The place the stack points to is - the "top" of the stack, all higher addresses are considered unused (no matter - what's in them). All the values in the stack are available to the currently - executing code, it simply needs to know either their absolute address or their - relative position to the stack pointer. - -* When a function is "called" the arguments to it are copied onto the top of the - stack, the stack pointer is increased to reflect the new stack height, and the - function's body is jumped to. Inside the body the function need only pop - values off the stack as it expects them, as long as it was called properly it - doesn't matter how or when the function was called. Once it's done operating - the function ensures all the input values have been popped off the stack, and - subsequently pushes the return values onto the stack, and jumps back to the - caller (the return address was also stored on the stack). - -That's not quite right, but it's close enough for most cases. The more I'm -reading about this the more I think it's not going to be worth it to backtrack -passed the stack. There's a lot of compiler and machine specific crap that gets -involved at that low of a level, and I don't think it's worth getting into it. -LLVM did all of that for me, I should learn how to make use of that to make what -I want happen. - -But what do I actually want? That's the hard part. I guess I've come full -circle. I pretty much *need* to use llvm functions. But I can't do it without -declaring the types ahead of time. Ugghh. - -################################ - -So here's the current problem: - -I have the concept of a list of statements representing a code block. It's -possible/probable that more than this will be needed to represent a code block, -but we'll see. - -There's two different ways I think it's logical to use a block: - -* As a way of running statements within a new context which inherits all of its - bindings from the parent. This would be used for things like if statements and - loops, and behaves the way a code block behaves in most other languages. - -* To define a operator body. An operator's body is effectively the same as the - first use-case, except that it has input/output as well. An operator can be - bound to an identifier and used in any statement. - -So the hard part, really, is that second point. I have the first done already. -The second one isn't too hard to "fake" using our current context system, but it -can't be made to be used as an operator in a statement. Here's how to fake it -though: - -* Define the list of statements -* Make a new context -* Bind the "input" bindings into the new context -* Run %do with that new context and list of statements -* Pull the "output" bindings out of that new context - -And that's it. It's a bit complicated but it ultimately works and effectively -inlines a function call. - -It's important that this looks like a normal operator call though, because I -believe in guy steele. Here's the current problems I'm having: - -* Defining the input/output values is the big one. In the inline method those - were defined implicitly based on what the statements actually use, and the - compiler would fail if any were missing or the wrong type. But here we ideally - want to define an actual llvm function and not inline everytime. So we need to - somehow "know" what the input/output is, and their types. - - * The output value isn't actually *that* difficult. We just look at the - output type of the last statement in the list and use that. - - * The input is where it gets tricky. One idea would be to use a statement - with no input as the first statement in the list, and that would define - the input type. The way macros work this could potentially "just work", - but it's tricky. - - * It would also be kind of difficult to make work with operators that take - in multiple parameters too. For example, `bind A, 1` would be the normal - syntax for binding, but if we want to bind an input value it gets weirder. - - * We could use a "future" kind of syntax, like `bind A, _` or something - like that, but that would requre a new expression type and also just - be kind of weird. - - * We could have a single macro which always returns the input, like - `%in` or something. So the bind would become `bind A, %in` or - `bind (A, B), %in` if we ever get destructuring. This isn't a terrible - solution, though a bit unfortunate in that it could get confusing with - different operators all using the same input variable effectively. It - also might be a bit difficult to implement, since it kind of forces us - to only have a single argument to the LLVM function? Hard to say how - that would work. Possibly all llvm functions could be made to take in - a struct, but that would be ghetto af. Not doing a struct would take a - special interaction though.... It might not be possible to do this - without a struct =/ - -* Somehow allowing to define the context which gets used on each call to the - operator, instead of always using a blank one, would be nice. - - * The big part of this problem is actually the syntax for calling the - operator. It's pretty easy to have this handled within the operator by the - %thisctx macro. But we want the operator to be callable by the same syntax - as all other operator calls, and currently that doesn't have any way of - passing in a new context. - - * Additionally, if we're implementing the operator as an LLVM function then - there's not really any way to pass in that context to it without making - those variables global or something, which is shitty. - -* So writing all this out it really feels like I'm dealing with two separate - types that just happen to look similar: - - * Block: a list of statements which run with a variable context. - - * Operator: a list of statements which run with a fixed (empty?) context, - and have input/output. - -* There's so very nearly a symmetry there. Things that are inconsistent: - - * A block doesn't have input/output - - * It sort of does, in the form of the context it's being run with and - %ctxget, but not an explicit input/output like the operator has. - - * If this could be reconciled I think this whole shitshow could be made - to have some consistency. - - * Using %in this pretty much "just works". But it's still weird. Really - we'd want to turn the block into a one-off operator everytime we use - it. This is possible. - - * An operator's context must be empty - - * It doesn't *have* to be, defining the ctx which goes with the operator - could be part of however an operator is created. - -* So after all of that, I think operators and blocks are kind of the same. - - * They both use %in to take in input, and both output using the last statement - in their list of statements. - - * They both have a context bound to them, operators are fixed but a block - changes. - - * An operator is a block with a bound context. - -##############@@@@@@@@@#$%^&^%$#@#$%^&* - -* New problem: type inference. LLVM requires that a function's definition have - the type specified up-front. This kind of blows. Well actually, it blows a lot - more than kind of. There's two things that need to be infered from a List of - Statements then: the input type and the output type. There's two approaches - I've thought of in the current setup. - - * There's two approaches to determining the type of an operator: analyze the - code as ginger expressions, or build the actual llvm structures and - analyze those. - - * Looking at the ginger expressions is definitely somewhat fuzzy. We can - look at all the statements and sub-statements until we find an - instance of %in, then look at what that's in input into. But if it's - simply binding into an Identifier then we have to find the identifier. - If it's destructuring then that gets even *more* complicated. - - * Destructuring is what really makes this approach difficult. - Presumably there's going to be a function that takes in an - Identifier (or %in I guess?) and a set of Statements and returns - the type for that Identifier. If we find that %in is destructured - into a tuple then we would run that function for each constituent - Identifier and put it all together. But then this inference - function is really coupled to %bind, which kind of blows. Also we - may one day want to support destructuring into non-tuples as well, - which would make this even harder. - - * We could make it the job of the macro definition to know its input - and output types, as well as the types of any bindings it makes. - That places some burden on user macros in the future, but then - maybe it can be inferred for user macros? That's a lot of hope. It - would also mean the macro would need the full set of statements - that will ever run in the same Context as it, so it can determine - the types of any bindings it makes. - - * The second method is to build the statements into LLVM structures and - then look at those structures. This has the benefit of being - non-ambiguous once we actually find the answer. LLVM is super strongly - typed, and re-iterates the types involved for every operation. So if - the llvm builder builds it then we need only look for the first usage - of every argument/return and we'll know the types involved. - - * This requires us to use structs for tuples, and not actually use - multiple arguments. Otherwise it won't be possible to know the - difference between a 3 argument function and a 4 argument one - which doesn't use its 4th argument (which shouldn't really happen, - but could). - - * The main hinderence is that the llvm builder is really not - designed for this sort of thing. We could conceivably create a - "dummy" function with bogus types and write the body, analyze the - body, erase the function, and start over with a non-dummy - function. But it's the "analyze the body" step that's difficult. - It's difficult to find the types of things without the llvm.Value - objects in hand, but since building is set up as a recursive - process that becomes non-trivial. This really feels like the way - to go though, I think it's actually doable. - - * This could be something we tack onto llvmVal, and then make - Build return extra data about what types the Statements it - handled input and output. - -* For other setups that would enable this a bit better, the one that keeps - coming to mind is a more pipeline style system. Things like %bind would need - to be refactored from something that takes a Tuple to something that only - takes an Identifier and returns a macro which will bind to that Identifier. - This doesn't *really* solve the type problem I guess, since whatever is input - into the Identifier's bind doesn't necessarily have a type attached to it. - Sooo yeah nvm. diff --git a/README.md b/README.md index 97144ef..fd77177 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,28 @@ -# Ginger - holy fuck again? +# Ginger -## The final result. A language which can do X +Fibonacci function in ginger: -- Support my OS - - Compile on many architectures - - Be low level and fast (effectively c-level) - - Be well defined, using a simple syntax - - Extensible based on which section of the OS I'm working on - - Good error messages +``` +fib { + decr { out add(in, -1) } -- Support other programmers and other programming areas - - Effectively means able to be used in most purposes - - Able to be quickly learned - - Able to be shared - - Free - - New or improved components shared between computers/platforms/people + out { + n 0(in), + a 1(in), + b 2(in), -- Support itself - - Garner a team to work on the compiler - - Team must not require my help for day-to-day - - Team must stick to the manifesto, either through the design or through - trust + out if( + zero?(n), + a, + recur(decr(n), b, add(a,b)) + ) -## The language: A manifesto, defines the concept of the language + }(in, 0, 1) +} +``` -- Quips - - Easier is not better +Usage of the function to generate the 6th fibonnaci number: -- Data as the language - - Differentiation between "syntax" and "language", parser vs compiler - - Syntax defines the form which is parsed - - The parser reads the syntax forms into data structures - - Language defines how the syntax is read into data structures and - "understood" (i.e. and what is done with those structures). - - A language maybe have multiple syntaxes, if they all parse into - the same underlying data structures they can be understood in the - same way. - - A compiler turns the parsed language into machine code. An - interpreter performs actions directly based off of the parsed - language. - -- Types, instances, and operations - - A language has a set of elemental types, and composite types - - "The type defines the [fundamental] operations that can be done on the - data, the meaning of the data, and the way values of that type can be - stored" - - Elemental types are all forms of numbers, since numbers are all a - computer really knows - - Composite types take two forms: - - Homogeneous: all composed values are the same type (arrays) - - Heterogeneous: all composed values are different - - If known size and known types per-index, tuples - - A 0-tuple is kind of special, and simply indicates absence of - any value. - - A third type, Any, indicates that the type is unknown at compile-time. - Type information must be passed around with it at runtime. - - An operation has an input and output. It does some action on the input - to produce the output (presumably). An operation may be performed as - many times as needed, given any value of the input type. The types of - both the input and output are constant, and together they form the - operation's type. - - A value is an instance of a type, where the type is known at compile-time - (though the type may be Any). Multiple values may be instances of the same - type. E.g.: 1 and 2 are both instances of int - - A value is immutable - - TODO value is a weird word, since an instance of a type has both a - type and value. I need to think about this more. Instance might be a - better name - -- Stack and scope - - A function call operates within a scope. The scope had arguments passed - into it. - - When a function calls another, that other's scope is said to be "inside" - the caller's scope. - - A pure function only operates on the arguments passed into it. - - A pointer allows for modification outside of the current scope, but only a - pointer into an outer scope. A function which does this is "impure" - -- Built-in - - Elementals - - ints (n-bit) - - tuples - - stack arrays - - indexable - - head/tail - - reversible (?) - - appendable - - functions (?) - - pointers (?) - - Any (?) - - Elementals must be enough to define the type of a variable - - Ability to create and modify elmental types - - immutable, pure functions - - Other builtin functionality: - - Load/call linked libraries - - Comiletime macros - - Red/Blue - -- Questions - - Strings need to be defined in terms of the built-in types, which would be - an array of lists. But this means I'm married to that definition of a - string, it'd be difficult for anyone to define their own and have it - interop. Unless "int" was some kind of macro type that did some fancy - shit, but that's kind of gross. - - An idea of the "equality" of two variables being tied not just to their - value but to the context in which they were created. Would aid in things - like compiler tagging. - - There's a "requirement loop" of things which need figuring out: - - function structure - - types - - seq type - - stack/scope - - Most likely I'm going to need some kind of elemental type to indicate - something should happen at compile-time and not runtime, or the other way - around. - -## The roadmap: A plan of action for tackling the language +``` +fib(5) +``` diff --git a/expr/build.go b/expr/build.go deleted file mode 100644 index b6a3887..0000000 --- a/expr/build.go +++ /dev/null @@ -1,219 +0,0 @@ -package expr - -import ( - "fmt" - "log" - - "llvm.org/llvm/bindings/go/llvm" -) - -func init() { - log.Printf("initializing llvm") - llvm.LinkInMCJIT() - llvm.InitializeNativeTarget() - llvm.InitializeNativeAsmPrinter() -} - -type BuildCtx struct { - B llvm.Builder - M llvm.Module -} - -func NewBuildCtx(moduleName string) BuildCtx { - return BuildCtx{ - B: llvm.NewBuilder(), - M: llvm.NewModule(moduleName), - } -} - -func (bctx BuildCtx) Build(ctx Ctx, stmts ...Statement) llvm.Value { - var lastVal llvm.Value - for _, stmt := range stmts { - if e := bctx.BuildStmt(ctx, stmt); e != nil { - if lv, ok := e.(llvmVal); ok { - lastVal = llvm.Value(lv) - } else { - log.Printf("BuildStmt returned non llvmVal from %v: %v (%T)", stmt, e, e) - } - } - } - if (lastVal == llvm.Value{}) { - lastVal = bctx.B.CreateRetVoid() - } - return lastVal -} - -func (bctx BuildCtx) BuildStmt(ctx Ctx, s Statement) Expr { - log.Printf("building: %v", s) - switch o := s.Op.(type) { - case Macro: - return ctx.Macro(o)(bctx, ctx, s.Arg) - case Identifier: - s2 := s - s2.Op = ctx.Identifier(o).(llvmVal) - return bctx.BuildStmt(ctx, s2) - case Statement: - s2 := s - s2.Op = bctx.BuildStmt(ctx, o) - return bctx.BuildStmt(ctx, s2) - case llvmVal: - arg := bctx.buildExpr(ctx, s.Arg).(llvmVal) - out := bctx.B.CreateCall(llvm.Value(o), []llvm.Value{llvm.Value(arg)}, "") - return llvmVal(out) - default: - panic(fmt.Sprintf("non op type %v (%T)", s.Op, s.Op)) - } -} - -// may return nil if e is a Statement which has no return -func (bctx BuildCtx) buildExpr(ctx Ctx, e Expr) Expr { - return bctx.buildExprTill(ctx, e, func(Expr) bool { return false }) -} - -// like buildExpr, but will stop short and stop recursing when the function -// returns true -func (bctx BuildCtx) buildExprTill(ctx Ctx, e Expr, fn func(e Expr) bool) Expr { - if fn(e) { - return e - } - - switch ea := e.(type) { - case llvmVal: - return e - case Int: - return llvmVal(llvm.ConstInt(llvm.Int64Type(), uint64(ea), false)) - case Identifier: - return ctx.Identifier(ea) - case Statement: - return bctx.BuildStmt(ctx, ea) - case Tuple: - // if the tuple is empty then it is a void - if len(ea) == 0 { - return llvmVal(llvm.Undef(llvm.VoidType())) - } - - ea2 := make(Tuple, len(ea)) - for i := range ea { - ea2[i] = bctx.buildExprTill(ctx, ea[i], fn) - } - - // if the fields of the tuple are all llvmVal then we can make a proper - // struct - vals := make([]llvm.Value, len(ea2)) - typs := make([]llvm.Type, len(ea2)) - for i := range ea2 { - if v, ok := ea2[i].(llvmVal); ok { - val := llvm.Value(v) - vals[i] = val - typs[i] = val.Type() - } else { - return ea2 - } - } - - str := llvm.Undef(llvm.StructType(typs, false)) - for i := range vals { - str = bctx.B.CreateInsertValue(str, vals[i], i, "") - } - return llvmVal(str) - case List: - ea2 := make(Tuple, len(ea)) - for i := range ea { - ea2[i] = bctx.buildExprTill(ctx, ea[i], fn) - } - return ea2 - case Ctx: - return ea - default: - panicf("%v (type %T) can't express a value", ea, ea) - } - panic("go is dumb") -} - -func (bctx BuildCtx) buildVal(ctx Ctx, e Expr) llvm.Value { - return llvm.Value(bctx.buildExpr(ctx, e).(llvmVal)) -} - -// globalCtx describes what's available to *all* contexts, and is what all -// contexts should have as the root parent in the tree. -// -// We define in this weird way cause NewCtx actually references globalCtx -var globalCtx *Ctx -var _ = func() bool { - globalCtx = &Ctx{ - macros: map[Macro]MacroFn{ - "add": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - tup := bctx.buildExpr(ctx, e).(llvmVal) - a := bctx.B.CreateExtractValue(llvm.Value(tup), 0, "") - b := bctx.B.CreateExtractValue(llvm.Value(tup), 1, "") - return llvmVal(bctx.B.CreateAdd(a, b, "")) - }, - - // TODO this chould be a user macro!!!! WUT this language is baller - "bind": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - tup := bctx.buildExprTill(ctx, e, isIdentifier).(Tuple) - id := bctx.buildExprTill(ctx, tup[0], isIdentifier).(Identifier) - val := bctx.buildExpr(ctx, tup[1]) - ctx.Bind(id, val) - return NewTuple() - }, - - "ctxnew": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - return NewCtx() - }, - - "ctxthis": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - return ctx - }, - - "ctxbind": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - tup := bctx.buildExprTill(ctx, e, isIdentifier).(Tuple) - thisCtx := bctx.buildExpr(ctx, tup[0]).(Ctx) - id := bctx.buildExprTill(ctx, tup[1], isIdentifier).(Identifier) - thisCtx.Bind(id, bctx.buildExpr(ctx, tup[2])) - return NewTuple() - }, - - "ctxget": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - tup := bctx.buildExprTill(ctx, e, isIdentifier).(Tuple) - thisCtx := bctx.buildExpr(ctx, tup[0]).(Ctx) - id := bctx.buildExprTill(ctx, tup[1], isIdentifier).(Identifier) - return thisCtx.Identifier(id) - }, - - "do": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - tup := bctx.buildExprTill(ctx, e, isStmt).(Tuple) - thisCtx := tup[0].(Ctx) - for _, stmtE := range tup[1].(List) { - bctx.BuildStmt(thisCtx, stmtE.(Statement)) - } - return NewTuple() - }, - - "op": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - l := bctx.buildExprTill(ctx, e, isList).(List) - stmts := make([]Statement, len(l)) - for i := range l { - stmts[i] = l[i].(Statement) - } - - // TODO obviously this needs to be fixed - fn := llvm.AddFunction(bctx.M, "", llvm.FunctionType(llvm.Int64Type(), []llvm.Type{llvm.Int64Type()}, false)) - fnbl := llvm.AddBasicBlock(fn, "") - - prevbl := bctx.B.GetInsertBlock() - bctx.B.SetInsertPoint(fnbl, fnbl.FirstInstruction()) - out := bctx.Build(NewCtx(), stmts...) - bctx.B.CreateRet(out) - bctx.B.SetInsertPointAtEnd(prevbl) - return llvmVal(fn) - }, - - "in": func(bctx BuildCtx, ctx Ctx, e Expr) Expr { - fn := bctx.B.GetInsertBlock().Parent() - return llvmVal(fn.Param(0)) - }, - }, - } - return false -}() diff --git a/expr/build_test.go b/expr/build_test.go deleted file mode 100644 index fe5e6ac..0000000 --- a/expr/build_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package expr - -import ( - "fmt" - . "testing" - - "llvm.org/llvm/bindings/go/llvm" -) - -func buildTest(t *T, expected int64, stmts ...Statement) { - fmt.Println("-----------------------------------------") - ctx := NewCtx() - bctx := NewBuildCtx("") - - fn := llvm.AddFunction(bctx.M, "", llvm.FunctionType(llvm.Int64Type(), []llvm.Type{}, false)) - fnbl := llvm.AddBasicBlock(fn, "") - bctx.B.SetInsertPoint(fnbl, fnbl.FirstInstruction()) - out := bctx.Build(ctx, stmts...) - bctx.B.CreateRet(out) - - fmt.Println("######## dumping IR") - bctx.M.Dump() - fmt.Println("######## done dumping IR") - - if err := llvm.VerifyModule(bctx.M, llvm.ReturnStatusAction); err != nil { - t.Fatal(err) - } - - eng, err := llvm.NewExecutionEngine(bctx.M) - if err != nil { - t.Fatal(err) - } - - res := eng.RunFunction(fn, []llvm.GenericValue{}).Int(false) - if int64(res) != expected { - t.Errorf("expected:[%T]%v actual:[%T]%v", expected, expected, res, res) - } -} - -func TestAdd(t *T) { - buildTest(t, 2, - NewStatement(Macro("add"), Int(1), Int(1))) - buildTest(t, 4, - NewStatement(Macro("add"), Int(1), - NewStatement(Macro("add"), Int(1), Int(2)))) - buildTest(t, 6, - NewStatement(Macro("add"), - NewStatement(Macro("add"), Int(1), Int(2)), - NewStatement(Macro("add"), Int(1), Int(2)))) -} - -func TestBind(t *T) { - buildTest(t, 2, - NewStatement(Macro("bind"), Identifier("A"), Int(1)), - NewStatement(Macro("add"), Identifier("A"), Int(1))) - buildTest(t, 2, - NewStatement(Macro("bind"), Identifier("A"), Int(1)), - NewStatement(Macro("add"), Identifier("A"), Identifier("A"))) - buildTest(t, 2, - NewStatement(Macro("bind"), Identifier("A"), NewTuple(Int(1), Int(1))), - NewStatement(Macro("add"), Identifier("A"))) - buildTest(t, 3, - NewStatement(Macro("bind"), Identifier("A"), NewTuple(Int(1), Int(1))), - NewStatement(Macro("add"), Int(1), - NewStatement(Macro("add"), Identifier("A")))) - buildTest(t, 4, - NewStatement(Macro("bind"), Identifier("A"), NewTuple(Int(1), Int(1))), - NewStatement(Macro("add"), - NewStatement(Macro("add"), Identifier("A")), - NewStatement(Macro("add"), Identifier("A")))) -} - -func TestOp(t *T) { - incr := NewStatement(Macro("op"), - NewList( - NewStatement(Macro("add"), Int(1), NewStatement(Macro("in"))), - ), - ) - - // bound op - buildTest(t, 2, - NewStatement(Macro("bind"), Identifier("incr"), incr), - NewStatement(Identifier("incr"), Int(1))) - - // double bound op - buildTest(t, 3, - NewStatement(Macro("bind"), Identifier("incr"), incr), - NewStatement(Identifier("incr"), - NewStatement(Identifier("incr"), Int(1)))) - - // anon op - buildTest(t, 2, - NewStatement(incr, Int(1))) - - // double anon op - buildTest(t, 3, - NewStatement(incr, - NewStatement(incr, Int(1)))) -} diff --git a/expr/ctx.go b/expr/ctx.go deleted file mode 100644 index 5c05a3d..0000000 --- a/expr/ctx.go +++ /dev/null @@ -1,72 +0,0 @@ -package expr - -// MacroFn is a compiler function which takes in an existing Expr and returns -// the llvm Value for it -type MacroFn func(BuildCtx, Ctx, Expr) Expr - -// Ctx contains all the Macros and Identifiers available. A Ctx also keeps a -// reference to the global context, which has a number of macros available for -// all contexts to use. -type Ctx struct { - global *Ctx - macros map[Macro]MacroFn - idents map[Identifier]Expr -} - -// NewCtx returns a blank context instance -func NewCtx() Ctx { - return Ctx{ - global: globalCtx, - macros: map[Macro]MacroFn{}, - idents: map[Identifier]Expr{}, - } -} - -// Macro returns the MacroFn associated with the given identifier, or panics -// if the macro isn't found -func (c Ctx) Macro(m Macro) MacroFn { - if fn := c.macros[m]; fn != nil { - return fn - } - if fn := c.global.macros[m]; fn != nil { - return fn - } - panicf("macro %q not found in context", m) - return nil -} - -// Identifier returns the llvm.Value for the Identifier, or panics -func (c Ctx) Identifier(i Identifier) Expr { - if e := c.idents[i]; e != nil { - return e - } - // The global context doesn't have any identifiers, so don't bother checking - panicf("identifier %q not found", i) - panic("go is dumb") -} - -// Copy returns a deep copy of the Ctx -func (c Ctx) Copy() Ctx { - cc := Ctx{ - global: c.global, - macros: make(map[Macro]MacroFn, len(c.macros)), - idents: make(map[Identifier]Expr, len(c.idents)), - } - for m, mfn := range c.macros { - cc.macros[m] = mfn - } - for i, e := range c.idents { - cc.idents[i] = e - } - return cc -} - -// Bind returns a new Ctx which is a copy of this one, but with the given -// Identifier bound to the given Expr. Will panic if the Identifier is already -// bound -func (c Ctx) Bind(i Identifier, e Expr) { - if _, ok := c.idents[i]; ok { - panicf("identifier %q is already bound", i) - } - c.idents[i] = e -} diff --git a/expr/expr.go b/expr/expr.go deleted file mode 100644 index 1c61642..0000000 --- a/expr/expr.go +++ /dev/null @@ -1,210 +0,0 @@ -package expr - -import ( - "fmt" - - "llvm.org/llvm/bindings/go/llvm" -) - -// Expr represents the actual expression in question. -type Expr interface{} - -// equaler is used to compare two expressions. The comparison should not take -// into account Token values, only the actual value being represented -type equaler interface { - equal(equaler) bool -} - -// will panic if either Expr doesn't implement equaler -func exprEqual(e1, e2 Expr) bool { - eq1, ok1 := e1.(equaler) - eq2, ok2 := e2.(equaler) - if !ok1 || !ok2 { - panic(fmt.Sprintf("can't compare %T and %T", e1, e2)) - } - return eq1.equal(eq2) -} - -//////////////////////////////////////////////////////////////////////////////// - -// an Expr which simply wraps an existing llvm.Value -type llvmVal llvm.Value - -/* -func voidVal(lctx LLVMCtx) llvmVal { - return llvmVal{lctx.B.CreateRetVoid()} -} -*/ - -//////////////////////////////////////////////////////////////////////////////// - -/* -// Void represents no data (size = 0) -type Void struct{} - -func (v Void) equal(e equaler) bool { - _, ok := e.(Void) - return ok -} -*/ - -//////////////////////////////////////////////////////////////////////////////// -/* -// Bool represents a true or false value -type Bool bool - -func (b Bool) equal(e equaler) bool { - bb, ok := e.(Bool) - if !ok { - return false - } - return bb == b -} -*/ -//////////////////////////////////////////////////////////////////////////////// - -// Int represents an integer value -type Int int64 - -func (i Int) equal(e equaler) bool { - ii, ok := e.(Int) - return ok && ii == i -} - -func (i Int) String() string { - return fmt.Sprintf("%d", i) -} - -//////////////////////////////////////////////////////////////////////////////// -/* -// String represents a string value -type String string - -func (s String) equal(e equaler) bool { - ss, ok := e.(String) - if !ok { - return false - } - return ss == s -} -*/ -//////////////////////////////////////////////////////////////////////////////// - -// Identifier represents a binding to some other value which has been given a -// name -type Identifier string - -func (id Identifier) equal(e equaler) bool { - idid, ok := e.(Identifier) - return ok && idid == id -} - -func isIdentifier(e Expr) bool { - _, ok := e.(Identifier) - return ok -} - -//////////////////////////////////////////////////////////////////////////////// - -// Macro is an identifier for a macro which can be used to transform -// expressions. The tokens for macros start with a '%', but the Macro identifier -// itself has that stripped off -type Macro string - -// String returns the Macro with a '%' prepended to it -func (m Macro) String() string { - return "%" + string(m) -} - -func (m Macro) equal(e equaler) bool { - mm, ok := e.(Macro) - return ok && m == mm -} - -//////////////////////////////////////////////////////////////////////////////// - -// Tuple represents a fixed set of expressions which are interacted with as if -// they were a single value -type Tuple []Expr - -// NewTuple returns a Tuple around the given list of Exprs -func NewTuple(ee ...Expr) Tuple { - return Tuple(ee) -} - -func (tup Tuple) String() string { - return "(" + exprsJoin(tup) + ")" -} - -func (tup Tuple) equal(e equaler) bool { - tuptup, ok := e.(Tuple) - return ok && exprsEqual(tup, tuptup) -} - -func isTuple(e Expr) bool { - _, ok := e.(Tuple) - return ok -} - -//////////////////////////////////////////////////////////////////////////////// - -// List represents an ordered set of Exprs, all of the same type. A List's size -// does not affect its type signature, unlike a Tuple -type List []Expr - -// NewList returns a List around the given list of Exprs -func NewList(ee ...Expr) List { - return List(ee) -} - -func (l List) String() string { - return "[" + exprsJoin(l) + "]" -} - -func (l List) equal(e equaler) bool { - ll, ok := e.(List) - return ok && exprsEqual(l, ll) -} - -func isList(e Expr) bool { - _, ok := e.(List) - return ok -} - -//////////////////////////////////////////////////////////////////////////////// - -// Statement represents an actual action which will be taken. The input value is -// used as the input to the pipe, and the output of the pipe is the output of -// the statement -type Statement struct { - Op, Arg Expr -} - -// NewStatement returns a Statement whose Op is the first Expr. If the given -// list is empty Arg will be 0-tuple, if its length is one Arg will be that -// single Expr, otherwise Arg will be a Tuple of the list -func NewStatement(e Expr, ee ...Expr) Statement { - s := Statement{Op: e} - if len(ee) > 1 { - s.Arg = NewTuple(ee...) - } else if len(ee) == 1 { - s.Arg = ee[0] - } else if len(ee) == 0 { - s.Arg = NewTuple() - } - return s -} - -func (s Statement) String() string { - return fmt.Sprintf("(%v %s)", s.Op, s.Arg) -} - -func (s Statement) equal(e equaler) bool { - ss, ok := e.(Statement) - return ok && exprEqual(s.Op, ss.Op) && exprEqual(s.Arg, ss.Arg) -} - -func isStmt(e Expr) bool { - _, ok := e.(Statement) - return ok -} diff --git a/expr/parse.go b/expr/parse.go deleted file mode 100644 index 7d5c197..0000000 --- a/expr/parse.go +++ /dev/null @@ -1,299 +0,0 @@ -package expr - -//type exprErr struct { -// reason string -// err error -// tok lexer.Token -// tokCtx string // e.g. "block starting at" or "open paren at" -//} -// -//func (e exprErr) Error() string { -// var msg string -// if e.err != nil { -// msg = e.err.Error() -// } else { -// msg = e.reason -// } -// if err := e.tok.Err(); err != nil { -// msg += " - token error: " + err.Error() -// } else if (e.tok != lexer.Token{}) { -// msg += " - " -// if e.tokCtx != "" { -// msg += e.tokCtx + ": " -// } -// msg = fmt.Sprintf("%s [line:%d col:%d]", msg, e.tok.Row, e.tok.Col) -// } -// return msg -//} -// -////////////////////////////////////////////////////////////////////////////////// -// -//// toks[0] must be start -//func sliceEnclosedToks(toks []lexer.Token, start, end lexer.Token) ([]lexer.Token, []lexer.Token, error) { -// c := 1 -// ret := []lexer.Token{} -// first := toks[0] -// for i, tok := range toks[1:] { -// if tok.Err() != nil { -// return nil, nil, exprErr{ -// reason: fmt.Sprintf("missing closing %v", end), -// tok: tok, -// } -// } -// -// if tok.Equal(start) { -// c++ -// } else if tok.Equal(end) { -// c-- -// } -// if c == 0 { -// return ret, toks[2+i:], nil -// } -// ret = append(ret, tok) -// } -// -// return nil, nil, exprErr{ -// reason: fmt.Sprintf("missing closing %v", end), -// tok: first, -// tokCtx: "starting at", -// } -//} -// -//// Parse reads in all expressions it can from the given io.Reader and returns -//// them -//func Parse(r io.Reader) ([]Expr, error) { -// toks := readAllToks(r) -// var ret []Expr -// var expr Expr -// var err error -// for len(toks) > 0 { -// if toks[0].TokenType == lexer.EOF { -// return ret, nil -// } -// expr, toks, err = parse(toks) -// if err != nil { -// return nil, err -// } -// ret = append(ret, expr) -// } -// return ret, nil -//} -// -//// ParseAsBlock reads the given io.Reader as if it was implicitly surrounded by -//// curly braces, making it into a Block. This means all expressions from the -//// io.Reader *must* be statements. The returned Expr's Actual will always be a -//// Block. -//func ParseAsBlock(r io.Reader) (Expr, error) { -// return parseBlock(readAllToks(r)) -//} -// -//func readAllToks(r io.Reader) []lexer.Token { -// l := lexer.New(r) -// var toks []lexer.Token -// for l.HasNext() { -// toks = append(toks, l.Next()) -// } -// return toks -//} -// -//// For all parse methods it is assumed that toks is not empty -// -//var ( -// openParen = lexer.Token{TokenType: lexer.Wrapper, Val: "("} -// closeParen = lexer.Token{TokenType: lexer.Wrapper, Val: ")"} -// openCurly = lexer.Token{TokenType: lexer.Wrapper, Val: "{"} -// closeCurly = lexer.Token{TokenType: lexer.Wrapper, Val: "}"} -// comma = lexer.Token{TokenType: lexer.Punctuation, Val: ","} -// arrow = lexer.Token{TokenType: lexer.Punctuation, Val: ">"} -//) -// -//func parse(toks []lexer.Token) (Expr, []lexer.Token, error) { -// expr, toks, err := parseSingle(toks) -// if err != nil { -// return Expr{}, nil, err -// } -// -// if len(toks) > 0 && toks[0].TokenType == lexer.Punctuation { -// return parseConnectingPunct(toks, expr) -// } -// -// return expr, toks, nil -//} -// -//func parseSingle(toks []lexer.Token) (Expr, []lexer.Token, error) { -// var expr Expr -// var err error -// -// if toks[0].Err() != nil { -// return Expr{}, nil, exprErr{ -// reason: "could not parse token", -// tok: toks[0], -// } -// } -// -// if toks[0].Equal(openParen) { -// starter := toks[0] -// var ptoks []lexer.Token -// ptoks, toks, err = sliceEnclosedToks(toks, openParen, closeParen) -// if err != nil { -// return Expr{}, nil, err -// } -// -// if expr, ptoks, err = parse(ptoks); err != nil { -// return Expr{}, nil, err -// } else if len(ptoks) > 0 { -// return Expr{}, nil, exprErr{ -// reason: "multiple expressions inside parenthesis", -// tok: starter, -// tokCtx: "starting at", -// } -// } -// return expr, toks, nil -// -// } else if toks[0].Equal(openCurly) { -// var btoks []lexer.Token -// btoks, toks, err = sliceEnclosedToks(toks, openCurly, closeCurly) -// if err != nil { -// return Expr{}, nil, err -// } -// -// if expr, err = parseBlock(btoks); err != nil { -// return Expr{}, nil, err -// } -// return expr, toks, nil -// } -// -// if expr, err = parseNonPunct(toks[0]); err != nil { -// return Expr{}, nil, err -// } -// return expr, toks[1:], nil -//} -// -//func parseNonPunct(tok lexer.Token) (Expr, error) { -// if tok.TokenType == lexer.Identifier { -// return parseIdentifier(tok) -// } else if tok.TokenType == lexer.String { -// //return parseString(tok) -// } -// -// return Expr{}, exprErr{ -// reason: "unexpected non-punctuation token", -// tok: tok, -// } -//} -// -//func parseIdentifier(t lexer.Token) (Expr, error) { -// e := Expr{Token: t} -// if t.Val[0] == '-' || (t.Val[0] >= '0' && t.Val[0] <= '9') { -// n, err := strconv.ParseInt(t.Val, 10, 64) -// if err != nil { -// return Expr{}, exprErr{ -// err: err, -// tok: t, -// } -// } -// e.Actual = Int(n) -// -// /* -// } else if t.Val == "%true" { -// e.Actual = Bool(true) -// -// } else if t.Val == "%false" { -// e.Actual = Bool(false) -// */ -// -// } else if t.Val[0] == '%' { -// e.Actual = Macro(t.Val[1:]) -// -// } else { -// e.Actual = Identifier(t.Val) -// } -// -// return e, nil -//} -// -///* -//func parseString(t lexer.Token) (Expr, error) { -// str, err := strconv.Unquote(t.Val) -// if err != nil { -// return Expr{}, exprErr{ -// err: err, -// tok: t, -// } -// } -// return Expr{Token: t, Actual: String(str)}, nil -//} -//*/ -// -//func parseConnectingPunct(toks []lexer.Token, root Expr) (Expr, []lexer.Token, error) { -// if toks[0].Equal(comma) { -// return parseTuple(toks, root) -// -// } else if toks[0].Equal(arrow) { -// expr, toks, err := parse(toks[1:]) -// if err != nil { -// return Expr{}, nil, err -// } -// return Expr{Token: root.Token, Actual: Statement{In: root, To: expr}}, toks, nil -// } -// -// return root, toks, nil -//} -// -//func parseTuple(toks []lexer.Token, root Expr) (Expr, []lexer.Token, error) { -// rootTup, ok := root.Actual.(Tuple) -// if !ok { -// rootTup = Tuple{root} -// } -// -// // rootTup is modified throughout, be we need to make it into an Expr for -// // every return, which is annoying. so make a function to do it on the fly -// mkRoot := func() Expr { -// return Expr{Token: rootTup[0].Token, Actual: rootTup} -// } -// -// if len(toks) < 2 { -// return mkRoot(), toks, nil -// } else if !toks[0].Equal(comma) { -// if toks[0].TokenType == lexer.Punctuation { -// return parseConnectingPunct(toks, mkRoot()) -// } -// return mkRoot(), toks, nil -// } -// -// var expr Expr -// var err error -// if expr, toks, err = parseSingle(toks[1:]); err != nil { -// return Expr{}, nil, err -// } -// -// rootTup = append(rootTup, expr) -// return parseTuple(toks, mkRoot()) -//} -// -//// parseBlock assumes that the given token list is the entire block, already -//// pulled from outer curly braces by sliceEnclosedToks, or determined to be the -//// entire block in some other way. -//func parseBlock(toks []lexer.Token) (Expr, error) { -// b := Block{} -// first := toks[0] -// var expr Expr -// var err error -// for { -// if len(toks) == 0 { -// return Expr{Token: first, Actual: b}, nil -// } -// -// if expr, toks, err = parse(toks); err != nil { -// return Expr{}, err -// } -// if _, ok := expr.Actual.(Statement); !ok { -// return Expr{}, exprErr{ -// reason: "blocks may only contain full statements", -// tok: expr.Token, -// tokCtx: "non-statement here", -// } -// } -// b = append(b, expr) -// } -//} diff --git a/expr/parse_test.go b/expr/parse_test.go deleted file mode 100644 index 03921ce..0000000 --- a/expr/parse_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package expr - -//import . "testing" - -//func TestSliceEnclosedToks(t *T) { -// doAssert := func(in, expOut, expRem []lexer.Token) { -// out, rem, err := sliceEnclosedToks(in, openParen, closeParen) -// require.Nil(t, err) -// assert.Equal(t, expOut, out) -// assert.Equal(t, expRem, rem) -// } -// foo := lexer.Token{TokenType: lexer.Identifier, Val: "foo"} -// bar := lexer.Token{TokenType: lexer.Identifier, Val: "bar"} -// -// toks := []lexer.Token{openParen, closeParen} -// doAssert(toks, []lexer.Token{}, []lexer.Token{}) -// -// toks = []lexer.Token{openParen, foo, closeParen, bar} -// doAssert(toks, []lexer.Token{foo}, []lexer.Token{bar}) -// -// toks = []lexer.Token{openParen, foo, foo, closeParen, bar, bar} -// doAssert(toks, []lexer.Token{foo, foo}, []lexer.Token{bar, bar}) -// -// toks = []lexer.Token{openParen, foo, openParen, bar, closeParen, closeParen} -// doAssert(toks, []lexer.Token{foo, openParen, bar, closeParen}, []lexer.Token{}) -// -// toks = []lexer.Token{openParen, foo, openParen, bar, closeParen, bar, closeParen, foo} -// doAssert(toks, []lexer.Token{foo, openParen, bar, closeParen, bar}, []lexer.Token{foo}) -//} -// -//func assertParse(t *T, in []lexer.Token, expExpr Expr, expOut []lexer.Token) { -// expr, out, err := parse(in) -// require.Nil(t, err) -// assert.True(t, expExpr.equal(expr), "expr:%+v expExpr:%+v", expr, expExpr) -// assert.Equal(t, expOut, out, "out:%v expOut:%v", out, expOut) -//} -// -//func TestParseSingle(t *T) { -// foo := lexer.Token{TokenType: lexer.Identifier, Val: "foo"} -// fooM := lexer.Token{TokenType: lexer.Identifier, Val: "%foo"} -// fooExpr := Expr{Actual: Identifier("foo")} -// fooMExpr := Expr{Actual: Macro("foo")} -// -// toks := []lexer.Token{foo} -// assertParse(t, toks, fooExpr, []lexer.Token{}) -// -// toks = []lexer.Token{foo, foo} -// assertParse(t, toks, fooExpr, []lexer.Token{foo}) -// -// toks = []lexer.Token{openParen, foo, closeParen, foo} -// assertParse(t, toks, fooExpr, []lexer.Token{foo}) -// -// toks = []lexer.Token{openParen, openParen, foo, closeParen, closeParen, foo} -// assertParse(t, toks, fooExpr, []lexer.Token{foo}) -// -// toks = []lexer.Token{fooM, foo} -// assertParse(t, toks, fooMExpr, []lexer.Token{foo}) -//} -// -//func TestParseTuple(t *T) { -// tup := func(ee ...Expr) Expr { -// return Expr{Actual: Tuple(ee)} -// } -// -// foo := lexer.Token{TokenType: lexer.Identifier, Val: "foo"} -// fooExpr := Expr{Actual: Identifier("foo")} -// -// toks := []lexer.Token{foo, comma, foo} -// assertParse(t, toks, tup(fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, comma, foo, foo} -// assertParse(t, toks, tup(fooExpr, fooExpr), []lexer.Token{foo}) -// -// toks = []lexer.Token{foo, comma, foo, comma, foo} -// assertParse(t, toks, tup(fooExpr, fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, comma, foo, comma, foo, comma, foo} -// assertParse(t, toks, tup(fooExpr, fooExpr, fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, comma, openParen, foo, comma, foo, closeParen, comma, foo} -// assertParse(t, toks, tup(fooExpr, tup(fooExpr, fooExpr), fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, comma, openParen, foo, comma, foo, closeParen, comma, foo, foo} -// assertParse(t, toks, tup(fooExpr, tup(fooExpr, fooExpr), fooExpr), []lexer.Token{foo}) -//} -// -//func TestParseStatement(t *T) { -// stmt := func(in, to Expr) Expr { -// return Expr{Actual: Statement{In: in, To: to}} -// } -// -// foo := lexer.Token{TokenType: lexer.Identifier, Val: "foo"} -// fooExpr := Expr{Actual: Identifier("foo")} -// -// toks := []lexer.Token{foo, arrow, foo} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{openParen, foo, arrow, foo, closeParen} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, arrow, openParen, foo, closeParen} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, arrow, foo} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{foo, arrow, foo, foo} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{foo}) -// -// toks = []lexer.Token{foo, arrow, openParen, foo, closeParen, foo} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{foo}) -// -// toks = []lexer.Token{openParen, foo, closeParen, arrow, openParen, foo, closeParen, foo} -// assertParse(t, toks, stmt(fooExpr, fooExpr), []lexer.Token{foo}) -// -// fooTupExpr := Expr{Actual: Tuple{fooExpr, fooExpr}} -// toks = []lexer.Token{foo, arrow, openParen, foo, comma, foo, closeParen, foo} -// assertParse(t, toks, stmt(fooExpr, fooTupExpr), []lexer.Token{foo}) -// -// toks = []lexer.Token{foo, comma, foo, arrow, foo} -// assertParse(t, toks, stmt(fooTupExpr, fooExpr), []lexer.Token{}) -// -// toks = []lexer.Token{openParen, foo, comma, foo, closeParen, arrow, foo} -// assertParse(t, toks, stmt(fooTupExpr, fooExpr), []lexer.Token{}) -//} -// -//func TestParseBlock(t *T) { -// stmt := func(in, to Expr) Expr { -// return Expr{Actual: Statement{In: in, To: to}} -// } -// block := func(stmts ...Expr) Expr { -// return Expr{Actual: Block(stmts)} -// } -// -// foo := lexer.Token{TokenType: lexer.Identifier, Val: "foo"} -// fooExpr := Expr{Actual: Identifier("foo")} -// -// toks := []lexer.Token{openCurly, foo, arrow, foo, closeCurly} -// assertParse(t, toks, block(stmt(fooExpr, fooExpr)), []lexer.Token{}) -// -// toks = []lexer.Token{openCurly, foo, arrow, foo, closeCurly, foo} -// assertParse(t, toks, block(stmt(fooExpr, fooExpr)), []lexer.Token{foo}) -// -// toks = []lexer.Token{openCurly, foo, arrow, foo, openParen, foo, arrow, foo, closeParen, closeCurly, foo} -// assertParse(t, toks, block(stmt(fooExpr, fooExpr), stmt(fooExpr, fooExpr)), []lexer.Token{foo}) -// -// toks = []lexer.Token{openCurly, foo, arrow, foo, openParen, foo, arrow, foo, closeParen, closeCurly, foo} -// assertParse(t, toks, block(stmt(fooExpr, fooExpr), stmt(fooExpr, fooExpr)), []lexer.Token{foo}) -//} diff --git a/expr/util.go b/expr/util.go deleted file mode 100644 index f783f48..0000000 --- a/expr/util.go +++ /dev/null @@ -1,40 +0,0 @@ -package expr - -import ( - "encoding/hex" - "fmt" - "math/rand" - "strings" -) - -func randStr() string { - b := make([]byte, 16) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return hex.EncodeToString(b) -} - -func exprsJoin(ee []Expr) string { - strs := make([]string, len(ee)) - for i := range ee { - strs[i] = fmt.Sprint(ee[i]) - } - return strings.Join(strs, ", ") -} - -func exprsEqual(ee1, ee2 []Expr) bool { - if len(ee1) != len(ee2) { - return false - } - for i := range ee1 { - if !exprEqual(ee1[i], ee2[i]) { - return false - } - } - return true -} - -func panicf(msg string, args ...interface{}) { - panic(fmt.Sprintf(msg, args...)) -} diff --git a/graph/graph.go b/graph/graph.go deleted file mode 100644 index d3e70e8..0000000 --- a/graph/graph.go +++ /dev/null @@ -1,522 +0,0 @@ -// Package graph implements an immutable unidirectional graph. -package graph - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "sort" - "strings" -) - -// Value wraps a go value in a way such that it will be uniquely identified -// within any Graph and between Graphs. Use NewValue to create a Value instance. -// You can create an instance manually as long as ID is globally unique. -type Value struct { - ID string - V interface{} -} - -// Void is the absence of any value. -var Void Value - -// NewValue returns a Value instance wrapping any go value. The Value returned -// will be independent of the passed in go value. So if the same go value is -// passed in twice then the two returned Value instances will be treated as -// being different values by Graph. -func NewValue(V interface{}) Value { - b := make([]byte, 8) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return Value{ - ID: hex.EncodeToString(b), - V: V, - } -} - -// Edge is a directional edge connecting two values in a Graph, the Tail and the -// Head. -type Edge interface { - Tail() Value // The Value the Edge is coming from - Head() Value // The Value the Edge is going to -} - -func edgeID(e Edge) string { - return fmt.Sprintf("%q->%q", e.Tail().ID, e.Head().ID) -} - -type edge struct { - tail, head Value -} - -// NewEdge constructs and returns an Edge running from tail to head. -func NewEdge(tail, head Value) Edge { - return edge{tail, head} -} - -func (e edge) Tail() Value { - return e.tail -} - -func (e edge) Head() Value { - return e.head -} - -func (e edge) String() string { - return edgeID(e) -} - -// NOTE the Node type exists primarily for convenience. As far as Graph's -// internals are concerned it doesn't _really_ exist, and no Graph method should -// ever take Node as a parameter (except the callback functions like in -// Traverse, where it's not really being taken in). - -// Node wraps a Value in a Graph to include that Node's input and output Edges -// in that Graph. -type Node struct { - Value - - // All Edges in the Graph with this Node's Value as their Head and Tail, - // respectively. These should not be expected to be deterministic. - Ins, Outs []Edge -} - -// an edgeIndex maps valueIDs to a set of edgeIDs. Graph keeps two edgeIndex's, -// one for input edges and one for output edges. -type edgeIndex map[string]map[string]struct{} - -func (ei edgeIndex) cp() edgeIndex { - if ei == nil { - return edgeIndex{} - } - ei2 := make(edgeIndex, len(ei)) - for valID, edgesM := range ei { - edgesM2 := make(map[string]struct{}, len(edgesM)) - for id := range edgesM { - edgesM2[id] = struct{}{} - } - ei2[valID] = edgesM2 - } - return ei2 -} - -func (ei edgeIndex) add(valID, edgeID string) { - edgesM, ok := ei[valID] - if !ok { - edgesM = map[string]struct{}{} - ei[valID] = edgesM - } - edgesM[edgeID] = struct{}{} -} - -func (ei edgeIndex) del(valID, edgeID string) { - edgesM, ok := ei[valID] - if !ok { - return - } - - delete(edgesM, edgeID) - if len(edgesM) == 0 { - delete(ei, valID) - } -} - -// Graph implements an immutable, unidirectional graph which can hold generic -// values. All methods are thread-safe as they don't modify the Graph in any -// way. -// -// The Graph's zero value is the initial empty graph. -// -// The Graph does not keep track of Edge ordering. Assume that all slices of -// Edges are in random order. -type Graph interface { - // Empty returns a graph with no edges which is of the same underlying type - // as this one. - Empty() Graph - - // Add returns a new Graph instance with the given Edge added to it. If the - // original Graph already had that Edge this returns the original Graph. - Add(Edge) Graph - - // Del returns a new Graph instance without the given Edge in it. If the - // original Graph didn't have that Edge this returns the original Graph. - Del(Edge) Graph - - // Edges returns all Edges which are part of the Graph, mapped using a - // string ID which is unique within the Graph and between Graphs of the same - // underlying type. - Edges() map[string]Edge - - // EdgesTo returns all Edges whose Head is the given Value. - EdgesTo(v Value) []Edge - - // EdgesFrom returns all Edges whose Tail is the given Value. - EdgesFrom(v Value) []Edge - - // Has returns true if the Graph contains at least one Edge with a Head or - // Tail of Value. - Has(v Value) bool -} - -type graph struct { - m map[string]Edge - - // these are indices mapping Value IDs to all the in/out edges for that - // Value in the Graph. - vIns, vOuts edgeIndex -} - -// Null is the empty graph from which all other Graphs are built. -var Null = (graph{}).Empty() - -func (g graph) Empty() Graph { - return (graph{}).cp() // cp also initializes -} - -func (g graph) cp() graph { - g2 := graph{ - m: make(map[string]Edge, len(g.m)), - vIns: g.vIns.cp(), - vOuts: g.vOuts.cp(), - } - for id, e := range g.m { - g2.m[id] = e - } - return g2 -} - -func (g graph) String() string { - edgeStrs := make([]string, 0, len(g.m)) - for _, edge := range g.m { - edgeStrs = append(edgeStrs, fmt.Sprint(edge)) - } - sort.Strings(edgeStrs) - return "Graph{" + strings.Join(edgeStrs, ",") + "}" -} - -func (g graph) Add(e Edge) Graph { - id := edgeID(e) - if _, ok := g.m[id]; ok { - return g - } - - g2 := g.cp() - g2.addDirty(id, e) - return g2 -} - -func (g graph) addDirty(edgeID string, e Edge) { - g.m[edgeID] = e - g.vIns.add(e.Head().ID, edgeID) - g.vOuts.add(e.Tail().ID, edgeID) -} - -// addDirty attempts to add the Edge to Graph using an addDirty method, -// otherwise it just uses Add like normal -func addDirty(g Graph, edgeID string, e Edge) Graph { - gd, ok := g.(interface { - addDirty(string, Edge) - }) - if !ok { - return g.Add(e) - } - gd.addDirty(edgeID, e) - return g -} - -func (g graph) Del(e Edge) Graph { - id := edgeID(e) - if _, ok := g.m[id]; !ok { - return g - } - - g2 := g.cp() - delete(g2.m, id) - g2.vIns.del(e.Head().ID, id) - g2.vOuts.del(e.Tail().ID, id) - return g2 -} - -func (g graph) Edges() map[string]Edge { - return g.m -} - -func (g graph) EdgesTo(v Value) []Edge { - vIns := g.vIns[v.ID] - ins := make([]Edge, 0, len(vIns)) - for edgeID := range vIns { - ins = append(ins, g.m[edgeID]) - } - return ins -} - -func (g graph) EdgesFrom(v Value) []Edge { - vOuts := g.vOuts[v.ID] - outs := make([]Edge, 0, len(vOuts)) - for edgeID := range vOuts { - outs = append(outs, g.m[edgeID]) - } - return outs -} - -func (g graph) Has(v Value) bool { - if _, ok := g.vIns[v.ID]; ok { - return true - } else if _, ok := g.vOuts[v.ID]; ok { - return true - } - return false -} - -//////////////////////////////////////////////////////////////////////////////// - -// Disjoin looks at the whole Graph and returns all sub-graphs of it which don't -// share any Edges between each other. -func Disjoin(g Graph) []Graph { - empty := g.Empty() - edges := g.Edges() - valM := make(map[string]*Graph, len(edges)) - graphForEdge := func(edge Edge) *Graph { - headGraph := valM[edge.Head().ID] - tailGraph := valM[edge.Tail().ID] - if headGraph == nil && tailGraph == nil { - newGraph := empty.Empty() - return &newGraph - } else if headGraph == nil && tailGraph != nil { - return tailGraph - } else if headGraph != nil && tailGraph == nil { - return headGraph - } else if headGraph == tailGraph { - return headGraph // doesn't matter which is returned - } - - // the two values are part of different graphs, join the smaller into - // the larger and change all values which were pointing to it to point - // into the larger (which will then be the join of them) - tailEdges := (*tailGraph).Edges() - if headEdges := (*headGraph).Edges(); len(headEdges) > len(tailEdges) { - headGraph, tailGraph = tailGraph, headGraph - tailEdges = headEdges - } - for edgeID, edge := range tailEdges { - *headGraph = addDirty(*headGraph, edgeID, edge) - } - for valID, valGraph := range valM { - if valGraph == tailGraph { - valM[valID] = headGraph - } - } - return headGraph - } - - for edgeID, edge := range edges { - graph := graphForEdge(edge) - *graph = addDirty(*graph, edgeID, edge) - valM[edge.Head().ID] = graph - valM[edge.Tail().ID] = graph - } - - found := map[*Graph]bool{} - graphs := make([]Graph, 0, len(valM)) - for _, graph := range valM { - if found[graph] { - continue - } - found[graph] = true - graphs = append(graphs, *graph) - } - return graphs -} - -// Join returns a new Graph which shares all Edges of all given Graphs. All -// given Graphs must be of the same underlying type. -func Join(graphs ...Graph) Graph { - g2 := graphs[0].Empty() - for _, graph := range graphs { - for edgeID, edge := range graph.Edges() { - g2 = addDirty(g2, edgeID, edge) - } - } - return g2 -} - -// GetNode returns the Node for the given Value, or false if the Graph doesn't -// contain the Value. -func GetNode(g Graph, v Value) (Node, bool) { - n := Node{ - Value: v, - Ins: g.EdgesTo(v), - Outs: g.EdgesFrom(v), - } - return n, len(n.Ins) > 0 || len(n.Outs) > 0 -} - -// GetNodes returns a Node for each Value which has at least one Edge in the -// Graph, with the Nodes mapped by their Value's ID. -func GetNodes(g Graph) map[string]Node { - edges := g.Edges() - nodesM := make(map[string]Node, len(edges)*2) - for _, edge := range edges { - // if head and tail are modified at the same time it messes up the case - // where they are the same node - { - headV := edge.Head() - head := nodesM[headV.ID] - head.Value = headV - head.Ins = append(head.Ins, edge) - nodesM[head.ID] = head - } - { - tailV := edge.Tail() - tail := nodesM[tailV.ID] - tail.Value = tailV - tail.Outs = append(tail.Outs, edge) - nodesM[tail.ID] = tail - } - } - return nodesM -} - -// Traverse is used to traverse the Graph until a stopping point is reached. -// Traversal starts with the cursor at the given start Value. Each hop is -// performed by passing the cursor Value's Node into the next function. The -// cursor moves to the returned Value and next is called again, and so on. -// -// If the boolean returned from the next function is false traversal stops and -// this method returns. -// -// If start has no Edges in the Graph, or a Value returned from next doesn't, -// this will still call next, but the Node will be the zero value. -func Traverse(g Graph, start Value, next func(n Node) (Value, bool)) { - curr := start - for { - currNode, ok := GetNode(g, curr) - if ok { - curr, ok = next(currNode) - } else { - curr, ok = next(Node{}) - } - if !ok { - return - } - } -} - -// VisitBreadth is like Traverse, except that each Node is only visited once, -// and the order of visited Nodes is determined by traversing each Node's output -// Edges breadth-wise. -// -// If the boolean returned from the callback function is false, or the start -// Value has no edges in the Graph, traversal stops and this method returns. -// -// The exact order of Nodes visited is _not_ deterministic. -func VisitBreadth(g Graph, start Value, callback func(n Node) bool) { - visited := map[string]bool{} - toVisit := make([]Value, 0, 16) - toVisit = append(toVisit, start) - - for { - if len(toVisit) == 0 { - return - } - - // shift val off front - val := toVisit[0] - toVisit = toVisit[1:] - if visited[val.ID] { - continue - } - node, ok := GetNode(g, val) - if !ok { - continue - } else if !callback(node) { - return - } - visited[val.ID] = true - for _, edge := range node.Outs { - headV := edge.Head() - if visited[headV.ID] { - continue - } - toVisit = append(toVisit, headV) - } - } -} - -// VisitDepth is like Traverse, except that each Node is only visited once, -// and the order of visited Nodes is determined by traversing each Node's output -// Edges depth-wise. -// -// If the boolean returned from the callback function is false, or the start -// Value has no edges in the Graph, traversal stops and this method returns. -// -// The exact order of Nodes visited is _not_ deterministic. -func VisitDepth(g Graph, start Value, callback func(n Node) bool) { - // VisitDepth is actually the same as VisitBreadth, only you read off the - // toVisit list from back-to-front - visited := map[string]bool{} - toVisit := make([]Value, 0, 16) - toVisit = append(toVisit, start) - - for { - if len(toVisit) == 0 { - return - } - - val := toVisit[0] - toVisit = toVisit[:len(toVisit)-1] // pop val off back - if visited[val.ID] { - continue - } - node, ok := GetNode(g, val) - if !ok { - continue - } else if !callback(node) { - return - } - visited[val.ID] = true - for _, edge := range node.Outs { - if visited[edge.Head().ID] { - continue - } - toVisit = append(toVisit, edge.Head()) - } - } -} - -func edgesShared(g, g2 Graph) bool { - gEdges := g.Edges() - for id := range g2.Edges() { - if _, ok := gEdges[id]; !ok { - return false - } - } - return true -} - -// SubGraph returns true if g2 is a sub-graph of g; i.e., all edges in g2 are -// also in g. Both Graphs should be of the same underlying type. -func SubGraph(g, g2 Graph) bool { - gEdges, g2Edges := g.Edges(), g2.Edges() - // as a quick check before iterating through the edges, if g has fewer edges - // than g2 then g2 can't possibly be a sub-graph of it - if len(gEdges) < len(g2Edges) { - return false - } - for id := range g2Edges { - if _, ok := gEdges[id]; !ok { - return false - } - } - return true -} - -// Equal returns true if g and g2 have exactly the same Edges. Both Graphs -// should be of the same underlying type. -func Equal(g, g2 Graph) bool { - if len(g.Edges()) != len(g2.Edges()) { - return false - } - return SubGraph(g, g2) -} diff --git a/graph/graph_test.go b/graph/graph_test.go deleted file mode 100644 index dc28d0a..0000000 --- a/graph/graph_test.go +++ /dev/null @@ -1,386 +0,0 @@ -package graph - -import ( - "fmt" - . "testing" - "time" - - "github.com/mediocregopher/mediocre-go-lib/mrand" - "github.com/mediocregopher/mediocre-go-lib/mtest/massert" - "github.com/mediocregopher/mediocre-go-lib/mtest/mchk" -) - -func strV(s string) Value { - return Value{ID: s, V: s} -} - -func TestGraph(t *T) { - t.Parallel() - type state struct { - Graph - - m map[string]Edge - } - - type params struct { - add Edge - del Edge - } - - chk := mchk.Checker{ - Init: func() mchk.State { - return state{ - Graph: Null, - m: map[string]Edge{}, - } - }, - Next: func(ss mchk.State) mchk.Action { - s := ss.(state) - var p params - if i := mrand.Intn(10); i == 0 && len(s.m) > 0 { - // add edge which is already there - for _, e := range s.m { - p.add = e - break - } - } else if i == 1 { - // delete edge which isn't there - p.del = NewEdge(strV("z"), strV("z")) - } else if i <= 5 { - // add probably new edge - p.add = NewEdge(strV(mrand.Hex(1)), strV(mrand.Hex(1))) - } else { - // probably del edge - p.del = NewEdge(strV(mrand.Hex(1)), strV(mrand.Hex(1))) - } - return mchk.Action{Params: p} - }, - Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { - s, p := ss.(state), a.Params.(params) - if p.add != nil { - s.Graph = s.Graph.Add(p.add) - s.m[edgeID(p.add)] = p.add - } else { - s.Graph = s.Graph.Del(p.del) - delete(s.m, edgeID(p.del)) - } - - { // test GetNodes and Edges methods - nodes := GetNodes(s.Graph) - edges := s.Graph.Edges() - var aa []massert.Assertion - vals := map[string]bool{} - ins, outs := map[string]int{}, map[string]int{} - for _, e := range s.m { - aa = append(aa, massert.Has(edges, e)) - aa = append(aa, massert.HasKey(nodes, e.Head().ID)) - aa = append(aa, massert.Has(nodes[e.Head().ID].Ins, e)) - aa = append(aa, massert.HasKey(nodes, e.Tail().ID)) - aa = append(aa, massert.Has(nodes[e.Tail().ID].Outs, e)) - vals[e.Head().ID] = true - vals[e.Tail().ID] = true - ins[e.Head().ID]++ - outs[e.Tail().ID]++ - } - aa = append(aa, massert.Len(edges, len(s.m))) - aa = append(aa, massert.Len(nodes, len(vals))) - for id, node := range nodes { - aa = append(aa, massert.Len(node.Ins, ins[id])) - aa = append(aa, massert.Len(node.Outs, outs[id])) - } - - if err := massert.All(aa...).Assert(); err != nil { - return nil, err - } - } - - { // test GetNode and Has. GetNodes has already been tested so we - // can use its returned Nodes as the expected ones - var aa []massert.Assertion - for _, expNode := range GetNodes(s.Graph) { - var naa []massert.Assertion - node, ok := GetNode(s.Graph, expNode.Value) - naa = append(naa, massert.Equal(true, ok)) - naa = append(naa, massert.Equal(true, s.Graph.Has(expNode.Value))) - naa = append(naa, massert.Subset(expNode.Ins, node.Ins)) - naa = append(naa, massert.Len(node.Ins, len(expNode.Ins))) - naa = append(naa, massert.Subset(expNode.Outs, node.Outs)) - naa = append(naa, massert.Len(node.Outs, len(expNode.Outs))) - - aa = append(aa, massert.Comment(massert.All(naa...), "v:%q", expNode.ID)) - } - _, ok := GetNode(s.Graph, strV("zz")) - aa = append(aa, massert.Equal(false, ok)) - aa = append(aa, massert.Equal(false, s.Graph.Has(strV("zz")))) - - if err := massert.All(aa...).Assert(); err != nil { - return nil, err - } - } - - return s, nil - }, - } - - if err := chk.RunFor(5 * time.Second); err != nil { - t.Fatal(err) - } -} - -func TestSubGraphAndEqual(t *T) { - t.Parallel() - type state struct { - g1, g2 Graph - expEqual, expSubGraph bool - } - - type params struct { - e Edge - add1, add2 bool - } - - chk := mchk.Checker{ - Init: func() mchk.State { - return state{ - g1: Null, - g2: Null, - expEqual: true, - expSubGraph: true, - } - }, - Next: func(ss mchk.State) mchk.Action { - i := mrand.Intn(10) - p := params{ - e: NewEdge(strV(mrand.Hex(4)), strV(mrand.Hex(4))), - add1: i != 0, - add2: i != 1, - } - return mchk.Action{Params: p} - }, - Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { - s, p := ss.(state), a.Params.(params) - if p.add1 { - s.g1 = s.g1.Add(p.e) - } - if p.add2 { - s.g2 = s.g2.Add(p.e) - } - s.expSubGraph = s.expSubGraph && p.add1 - s.expEqual = s.expEqual && p.add1 && p.add2 - - if SubGraph(s.g1, s.g2) != s.expSubGraph { - return nil, fmt.Errorf("SubGraph expected to return %v", s.expSubGraph) - } - - if Equal(s.g1, s.g2) != s.expEqual { - return nil, fmt.Errorf("Equal expected to return %v", s.expEqual) - } - - return s, nil - }, - MaxLength: 100, - } - - if err := chk.RunFor(5 * time.Second); err != nil { - t.Fatal(err) - } -} - -func TestDisjoinUnion(t *T) { - t.Parallel() - type state struct { - g Graph - // prefix -> Values with that prefix. contains dupes - valM map[string][]Value - disjM map[string]Graph - } - - type params struct { - prefix string - e Edge - } - - chk := mchk.Checker{ - Init: func() mchk.State { - return state{ - g: Null, - valM: map[string][]Value{}, - disjM: map[string]Graph{}, - } - }, - Next: func(ss mchk.State) mchk.Action { - s := ss.(state) - prefix := mrand.Hex(1) - var edge Edge - if vals := s.valM[prefix]; len(vals) == 0 { - edge = NewEdge( - strV(prefix+mrand.Hex(1)), - strV(prefix+mrand.Hex(1)), - ) - } else if mrand.Intn(2) == 0 { - edge = NewEdge( - mrand.Element(vals, nil).(Value), - strV(prefix+mrand.Hex(1)), - ) - } else { - edge = NewEdge( - strV(prefix+mrand.Hex(1)), - mrand.Element(vals, nil).(Value), - ) - } - - return mchk.Action{Params: params{prefix: prefix, e: edge}} - }, - Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { - s, p := ss.(state), a.Params.(params) - s.g = s.g.Add(p.e) - s.valM[p.prefix] = append(s.valM[p.prefix], p.e.Head(), p.e.Tail()) - if s.disjM[p.prefix] == nil { - s.disjM[p.prefix] = Null - } - s.disjM[p.prefix] = s.disjM[p.prefix].Add(p.e) - - var aa []massert.Assertion - - // test Disjoin - disj := Disjoin(s.g) - for prefix, graph := range s.disjM { - aa = append(aa, massert.Comment( - massert.Equal(true, Equal(graph, s.disjM[prefix])), - "prefix:%q", prefix, - )) - } - aa = append(aa, massert.Len(disj, len(s.disjM))) - - // now test Join - join := Join(disj...) - aa = append(aa, massert.Equal(true, Equal(s.g, join))) - - return s, massert.All(aa...).Assert() - }, - MaxLength: 100, - // Each action is required for subsequent ones to make sense, so - // minimizing won't work - DontMinimize: true, - } - - if err := chk.RunFor(5 * time.Second); err != nil { - t.Fatal(err) - } -} - -func TestVisitBreadth(t *T) { - t.Parallel() - type state struct { - g Graph - // each rank describes the set of values (by ID) which should be - // visited in that rank. Within a rank the values will be visited in any - // order - ranks []map[string]bool - } - - thisRank := func(s state) map[string]bool { - return s.ranks[len(s.ranks)-1] - } - - prevRank := func(s state) map[string]bool { - return s.ranks[len(s.ranks)-2] - } - - randFromRank := func(s state, rankPickFn func(state) map[string]bool) Value { - rank := rankPickFn(s) - rankL := make([]string, 0, len(rank)) - for id := range rank { - rankL = append(rankL, id) - } - return strV(mrand.Element(rankL, nil).(string)) - } - - randNew := func(s state) Value { - for { - v := strV(mrand.Hex(2)) - if !s.g.Has(v) { - return v - } - } - } - - type params struct { - newRank bool - e Edge - } - - chk := mchk.Checker{ - Init: func() mchk.State { - return state{ - g: Null, - ranks: []map[string]bool{ - {"start": true}, - {}, - }, - } - }, - Next: func(ss mchk.State) mchk.Action { - s := ss.(state) - var p params - p.newRank = len(thisRank(s)) > 0 && mrand.Intn(10) == 0 - if p.newRank { - p.e = NewEdge( - randFromRank(s, thisRank), - randNew(s), - ) - } else { - p.e = NewEdge( - randFromRank(s, prevRank), - strV(mrand.Hex(2)), - ) - } - return mchk.Action{Params: p} - }, - Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) { - s, p := ss.(state), a.Params.(params) - if p.newRank { - s.ranks = append(s.ranks, map[string]bool{}) - } - if !s.g.Has(p.e.Head()) { - thisRank(s)[p.e.Head().ID] = true - } - s.g = s.g.Add(p.e) - - // check the visit - var err error - expRanks := s.ranks - currRank := map[string]bool{} - VisitBreadth(s.g, strV("start"), func(n Node) bool { - currRank[n.Value.ID] = true - if len(currRank) != len(expRanks[0]) { - return true - } - if err = massert.Equal(expRanks[0], currRank).Assert(); err != nil { - return false - } - expRanks = expRanks[1:] - currRank = map[string]bool{} - return true - }) - if err != nil { - return nil, err - } - - err = massert.All( - massert.Len(expRanks, 0), - massert.Len(currRank, 0), - ).Assert() - return s, err - }, - DontMinimize: true, - } - - if err := chk.RunCase(); err != nil { - t.Fatal(err) - } - - if err := chk.RunFor(5 * time.Second); err != nil { - t.Fatal(err) - } -} diff --git a/lang/lang.go b/lang/lang.go deleted file mode 100644 index c539351..0000000 --- a/lang/lang.go +++ /dev/null @@ -1,118 +0,0 @@ -package lang - -import ( - "fmt" - "reflect" - "strings" -) - -// Commonly used Terms -var ( - // Language structure types - AAtom = Atom("atom") - AConst = Atom("const") - ATuple = Atom("tup") - AList = Atom("list") - - // Match shortcuts - AUnder = Atom("_") - TDblUnder = Tuple{AUnder, AUnder} -) - -// Term is a unit of language which carries some meaning. Some Terms are -// actually comprised of multiple sub-Terms. -type Term interface { - fmt.Stringer // for debugging - - // Type returns a Term which describes the type of this Term, i.e. the - // components this Term is comprised of. - Type() Term -} - -// Equal returns whether or not two Terms are of equal value -func Equal(t1, t2 Term) bool { - return reflect.DeepEqual(t1, t2) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Atom is a constant with no other meaning than that it can be equal or not -// equal to another Atom. -type Atom string - -func (a Atom) String() string { - return string(a) -} - -// Type implements the method for Term -func (a Atom) Type() Term { - return AAtom -} - -//////////////////////////////////////////////////////////////////////////////// - -// Const is a constant whose meaning depends on the context in which it is used -type Const string - -func (a Const) String() string { - return string(a) -} - -// Type implements the method for Term -func (a Const) Type() Term { - return AConst -} - -//////////////////////////////////////////////////////////////////////////////// - -// Tuple is a compound Term of zero or more sub-Terms, each of which may have a -// different Type. Both the length of the Tuple and the Type of each of it's -// sub-Terms are components in the Tuple's Type. -type Tuple []Term - -func (t Tuple) String() string { - ss := make([]string, len(t)) - for i := range t { - ss[i] = t[i].String() - } - return "(" + strings.Join(ss, " ") + ")" -} - -// Type implements the method for Term -func (t Tuple) Type() Term { - tt := make(Tuple, len(t)) - for i := range t { - tt[i] = t[i].Type() - } - return Tuple{ATuple, tt} -} - -//////////////////////////////////////////////////////////////////////////////// - -type list struct { - typ Term - ll []Term -} - -// List is a compound Term of zero or more sub-Terms, each of which must have -// the same Type (the one given as the first argument to this function). Only -// the Type of the sub-Terms is a component in the List's Type. -func List(typ Term, elems ...Term) Term { - return list{ - typ: typ, - ll: elems, - } -} - -func (l list) String() string { - ss := make([]string, len(l.ll)) - for i := range l.ll { - ss[i] = l.ll[i].String() - } - return "[" + strings.Join(ss, " ") + "]" -} - -// Type implements the method for Term -func (l list) Type() Term { - return Tuple{AList, l.typ} -} diff --git a/lang/match.go b/lang/match.go deleted file mode 100644 index 96520cd..0000000 --- a/lang/match.go +++ /dev/null @@ -1,54 +0,0 @@ -package lang - -import "fmt" - -// Match is used to pattern match an arbitrary Term against a pattern. A pattern -// is a 2-tuple of the type (as an atom, e.g. AAtom, AConst) and a matching -// value. -// -// If the value is AUnder the pattern will match all Terms of the type, -// regardless of their value. If the pattern's type and value are both AUnder -// the pattern will match all Terms. -// -// If the pattern's value is a Tuple or a List, each of its values will be used -// as a sub-pattern to match against the corresponding value in the value. -func Match(pat Tuple, t Term) bool { - if len(pat) != 2 { - return false - } - pt, pv := pat[0], pat[1] - - switch pt { - case AAtom: - a, ok := t.(Atom) - return ok && (Equal(pv, AUnder) || Equal(pv, a)) - case AConst: - c, ok := t.(Const) - return ok && (Equal(pv, AUnder) || Equal(pv, c)) - case ATuple: - tt, ok := t.(Tuple) - if !ok { - return false - } else if Equal(pv, AUnder) { - return true - } - - pvt := pv.(Tuple) - if len(tt) != len(pvt) { - return false - } - for i := range tt { - pvti, ok := pvt[i].(Tuple) - if !ok || !Match(pvti, tt[i]) { - return false - } - } - return true - case AList: - panic("TODO") - case AUnder: - return true - default: - panic(fmt.Sprintf("unknown type %T", pt)) - } -} diff --git a/lang/match_test.go b/lang/match_test.go deleted file mode 100644 index 02d8b25..0000000 --- a/lang/match_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package lang - -import ( - . "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMatch(t *T) { - pat := func(typ, val Term) Tuple { - return Tuple{typ, val} - } - - tests := []struct { - pattern Tuple - t Term - exp bool - }{ - {pat(AAtom, Atom("foo")), Atom("foo"), true}, - {pat(AAtom, Atom("foo")), Atom("bar"), false}, - {pat(AAtom, Atom("foo")), Const("foo"), false}, - {pat(AAtom, Atom("foo")), Tuple{Atom("a"), Atom("b")}, false}, - {pat(AAtom, Atom("_")), Atom("bar"), true}, - {pat(AAtom, Atom("_")), Const("bar"), false}, - - {pat(AConst, Const("foo")), Const("foo"), true}, - {pat(AConst, Const("foo")), Atom("foo"), false}, - {pat(AConst, Const("foo")), Const("bar"), false}, - {pat(AConst, Atom("_")), Const("bar"), true}, - {pat(AConst, Atom("_")), Atom("foo"), false}, - - { - pat(ATuple, Tuple{ - pat(AAtom, Atom("foo")), - pat(AAtom, Atom("bar")), - }), - Tuple{Atom("foo"), Atom("bar")}, - true, - }, - { - pat(ATuple, Tuple{ - pat(AAtom, Atom("_")), - pat(AAtom, Atom("bar")), - }), - Tuple{Atom("foo"), Atom("bar")}, - true, - }, - { - pat(ATuple, Tuple{ - pat(AAtom, Atom("_")), - pat(AAtom, Atom("_")), - pat(AAtom, Atom("_")), - }), - Tuple{Atom("foo"), Atom("bar")}, - false, - }, - - {pat(AUnder, AUnder), Atom("foo"), true}, - {pat(AUnder, AUnder), Const("foo"), true}, - {pat(AUnder, AUnder), Tuple{Atom("a"), Atom("b")}, true}, - } - - for _, testCase := range tests { - assert.Equal(t, testCase.exp, Match(testCase.pattern, testCase.t), "%#v", testCase) - } -} diff --git a/main.go b/main.go deleted file mode 100644 index 3194c67..0000000 --- a/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/mediocregopher/ginger/lang" - "github.com/mediocregopher/ginger/vm" -) - -func main() { - mkcmd := func(a lang.Atom, args ...lang.Term) lang.Tuple { - if len(args) == 1 { - return lang.Tuple{a, args[0]} - } - return lang.Tuple{a, lang.Tuple(args)} - } - mkint := func(i string) lang.Tuple { - return lang.Tuple{vm.Int, lang.Const(i)} - } - - //foo := lang.Atom("foo") - //tt := []lang.Term{ - // mkcmd(vm.Assign, foo, mkint("1")), - // mkcmd(vm.Add, mkcmd(vm.Tuple, mkcmd(vm.Var, foo), mkint("2"))), - //} - - foo := lang.Atom("foo") - bar := lang.Atom("bar") - baz := lang.Atom("baz") - tt := []lang.Term{ - mkcmd(vm.Assign, foo, mkcmd(vm.Tuple, mkint("1"), mkint("2"))), - mkcmd(vm.Assign, bar, mkcmd(vm.Add, mkcmd(vm.Var, foo))), - mkcmd(vm.Assign, baz, mkcmd(vm.Add, mkcmd(vm.Var, foo))), - mkcmd(vm.Add, mkcmd(vm.Tuple, mkcmd(vm.Var, bar), mkcmd(vm.Var, baz))), - } - - mod, err := vm.Build(tt...) - if err != nil { - panic(err) - } - defer mod.Dispose() - - mod.Dump() - - out, err := mod.Run() - fmt.Printf("\n\n########\nout: %v %v\n", out, err) -} diff --git a/sandbox/list/list.go b/sandbox/list/list.go deleted file mode 100644 index 10b3cfe..0000000 --- a/sandbox/list/list.go +++ /dev/null @@ -1,39 +0,0 @@ -package list - -import "fmt" - -/* - + size isn't really _necessary_ unless O(1) Len is wanted - + append doesn't work well on stack -*/ - -type List struct { - // in practice this would be a constant size, with the compiler knowing the - // size - underlying []int - head, size int -} - -func New(ii ...int) List { - l := List{ - underlying: make([]int, ii), - size: len(ii), - } - copy(l.underlying, ii) - return l -} - -func (l List) Len() int { - return l.size -} - -func (l List) HeadTail() (int, List) { - if l.size == 0 { - panic(fmt.Sprintf("can't take HeadTail of empty list")) - } - return l.underlying[l.head], List{ - underlying: l.underlying, - head: l.head + 1, - size: l.size - 1, - } -} diff --git a/vm/cmds.go b/vm/cmds.go deleted file mode 100644 index 2358af9..0000000 --- a/vm/cmds.go +++ /dev/null @@ -1,280 +0,0 @@ -package vm - -import ( - "errors" - "fmt" - "strconv" - - "github.com/mediocregopher/ginger/lang" - "llvm.org/llvm/bindings/go/llvm" -) - -type op interface { - inType() valType - outType() valType - build(*Module) (llvm.Value, error) -} - -type valType struct { - term lang.Term - llvm llvm.Type -} - -func (vt valType) isInt() bool { - return lang.Equal(Int, vt.term) -} - -func (vt valType) eq(vt2 valType) bool { - return lang.Equal(vt.term, vt2.term) && vt.llvm == vt2.llvm -} - -// primitive valTypes -var ( - valTypeVoid = valType{term: lang.Tuple{}, llvm: llvm.VoidType()} - valTypeInt = valType{term: Int, llvm: llvm.Int64Type()} -) - -//////////////////////////////////////////////////////////////////////////////// - -// most types don't have an input, so we use this as a shortcut -type voidIn struct{} - -func (voidIn) inType() valType { - return valTypeVoid -} - -//////////////////////////////////////////////////////////////////////////////// - -type intOp struct { - voidIn - c lang.Const -} - -func (io intOp) outType() valType { - return valTypeInt -} - -func (io intOp) build(mod *Module) (llvm.Value, error) { - ci, err := strconv.ParseInt(string(io.c), 10, 64) - if err != nil { - return llvm.Value{}, err - } - return llvm.ConstInt(llvm.Int64Type(), uint64(ci), false), nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type tupOp struct { - voidIn - els []op -} - -func (to tupOp) outType() valType { - termTypes := make(lang.Tuple, len(to.els)) - llvmTypes := make([]llvm.Type, len(to.els)) - for i := range to.els { - elValType := to.els[i].outType() - termTypes[i] = elValType.term - llvmTypes[i] = elValType.llvm - } - vt := valType{term: lang.Tuple{Tuple, termTypes}} - if len(llvmTypes) == 0 { - vt.llvm = llvm.VoidType() - } else { - vt.llvm = llvm.StructType(llvmTypes, false) - } - return vt -} - -func (to tupOp) build(mod *Module) (llvm.Value, error) { - str := llvm.Undef(to.outType().llvm) - var val llvm.Value - var err error - for i := range to.els { - if val, err = to.els[i].build(mod); err != nil { - return llvm.Value{}, err - } - str = mod.b.CreateInsertValue(str, val, i, "") - } - return str, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type tupElOp struct { - voidIn - tup op - i int -} - -func (teo tupElOp) outType() valType { - tupType := teo.tup.outType() - return valType{ - llvm: tupType.llvm.StructElementTypes()[teo.i], - term: tupType.term.(lang.Tuple)[1].(lang.Tuple)[1], - } -} - -func (teo tupElOp) build(mod *Module) (llvm.Value, error) { - if to, ok := teo.tup.(tupOp); ok { - return to.els[teo.i].build(mod) - } - - tv, err := teo.tup.build(mod) - if err != nil { - return llvm.Value{}, err - } - return mod.b.CreateExtractValue(tv, teo.i, ""), nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type varOp struct { - op - v llvm.Value - built bool -} - -func (vo *varOp) build(mod *Module) (llvm.Value, error) { - if !vo.built { - var err error - if vo.v, err = vo.op.build(mod); err != nil { - return llvm.Value{}, err - } - vo.built = true - } - return vo.v, nil -} - -type varCtx map[string]*varOp - -func (c varCtx) assign(name string, vo *varOp) error { - if _, ok := c[name]; ok { - return fmt.Errorf("var %q already assigned", name) - } - c[name] = vo - return nil -} - -func (c varCtx) get(name string) (*varOp, error) { - if o, ok := c[name]; ok { - return o, nil - } - return nil, fmt.Errorf("var %q referenced before assignment", name) -} - -//////////////////////////////////////////////////////////////////////////////// - -type addOp struct { - voidIn - a, b op -} - -func (ao addOp) outType() valType { - return ao.a.outType() -} - -func (ao addOp) build(mod *Module) (llvm.Value, error) { - av, err := ao.a.build(mod) - if err != nil { - return llvm.Value{}, err - } - bv, err := ao.b.build(mod) - if err != nil { - return llvm.Value{}, err - } - return mod.b.CreateAdd(av, bv, ""), nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func termToOp(ctx varCtx, t lang.Term) (op, error) { - aPat := func(a lang.Atom) lang.Tuple { - return lang.Tuple{lang.AAtom, a} - } - cPat := func(t lang.Term) lang.Tuple { - return lang.Tuple{lang.AConst, t} - } - tPat := func(el ...lang.Term) lang.Tuple { - return lang.Tuple{Tuple, lang.Tuple(el)} - } - - if !lang.Match(tPat(aPat(lang.AUnder), lang.TDblUnder), t) { - return nil, fmt.Errorf("term %v does not look like a vm command", t) - } - k := t.(lang.Tuple)[0].(lang.Atom) - v := t.(lang.Tuple)[1] - - // for when v is a Tuple argument, convenience function for casting - vAsTup := func(n int) ([]op, error) { - vop, err := termToOp(ctx, v) - if err != nil { - return nil, err - } - ops := make([]op, n) - for i := range ops { - ops[i] = tupElOp{tup: vop, i: i} - } - - return ops, nil - } - - switch k { - case Int: - if !lang.Match(cPat(lang.AUnder), v) { - return nil, errors.New("int requires constant arg") - } - return intOp{c: v.(lang.Const)}, nil - case Tuple: - if !lang.Match(lang.Tuple{Tuple, lang.AUnder}, v) { - return nil, errors.New("tup requires tuple arg") - } - tup := v.(lang.Tuple) - tc := tupOp{els: make([]op, len(tup))} - var err error - for i := range tup { - if tc.els[i], err = termToOp(ctx, tup[i]); err != nil { - return nil, err - } - } - return tc, nil - case Var: - if !lang.Match(aPat(lang.AUnder), v) { - return nil, errors.New("var requires atom arg") - } - name := v.(lang.Atom).String() - return ctx.get(name) - - case Assign: - if !lang.Match(tPat(tPat(aPat(Var), aPat(lang.AUnder)), lang.TDblUnder), v) { - return nil, errors.New("assign requires 2-tuple arg, the first being a var") - } - tup := v.(lang.Tuple) - name := tup[0].(lang.Tuple)[1].String() - o, err := termToOp(ctx, tup[1]) - if err != nil { - return nil, err - } - - vo := &varOp{op: o} - if err := ctx.assign(name, vo); err != nil { - return nil, err - } - return vo, nil - - // Add is special in some way, I think it's a function not a compiler op, - // not sure yet though - case Add: - els, err := vAsTup(2) - if err != nil { - return nil, err - } else if !els[0].outType().eq(valTypeInt) { - return nil, errors.New("add args must be numbers of the same type") - } else if !els[1].outType().eq(valTypeInt) { - return nil, errors.New("add args must be numbers of the same type") - } - return addOp{a: els[0], b: els[1]}, nil - default: - return nil, fmt.Errorf("op %v unknown, or its args are malformed", t) - } -} diff --git a/vm/vm.go b/vm/vm.go deleted file mode 100644 index 1ce39fb..0000000 --- a/vm/vm.go +++ /dev/null @@ -1,129 +0,0 @@ -package vm - -import ( - "errors" - "fmt" - "sync" - - "github.com/mediocregopher/ginger/lang" - - "llvm.org/llvm/bindings/go/llvm" -) - -// Types supported by the vm in addition to those which are part of lang -var ( - Atom = lang.AAtom - Tuple = lang.ATuple - Int = lang.Atom("int") -) - -// Ops supported by the vm -var ( - Add = lang.Atom("add") - Assign = lang.Atom("assign") - Var = lang.Atom("var") -) - -//////////////////////////////////////////////////////////////////////////////// - -// Module contains a compiled set of code which can be run, dumped in IR form, -// or compiled. A Module should be Dispose()'d of once it's no longer being -// used. -type Module struct { - b llvm.Builder - m llvm.Module - ctx varCtx - mainFn llvm.Value -} - -var initOnce sync.Once - -// Build creates a new Module by compiling the given Terms as code -// TODO only take in a single Term, implement List and use that with a do op -func Build(tt ...lang.Term) (*Module, error) { - initOnce.Do(func() { - llvm.LinkInMCJIT() - llvm.InitializeNativeTarget() - llvm.InitializeNativeAsmPrinter() - }) - mod := &Module{ - b: llvm.NewBuilder(), - m: llvm.NewModule(""), - ctx: varCtx{}, - } - - var err error - if mod.mainFn, err = mod.buildFn(tt...); err != nil { - mod.Dispose() - return nil, err - } - - if err := llvm.VerifyModule(mod.m, llvm.ReturnStatusAction); err != nil { - mod.Dispose() - return nil, fmt.Errorf("could not verify module: %s", err) - } - - return mod, nil -} - -// Dispose cleans up all resources held by the Module -func (mod *Module) Dispose() { - // TODO this panics for some reason... - //mod.m.Dispose() - //mod.b.Dispose() -} - -// TODO make this return a val once we get function types -func (mod *Module) buildFn(tt ...lang.Term) (llvm.Value, error) { - if len(tt) == 0 { - return llvm.Value{}, errors.New("function cannot be empty") - } - - ops := make([]op, len(tt)) - var err error - for i := range tt { - if ops[i], err = termToOp(mod.ctx, tt[i]); err != nil { - return llvm.Value{}, err - } - } - - var llvmIns []llvm.Type - if in := ops[0].inType(); in.llvm.TypeKind() == llvm.VoidTypeKind { - llvmIns = []llvm.Type{} - } else { - llvmIns = []llvm.Type{in.llvm} - } - llvmOut := ops[len(ops)-1].outType().llvm - - fn := llvm.AddFunction(mod.m, "", llvm.FunctionType(llvmOut, llvmIns, false)) - block := llvm.AddBasicBlock(fn, "") - mod.b.SetInsertPoint(block, block.FirstInstruction()) - - var out llvm.Value - for i := range ops { - if out, err = ops[i].build(mod); err != nil { - return llvm.Value{}, err - } - } - mod.b.CreateRet(out) - return fn, nil -} - -// Dump dumps the Module's IR to stdout -func (mod *Module) Dump() { - mod.m.Dump() -} - -// Run executes the Module -// TODO input and output? -func (mod *Module) Run() (interface{}, error) { - engine, err := llvm.NewExecutionEngine(mod.m) - if err != nil { - return nil, err - } - defer engine.Dispose() - - funcResult := engine.RunFunction(mod.mainFn, []llvm.GenericValue{}) - defer funcResult.Dispose() - return funcResult.Int(false), nil -} diff --git a/vm/vm_test.go b/vm/vm_test.go deleted file mode 100644 index b08a78c..0000000 --- a/vm/vm_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package vm - -import ( - . "testing" - - "github.com/mediocregopher/ginger/lang" -) - -func TestCompiler(t *T) { - mkcmd := func(a lang.Atom, args ...lang.Term) lang.Tuple { - // TODO a 1-tuple should be the same as its element? - if len(args) == 1 { - return lang.Tuple{a, args[0]} - } - return lang.Tuple{a, lang.Tuple(args)} - } - mkint := func(i string) lang.Tuple { - return lang.Tuple{Int, lang.Const(i)} - } - - type test struct { - in []lang.Term - exp uint64 - } - - one := mkint("1") - two := mkint("2") - foo := mkcmd(Var, lang.Atom("foo")) - bar := mkcmd(Var, lang.Atom("bar")) - baz := mkcmd(Var, lang.Atom("baz")) - - tests := []test{ - { - in: []lang.Term{one}, - exp: 1, - }, - { - in: []lang.Term{ - mkcmd(Add, mkcmd(Tuple, one, two)), - }, - exp: 3, - }, - { - in: []lang.Term{ - mkcmd(Assign, foo, one), - mkcmd(Add, mkcmd(Tuple, foo, two)), - }, - exp: 3, - }, - { - in: []lang.Term{ - mkcmd(Assign, foo, mkcmd(Tuple, one, two)), - mkcmd(Add, foo), - }, - exp: 3, - }, - { - in: []lang.Term{ - mkcmd(Assign, foo, mkcmd(Tuple, one, two)), - mkcmd(Assign, bar, mkcmd(Add, foo)), - mkcmd(Assign, baz, mkcmd(Add, foo)), - mkcmd(Add, mkcmd(Tuple, bar, baz)), - }, - exp: 6, - }, - } - - for _, test := range tests { - t.Logf("testing program: %v", test.in) - mod, err := Build(test.in...) - if err != nil { - t.Fatalf("building failed: %s", err) - } - - out, err := mod.Run() - if err != nil { - mod.Dump() - t.Fatalf("running failed: %s", err) - } else if out != test.exp { - mod.Dump() - t.Fatalf("expected result %T:%v, got %T:%v", test.exp, test.exp, out, out) - } - } -}