F+ (Forth+)
Stack-based programming language made by me (TwoSpikes, 2022-2023) for studing purposes.
Done:
- Turing-completeness (
:
andif
operations) - Multi-line comment support (
/* comment */
) - Chars (
''
), strings (""
) and string postfixes (""b
and""c
) - repr(&str) and urepr(&str) functions (proper escaping like in C)
- Function predeclaration (linking)
- Negative numbers (
-1
,---50
) - Drop-in documentation
-
dump
subcommand (dump tokens) - Output debug information to stderr instead of stdout
- Command line arguments (see argc and argv)
- Global variables that switch debug information on/off
-
include
keyword - Nested label scopes
- Ability to stderr printing
- File reading
-
gettime
operation — returns time in nanoseconds as i128 - Colored debug output (Error (red) and Warning (yellow))
- Variables that switch debug information on/off through file
debug.tsconf
or through command line options - Unsigned 64-bit numbers (same type with signed numbers) (like
30u
) - One-line comment support (
// comment
)
In progress:
To do:
- Float numbers (same type with "normal" numbers) (F64)
- Float numbers with double precision (same type with "normal" numbers) (D64)
-
macro
keyword -
undump
subcommand -
com
andtoken-com
subcommand (for compilation) -
addsource
keyword - Vim and Emacs syntax highlighting (later)
- Colored output somehow through escaping
- C-like file reading
- Raw file reading
- File writing
- Self-hosted compiler (this thing was abandoned, maybe forever)
Standard library:
- max_2_I64 function ((b: I64, a: I64) -> I64)
- min_2_I64 function ((b: I64, a: I64) -> I64)
- I64ToStr function ((val: I64) -> (len: I64, I64[len]))
- U64ToStr function ((val: U64) -> (len: I64, I64[len]))
- F64ToStr function ((val: F64) -> (len: I64, I64[len]))
- D64ToStr function ((val: D64) -> (len: I64, I64[len]))
- StrToI64 function ((len: I64, arr: I64[len]) -> (I64))
- StrToU64 function ((len: I64, arr: I64[len]) -> (U64))
- StrToF64 function ((len: I64, arr: I64[len]) -> (F64))
- StrToD64 function ((len: I64, arr: I64[len]) -> (D64))
- I64ToU64 function ((val: I64) -> (U64))
- U64ToI64 function ((val: I64) -> (I64))
- I64ToF64 function ((val: I64) -> (F64))
- F64ToI64 function ((val: F64) -> (I64))
- U64ToF64 function ((val: U64) -> (F64))
- F64ToU64 function ((val: F64) -> (U64))
- I64ToD64 function ((val: I64) -> (D64))
- D64ToI64 function ((val: D64) -> (I64))
- U64ToD64 function ((val: U64) -> (D64))
- D64ToU64 function ((val: D64) -> (U64))
- F64ToD64 function ((val: F64) -> (D64))
- D64ToF64 function ((val: D64) -> (F64))
- veccpy function
Supported compilation modes:
- Simulation (default)
- C (not implemented yet)
$ make
If without Make:
$ cargo build --release
$ install ./target/release/fplus $PREFIX/bin
(I use $PREFIX because I am using Termux to create this project and Make does not work without $PREFIX)
Dependences:
- Cargo
- make (unneccesary)
Usage:
$ fplus SUBCOMMAND [OPTION]... [SOURCE]... -- [ARG]...
SUBCOMMAND (insensitive to register): {sim s} Simulate program {version ver v} Print version information and exit
{usage use u help h ?} info information Print help information and exit
{dump d} Dump the tokens of the program. {error e} Print error code and information about them
OPTION (insensitive to register): {-o --output} FILE dump output to FILE --lex-debug -lex-debug} show debug information during lexing --only-lex -only-lex} stop on lexing (for debugging purposes) --link-debug -link-debug} show debug information during linking --only-link -only-link} stop on linking (for debugging purposes) --link-debug-succed show [linking succed] --parse-debug -parse-debug} show debug information during parsing --parse-debug-state show State information during parsing --parse-debug-id show scope-id information during parsing --parse-debug-call show function calling information during parsing --parse-debug-string show string information during parsing --parse-debug-include show including information --parse-debug-include-adding TODO --parse-debug-include-succed TODO --sim-debug show debug information during simulation
--sim-debug-puts show debug information debore printing --max-include-level NUMBER
set max include level (now is 500) --disable-colors disable terminal colors
While building, you must be in fplus
directory (it is important)
F+, a stack-based interpreting programming language
written on Rust v.1.68.2
version: 0.1.0-5
download: https://github.com/TwoSpikes/fplus
2022-2023 @ TwoSpikes
This message will be shown with error
(or e
) subcommand:
errorcodes:
E0 Cannot open file
$ fplus sim main.tspol
ALSO KNOWN AS
$ fplus sim main.tspol --
F+ will simulate ./main.tspol file
$ fplus sim main.tspol -- a b c
F+ will simulate ./main.tspol file with a b c
command line arguments
$ fplus dump subc-dump.tspl -o subc-dump-file.txt
It will translate F+ code to tokens and write this to subc-dump-file.txt:
2:1:PUSHNTH
2:11:DROPNTH
2:19:NBROT
2:25:Push(2)
2:27:MUL
2:29:PLUS
3:1:DUMP
3:8:PUTS
3:13:Push(6969)
3:18:PRINT
-2:-2:Push(0)
If you will not provide -o
option, tokens will dump on the stdout instead (with debug information).
$ fplus sim hello-world.tspl -o hello-world-output.txt
It will write
Hello, World!
into the hello-world-output.txt
file.
If you will not provide -o
option, Hello, World! will be printed to the stdout instead.
Stack is a dynamic array of 64-bit integer numbers (from -9223372036854775808 (-2^63) to 9223372036854775807 (2^63-1)).
34 36
This program will push 34 and 36 on the stack.
Stack: [34, 36]
Pushnth takes one argument: (I64) a
First, provide this argument, and second, write pushnth
If stack is [10, 11, 12, 13, 14],
After providing argument (3), it will be [10, 11, 12, 13, 14, 3]
It will consume argument and take third element from right counting from 0 (it is 11).
Argument | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Index (from right) | 4 | 3 | 2 | 1 | 0 | NA |
Element (from right) | 14 | 13 | 12 | 11 | 10 | NA |
Element (from left) | 10 | 11 | 12 | 13 | 14 | NA |
34 36
0 pushnth
Stack: [34, 36, 36]
Consumes 2 arguments: (I64) a, (I64) b
Returns: (I64)(a + b)
34 35 +
Stack: [69]
To sum up more than 2 numbers:
1 1 + 1 +
Stack: [3]
To substract, use Negative numbers:
69 -21 +
Stack: [48]
Consumes 2 arguments: (I64) a, (I64) b
Returns: (I64)(a * b)
2 3 *
Stack: [6]
-49 2 *
Stack: [-98]
Consumes 2 arguments: (I64) a, (I64) b
Returns: (I64)(a / b)
2 / 3
Stack: [0]
48 -2 /
Stack: [-24]
Consumes 2 arguments: (I64) a, (I64) b
Returns: (boolean)(a < b)
34 35 <
Stack: [1] because (34 < 35 <=> true)
35 34 <
Stack: [0] because (35 < 34 <=> false)
34 34 <
Stack: [0] because (34 < 34 <=> false)
Consumes 2 arguments: (I64) a, (I64) b
Returns: (boolean)(a = b)
34 34 =
Stack: [1] because (34 = 34 <=> true)
35 34 =
Stack: [0] because (35 = 34 <=> false)
Consumes one argument: (boolean) a
Returns: (boolean) !a
0 !
Stack: [1] because (!0 <=> 1)
35 34 <
!
Stack: [1] because
- 35 < 34 <=> false
- !Ans <=> true
1 ! !
Stack: [1] because
- !1 <=> 0
- !Ans <=> 1
69 ! !
Stack: [1] because
- !69 <=> 0
- !Ans <=> 1
Consumes 2 arguments: (boolean) a, (boolean) b
Returns: (boolean)(a | b)
0 1 |
Stack: [1] because (0 | 1 <=> true)
0 0 |
Stack: [0] because (0 | 0 <=> false)
1 1 |
Stack: [1] because (1 | 1 <=> true)
Same will happen if all 1's will change to bigger numbers because Or
anyways will cast arguments to boolean.
$ ./target/release/fplus sim main.tspl -- a b c
This option will provide main.tspl
, a
, b
and c
command line arguments.
This is how to get access to him:
Consumes 0 arguments.
Returns number of provided command line arguments.
argc
Stack: [4]
Consumes 1 argument: (I64) a
Returns String with a-th command line argument.
2 argv
Stack: [98, 1] or "b"
All this programs will work like that only if you provide a
, b
and c
command line arguments.
Consumes 1 String.
Return 1 String: file's content with given name.
"main.tspl" read
Stack: [108, 112, 115, 116, 46, 110, 105, 97, 109, 9, 16] or '"main.tspl" read'
Does nothing. Just need for debugging purposes.
Consumes 1 argument: (I64) a
Print a
without converting it from ASCII to string.
Need for debugging purposes.
69 dump
Stdout is empty.
Stderr: "69"
Exit the program
Consumes 1 argument: (I64) exitcode
Exits the program. It does not need to return anything.
0 exit
Stderr: '[Simulation of "main.tspl" succed]'
1 exit
Stderr: '[Simulation of "main.tspl" finished with exit code 1]'
Consumes 1 argument: (I64) chr
Returns nothing.
Prints chr as ASCII to stdout.
69 putc
Stdout: "E"
I will write how to make String later.
65 66 67 3 putsln
Stdout: "abc"
"Hello, World!" putsln
Stdout: "Hello, World!"
String is a pointer to a data
variable that contains the bytes (i64) and the string length at the end.
"Hello, World!"
Stack: [13]
You can dereference it like this:
include std.tsplh
pub fn main
"Hello, world!" ->vec ???
Stdout: [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 13]
You can use simple escaping like in C like this:
Code | Stdout |
---|---|
'\r' | carriage return |
'\n' | new string |
'\t' | tab |
'\\' | backslash |
'\'' | quote |
'\"' | double quote |
'\x{val}' (val is 00-FF) | byte (for ANSI commands) |
Strings are inverted at the stack and has its length at the end.
Empty string:
""
Stack: [0]
First, you need to create a function
We will call it foo
:
Syntax:
fn {function name}
fn foo
main
function is the start of the program
If you don't have main
function then program will start from first line of file you given.
You cannot have the main
function (maybe later I will implement it) in files that you included.
You will define it like this:
fn main
With the previous function we created: foo
:
fn foo
fn main
You can call your function by just writing its name:
fn foo
fn main
foo
But this program will go into the infinite loop (because it is returing to main
function) so you have to exit it somewhere.
fn foo
1 exit
fn main
foo
Let's print string as indicator that we called it:
fn foo
"foo called" putsln
1 exit
fn main
foo
Okay, now let's move on!
Actually you called it and save your next operation address to jump to it at the end of function.
Let's check:
fn foo
"foo called" putsln
dump
1 exit
fn main
foo
Stdout: "foo called\n16\n"
This address is too large because of our string.
Every char is the 1 operation (and the length of string too).
We do not need it now so I will show how to not do that:
fn foo
"foo called" putsln
1 exit
fn main
#foo
Yeah, you just need to put #
sign before function name
You can get function addresses by pushing :
character before its name
Let's get address of foo
function
fn foo
"foo called" putsln
1 exit
fn main
:foo
Stack: [0] because foo
starts at first operation
You cannot access fns outside of its scope.
fn a {
fn a.1
fn a.2
fn a.3
}
fn main
:a /* 0 */
:a.1 /* Error: label is private */
They are have some bug so do not use it before this message disappear.
You can provide arguments to function, placing them before calling:
fn foo
dump dump dump
0 exit
fn main
1 2 3 #foo
Stdout: "3\n2\n1\n"
fn foo
dump dump dump
0 exit
fn main
:#foo(1 2 3)
Stdout: "3\n2\n1\n"
/* this is a comment */
/* this is a nested comment
/* yeah, nested comment */
*/
Yes, the space before and after /*
is neccesary
// this is the one-line comment
// // nested
// //nested even w/o space
// /* multi-line in one-line */
// /*w/o space again*/
Yes, the space before and after //
is neccesary
"abc"
Stack: [3]
It is pointing to the last data
array element (where the length of the string is located. It is equal to the length of the string now because there is only one string in the program for now)
"abc" ->
Stack: [3]
Bc they are equal
"abc" -1 + ->
Stack: [97] because 'a' is 97
Located in std.tsplh.
Places a whole string to the stack.
include std.tsplh
"abc" ->vec
Stack: [95, 96, 97, 3]
['a 'b 'c]
Stack: [3]
You can dereference it by [->vec
](#Dereference a string)`
include std.tsplh
['a 'b 'c] ->vec