taking a new approach using tuples and atoms for compilation, it's working out a lot better
This commit is contained in:
parent
b0b5b01fd9
commit
54448fda80
18
lang/common.go
Normal file
18
lang/common.go
Normal 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
105
lang/lang.go
Normal 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
54
lang/match.go
Normal 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
66
lang/match_test.go
Normal 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
26
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))
|
||||
}
|
||||
*/
|
||||
|
154
vm/vm.go
Normal file
154
vm/vm.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user