tRack is a monotype language, the only type being numbers. See this overview video.
Grammar ::= def-expr*
def-expr ::= define | expr
define ::= "(" "define" "("variable variable*")" expr")"
| "(" "define" variable expr")"
expr ::= variable
| value
| "("variable expr*")"
| "(" "cond" ("("expr expr")")+ ("(" "else" expr ")")? ")"
variable ::= [^'"()\n ]+
value ::= [0-9]+('.'[0-9]+)?
The REPL behaves like one would expect. It can be reached by running tRack without an argument. Define operations do not return a value, but all others should.
| Variable | Description | Example | Result |
|---|---|---|---|
| + | Binary operator. Adds two numbers. | (+ 3.5 7) | 10.5 |
| - | Binary operator. Subtracts two numbers. | (- 3.5 7) | -4.5 |
| * | Binary operator. Multiplies two numbers. | (* 3.5 7) | 24.5 |
| / | Binary operator. Divides two numbers. | (/ 3.5 .5) | 7.0 |
| % | Binary operator. Divides two numbers and returns the remainder. | (% 3 2) | 1 |
| read | Reads a line of text from the terminal. | read | note: This feature does not seem to work perfectly in the REPL |
| expt | Binary operator. Raises the first number to the power of the second. | (expt 3 2) | 9 |
| equal? | Binary operator. Returns 1 if two values are equal, 0 if not. | (equal? 3.5 3.5) | 1 |
| Prints a value to the console. | (print (- 10 3)) | 7 |
| Variable | Description | Equivalent value |
|---|---|---|
| pi | Equal to the value π | TypeScript's Math.PI |
How does one implement conditions without bool types? Anything that is not 0 is truthy, 0 is the only falsy value.
Functions can either have one expression or multiple expressions. If they have multiple expressions, they will return 1. If they have one expression, they will return the value of that expression. Is this the way it should be? Well, it is the way it is.
100% test coverage achieved? You betcha! To run test suite, navigate to _src/test and run:
node test_tRack.mjs
1. Define
In order to test that we can define variables, we implemented a test that defines a variable "r" and then uses it to compute the area of a circle. This test also tests that the value of pi is correct.
circleArea.track
(define r 10)
(print (* pi (* r r)))
Expected output (expected/circleArea):
314.1592653589793
In order to test the the ability to define a function, we extended the above program to define a function "multiply" that takes a two arguments argument and returns the square of that argument. this tests that a function can have multiple arguments. Also, we added a function to compute the area of a circle that utilizes the multiply function. We then test the circleArea function by calling it with a radius of 10.
circleAreaFunction.track
(define radius 10)
(define (multiply x y) (* x y))
(define (circleArea r) (* pi (multiply r r)))
(print (circleArea radius))
Expected Output (expected/circleAreaFunction):
314.1592653589793
2. Expr
The above tests also test most of the expression tree. The creation of a variable (which is made up of a string that does not include quotes, newlines, parentheses, or spaces), the creation of a value (which is made up of a number either whole or decimal), and the use of a function call (which is made up of a function name and a list of arguments). The last piece to test in the expressions leaf is the use of conditional expressions.
To this end, a recursive factorial function was created that tests for the base case by utilizing a conditional.
fact100.track
(define (factorial base)
(cond ((equal? base 1) 1)
(else (* base (factorial (- base 1))))))
(print (factorial 100))
Expected Output (expected/fact100):
9.33262154439441e+157
In order to test the ability to have multiple conditionals prior to the else, we created a simple test that should return 5, and not run the else. Then, in order to test else, we then duplicated this and changed the conditionals such that the else should run.
conditionals.track
(define base 3)
(cond
((equal? base 4) (print 1))
((equal? 3 base) (print 5))
(else (print 3))
)
(cond
((equal? base 4) (print 1))
((equal? 5 base) (print 5))
(else (print 3))
)
Expected Output (expected/conditionals):
5
3
All of the built in functions, except for read, are tested in the below test.
built_in_functions.track
(define w 2)
(define x 5)
(define y 10)
(define z x)
(define a (+ x y))
(define b (- a y))
(define c (/ x b))
(define d (/ x w))
(define e (* c z))
(define f (equal? e y))
(define g (equal? z x))
(define h (% x w))
(define i (expt w x))
(print a)
(print b)
(print c)
(print d)
(print e)
(print f)
(print g)
(print h)
(print i)
Expected output:
expected/built_in_functions
15
5
1
2.5
5
0
1
1
32
In order to test read, run one of the scripts in the scripts/requires_input directory. It will prompt you to enter a value to determine the iterations/value to compute.
There is currently only 1 built in value, pi. It is tested within both the circleAreaFunction.track and circleArea.track tests.
tRack utilizes environments to store variables/functions and their values as opposed to implementing substitution. The environment is a list of pairs, where each pair is a variable and its value/function.
Testing that environments are implemented correctly is done in the environment.track test.
environment.track
(define test 15)
(define test2 25)
(define (env1)
(
(print test)
(define test 2)
(print test)
(define (env2)
(
(print test)
(define test 3)
(print test)
)
)
(env2)
(print test2)
(print test)
)
)
(env1)
Expected output (expected/environment):
15
2
2
3
25
2
1. Parse Errors
-
Unexpected end of input
If the program reaches the end while still expecting more content, it is a parse error.unexpected_end_of_input.track
(define test 4) (print testExpected output (expected/unexpected_end_of_input):
Error: Unexpected end of input -
Unexpected ")"
If there are too many closing parentheses, the parser will throw an error. This script tests this functionality.unexpected_closing_paren.track
(define test 4)) (print test)Expected output (expected/unexpected_closing_paren):
Error: unexpected ')'
2. Runtime Errors
The following are runtime errors:
-
Invalid conditional expression
This error is thrown when a conditional expression is not an array. This is likely because the programmer did not use the correct syntax. This should probably be a parse error, but it isn't.
conditional_not_formatted_correctly.track
(define test 15) (define test2 14) (cond (equal? 15 test2) ((equal? 16 test) (print test2)) )Expected output (expected/conditional_not_formatted_correctly):
Error: Invalid conditional expression. Conditionals must have exactly two elements. -
No conditional matched
Every expression must evaluate to a value. If a conditional has no match, and no else, then it is a runtime error.no_conditional_matched.track
(define test 15) (define test2 14) (cond ((equal? 15 test2) (print 15)) ((equal? 16 test) (print test2)) )Expected output (expected/no_conditional_matched):
Error: No conditional matched -
Function name must be a string
This error is thrown when a function name is not a string. function_must_be_string.track(define (test" r)(* r r)) (print (test" 14))Expected output (expected/function_must_be_string):
Error: Function name must be a string -
Variable name must be a string
Variable names must be strings that do not include parentheses, quotes, or newlines.variable_must_be_string.track
(define test' 5) (print test')Expected output (expected/variable_must_be_string):
Error: Variable name must be a string -
Conditionals must have exactly two elements. Each conditional must have a condition and an expression to be evaluated if the condition evaluates to true.
runtime_error_conditional_2_elements.track
(define test 15) (define test2 14) (cond ((equal? 15 test2)) )Expected output (expected/runtime_error_conditional_2_elements):
Error: Invalid conditional expression. Conditionals must have exactly two elements. -
Conditional expression must be a list. Each conditional expression must have been parsed into a list. This basically means that the expression must be wrapped in parentheses.
runtime_error_conditional_must_be_list.track
(cond 5 (print test))Expected output (expected/runtime_error_conditional_must_be_list):
Error: Conditional expression must be a list. Is your conditional expression surrounded by parentheses?
(define (factorial base)
(cond ((equal? base 1) 1)
(else (* base (factorial (- base 1))))))
(print (factorial 100))
Returns:
9.33262154439441e+157
(define (calc_pi n)
(cond
((equal? n 1) 4)
(else (+ (* (* 4 (expt (- 0 1) (+ n 1))) (/ 1 (- (* 2 n) 1))) (calc_pi (- n 1))))
)
)
(print (calc_pi 400))
Returns:
3.1390926574960143