NOTE I have migrated all these files over to the knight-lang organization. This exists only for posterity, and will not be updated.
An extremely simple programming language that I've designed to be easy to implement in a variety of languages. It's not actually meant to be used, though it is a fully-functional lang.
Unofficial Tag-line: "Knight: Runs everywhere. Not because it's cross-platform, but because it has a implementation in virtually all major languages."
Checkout the community, and join us on discord: https://discord.gg/SE3TjsewDk.
The following is the list of all languages that I've written it in. All in-progress implementations are in separate branches.
Language | 100% Spec Conformance | Documented | Mostly Functional | Begun | Notes |
---|---|---|---|---|---|
AWK | X | X | X | X | My AWK interpreter segfaults randomly, but after running each test 100x, they all passed. |
Assembly (x86) | X | The parser is completed. | |||
C | X | X | X | X | Fully functional; Probably the best documented code. |
C++ | X | X | X | X | Works with C++17; It could use a facelift though, as I used a bit too much dynamic dispatch. |
C# | X | X | X | Simple version without any documentation. It can be cleaned up slightly though. | |
Haskell | ish | X | X | Works for an older spec of Knight, needs to be updated. | |
JavaScript | X | X | X | X | Fully Functional, although it requires Node.js for the OS-related functions. |
Knight | X | X | Yes, this is a Knight interpreter, written in Knight; It's yet to be tested for spec compliance, though. | ||
Perl | X | X | X | X | Fully Functional on at least v5.18. |
PHP | X | X | X | X | Fully Functional, with type annotations. |
POSIX-Compliant SH | X | X | Mostly works, but has some bug fixes that need to be done. It could definitely use some TL&C, though. | ||
Prolog | X | The very beginnings of a Prolog implementation. | |||
Python | X | X | X | X | Fully Functional, though setrecursionlimit is needed to ensure "FizzBuzz in Knight in Python" works. |
Quest | X | An implementation in my other programming language. | |||
Raku | X | X | X | X | Fully Functional, but quite slow. But hey, it was fun to write in. |
Ruby | X | X | X | A hacky version currently exists; a more sophisticated one is being worked on. | |
Rust | X | X | X | Simple implementation without comments. It intentionally captures all UB, but eventually will have an efficient implementation. | |
Java | Planned; I know Java already, so this should be fairly simple. | ||||
SML | Planned. I used this in college, and enjoyed it. | ||||
Racket | Planned. I used this in college, and enjoyed it. | ||||
LaTeX | Eventually; Because why not? I did a lot of LaTeX in college. | ||||
Scratch | My first language! Might be fun to implement it in this |
The following able describes how fast each implementation (in user
time) was at running examples/fizzbuzz.kn
in knight.kn
in knight.kn
in their implementation, on my machine. You can test it yourself via the timeit script provided.
Note that these are simply benchmarks of my implementations of Knight, and not a reflection of the efficiency of the languages themselves.
Language | Time | <implementation> |
Notes |
---|---|---|---|
C | 7.01s | c/ast/knight |
Compiled using COMPUTED_GOTOS=1 CFLAGS=-DKN_RECKLESS make optimized ; See c/ast/README.md for details. |
C# | 13.75s | csharp/bin/Release/netcoreapp2.1/<impl>/Knight |
|
C++ | 21.48s | cpp/knight |
Copiled using make optimized |
Rust | 28.99s | rust/target/release/knight |
Built with cargo build --release and the reckless flag. |
JavaScript | 30.64s | node --stack-size=1000000 javasript/bin/knight.js |
Default stack's too small, so we had to bump it up. |
PHP | 64.73s | php/knight.php |
|
Ruby | 110.04s | ruby/knight.rb |
Default stack's too small, so RUBY_THREAD_VM_STACK_SIZE=10000000 was needed. |
Python | 236.01s | python/main.py |
Default stack's too small, so setrecursionlimit(100000) was needed. |
Perl | 436.55s | perl/bin/knight.pl |
Here's some examples of the syntax to give you a feel for it:
; = max 100 # max = 100
; = secret (RAND 1 max) # secret = rand(1, max)
; = nguess 0 # nguess = 0
; = guess 0 # guess = 0
; OUTPUT (+ 'guess 1-' max) # print('pick from 1-' + m)
; WHILE (| (< guess secret) (> guess secret)) # while guess != s:
; = guess (+ 0 (PROMPT '> ')) # guess = int(prompt('> '))
; = nguess (+ nguess 1) # nguess += 1
: OUTPUT ( # print(
IF (< guess secret) 'too low' # if guess < secret: 'too low'
IF (> guess secret) 'too high' # if guess > secret: 'too high'
'correct') # else: 'correct')
: OUTPUT (+ 'tries: ' nguess) # print('tries: ' + n)
; = fib BLOCK # function fib:
; = a 0 # a = 0
; = b 1 # b = 1
; WHILE n # while n != 0:
; = b + a = tmp b # b = a + (tmp = b)
; = a tmp # a = tmp
: = n - n 1 # n -= 1
: a # return a
; = n 10 # n = 10
: OUTPUT +++ 'fib(' n ')=' CALL fib # print "fib(" + n + ")=" + fib()
# => fib(10)=55
For exact details please see specs.md. The following is just a rough overview, and is probably out of date.
Every Knight program is a single expression. (The ;
function can be used to write more than one expression, sequentially.) Because of this, parsing is extremely simple: Parse a token, then parse as many arguments as that expression dictates.
Non-symbol functions are defined by their first character: additional uppercase characters following it are ignored. Because of this, OUTPUT
is the same as OUT
, which is the same as OFOOBARBAZ
.
All whitespace (including (
, )
, [
, ],
{,
}
, and :
) outside of strings is completely ignored except in four cases:
- Between two "word" keywords, such as
IF PROMPT
- Between two numbers, such as
+ 1 2
- Between two identifiers, such as
* a b
- Between an identifier and a number, such as
+ a 3
.
As such, expressions such as OUTPUT * a (IF b 3 b)
can be written as O*aIb 3b
.
expr
:= nullary
| unary expr
| binary expr expr
| ternary expr expr expr
| quaternary expr expr expr expr
nullary
:= [0-9]+
| `'` [^']* `'` | `"` [^"]* `"`
| [a-z_][a-z_0-9]*
| ('T' | 'F' | 'N' | 'P' | 'R') {UPPER}
;
unary
:= ('B' | 'C' | 'O' | 'Q' | 'L') {UPPER}
| '`'
| '!'
;
binary
:= 'W' {UPPER}
| '-' | '+' | '*' | '/' | '^'
| '<' | '>' | '&' | '|' | '?'
| ';' | '='
;
ternary := 'I' | 'G' ;
quaternary := 'S' ;
UPPER := [A-Z_]
# - comment until EOL
TRUE () - `TRUE` literal.
FALSE () - `FALSE` literal.
NULL () - `NULL` literal.
PROMPT () - Reads a line from stdin.
RAND () - Returns a random integer.
EVAL (string) - Evaluates `string`.
BLOCK (body) - Anonymous function.
CALL (block) - Calls `block`.
QUIT (status) - Quits with `status`.
! (x) - Boolean negation of `x`.
` (x) - Runs `x` as a shell command, returns `x`'s stdout.
DEBUG (code) - Prints debugging information for `code`.
LENGTH (string) - Length of `string`.
OUTPUT (thing) - Prints `thing`. If `thing` ends with `\`, it omits the last character, otherwise appends a newline.
X(...) -- This function identifier is explicitly unassigned, so extensions may do with it as they wish.
+ (x, y) - If `x` is a string, converts `y` to a string and concats. Otherwise, convert both to a number and add them.
- (x, y) - Converts both to an integer returns `x - y`.
* (x, y) - Converts both to an integer returns `x * y`.
/ (x, y) - Converts both to an integer returns `x / y`.
% (x, y) - Converts both to an integer returns `x % y`. Optionally supports printf for a single argument if the language
has easy support for it.
^ (x, y) - Converts both to an integer returns `x ^ y`.
& (x, y) - Evaluates both, returns `x` if `x` is truthy, otherwise returns `y`.
| (x, y) - Evaluates both, returns `y` if `x` is truthy, otherwise returns `x`.
< (x, y) - If `x` is a string, converts `y` to a string and checks to see if `x` is less than `y` in the current local. Otherwise, converts both to an integer and sees if `x` is less than `y`.
> (x, y) - If `x` is a string, converts `y` to a string and checks to see if `x` is greater than `y` in the current local. Otherwise, converts both to an integer and sees if `x` is greater than `y`.
? (x, y) - If `x` is a string, converts `y` to a string and checks to see if both are equal. Otherwise, converts both to an integer and sees if theyre both equal.
; (x, y) - Evaluates `x`, then `y`, and returns `y`.
= (x, y) - Sets `x` in the global scope to `y`, crashing if `x` isnt an identifier.
WHILE (cond, body) - Evaluates the `body` while the `cond`s true. Returns `body`s last value, or `NULL` if it never ran.
GET (string, index, length) - Gets a substring of length `length` from `string` starting at `index`.
IF (cond, if_t, if_f) - Evaluates and returns `if_t` if `cond` is truthy. Otherwise, evaluates and returns `if_f`.
SET (string, start, len, repl) - Returns a new string with the substring of length `len`, starting at `start`, replaced with `repl`.
The exact details of the language are not nailed down: This is intentional, as it's meant to be fairly easy to be implemented in each language. Thus, the maximum and minimum of integer types is unspecified