Knight is a minimalistic programming language designed to be easily implementable in a variety of languages. While it is a fully-functional programming language, it's main purpose is to be a somewhat-easy-to-implement language.
Unofficial Tag-line: "Knight: Runs everywhere. Not because it's cross-platform, but because it works in any almost any language you have."
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.
Language | Version | 100% Spec Conformance | Documented | Mostly Functional | Begun | Notes |
---|---|---|---|---|---|---|
AWK | 1.0 | X | X | X | X | My AWK interpreter segfaults randomly, but after running each test 100x, they all passed. |
Assembly (x86) | 1.0 | X | X | Functional enough to run the benchmark. A few auxiliary functions (eg * STRING NUM ) are left. |
||
C | 2.0.1 | X | X | X | X | Fully functional |
C++ | 2.0.1 | X | X | X | X | Works with C++17 |
C# | 1.0 | X | X | X | X | Simple version without any documentation. It can be cleaned up slightly though. |
Go | 2.0.1 | X | X | X | Fully functional, but undocumented. | |
Haskell | pre-1.0 | ish | X | Works for an older spec of Knight, needs to be updated. | ||
Java | 1.0 | X | X | X | Simple version without any documentation. It can be cleaned up slightly though. | |
JavaScript | 1.0 | X | X | X | X | Fully Functional, although it requires Node.js for the OS-related functions. |
Knight | 2.0.1 | X | X | Yes, this is a Knight interpreter, written in Knight. | ||
Kotlin | 1.0 | X | X | X | X | Fully funcitonal, and barring ` , can be compiled to native. |
Perl | 1.1 | X | X | X | X | Fully Functional on at least v5.18. |
PHP | 1.0 | X | X | X | X | Fully Functional, with type annotations. |
POSIX-Compliant SH | 1.0 | X | X | Mostly works, but has some bug fixes that need to be done. It could definitely use some TL&C, though. | ||
Python | 1.0 | X | X | X | X | Fully Functional, though setrecursionlimit is needed to ensure "FizzBuzz in Knight in Python" works. |
Raku | 1.1 | X | X | X | X | Fully Functional, but quite slow. But hey, it was fun to write in. |
Ruby | 1.0 | X | X | X | A hacky version currently exists; a more sophisticated one is being worked on. | |
Rust | 2.0.1 | X | X | X | Captures all UB if you enable strict-compliance . Also has most extensions |
I love language design, and have written quite a few programming languages. I generally try to get the languages fleshed out enough so that I can write a fully compliant Knight interpreter in it.
Language | 100% Spec Conformance | Documented | Mostly Functional | Begun | Notes |
---|---|---|---|---|---|
Brick | X | X | X | ||
Squire | X | X | X | ||
Quest | X | Implemented in quest 1, so not entirely working. | |||
Stick | X | ||||
Lance | X | X | Few bugs left to workout | ||
Mercenary |
In addition, the following is a list of languages which I want to write an implementation in at some point.
Language | Notes |
---|---|
Elixir | Probably the next one I'll do. |
Lua | Planned for somepoint soon. |
SML | Eventually. I used this in college, and enjoyed it. |
Racket | Eventually. 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 |
Prolog | The very beginnings of a Prolog implementation. |
Fortran | This might be fun to try, but there's not a lot of documentations about it. |
NOTE: These are outdated. I no longer own the computer I tested them on, and the timeit
file doesn't actually work. I'll be updating them in the future.
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 | 3.31s | c/ast/knight |
Compiled using COMPUTED_GOTOS=1 CFLAGS='-DKN_RECKLESS -DKN_USE_EXTENSIONS' make optimized ; See https://github.com/knight-lang/c/ast/README.md for details. |
Kotlin | 5.84s | kotlin/knight |
|
x86 Assembly | 6.29s | asm/knight |
Currently only has AST caching. (The C impl without caching runs 10.22s) |
Java | 6.99s | java/knight |
Requires a larger stack to prevent overflow; java -Xss515m was used. |
Rust | 10.06s | rust/target/release/knight |
Built with cargo build --release --no-default-features --features=unsafe-optimized . Still being improved. |
C# | 11.82s | csharp/bin/Release/netcoreapp2.1/<impl>/Knight |
|
C++ | 13.61s | cpp/knight |
Copiled using make optimized |
Go | 14.17s | go/knight/knight |
|
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 + 1 (% RAND 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:
; OUTPUT '> \' # print('> ', end='')
; = 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
The following is just a rough overview, and is not to be taken as authoritative; for exact details please see specs.md.
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
.
Tokens may follow directly one after another (eg 1a
is parsed as 1
and then a
), except in the following four cases (which must have whitespace or comments):
- 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
.
Additionally, while not technically whitesapce, (
, )
, and :
can be safely interpreted as whitespace as well. As such, expressions such as OUTPUT * a (IF b 3 b)
can be written as O*aIb 3b
.
Knight's pseudo-ENBF is as follows:
program := expr ;
expr
:= integer-literal
| string-literal
| identifier
| nullary
| unary expr
| binary expr expr
| ternary expr expr expr
| quaternary expr expr expr expr ;
integer-literal := DIGIT {DIGIT} ;
string-literal := `'` {NON_SINGLE} `'` | `"` {NON_DOUBLE} `"` ;
identifier := LOWER {LOWER | DIGIT};
nullary
:= '@'
| ('T' | 'F' | 'N' | 'P' | 'R'){UPPER} ;
unary
:= ':' | '!' | '~' | ',' | '[' | ']'
| ('B' | 'C' | 'Q' | 'D' | 'O' | 'L' | 'A'){UPPER} ;
binary
:= '+' | '-' | '*' | '/' | '%' | '^' | '<'
| '>' | '?' | '&' | '|' | ';' | '='
| 'W'{UPPER} ;
ternary := ('I' | 'G'){UPPER} ;
quaternary := 'S'{UPPER} ;
UPPER := [A-Z_] ;
LOWER := [a-z_] ;
DIGIT := [0-9] ;
NON_SINGLE := (? any character except single quote (`'`) *)
NON_DOUBLE := (? any character except double quote (`"`) *)
TRUE()
: Returns thetrue
value.FALSE()
: Returns thefalse
value.NULL()
: Returns thenull
value.@()
: Returns an empty list.PROMPT()
: Reads a line from stdin, deleting the newline; returnsnull
if stdin is done.RANDOM()
: Returns a random integer.:(value)
: A no-op, simply returnsvalue
.BLOCK(body)
: Returnsbody
without evaluating it.CALL(block)
: Executes a block's body.QUIT(code)
: Exits with the status code.DUMP(value)
: Dumps a debugging representation ofvalue
to stdout, then returns itOUTPUT(value)
: Printsvalue
to stdout. Prints a newline unless the value ends in\
(which wont be printed).LENGTH(value)
: Convertsvalue
to a list and gets its length.!(value)
: Convertsvalue
to a boolean and negates it.~(value)
: Convertsvalue
to a number and negates it.ASCII(value)
: Acts aschr
/ord
in other langs depending on its argument.,(value)
: Returns a list containing justvalue
.[(container)
: Returns the first character/element.](container)
: Returns the everything but the character/element.+(lhs, rhs)
: Addslhs
andrhs
; coercesrhs
tolhs
's type.-(lhs, rhs)
: Subtracts two integers.*(lhs, rhs)
: Multiplies two integers, or repeats lists and strings./(lhs, rhs)
: Divides two integers.%(lhs, rhs)
: Modulos two integers.^(lhs, rhs)
: Exponentiates two integers, or joins a list by a string.<(lhs, rhs)
: Sees iflhs
is less thanrhs
.>(lhs, rhs)
: Sees iflhs
is greater thanrhs
.?(lhs, rhs)
: Sees iflhs
is equal torhs
.&(lhs, rhs)
: Returnslhs
if falsey, otherwise evaluates rhs.|(lhs, rhs)
: Returnslhs
if truthy, otherwise evaluates rhs.;(lhs, rhs)
: Evaluateslhs
, then evaluates and returnsrhs
.=(variable, value)
: Assignsvalue
tovariable
.WHILE(cond, body)
: Executesbody
whilecond
is truthy.IF(cond, iftrue, iffalse)
: Executes and returnsiftrue
oriffalse
depending oncond
.GET(container, start, length)
: Returns a sublist/substring ofcontainer
in the range[start, start+length]
SET(container, start, length, replacement)
: Returns a new list/string with the given range ofcontainer
replaced byreplacement
.