Skip to content

Commit

Permalink
vm: Implement COMPARE instruction and remove IS_EQUAL
Browse files Browse the repository at this point in the history
  • Loading branch information
rpbeltran committed Jan 4, 2025
1 parent efa5e1f commit d100116
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 91 deletions.
114 changes: 114 additions & 0 deletions vm/instruction_compare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package vm

import (
"fmt"
"yolk/types"
)

const (
comparison_equal uint8 = iota
comparison_unequal uint8 = iota
comparison_less uint8 = iota
comparison_lte uint8 = iota
comparison_greater uint8 = iota
comparison_gte uint8 = iota
)

type Instruction_COMPARE struct {
mode uint8
}

func (instruction *Instruction_COMPARE) Parse(args *string) error {
switch *args {
case "equal":
instruction.mode = comparison_equal
case "unequal":
instruction.mode = comparison_unequal
case "less":
instruction.mode = comparison_less
case "lte":
instruction.mode = comparison_lte
case "greater":
instruction.mode = comparison_greater
case "gte":
instruction.mode = comparison_gte
default:
if len(*args) == 0 {
return fmt.Errorf("COMPARE instruction needs a test mode, none provided")
}
return fmt.Errorf("COMPARE instruction specifies unexpected test mode %q", *args)
}
return nil
}

func (instruction *Instruction_COMPARE) String() string {
switch instruction.mode {
case comparison_equal:
return "COMPARE equal"
case comparison_unequal:
return "COMPARE unequal"
case comparison_less:
return "COMPARE less"
case comparison_lte:
return "COMPARE lte"
case comparison_greater:
return "COMPARE greater"
case comparison_gte:
return "COMPARE gte"
default:
panic(fmt.Sprintf("Unimplemented COMPARE serialization for test mode %d", instruction.mode))
}
}

func (instruction *Instruction_COMPARE) Perform(vm *VirtualMachine) error {
left, err := vm.stack.Pop()
if err != nil {
return fmt.Errorf("popping lhs for COMPARE: %v", err)
}

right, err := vm.stack.Pop()
if err != nil {
return fmt.Errorf("popping rhs for COMPARE: %v", err)
}

switch instruction.mode {
case comparison_equal:
vm.stack.Push(types.MakeBool(left.Equal(right)))
case comparison_unequal:
vm.stack.Push(types.MakeBool(!left.Equal(right)))
case comparison_less:
if lt, err := left.LessThan(right); err != nil {
return fmt.Errorf("computing \"less than\": %v", err)
} else {
vm.stack.Push(types.MakeBool(lt))
}
case comparison_lte:
if left.Equal(right) {
vm.stack.Push(types.MakeBool(true))
} else if lt, err := left.LessThan(right); err != nil {
return fmt.Errorf("computing \"less than or equal to\": %v", err)
} else {
vm.stack.Push(types.MakeBool(lt))
}
case comparison_greater:
if left.Equal(right) {
vm.stack.Push(types.MakeBool(false))
} else if lt, err := left.LessThan(right); err != nil {
return fmt.Errorf("computing \"greater than\": %v", err)
} else {
vm.stack.Push(types.MakeBool(!lt))
}
case comparison_gte:
if left.Equal(right) {
vm.stack.Push(types.MakeBool(true))
} else if lt, err := left.LessThan(right); err != nil {
return fmt.Errorf("computing \"greater than\": %v", err)
} else {
vm.stack.Push(types.MakeBool(!lt))
}
default:
panic(fmt.Sprintf("Unimplemented COMPARE for test mode %d", instruction.mode))
}

return nil
}
91 changes: 91 additions & 0 deletions vm/instruction_compare_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package vm

import (
"fmt"
"testing"
"yolk/types"
)

func TestIsEqualParsing(t *testing.T) {
expected_type := "*vm.Instruction_COMPARE"

ExpectParse(t, "COMPARE equal", expected_type, "COMPARE equal")
ExpectParse(t, "COMPARE unequal", expected_type, "COMPARE unequal")
ExpectParse(t, "COMPARE less", expected_type, "COMPARE less")
ExpectParse(t, "COMPARE lte", expected_type, "COMPARE lte")
ExpectParse(t, "COMPARE greater", expected_type, "COMPARE greater")
ExpectParse(t, "COMPARE gte", expected_type, "COMPARE gte")
ExpectParseFailure(t, "COMPARE", "needs a test mode")
ExpectParseFailure(t, "COMPARE 1", "unexpected test mode")
ExpectParseFailure(t, "COMPARE foo", "unexpected test mode")
ExpectParseFailure(t, "COMPARE true", "unexpected test mode")
}

type CompareTestCase struct {
left types.Primitive
right types.Primitive
comparison string
expected bool
}

func TestComparePerform(t *testing.T) {
tests := []CompareTestCase{
{types.MakeString("foo"), types.MakeString("bar"), "equal", false},
{types.MakeString("foo"), types.MakeString("foo"), "equal", true},
{types.MakeString("foo"), types.MakeString("bar"), "unequal", true},
{types.MakeString("foo"), types.MakeString("foo"), "unequal", false},
{types.MakeString("a"), types.MakeString("b"), "less", true},
{types.MakeString("a"), types.MakeString("a"), "less", false},
{types.MakeString("b"), types.MakeString("a"), "less", false},
{types.MakeString("a"), types.MakeString("b"), "lte", true},
{types.MakeString("a"), types.MakeString("a"), "lte", true},
{types.MakeString("b"), types.MakeString("a"), "lte", false},
{types.MakeString("a"), types.MakeString("b"), "greater", false},
{types.MakeString("a"), types.MakeString("a"), "greater", false},
{types.MakeString("b"), types.MakeString("a"), "greater", true},
{types.MakeString("a"), types.MakeString("b"), "gte", false},
{types.MakeString("a"), types.MakeString("a"), "gte", true},
{types.MakeString("b"), types.MakeString("a"), "gte", true},
}

for _, test := range tests {
vm := NewVM()

vm.stack.Push(test.right)
vm.stack.Push(test.left)

test_instruction := fmt.Sprintf("COMPARE %s", test.comparison)

if instruction, err := ParseInstruction(test_instruction); err != nil {
t.Fatalf("Error parsing instruction %q: %v", test_instruction, err)
} else if err := instruction.Perform(&vm); err != nil {
t.Fatalf("Unexpected error executing %q: %v", test_instruction, err)
} else if stack_size := vm.stack.Size(); stack_size != 1 {
t.Fatalf("Unexpected stack to have 1 element after %q, had: %d", test_instruction, stack_size)
} else if top, err := vm.stack.Pop(); err != nil {
t.Fatalf("Unexpected error popping stack after %q: %v", test_instruction, err)
} else if top_bool, err := top.RequireBool(); err != nil {
t.Fatalf("Unexpected error interpretting result of %q as a bool: %v", test_instruction, err)
} else if top_bool.Truthy() != test.expected {
t.Fatalf("Expected %q to push %t, got %t", test_instruction, test.expected, !test.expected)
}
}
}

func TestCompareArgFailures(t *testing.T) {
vm := NewVM()

if instruction, err := ParseInstruction("COMPARE equal"); err != nil {
t.Fatalf("Error parsing instruction %q: %v", instruction, err)
} else if err := instruction.Perform(&vm); err == nil {
t.Fatalf("Expected error executing 'COMPARE equal', got success")
}

vm.stack.Push(types.MakeString("foo"))

if instruction, err := ParseInstruction("COMPARE equal"); err != nil {
t.Fatalf("Error parsing instruction %q: %v", instruction, err)
} else if err := instruction.Perform(&vm); err == nil {
t.Fatalf("Expected error executing 'COMPARE equal', got success")
}
}
31 changes: 0 additions & 31 deletions vm/instruction_is_equal.go

This file was deleted.

54 changes: 0 additions & 54 deletions vm/instruction_is_equal_test.go

This file was deleted.

12 changes: 6 additions & 6 deletions vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ func ParseInstruction(yolk_line string) (Instruction, error) {
return nil, err
}
return &instruction, nil
case "DECLARE_NAME":
var instruction Instruction_DECLARE_NAME
case "COMPARE":
var instruction Instruction_COMPARE
if err := instruction.Parse(&args); err != nil {
return nil, err
}
return &instruction, nil
case "EXEC":
var instruction Instruction_EXEC
case "DECLARE_NAME":
var instruction Instruction_DECLARE_NAME
if err := instruction.Parse(&args); err != nil {
return nil, err
}
return &instruction, nil
case "IS_EQUAL":
var instruction Instruction_IS_EQUAL
case "EXEC":
var instruction Instruction_EXEC
if err := instruction.Parse(&args); err != nil {
return nil, err
}
Expand Down

0 comments on commit d100116

Please sign in to comment.