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 (
|
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
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