-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
vm: Implement COMPARE instruction and remove IS_EQUAL
- Loading branch information
Showing
5 changed files
with
211 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters