taking a new approach using tuples and atoms for compilation, it's working out a lot better

This commit is contained in:
Brian Picciano 2017-02-11 10:24:02 -07:00
parent b0b5b01fd9
commit 54448fda80
6 changed files with 419 additions and 4 deletions

18
lang/common.go Normal file
View File

@ -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")
)

105
lang/lang.go Normal file
View File

@ -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}
}

54
lang/match.go Normal file
View File

@ -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))
}
}

66
lang/match_test.go Normal file
View File

@ -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)
}
}

26
main.go
View File

@ -2,13 +2,30 @@ package main
import ( import (
"fmt" "fmt"
"log"
"github.com/mediocregopher/ginger/expr" "github.com/mediocregopher/ginger/lang"
"github.com/mediocregopher/ginger/vm"
"llvm.org/llvm/bindings/go/llvm"
) )
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() { func main() {
//ee, err := expr.Parse(os.Stdin) //ee, err := expr.Parse(os.Stdin)
//if err != nil { //if err != nil {
@ -73,3 +90,4 @@ func main() {
funcResult := engine.RunFunction(bctx.M.NamedFunction("main"), []llvm.GenericValue{}) funcResult := engine.RunFunction(bctx.M.NamedFunction("main"), []llvm.GenericValue{})
fmt.Printf("\nOUTPUT:\n%d\n", funcResult.Int(false)) fmt.Printf("\nOUTPUT:\n%d\n", funcResult.Int(false))
} }
*/

154
vm/vm.go Normal file
View File

@ -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
}