Skip to content

Commit

Permalink
Merge pull request #17 from axone-protocol/feat/dicts
Browse files Browse the repository at this point in the history
Add (preliminary) support for dicts structure
  • Loading branch information
ccamel authored Nov 20, 2024
2 parents be90013 + f1d5e88 commit 7be6699
Show file tree
Hide file tree
Showing 18 changed files with 1,920 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.23

- name: Build
run: go build -v ./...
Expand Down
2 changes: 2 additions & 0 deletions engine/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
atomGreaterThan = NewAtom(">")
atomDot = NewAtom(".")
atomComma = NewAtom(",")
atomDict = NewAtom("dict")
atomBar = NewAtom("|")
atomCut = NewAtom("!")
atomSemiColon = NewAtom(";")
Expand Down Expand Up @@ -66,6 +67,7 @@ var (
atomCompound = NewAtom("compound")
atomCreate = NewAtom("create")
atomDebug = NewAtom("debug")
atomDictKey = NewAtom("dict_key")
atomDiscontiguous = NewAtom("discontiguous")
atomDiv = NewAtom("div")
atomDomainError = NewAtom("domain_error")
Expand Down
5 changes: 5 additions & 0 deletions engine/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ func renamedCopy(t Term, copied map[termID]Term, env *Env) (Term, error) {
}
c.args[i] = cp
}

if _, ok := t.(Dict); ok {
return &dict{c}, nil
}

return &c, nil
default:
return t, nil
Expand Down
15 changes: 12 additions & 3 deletions engine/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestCall(t *testing.T) {
assert.NoError(t, vm.Compile(context.Background(), `
foo.
foo(_, _).
f(g([a, [b, c|X]])).
f(g([a, [b, c|X], Y{x:5}])).
`))

tests := []struct {
Expand All @@ -55,14 +55,14 @@ f(g([a, [b, c|X]])).
{title: `undefined atom`, goal: NewAtom("bar"), ok: false, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(0)), nil)},
{title: `defined atom`, goal: NewAtom("foo"), ok: true},
{title: `undefined compound`, goal: NewAtom("bar").Apply(NewVariable(), NewVariable()), ok: false, err: existenceError(objectTypeProcedure, atomSlash.Apply(NewAtom("bar"), Integer(2)), nil)},
{title: `defined compound`, goal: NewAtom("foo").Apply(NewVariable(), NewVariable()), ok: true},
{title: `defined compound`, goal: NewAtom("foo").Apply(NewVariable(), makeDict(NewVariable())), ok: true},
{title: `variable: single predicate`, goal: NewVariable(), ok: false, err: InstantiationError(nil)},
{title: `variable: multiple predicates`, goal: atomComma.Apply(atomFail, NewVariable()), ok: false},
{title: `not callable: single predicate`, goal: Integer(0), ok: false, err: typeError(validTypeCallable, Integer(0), nil)},
{title: `not callable: conjunction`, goal: atomComma.Apply(atomTrue, Integer(0)), ok: false, err: typeError(validTypeCallable, atomComma.Apply(atomTrue, Integer(0)), nil)},
{title: `not callable: disjunction`, goal: atomSemiColon.Apply(Integer(1), atomTrue), ok: false, err: typeError(validTypeCallable, atomSemiColon.Apply(Integer(1), atomTrue), nil)},

{title: `cover all`, goal: atomComma.Apply(atomCut, NewAtom("f").Apply(NewAtom("g").Apply(List(NewAtom("a"), PartialList(NewVariable(), NewAtom("b"), NewAtom("c")))))), ok: true},
{title: `cover all`, goal: atomComma.Apply(atomCut, NewAtom("f").Apply(NewAtom("g").Apply(List(NewAtom("a"), PartialList(NewVariable(), NewAtom("b"), NewAtom("c")), makeDict(NewAtom("foo"), NewAtom("x"), Integer(5)))))), ok: true},
{title: `out of memory`, goal: NewAtom("foo").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), err: resourceError(resourceMemory, nil), mem: 1},
{title: `panic`, goal: NewAtom("do_not_call"), err: PanicError{errors.New("told you")}},
{title: `panic (lazy)`, goal: NewAtom("lazy_do_not_call"), err: PanicError{errors.New("told you")}},
Expand Down Expand Up @@ -583,6 +583,15 @@ func TestTypeCompound(t *testing.T) {
assert.True(t, ok)
})

t.Run("dict is compound", func(t *testing.T) {
ok, err := TypeCompound(nil, &dict{compound{
functor: NewAtom("foo"),
args: []Term{NewAtom("a")},
}}, Success, nil).Force(context.Background())
assert.NoError(t, err)
assert.True(t, ok)
})

t.Run("not compound", func(t *testing.T) {
ok, err := TypeCompound(nil, NewAtom("foo"), Success, nil).Force(context.Background())
assert.NoError(t, err)
Expand Down
125 changes: 102 additions & 23 deletions engine/clause.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,78 @@ type clause struct {

func compileClause(head Term, body Term, env *Env) (clause, error) {
var c clause
var goals []Term

head, goals = desugar(head, goals)
body, goals = desugar(body, goals)

if body != nil {
goals = append(goals, body)
}
if len(goals) > 0 {
body = seq(atomComma, goals...)
}

c.compileHead(head, env)

if body != nil {
if err := c.compileBody(body, env); err != nil {
return c, typeError(validTypeCallable, body, env)
}
}
c.bytecode = append(c.bytecode, instruction{opcode: OpExit})

c.emit(instruction{opcode: OpExit})
return c, nil
}

func desugar(term Term, acc []Term) (Term, []Term) {
switch t := term.(type) {
case charList, codeList:
return t, acc
case list:
l := make(list, len(t))
for i, e := range t {
l[i], acc = desugar(e, acc)
}
return l, acc
case *partial:
c, acc := desugar(t.Compound, acc)
tail, acc := desugar(*t.tail, acc)
return &partial{
Compound: c.(Compound),
tail: &tail,
}, acc
case Compound:
if t.Functor() == atomSpecialDot && t.Arity() == 2 {
tempV := NewVariable()
lhs, acc := desugar(t.Arg(0), acc)
rhs, acc := desugar(t.Arg(1), acc)

return tempV, append(acc, atomDot.Apply(lhs, rhs, tempV))
}

c := compound{
functor: t.Functor(),
args: make([]Term, t.Arity()),
}
for i := 0; i < t.Arity(); i++ {
c.args[i], acc = desugar(t.Arg(i), acc)
}

if _, ok := t.(Dict); ok {
return &dict{c}, acc
}

return &c, acc
default:
return t, acc
}
}

func (c *clause) emit(i instruction) {
c.bytecode = append(c.bytecode, i)
}

func (c *clause) compileHead(head Term, env *Env) {
switch head := env.Resolve(head).(type) {
case Atom:
Expand All @@ -88,7 +150,7 @@ func (c *clause) compileHead(head Term, env *Env) {
}

func (c *clause) compileBody(body Term, env *Env) error {
c.bytecode = append(c.bytecode, instruction{opcode: OpEnter})
c.emit(instruction{opcode: OpEnter})
iter := seqIterator{Seq: body, Env: env}
for iter.Next() {
if err := c.compilePred(iter.Current(), env); err != nil {
Expand All @@ -107,16 +169,16 @@ func (c *clause) compilePred(p Term, env *Env) error {
case Atom:
switch p {
case atomCut:
c.bytecode = append(c.bytecode, instruction{opcode: OpCut})
c.emit(instruction{opcode: OpCut})
return nil
}
c.bytecode = append(c.bytecode, instruction{opcode: OpCall, operand: procedureIndicator{name: p, arity: 0}})
c.emit(instruction{opcode: OpCall, operand: procedureIndicator{name: p, arity: 0}})
return nil
case Compound:
for i := 0; i < p.Arity(); i++ {
c.compileBodyArg(p.Arg(i), env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpCall, operand: procedureIndicator{name: p.Functor(), arity: Integer(p.Arity())}})
c.emit(instruction{opcode: OpCall, operand: procedureIndicator{name: p.Functor(), arity: Integer(p.Arity())}})
return nil
default:
return errNotCallable
Expand All @@ -126,67 +188,84 @@ func (c *clause) compilePred(p Term, env *Env) error {
func (c *clause) compileHeadArg(a Term, env *Env) {
switch a := env.Resolve(a).(type) {
case Variable:
c.bytecode = append(c.bytecode, instruction{opcode: OpGetVar, operand: c.varOffset(a)})
c.emit(instruction{opcode: OpGetVar, operand: c.varOffset(a)})
case charList, codeList: // Treat them as if they're atomic.
c.bytecode = append(c.bytecode, instruction{opcode: OpGetConst, operand: a})
c.emit(instruction{opcode: OpGetConst, operand: a})
case list:
c.bytecode = append(c.bytecode, instruction{opcode: OpGetList, operand: Integer(len(a))})
c.emit(instruction{opcode: OpGetList, operand: Integer(len(a))})
for _, arg := range a {
c.compileHeadArg(arg, env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
case *partial:
prefix := a.Compound.(list)
c.bytecode = append(c.bytecode, instruction{opcode: OpGetPartial, operand: Integer(len(prefix))})
c.emit(instruction{opcode: OpGetPartial, operand: Integer(len(prefix))})
c.compileHeadArg(*a.tail, env)
for _, arg := range prefix {
c.compileHeadArg(arg, env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
case Compound:
c.bytecode = append(c.bytecode, instruction{opcode: OpGetFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}})
switch a.(type) {
case Dict:
c.emit(instruction{opcode: OpGetDict, operand: Integer(a.Arity())})
default:
c.emit(instruction{opcode: OpGetFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}})
}

for i := 0; i < a.Arity(); i++ {
c.compileHeadArg(a.Arg(i), env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
default:
c.bytecode = append(c.bytecode, instruction{opcode: OpGetConst, operand: a})
c.emit(instruction{opcode: OpGetConst, operand: a})
}
}

func (c *clause) compileBodyArg(a Term, env *Env) {
switch a := env.Resolve(a).(type) {
case Variable:
c.bytecode = append(c.bytecode, instruction{opcode: OpPutVar, operand: c.varOffset(a)})
c.emit(instruction{opcode: OpPutVar, operand: c.varOffset(a)})
case charList, codeList: // Treat them as if they're atomic.
c.bytecode = append(c.bytecode, instruction{opcode: OpPutConst, operand: a})
c.emit(instruction{opcode: OpPutConst, operand: a})
case list:
c.bytecode = append(c.bytecode, instruction{opcode: OpPutList, operand: Integer(len(a))})
c.emit(instruction{opcode: OpPutList, operand: Integer(len(a))})
for _, arg := range a {
c.compileBodyArg(arg, env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
case Dict:
c.emit(instruction{opcode: OpPutDict, operand: Integer(a.Arity())})
for i := 0; i < a.Arity(); i++ {
c.compileBodyArg(a.Arg(i), env)
}
c.emit(instruction{opcode: OpPop})
case *partial:
var l int
iter := ListIterator{List: a.Compound}
for iter.Next() {
l++
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPutPartial, operand: Integer(l)})
c.emit(instruction{opcode: OpPutPartial, operand: Integer(l)})
c.compileBodyArg(*a.tail, env)
iter = ListIterator{List: a.Compound}
for iter.Next() {
c.compileBodyArg(iter.Current(), env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
case Compound:
c.bytecode = append(c.bytecode, instruction{opcode: OpPutFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}})
switch a.(type) {
case Dict:
c.emit(instruction{opcode: OpPutDict, operand: Integer(a.Arity())})
default:
c.emit(instruction{opcode: OpPutFunctor, operand: procedureIndicator{name: a.Functor(), arity: Integer(a.Arity())}})
}
for i := 0; i < a.Arity(); i++ {
c.compileBodyArg(a.Arg(i), env)
}
c.bytecode = append(c.bytecode, instruction{opcode: OpPop})
c.emit(instruction{opcode: OpPop})
default:
c.bytecode = append(c.bytecode, instruction{opcode: OpPutConst, operand: a})
c.emit(instruction{opcode: OpPutConst, operand: a})
}
}

Expand Down
Loading

0 comments on commit 7be6699

Please sign in to comment.