jak-project/test/test_goos.cpp
water111 1a41c5f8dd
try using a hash table for art groups (#1370)
* try using a hash table for art groups

* comments and test
2022-05-21 19:36:14 -04:00

1331 lines
33 KiB
C++

/*!
* @file test_goos.cpp
* Tests for the GOOS macro language.
*/
#include "gtest/gtest.h"
#include "common/goos/Interpreter.h"
using namespace goos;
namespace {
// helper to evaluate a string as a goos expression.
std::string e(Interpreter& interp, const std::string& in) {
return interp.eval(interp.reader.read_from_string(in), interp.global_environment.as_env_ptr())
.print();
}
} // namespace
TEST(GoosBuiltins, Begin) {
Interpreter i;
// begin returns the last thing in the list
EXPECT_EQ(e(i, "(begin 1 2)"), "2");
// begin with nothing returns the empty list
EXPECT_EQ(e(i, "(begin)"), "()");
e(i, "(begin (define x 123) 3)");
EXPECT_EQ(e(i, "x"), "123");
i.disable_printfs();
for (auto x : {"(begin :test 1)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Read) {
Interpreter i;
// test that we can read a string into the reader at runtime.
EXPECT_EQ(e(i, "(read \"1\")"), "(top-level 1)");
i.disable_printfs();
// check bad expressions.
for (auto x :
{"(read 1)", "(read)", R"((read "a" "b"))", "(read :a b \"a\")", "(read \"(((((\")"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, ReadFile) {
Interpreter i;
// check that we can read a file.
EXPECT_EQ(e(i, "(read-file \"test/test_data/test_reader_file0.gc\")"), "(top-level (1 2 3 4))");
i.disable_printfs();
for (auto x : {"(read-file 1)", "(read-file)", "(read-file \"goal/test/not_a_real_file.gc\")"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, LoadFile) {
Interpreter i;
// check that we can read and execute a file.
e(i, "(load-file \"test/test_data/test_goos_file0.gs\")");
EXPECT_EQ(e(i, "x"), "23");
i.disable_printfs();
for (auto x : {"(load-file 1)", "(load-file)", "(load-file \"goal/test/not_a_real_file.gc\")"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, PrintAndInspect) {
Interpreter i;
i.disable_printfs();
// check that we can print/inspect (doesn't check result, just sees that it doesn't crash)
EXPECT_EQ(e(i, "(print 10)"), "()");
EXPECT_EQ(e(i, "(inspect 10)"), "()");
for (auto x : {"(print 1 2)", "(print)", "(print 'a :a b)", "(print :a b)", "(inspect 1 2)",
"(inspect)", "(inspect 'a :a b)", "(inspect :a b)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
namespace {
// checks if two things are equal by running them through the GOOS eq? form.
bool eq(Interpreter& i, const std::string& a, const std::string& b) {
return e(i, "(eq? " + a + " " + " " + b + ")") == "#t";
}
} // namespace
TEST(GoosBuiltins, Equality) {
Interpreter i;
// STRING
EXPECT_TRUE(eq(i, "\"asdf\"", "\"asdf\""));
EXPECT_FALSE(eq(i, "\"asdgf\"", "\"asdf\""));
// INTEGER
EXPECT_TRUE(eq(i, "123", "0123"));
EXPECT_FALSE(eq(i, "123", "10123"));
// FLOAT
EXPECT_TRUE(eq(i, "1.23", "01.23"));
EXPECT_FALSE(eq(i, "1.23", "10.123"));
// CHAR
EXPECT_TRUE(eq(i, "#\\a", "#\\a"));
EXPECT_FALSE(eq(i, "#\\b", "#\\a"));
// SYMBOL
EXPECT_TRUE(eq(i, "'a", "'a"));
EXPECT_FALSE(eq(i, "'a", "'b"));
// ENV
EXPECT_TRUE(eq(i, "*global-env*", "*global-env*"));
EXPECT_FALSE(eq(i, "*global-env*", "*goal-env*"));
// LAMBDA
e(i, "(desfun test-fun-1 () 2)");
e(i, "(desfun test-fun-2 () 3)");
e(i, "(define test-fun-3 test-fun-1)");
EXPECT_TRUE(eq(i, "test-fun-1", "test-fun-3"));
EXPECT_FALSE(eq(i, "test-fun-1", "test-fun-2"));
// MACRO
e(i, "(defsmacro test-mac-1 () 2)");
e(i, "(defsmacro test-mac-2 () 3)");
e(i, "(define test-mac-3 test-mac-1)");
EXPECT_TRUE(eq(i, "test-mac-1", "test-mac-3"));
EXPECT_FALSE(eq(i, "test-mac-1", "test-mac-2"));
// EMPTY LIST
EXPECT_TRUE(eq(i, "'()", "'()"));
// PAIR
EXPECT_TRUE(eq(i, "'(1 2 3)", "'(1 2 3)"));
EXPECT_FALSE(eq(i, "'(1 2 3)", "'(1 2 4)"));
// ARRAY
EXPECT_TRUE(eq(i, "'#(1 2 3)", "'#(1 2 3)"));
EXPECT_FALSE(eq(i, "'#(1 2 3)", "'#(1 2 4)"));
EXPECT_FALSE(eq(i, "'#(1 2 3)", "'#(1 2 3 4)"));
EXPECT_FALSE(eq(i, "'#(1 2 3 4 5)", "'#(1 2 3 4)"));
i.disable_printfs();
for (auto x : {"(eq?)", "(eq? 1)", "(eq? 1 2 3)", "(eq? 1 2 :keyword 2)", "(eq? 1 :keyword 2)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Addition) {
Interpreter i;
// single element adding
EXPECT_EQ(e(i, "(+ 1)"), "1");
EXPECT_EQ(e(i, "(+ 1.)"), "1.0");
// two element adding
EXPECT_EQ(e(i, "(+ 1 2)"), "3");
EXPECT_EQ(e(i, "(+ 1.1 2.2)"), "3.3");
// mixed
EXPECT_EQ(e(i, "(+ 1 1.1)"), "2");
EXPECT_EQ(e(i, "(+ 1.1 1)"), "2.1");
// many, and check rounding happens at the right time
EXPECT_EQ(e(i, "(+ 1 1.4 1.4 1.4 1.4)"), "5");
i.disable_printfs();
for (auto x : {"(+)", "(+ 'a)", "(+ #\\a)", "(+ 1 :test 2)", "(+ 1 2 :test 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Multiplication) {
Interpreter i;
// single element adding
EXPECT_EQ(e(i, "(* 2)"), "2");
EXPECT_EQ(e(i, "(* 2.)"), "2.0");
// two element adding
EXPECT_EQ(e(i, "(* 3 2)"), "6");
EXPECT_EQ(e(i, "(* 1.1 2.2)"), "2.42");
// mixed
EXPECT_EQ(e(i, "(* 1 1.1)"), "1");
EXPECT_EQ(e(i, "(* 1.1 1)"), "1.1");
// many, and check rounding happens at the right time
EXPECT_EQ(e(i, "(* 3 1.4 1.4 1.4 1.4)"), "3");
i.disable_printfs();
for (auto x : {"(*)", "(* 'a)", "(* #\\a)", "(* 1 :test 2)", "(* 1 2 :test 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Subtraction) {
Interpreter i;
// single element adding
EXPECT_EQ(e(i, "(- 2)"), "-2");
EXPECT_EQ(e(i, "(- 2.)"), "-2.0");
// two element adding
EXPECT_EQ(e(i, "(- 3 2)"), "1");
EXPECT_EQ(e(i, "(- 1.1 2.2)"), "-1.1");
// mixed
EXPECT_EQ(e(i, "(- 1 1.1)"), "0");
EXPECT_EQ(e(i, "(- 1.1 1)"), "0.1");
// many, and check rounding happens at the right time
EXPECT_EQ(e(i, "(- 3 1.4 1.4 1.4 1.4)"), "-1");
i.disable_printfs();
for (auto x : {"(-)", "(- 'a)", "(- #\\a)", "(- 1 :test 2)", "(- 1 2 :test 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Division) {
Interpreter i;
// two element adding
EXPECT_EQ(e(i, "(/ 16 2)"), "8");
EXPECT_EQ(e(i, "(/ 9. 2.)"), "4.5");
// mixed
EXPECT_EQ(e(i, "(/ 3 2.)"), "1");
EXPECT_EQ(e(i, "(/ 3. 2)"), "1.5");
i.disable_printfs();
for (auto x : {"(/ 1)", "(/ 1.0)", "(/)", "(/ 'a)", "(/ #\\a)", "(/ 1 :test 2)",
"(/ 1 2 :test 3)", "(/ 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, NumberEquals) {
Interpreter i;
EXPECT_EQ(e(i, "(= 16 2)"), "#f");
EXPECT_EQ(e(i, "(= 2 2)"), "#t");
EXPECT_EQ(e(i, "(= 16.3 2.4)"), "#f");
EXPECT_EQ(e(i, "(= 2.2 2.2)"), "#t");
EXPECT_EQ(e(i, "(= 2.0 2)"), "#t");
EXPECT_EQ(e(i, "(= 2 2.2)"), "#t");
EXPECT_EQ(e(i, "(= 2.2 2)"), "#f");
EXPECT_EQ(e(i, "(= 2 3.2)"), "#f");
EXPECT_EQ(e(i, "(= 2 2 2 2 2)"), "#t");
EXPECT_EQ(e(i, "(= 2 2 2 2.2 2.3)"), "#t");
EXPECT_EQ(e(i, "(= 2 2 2 2.2 3.0)"), "#f");
i.disable_printfs();
for (auto x : {"(= 1)", "(=)", "(= a 1)", "(= 'a 1)", "(= 1 2 :test 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, NumberLessThan) {
Interpreter i;
EXPECT_EQ(e(i, "(< 16 2)"), "#f");
EXPECT_EQ(e(i, "(< 2 2)"), "#f");
EXPECT_EQ(e(i, "(< 1 2)"), "#t");
EXPECT_EQ(e(i, "(< 16.8 16.2)"), "#f");
EXPECT_EQ(e(i, "(< 2.2 2.2)"), "#f");
EXPECT_EQ(e(i, "(< 1.9 2.1)"), "#t");
EXPECT_EQ(e(i, "(< 1.1 2)"), "#t");
EXPECT_EQ(e(i, "(< 1 2.1)"), "#t");
EXPECT_EQ(e(i, "(< 2.2 2)"), "#f");
EXPECT_EQ(e(i, "(< 2 2.2)"), "#f");
i.disable_printfs();
for (auto x : {"(< 1)", "(<)", "(< a 1)", "(< 'a 1)", "(< 1 2 :test 3)", "(< 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, NumberLessThanEqual) {
Interpreter i;
EXPECT_EQ(e(i, "(<= 16 2)"), "#f");
EXPECT_EQ(e(i, "(<= 2 2)"), "#t");
EXPECT_EQ(e(i, "(<= 1 2)"), "#t");
EXPECT_EQ(e(i, "(<= 16.8 16.2)"), "#f");
EXPECT_EQ(e(i, "(<= 2.2 2.2)"), "#t");
EXPECT_EQ(e(i, "(<= 1.9 2.1)"), "#t");
EXPECT_EQ(e(i, "(<= 1.1 2)"), "#t");
EXPECT_EQ(e(i, "(<= 1 2.1)"), "#t");
EXPECT_EQ(e(i, "(<= 2.2 2)"), "#f");
EXPECT_EQ(e(i, "(<= 2 2.2)"), "#t");
i.disable_printfs();
for (auto x : {"(<= 1)", "(<=)", "(<= a 1)", "(<= 'a 1)", "(<= 1 2 :test 3)", "(<= 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, NumberGreaterThan) {
Interpreter i;
EXPECT_EQ(e(i, "(> 16 2)"), "#t");
EXPECT_EQ(e(i, "(> 2 2)"), "#f");
EXPECT_EQ(e(i, "(> 1 2)"), "#f");
EXPECT_EQ(e(i, "(> 16.8 16.2)"), "#t");
EXPECT_EQ(e(i, "(> 2.2 2.2)"), "#f");
EXPECT_EQ(e(i, "(> 1.9 2.1)"), "#f");
EXPECT_EQ(e(i, "(> 1.1 2)"), "#f");
EXPECT_EQ(e(i, "(> 1 2.1)"), "#f");
EXPECT_EQ(e(i, "(> 2.2 2)"), "#t");
EXPECT_EQ(e(i, "(> 2 2.2)"), "#f");
i.disable_printfs();
for (auto x : {"(> 1)", "(>)", "(> a 1)", "(> 'a 1)", "(> 1 2 :test 3)", "(> 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, NumberGreaterThanEqual) {
Interpreter i;
EXPECT_EQ(e(i, "(>= 16 2)"), "#t");
EXPECT_EQ(e(i, "(>= 2 2)"), "#t");
EXPECT_EQ(e(i, "(>= 1 2)"), "#f");
EXPECT_EQ(e(i, "(>= 16.8 16.2)"), "#t");
EXPECT_EQ(e(i, "(>= 2.2 2.2)"), "#t");
EXPECT_EQ(e(i, "(>= 1.9 2.1)"), "#f");
EXPECT_EQ(e(i, "(>= 1.1 2)"), "#f");
EXPECT_EQ(e(i, "(>= 1 2.1)"), "#f");
EXPECT_EQ(e(i, "(>= 2.2 2)"), "#t");
EXPECT_EQ(e(i, "(>= 2 2.2)"), "#t");
i.disable_printfs();
for (auto x : {"(>= 1)", "(>=)", "(>= a 1)", "(>= 'a 1)", "(>= 1 2 :test 3)", "(>= 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Eval) {
Interpreter i;
EXPECT_EQ(e(i, "(eval '(+ 1 2))"), "3");
i.disable_printfs();
for (auto x : {"(eval bad-to-evaluate-me)", "(eval)", "(eval 1 2)", "(eval :test 3)",
"(eval 1 :test 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, CarCdr) {
Interpreter i;
EXPECT_EQ(e(i, "(car '(3 . 4))"), "3");
EXPECT_EQ(e(i, "(cdr '(3 . 4))"), "4");
i.disable_printfs();
for (auto x : {"(car)", "(car '(1 . 2) '(3 . 4))", "(cdr)", "(cdr '(1 . 2) '(3 . 4))"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, SetCarCdr) {
Interpreter i;
e(i, "(define x (cons 1 2))");
EXPECT_EQ(e(i, "(set-car! x (+ 1 3))"), "(4 . 2)");
EXPECT_EQ(e(i, "x"), "(4 . 2)");
EXPECT_EQ(e(i, "(set-cdr! x (+ 2 3))"), "(4 . 5)");
EXPECT_EQ(e(i, "x"), "(4 . 5)");
i.disable_printfs();
for (auto x : {"(set-car!)", "(set-car! '(1 . 2))", "(set-cdr!)", "(set-cdr! '(1 . 2))"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, GenSym) {
Interpreter i;
EXPECT_EQ(e(i, "(eq? (gensym) (gensym))"), "#f");
i.disable_printfs();
for (auto x : {"(gensym 1)", "(gensym :a 2)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Cons) {
Interpreter i;
EXPECT_EQ(e(i, "(cons 'a 2)"), "(a . 2)");
EXPECT_EQ(e(i, "(cons 'a (cons 2 (cons 3 '())))"), "(a 2 3)");
i.disable_printfs();
for (auto x : {"(cons 1)", "(cons)", "(const 1 2 3)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Null) {
Interpreter i;
EXPECT_EQ(e(i, "(null? 1)"), "#f");
EXPECT_EQ(e(i, "(null? '())"), "#t");
i.disable_printfs();
for (auto x : {"(null? 1 2)", "(null?)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Type) {
Interpreter i;
EXPECT_EQ(e(i, "(type? 'empty-list '())"), "#t");
EXPECT_EQ(e(i, "(type? 'empty-list 1)"), "#f");
EXPECT_EQ(e(i, "(type? 'integer 2)"), "#t");
EXPECT_EQ(e(i, "(type? 'integer '())"), "#f");
EXPECT_EQ(e(i, "(type? 'float 2.)"), "#t");
EXPECT_EQ(e(i, "(type? 'float '())"), "#f");
EXPECT_EQ(e(i, "(type? 'char #\\c)"), "#t");
EXPECT_EQ(e(i, "(type? 'char '())"), "#f");
EXPECT_EQ(e(i, "(type? 'symbol 'a)"), "#t");
EXPECT_EQ(e(i, "(type? 'symbol '())"), "#f");
EXPECT_EQ(e(i, "(type? 'string \"test\")"), "#t");
EXPECT_EQ(e(i, "(type? 'string '())"), "#f");
EXPECT_EQ(e(i, "(type? 'pair '(1 . 2))"), "#t");
EXPECT_EQ(e(i, "(type? 'pair '())"), "#f");
EXPECT_EQ(e(i, "(type? 'array '#(1 2))"), "#t");
EXPECT_EQ(e(i, "(type? 'array '())"), "#f");
EXPECT_EQ(e(i, "(type? 'lambda (lambda () 1))"), "#t");
EXPECT_EQ(e(i, "(type? 'lambda '())"), "#f");
EXPECT_EQ(e(i, "(type? 'macro (macro () 1))"), "#t");
EXPECT_EQ(e(i, "(type? 'macro '())"), "#f");
EXPECT_EQ(e(i, "(type? 'environment *global-env*)"), "#t");
EXPECT_EQ(e(i, "(type? 'environment '())"), "#f");
}
/*!
* Confirm that the global and goos env variables appear
*/
TEST(GoosEval, GlobalAndGoalEnv) {
Interpreter i;
Object goal_env, goos_env;
EXPECT_TRUE(i.get_global_variable_by_name("*global-env*", &goos_env));
EXPECT_TRUE(i.get_global_variable_by_name("*goal-env*", &goal_env));
auto& goal_vars = goal_env.as_env()->vars;
auto& goos_vars = goos_env.as_env()->vars;
EXPECT_TRUE(goal_vars.find(i.intern("*global-env*").as_symbol()) != goal_vars.end());
EXPECT_TRUE(goal_vars.find(i.intern("*goal-env*").as_symbol()) != goal_vars.end());
EXPECT_TRUE(goos_vars.find(i.intern("*global-env*").as_symbol()) != goos_vars.end());
EXPECT_TRUE(goos_vars.find(i.intern("*goal-env*").as_symbol()) != goos_vars.end());
EXPECT_TRUE(goos_vars.find(i.intern("*goal-env*").as_symbol())->second ==
goal_vars.find(i.intern("*goal-env*").as_symbol())->second);
EXPECT_TRUE(goos_vars.find(i.intern("*global-env*").as_symbol())->second ==
goal_vars.find(i.intern("*global-env*").as_symbol())->second);
EXPECT_TRUE(goos_vars.find(i.intern("*global-env*").as_symbol())->second !=
goos_vars.find(i.intern("*goal-env*").as_symbol())->second);
}
/*!
* Confirm that the GOOS Library is loaded automatically on interpreter start.
*/
TEST(GoosEval, GoosLibLoaded) {
Interpreter i;
Object loaded;
EXPECT_TRUE(i.get_global_variable_by_name("__goos-lib-loaded__", &loaded));
EXPECT_EQ(loaded.print(), "#t");
}
/*!
* Check that integers are evaluated.
*/
TEST(GoosEval, EvalSelfEvaluating) {
Interpreter i;
EXPECT_EQ(e(i, "010"), "10");
EXPECT_EQ(e(i, "-010"), "-10");
EXPECT_EQ(e(i, "\"test\""), "\"test\"");
EXPECT_EQ(e(i, "1.2"), "1.2"); // this depends on how we decide to print floats
EXPECT_EQ(e(i, "#\\a"), "#\\a");
EXPECT_EQ(e(i, "#\\\\n"), "#\\\\n");
i.disable_printfs();
EXPECT_ANY_THROW(e(i, "#\\\\a"));
}
TEST(GoosEval, FloatEvalAndPrinting) {
Interpreter i;
EXPECT_EQ(e(i, "0.9999979734420776"), "0.999998");
EXPECT_EQ(e(i, "0.999998"), e(i, "0.9999979734420776"));
EXPECT_EQ(e(i, "1."), "1.0");
EXPECT_EQ(e(i, ".03"), "0.03");
EXPECT_EQ(e(i, "0.02999999932944774627685546875"), e(i, "0.03"));
EXPECT_EQ(e(i, "0.5883"), e(i, "0.5882999897"));
}
/*!
* Check named "keyword" arguments
*/
TEST(GoosEval, KeywordArgs) {
Interpreter i;
e(i, "(desfun test (a b &key c &key d) c)");
EXPECT_EQ(e(i, "(test 1 2 :c 3 :d 4)"), "3");
EXPECT_EQ(e(i, "(test 1 2 :d 3 :c 4)"), "4");
e(i, "(desfun test (a b &key c &key d) (+ b d))");
EXPECT_EQ(e(i, "(test 1 2 :c 3 :d 4)"), "6");
EXPECT_EQ(e(i, "(test 1 2 :d 3 :c 4)"), "5");
EXPECT_EQ(e(i, "(test 1 :d 3 2 :c 4)"), "5");
EXPECT_EQ(e(i, "(test :d 3 1 2 :c 4)"), "5");
i.disable_printfs();
for (const auto& x : {"(test 1 2 :c 3 :d)", "(test 1 2 :c 3)", "(test 1 2 :c 3 :d 4 :e 3)",
"(test 1 2 :c 3 :d 4 :c 4)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosEval, KeywordArgsWithDefault) {
Interpreter i;
e(i, "(desfun test (a b &key (c 3) &key d) c)");
EXPECT_EQ(e(i, "(test 1 2 :c 7 :d 4)"), "7");
EXPECT_EQ(e(i, "(test 1 2 :d 4)"), "3");
e(i, "(desfun test (a b &key (c 3) &key d) c)");
i.disable_printfs();
for (const auto& x :
{"(test 1 2 :c 3 :d)", "(test 1 2 :c :d 3)", "(test 1 2 :d 3 :c )", "(test 1 2 :c 3)",
"(test 1 2 :c 3 :d 4 :e 3)", "(test 1 2 :c 3 :d 4 :c 4)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosIntegrated, Begin) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define *test-value* (begin
(define x 10)
#|
asdfasdfasdfa
|#
(define y 20)
(set! x y)
x
; y
)
)
)"),
"20");
}
TEST(GoosIntegrated, Lambda1) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define x 10)
(define f (lambda (x) ((lambda () x))))
(define *test-actual* (f 20))
)"),
"20");
}
TEST(GoosIntegrated, Lambda2) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define make-returner (lambda (thing-to-return)
(lambda () thing-to-return)))
(define my-func (make-returner "beans"))
(define fake-func (make-returner "cheese"))
(fake-func)
(define *test-actual* (fake-func))
(set! *test-actual* (my-func))
)"),
"\"beans\"");
}
TEST(GoosIntegrated, Cond) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define s1 (cond
(#f a)
((+ 2 3) (+ 3 4))
(#t 3333)
(#t this-cannot-be-evaluated)
)
)
(define s2 (cond (2)))
(define s3 (cond (#f a)
(#t (define s4 10)
(+ 1 1)
)
)
)
(define *test-actual* (+ s1 s2 s3 s4))
)"),
"21");
}
TEST(GoosIntegrated, CountingChange) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define count-change
(lambda (amount)
(cc amount 5)
)
)
(define cc
(lambda (amount kinds-of-coins)
(cond
;; base case
((= amount 0) 1)
;; impossible
((or (< amount 0) (= kinds-of-coins 0)) 0)
;; otherwise
(#t (+ (cc amount (- kinds-of-coins 1))
(cc (- amount (first-denom kinds-of-coins)) kinds-of-coins)
)
)
)
)
)
(define first-denom
(lambda (kinds-of-coins)
(cond
((= kinds-of-coins 1) 1)
((= kinds-of-coins 2) 5)
((= kinds-of-coins 3) 10)
((= kinds-of-coins 4) 25)
((= kinds-of-coins 5) 50)
)
)
)
(define *test-actual* (count-change 100))
)"),
"292");
}
TEST(GoosIntegrated, Eval) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define *test-actual*
(eval '(+ 1 2)
)
)
)"),
"3");
}
TEST(GoosIntegrated, Read) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define *test-actual*
(eval (read "(+ 1 2) ")
)
)
)"),
"3");
}
// todo - test read?
TEST(GoosIntegrated, LambdaWithRest) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define func (lambda (x &rest y) y))
(define x (func -123 2 3 1))
(define *test-actual* (+ (car x) (car (cdr x)) (car (cdr (cdr x)))))
)"),
"6");
}
TEST(GoosIntegrated, IntegerEquality1) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define x 2)
(set! x 1)
(define *test-actual* (eq? x 2))
)"),
"#f");
}
TEST(GoosIntegrated, IntegerEquality2) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define x 2)
(define *test-actual* (eq? x 2))
)"),
"#t");
}
TEST(GoosIntegrated, Lambda3) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define my-func (lambda (a b c) b))
(define *test-actual* (my-func 1 2 3))
)"),
"2");
}
TEST(GoosIntegrated, Lambda4) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define func1 (lambda () 1))
(define func2 (lambda () (func1)))
(set! func1 (lambda () 2))
(define *test-actual* (func2))
)"),
"2");
}
TEST(GoosIntegrated, Lambda5) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define x 10)
(define my-func (lambda (x) x))
(define s1 (my-func 20))
(define s2 x)
(define s3 ((lambda (x) x) 30))
(define my-func-2 (lambda (x y) (set! y x) y))
(define s4 (my-func-2 11 12))
(define f3 (lambda (x) (set! x (+ 1 x)) x))
(define s5 (f3 14))
(define x ((lambda (x) (set! x (+ x 1)) x) 16))
(define s6 x)
(define f1 (lambda (x) (+ x 1)))
(define f2 (lambda (x) (+ 2 (f1 x))))
(define s7 (f2 (+ 1 2)))
(define *test-actual* (+ s1 s2 s3 s4 s5 s6 s7))
)"),
"109");
}
TEST(GoosIntegrated, Let1) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define s1
(let ((x (+ 1 2))
(y (+ 3 4)))
(+ x y)
)
)
(define var-1 2)
(define s2
(let ((x (lambda () var-1)))
(set! var-1 1000)
(x)
)
)
(define *test-actual* (+ s1 s2))
)"),
"1010");
}
TEST(GoosIntegrated, Let2) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define s1
(let* ((x (+ 1 2))
(y (+ x 5)))
(+ x y)
)
)
(define var-1 2)
(define s2
(let ((x (lambda () var-1)))
(set! var-1 1000)
(x)
)
)
(define x 0)
(define s3
(let ((x (+ 1 2))
(y x))
y
)
)
(define x 0)
(define s4
(let* ((x (+ 1 2))
(y x))
y
)
)
(define *test-actual* (+ s1 s2 s3 s4))
)"),
"1014");
}
TEST(GoosIntegrated, WeirdSymbols) {
Interpreter i;
EXPECT_EQ(e(i, R"(
'+
'-
'...
'!..
'$.+
'%.-
'&.!
'*.:
'/:.
;':+. ; forget this one, it gets recognized as a keyword argument to (quote).
'<-.
'=.
'>.
'?.
'~.
'_.
'^.
1
)"),
"1");
}
TEST(GoosIntegrated, WhileLoop) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(define count 0)
(define sum 0)
(while (< count 100)
(set! sum (+ sum count))
(set! count (+ count 1))
)
(define *test-actual* sum)
)"),
"4950");
}
TEST(GoosLib, Desfun) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(desfun test-function (a b c &rest d)
(set! a (+ a b c))
(cons a d)
)
(test-function 1 2 3 4 5 6)
)"),
"(6 4 5 6)");
}
TEST(GoosLib, Factorial) {
Interpreter i;
// now large numbers are printed as hex.
EXPECT_EQ(e(i, "(factorial 10)"), "#x375f00");
}
TEST(GoosLib, ApplySimple) {
Interpreter i;
EXPECT_EQ(e(i, "(apply (lambda (x) (* x 2)) '(1 2 3))"), "(2 4 6)");
}
TEST(GoosLib, ApplyComplex1) {
Interpreter i;
e(i, "(define y 2)");
e(i, "(define test-func (lambda (x) (* x y)))");
EXPECT_EQ(e(i, "(apply test-func '(1 2 3))"), "(2 4 6)");
}
TEST(GoosLib, ApplyComplex2) {
Interpreter i;
e(i, "(define y 2)");
e(i, "(define test-func (lambda (x) (* x y)))");
e(i, "(set! y 4)");
EXPECT_EQ(e(i, "(apply test-func '(1 2 3))"), "(4 8 12)");
}
TEST(GoosLib, ApplyComplex3) {
Interpreter i;
e(i, "(desfun make-mult-by-x-fun (x) (lambda (y) (* y x)))");
e(i, "(define mult3 (make-mult-by-x-fun 3))");
e(i, "(define mult2 (make-mult-by-x-fun 2))");
EXPECT_EQ(e(i, "(apply mult2 '(1 2 3))"), "(2 4 6)");
EXPECT_EQ(e(i, "(apply mult3 '(1 2 3))"), "(3 6 9)");
EXPECT_EQ(e(i, "(apply mult2 '(1 2 3))"), "(2 4 6)");
}
TEST(GoosLib, Let) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(let ((x 1)
(y 2)
(z 3))
(+ x y z))
)"),
"6");
}
TEST(GoosLib, LetRec) {
Interpreter i;
EXPECT_EQ(e(i, R"(
(let* ((x 1) ; 1
(y (+ 2 x)) ; 3
(z (+ 3 y))) ; 6
(+ x y z))
)"),
"10");
}
/*!
* Test special case character printing.
*/
TEST(GoosObject, char_to_string) {
// printable
EXPECT_EQ("#\\d", goos::fixed_to_string('d'));
// special ones
EXPECT_EQ("#\\\\s", goos::fixed_to_string(' '));
EXPECT_EQ("#\\\\n", goos::fixed_to_string('\n'));
EXPECT_EQ("#\\\\t", goos::fixed_to_string('\t'));
// decimal for ones we don't have special cased
EXPECT_EQ("#\\{17}", goos::fixed_to_string(char(17)));
}
/*!
* Test the EmptyListObject
*/
TEST(GoosObject, EmptyList) {
// create two empty lists
Object nil = Object::make_empty_list();
Object nil2 = Object::make_empty_list();
// check type is set
EXPECT_TRUE(nil.is_empty_list());
// check equality operator
EXPECT_TRUE(nil == nil2);
// check print and inspect
EXPECT_EQ(nil.print(), "()");
EXPECT_EQ(nil.inspect(), "[empty list] ()\n");
}
/*!
* Test IntegerObject
*/
TEST(GoosObject, Integer) {
// create integer objects
Object io = Object::make_integer(-1234);
Object different = Object::make_integer(3);
Object same = Object::make_integer(-1234);
// check type
EXPECT_TRUE(io.is_int());
// check equality
EXPECT_TRUE(same == io);
EXPECT_FALSE(different == io);
// check changing the value through the as_int reference
different.as_int() = -1234;
EXPECT_TRUE(different.as_int() == -1234);
EXPECT_TRUE(different == same);
// check print and inspect
EXPECT_EQ(different.print(), "-1234");
EXPECT_EQ(different.inspect(), "[integer] -1234\n");
}
/*!
* Test FloatObject
*/
TEST(GoosObject, Float) {
// create integer objects
Object fo = Object::make_float(-12.34);
Object different = Object::make_float(3);
Object same = Object::make_float(-12.34);
// check type
EXPECT_FALSE(fo.is_int());
EXPECT_TRUE(fo.is_float());
// check equality
EXPECT_TRUE(same == fo);
EXPECT_FALSE(different == fo);
// check changing the value through the as_float reference
different.as_float() = -12.34;
EXPECT_TRUE(different.as_float() == -12.34);
EXPECT_TRUE(different == same);
// check print and inspect
EXPECT_EQ(different.print(), "-12.34");
EXPECT_EQ(different.inspect(), "[float] -12.34\n");
}
/*!
* Test CharObject
*/
TEST(GoosObject, Char) {
// create integer objects
Object co = Object::make_char('w');
Object different = Object::make_char('X');
Object same = Object::make_char('w');
// check type
EXPECT_FALSE(co.is_int());
EXPECT_TRUE(co.is_char());
// check equality
EXPECT_TRUE(same == co);
EXPECT_FALSE(different == co);
// check changing the value through the as_char reference
different.as_char() = 'w';
EXPECT_TRUE(different.as_char() == 'w');
EXPECT_TRUE(different == same);
// check print and inspect
EXPECT_EQ(different.print(), "#\\w");
EXPECT_EQ(different.inspect(), "[char] #\\w\n");
}
/*!
* Test SymbolObject
*/
TEST(GoosObject, Symbol) {
SymbolTable st, st2;
Object obj = SymbolObject::make_new(st, "test1");
Object obj2 = SymbolObject::make_new(st, "test2");
Object obj3 = SymbolObject::make_new(st, "test1");
Object obj4 = SymbolObject::make_new(st2, "test1");
// check type
EXPECT_TRUE(obj.is_symbol());
// check equality
EXPECT_TRUE(obj == obj3);
EXPECT_FALSE(obj == obj2);
EXPECT_FALSE(obj == obj4); // different because different st's
// check interning works
auto obj_as = obj.as_symbol();
auto obj2_as = obj2.as_symbol();
auto obj3_as = obj3.as_symbol();
auto obj4_as = obj4.as_symbol();
EXPECT_TRUE(obj_as == obj3_as);
EXPECT_FALSE(obj_as == obj2_as);
EXPECT_FALSE(obj_as == obj4_as); // different because different st's.
// check print and inspect
EXPECT_EQ(obj.print(), "test1");
EXPECT_EQ(obj.inspect(), "[symbol] test1\n");
}
/*!
* Test StringObject
*/
TEST(GoosObject, String) {
Object obj1 = StringObject::make_new("test1");
Object obj2 = StringObject::make_new("test2");
Object obj3 = StringObject::make_new("test1");
Object obj4 = StringObject::make_new("test-with\"quote");
EXPECT_TRUE(obj1.is_string());
EXPECT_TRUE(obj1 == obj3);
EXPECT_FALSE(obj1 == obj2);
obj2.as_string()->data = "test1";
EXPECT_TRUE(obj1 == obj2);
EXPECT_EQ(obj1.print(), "\"test1\"");
EXPECT_EQ(obj1.inspect(), "[string] \"test1\"\n");
EXPECT_EQ(obj4.print(), "\"test-with\\\"quote\"");
}
/*!
* Test PairObject
*/
TEST(GoosObject, Pair) {
Object obj = PairObject::make_new(Object::make_integer(1), Object::make_integer(2));
Object obj2 = PairObject::make_new(Object::make_integer(2), Object::make_integer(2));
Object obj3 = PairObject::make_new(Object::make_integer(1), Object::make_integer(2));
EXPECT_TRUE(obj.is_pair());
EXPECT_TRUE(obj == obj3);
EXPECT_FALSE(obj == obj2);
obj2.as_pair()->car = Object::make_integer(1);
EXPECT_TRUE(obj == obj2);
EXPECT_EQ(obj.print(), "(1 . 2)");
EXPECT_EQ(obj.inspect(), "[pair] (1 . 2)\n");
}
/*!
* Test proper list stuff
*/
TEST(GoosObject, PairList) {
Object obj = build_list({});
EXPECT_TRUE(obj.is_empty_list());
obj = build_list({Object::make_integer(1)});
EXPECT_EQ(obj.print(), "(1)");
obj = build_list({Object::make_integer(1), Object::make_integer(2), Object::make_integer(3)});
auto obj2 =
build_list({Object::make_integer(1), Object::make_integer(2), Object::make_integer(2)});
auto obj3 =
build_list({Object::make_integer(1), Object::make_integer(2), Object::make_integer(3)});
EXPECT_EQ(obj.print(), "(1 2 3)");
EXPECT_TRUE(obj == obj3);
EXPECT_FALSE(obj == obj2);
}
/*!
* Test ArrayObject
*/
TEST(GoosObject, Array) {
Object empty_array = ArrayObject::make_new({});
EXPECT_EQ(empty_array.as_array()->size(), 0);
EXPECT_TRUE(empty_array.is_array());
EXPECT_EQ(empty_array.print(), "#()");
auto array1 = ArrayObject::make_new(
{Object::make_integer(1), Object::make_integer(2), Object::make_integer(3)});
auto array2 = ArrayObject::make_new(
{Object::make_integer(1), Object::make_integer(2), Object::make_integer(4)});
auto array3 = ArrayObject::make_new(
{Object::make_integer(1), Object::make_integer(2), Object::make_integer(3)});
EXPECT_TRUE(array1 == array3);
EXPECT_TRUE(array1 != array2);
EXPECT_EQ(array1.print(), "#(1 2 3)");
EXPECT_EQ(array1.inspect(), "[array] size: 3 data: #(1 2 3)\n");
}
TEST(GoosSpecialForms, Define) {
Interpreter i;
e(i, "(define x 010)");
EXPECT_EQ(e(i, "x"), "10");
e(i, "(define :env *goal-env* x 20)");
EXPECT_EQ(e(i, "x"), "10");
Object goal_env;
EXPECT_TRUE(i.get_global_variable_by_name("*goal-env*", &goal_env));
auto x_in_goal_env = goal_env.as_env()->vars.find(i.intern("x").as_symbol());
EXPECT_TRUE(x_in_goal_env != goal_env.as_env()->vars.end());
EXPECT_EQ(x_in_goal_env->second.print(), "20");
// test automatic environment of define
e(i, "(begin (desfun test-define () (define x 500)) (test-define))");
EXPECT_EQ(e(i, "x"), "10");
// test manual setting of global env for define
e(i, "(begin (desfun test-define () (define :env *global-env* x 500)) (test-define))");
EXPECT_EQ(e(i, "x"), "500");
e(i, "(begin (desfun test-define () (define x :env *global-env* 600)) (test-define))");
EXPECT_EQ(e(i, "x"), "600");
e(i, "(begin (desfun test-define () (define x 700 :env *global-env*)) (test-define))");
EXPECT_EQ(e(i, "x"), "700");
i.disable_printfs();
EXPECT_ANY_THROW(e(i, "(define :env 3 x 10)"));
EXPECT_ANY_THROW(e(i, "(define :beans 3 x 10)"));
EXPECT_ANY_THROW(e(i, "(define x)"));
EXPECT_ANY_THROW(e(i, "(define x 1 2)"));
EXPECT_ANY_THROW(e(i, "(define 1)"));
EXPECT_ANY_THROW(e(i, "(define 1 2)"));
EXPECT_ANY_THROW(e(i, "(define)"));
}
TEST(GoosSpecialForms, Set) {
Interpreter i;
e(i, "(define x 10)");
EXPECT_EQ(e(i, "(set! x (+ 10 10))"), "20");
EXPECT_EQ(e(i, "x"), "20");
// set parent env
EXPECT_EQ(e(i, "(begin (desfun test-set () (set! x 30)) (test-set))"), "30");
EXPECT_EQ(e(i, "x"), "30");
// check non-global env (also a good check with two different variables named x)
EXPECT_EQ(e(i, "(begin (desfun test-set (x) (set! x (+ x 30))) (test-set x))"), "60");
EXPECT_EQ(e(i, "x"), "30");
i.disable_printfs();
for (auto x :
{"(set!)", "(set! x)", "(set! 1)", "(set! 1 1)", "(set! x 1 2)", "(set! x 1 :test 2)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosSpecialForms, Quote) {
Interpreter i;
e(i, "(define x 'y)");
EXPECT_EQ(e(i, "x"), "y");
e(i, "(define x (quote z))");
EXPECT_EQ(e(i, "x"), "z");
e(i, "(define x '(1 2 3))");
EXPECT_EQ(e(i, "x"), "(1 2 3)");
e(i, "(define x '(1 2 3 ,w))");
EXPECT_EQ(e(i, "x"), "(1 2 3 (unquote w))");
i.disable_printfs();
for (auto x : {"(quote)", "(quote 1 2)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosSpecialForms, DoubleQuote) {
Interpreter i;
e(i, "(define x ''y)");
EXPECT_EQ(e(i, "x"), "(quote y)");
e(i, "(define x `'`y)");
EXPECT_EQ(e(i, "x"), "(quote (quasiquote y))");
}
TEST(GoosSpecialForms, QuasiQuote) {
Interpreter i;
e(i, "(define x 'y)");
EXPECT_EQ(e(i, "(define z `(x ,x z))"), "(x y z)");
i.disable_printfs();
for (auto x : {"(quasiquote)", "(quasiquote 1 2)", "(unquote 1)", "(unquote-spicing 1)",
"`( (unquote-splicing) 2)", "`( (unquote-splicing 1 2) 2)",
"`( (unquote-splicing 1) 2)", "`( (unquote) 2)", "`( (unquote 1 2) 2)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosSpecialForms, QuasiQuoteSplicing) {
Interpreter i;
e(i, "(define x '(1 2 3))");
EXPECT_EQ(e(i, "(define z `(x ,x z))"), "(x (1 2 3) z)");
EXPECT_EQ(e(i, "(define z `(x ,@x z))"), "(x 1 2 3 z)");
}
TEST(GoosSpecialForms, Or) {
Interpreter i;
e(i, "(desfun rf () #f)");
e(i, "(desfun r1 () 1)");
EXPECT_EQ(e(i, "(or #f (rf) #f)"), "#f");
EXPECT_EQ(e(i, "(or #f (rf) (r1) 2 #f)"), "1");
EXPECT_EQ(e(i, "(or #f (rf) 1 2 cannot-be-evaluated)"), "1");
i.disable_printfs();
for (auto x : {"(or)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosSpecialForms, And) {
Interpreter i;
e(i, "(desfun rf () #f)");
e(i, "(desfun r1 () 1)");
EXPECT_EQ(e(i, "(and #f (rf) #f)"), "#f");
EXPECT_EQ(e(i, "(and (r1) 2 3 4 3 #t 3 2)"), "2");
EXPECT_EQ(e(i, "(and #f (rf) 1 2 cannot-be-evaluated 2 3 4)"), "#f");
i.disable_printfs();
for (auto x : {"(and)"}) {
EXPECT_ANY_THROW(e(i, x));
}
}
TEST(GoosBuiltins, Format) {
Interpreter i;
EXPECT_EQ(e(i, "(fmt #f \"{}, {}, {}\" 3 'bean \"str\")"), "\"3, bean, str\"");
}
TEST(GoosBuiltins, Error) {
Interpreter i;
EXPECT_ANY_THROW(e(i, "(error \"hi\")"));
}
TEST(GoosBuiltins, Ash) {
Interpreter i;
EXPECT_EQ(e(i, "(ash 3 2)"), "12");
EXPECT_EQ(e(i, "(ash 3 -1)"), "1");
}
TEST(GoosBuiltins, StringUtils) {
Interpreter i;
EXPECT_EQ(e(i, "(string-ref \"test\" 2)"), "#\\s");
EXPECT_EQ(e(i, "(string-length \"test\")"), "4");
EXPECT_EQ(e(i, "(string-append \"hello\" \" \" \"world\")"), "\"hello world\"");
EXPECT_EQ(e(i, "(symbol->string 'test)"), "\"test\"");
}
TEST(GoosBuiltins, HashTable) {
Interpreter i;
e(i, "(define ht (make-string-hash-table))");
EXPECT_EQ(e(i, "(car (hash-table-try-ref ht \"foo\"))"), "#f");
e(i, "(hash-table-set! ht \"foo\" 123)");
EXPECT_EQ(e(i, "(car (hash-table-try-ref ht \"bar\"))"), "#f");
EXPECT_EQ(e(i, "(car (hash-table-try-ref ht \"foo\"))"), "#t");
EXPECT_EQ(e(i, "(cdr (hash-table-try-ref ht \"foo\"))"), "123");
e(i, "(hash-table-set! ht \"foo\" 456)");
EXPECT_EQ(e(i, "(cdr (hash-table-try-ref ht \"foo\"))"), "456");
}