diff --git a/lang/common.go b/lang/common.go new file mode 100644 index 0000000..5cffd60 --- /dev/null +++ b/lang/common.go @@ -0,0 +1,18 @@ +package lang + +// 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} + + // VM commands + AInt = Atom("int") + AAdd = Atom("add") +) diff --git a/lang/lang.go b/lang/lang.go new file mode 100644 index 0000000..bf03ce7 --- /dev/null +++ b/lang/lang.go @@ -0,0 +1,105 @@ +package lang + +import ( + "fmt" + "reflect" + "strings" +) + +// 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 Atom("atom") +} + +//////////////////////////////////////////////////////////////////////////////// + +// 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 Const("const") +} + +//////////////////////////////////////////////////////////////////////////////// + +// 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{Atom("tup"), 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{Atom("list"), l.typ} +} diff --git a/lang/match.go b/lang/match.go new file mode 100644 index 0000000..96520cd --- /dev/null +++ b/lang/match.go @@ -0,0 +1,54 @@ +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 new file mode 100644 index 0000000..02d8b25 --- /dev/null +++ b/lang/match_test.go @@ -0,0 +1,66 @@ +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 index 3113565..83ec625 100644 --- a/main.go +++ b/main.go @@ -2,13 +2,30 @@ package main import ( "fmt" - "log" - "github.com/mediocregopher/ginger/expr" - - "llvm.org/llvm/bindings/go/llvm" + "github.com/mediocregopher/ginger/lang" + "github.com/mediocregopher/ginger/vm" ) +func main() { + t := lang.Tuple{lang.AAdd, lang.Tuple{ + lang.Tuple{lang.AInt, lang.Const("1")}, + lang.Tuple{lang.AInt, lang.Const("2")}, + }} + + mod, err := vm.Build(t) + if err != nil { + panic(err) + } + defer mod.Dispose() + + mod.Dump() + + out, err := mod.Run() + fmt.Printf("\n\n########\nout: %v %v\n", out, err) +} + +/* func main() { //ee, err := expr.Parse(os.Stdin) //if err != nil { @@ -73,3 +90,4 @@ func main() { funcResult := engine.RunFunction(bctx.M.NamedFunction("main"), []llvm.GenericValue{}) fmt.Printf("\nOUTPUT:\n%d\n", funcResult.Int(false)) } +*/ diff --git a/vm/vm.go b/vm/vm.go new file mode 100644 index 0000000..649d8f3 --- /dev/null +++ b/vm/vm.go @@ -0,0 +1,154 @@ +package vm + +import ( + "fmt" + "strconv" + "sync" + + "github.com/mediocregopher/ginger/lang" + + "llvm.org/llvm/bindings/go/llvm" +) + +// Val holds onto a value which has been created within the VM +type Val struct { + v llvm.Value +} + +// 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 + mainFn llvm.Value +} + +var initOnce sync.Once + +// Build creates a new Module by compiling the given Term as code +func Build(t lang.Term) (*Module, error) { + initOnce.Do(func() { + llvm.LinkInMCJIT() + llvm.InitializeNativeTarget() + llvm.InitializeNativeAsmPrinter() + }) + mod := &Module{ + b: llvm.NewBuilder(), + m: llvm.NewModule(""), + } + + // TODO figure out types + mod.mainFn = llvm.AddFunction(mod.m, "", llvm.FunctionType(llvm.Int64Type(), []llvm.Type{}, false)) + block := llvm.AddBasicBlock(mod.mainFn, "") + mod.b.SetInsertPoint(block, block.FirstInstruction()) + + out, err := mod.build(t) + if err != nil { + mod.Dispose() + return nil, err + } + mod.b.CreateRet(out.v) + + 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() +} + +func (mod *Module) build(t lang.Term) (Val, 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{lang.ATuple, lang.Tuple(el)} + } + match := func(a lang.Atom, b lang.Tuple) bool { + return lang.Match(tPat(aPat(a), b), t) + } + + switch { + // (int 42) + case match(lang.AInt, cPat(lang.AUnder)): + con := t.(lang.Tuple)[1].(lang.Const) + coni, err := strconv.ParseInt(string(con), 10, 64) + if err != nil { + return Val{}, err + } + return Val{ + // TODO why does this have to be cast? + v: llvm.ConstInt(llvm.Int64Type(), uint64(coni), false), + }, nil + + // (tup ((atom foo) (const 10))) + case match(lang.ATuple, lang.Tuple{lang.ATuple, lang.AUnder}): + tup := t.(lang.Tuple)[1].(lang.Tuple) + // if the tuple is empty then it is a void + if len(tup) == 0 { + return Val{v: llvm.Undef(llvm.VoidType())}, nil + } + + var err error + vals := make([]Val, len(tup)) + typs := make([]llvm.Type, len(tup)) + for i := range tup { + if vals[i], err = mod.build(tup[i]); err != nil { + return Val{}, err + } + typs[i] = vals[i].v.Type() + } + + str := llvm.Undef(llvm.StructType(typs, false)) + for i := range vals { + str = mod.b.CreateInsertValue(str, vals[i].v, i, "") + } + return Val{v: str}, nil + + // (add ((const 5) (var foo))) + case match(lang.AAdd, tPat(lang.TDblUnder, lang.TDblUnder)): + tup := t.(lang.Tuple)[1].(lang.Tuple) + v1, err := mod.build(tup[0]) + if err != nil { + return Val{}, err + } + v2, err := mod.build(tup[1]) + if err != nil { + return Val{}, err + } + return Val{v: mod.b.CreateAdd(v1.v, v2.v, "")}, nil + + default: + return Val{}, fmt.Errorf("unknown compiler command %v", t) + } +} + +// 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 +}