//
// Logo Interpreter in Javascript
//

// Copyright 2009 Joshua Bell
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//----------------------------------------------------------------------
function test(turtle, report)
//----------------------------------------------------------------------
{

    var stream = {
        inputbuffer: "",

        read: function() {
            var res = this.inputbuffer;
            this.inputbuffer = "";
            return res;
        },

        outputbuffer: "",

        write: function() {
            for (var i = 0; i < arguments.length; i += 1) {
                this.outputbuffer += arguments[i];
            }
        },
        clear: function() {
            this.outputbuffer = "";
        },
    };

    var canvas = document.getElementById("sandbox");
    var interpreter = new LogoInterpreter(turtle, stream);


    var tests = 0, pass = 0;

    // Equality comparison that allows slack when comparing numbers
    var EPSILON = 1e-12;

    function assert_true(value, message) {
        tests += 1;
        if (!value) {
            report("TEST FAILED: ", message);
            return;
        }
        pass += 1;
    }

    function assert_equals(expression, expected) {
        tests += 1;
        try {
            var actual = interpreter.run(expression);

            if (!interpreter.equal(actual, expected, EPSILON)) {
                report("TEST FAILED: ", expression, "  Expected: '", expected, "'  Actual: '", actual, "'");
                return;
            }
            pass += 1;

        } catch (e) {
            report("TEST FAILED: ", expression, "  Exception:", e);
        }
    }

    function assert_stream(expression, expected) {
        tests += 1;
        try {
            stream.clear();
            interpreter.run(expression);
            var actual = stream.outputbuffer;
            stream.clear();
            
            if (actual !== expected) {
                report("TEST FAILED: ", expression, "  Expected stream: '", expected, "'  Actual: '", actual, "'");
                return;
            }
            pass += 1;

        } catch (e) {
            report("TEST FAILED: ", expression, "  Exception:", e);
        }
    }

    assert_true(stream.outputbuffer === "", "Stream buffer not cleared");


    function assert_predicate(expression, predicate) {
        tests += 1;
        try {
            var result = interpreter.run(expression);
            if (!predicate(result)) {
                report("TEST FAILED: ", expression, "  Predicate: '", predicate, "'  Actual: '", result, "'");
                return;
            }
            pass += 1;

        } catch (e) {
            report("TEST FAILED: ", expression, "  Exception:", e);
        }
    }

    function assert_error(expression, expected) {
        tests += 1;
        try {
            var result = interpreter.run(expression);
            report("TEST FAILED: ", expression, "  Expecting error: '", expected, "' Actual result: '", result, "'");
            return;

        } catch (e) {
            if (e !== expected) {
                report("TEST FAILED: ", expression, "  Expecting error: '", expected, "' Actual error: '", e, "'");
                return;
            }

            pass += 1;
        }
    }

    var i;

    //----------------------------------------------------------------------
    //
    // 0. Parser
    //
    //----------------------------------------------------------------------

    //
    // Types
    //

    assert_equals('"test', 'test');
    assert_equals('1', 1);
    assert_equals('[ 1 2 3 ]', [1, 2, 3]);

    //
    // Unary Minus
    //

    assert_equals('-4', -4); // unary
    assert_equals('- 4 + 10', 6); // unary
    assert_equals('10 + - 4', 6); // unary
    assert_equals('(-4)', -4); // unary
    assert_equals('make "t 10 -4 :t', 10); // unary - the -4 is a statement
    assert_equals('make "t 10 - 4 :t', 6); // infix
    assert_equals('make "t 10-4 :t', 6); // infix
    assert_equals('make "t 10- 4 :t', 6); // infix
    assert_equals('sum 10 -4', 6); // unary
    assert_error('sum 10 - 4', 'Unexpected end of instructions'); // infix - should error
    assert_equals('sum 10 (-4)', 6); // unary
    assert_equals('sum 10 ( -4 )', 6); // unary
    assert_equals('sum 10 ( - 4 )', 6); // unary
    assert_equals('sum 10 (- 4)', 6); // unary

    //
    // Case insensitive
    //

    assert_equals('make "t 1 :t', 1);
    assert_equals('MAKE "t 1 :t', 1);
    assert_equals('MaKe "t 1 :t', 1);

    assert_equals('make "t 2 :t', 2);
    assert_equals('make "T 3 :t', 3);
    assert_equals('make "t 4 :T', 4);
    assert_equals('make "T 5 :T', 5);

    assert_equals('to foo output 6 end  foo', 6);
    assert_equals('to FOO output 7 end  foo', 7);
    assert_equals('to foo output 8 end  FOO', 8);
    assert_equals('to FOO output 9 end  FOO', 9);

    //
    // Lists
    //

    assert_stream('print [ Hello World ]', 'Hello World\n');

    //
    // Numbers
    //
    
    assert_stream('type .2 + .3', '0.5');
    

    //----------------------------------------------------------------------
    //
    // 2. Data Structure Primitives
    //
    //----------------------------------------------------------------------

    //
    // 2.1 Constructors
    //

    assert_equals('word "hello "world', 'hello world');
    assert_equals('(word "a "b "c)', 'a b c');
    assert_equals('(word)', '');

    assert_equals('list 1 2', [1, 2]);
    assert_equals('(list 1 2 3)', [1, 2, 3]);

    assert_equals('sentence 1 2', [1, 2]);
    assert_equals('se 1 2', [1, 2]);
    assert_equals('(sentence 1)', [1]);
    assert_equals('(sentence 1 2 3)', [1, 2, 3]);
    assert_equals('sentence [1] [2]', [1, 2]);
    assert_equals('sentence [1 2] [3 4]', [1, 2, 3, 4]);
    assert_equals('sentence 1 [2 3]', [1, 2, 3]);

    assert_equals('fput 0 [ 1 2 3 ]', [0, 1, 2, 3]);
    assert_equals('lput 0 [ 1 2 3 ]', [1, 2, 3, 0]);

    assert_equals('combine "a "b', 'a b');
    assert_equals('combine 1 [2]', [1, 2]);

    assert_equals('reverse [ 1 2 3 ]', [3, 2, 1]);

    assert_equals('gensym <> gensym', 1);

    //
    // 2.2 Data Selectors
    //

    assert_equals('first (LIST 1 2 3 )', 1);
    assert_equals('firsts [ [ 1 2 3 ] [ "a "b "c] ]', [1, '"a']);
    assert_equals('last  [ 1 2 3 ]', 3);
    assert_equals('butfirst [ 1 2 3 ]', [2, 3]);
    assert_equals('bf [ 1 2 3 ]', [2, 3]);
    assert_equals('butfirsts [ [ 1 2 3 ] [ "a "b "c] ]', [ [2, 3], ['"b', '"c'] ]);
    assert_equals('bfs [ [ 1 2 3 ] [ "a "b "c] ]', [ [2, 3], ['"b', '"c'] ]);
    assert_equals('butlast  [ 1 2 3 ]', [1, 2]);
    assert_equals('bl  [ 1 2 3 ]', [1, 2]);

    assert_equals('first "123', '1');
    assert_equals('last  "123', '3');
    //assert_equals('butfirst "123', '23');
    //assert_equals('butlast  "123', '12');

    assert_equals('first 123', '1');
    assert_equals('last  123', '3');
    //assert_equals('butfirst 123', '23');
    //assert_equals('butlast  123', '12');


    assert_error('item 0 [ 1 2 3 ]', 'index out of bounds');
    assert_equals('item 1 [ 1 2 3 ]', 1);
    assert_equals('item 2 [ 1 2 3 ]', 2);
    assert_equals('item 3 [ 1 2 3 ]', 3);
    assert_error('item 4 [ 1 2 3 ]', 'index out of bounds');
    for (i = 0; i < 10; i += 1) {
        assert_predicate('pick [ 1 2 3 4 ]', function(x) { return 1 <= x && x <= 4; });
    }
    assert_equals('remove 2 [ 1 2 3 ]', [1, 3]);
    assert_equals('remove 4 [ 1 2 3 ]', [1, 2, 3]);
    assert_equals('remdup [ 1 2 3 1 2 3 ]', [1, 2, 3]);

    //
    // 2.3 Data Mutators
    //

    assert_equals('make "s [] repeat 5 [ push "s repcount ] :s', [5, 4, 3, 2, 1]);
    assert_equals('make "s [ 1 2 3 ] (list pop "s pop "s pop "s)', [1, 2, 3]);
    assert_equals('make "q [] repeat 5 [ queue "q repcount ] :q', [1, 2, 3, 4, 5]);
    assert_equals('make "q [ 1 2 3 ] (list dequeue "q dequeue "q dequeue "q)', [1, 2, 3]);

    //
    // 2.4 Predicates
    //

    assert_equals('wordp "a', 1);
    assert_equals('wordp 1', 0);
    assert_equals('wordp [ 1 ]', 0);
    assert_equals('word? "a', 1);
    assert_equals('word? 1', 0);
    assert_equals('word? [ 1 ]', 0);

    assert_equals('listp "a', 0);
    assert_equals('listp 1', 0);
    assert_equals('listp [ 1 ]', 1);
    assert_equals('list? "a', 0);
    assert_equals('list? 1', 0);
    assert_equals('list? [ 1 ]', 1);

    assert_equals('equalp 3 4', 0);
    assert_equals('equalp 3 3', 1);
    assert_equals('equalp 3 2', 0);
    assert_equals('equal? 3 4', 0);
    assert_equals('equal? 3 3', 1);
    assert_equals('equal? 3 2', 0);
    assert_equals('3 = 4', 0);
    assert_equals('3 = 3', 1);
    assert_equals('3 = 2', 0);
    assert_equals('notequalp 3 4', 1);
    assert_equals('notequalp 3 3', 0);
    assert_equals('notequalp 3 2', 1);
    assert_equals('notequal? 3 4', 1);
    assert_equals('notequal? 3 3', 0);
    assert_equals('notequal? 3 2', 1);
    assert_equals('3 <> 4', 1);
    assert_equals('3 <> 3', 0);
    assert_equals('3 <> 2', 1);

    assert_equals('equalp "a "a', 1);
    assert_equals('equalp "a "b', 0);
    assert_equals('"a = "a', 1);
    assert_equals('"a = "b', 0);
    assert_equals('equalp [1 2] [1 2]', 1);
    assert_equals('equalp [1 2] [1 3]', 0);
    assert_equals('[ 1 2 ] = [ 1 2 ]', 1);
    assert_equals('[ 1 2 ] = [ 1 3 ]', 0);

    assert_equals('equalp "a 1', 0);
    assert_equals('equalp "a [ 1 ]', 0);
    assert_equals('equalp 1 [ 1 ]', 0);


    assert_equals('numberp "a', 0);
    assert_equals('numberp 1', 1);
    assert_equals('numberp [ 1 ]', 0);
    assert_equals('number? "a', 0);
    assert_equals('number? 1', 1);
    assert_equals('number? [ 1 ]', 0);

    assert_equals('emptyp []', 1);
    assert_equals('empty? []', 1);
    assert_equals('emptyp [ 1 ]', 0);
    assert_equals('empty? [ 1 ]', 0);
    assert_equals('emptyp "', 1);
    assert_equals('empty? "', 1);
    assert_equals('emptyp "a', 0);
    assert_equals('empty? "a', 0);

    assert_equals('beforep "a "b', 1);
    assert_equals('beforep "b "b', 0);
    assert_equals('beforep "c "b', 0);
    assert_equals('before? "a "b', 1);
    assert_equals('before? "b "b', 0);
    assert_equals('before? "c "b', 0);

    assert_equals('substringp "a "abc', 1);
    assert_equals('substringp "z "abc', 0);
    assert_equals('substring? "a "abc', 1);
    assert_equals('substring? "z "abc', 0);

    assert_equals('memberp 2 [ 1 2 3 ]', 1);
    assert_equals('memberp 5 [ 1 2 3 ]', 0);
    assert_equals('memberp [ "b ] [ [ "a ] [ "b ] [ "c ] ]', 1);
    assert_equals('member? 2 [ 1 2 3 ]', 1);
    assert_equals('member? 5 [ 1 2 3 ]', 0);
    assert_equals('member? [ "b ] [ [ "a ] [ "b ] [ "c ] ]', 1);

    //
    // 2.5 Queries
    //

    assert_equals('count [ ]', 0);
    assert_equals('count [ 1 ]', 1);
    assert_equals('count [ 1 2 ]', 2);
    assert_equals('count "', 0);
    assert_equals('count "a', 1);
    assert_equals('count "ab', 2);

    assert_equals('ascii "A', 65);
    assert_equals('char 65', 'A');

    assert_equals('lowercase "ABcd', 'abcd');
    assert_equals('uppercase "ABcd', 'ABCD');
    assert_equals('standout "whatever', 'whatever');


    //----------------------------------------------------------------------
    //
    // 3. Communication
    //
    //----------------------------------------------------------------------

    // 3.1 Transmitters

    stream.clear();
    assert_true(stream.outputbuffer === "", "Stream buffer not cleared");

    assert_stream('print "a', 'a\n');
    assert_stream('print 1', '1\n');
    assert_stream('print [ 1 ]', '1\n');
    assert_stream('print [ 1 [ 2 ] ]', '1 [ 2 ]\n');
    assert_stream('(print "a 1 [ 2 [ 3 ] ])', 'a 1 2 [ 3 ]\n');
    
    assert_stream('type "a', 'a');
    assert_stream('(type "a 1 [ 2 [ 3 ] ])', 'a12 [ 3 ]');

    assert_stream('(print "hello "world)', "hello world\n");
    assert_stream('(type "hello "world)', "helloworld");

    assert_stream('show "a', 'a\n');
    assert_stream('show 1', '1\n');
    assert_stream('show [ 1 ]', '[ 1 ]\n');
    assert_stream('show [ 1 [ 2 ] ]', '[ 1 [ 2 ] ]\n');
    assert_stream('(show "a 1 [ 2 [ 3 ] ])', 'a 1 [ 2 [ 3 ] ]\n');

    // 3.2 Receivers
    
    stream.inputbuffer = "test";
    assert_equals('readword', 'test');

    stream.inputbuffer = "a b c 1 2 3";
    assert_equals('readword', 'a b c 1 2 3');
    
    // 3.3 File Access
    // 3.4 Terminal Access

    assert_stream('print "a cleartext', '');
    assert_stream('print "a ct', '');

    stream.clear();

    //----------------------------------------------------------------------
    //
    // 4. Arithmetic
    //
    //----------------------------------------------------------------------

    //
    // 4.1 Numeric Operations
    //

    assert_equals('sum 1 2', 3);
    assert_equals('(sum 1 2 3 4)', 10);
    assert_equals('1 + 2', 3);
    
    assert_equals('"3 + "2', 5);  
    
    assert_equals('difference 3 1', 2);
    assert_equals('3 - 1', 2);
    assert_equals('minus 3 + 4', -(3+4));
    assert_equals('- 3 + 4', (-3)+4);
    assert_equals('minus 3', -3);
    assert_equals('- 3', -3);
    assert_equals('product 2 3', 6);
    assert_equals('(product 2 3 4)', 24);
    assert_equals('2 * 3', 6);
    assert_equals('quotient 6 2', 3);
    assert_equals('(quotient 2)', 1/2);
    assert_equals('6 / 2', 3);

    assert_equals('remainder 7 4', 3);
    assert_equals('remainder 7 -4', 3);
    assert_equals('remainder -7 4', -3);
    assert_equals('remainder -7 -4', -3);
    assert_equals('7 % 4', 3);
    assert_equals('7 % -4', 3);
    assert_equals('-7 % 4', -3);
    assert_equals('-7 % -4', -3);

    assert_equals('modulo 7 4', 3);
    assert_equals('modulo 7 -4', -3);
    assert_equals('modulo -7 4', 3);
    assert_equals('modulo -7 -4', -3);

    assert_equals('abs -1', 1);
    assert_equals('abs 0', 0);
    assert_equals('abs 1', 1);


    assert_equals('int 3.5', 3);
    assert_equals('int -3.5', -3);
    assert_equals('round 2.4', 2);
    assert_equals('round 2.5', 3);
    assert_equals('round 2.6', 3);
    assert_equals('round -2.4', -2);
    assert_equals('round -2.5', -2);
    assert_equals('round -2.6', -3);

    assert_equals('sqrt 9', 3);
    assert_equals('power 3 2', 9);
    assert_equals('3 ^ 2', 9);

    assert_equals('exp 2', 7.38905609893065);
    assert_equals('log10 100', 2);
    assert_equals('ln 9', 2.1972245773362196);

    assert_equals('arctan 1', 45);
    assert_equals('2*(arctan 0 1)', 180);
    assert_equals('sin 30', 0.5);
    assert_equals('cos 60', 0.5);
    assert_equals('tan 45', 1);

    assert_equals('radarctan 1', Math.PI/4);
    assert_equals('2*(radarctan 0 1)', Math.PI);
    assert_equals('radsin 0.5235987755982988', 0.5);
    assert_equals('radcos 1.0471975511965976', 0.5);
    assert_equals('radtan 0.7853981633974483', 1);

    assert_equals('iseq 1 4', [1, 2, 3, 4]);
    assert_equals('iseq 3 7', [3, 4, 5, 6, 7]);
    assert_equals('iseq 7 3', [7, 6, 5, 4, 3]);

    assert_equals('rseq 3 5 9', [3, 3.25, 3.5, 3.75, 4, 4.25, 4.5, 4.75, 5]);
    assert_equals('rseq 3 5 5', [3, 3.5, 4, 4.5, 5]);

    //
    // 4.2 Numeric Predicates
    //

    assert_equals('greaterp 3 4', 0);
    assert_equals('greaterp 3 3', 0);
    assert_equals('greaterp 3 2', 1);
    assert_equals('greater? 3 4', 0);
    assert_equals('greater? 3 3', 0);
    assert_equals('greater? 3 2', 1);
    assert_equals('3 > 4', 0);
    assert_equals('3 > 3', 0);
    assert_equals('3 > 2', 1);
    assert_equals('greaterequalp 3 4', 0);
    assert_equals('greaterequalp 3 3', 1);
    assert_equals('greaterequalp 3 2', 1);
    assert_equals('greaterequal? 3 4', 0);
    assert_equals('greaterequal? 3 3', 1);
    assert_equals('greaterequal? 3 2', 1);
    assert_equals('3 >= 4', 0);
    assert_equals('3 >= 3', 1);
    assert_equals('3 >= 2', 1);
    assert_equals('lessp 3 4', 1);
    assert_equals('lessp 3 3', 0);
    assert_equals('lessp 3 2', 0);
    assert_equals('less? 3 4', 1);
    assert_equals('less? 3 3', 0);
    assert_equals('less? 3 2', 0);
    assert_equals('3 < 4', 1);
    assert_equals('3 < 3', 0);
    assert_equals('3 < 2', 0);
    assert_equals('lessequalp 3 4', 1);
    assert_equals('lessequalp 3 3', 1);
    assert_equals('lessequalp 3 2', 0);
    assert_equals('lessequal? 3 4', 1);
    assert_equals('lessequal? 3 3', 1);
    assert_equals('lessequal? 3 2', 0);
    assert_equals('3 <= 4', 1);
    assert_equals('3 <= 3', 1);
    assert_equals('3 <= 2', 0);

    assert_equals('"3 < "22', 1);  

    //
    // 4.3 Random Numbers
    //

    for (i = 0; i < 10; i += 1) {
        assert_predicate('random 10', function(x) { return 0 <= x && x < 10; });
    }

    // 4.4 Print Formatting

    //
    // 4.5 Bitwise Operations
    //

    assert_equals('bitand 1 2', 0);
    assert_equals('bitand 7 2', 2);
    assert_equals('(bitand 7 11 15)', 3);

    assert_equals('bitor 1 2', 3);
    assert_equals('bitor 7 2', 7);
    assert_equals('(bitor 1 2 4)', 7);

    assert_equals('bitxor 1 2', 3);
    assert_equals('bitxor 7 2', 5);
    assert_equals('(bitxor 1 2 7)', 4);

    assert_equals('bitnot 0', -1);
    assert_equals('bitnot -1', 0);
    assert_equals('bitand (bitnot 123) 123', 0);

    assert_equals('ashift 1 2', 4);
    assert_equals('ashift 8 -2', 2);
    assert_equals('lshift 1 2', 4);
    assert_equals('lshift 8 -2', 2);

    assert_equals('ashift -1024 -1', -512);
    assert_equals('ashift -1 -1', -1);
    assert_equals('lshift -1 -1', 0x7fffffff);

    //----------------------------------------------------------------------
    //
    // 5. Logical Operations
    //
    //----------------------------------------------------------------------

    assert_equals('true', 1);
    assert_equals('false', 0);
    assert_equals('and 0 0', 0);
    assert_equals('and 0 1', 0);
    assert_equals('and 1 0', 0);
    assert_equals('and 1 1', 1);
    assert_equals('(and 0 0 0)', 0);
    assert_equals('(and 1 0 1)', 0);
    assert_equals('(and 1 1 1)', 1);
    assert_equals('or 0 0', 0);
    assert_equals('or 0 1', 1);
    assert_equals('or 1 0', 1);
    assert_equals('or 1 1', 1);
    assert_equals('(or 0 0 0)', 0);
    assert_equals('(or 1 0 1)', 1);
    assert_equals('(or 1 1 1)', 1);
    assert_equals('xor 0 0', 0);
    assert_equals('xor 0 1', 1);
    assert_equals('xor 1 0', 1);
    assert_equals('xor 1 1', 0);
    assert_equals('(xor 0 0 0)', 0);
    assert_equals('(xor 1 0 1)', 0);
    assert_equals('(xor 1 1 1)', 1);
    assert_equals('not 0', 1);
    assert_equals('not 1', 0);

    // short circuits
    
    assert_stream('and 0 (print "nope)', '');
    assert_stream('or 1 (print "nope)', '');

    assert_stream('and 1 (type "yup)', 'yup');
    assert_stream('or 0 (type "yup)', 'yup');    

    //----------------------------------------------------------------------
    //
    // 6. Graphics
    //
    //----------------------------------------------------------------------

    // NOTE: test canvas is 300,300 (so -150...150 coordinates before hitting)
    // edge

    interpreter.run('clearscreen');
    assert_equals('clean home (list heading xcor ycor)', [0, 0, 0]);

    //
    // 6.1 Turtle Motion
    //

    assert_equals('home forward 100 pos', [0, 100]);
    assert_equals('home fd 100 pos', [0, 100]);
    assert_equals('home back 100 pos', [0, -100]);
    assert_equals('home bk 100 pos', [0, -100]);
    assert_equals('home left 45 heading', -45);
    assert_equals('home lt 45 heading', -45);
    assert_equals('home right 45 heading', 45);
    assert_equals('home rt 45 heading', 45);

    assert_equals('setpos [ 12 34 ] pos', [12, 34]);
    assert_equals('setxy 56 78 pos', [56, 78]);
    assert_equals('setxy 0 0 (list xcor ycor)', [0, 0]);
    assert_equals('setx 123 xcor', 123);
    assert_equals('sety 45 ycor', 45);
    assert_equals('setheading 69 heading', 69);
    assert_equals('seth 13 heading', 13);

    assert_equals('forward 100 rt 90 home (list heading xcor ycor)', [0, 0, 0]);

    //
    // 6.2 Turtle Motion Queries
    //

    assert_equals('setpos [ 12 34 ] pos', [12, 34]);
    assert_equals('setx 123 xcor', 123);
    assert_equals('sety 45 ycor', 45);
    assert_equals('setheading 69 heading', 69);
    assert_equals('seth 69 heading', 69);
    assert_equals('setxy -100 -100 towards [ 0 0 ]', 45);

    //
    // 6.3 Turtle and Window Control
    //

    assert_equals('showturtle shownp', 1);
    assert_equals('st shownp', 1);
    assert_equals('hideturtle shownp', 0);
    assert_equals('ht shownp', 0);
    assert_equals('setpos [ 12 34 ] clean pos', [12, 34]);
    assert_equals('setpos [ 12 34 ] clearscreen (list heading xcor ycor)', [0, 0, 0]);
    assert_equals('setpos [ 12 34 ] cs (list heading xcor ycor)', [0, 0, 0]);

    assert_equals('wrap turtlemode', 'WRAP');
    assert_equals('setxy 0 0 setxy 160 160 (list xcor ycor)', [-140, -140]);     
    
    assert_equals('window turtlemode', 'WINDOW');
    assert_equals('setxy 0 0 setxy 160 160 (list xcor ycor)', [160, 160]);     
    
    assert_equals('fence turtlemode', 'FENCE');
    assert_equals('setxy 0 0 setxy 160 160 (list xcor ycor)', [150, 150]);     

    assert_equals('wrap turtlemode', 'WRAP');

    assert_equals('(label "a 1 [ 2 [ 3 ] ])', 'a 1 2 [ 3 ]');
    assert_equals('setlabelheight 5 labelsize', [5, 5]);
    assert_equals('setlabelheight 10 labelsize', [10, 10]);

    //
    // 6.4 Turtle and Window Queries
    //

    assert_equals('showturtle shownp', 1);
    assert_equals('hideturtle shownp', 0);

    assert_equals('wrap turtlemode', 'WRAP');
    assert_equals('window turtlemode', 'WINDOW');
    assert_equals('fence turtlemode', 'FENCE');
    assert_equals('wrap turtlemode', 'WRAP');


    assert_equals('setlabelheight 5 labelsize', [5, 5]);

    //
    // 6.5 Pen and Background Control
    //

    assert_equals('pendown pendownp', 1);
    assert_equals('penup pendownp', 0);
    assert_equals('pd pendownp', 1);
    assert_equals('pu pendownp', 0);

    assert_equals('penpaint penmode', 'PAINT');
    assert_equals('penerase penmode', 'ERASE');
    assert_equals('penreverse penmode', 'REVERSE');

    assert_equals('setpencolor 0 pencolor', 'black');
    assert_equals('setpc 0 pencolor', 'black');
    assert_equals('setpencolor "#123456 pencolor', '#123456');
    assert_equals('(setpencolor 0 50 99) pencolor', '#0081ff');

    assert_equals('setpensize 6 pensize', [6, 6]);
    assert_equals('setpensize [6 6] pensize', [6, 6]);

    //
    // 6.6 Pen Queries
    //

    assert_equals('pendown pendownp', 1);
    assert_equals('penup pendownp', 0);

    assert_equals('penpaint penmode', 'PAINT');
    assert_equals('penerase penmode', 'ERASE');
    assert_equals('penreverse penmode', 'REVERSE');

    assert_equals('setpencolor 0 pencolor', 'black');
    assert_equals('setpencolor "#123456 pencolor', '#123456');
    assert_equals('setpensize 6 pensize', [6, 6]);

    // 6.7 Saving and Loading Pictures
    // 6.8 Mouse Queries

    interpreter.run('clearscreen');

    //----------------------------------------------------------------------
    //
    // 7. Workspace Management
    //
    //----------------------------------------------------------------------

    // 7.1 Procedure Definition

    assert_equals('to square :x output :x * :x end  square 5', 25);
    assert_equals('to foo output 5 end  foo', 5);
    assert_equals('to foo :x :y output 5 end  foo 1 2', 5);
    assert_equals('to foo :x :y output :x + :y end  foo 1 2', 3);
    assert_equals('to foo :x :y output :x + :y end  def "foo', 'to foo :x :y output :x + :y end');
    assert_equals('to foo :x bar 1 "a + :x [ 1 2 ] end  def "foo', 'to foo :x bar 1 "a + :x [ 1 2 ] end');
    assert_equals('to foo 1 + 2 - 3 * 4 / 5 % 6 ^ -1 end  def "foo', 'to foo 1 + 2 - 3 * 4 / 5 % 6 ^ -1 end');

    //
    // 7.2 Variable Definition
    //

    assert_equals('make "foo 5 :foo', 5);
    assert_equals('make "foo "a :foo', 'a');
    assert_equals('make "foo [1 2] :foo', [1, 2]);
    assert_equals('make "n "alpha make :n "beta :alpha', 'beta');

    // by default, make operates in global scope
    assert_equals('to dofoo ' +
                  '  make "foo 456 ' +
                  '  output :foo ' +
                  'end ' +
                  'make "foo 123 ' +
                  'dofoo + :foo', 456 + 456);

    assert_equals('to dofoo2 :foo ' +
                  '  make "foo 456 ' +
                  '  output :foo + :foo ' +
                  'end ' +
                  'make "foo 123 ' +
                  '(dofoo2 111) + :foo', 123 + 456 + 456);

    assert_equals('name 5 "foo :foo', 5);
    assert_equals('name "a "foo :foo', 'a');
    assert_equals('name [1 2] "foo :foo', [1, 2]);
    assert_equals('name "gamma "m  name "delta :m :gamma', 'delta');

    assert_equals('to dofoo ' +
                  '  local "foo ' +
                  '  make "foo 456' +
                  '  output :foo ' +
                  'end ' +
                  'make "foo 123 ' +
                  'dofoo + :foo', 456 + 123);

    assert_equals('to dofoo ' +
                  '  localmake "foo 456' +
                  '  output :foo ' +
                  'end ' +
                  'make "foo 123 ' +
                  'dofoo + :foo', 456 + 123);

    assert_equals('make "baz 321 thing "baz', 321);
    assert_equals('make "baz "a thing "baz', 'a');
    assert_equals('make "baz [1 2 3] thing "baz', [1, 2, 3]);

    assert_equals('global "foo 1', 1); // Doesn't actually test anything
    assert_equals('(global "foo "bar) 1', 1); // Doesn't actually test anything

    assert_equals('procedurep "notdefined', 0);
    assert_equals('to foo end  procedurep "foo', 1);
    assert_equals('procedure? "notdefined', 0);
    assert_equals('to foo end  procedure? "foo', 1);

    assert_equals('primitivep "notdefined', 0);
    assert_equals('to foo end  primitivep "foo', 0);
    assert_equals('primitivep "sentence', 1);
    assert_equals('primitive? "notdefined', 0);
    assert_equals('to foo end  primitive? "foo', 0);
    assert_equals('primitive? "sentence', 1);

    assert_equals('definedp "notdefined', 0);
    assert_equals('to foo end  definedp "foo', 1);
    assert_equals('definedp "sentence', 0);
    assert_equals('defined? "notdefined', 0);
    assert_equals('to foo end  defined? "foo', 1);
    assert_equals('defined? "sentence', 0);

    assert_equals('namep "notdefined', 0);
    assert_equals('make "foo 5 namep "foo', 1);

    // 7.3 Property Lists
    // 7.4 Workspace Predicates

    //
    // 7.5 Workspace Queries
    //
    
    assert_equals('erall  contents', [[], []]);

    assert_equals('erall  make "a 1  to b output 2 end  contents', [['b'], ['a']]);
    assert_equals('erall  make "a 1  to b output 2 end  procedures', ['b']);
    // TODO: primitives
    assert_equals('erall  make "a 1  to b output 2 end  globals', ['a']);
    assert_equals('erall  make "a 1  to b output 2 end  names', [[], ['a']]);

    assert_equals('erall  make "a 1  make "b 2  to a output 1 end  to b output 2 end  erase [[a] [b]]  contents', [['b'], ['a']]);
    assert_equals('erall  make "a 1  make "b 2  to a output 1 end  to b output 2 end  erall  contents', [[], []]);

    
    // 7.6 Workspace Inspection
    // 7.7 Workspace Control

    //----------------------------------------------------------------------
    //
    // 8. Control Structures
    //
    //----------------------------------------------------------------------

    //
    // 8.1 Control
    //

    assert_equals('make "c 0  run [ ]  :c', 0);
    assert_equals('make "c 0  run [ make "c 5 ]  :c', 5);

    assert_equals('runresult [ make "x 1 ]', []);
    assert_equals('runresult [ 1 + 2]', [ 3 ]);

    assert_equals('make "c 0  repeat 5 [ make "c :c + 1 ]  :c', 5);
    assert_equals('make "c 0  repeat 4 [ make "c :c + repcount ]  :c', 10);

    assert_equals('make "c 0  to foo forever [ make "c :c + 1 if repcount = 5 [ stop ] ] end  foo  :c', 5);
    assert_equals('make "c 0  to foo forever [ make "c :c + repcount if repcount = 4 [ stop ] ] end  foo  :c', 10);

    assert_equals('ifelse 1 [ "a ] [ "b ]', 'a');
    assert_equals('ifelse 0 [ "a ] [ "b ]', 'b');

    assert_equals('to foo if 1 [ output "a ] output "b end  foo', 'a');
    assert_equals('to foo if 0 [ output "a ] output "b end  foo', 'b');

    assert_equals('make "c 1  test 2 > 1  iftrue  [ make "c 2 ]  :c', 2);
    assert_equals('make "c 1  test 2 > 1  ift  [ make "c 2 ]  :c', 2);
    assert_equals('make "c 1  test 2 > 1  iffalse [ make "c 2 ]  :c', 1);    
    assert_equals('make "c 1  test 2 > 1  iff [ make "c 2 ]  :c', 1);    

    assert_equals('to foo forever [ if repcount = 5 [ make "c 234 stop ] ] end  foo  :c', 234);

    assert_equals('forever [ if repcount = 5 [ bye ] ]', undefined);

    assert_equals('to foo output 123 end  foo', 123);
    assert_equals('to foo op 123 end  foo', 123);


    assert_equals('to foo .maybeoutput 5 end  foo', 5);
    assert_equals('to foo .maybeoutput make "c 0 end  foo', undefined);


    assert_equals('ignore 1 > 2', undefined);

    assert_equals('make "x 0  for [ r 1 5 ] [ make "x :x + :r ]  :x', 15);
    assert_equals('make "x 0  for [ r 0 10 2 ] [ make "x :x + :r ]  :x', 30);
    assert_equals('make "x 0  for [ r 10 0 -2 ] [ make "x :x + :r ]  :x', 30);
    assert_equals('make "x 0  for [ r 10 0 -2-2 ] [ make "x :x + :r ]  :x', 18);

    assert_equals('make "x 0  do.while [ make "x :x + 1 ] :x < 10  :x', 10);
    assert_equals('make "x 0  while :x < 10 [ make "x :x + 1 ]     :x', 10);

    assert_equals('make "x 0  do.until [ make "x :x + 1 ] :x > 10  :x', 11);
    assert_equals('make "x 0  until :x > 10 [ make "x :x + 1 ]     :x', 11);

    //
    // 8.2 Template-based Iteration
    //

    assert_equals('apply "word ["a "b "c]', '"a "b "c');
    assert_equals('(invoke "word "a "b "c)', 'a b c');
    assert_equals('make "x 0  to addx :a make "x :x+:a end  foreach "addx [ 1 2 3 4 5 ]  :x', 15);
    assert_equals('to double :x output :x * 2 end  map "double [ 1 2 3 ]', [2, 4, 6]);
    assert_equals('to odd :x output :x % 2 end  filter "odd [ 1 2 3 ]', [1, 3]);
    assert_equals('find "numberp [ "a "b "c 4 "e "f ]', 4);
    assert_equals('find "numberp [ "a "b "c "d "e "f ]', []);
    assert_equals('reduce "sum [ 1 2 3 4 ]', 10);
    assert_equals('(reduce "sum [ 1 2 3 4 ] 10)', 20);

    // TODO: Order of operations
    // TODO: Structures, lists of lists

    report("Tests: ", tests, "  Pass: ", pass, "  Fail: ", (tests - pass));
    if (tests != pass) {
        report("******** FAILED! *******");
    }
}


