Compare commits

...

96 Commits

Author SHA1 Message Date
Mattia Giambirtone 096bfaf662 More changes to the README because yes 2022-08-17 21:10:55 +02:00
Mattia Giambirtone fd90d08688 Fixed wording 2022-08-17 20:55:00 +02:00
Mattia Giambirtone bcf30213f5 Fixed typos 2022-08-17 20:54:35 +02:00
Mattia Giambirtone 55966ba93c Updated docs and README 2022-08-17 20:52:23 +02:00
Mattia Giambirtone 47a6f16664 Even more cleanup. Added pretty colors to the VM's debugger 2022-08-17 20:40:34 +02:00
Mattia Giambirtone 77fd5931fa Further clean up 2022-08-17 19:31:27 +02:00
Mattia Giambirtone f7733d925f Cleaned up and renamed some things 2022-08-17 19:23:11 +02:00
Mattia Giambirtone 19a089f4a2 Move towards unboxed types in the Peon VM 2022-08-17 17:31:15 +02:00
Mattia Giambirtone fc14cfec2d Added chained imports test 2022-08-16 14:23:58 +02:00
Mattia Giambirtone 9a19fad1ad More fixes to the import system and incremental compilation 2022-08-16 13:37:09 +02:00
Mattia Giambirtone 3636c74a6a WIP for importing the same module multiple times and other failed fixes 2022-08-16 13:11:09 +02:00
Mattia Giambirtone 13b432b2d2 Basic support for actual incremental compilation 2022-08-16 12:20:17 +02:00
Mattia Giambirtone 26c55c403e Removed debugging echo 2022-08-15 22:34:08 +02:00
Mattia Giambirtone 626375bc1f Added all missing comparison operators and fixed error reporting system 2022-08-15 22:15:06 +02:00
Mattia Giambirtone 5dc8ce437c Expanded comparison test and fixed some typos/mistakes 2022-08-15 20:09:54 +02:00
Mattia Giambirtone 70a5f9dcd3 Fixed issues with '>', thanks Nim... 2022-08-15 19:52:06 +02:00
Mattia Giambirtone 0861135e7f Initial work on a stdlib of sorts, added comparison operators and refactored tests 2022-08-15 19:07:37 +02:00
Mattia Giambirtone 7c8ec4bc6c Deleted standalone fibonacci test 2022-08-15 17:20:21 +02:00
Mattia Giambirtone 7ef5b4dfbf Fixed some issues with strings and added debug print to fibonacci test 2022-08-15 17:20:09 +02:00
Mattia Giambirtone edef50deca Made exception handling in main.nim module-aware and did some minor refactoring 2022-08-15 11:46:24 +02:00
Mattia Giambirtone 39a84182b0 Fixed name resolution error in findByName 2022-08-14 19:51:12 +02:00
Mattia Giambirtone c85fff8f67 Initial tests for an import system 2022-08-14 18:37:17 +02:00
Mattia Giambirtone f50dd66741 Minor documentation additions 2022-08-04 17:48:56 +02:00
Mattia Giambirtone da651355b9 Added a few more tests 2022-08-01 13:16:07 +02:00
Mattia Giambirtone a370961218 Removed debug print 2022-08-01 11:36:23 +02:00
Mattia Giambirtone 0fdddbfda4 Fixed fib to work again 2022-08-01 11:36:00 +02:00
Mattia Giambirtone 3fba30b8ac Fixed inference of unary and binary operators 2022-08-01 11:30:44 +02:00
Mattia Giambirtone cc49cb98a6 Updated the manual and changed the syntax for foreach loops 2022-08-01 11:03:49 +02:00
Mattia Giambirtone 67b44dbfc9 Added support for GenericPrint opcode 2022-08-01 10:44:38 +02:00
Mattia Giambirtone ff0ae8fcba Various fixes to frames, recursion, and more. Removed stack bottom from CFI data. Added comparison opcode for fib test as well as a clock opcode 2022-08-01 10:38:51 +02:00
Mattia Giambirtone b4628109ce Fixed a bug with nested scopes 2022-07-31 16:40:47 +02:00
Mattia Giambirtone 33066d3b9b Fixed bug with calling a call 2022-07-31 16:09:22 +02:00
Mattia Giambirtone da2cfefe75 Removed the need for parentheses around control flow and looping statements, enforced braces instead. Got rid of the old C-style for loop 2022-07-16 13:21:40 +02:00
Mattia Giambirtone b40275b52f Added Compiler.check(), made type constraints mandatory in generics, reverted '\n' being converted to a semicolon, minor refactoring 2022-07-16 13:04:00 +02:00
Mattia Giambirtone 2072f34d4c BYE BYE SEMICOLONS! 2022-07-15 19:48:32 +02:00
Mattia Giambirtone 60028ed664 Fixed issues when debugSerializer == true 2022-07-10 16:31:30 +02:00
Mattia Giambirtone 9cedc72f68 Fixed error message when returning values from void functions 2022-07-10 15:10:01 +02:00
Mattia Giambirtone 70c839f5b8 Updated closures test 2022-07-10 15:07:57 +02:00
Mattia Giambirtone 39d1eab234 Temporary fix for not closing over function arguments 2022-07-10 15:07:44 +02:00
Mattia Giambirtone 13cc641e7b Initial work on closures. Seems to be working 2022-07-10 14:50:08 +02:00
Mattia Giambirtone 6116c127c6 Moved for peon types to its own module, added support for negating primitives in the compiler and for floats in the VM, added some more docs to the VM and restored the old empty-jump mechanism for closure declaration. Minor changes to endScope 2022-07-10 13:19:57 +02:00
Mattia Giambirtone e38610fdbd Updated Makefile 2022-07-09 16:25:19 +02:00
Mattia Giambirtone 7ac322e58c Updated .gitignore 2022-07-09 13:37:51 +02:00
Mattia Giambirtone f32a45c8d8 Added test for scopes 2022-07-09 13:37:16 +02:00
Mattia Giambirtone 3f5f514259 Fixed some issued with scoping and globals 2022-07-09 13:36:29 +02:00
Mattia Giambirtone cc0aab850e Simplified calling convention, added for PeonObject, added some comments, fixed bug with StoreVar in stack frames, fixed issues with functions assigned to variables, changed the way closures are emitted so that empty jumps are no longer needed 2022-07-09 12:47:53 +02:00
Mattia Giambirtone 2c6325d33b Initial broken work on generics 2022-06-21 20:18:53 +02:00
Mattia Giambirtone 985ceed075 Initial unfinished work on generic functions 2022-06-20 09:39:54 +02:00
Mattia Giambirtone 95880b7ba2 Updated .gitignore, slightly edited README and added some more tests 2022-06-19 14:45:38 +02:00
Mattia Giambirtone 6f60f76270 More fixes for assigning builtin functions to variables 2022-06-14 23:34:42 +02:00
Mattia Giambirtone b974ba8ba3 Removed extra bloat 2022-06-14 22:46:55 +02:00
Mattia Giambirtone a361f91950 Updated .gitignore 2022-06-14 22:46:25 +02:00
Mattia Giambirtone d3b9418fea Added some missing files 2022-06-14 22:45:41 +02:00
Mattia Giambirtone e32b8e258f Initial work on generics and fixed bugs in the parser with stropped operator names 2022-06-14 22:45:32 +02:00
Mattia Giambirtone 4591e5ca0e Added built-in operators as a single instruction (src/peon/stdlib/arithmetics.pn is now fully functional) 2022-06-14 18:10:13 +02:00
Mattia Giambirtone 73381513f9 Minor fixes 2022-06-14 12:14:58 +02:00
Mattia Giambirtone 5d572386a3 Initial work on pragma handling (+ some parser fixes). Changed the way the lexer handles indentation and made tabs illegal. Added instructions for some operations on primitive types, removed file argument from serializer 2022-06-14 12:12:56 +02:00
Mattia Giambirtone d241333047 Initial work on closures (again?) 2022-06-13 17:28:05 +02:00
Mattia Giambirtone e3ab2fbdb6 Fixed issues with returning/calling function objects 2022-06-13 15:44:53 +02:00
Mattia Giambirtone 02f1f8a54d Various style fixes with nil checks, added PushC opcode, added support for calling function objects 2022-06-13 15:04:53 +02:00
Mattia Giambirtone 11b15abc01 More work on pragmas, returning functions now works 2022-06-08 16:07:08 +02:00
Mattia Giambirtone dac0cca1bc Initial experimental support for parsing pragmas 2022-06-07 11:23:08 +02:00
Mattia Giambirtone aed0f6e8f2 Initial work on type declarations 2022-06-02 14:23:05 +02:00
Mattia Giambirtone 1d228c6310 Fixed logic bug within if/else construct 2022-06-02 12:19:18 +02:00
Mattia Giambirtone dfa42d994b Fixed if/else 2022-06-02 12:05:22 +02:00
Mattia Giambirtone 72ba5c7528 Updated config.nim 2022-06-02 11:48:17 +02:00
Mattia Giambirtone e9cb3fca89 Fixed local variables 2022-06-02 11:45:27 +02:00
Mattia Giambirtone 6b314f2882 Fixed nested calls 2022-06-02 10:19:34 +02:00
Mattia Giambirtone f2f0fae36f Fixed inverse parameter ordering (whoops) 2022-06-02 01:50:06 +02:00
Mattia Giambirtone 099f733db6 Initial work on a two-stack design 2022-06-02 01:33:56 +02:00
Mattia Giambirtone f8ab292c27 Fixed peon calling convention and various errors with function calls 2022-05-30 22:06:15 +02:00
Mattia Giambirtone 369dff7da2 Removed debugging print (oops) 2022-05-30 12:33:30 +02:00
Mattia Giambirtone df105125c4 Various fixes to stack frame alignment and added incremental compilation to REPL 2022-05-30 12:32:24 +02:00
Mattia Giambirtone 0887dab246 Added lastPop field to VM 2022-05-30 09:32:15 +02:00
Mattia Giambirtone 9f126845fc Initial work on function calls 2022-05-30 09:29:03 +02:00
Mattia Giambirtone 50b7b56feb Fixed some segfaults 2022-05-29 23:01:36 +02:00
Mattia Giambirtone 9dacda4009 Various fixes and stack frame changes 2022-05-29 17:04:19 +02:00
Mattia Giambirtone b0515d3573 Refactored the type system which no longer relies on AST node objects. Added types for ref, ptr and mutable types 2022-05-29 15:54:01 +02:00
Mattia Giambirtone a8345d065a Removed bytecode file 2022-05-29 15:01:15 +02:00
Mattia Giambirtone 74c58fbe5e Updated .gitignore 2022-05-29 14:57:37 +02:00
Mattia Giambirtone 9c20032690 Fixed .gitignore (maybe??) 2022-05-29 14:54:22 +02:00
Mattia Giambirtone 57313235a9 Added tests directory 2022-05-29 14:52:47 +02:00
Mattia Giambirtone bf9b9389ce Some work for heap vars, wip fixes for higher-order functions 2022-05-27 14:01:57 +02:00
Mattia Giambirtone 71c05ec1bf Fixed closure variables and debugger output with nested CFI data (also removed unneeded peon files) 2022-05-26 18:32:02 +02:00
Mattia Giambirtone 0f0a442578 Fixed issues with stack frames when returning from functions 2022-05-25 14:38:40 +02:00
Mattia Giambirtone e15b6a4915 Fixed recursion error inside inferType 2022-05-25 14:17:58 +02:00
Mattia Giambirtone 5bf5c6d3fd Fixed variable declarations not compiling in some cases 2022-05-25 12:15:45 +02:00
Mattia Giambirtone 990b54fa3e Made main.nim a bit nicer with command-line options 2022-05-25 11:49:21 +02:00
Mattia Giambirtone a6d22f740d Updated .gitignore 2022-05-25 11:43:02 +02:00
Mattia Giambirtone be4c2500ac Added info about CFI section and made minor changes to README 2022-05-25 11:36:12 +02:00
Mattia Giambirtone 48d1c3fc8c Initial work on CFI-like functionality for better debugging 2022-05-24 22:26:45 +02:00
Mattia Giambirtone dbeae16dc4 Various fixes to matchImpl. Variables can now shadow functions, but not other variables 2022-05-24 10:23:34 +02:00
Mattia Giambirtone 15f412bcac Updated config.nim and changed mechanism for finding operators (also added binary operator lookup) 2022-05-24 09:56:11 +02:00
Mattia Giambirtone b02e2c3d02 Removed old binary 2022-05-23 23:34:33 +02:00
Mattia Giambirtone 2ff70f912d Merge branch 'master' of https://git.nocturn9x.space/nocturn9x/peon 2022-05-23 23:25:21 +02:00
Mattia Giambirtone 630de7a30c Fixed typo 2022-05-23 23:15:09 +02:00
37 changed files with 3886 additions and 1688 deletions

7
.gitignore vendored
View File

@ -2,6 +2,7 @@
nimcache/
nimblecache/
htmldocs/
*.pbc # Peon bytecode files
stdin.pbc
tests.pbc
tests/*.pbc # Peon bytecode files
*.pbc
bin/
.vscode/

View File

@ -1,5 +1,5 @@
run:
nim --hints:off --warnings:off r src/test.nim
repl:
nim --hints:off --warnings:off r src/main.nim
pretty:
nimpretty src/*.nim src/backend/*.nim src/frontend/*.nim src/frontend/meta/*.nim src/memory/*.nim src/util/*.nim

View File

@ -24,11 +24,17 @@ Peon is a simple, functional, async-first programming language with a focus on c
## Credits
- Araq, for creating the amazing language that is [Nim](https://nim-lang.org)
- Araq, for creating the amazing language that is [Nim](https://nim-lang.org) (as well as all of its contributors!)
- Guido Van Rossum, aka the chad who created [Python](https://python.org) and its awesome community and resources
- The Nim community and contributors, for making Nim what it is today
- Bob Nystrom, for his amazing [book](https://craftinginterpreters.com) that inspired me
and taught me how to actually make a programming language
and taught me how to actually make a programming language (kinda, I'm still very dumb)
- [Njsmith](https://vorpus.org/), for his awesome articles on structured concurrency
- All the amazing people in the [r/ProgrammingLanguages](https://reddit.com/r/ProgrammingLanguages) subreddit and its [Discord](https://discord.gg/tuFCPmB7Un) server
- [Art](https://git.nocturn9x.space/art) <3
- Everyone to listened (and still listens to) me ramble about compilers, programming languages and the likes (and for giving me ideas and testing peon!)
- ... More? (I'd thank the contributors but it's just me :P)
- Me! I guess
## Project State
@ -52,28 +58,52 @@ In no particular order, here's a list of stuff that's done/to do (might be incom
Toolchain:
- Tokenizer (with dynamic symbol table) [x]
- Parser (with support for custom operators, even builtins) [x]
- Compiler [ ] (Work in Progress)
- VM [ ] (Work in Progress)
- Bytecode (de-)serializer [x]
- Static code debugger [x]
- Tokenizer (with dynamic symbol table) [X]
- Parser (with support for custom operators, even builtins) [X]
- Compiler [ ] -> Being written
- VM [ ] -> Being written
- Bytecode (de-)serializer [X]
- Static code debugger [X]
- Runtime debugger/inspection tool [ ]
Type system:
- Custom types [ ]
- Intrinsics [x]
- Generics [ ] (Work in Progress)
- Function calls [ ] (Work in Progress)
- Intrinsics [X]
- Generics [ ] -> WIP
- Functions [X]
Misc:
- Pragmas [ ] (Work in Progress)
- Pragmas [ ] -> WIP (Some pragmas implemented)
- Attribute resolution [ ]
- method-like call syntax without actual methods (dispatched at compile-time) [ ]
- ... More?
## Features
Aside from the obvious basics like exceptions, a true import system with namespaces and a standard library (duh), here's a
random list of high-level features I plan peon to have and that I think are kinda neat:
- References not being nullable by default (must use `#pragma[nullable]`)
- Easy C/Nim interop via FFI
- C/C++ backend
- Nim backend (maybe)
- Structured concurrency
- Capability-based programming (i.e. functions are passed objects to act on the real world)
- Parametric Polymorphism (with untagged typed unions and maybe interfaces)
- Simple OOP (with multiple dispatch!)
- RTTI, with methods that dispatch at runtime based on the true type of a value (maybe)
- Limited compile-time evaluation (embed the Peon VM in the C/C++/Nim backend and use that to execute peon code at compile time)
## The name
The name for peon comes from my and [Productive2's](https://git.nocturn9x.space/prod2) genius and is a result of shortening
the name of the fastest animal on earth: the **Pe**regrine Falc**on**. I guess I wanted this to mean peon will be blazing fast
the name of the fastest animal on earth: the **Pe**regrine Falc**on**. I guess I wanted this to mean peon will be blazing fast
# Peon needs you.
No, but really. I need help. This project is huge and (IMHO) awesome, but there's a lot of non-trivial work to do and doing
it with other people is just plain more fun and rewarding. If you want to get involved, definitely try [contacting](https://nocturn9x.space/contact) me
or open an issue/PR!

View File

@ -16,25 +16,27 @@ A peon bytecode dump contains:
- Debugging information
- File and version metadata
## Encoding
### Header
## File Headers
A peon bytecode file starts with the header, which is structured as follows:
- The literal string `PEON_BYTECODE`
- A 3-byte version number (the major, minor and patch versions of the compiler that generated the file as per the SemVer versioning standard)
- A 3-byte version number (the major, minor and patch version numbers of the compiler that generated the file)
- The branch name of the repository the compiler was built from, prepended with its length as a 1 byte integer
- The full commit hash (encoded as a 40-byte hex-encoded string) in the aforementioned branch from which the compiler was built from (particularly useful in development builds)
- The commit hash (encoded as a 40-byte hex-encoded string) in the aforementioned branch from which the compiler was built from (particularly useful in development builds)
- An 8-byte UNIX timestamp (with Epoch 0 starting at 1/1/1970 12:00 AM) representing the exact date and time of when the file was generated
- A 32-byte, hex-encoded SHA256 hash of the source file's content, used to track file changes
### Line data section
## Debug information
The line data section contains information about each instruction in the code section and associates them
The following segments contain extra information and metadata about the compiled bytecode to aid debugging, but they may be missing
in release builds.
### Line data segment
The line data segment contains information about each instruction in the code segment and associates them
1:1 with a line number in the original source file for easier debugging using run-length encoding. The section's
size is fixed and is encoded at the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer). The data
in this section can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L28), which is quoted
in this segment can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L28), which is quoted
below:
```
[...]
@ -54,19 +56,41 @@ below:
[...]
```
### Constant section
### CFI segment
The constant section contains all the read-only values that the code will need at runtime, such as hardcoded
The CFI segment (where CFI stands for **C**all **F**rame **I**nformation), contains details about each function in
the original file. The segment's size is fixed and is encoded at the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer).
The data in this segment can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L41), which is quoted
below:
```
[...]
## cfi represents Call Frame Information and encodes the following information:
## - Function name
## - Argument count
## - Function boundaries
## The encoding for CFI data is the following:
## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer)
## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer)
## - After that follows the argument count as a 1 byte integer
## - Lastly, the function's name (optional) is encoded in ASCII, prepended with
## its size as a 2-byte integer
[...]
```
## Constant segment
The constant segment contains all the read-only values that the code will need at runtime, such as hardcoded
variable initializers or constant expressions. It is similar to the `.rodata` section of Assembly files, although
the implementation is different. Constants are encoded as a linear sequence of bytes with no type information about
them whatsoever: it is the code that, at runtime, loads each constant (whose type is determined at compile time) onto
the stack accordingly. For example, a 32 bit integer constant would be encoded as a sequence of 4 bytes, which would
then be loaded by the appropriate `LoadInt32` instruction at runtime. The section's size is fixed and is encoded at
the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer). The constant section may be empty, although in
real-world scenarios it's unlikely that it would.
then be loaded by the appropriate `LoadInt32` instruction at runtime. The segment's size is fixed and is encoded at
the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer). The constant segment may be empty, although in
real-world scenarios likely won't.
### Code section
## Code segment
The code section contains the linear sequence of bytecode instructions of a peon program. It is to be read directly
and without modifications. The section's size is fixed and is encoded at the beginning as a sequence of 3 bytes
The code segment contains the linear sequence of bytecode instructions of a peon program. It is to be read directly
and without modifications. The segment's size is fixed and is encoded at the beginning as a sequence of 3 bytes
(i.e. a single 24 bit integer).

View File

@ -33,20 +33,25 @@ to happen, we need:
- C/Nim FFI
- A package manager
Peon ~~steals~~ borrows many ideas from Python and Nim (the latter being the language peon itself is written in).
Peon ~~steals~~ borrows many ideas from Python, Nim (the the language peon itself is written in), C and many others.
## Peon by Example
Here follow a few examples of peon code to make it clear what the end product should look like
Here follow a few examples of peon code to make it clear what the end product should look like. Note that
not all examples represent working functionality and some of these examples might not be up to date either.
For somewhat updated tests, check the [tests](../tests/) directory.
### Variable declarations
```
var x = 5; # Inferred type is int64
var y = 3'u16; # Type is specified as uint16
x = 6; # Works: type matches
x = 3.0; # Cannot assign float64 to x
var x = 3.14; # Cannot re-declare x
var x = 5; # Inferred type is int64
var y = 3'u16; # Type is specified as uint16
x = 6; # Works: type matches
x = 3.0; # Error: Cannot assign float64 to x
var x = 3.14; # Error: Cannot re-declare x
const z = 6.28; # Constant declaration
let a = "hi!"; # Cannot be reassigned/mutated
var b: int32 = 5; # Explicit type declaration
```
__Note__: Peon supports [name stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)), meaning
@ -57,7 +62,7 @@ __Note__: Peon supports [name stropping](https://en.wikipedia.org/wiki/Stropping
```
fn fib(n: int): int {
if (n < 3) {
if n < 3 {
return n;
}
return fib(n - 1) + fib(n - 2);
@ -78,7 +83,7 @@ type Foo = object { # Can also be "ref object" for reference types (managed auto
### Operator overloading
```
operator `+`(a, b: Foo) {
operator `+`(a, b: Foo): Foo {
return Foo(fieldOne: a.fieldOne + b.fieldOne, fieldTwo: a.fieldTwo + b.fieldTwo);
}
@ -102,14 +107,14 @@ __Note__: Operators can be called as functions too. Just wrap their name in back
```
__Note__: Code the likes of `a.b()` is desugared to `b(a)` if there exists a function `b` whose
signature is compatible with the value of of `a` (assuming `a` doesn't have a `b` field, in
signature is compatible with the value of `a` (assuming `a` doesn't have a field named `b`, in
which case the attribute resolution takes precedence)
### Generic declarations
### Generics
```
fn genericSum[T](a, b: T): T { # Note: "a, b: T" means that both a and b are of type T
fn genericSum[T: Number](a, b: T): T { # Note: "a, b: T" means that both a and b are of type T
return a + b;
}
@ -120,16 +125,28 @@ genericSum(3.14, 0.1);
genericSum(1'u8, 250'u8);
```
#### Multiple generics
#### More generics!
```
fn genericSth[T, K](a: T, b: K) { # Note: no return type == void function!
fn genericSth[T: someInterface, K: someInterface2](a: T, b: K) { # Note: no return type == void function!
# code...
}
genericSth(1, 3.0);
```
#### Even more generics?
```
type Box*[T: SomeNumber] = object {
num: T;
}
var boxFloat = Box[float](1.0);
var boxInt = Box[int](1);
```
__Note__: The `*` modifier to make a name visible outside the current module must be put
__before__ generics declarations, so only `fn foo*[T](a: T) {}` is the correct syntax
@ -138,7 +155,7 @@ __before__ generics declarations, so only `fn foo*[T](a: T) {}` is the correct s
```
fn someF: int; # Semicolon, no body!
someF(); # This works!
print(someF()); # This works!
fn someF: int {
return 42;
@ -149,7 +166,7 @@ fn someF: int {
```
generator count(n: int): int {
while (n > 0) {
while n > 0 {
yield n;
n -= 1;
}
@ -175,12 +192,13 @@ coroutine req(url: string): string {
coroutine main(urls: list[string]) {
pool = concur.pool(); # Creates a task pool: like a nursery in njsmith's article
for (var i = 0; i < urls.len(); i += 1) {
pool.spawn(req, urls[i]);
foreach url in urls {
pool.spawn(req, urls);
}
# The pool has internal machinery that makes the parent
# task wait until all child exit! When this function
# returns, ALL child tasks will have exited somehow
# returns, ALL child tasks will have exited somehow.
# Exceptions and return values propagate neatly, too.
}

BIN
peon

Binary file not shown.

View File

@ -1,54 +0,0 @@
# Copyright 2022 Mattia Giambirtone & All Contributors
#
# 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.
type
ObjectKind* = enum
## Enumeration of Peon
## types
Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Function, CustomType,
Nil, Nan, Bool, Inf
PeonObject* = object
## A generic Peon object
case kind*: ObjectKind:
of Bool:
boolean*: bool
of Inf:
positive*: bool
of Byte:
`byte`*: byte
of Int8:
tiny*: uint8
of UInt8:
uTiny*: uint8
of Int16:
short*: int16
of UInt16:
uShort*: uint16
of Int32:
`int`*: int32
of UInt32:
uInt*: uint32
of Int64:
long*: int64
of UInt64:
uLong*: uint64
of Nil, Nan:
discard
of CustomType:
fields*: seq[PeonObject]
else:
discard # TODO

View File

@ -11,35 +11,59 @@
# 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.
## The Peon runtime environment
import types
import strformat
{.push checks:off.} # The VM is a critical point where checks are deleterious
import std/monotimes
import std/math
import ../config
import ../frontend/meta/bytecode
import ../util/multibyte
import strutils
when debugVM:
import std/strformat
import std/terminal
type
PeonVM* = ref object
## The Peon Virtual Machine
stack: seq[PeonObject]
ip: int # Instruction pointer
cache: array[6, PeonObject] # Singletons cache
## The Peon Virtual Machine.
## Note how the only data
## type we handle here is
## a 64-bit unsigned integer:
## This is to allow the use
## of unboxed primitive types.
## For more complex types, the
## value represents a pointer to
## some stack- or heap-allocated
## object. The VM has no concept
## of type by itself and it relies
## on the compiler to produce the
## correct results
ip: uint64 # Instruction pointer
chunk: Chunk # Piece of bytecode to execute
frames: seq[int] # Stores the initial index of stack frames
heapVars: seq[PeonObject] # Stores variables that do not have stack semantics (i.e. "static")
calls: seq[uint64] # Our call stack
operands: seq[uint64] # Our operand stack
cache: array[6, uint64] # Singletons cache
frames: seq[uint64] # Stores the bottom of stack frames
closedOver: seq[uint64] # Stores variables that do not have stack semantics
results: seq[uint64] # Stores function's results (return values)
proc initCache*(self: PeonVM) =
## Initializes the VM's
## singletons cache
self.cache[0] = PeonObject(kind: Nil)
self.cache[1] = PeonObject(kind: Bool, boolean: true)
self.cache[2] = PeonObject(kind: Bool, boolean: false)
self.cache[3] = PeonObject(kind: ObjectKind.Inf, positive: true)
self.cache[4] = PeonObject(kind: ObjectKind.Inf, positive: false)
self.cache[5] = PeonObject(kind: ObjectKind.Nan)
self.cache[0] = 0x0 # Nil
self.cache[1] = 0x1 # True
self.cache[2] = 0x2 # False
self.cache[3] = 0x3 # Positive inf
self.cache[4] = 0x4 # Negative inf
self.cache[5] = 0x5 # NaN
proc newPeonVM*: PeonVM =
@ -48,66 +72,114 @@ proc newPeonVM*: PeonVM =
new(result)
result.ip = 0
result.frames = @[]
result.stack = newSeq[PeonObject]()
result.calls = newSeq[uint64]()
result.operands = newSeq[uint64]()
result.initCache()
## Getters for singleton types (they are cached!)
# Getters for singleton types
{.push inline.}
proc getNil*(self: PeonVM): PeonObject = self.cache[0]
proc getNil*(self: PeonVM): uint64 = self.cache[2]
proc getBool*(self: PeonVM, value: bool): PeonObject =
proc getBool*(self: PeonVM, value: bool): uint64 =
if value:
return self.cache[1]
return self.cache[2]
return self.cache[0]
proc getInf*(self: PeonVM, positive: bool): PeonObject =
proc getInf*(self: PeonVM, positive: bool): uint64 =
if positive:
return self.cache[3]
return self.cache[4]
proc getNan*(self: PeonVM): PeonObject = self.cache[5]
## Stack primitives. Note: all stack accessing that goes
## through the get/set wrappers is frame-relative, meaning
## that the index is added to the current stack frame's
## bottom to obtain an absolute stack index.
proc push(self: PeonVM, obj: PeonObject) =
## Pushes a Peon object onto the
## stack
self.stack.add(obj)
proc getNan*(self: PeonVM): uint64 = self.cache[5]
proc pop(self: PeonVM): PeonObject =
## Pops a Peon object off the
## stack, decreasing the stack
## pointer. The object is returned
return self.stack.pop()
# Thanks to nim's *genius* idea of making x !> y a template
# for y < x (which by itself is fine) together with the fact
# that the order of evaluation of templates with the same
# expression is fucking stupid (see https://nim-lang.org/docs/manual.html#order-of-evaluation
# and https://github.com/nim-lang/Nim/issues/10425 and try not to
# bang your head against the nearest wall), we need a custom operator
# that preserves the natural order of evaluation
proc `!>`[T](a, b: T): auto {.inline.} =
b < a
proc peek(self: PeonVM): PeonObject =
## Returns the Peon object at the top
## of the stack without consuming
## it
return self.stack[^1]
proc `!>=`[T](a, b: T): auto {.inline, used.} =
b <= a
proc get(self: PeonVM, idx: int): PeonObject =
# Stack primitives. Note: all accesses to the call stack
# that go through the getc/setc wrappers is frame-relative,
# meaning that the index is added to the current stack frame's
# bottom to obtain an absolute stack index
proc push(self: PeonVM, obj: uint64) =
## Pushes a value object onto the
## operand stack
self.operands.add(obj)
proc pop(self: PeonVM): uint64 =
## Pops a value off the
## operand stack and
## returns it
return self.operands.pop()
proc peekb(self: PeonVM, distance: BackwardsIndex = ^1): uint64 =
## Returns the value at the
## given (backwards) distance from the top of
## the operand stack without consuming it
return self.operands[distance]
proc peek(self: PeonVM, distance: int = 0): uint64 =
## Returns the value at the
## given distance from the top of
## the operand stack without consuming it
if distance < 0:
return self.peekb(^(-distance))
return self.operands[self.operands.high() + distance]
proc pushc(self: PeonVM, val: uint64) =
## Pushes a value to the
## call stack
self.calls.add(val)
proc popc(self: PeonVM): uint64 =
## Pops a value off the call
## stack and returns it
return self.calls.pop()
proc peekc(self: PeonVM, distance: int = 0): uint64 {.used.} =
## Returns the value at the
## given distance from the top of
## the call stack without consuming it
return self.calls[self.calls.high() + distance]
proc getc(self: PeonVM, idx: uint): uint64 =
## Accessor method that abstracts
## stack accessing through stack
## indexing our call stack through stack
## frames
return self.stack[idx + self.frames[^1]]
return self.calls[idx + self.frames[^1]]
proc set(self: PeonVM, idx: int, val: PeonObject) =
proc setc(self: PeonVM, idx: uint, val: uint64) =
## Setter method that abstracts
## stack accessing through stack
## indexing our call stack through stack
## frames
self.stack[idx + self.frames[^1]] = val
self.calls[idx + self.frames[^1]] = val
# Byte-level primitives to read and decode
# bytecode
proc readByte(self: PeonVM): uint8 =
proc readByte(self: PeonVM): uint8 =
## Reads a single byte from the
## bytecode and returns it as an
## unsigned 8 bit integer
@ -115,7 +187,7 @@ proc readByte(self: PeonVM): uint8 =
return self.chunk.code[self.ip - 1]
proc readShort(self: PeonVM): uint16 =
proc readShort(self: PeonVM): uint16 =
## Reads two bytes from the
## bytecode and returns them
## as an unsigned 16 bit
@ -123,7 +195,7 @@ proc readShort(self: PeonVM): uint16 =
return [self.readByte(), self.readByte()].fromDouble()
proc readLong(self: PeonVM): uint32 =
proc readLong(self: PeonVM): uint32 =
## Reads three bytes from the
## bytecode and returns them
## as an unsigned 32 bit
@ -133,69 +205,170 @@ proc readLong(self: PeonVM): uint32 =
return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple())
proc readInt64(self: PeonVM, idx: int): PeonObject =
proc readUInt(self: PeonVM): uint32 =
## Reads three bytes from the
## bytecode and returns them
## as an unsigned 32 bit
## integer
return uint32([self.readByte(), self.readByte(), self.readByte(), self.readByte()].fromQuad())
# Functions to read primitives from the chunk's
# constants table
proc constReadInt64(self: PeonVM, idx: int): int64 =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is an Int64
## returns it as an int64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
]
result = PeonObject(kind: Int64)
copyMem(result.long.addr, arr.addr, sizeof(arr))
copyMem(result.addr, arr.addr, sizeof(arr))
proc readUInt64(self: PeonVM, idx: int): PeonObject =
proc constReadUInt64(self: PeonVM, idx: int): uint64 =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is an UInt64
## returns it as an uint64
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7],
]
result = PeonObject(kind: UInt64)
copyMem(result.uLong.addr, arr.addr, sizeof(arr))
copyMem(result.addr, arr.addr, sizeof(arr))
proc readUInt32(self: PeonVM, idx: int): PeonObject =
proc constReadUInt32(self: PeonVM, idx: int): uint32 =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is an UInt32
## returns it as an int32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: UInt32)
copyMem(result.uInt.addr, arr.addr, sizeof(arr))
copyMem(result.addr, arr.addr, sizeof(arr))
proc readInt32(self: PeonVM, idx: int): PeonObject =
proc constReadInt32(self: PeonVM, idx: int): int32 =
## Reads a constant from the
## chunk's constant table and
## returns a Peon object. Assumes
## the constant is an Int32
## returns it as an uint32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
result = PeonObject(kind: Int32)
copyMem(result.`int`.addr, arr.addr, sizeof(arr))
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadInt16(self: PeonVM, idx: int): int16 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int16
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadUInt16(self: PeonVM, idx: int): uint16 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint16
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1]]
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadInt8(self: PeonVM, idx: int): int8 =
## Reads a constant from the
## chunk's constant table and
## returns it as an int8
result = int8(self.chunk.consts[idx])
proc constReadUInt8(self: PeonVM, idx: int): uint8 =
## Reads a constant from the
## chunk's constant table and
## returns it as an uint8
result = self.chunk.consts[idx]
proc constReadFloat32(self: PeonVM, idx: int): float32 =
## Reads a constant from the
## chunk's constant table and
## returns it as a float32
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3]]
copyMem(result.addr, arr.addr, sizeof(arr))
proc constReadFloat64(self: PeonVM, idx: int): float =
## Reads a constant from the
## chunk's constant table and
## returns it as a float
var arr = [self.chunk.consts[idx], self.chunk.consts[idx + 1],
self.chunk.consts[idx + 2], self.chunk.consts[idx + 3],
self.chunk.consts[idx + 4], self.chunk.consts[idx + 5],
self.chunk.consts[idx + 6], self.chunk.consts[idx + 7]]
copyMem(result.addr, arr.addr, sizeof(arr))
{.pop.}
when debugVM: # So nim shuts up
proc debug(self: PeonVM) =
## Implements the VM's runtime
## debugger
styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}"
styledEcho fgBlue, "Instruction: ", fgRed, &"{OpCode(self.chunk.code[self.ip])} (", fgYellow, $self.chunk.code[self.ip], fgRed, ")"
if self.calls.len() !> 0:
stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[")
for i, e in self.calls:
stdout.styledWrite(fgYellow, $e)
if i < self.calls.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
if self.operands.len() !> 0:
stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[")
for i, e in self.operands:
stdout.styledWrite(fgYellow, $e)
if i < self.operands.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
if self.frames.len() !> 0:
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
for i, e in self.calls[self.frames[^1]..^1]:
stdout.styledWrite(fgYellow, $e)
if i < self.calls.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
for i, e in self.frames:
stdout.styledWrite(fgYellow, $e)
if i < self.frames.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
if self.closedOver.len() !> 0:
stdout.styledWrite(fgGreen, "Closure Array: ", fgMagenta, "[")
for i, e in self.closedOver:
stdout.styledWrite(fgYellow, $e)
if i < self.closedOver.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
if self.results.len() !> 0:
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
for i, e in self.results:
stdout.styledWrite(fgYellow, $e)
if i < self.results.high():
stdout.styledWrite(fgYellow, ", ")
styledEcho fgMagenta, "]"
discard readLine stdin
proc dispatch*(self: PeonVM) =
## Main bytecode dispatch loop
var instruction: OpCode
var instruction {.register.}: OpCode
while true:
{.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
when debugVM:
self.debug()
instruction = OpCode(self.readByte())
when DEBUG_TRACE_VM:
echo &"IP: {self.ip}"
echo &"SP: {self.stack.high()}"
echo &"Stack: {self.stack}"
echo &"Instruction: {instruction}"
discard readLine stdin
case instruction:
# Constant loading
# Constant loading instructions
of LoadTrue:
self.push(self.getBool(true))
of LoadFalse:
@ -207,106 +380,316 @@ proc dispatch*(self: PeonVM) =
of LoadInf:
self.push(self.getInf(true))
of LoadInt64:
self.push(self.readInt64(int(self.readLong())))
self.push(uint64(self.constReadInt64(int(self.readLong()))))
of LoadUInt64:
self.push(self.readUInt64(int(self.readLong())))
self.push(uint64(self.constReadUInt64(int(self.readLong()))))
of LoadUInt32:
self.push(self.readUInt32(int(self.readLong())))
self.push(uint64(self.constReadUInt32(int(self.readLong()))))
of LoadInt32:
self.push(uint64(self.constReadInt32(int(self.readLong()))))
of LoadInt16:
self.push(uint64(self.constReadInt16(int(self.readLong()))))
of LoadUInt16:
self.push(uint64(self.constReadUInt16(int(self.readLong()))))
of LoadInt8:
self.push(uint64(self.constReadInt8(int(self.readLong()))))
of LoadUInt8:
self.push(uint64(self.constReadUInt8(int(self.readLong()))))
of LoadString:
# TODO: Use constReadString with own memory manager
# Strings are broken rn!!
let size = int(self.readLong())
let idx = int(self.readLong())
var str = self.chunk.consts[idx..<idx + size].fromBytes()
self.push(cast[uint64](str.addr))
# We cast instead of converting because, unlike with integers,
# we don't want nim to touch any of the bits of the underlying
# value!
of LoadFloat32:
self.push(cast[uint64](self.constReadFloat32(int(self.readLong()))))
of LoadFloat64:
self.push(cast[uint64](self.constReadFloat64(int(self.readLong()))))
of LoadFunction:
# Loads a function address onto the operand stack
self.push(uint64(self.readLong()))
of LoadReturnAddress:
# Loads a 32-bit unsigned integer onto the operand stack.
# Used to load function return addresses
self.push(uint64(self.readUInt()))
of Call:
# Calls a function. The calling convention for peon
# functions is pretty simple: the return address sits
# at the bottom of the stack frame, then follow the
# arguments and all temporaries/local variables
let newIp = self.readLong()
# We do this because if we immediately changed
# the instruction pointer, we'd read the wrong
# value for the argument count. Storing it and
# changing it later fixes this issue
self.frames.add(int(self.readLong()))
self.ip = int(newIp)
of OpCode.Return:
# Returns from a void function or terminates the
# program entirely if we're at the topmost frame
if self.frames.len() > 1:
let frame = self.frames.pop()
for i in countdown(self.stack.high(), frame):
discard self.pop()
self.ip = int(self.pop().uInt)
else:
return
of ReturnValue:
# Returns from a function which has a return value,
# pushing it on the stack
let retVal = self.pop()
let frame = self.frames.pop()
for i in countdown(self.stack.high(), frame):
discard self.pop()
self.ip = int(self.pop().uInt)
self.push(retVal)
of StoreVar:
# Stores the value at the top of the stack
# into the given stack index
self.set(int(self.readLong()), self.pop())
of StoreHeap:
self.heapVars.add(self.pop())
of LoadHeap:
self.push(self.heapVars[self.readLong()])
of LoadVar:
self.push(self.get(int(self.readLong())))
of NoOp:
continue
of Pop:
# Calls a peon function. The calling convention here
# is pretty simple: the first value in the frame is
# the new instruction pointer to jump to, then a
# 32-bit return address follows. After that, all
# arguments and locals follow. Note that, due to
# how the stack works, all arguments before the call
# are in the reverse order in which they are passed
# to the function
let argc = self.readLong().int
let retAddr = self.peek(-argc - 1) # Return address
let jmpAddr = self.peek(-argc - 2) # Function address
self.ip = jmpAddr
self.pushc(jmpAddr)
self.pushc(retAddr)
# Creates a new result slot for the
# function's return value
self.results.add(self.getNil())
# Creates a new call frame
self.frames.add(uint64(self.calls.len() - 2))
# Loads the arguments onto the stack
for _ in 0..<argc:
self.pushc(self.pop())
# Pops the function and return address
# off the operand stack since they're
# not needed there anymore
discard self.pop()
of PopN:
for _ in 0..<int(self.readLong()):
discard self.pop()
of Return:
# Returns from a function.
# Every peon program is wrapped
# in a hidden function, so this
# will also exit the VM if we're
# at the end of the program
while self.calls.len().uint64 !> self.frames[^1] + 2'u64:
# Discards the function's local variables,
# if there is any
discard self.popc()
let ret = self.popc() # Return address
discard self.popc() # Function address
if self.readByte() == 1:
# Function is non-void!
self.push(self.results.pop())
else:
discard self.results.pop()
# Discard the topmost stack frame
discard self.frames.pop()
if self.frames.len() == 0:
# End of the program!
return
self.ip = ret.uInt
of SetResult:
# Sets the result of the
# current function. A Return
# instruction will pop this
# off the results array and
# onto the operand stack when
# the current function exits.
self.results[self.frames.high()] = self.pop()
of StoreVar:
# Stores the value at the top of the operand stack
# into the given call stack index
let idx = self.readLong()
when debugVM:
assert idx.int - self.calls.high() <= 1, "StoreVar index is bigger than the length of the call stack"
if idx + self.frames[^1] <= self.calls.high().uint:
self.setc(idx, self.pop())
else:
self.pushc(self.pop())
of StoreClosure:
# Stores/updates the value of a closed-over
# variable
let idx = self.readLong().int
if idx !> self.closedOver.high():
# Note: we *peek* the stack, but we
# don't pop!
self.closedOver.add(self.peek())
else:
self.closedOver[idx] = self.peek()
of LoadClosure:
# Loads a closed-over variable onto the
# stack
self.push(self.closedOver[self.readLong()])
of LoadVar:
# Pushes a variable onto the operand
# stack
self.push(self.getc(self.readLong()))
of NoOp:
# Does nothing
continue
of PopC:
# Pops a value off the call stack
discard self.popc()
of Pop:
# Pops a value off the operand stack
discard self.pop()
of PushC:
# Pushes a value from the operand stack
# onto the call stack
self.pushc(self.pop())
of PopRepl:
# Pops a peon object off the
# operand stack and prints it.
# Used in interactive REPL mode
if self.frames.len() !> 1:
discard self.pop()
continue
echo self.pop()
of PopN:
# Pops N elements off the call stack
for _ in 0..<int(self.readShort()):
discard self.popc()
# Jump opcodes
of Jump:
self.ip = int(self.readShort())
# Absolute jump
self.ip = self.readLong()
of JumpForwards:
self.ip += int(self.readShort())
# Relative, forward-jump
self.ip += self.readLong()
of JumpBackwards:
self.ip -= int(self.readShort())
# Relative, backward-jump
self.ip -= self.readLong()
of JumpIfFalse:
if not self.peek().boolean:
self.ip += int(self.readShort())
# Conditional positive jump
if not self.peek().bool:
self.ip += self.readLong()
of JumpIfTrue:
if self.peek().boolean:
self.ip += int(self.readShort())
# Conditional positive jump
if self.peek().bool:
self.ip += self.readLong()
of JumpIfFalsePop:
if not self.peek().boolean:
self.ip += int(self.readShort())
let ip = self.readLong()
if not self.peek().bool:
self.ip += ip
discard self.pop()
of JumpIfFalseOrPop:
if not self.peek().boolean:
self.ip += int(self.readShort())
if not self.peek().bool:
self.ip += self.readLong()
else:
discard self.pop()
of LongJumpIfFalse:
if not self.peek().boolean:
self.ip += int(self.readLong())
of LongJumpIfFalsePop:
if not self.peek().boolean:
self.ip += int(self.readLong())
discard self.pop()
of LongJumpForwards:
self.ip += int(self.readLong())
of LongJumpBackwards:
self.ip -= int(self.readLong())
of LongJump:
self.ip = int(self.readLong())
of LongJumpIfFalseOrPop:
if not self.peek().boolean:
self.ip += int(self.readLong())
# Built-in operations on primitive types.
# Note: for operations where the order of
# the operands matters, we don't need to
# swap the order of the calls to pop: this
# is because operators are handled like peon
# functions, which means the arguments are
# already reversed on the stack when we
# execute the instruction
of Negate:
self.push(uint64(-int64(self.pop())))
of NegateFloat64:
self.push(cast[uint64](-cast[float](self.pop())))
of NegateFloat32:
self.push(cast[uint64](-cast[float32](self.pop())))
of Add:
self.push(self.pop() + self.pop())
of Subtract:
self.push(self.pop() - self.pop())
of Multiply:
self.push(self.pop() * self.pop())
of Divide:
self.push(self.pop() div self.pop())
of SignedDivide:
self.push(uint64(int64(self.pop()) div int64(self.pop())))
of AddFloat64:
self.push(cast[uint64](cast[float](self.pop()) + cast[float](self.pop())))
of SubtractFloat64:
self.push(cast[uint64](cast[float](self.pop()) - cast[float](self.pop())))
of MultiplyFloat64:
self.push(cast[uint64](cast[float](self.pop()) * cast[float](self.pop())))
of DivideFloat64:
self.push(cast[uint64](cast[float](self.pop()) / cast[float](self.pop())))
of AddFloat32:
self.push(cast[uint64](cast[float32](self.pop()) + cast[float32](self.pop())))
of SubtractFloat32:
self.push(cast[uint64](cast[float32](self.pop()) - cast[float32](self.pop())))
of MultiplyFloat32:
self.push(cast[uint64](cast[float32](self.pop()) * cast[float32](self.pop())))
of DivideFloat32:
self.push(cast[uint64](cast[float32](self.pop()) / cast[float32](self.pop())))
of Pow:
self.push(uint64(self.pop() ^ self.pop()))
of SignedPow:
self.push(uint64(int64(self.pop()) ^ int64(self.pop())))
of PowFloat64:
self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of PowFloat32:
self.push(cast[uint64](pow(cast[float](self.pop()), cast[float](self.pop()))))
of Mod:
self.push(uint64(self.pop() mod self.pop()))
of SignedMod:
self.push(uint64(int64(self.pop()) mod int64(self.pop())))
of ModFloat64:
self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of ModFloat32:
self.push(cast[uint64](floorMod(cast[float](self.pop()), cast[float](self.pop()))))
of LShift:
self.push(self.pop() shl self.pop())
of RShift:
self.push(self.pop() shr self.pop())
of Xor:
self.push(self.pop() xor self.pop())
of Not:
self.push(not self.pop())
of And:
self.push(self.pop() and self.pop())
# Comparison opcodes
of Equal:
self.push(self.getBool(self.pop() == self.pop()))
of NotEqual:
self.push(self.getBool(self.pop() != self.pop()))
of GreaterThan:
self.push(self.getBool(self.pop() !> self.pop()))
of LessThan:
self.push(self.getBool(self.pop() < self.pop()))
of GreaterOrEqual:
self.push(self.getBool(self.pop() !>= self.pop()))
of LessOrEqual:
self.push(self.getBool(self.pop() <= self.pop()))
# Print opcodes
of PrintInt64:
echo int64(self.pop())
of PrintUInt64:
echo self.pop()
of PrintInt32:
echo int32(self.pop())
of PrintUInt32:
echo uint32(self.pop())
of PrintInt16:
echo int16(self.pop())
of PrintUInt16:
echo uint16(self.pop())
of PrintInt8:
echo int8(self.pop())
of PrintUInt8:
echo uint8(self.pop())
of PrintFloat32:
echo cast[float32](self.pop())
of PrintFloat64:
echo cast[float](self.pop())
of PrintHex:
echo "0x" & self.pop().toHex().strip(chars={'0'})
of PrintBool:
if self.pop().bool:
echo "true"
else:
discard self.pop()
echo "false"
of PrintInf:
if self.pop() == 0x3:
echo "-inf"
else:
echo "inf"
of PrintNan:
echo "nan"
of PrintString:
echo cast[ptr string](self.pop())[] # TODO
of SysClock64:
# Pushes the value of a monotonic clock
# onto the operand stack. This can be used
# to track system time accurately, but it
# cannot be converted to a date. The number
# is in seconds
self.push(cast[uint64](getMonoTime().ticks().float() / 1_000_000_000))
else:
discard
proc run*(self: PeonVM, chunk: Chunk) =
## Executes a piece of Peon bytecode.
## Executes a piece of Peon bytecode
self.chunk = chunk
self.frames = @[0]
self.stack = @[]
self.frames = @[]
self.calls = @[]
self.operands = @[]
self.ip = 0
self.dispatch()
{.pop.}

View File

@ -14,25 +14,28 @@
import strformat
const BYTECODE_MARKER* = "PEON_BYTECODE"
const HEAP_GROW_FACTOR* = 2 # How much extra memory to allocate for dynamic arrays and garbage collection when resizing
when HEAP_GROW_FACTOR <= 1:
# Debug various components of peon
const debugLexer* {.booldefine.} = false
const debugParser* {.booldefine.} = false
const debugCompiler* {.booldefine.} = false
const debugVM* {.booldefine.} = false
const debugGC* {.booldefine.} = false
const debugMem* {.booldefine.} = false
const debugSerializer* {.booldefine.} = false
const PeonBytecodeMarker* = "PEON_BYTECODE"
const HeapGrowFactor* = 2 # How much extra memory to allocate for dynamic arrays and garbage collection when resizing
when HeapGrowFactor <= 1:
{.fatal: "Heap growth factor must be > 1".}
const PEON_VERSION* = (major: 0, minor: 4, patch: 0)
const PEON_RELEASE* = "alpha"
const PEON_COMMIT_HASH* = "ed79385e2a93100331697f26a4a90157e60ad27a"
when len(PEON_COMMIT_HASH) != 40:
const PeonVersion* = (major: 0, minor: 1, patch: 0)
const PeonRelease* = "alpha"
const PeonCommitHash* = "b273cd744883458a4a6354a0cc5f4f5d0f560c31"
when len(PeonCommitHash) != 40:
{.fatal: "The git commit hash must be exactly 40 characters long".}
const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255:
const PeonBranch* = "unboxed-types"
when len(PeonBranch) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = true # Traces VM execution
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
const PEON_VERSION_STRING* = &"Peon {PEON_VERSION.major}.{PEON_VERSION.minor}.{PEON_VERSION.patch} {PEON_RELEASE} ({PEON_BRANCH}, {CompileDate}, {CompileTime}, {PEON_COMMIT_HASH[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
const HELP_MESSAGE* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch}, {CompileDate}, {CompileTime}, {PeonCommitHash[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
const HelpMessage* = """The peon programming language, Copyright (C) 2022 Mattia Giambirtone & All Contributors
This program is free software, see the license distributed with this program or check
http://www.apache.org/licenses/LICENSE-2.0 for more info.

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,10 @@
## A simple and modular tokenizer implementation with arbitrary lookahead
## using a customizable symbol table
import strutils
import parseutils
import strformat
import tables
import std/strutils
import std/parseutils
import std/strformat
import std/tables
import meta/token
@ -51,9 +51,17 @@ type
file: string
lines: seq[tuple[start, stop: int]]
lastLine: int
spaces: int
LexingError* = ref object of PeonException
## A lexing error
lexer*: Lexer
file*: string
lexeme*: string
line*: int
proc newSymbolTable: SymbolTable =
## Initializes a new symbol table
new(result)
result.keywords = newTable[string, TokenType]()
result.symbols = newTable[string, TokenType]()
@ -143,6 +151,7 @@ proc isAlphaNumeric(s: string): bool =
return false
return true
# Forward declaration
proc incLine(self: Lexer)
# Simple public getters used for error
@ -151,6 +160,7 @@ proc getStart*(self: Lexer): int = self.start
proc getFile*(self: Lexer): string = self.file
proc getCurrent*(self: Lexer): int = self.current
proc getLine*(self: Lexer): int = self.line
proc getLines*(self: Lexer): seq[tuple[start, stop: int]] = self.lines
proc getSource*(self: Lexer): string = self.source
proc getRelPos*(self: Lexer, line: int): tuple[start, stop: int] =
if self.tokens.len() == 0 or self.tokens[^1].kind != EndOfFile:
@ -173,6 +183,7 @@ proc newLexer*(self: Lexer = nil): Lexer =
result.lines = @[]
result.lastLine = 0
result.symbols = newSymbolTable()
result.spaces = 0
proc done(self: Lexer): bool =
@ -182,8 +193,8 @@ proc done(self: Lexer): bool =
proc incLine(self: Lexer) =
## Increments the lexer's line
## and updates internal line
## metadata
## counter and updates internal
## line metadata
self.lines.add((self.lastLine, self.current))
self.lastLine = self.current
self.line += 1
@ -211,7 +222,8 @@ proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
## previously consumed tokens. If the
## distance and/or the length are beyond
## EOF (even partially), the resulting string
## will be shorter than length bytes
## will be shorter than length bytes. The string
## may be empty
var i = distance
while len(result) < length:
if self.done() or self.current + i > self.source.high() or
@ -223,9 +235,9 @@ proc peek(self: Lexer, distance: int = 0, length: int = 1): string =
proc error(self: Lexer, message: string) =
## Raises a lexing error with a formatted
## error message
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek())
## Raises a lexing error with info
## for error messages
raise LexingError(msg: message, line: self.line, file: self.file, lexeme: self.peek(), lexer: self)
proc check(self: Lexer, s: string, distance: int = 0): bool =
@ -282,9 +294,9 @@ proc createToken(self: Lexer, tokenType: TokenType) =
tok.kind = tokenType
tok.lexeme = self.source[self.start..<self.current]
tok.line = self.line
tok.pos = (start: self.start, stop: self.current)
if len(tok.lexeme) != tok.pos.stop - tok.pos.start:
self.error("invalid state: len(tok.lexeme) != tok.pos.stop - tok.pos.start (this is most likely a compiler bug!)")
tok.spaces = self.spaces
self.spaces = 0
tok.pos = (start: self.start, stop: self.current - 1)
self.tokens.add(tok)
@ -295,7 +307,7 @@ proc parseEscape(self: Lexer) =
# likely be soon. Another notable limitation is that
# \xhhh and \nnn are limited to the size of a char
# (i.e. uint8, or 256 values)
case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings
case self.peek()[0]: # We use a char instead of a string because of how case statements handle ranges with strings
# (i.e. not well, given they crash the C code generator)
of 'a':
self.source[self.current] = cast[char](0x07)
@ -555,13 +567,15 @@ proc next(self: Lexer) =
return
elif self.match(" "):
# Whitespaces
self.createToken(TokenType.Whitespace)
inc(self.spaces)
elif self.match("\r"):
# Tabs
self.createToken(TokenType.Tab)
self.error("tabs are not allowed in peon code")
elif self.match("\n"):
# New line
self.incLine()
# TODO: Broken
#[if not self.getToken("\n").isNil():
self.createToken(Semicolon)]#
elif self.match("`"):
# Stropped token
self.parseBackticks()
@ -576,7 +590,7 @@ proc next(self: Lexer) =
self.parseString(self.peek(-1), mode)
elif self.peek().isDigit():
discard self.step() # Needed because parseNumber reads the next
# character to tell the base of the number
# character to tell the base of the number
# Number literal
self.parseNumber()
elif self.peek().isAlphaNumeric() and self.check(["\"", "'"], 1):
@ -594,10 +608,14 @@ proc next(self: Lexer) =
# Keywords and identifiers
self.parseIdentifier()
elif self.match("#"):
# Inline comments, pragmas, etc.
while not (self.check("\n") or self.done()):
discard self.step()
self.createToken(Comment)
if not self.match("pragma["):
# Inline comments
while not (self.match("\n") or self.done()):
discard self.step()
self.createToken(Comment)
self.incLine()
else:
self.createToken(Pragma)
else:
# If none of the above conditions matched, there's a few
# other options left:
@ -607,7 +625,7 @@ proc next(self: Lexer) =
# We handle all of these cases here by trying to
# match the longest sequence of characters possible
# as either an operator or a statement/expression
# delimiter, erroring out if there's no match
# delimiter
var n = self.symbols.getMaxSymbolSize()
while n > 0:
for symbol in self.symbols.getSymbols(n):

View File

@ -16,8 +16,8 @@
## top-down parser. For more info, check out docs/grammar.md
import strformat
import strutils
import std/strformat
import std/strutils
import token
@ -30,10 +30,10 @@ type
## precedence
# Declarations
funDecl = 0'u8,
typeDecl = 0'u8
funDecl,
varDecl,
# Statements
forStmt, # Unused for now (for loops are compiled to while loops)
ifStmt,
returnStmt,
breakStmt,
@ -61,7 +61,7 @@ type
sliceExpr,
callExpr,
getItemExpr, # Get expressions like a.b
# Primary expressions
# Primary expressions
groupingExpr, # Parenthesized expressions such as (true) and (3 + 4)
trueExpr,
falseExpr,
@ -76,7 +76,10 @@ type
nanExpr,
infExpr,
identExpr, # Identifier
pragmaExpr
pragmaExpr,
varExpr,
refExpr,
ptrExpr
# Here I would've rather used object variants, and in fact that's what was in
# place before, but not being able to re-declare a field of the same type in
@ -97,6 +100,7 @@ type
# work properly
Declaration* = ref object of ASTNode
## A declaration
isPrivate*: bool
pragmas*: seq[Pragma]
generics*: seq[tuple[name: IdentExpr, cond: Expression]]
@ -132,6 +136,7 @@ type
IdentExpr* = ref object of Expression
name*: Token
depth*: int
GroupingExpr* = ref object of Expression
expression*: Expression
@ -150,6 +155,7 @@ type
callee*: Expression # The object being called
arguments*: tuple[positionals: seq[Expression], keyword: seq[tuple[
name: IdentExpr, value: Expression]]]
genericArgs*: seq[Expression]
UnaryExpr* = ref object of Expression
operator*: Token
@ -169,15 +175,15 @@ type
LambdaExpr* = ref object of Expression
body*: Statement
arguments*: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]]
arguments*: seq[tuple[name: IdentExpr, valueType: Expression]]
defaults*: seq[Expression]
isGenerator*: bool
isAsync*: bool
isPure*: bool
returnType*: Expression
hasExplicitReturn*: bool
freeVars*: seq[IdentExpr]
depth*: int
SliceExpr* = ref object of Expression
expression*: Expression
@ -245,25 +251,40 @@ type
name*: IdentExpr
value*: Expression
isConst*: bool
isPrivate*: bool
isLet*: bool
valueType*: Expression
FunDecl* = ref object of Declaration
name*: IdentExpr
body*: Statement
arguments*: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]]
arguments*: seq[tuple[name: IdentExpr, valueType: Expression]]
defaults*: seq[Expression]
isAsync*: bool
isGenerator*: bool
isPrivate*: bool
isPure*: bool
returnType*: Expression
hasExplicitReturn*: bool
freeVars*: seq[IdentExpr]
depth*: int
TypeDecl* = ref object of Declaration
name*: IdentExpr
fields*: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]]
defaults*: seq[Expression]
valueType*: Expression
Pragma* = ref object of Expression
name*: IdentExpr
args*: seq[LiteralExpr]
Var* = ref object of Expression
value*: Expression
Ref* = ref object of Expression
value*: Expression
Ptr* = ref object of Expression
value*: Expression
proc isConst*(self: ASTNode): bool =
@ -300,6 +321,28 @@ proc newPragma*(name: IdentExpr, args: seq[LiteralExpr]): Pragma =
result.kind = pragmaExpr
result.args = args
result.name = name
result.token = name.token
proc newVarExpr*(expression: Expression, token: Token): Var =
new(result)
result.kind = varExpr
result.value = expression
result.token = token
proc newRefExpr*(expression: Expression, token: Token): Ref =
new(result)
result.kind = refExpr
result.value = expression
result.token = token
proc newPtrExpr*(expression: Expression, token: Token): Ptr =
new(result)
result.kind = ptrExpr
result.value = expression
result.token = token
proc newIntExpr*(literal: Token): IntExpr =
@ -356,10 +399,11 @@ proc newCharExpr*(literal: Token): CharExpr =
result.token = literal
proc newIdentExpr*(name: Token): IdentExpr =
proc newIdentExpr*(name: Token, depth: int = 0): IdentExpr =
result = IdentExpr(kind: identExpr)
result.name = name
result.token = name
result.depth = depth
proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
@ -368,11 +412,11 @@ proc newGroupingExpr*(expression: Expression, token: Token): GroupingExpr =
result.token = token
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression,
mutable: bool, isRef: bool, isPtr: bool]], defaults: seq[Expression],
body: Statement, isGenerator: bool, isAsync: bool, token: Token,
returnType: Expression, pragmas: seq[Pragma],
generics: seq[tuple[name: IdentExpr, cond: Expression]]): LambdaExpr =
proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
token: Token, depth: int, pragmas: seq[Pragma] = @[],
returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[],
freeVars: seq[IdentExpr] = @[]): LambdaExpr =
result = LambdaExpr(kind: lambdaExpr)
result.body = body
result.arguments = arguments
@ -384,6 +428,8 @@ proc newLambdaExpr*(arguments: seq[tuple[name: IdentExpr, valueType: Expression,
result.isPure = false
result.pragmas = pragmas
result.generics = generics
result.freeVars = freeVars
result.depth = depth
proc newGetItemExpr*(obj: Expression, name: IdentExpr,
@ -405,15 +451,15 @@ proc newSetItemExpr*(obj: Expression, name: IdentExpr, value: Expression,
proc newCallExpr*(callee: Expression, arguments: tuple[positionals: seq[
Expression], keyword: seq[tuple[name: IdentExpr, value: Expression]]],
token: Token): CallExpr =
token: Token, genericArgs: seq[Expression] = @[]): CallExpr =
result = CallExpr(kind: callExpr)
result.callee = callee
result.arguments = arguments
result.token = token
result.genericArgs = @[]
proc newSliceExpr*(expression: Expression, ends: seq[Expression],
token: Token): SliceExpr =
proc newSliceExpr*(expression: Expression, ends: seq[Expression], token: Token): SliceExpr =
result = SliceExpr(kind: sliceExpr)
result.expression = expression
result.ends = ends
@ -570,10 +616,11 @@ proc newVarDecl*(name: IdentExpr, value: Expression, isConst: bool = false,
result.pragmas = pragmas
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression, mutable: bool, isRef: bool, isPtr: bool]], defaults: seq[Expression],
proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueType: Expression]], defaults: seq[Expression],
body: Statement, isAsync, isGenerator: bool,
isPrivate: bool, token: Token, pragmas: seq[Pragma],
returnType: Expression, generics: seq[tuple[name: IdentExpr, cond: Expression]]): FunDecl =
isPrivate: bool, token: Token, depth: int,
pragmas: seq[Pragma] = @[], returnType: Expression,
generics: seq[tuple[name: IdentExpr, cond: Expression]] = @[], freeVars: seq[IdentExpr] = @[]): FunDecl =
result = FunDecl(kind: funDecl)
result.name = name
result.arguments = arguments
@ -587,6 +634,22 @@ proc newFunDecl*(name: IdentExpr, arguments: seq[tuple[name: IdentExpr, valueTyp
result.returnType = returnType
result.isPure = false
result.generics = generics
result.freeVars = freeVars
result.depth = depth
proc newTypeDecl*(name: IdentExpr, fields: seq[tuple[name: IdentExpr, valueType: Expression, isPrivate: bool]],
defaults: seq[Expression], isPrivate: bool, token: Token, pragmas: seq[Pragma],
generics: seq[tuple[name: IdentExpr, cond: Expression]], valueType: Expression): TypeDecl =
result = TypeDecl(kind: typeDecl)
result.name = name
result.fields = fields
result.defaults = defaults
result.isPrivate = isPrivate
result.token = token
result.pragmas = pragmas
result.generics = generics
result.valueType = valueType
proc `$`*(self: ASTNode): string =
@ -669,13 +732,16 @@ proc `$`*(self: ASTNode): string =
result &= &"AwaitStmt({self.expression})"
of varDecl:
var self = VarDecl(self)
result &= &"Var(name={self.name}, value={self.value}, const={self.isConst}, private={self.isPrivate}, type={self.valueType})"
result &= &"Var(name={self.name}, value={self.value}, const={self.isConst}, private={self.isPrivate}, type={self.valueType}, pragmas={self.pragmas})"
of funDecl:
var self = FunDecl(self)
result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generics=[{self.generics.join(", ")}], async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate})"""
result &= &"""FunDecl(name={self.name}, body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generics=[{self.generics.join(", ")}], async={self.isAsync}, generator={self.isGenerator}, private={self.isPrivate}, pragmas={self.pragmas}, vars=[{self.freeVars.join(", ")}])"""
of typeDecl:
var self = TypeDecl(self)
result &= &"""TypeDecl(name={self.name}, fields={self.fields}, defaults={self.defaults}, private={self.isPrivate}, pragmas={self.pragmas}, generics={self.generics}, pragmas={self.pragmas}, type={self.valueType})"""
of lambdaExpr:
var self = LambdaExpr(self)
result &= &"""Lambda(body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generator={self.isGenerator}, async={self.isAsync})"""
result &= &"""Lambda(body={self.body}, type={self.returnType}, arguments=[{self.arguments.join(", ")}], defaults=[{self.defaults.join(", ")}], generator={self.isGenerator}, async={self.isAsync}, pragmas={self.pragmas}, vars=[{self.freeVars.join(", ")}])"""
of deferStmt:
var self = DeferStmt(self)
result &= &"Defer({self.expression})"
@ -694,6 +760,15 @@ proc `$`*(self: ASTNode): string =
else:
result &= ", elseClause=nil"
result &= ")"
of pragmaExpr:
var self = Pragma(self)
result &= &"Pragma(name={self.name}, args={self.args})"
of varExpr:
result &= &"Var({Var(self).value})"
of refExpr:
result &= &"Ptr({Ref(self).value})"
of ptrExpr:
result &= &"Ptr({Ptr(self).value})"
else:
discard

View File

@ -14,8 +14,8 @@
## Low level bytecode implementation details
import strutils
import strformat
import std/strutils
import std/strformat
import ../../util/multibyte
@ -23,7 +23,7 @@ import ../../util/multibyte
type
Chunk* = ref object
## A piece of bytecode.
## consts is used when serializing to/from a bytecode stream.
## consts is the code's constants table.
## code is the linear sequence of compiled bytecode instructions.
## lines maps bytecode instructions to line numbers using Run
## Length Encoding. Instructions are encoded in groups whose structure
@ -38,9 +38,20 @@ type
## are 3 and 4"
## This is more efficient than using the naive approach, which would encode
## the same line number multiple times and waste considerable amounts of space.
## cfi represents Call Frame Information and encodes the following information:
## - Function name
## - Argument count
## - Function boundaries
## The encoding for CFI data is the following:
## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer)
## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer)
## - After that follows the argument count as a 1 byte integer
## - Lastly, the function's name (optional) is encoded in ASCII, prepended with
## its size as a 2-byte integer
consts*: seq[uint8]
code*: seq[uint8]
lines*: seq[int]
cfi*: seq[uint8]
OpCode* {.pure.} = enum
## Enum of Peon's bytecode opcodes
@ -57,7 +68,8 @@ type
# or 24 bit numbers that are defined statically
# at compilation time into the bytecode
# These push a constant onto the stack
# These push a constant at position x in the
# constant table onto the stack
LoadInt64 = 0u8,
LoadUInt64,
LoadInt32,
@ -69,22 +81,77 @@ type
LoadFloat64,
LoadFloat32,
LoadString,
LoadFunction,
LoadReturnAddress,
## Singleton opcodes (each of them pushes a constant singleton on the stack)
LoadNil,
LoadTrue,
LoadFalse,
LoadNan,
LoadInf,
## Operations on primitive types
Negate,
NegateFloat64,
NegateFloat32,
Add,
Subtract,
Multiply,
Divide,
SignedDivide,
AddFloat64,
SubtractFloat64,
MultiplyFloat64,
DivideFloat64,
AddFloat32,
SubtractFloat32,
MultiplyFloat32,
DivideFloat32,
Pow,
SignedPow,
Mod,
SignedMod,
PowFloat64,
PowFloat32,
ModFloat64,
ModFloat32,
LShift,
RSHift,
Xor,
Or,
And,
Not,
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
## Print opcodes
PrintInt64,
PrintUInt64,
PrintInt32,
PrintUInt32,
PrintInt16,
PrintUint16,
PrintInt8,
PrintUInt8,
PrintFloat64,
PrintFloat32,
PrintHex,
PrintBool,
PrintNan,
PrintInf,
PrintString,
## Basic stack operations
Pop, # Pops an element off the stack and discards it
Push, # Pushes x onto the stack
PopN, # Pops x elements off the stack (optimization for exiting local scopes which usually pop many elements)
PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode)
PopN, # Pops x elements off the call stack (optimization for exiting local scopes which usually pop many elements)
## Name resolution/handling
LoadAttribute, # Pushes the attribute b of object a onto the stack
LoadVar, # Pushes the object at position x in the stack onto the stack
StoreVar, # Stores the value of b at position a in the stack
LoadHeap, # Pushes the object position x in the closure array onto the stack
StoreHeap, # Stores the value of b at position a in the closure array
LoadClosure, # Pushes the object position x in the closure array onto the stack
StoreClosure, # Stores the value of b at position a in the closure array
## Looping and jumping
Jump, # Absolute, unconditional jump into the bytecode
JumpForwards, # Relative, unconditional, positive jump in the bytecode
@ -93,18 +160,10 @@ type
JumpIfTrue, # Jumps to a relative index in the bytecode if x is true
JumpIfFalsePop, # Like JumpIfFalse, but also pops off the stack (regardless of truthyness). Optimization for if statements
JumpIfFalseOrPop, # Jumps to an absolute index in the bytecode if x is false and pops otherwise (used for logical and)
## Long variants of jumps (they use a 24-bit operand instead of a 16-bit one)
LongJump,
LongJumpIfFalse,
LongJumpIfTrue,
LongJumpIfFalsePop,
LongJumpIfFalseOrPop,
LongJumpForwards,
LongJumpBackwards,
## Functions
Call, # Calls a function and initiates a new stack frame
Return, # Terminates the current function without popping off the stack
ReturnValue, # Pops a return value off the stack and terminates the current function
Return, # Terminates the current function
SetResult, # Sets the result of the current function
## Exception handling
Raise, # Raises exception x or re-raises active exception if x is nil
BeginTry, # Initiates an exception handling context
@ -114,21 +173,75 @@ type
## Coroutines
Await, # Calls an asynchronous function
## Misc
Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op
Assert, # Raises an AssertionFailed exception if x is false
NoOp, # Just a no-op
PopC, # Pop off the call stack onto the operand stack
PushC, # Pop off the operand stack onto the call stack
SysClock64 # Pushes the output of a monotonic clock on the stack
# We group instructions by their operation/operand types for easier handling when debugging
# Simple instructions encompass instructions that push onto/pop off the stack unconditionally (True, False, Pop, etc.)
const simpleInstructions* = {OpCode.Return, LoadNil,
const simpleInstructions* = {Return, LoadNil,
LoadTrue, LoadFalse,
LoadNan, LoadInf,
Pop, OpCode.Raise,
BeginTry, FinishTry,
OpCode.Yield, OpCode.Await,
OpCode.NoOp, OpCode.Return,
OpCode.ReturnValue}
Pop, Raise,
BeginTry, FinishTry, Yield,
Await, NoOp, SetResult,
PopC, PushC, SysClock64,
Negate,
NegateFloat64,
NegateFloat32,
Add,
Subtract,
Multiply,
Divide,
SignedDivide,
AddFloat64,
SubtractFloat64,
MultiplyFloat64,
DivideFloat64,
AddFloat32,
SubtractFloat32,
MultiplyFloat32,
DivideFloat32,
Pow,
SignedPow,
Mod,
SignedMod,
PowFloat64,
PowFloat32,
ModFloat64,
ModFloat32,
LShift,
RSHift,
Xor,
Or,
And,
Not,
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
PrintInt64,
PrintUInt64,
PrintInt32,
PrintUInt32,
PrintInt16,
PrintUint16,
PrintInt8,
PrintUInt8,
PrintFloat64,
PrintFloat32,
PrintHex,
PrintBool,
PrintNan,
PrintInf,
PrintString,
}
# Constant instructions are instructions that operate on the bytecode constant table
const constantInstructions* = {LoadInt64, LoadUInt64,
@ -140,7 +253,7 @@ const constantInstructions* = {LoadInt64, LoadUInt64,
# Stack triple instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 24 bit integers
const stackTripleInstructions* = {StoreVar, LoadVar, LoadHeap, StoreHeap}
const stackTripleInstructions* = {StoreVar, LoadVar, LoadCLosure, }
# Stack double instructions operate on the stack at arbitrary offsets and pop arguments off of it in the form
# of 16 bit integers
@ -150,22 +263,20 @@ const stackDoubleInstructions* = {}
const argumentDoubleInstructions* = {PopN, }
# Argument double argument instructions take hardcoded arguments as 24 bit integers
const argumentTripleInstructions* = {}
const argumentTripleInstructions* = {StoreClosure}
# Instructions that call functions
const callInstructions* = {Call, }
# Jump instructions jump at relative or absolute bytecode offsets
const jumpInstructions* = {Jump, LongJump, JumpIfFalse, JumpIfFalsePop,
const jumpInstructions* = {Jump, JumpIfFalse, JumpIfFalsePop,
JumpForwards, JumpBackwards,
LongJumpIfFalse, LongJumpIfFalsePop,
LongJumpForwards, LongJumpBackwards,
JumpIfTrue, LongJumpIfTrue}
JumpIfTrue}
proc newChunk*: Chunk =
## Initializes a new, empty chunk
result = Chunk(consts: @[], code: @[], lines: @[])
result = Chunk(consts: @[], code: @[], lines: @[], cfi: @[])
proc `$`*(self: Chunk): string = &"""Chunk(consts=[{self.consts.join(", ")}], code=[{self.code.join(", ")}], lines=[{self.lines.join(", ")}])"""

View File

@ -11,24 +11,10 @@
# 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.
import token
import ast
type
## Nim exceptions for internal Peon failures
PeonException* = ref object of CatchableError
LexingError* = ref object of PeonException
file*: string
lexeme*: string
line*: int
ParseError* = ref object of PeonException
file*: string
token*: Token
module*: string
CompileError* = ref object of PeonException
node*: ASTNode
file*: string
module*: string
SerializationError* = ref object of PeonException
file*: string

View File

@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import strformat
import std/strformat
import std/strutils
type
@ -39,7 +40,7 @@ type
Raise, Assert, Await, Foreach,
Yield, Defer, Try, Except,
Finally, Type, Operator, Case,
Enum, From, Ptr, Ref
Enum, From, Ptr, Ref, Object
# Literal types
Integer, Float, String, Identifier,
@ -59,11 +60,9 @@ type
NoMatch, # Used internally by the symbol table
Comment, # Useful for documentation comments, pragmas, etc.
Symbol, # A generic symbol
# These are not used at the moment but may be
# employed to enforce indentation or other neat
# stuff I haven't thought about yet
Whitespace,
Tab,
Pragma,
Token* = ref object
@ -73,13 +72,14 @@ type
line*: int # The line where the token appears
pos*: tuple[start, stop: int] # The absolute position in the source file
# (0-indexed and inclusive at the beginning)
spaces*: int # Number of spaces before this token
proc `$`*(self: Token): string =
## Strinfifies
if self != nil:
result = &"Token(kind={self.kind}, lexeme='{$(self.lexeme)}', line={self.line}, pos=({self.pos.start}, {self.pos.stop}))"
result = &"Token(kind={self.kind}, lexeme={self.lexeme.escape()}, line={self.line}, pos=({self.pos.start}, {self.pos.stop}), spaces={self.spaces})"
else:
result = "nil"

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
# Builtins & external libs
import strformat
import strutils
import terminal
import os
import std/strformat
import std/strutils
import std/terminal
import std/parseopt
import std/times
import std/os
# Thanks art <3
import jale/editor as ed
import jale/templates
@ -11,32 +14,19 @@ import jale/plugin/editor_history
import jale/keycodes
import jale/multiline
# Our stuff
import frontend/lexer as l
import frontend/parser as p
import frontend/compiler as c
import backend/vm as v
import util/serializer as s
import util/debugger
import util/symbols
import config
# Forward declarations
proc fillSymbolTable(tokenizer: Lexer)
proc getLineEditor: LineEditor
# Handy dandy compile-time constants
const debugLexer = false
const debugParser = false
const debugCompiler = true
const debugSerializer = false
const debugRuntime = false
when debugSerializer:
import nimSHA2
import times
when debugCompiler:
import util/debugger
proc repl =
styledEcho fgMagenta, "Welcome into the peon REPL!"
@ -44,29 +34,44 @@ proc repl =
keep = true
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
compiled: Chunk
compiled: Chunk = newChunk()
serialized: Serialized
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
serializer = newSerializer()
compiler = newCompiler(replMode=true)
vm = newPeonVM()
debugger = newDebugger()
serializer = newSerializer()
editor = getLineEditor()
input: string
current: string
incremental: bool = false
tokenizer.fillSymbolTable()
editor.bindEvent(jeQuit):
stdout.styledWriteLine(fgGreen, "Goodbye!")
editor.prompt = ""
keep = false
input = ""
editor.bindKey("ctrl+a"):
editor.content.home()
editor.bindKey("ctrl+e"):
editor.content.`end`()
while keep:
try:
# We incrementally add content to the input
# so that you can, for example, define a function
# then press enter and use it at the next iteration
# of the read loop
input = editor.read()
if input.len() == 0:
continue
elif input == "#reset":
compiled = newChunk()
incremental = false
continue
elif input == "#clear":
stdout.write("\x1Bc")
continue
tokens = tokenizer.lex(input, "stdin")
if tokens.len() == 0:
continue
@ -78,7 +83,7 @@ proc repl =
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, "stdin")
tree = parser.parse(tokens, "stdin", tokenizer.getLines(), input, persist=true)
if tree.len() == 0:
continue
when debugParser:
@ -86,22 +91,17 @@ proc repl =
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
compiled = compiler.compile(tree, "stdin")
discard compiler.compile(tree, "stdin", tokenizer.getLines(), input, chunk=compiled, incremental=incremental, terminateScope=false)
incremental = true
when debugCompiler:
styledEcho fgCyan, "Compilation step:"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, "stdin")
styledEcho fgCyan, "Compilation step:\n"
debugger.disassembleChunk(compiled, "stdin")
echo ""
serializer.dumpFile(compiled, input, "stdin", "stdin.pbc")
serialized = serializer.loadFile("stdin.pbc")
serialized = serializer.loadBytes(serializer.dumpBytes(compiled, "stdin"))
when debugSerializer:
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
styledEcho fgCyan, "Serialization step: "
styledEcho fgBlue, &"\t- File hash: ", fgYellow, serialized.fileHash, fgBlue, " (", if hashMatches: fgGreen else: fgRed, if hashMatches: "OK" else: "Fail", fgBlue, ")"
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch}", fgBlue, " (commit ", fgYellow, serialized.commitHash[0..8], fgBlue, ") on branch ", fgYellow, serialized.peonBranch
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == compiled.consts:
@ -118,46 +118,57 @@ proc repl =
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
when debugRuntime:
styledEcho fgCyan, "\n\nExecution step: "
stdout.styledWrite(fgBlue, "\t- CFI segment: ")
if serialized.chunk.cfi == compiled.cfi:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
vm.run(serialized.chunk)
except LexingError:
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
input = ""
var exc = LexingError(getCurrentException())
if exc.lexeme == "":
exc.line -= 1
let relPos = exc.lexer.getRelPos(exc.line)
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
input = ""
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo)
var lineNo = exc.token.line
if exc.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.parser.getRelPos(lineNo)
let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError:
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fn = compiler.getCurrentFunction()
var lineNo = exc.node.token.line
if exc.node.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.compiler.getRelPos(lineNo)
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = exc.compiler.getCurrentFunction()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while compiling ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except SerializationError:
@ -166,7 +177,7 @@ proc repl =
quit(0)
proc runFile(f: string) =
proc runFile(f: string, interactive: bool = false, fromString: bool = false) =
var
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
@ -175,12 +186,20 @@ proc runFile(f: string) =
tokenizer = newLexer()
parser = newParser()
compiler = newCompiler()
debugger {.used.} = newDebugger()
serializer = newSerializer()
vm = newPeonVM()
input: string
tokenizer.fillSymbolTable()
try:
input = readFile(f)
var f = f
if not fromString:
if not f.endsWith(".pn"):
f &= ".pn"
input = readFile(f)
else:
input = f
f = "<string>"
tokens = tokenizer.lex(input, f)
if tokens.len() == 0:
return
@ -192,7 +211,7 @@ proc runFile(f: string) =
break
styledEcho fgGreen, "\t", $token
echo ""
tree = parser.parse(tokens, f)
tree = parser.parse(tokens, f, tokenizer.getLines(), input)
if tree.len() == 0:
return
when debugParser:
@ -200,22 +219,22 @@ proc runFile(f: string) =
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
compiled = compiler.compile(tree, f)
compiled = compiler.compile(tree, f, tokenizer.getLines(), input)
when debugCompiler:
styledEcho fgCyan, "Compilation step:"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
disassembleChunk(compiled, f)
styledEcho fgCyan, "Compilation step:\n"
debugger.disassembleChunk(compiled, f)
echo ""
serializer.dumpFile(compiled, input, f, splitFile(f).name & ".pbc")
serialized = serializer.loadFile(splitFile(f).name & ".pbc")
var path = splitFile(f).dir
if path.len() > 0:
path &= "/"
path &= splitFile(f).name & ".pbc"
serializer.dumpFile(compiled, f, path)
serialized = serializer.loadFile(path)
if fromString:
discard tryRemoveFile("<string>.pbc")
when debugSerializer:
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
styledEcho fgCyan, "Serialization step: "
styledEcho fgBlue, &"\t- File hash: ", fgYellow, serialized.fileHash, fgBlue, " (", if hashMatches: fgGreen else: fgRed, if hashMatches: "OK" else: "Fail", fgBlue, ")"
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.peonVer.major}.{serialized.peonVer.minor}.{serialized.peonVer.patch}", fgBlue, " (commit ", fgYellow, serialized.commitHash[0..8], fgBlue, ") on branch ", fgYellow, serialized.peonBranch
styledEcho fgBlue, "\t- Peon version: ", fgYellow, &"{serialized.version.major}.{serialized.version.minor}.{serialized.version.patch}", fgBlue, " (commit ", fgYellow, serialized.commit[0..8], fgBlue, ") on branch ", fgYellow, serialized.branch
stdout.styledWriteLine(fgBlue, "\t- Compilation date & time: ", fgYellow, fromUnix(serialized.compileDate).format("d/M/yyyy HH:mm:ss"))
stdout.styledWrite(fgBlue, &"\t- Constants segment: ")
if serialized.chunk.consts == compiled.consts:
@ -232,40 +251,49 @@ proc runFile(f: string) =
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
when debugRuntime:
styledEcho fgCyan, "\n\nExecution step: "
stdout.styledWrite(fgBlue, "\t- CFI segment: ")
if serialized.chunk.cfi == compiled.cfi:
styledEcho fgGreen, "OK"
else:
styledEcho fgRed, "Corrupted"
vm.run(serialized.chunk)
except LexingError:
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
var exc = LexingError(getCurrentException())
if exc.lexeme == "":
exc.line -= 1
let relPos = exc.lexer.getRelPos(exc.line)
let line = exc.lexer.getSource().splitLines()[exc.line - 1].strip(chars={'\n'})
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme}'",
fgYellow, &"'{exc.file.extractFilename()}'", fgRed, ", line ", fgYellow, $exc.line, fgRed, " at ", fgYellow, &"'{exc.lexeme.escape()}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line
let relPos = tokenizer.getRelPos(lineNo)
var lineNo = exc.token.line
if exc.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.parser.getRelPos(lineNo)
let fn = parser.getCurrentFunction()
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
let line = exc.parser.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
stderr.styledWriteLine(fgRed, "A fatal error occurred while parsing ", fgYellow, &"'{exc.file}'", fgRed, ", module ",
fgYellow, &"'{exc.file}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgYellow, &"'{exc.module}'", fgRed, ", line ", fgYellow, $lineNo, fgRed, " at ", fgYellow, &"'{lexeme}'",
fgRed, ": ", fgGreen , getCurrentExceptionMsg())
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError:
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line
let relPos = tokenizer.getRelPos(lineNo)
let line = tokenizer.getSource().splitLines()[lineNo - 1].strip()
var fn = compiler.getCurrentFunction()
var lineNo = exc.node.token.line
if exc.node.token.kind == EndOfFile:
lineNo -= 1
let relPos = exc.compiler.getRelPos(lineNo)
let line = exc.compiler.getSource().splitLines()[lineNo - 1].strip(chars={'\n'})
var fn = exc.compiler.getCurrentFunction()
var fnMsg = ""
if fn != nil and fn.kind == funDecl:
fnMsg &= &"in function '{FunDecl(fn).name.token.lexeme}'"
@ -283,73 +311,59 @@ proc runFile(f: string) =
stderr.styledWriteLine(fgRed, "An error occurred while trying to read ", fgYellow, &"'{f}'", fgGreen, &": {osErrorMsg(osLastError())} [errno {osLastError()}]")
when isMainModule:
setControlCHook(proc () {.noconv.} = quit(0))
let args = commandLineParams()
if args.len() == 0:
var optParser = initOptParser(commandLineParams())
var file: string = ""
var fromString: bool = false
var interactive: bool = false
for kind, key, value in optParser.getopt():
case kind:
of cmdArgument:
file = key
of cmdLongOption:
case key:
of "help":
echo HELP_MESSAGE
quit()
of "version":
echo PEON_VERSION_STRING
quit()
of "string":
file = key
fromString = true
of "interactive":
interactive = true
else:
echo &"error: unkown option '{key}'"
quit()
of cmdShortOption:
case key:
of "h":
echo HELP_MESSAGE
quit()
of "v":
echo PEON_VERSION_STRING
quit()
of "s":
file = key
fromString = true
of "i":
interactive = true
else:
echo &"error: unkown option '{key}'"
quit()
else:
echo "usage: peon [options] [filename.pn]"
quit()
# TODO: Use interactive
if file == "":
repl()
else:
runFile(args[0])
runFile(file, interactive, fromString)
proc fillSymbolTable(tokenizer: Lexer) =
## Initializes the Lexer's symbol
## table with the builtin symbols
## and keywords
# 1-byte symbols
tokenizer.symbols.addSymbol("{", LeftBrace)
tokenizer.symbols.addSymbol("}", RightBrace)
tokenizer.symbols.addSymbol("(", LeftParen)
tokenizer.symbols.addSymbol(")", RightParen)
tokenizer.symbols.addSymbol("[", LeftBracket)
tokenizer.symbols.addSymbol("]", RightBracket)
tokenizer.symbols.addSymbol(".", Dot)
tokenizer.symbols.addSymbol(",", Comma)
tokenizer.symbols.addSymbol(";", Semicolon)
# Keywords
tokenizer.symbols.addKeyword("type", TokenType.Type)
tokenizer.symbols.addKeyword("enum", Enum)
tokenizer.symbols.addKeyword("case", Case)
tokenizer.symbols.addKeyword("operator", Operator)
tokenizer.symbols.addKeyword("generator", Generator)
tokenizer.symbols.addKeyword("fn", TokenType.Function)
tokenizer.symbols.addKeyword("coroutine", Coroutine)
tokenizer.symbols.addKeyword("break", TokenType.Break)
tokenizer.symbols.addKeyword("continue", Continue)
tokenizer.symbols.addKeyword("while", While)
tokenizer.symbols.addKeyword("for", For)
tokenizer.symbols.addKeyword("foreach", Foreach)
tokenizer.symbols.addKeyword("if", If)
tokenizer.symbols.addKeyword("else", Else)
tokenizer.symbols.addKeyword("await", TokenType.Await)
tokenizer.symbols.addKeyword("defer", Defer)
tokenizer.symbols.addKeyword("try", Try)
tokenizer.symbols.addKeyword("except", Except)
tokenizer.symbols.addKeyword("finally", Finally)
tokenizer.symbols.addKeyword("raise", TokenType.Raise)
tokenizer.symbols.addKeyword("assert", TokenType.Assert)
tokenizer.symbols.addKeyword("const", Const)
tokenizer.symbols.addKeyword("let", Let)
tokenizer.symbols.addKeyword("var", Var)
tokenizer.symbols.addKeyword("import", Import)
tokenizer.symbols.addKeyword("yield", TokenType.Yield)
tokenizer.symbols.addKeyword("return", TokenType.Return)
# These are more like expressions with a reserved
# name that produce a value of a builtin type,
# but we don't need to care about that until
# we're in the parsing/ compilation steps so
# it's fine
tokenizer.symbols.addKeyword("nan", NotANumber)
tokenizer.symbols.addKeyword("inf", Infinity)
tokenizer.symbols.addKeyword("nil", TokenType.Nil)
tokenizer.symbols.addKeyword("true", True)
tokenizer.symbols.addKeyword("false", False)
tokenizer.symbols.addKeyword("ref", Ref)
tokenizer.symbols.addKeyword("ptr", Ptr)
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":"]:
tokenizer.symbols.addSymbol(sym, Symbol)
proc getLineEditor: LineEditor =
result = newLineEditor()

View File

@ -15,29 +15,29 @@
## Memory allocator from JAPL
import segfaults
import std/segfaults
import ../config
when DEBUG_TRACE_ALLOCATION:
import strformat
when debugMem:
import std/strformat
proc reallocate*(p: pointer, oldSize: int, newSize: int): pointer =
## Wrapper around realloc/dealloc
## Simple wrapper around realloc/dealloc
try:
if newSize == 0 and p != nil:
when DEBUG_TRACE_ALLOCATION:
when debugMem:
if oldSize > 1:
echo &"DEBUG - Memory manager: Deallocating {oldSize} bytes"
else:
echo "DEBUG - Memory manager: Deallocating 1 byte"
dealloc(p)
return nil
when DEBUG_TRACE_ALLOCATION:
when debugMem:
if pointr == nil and newSize == 0:
echo &"DEBUG - Memory manager: Warning, asked to dealloc() nil pointer from {oldSize} to {newSize} bytes, ignoring request"
if oldSize > 0 and p != nil or oldSize == 0:
when DEBUG_TRACE_ALLOCATION:
when debugMem:
if oldSize == 0:
if newSize > 1:
echo &"DEBUG - Memory manager: Allocating {newSize} bytes of memory"
@ -46,40 +46,35 @@ proc reallocate*(p: pointer, oldSize: int, newSize: int): pointer =
else:
echo &"DEBUG - Memory manager: Resizing {oldSize} bytes of memory to {newSize} bytes"
result = realloc(p, newSize)
when DEBUG_TRACE_ALLOCATION:
when debugMem:
if oldSize > 0 and pointr == nil:
echo &"DEBUG - Memory manager: Warning, asked to realloc() nil pointer from {oldSize} to {newSize} bytes, ignoring request"
except NilAccessDefect:
stderr.write("JAPL: could not manage memory, segmentation fault\n")
stderr.write("Peon: could not manage memory, segmentation fault\n")
quit(139) # For now, there's not much we can do if we can't get the memory we need, so we exit
template resizeArray*(kind: untyped, pointr: pointer, oldCount,
newCount: int): untyped =
## Handy macro (in the C sense of macro, not nim's) to resize a dynamic array
cast[ptr UncheckedArray[kind]](reallocate(pointr, sizeof(kind) * oldCount,
sizeof(kind) * newCount))
template resizeArray*(kind: untyped, p: pointer, oldCount, newCount: int): untyped =
## Handy template to resize a dynamic array
cast[ptr UncheckedArray[kind]](reallocate(p, sizeof(kind) * oldCount, sizeof(kind) * newCount))
template freeArray*(kind: untyped, pointr: pointer, oldCount: int): untyped =
template freeArray*(kind: untyped, p: pointer, oldCount: int): untyped =
## Frees a dynamic array
reallocate(pointr, sizeof(kind) * oldCount, 0)
reallocate(p, sizeof(kind) * oldCount, 0)
template free*(kind: untyped, pointr: pointer): untyped =
template free*(kind: untyped, p: pointer): untyped =
## Frees a pointer by reallocating its
## size to 0
reallocate(pointr, sizeof(kind), 0)
reallocate(p, sizeof(kind), 0)
template growCapacity*(capacity: int): untyped =
## Handy macro used to calculate how much
## Handy template used to calculate how much
## more memory is needed when reallocating
## dynamic arrays
if capacity < 8:
8
else:
capacity * ARRAY_GROW_FACTOR
if capacity < 8: 8 else: capacity * HeapGrowFactor
template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped =

View File

@ -1,193 +0,0 @@
## Builtin arithmetic operators for Peon
operator `+`(a, b: int): int {
#pragma[magic: AddInt64, pure]
return;
}
operator `+`(a, b: uint): uint {
#pragma[magic: AddUInt64, pure]
return;
}
operator `+`(a, b: int32): int32 {
#pragma[magic: AddInt32, pure]
return;
}
operator `+`(a, b: uint32): uint32 {
#pragma[magic: AddUInt32, pure]
return;
}
operator `+`(a, b: int16): int16 {
#pragma[magic: AddInt16, pure]
return;
}
operator `+`(a, b: uint16): uint16 {
#pragma[magic: AddUInt16, pure]
return;
}
operator `+`(a, b: int8): int8 {
#pragma[magic: AddInt8, pure]
return;
}
operator `+`(a, b: uint8): uint8 {
#pragma[magic: AddUInt8, pure]
return;
}
operator `-`(a, b: int): int {
#pragma[magic: SubInt64, pure]
return;
}
operator `-`(a, b: uint): uint {
#pragma[magic: SubUInt64, pure]
return;
}
operator `-`(a, b: int32): int32 {
#pragma[magic: SubInt32, pure]
return;
}
operator `-`(a, b: uint32): uint32 {
#pragma[magic: SubUInt32, pure]
return;
}
operator `-`(a, b: int16): int16 {
#pragma[magic: SubInt16, pure]
return;
}
operator `-`(a, b: uint16): uint16 {
#pragma[magic: SubUInt16, pure]
return;
}
operator `-`(a, b: int8): int8 {
#pragma[magic: SubInt8, pure]
return;
}
operator `-`(a, b: uint8): uint8 {
#pragma[magic: SubUInt8, pure]
return;
}
operator `*`(a, b: int): int {
#pragma[magic: MulInt64, pure]
return;
}
operator `*`(a, b: uint): uint {
#pragma[magic: MulUInt64, pure]
return;
}
operator `*`(a, b: int32): int32 {
#pragma[magic: MulInt32, pure]
return;
}
operator `*`(a, b: uint32): uint32 {
#pragma[magic: MulUInt32, pure]
return;
}
operator `*`(a, b: int16): int16 {
#pragma[magic: MulInt16, pure]
return;
}
operator `*`(a, b: uint16): uint16 {
#pragma[magic: MulUInt16, pure]
return;
}
operator `*`(a, b: int8): int8 {
#pragma[magic: MulInt8, pure]
return;
}
operator `*`(a, b: uint8): uint8 {
#pragma[magic: MulUInt8, pure]
return;
}
operator `/`(a, b: int): int {
#pragma[magic: DivInt64, pure]
return;
}
operator `/`(a, b: uint): uint {
#pragma[magic: DivUInt64, pure]
return;
}
operator `/`(a, b: int32): int32 {
#pragma[magic: DivInt32, pure]
return;
}
operator `/`(a, b: uint32): uint32 {
#pragma[magic: DivUInt32, pure]
return;
}
operator `/`(a, b: int16): int16 {
#pragma[magic: DivInt16, pure]
return;
}
operator `/`(a, b: uint16): uint16 {
#pragma[magic: DivUInt16, pure]
return;
}
operator `/`(a, b: int8): int8 {
#pragma[magic: DivInt8, pure]
return;
}
operator `/`(a, b: uint8): uint8 {
#pragma[magic: DivUInt8, pure]
return;
}

View File

@ -16,9 +16,27 @@ import ../frontend/meta/bytecode
import multibyte
import strformat
import strutils
import terminal
import std/strformat
import std/strutils
import std/terminal
type
CFIElement = ref object
start, stop, bottom, argc: int
name: string
started, stopped: bool
Debugger* = ref object
chunk: Chunk
cfiData: seq[CFIElement]
current: int
proc newDebugger*: Debugger =
## Initializes a new, empty
## debugger object
new(result)
result.cfiData = @[]
proc nl = stdout.write("\n")
@ -44,131 +62,195 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
nl()
proc simpleInstruction(instruction: OpCode, offset: int): int =
printInstruction(instruction)
nl()
return offset + 1
proc checkFrameStart(self: Debugger, n: int) =
## Checks if a call frame begins at the given
## bytecode offset
for i, e in self.cfiData:
if n == e.start and not (e.started or e.stopped):
e.started = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
proc stackTripleInstruction(instruction: OpCode, chunk: Chunk,
offset: int): int =
proc checkFrameEnd(self: Debugger, n: int) =
## Checks if a call frame ends at the given
## bytecode offset
for i, e in self.cfiData:
if n == e.stop and e.started and not e.stopped:
e.stopped = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
proc simpleInstruction(self: Debugger, instruction: OpCode) =
## Debugs simple instructions
printInstruction(instruction, true)
self.current += 1
if instruction == Return:
printDebug("Void: ")
if self.chunk.code[self.current] == 0:
stdout.styledWriteLine(fgYellow, "Yes")
else:
stdout.styledWriteLine(fgYellow, "No")
self.current += 1
self.checkFrameEnd(self.current - 2)
self.checkFrameEnd(self.current - 1)
self.checkFrameEnd(self.current)
proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
offset + 3]].fromTriple()
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to index ")
stdout.styledWriteLine(fgYellow, &"{slot}")
return offset + 4
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
self.current += 4
proc stackDoubleInstruction(instruction: OpCode, chunk: Chunk,
offset: int): int =
proc stackDoubleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a single value on the stack using a 16-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble()
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
printInstruction(instruction)
stdout.write(&", points to index ")
stdout.styledWrite(fgGreen, &", points to index ")
stdout.styledWriteLine(fgYellow, &"{slot}")
return offset + 3
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
self.current += 3
proc argumentDoubleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc argumentDoubleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble()
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", has argument ")
stdout.styledWriteLine(fgYellow, $slot)
return offset + 3
stdout.styledWriteLine(fgGreen, &", has argument ", fgYellow, $slot)
self.current += 3
proc argumentTripleInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc argumentTripleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, ", has argument ")
stdout.styledWriteLine(fgYellow, $slot)
return offset + 4
stdout.styledWriteLine(fgGreen, ", has argument ", fgYellow, $slot)
self.current += 4
proc callInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc callInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls
var slot = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[offset + 3]].fromTriple()
var args = [chunk.code[offset + 4], chunk.code[offset + 5], chunk.code[offset + 6]].fromTriple()
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
if args > 1:
stdout.styledWrite(fgYellow, "s")
nl()
return offset + 7
styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2)
self.current += 4
proc constantInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc functionInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
styledEcho fgGreen, &", loads function at address ", fgYellow, $address
self.current += 4
proc loadAddressInstruction(self: Debugger, instruction: OpCode) =
## Debugs LoadReturnAddress instructions
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3],
self.chunk.code[self.current + 4]].fromQuad()
printInstruction(instruction)
styledEcho fgGreen, &", loads address ", fgYellow, $address
self.current += 5
proc constantInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on the constant table
var constant = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
offset + 3]].fromTriple()
var size: uint
if instruction == LoadString:
size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
self.current += 3
var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant)
nl()
printDebug("Operand: ")
stdout.styledWriteLine(fgYellow, &"{chunk.consts[constant]}")
return offset + 4
self.current += 4
if instruction == LoadString:
stdout.styledWriteLine(fgGreen, " of length ", fgYellow, $size)
else:
stdout.write("\n")
proc jumpInstruction(instruction: OpCode, chunk: Chunk, offset: int): int =
proc jumpInstruction(self: Debugger, instruction: OpCode) =
## Debugs jumps
var jump: int
case instruction:
of Jump, JumpIfFalse, JumpIfTrue, JumpIfFalsePop, JumpForwards, JumpBackwards:
jump = [chunk.code[offset + 1], chunk.code[offset + 2]].fromDouble().int()
of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop,
LongJumpForwards, LongJumpBackwards:
jump = [chunk.code[offset + 1], chunk.code[offset + 2], chunk.code[
offset + 3]].fromTriple().int()
else:
discard # Unreachable
var orig = self.current
var jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple().int()
printInstruction(instruction, true)
printDebug("Jump size: ")
stdout.styledWrite(fgYellow, $jump)
nl()
return offset + 3
self.current += 4
for i in countup(orig, self.current + 1):
self.checkFrameStart(i)
proc disassembleInstruction*(chunk: Chunk, offset: int): int =
proc disassembleInstruction*(self: Debugger) =
## Takes one bytecode instruction and prints it
printDebug("Offset: ")
stdout.styledWriteLine(fgYellow, $offset)
stdout.styledWriteLine(fgYellow, $(self.current))
printDebug("Line: ")
stdout.styledWriteLine(fgYellow, &"{chunk.getLine(offset)}")
var opcode = OpCode(chunk.code[offset])
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
var opcode = OpCode(self.chunk.code[self.current])
case opcode:
of simpleInstructions:
result = simpleInstruction(opcode, offset)
self.simpleInstruction(opcode)
of constantInstructions:
result = constantInstruction(opcode, chunk, offset)
self.constantInstruction(opcode)
of stackDoubleInstructions:
result = stackDoubleInstruction(opcode, chunk, offset)
self.stackDoubleInstruction(opcode)
of stackTripleInstructions:
result = stackTripleInstruction(opcode, chunk, offset)
self.stackTripleInstruction(opcode)
of argumentDoubleInstructions:
result = argumentDoubleInstruction(opcode, chunk, offset)
self.argumentDoubleInstruction(opcode)
of argumentTripleInstructions:
result = argumentTripleInstruction(opcode, chunk, offset)
self.argumentTripleInstruction(opcode)
of callInstructions:
result = callInstruction(opcode, chunk, offset)
self.callInstruction(opcode)
of jumpInstructions:
result = jumpInstruction(opcode, chunk, offset)
self.jumpInstruction(opcode)
of LoadFunction:
self.functionInstruction(opcode)
of LoadReturnAddress:
self.loadAddressInstruction(opcode)
else:
echo &"DEBUG - Unknown opcode {opcode} at index {offset}"
result = offset + 1
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
self.current += 1
proc disassembleChunk*(chunk: Chunk, name: string) =
## Takes a chunk of bytecode, and prints it
echo &"==== Peon Bytecode Debugger - Chunk '{name}' ====\n"
var index = 0
while index < chunk.code.len:
index = disassembleInstruction(chunk, index)
proc parseCFIData(self: Debugger) =
## Parses CFI information in the chunk
var
start, stop, argc: int
name: string
idx = 0
size = 0
while idx < len(self.chunk.cfi) - 1:
start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
argc = int(self.chunk.cfi[idx])
inc(idx)
size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble())
idx += 2
name = self.chunk.cfi[idx..<idx + size].fromBytes()
inc(idx, size)
self.cfiData.add(CFIElement(start: start, stop: stop,
argc: argc, name: name))
proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) =
## Takes a chunk of bytecode and prints it
self.chunk = chunk
styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session starting - Chunk '{name}' ====\n"
self.current = 0
self.parseCFIData()
while self.current < self.chunk.code.len:
self.disassembleInstruction()
echo ""
echo &"==== Debug session ended - Chunk '{name}' ===="
styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session ended - Chunk '{name}' ===="

View File

@ -12,8 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
## Utilities to convert from/to our 16-bit and 24-bit representations
## of numbers
## Utilities to handle multibyte sequences
proc toDouble*(input: int | uint | uint16): array[2, uint8] =
@ -58,4 +57,29 @@ proc fromQuad*(input: array[4, uint8]): uint =
proc fromLong*(input: array[8, uint8]): uint =
## Rebuilts the output of toQuad into
## an uint
copyMem(result.addr, unsafeAddr(input), sizeof(uint64))
copyMem(result.addr, unsafeAddr(input), sizeof(uint64))
proc toBytes*(s: string): seq[byte] =
## Converts a string into a sequence
## of bytes
for c in s:
result.add(byte(c))
proc toBytes*(s: int): array[8, uint8] =
## Converts
result = cast[array[8, uint8]](s)
proc fromBytes*(input: seq[byte]): string =
## Converts a sequence of bytes to
## a string
for b in input:
result.add(char(b))
proc extend*[T](s: var seq[T], a: openarray[T]) =
## Extends s with the elements of a
for e in a:
s.add(e)

View File

@ -13,14 +13,14 @@
# limitations under the License.
import ../frontend/meta/errors
import ../frontend/meta/bytecode
import ../config
import multibyte
import ../frontend/compiler
import multibyte
import ../config
import strformat
import strutils
import nimSHA2
import times
import std/strformat
import std/strutils
import std/times
export ast
@ -35,16 +35,15 @@ type
## the Serializer.read*
## procedures to store
## metadata
fileHash*: string
peonVer*: tuple[major, minor, patch: int]
peonBranch*: string
commitHash*: string
version*: tuple[major, minor, patch: int]
branch*: string
commit*: string
compileDate*: int
chunk*: Chunk
proc `$`*(self: Serialized): string =
result = &"Serialized(fileHash={self.fileHash}, version={self.peonVer.major}.{self.peonVer.minor}.{self.peonVer.patch}, branch={self.peonBranch}), commitHash={self.commitHash}, date={self.compileDate}, chunk={self.chunk[]}"
result = &"Serialized(version={self.version.major}.{self.version.minor}.{self.version.patch}, branch={self.branch}), commitHash={self.commit}, date={self.compileDate}, chunk={self.chunk[]}"
proc error(self: Serializer, message: string) =
@ -61,44 +60,16 @@ proc newSerializer*(self: Serializer = nil): Serializer =
result.chunk = nil
## Basic routines and helpers to convert various objects from and to to their byte representation
proc toBytes(self: Serializer, s: string): seq[byte] =
for c in s:
result.add(byte(c))
proc toBytes(self: Serializer, s: int): array[8, uint8] =
result = cast[array[8, uint8]](s)
proc toBytes(self: Serializer, d: SHA256Digest): seq[byte] =
for b in d:
result.add(b)
proc bytesToString(self: Serializer, input: seq[byte]): string =
for b in input:
result.add(char(b))
proc extend[T](s: var seq[T], a: openarray[T]) =
## Extends s with the elements of a
for e in a:
s.add(e)
proc writeHeaders(self: Serializer, stream: var seq[byte], file: string) =
proc writeHeaders(self: Serializer, stream: var seq[byte]) =
## Writes the Peon bytecode headers in-place into a byte stream
stream.extend(self.toBytes(BYTECODE_MARKER))
stream.extend(PeonBytecodeMarker.toBytes())
stream.add(byte(PEON_VERSION.major))
stream.add(byte(PEON_VERSION.minor))
stream.add(byte(PEON_VERSION.patch))
stream.add(byte(len(PEON_BRANCH)))
stream.extend(self.toBytes(PEON_BRANCH))
stream.extend(self.toBytes(PEON_COMMIT_HASH))
stream.extend(self.toBytes(getTime().toUnixFloat().int()))
stream.extend(self.toBytes(computeSHA256(file)))
stream.extend(PEON_BRANCH.toBytes())
stream.extend(PEON_COMMIT_HASH.toBytes())
stream.extend(getTime().toUnixFloat().int().toBytes())
proc writeLineData(self: Serializer, stream: var seq[byte]) =
@ -107,7 +78,14 @@ proc writeLineData(self: Serializer, stream: var seq[byte]) =
stream.extend(len(self.chunk.lines).toQuad())
for b in self.chunk.lines:
stream.extend(b.toTriple())
proc writeCFIData(self: Serializer, stream: var seq[byte]) =
## Writes Call Frame Information for debugging
## functions
stream.extend(len(self.chunk.cfi).toQuad())
stream.extend(self.chunk.cfi)
proc writeConstants(self: Serializer, stream: var seq[byte]) =
## Writes the constants table in-place into the
@ -128,28 +106,27 @@ proc readHeaders(self: Serializer, stream: seq[byte], serialized: Serialized): i
## Reads the bytecode headers from a given stream
## of bytes
var stream = stream
if stream[0..<len(BYTECODE_MARKER)] != self.toBytes(BYTECODE_MARKER):
if stream[0..<len(PeonBytecodeMarker)] != PeonBytecodeMarker.toBytes():
self.error("malformed bytecode marker")
result += len(BYTECODE_MARKER)
stream = stream[len(BYTECODE_MARKER)..^1]
serialized.peonVer = (major: int(stream[0]), minor: int(stream[1]), patch: int(stream[2]))
result += len(PeonBytecodeMarker)
stream = stream[len(PeonBytecodeMarker)..^1]
serialized.version = (major: int(stream[0]), minor: int(stream[1]), patch: int(stream[2]))
stream = stream[3..^1]
result += 3
let branchLength = stream[0]
stream = stream[1..^1]
result += 1
serialized.peonBranch = self.bytesToString(stream[0..<branchLength])
serialized.branch = stream[0..<branchLength].fromBytes()
stream = stream[branchLength..^1]
result += int(branchLength)
serialized.commitHash = self.bytesToString(stream[0..<40]).toLowerAscii()
serialized.commit = stream[0..<40].fromBytes().toLowerAscii()
stream = stream[40..^1]
result += 40
serialized.compileDate = int(fromLong([stream[0], stream[1], stream[2],
stream[3], stream[4], stream[5], stream[6], stream[7]]))
stream = stream[8..^1]
result += 8
serialized.fileHash = self.bytesToString(stream[0..<32]).toHex().toLowerAscii()
result += 32
proc readLineData(self: Serializer, stream: seq[byte]): int =
@ -164,6 +141,17 @@ proc readLineData(self: Serializer, stream: seq[byte]): int =
stream = stream[3..^1]
proc readCFIData(self: Serializer, stream: seq[byte]): int =
## Reads Call Frame Information from a stream
## of bytes
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
result += 4
var stream = stream[4..^1]
for i in countup(0, int(size) - 1):
self.chunk.cfi.add(stream[i])
inc(result)
proc readConstants(self: Serializer, stream: seq[byte]): int =
## Reads the constant table from the given stream
## of bytes
@ -186,24 +174,22 @@ proc readCode(self: Serializer, stream: seq[byte]): int =
return int(size)
proc dumpBytes*(self: Serializer, chunk: Chunk, file, filename: string): seq[byte] =
proc dumpBytes*(self: Serializer, chunk: Chunk, filename: string): seq[byte] =
## Dumps the given bytecode and file to a sequence of bytes and returns it.
## The file argument must be the actual file's content and is needed to
## compute its SHA256 hash.
self.file = file
self.filename = filename
self.chunk = chunk
self.writeHeaders(result, self.file)
self.writeHeaders(result)
self.writeLineData(result)
self.writeCFIData(result)
self.writeConstants(result)
self.writeCode(result)
proc dumpFile*(self: Serializer, chunk: Chunk, file, filename, dest: string) =
proc dumpFile*(self: Serializer, chunk: Chunk, filename, dest: string) =
## Dumps the result of dumpBytes to a file at dest
var fp = open(dest, fmWrite)
defer: fp.close()
let data = self.dumpBytes(chunk, file, filename)
let data = self.dumpBytes(chunk, filename)
discard fp.writeBytes(data, 0, len(data))
@ -218,6 +204,7 @@ proc loadBytes*(self: Serializer, stream: seq[byte]): Serialized =
try:
stream = stream[self.readHeaders(stream, result)..^1]
stream = stream[self.readLineData(stream)..^1]
stream = stream[self.readCFIData(stream)..^1]
stream = stream[self.readConstants(stream)..^1]
stream = stream[self.readCode(stream)..^1]
except IndexDefect:

63
src/util/symbols.nim Normal file
View File

@ -0,0 +1,63 @@
import ../frontend/lexer
proc fillSymbolTable*(tokenizer: Lexer) =
## Initializes the Lexer's symbol
## table with the builtin symbols
## and keywords
# 1-byte symbols
tokenizer.symbols.addSymbol("{", LeftBrace)
tokenizer.symbols.addSymbol("}", RightBrace)
tokenizer.symbols.addSymbol("(", LeftParen)
tokenizer.symbols.addSymbol(")", RightParen)
tokenizer.symbols.addSymbol("[", LeftBracket)
tokenizer.symbols.addSymbol("]", RightBracket)
tokenizer.symbols.addSymbol(".", Dot)
tokenizer.symbols.addSymbol(",", Comma)
tokenizer.symbols.addSymbol(";", Semicolon)
# tokenizer.symbols.addSymbol("\n", Semicolon) # TODO: Broken
# Keywords
tokenizer.symbols.addKeyword("type", TokenType.Type)
tokenizer.symbols.addKeyword("enum", Enum)
tokenizer.symbols.addKeyword("case", Case)
tokenizer.symbols.addKeyword("operator", Operator)
tokenizer.symbols.addKeyword("generator", Generator)
tokenizer.symbols.addKeyword("fn", TokenType.Function)
tokenizer.symbols.addKeyword("coroutine", Coroutine)
tokenizer.symbols.addKeyword("break", TokenType.Break)
tokenizer.symbols.addKeyword("continue", Continue)
tokenizer.symbols.addKeyword("while", While)
tokenizer.symbols.addKeyword("for", For)
tokenizer.symbols.addKeyword("foreach", Foreach)
tokenizer.symbols.addKeyword("if", If)
tokenizer.symbols.addKeyword("else", Else)
tokenizer.symbols.addKeyword("await", TokenType.Await)
tokenizer.symbols.addKeyword("defer", Defer)
tokenizer.symbols.addKeyword("try", Try)
tokenizer.symbols.addKeyword("except", Except)
tokenizer.symbols.addKeyword("finally", Finally)
tokenizer.symbols.addKeyword("raise", TokenType.Raise)
tokenizer.symbols.addKeyword("assert", TokenType.Assert)
tokenizer.symbols.addKeyword("const", Const)
tokenizer.symbols.addKeyword("let", Let)
tokenizer.symbols.addKeyword("var", TokenType.Var)
tokenizer.symbols.addKeyword("import", Import)
tokenizer.symbols.addKeyword("yield", TokenType.Yield)
tokenizer.symbols.addKeyword("return", TokenType.Return)
tokenizer.symbols.addKeyword("object", Object)
# These are more like expressions with a reserved
# name that produce a value of a builtin type,
# but we don't need to care about that until
# we're in the parsing/ compilation steps so
# it's fine
tokenizer.symbols.addKeyword("nan", NotANumber)
tokenizer.symbols.addKeyword("inf", Infinity)
tokenizer.symbols.addKeyword("nil", TokenType.Nil)
tokenizer.symbols.addKeyword("true", True)
tokenizer.symbols.addKeyword("false", False)
tokenizer.symbols.addKeyword("ref", TokenType.Ref)
tokenizer.symbols.addKeyword("ptr", TokenType.Ptr)
for sym in [">", "<", "=", "~", "/", "+", "-", "_", "*", "?", "@", ":", "==", "!=",
">=", "<=", "+=", "-=", "/=", "*=", "**=", "!"]:
tokenizer.symbols.addSymbol(sym, Symbol)

19
tests/calls.pn Normal file
View File

@ -0,0 +1,19 @@
# Tests simple calls
import std;
fn noReturn(n: int) {
var n = n;
var `17` = 17;
return;
}
fn fooBar(a, b: int): int {
var baz = 38;
return a;
}
noReturn(1);
print(fooBar(1, 3)); # 1

26
tests/chainedCalls.pn Normal file
View File

@ -0,0 +1,26 @@
fn first(a, b: int): int {
return a;
}
fn second(a, b: int): int {
return b;
}
fn last(a, b, c: int): int {
return c;
}
fn middle(a, b, c: int): int {
return last(a, c, b);
}
fn first(a, b, c: int): int {
return middle(b, a, c);
}
first(1, 2, 3);
var x = first(second(1, 2), 3);

16
tests/closures.pn Normal file
View File

@ -0,0 +1,16 @@
# Tests closures
import std;
fn makeClosure(n: int): fn: int {
let n = n; # Workaround
fn inner: int {
return n;
}
return inner;
}
var closure = makeClosure(1)();
print(closure); # 1
print(makeClosure(2)()); # 2

152
tests/comparisons.pn Normal file
View File

@ -0,0 +1,152 @@
# Tests that comparisons work
import std;
# int64
print(3 > 2); # true
print(2 < 3); # true
print(3 < 2); # false
print(2 > 3); # false
print(2 != 3); # true
print(3 != 2); # true
print(3 == 2); # false
print(2 == 3); # false
print(2 <= 3); # true
print(3 >= 2); # true
print(2 >= 3); # false
print(3 <= 2); # false
# uint64
var x = 3'u64;
var y = 2'u64;
print(x > y); # true
print(y < x); # true
print(x < y); # false
print(y > x); # false
print(y != x); # true
print(x != y); # true
print(x == y); # false
print(y == x); # false
print(y <= x); # true
print(x >= y); # true
print(y >= x); # false
print(x <= y); # false
# int32
var x1 = 3'i32;
var y1 = 2'i32;
print(x1 > y1); # true
print(y1 < x1); # true
print(x1 < y1); # false
print(y1 > x1); # false
print(y1 != x1); # true
print(x1 != y1); # true
print(x1 == y1); # false
print(y1 == x1); # false
print(y1 <= x1); # true
print(x1 >= y1); # true
print(y1 >= x1); # false
print(x1 <= y1); # false
# uint32
var x2 = 3'u32;
var y2 = 2'u32;
print(x2 > y2); # true
print(y2 < x2); # true
print(x2 < y2); # false
print(y2 > x2); # false
print(y2 != x2); # true
print(x2 != y2); # true
print(x2 == y2); # false
print(y2 == x2); # false
print(y2 <= x2); # true
print(x2 >= y2); # true
print(y2 >= x2); # false
print(x2 <= y2); # false
# int16
var x3 = 3'i16;
var y3 = 2'i16;
print(x3 > y3); # true
print(y3 < x3); # true
print(x3 < y3); # false
print(y3 > x3); # false
print(y3 != x3); # true
print(x3 != y3); # true
print(x3 == y3); # false
print(y3 == x3); # false
print(y3 <= x3); # true
print(x3 >= y3); # true
print(y3 >= x3); # false
print(x3 <= y3); # false
# uint16
var x4 = 3'u16;
var y4 = 2'u16;
print(x4 > y4); # true
print(y4 < x4); # true
print(x4 < y4); # false
print(y4 > x4); # false
print(y4 != x4); # true
print(x4 != y4); # true
print(x4 == y4); # false
print(y4 == x4); # false
print(y4 <= x4); # true
print(x4 >= y4); # true
print(y4 >= x4); # false
print(x4 <= y4); # false
# int8
var x5 = 3'i8;
var y5 = 2'i8;
print(x5 > y5); # true
print(y5 < x5); # true
print(x5 < y5); # false
print(y5 > x5); # false
print(y5 != x5); # true
print(x5 != y5); # true
print(x5 == y5); # false
print(y5 == x5); # false
print(y5 <= x5); # true
print(x5 >= y5); # true
print(y5 >= x5); # false
print(x5 <= y5); # false
# uint8
var x6 = 3'u8;
var y6 = 2'u8;
print(x6 > y6); # true
print(y6 < x6); # true
print(x6 < y6); # false
print(y6 > x6); # false
print(y6 != x6); # true
print(x6 != y6); # true
print(x6 == y6); # false
print(y6 == x6); # false
print(y6 <= x6); # true
print(x6 >= y6); # true
print(y6 >= x6); # false
print(x6 <= y6); # false
# float64
var x7 = 3.0;
var y7 = 2.0;
print(x7 > y7); # true
print(y7 < x7); # true
print(x7 < y7); # false
print(y7 > x7); # false
print(y7 != x7); # true
print(x7 != y7); # true
print(x7 == y7); # false
print(y7 == x7); # false
print(y7 <= x7); # true
print(x7 >= y7); # true
print(y7 >= x7); # false
print(x7 <= y7); # false
# float32
var x8 = 3'f32;
var y8 = 2'f32;
print(x8 > y8); # true
print(y8 < x8); # true
print(x8 < y8); # false
print(y8 > x8); # false
print(y8 != x8); # true
print(x8 != y8); # true
print(x8 == y8); # false
print(y8 == x8); # false
print(y8 <= x8); # true
print(x8 >= y8); # true
print(y8 >= x8); # false
print(x8 <= y8); # false

View File

@ -1,5 +1,10 @@
# Tests operator dispatching
operator `+`(a: int): int {
return a;
}
+1; # Works: defined for int64
+1'i32; # Will not work

52
tests/fib.pn Normal file
View File

@ -0,0 +1,52 @@
operator `<`(a, b: int): bool {
#pragma[magic: "LessThan", pure]
}
operator `+`(a, b: int): int {
#pragma[magic: "Add", pure]
}
operator `-`(a, b: int): int {
#pragma[magic: "Subtract", pure]
}
operator `-`(a, b: float): float {
#pragma[magic: "SubtractFloat64", pure]
}
fn clock: float {
#pragma[magic: "SysClock64"]
}
fn print(x: int) {
#pragma[magic: "PrintInt64"]
}
fn print(x: float) {
#pragma[magic: "PrintFloat64"]
}
fn print(x: string) {
#pragma[magic: "PrintString"]
}
fn fib(n: int): int {
if n < 2 {
return n;
}
return fib(n - 2) + fib(n - 1);
}
print("Computing the value of fib(30)");
var x = clock();
print(fib(30));
print(clock() - x);
print("Done!");

29
tests/functionObj.pn Normal file
View File

@ -0,0 +1,29 @@
# Tests first class functions
import std;
fn outer: fn (n: int): int {
fn inner(n: int): int {
return n;
}
return inner;
}
fn getAdder(a, b: int): fn: int64 {
var x = a;
var y = b;
fn adder: int {
return x + y;
}
return adder;
}
print(outer()(1)); # 1
var a = 1;
var b = 2;
var adder = getAdder(a, b);
print(adder()); # 3

16
tests/generics.pn Normal file
View File

@ -0,0 +1,16 @@
operator `+`(a, b: int): int {
#pragma[magic: "AddInt64", pure]
}
operator `+`(a, b: int32): int32 {
#pragma[magic: "AddInt64", pure]
}
fn sum[T](a, b: T): T {
return a + b;
}
sum(1, 2);
sum(1'i32, 2'i32);

19
tests/mutable.pn Normal file
View File

@ -0,0 +1,19 @@
# Tests var parameters
import std;
operator `+=`(a: var int, b: int) {
a = a + b;
}
fn plusOne(x: var int): int {
x += 1;
return x;
}
var x = 5;
print(plusOne(x));
# plusOne(38); # If you uncomment this, the compiler errors out!

22
tests/nestedCalls.pn Normal file
View File

@ -0,0 +1,22 @@
# Tests nested calls
import std;
fn outer: int {
fn inner: int {
return 69420;
}
return inner();
}
fn outerTwo(n: int): int {
fn inner(z: int): int {
return z;
}
return inner(n);
}
print(outerTwo(5));
print(outer());

10
tests/operators.pn Normal file
View File

@ -0,0 +1,10 @@
# Tests the creation and use of custom operators
import std;
operator `sum`(a, b: int): int {
return a + b;
}
print(2 sum 2); # 4

14
tests/scopes.pn Normal file
View File

@ -0,0 +1,14 @@
# Tests local scopes
import std;
var x = 5;
{
var x = 55;
{
var x = 22;
print(x);
}
print(x);
}
print(x);

560
tests/std.pn Normal file
View File

@ -0,0 +1,560 @@
## The peon standard library
# Builtin arithmetic operators for Peon
# Note: Most of these do nothing on their own. All they do
# is serve as placeholders for emitting specific VM
# instructions. They're implemented this way because:
# - They tie into the existing type system nicely
# - It makes the implementation easier and more flexible
operator `+`*(a, b: int): int {
#pragma[magic: "SignedAdd", pure]
}
operator `+`*(a, b: uint64): uint64 {
#pragma[magic: "Add", pure]
}
operator `+`*(a, b: int32): int32 {
#pragma[magic: "SignedAdd", pure]
}
operator `+`*(a, b: uint32): uint32 {
#pragma[magic: "Add", pure]
}
operator `+`*(a, b: int16): int16 {
#pragma[magic: "SignedAdd", pure]
}
operator `+`*(a, b: uint16): uint16 {
#pragma[magic: "Add", pure]
}
operator `+`*(a, b: int8): int8 {
#pragma[magic: "Add", pure]
}
operator `+`*(a, b: uint8): uint8 {
#pragma[magic: "AddUInt8", pure]
}
operator `+`*(a, b: float64): float64 {
#pragma[magic: "AddFloat64", pure]
}
operator `+`*(a, b: float32): float32 {
#pragma[magic: "AddFloat32", pure]
}
operator `-`*(a, b: int): int {
#pragma[magic: "SubInt64", pure]
}
operator `-`*(a, b: uint64): uint64 {
#pragma[magic: "SubUInt64", pure]
}
operator `-`*(a, b: int32): int32 {
#pragma[magic: "SubInt32", pure]
}
operator `-`*(a, b: uint32): uint32 {
#pragma[magic: "SubUInt32", pure]
}
operator `-`*(a, b: int16): int16 {
#pragma[magic: "SubInt16", pure]
}
operator `-`*(a, b: uint16): uint16 {
#pragma[magic: "SubUInt16", pure]
}
operator `-`*(a, b: int8): int8 {
#pragma[magic: "SubInt8", pure]
}
operator `-`*(a, b: uint8): uint8 {
#pragma[magic: "SubUInt8", pure]
}
operator `-`*(a, b: float64): float64 {
#pragma[magic: "SubFloat64", pure]
}
operator `-`*(a, b: float32): float32 {
#pragma[magic: "SubFloat32", pure]
}
operator `*`*(a, b: int): int {
#pragma[magic: "SignedMultiply", pure]
}
operator `*`*(a, b: uint64): uint64 {
#pragma[magic: "MulUInt64", pure]
}
operator `*`*(a, b: int32): int32 {
#pragma[magic: "SignedMultiply", pure]
}
operator `*`*(a, b: uint32): uint32 {
#pragma[magic: "MulUInt32", pure]
}
operator `*`*(a, b: int16): int16 {
#pragma[magic: "SignedMultiply", pure]
}
operator `*`*(a, b: uint16): uint16 {
#pragma[magic: "MulUInt16", pure]
}
operator `*`*(a, b: int8): int8 {
#pragma[magic: "SignedMultiply", pure]
}
operator `*`*(a, b: uint8): uint8 {
#pragma[magic: "MulUInt8", pure]
}
operator `*`*(a, b: float64): float64 {
#pragma[magic: "MulFloat64", pure]
}
operator `*`*(a, b: float32): float32 {
#pragma[magic: "MulFloat32", pure]
}
operator `/`*(a, b: int): int {
#pragma[magic: "DivInt64", pure]
}
operator `/`*(a, b: uint64): uint64 {
#pragma[magic: "DivUInt64", pure]
}
operator `/`*(a, b: int32): int32 {
#pragma[magic: "DivInt32", pure]
}
operator `/`*(a, b: uint32): uint32 {
#pragma[magic: "DivUInt32", pure]
}
operator `/`*(a, b: int16): int16 {
#pragma[magic: "DivInt16", pure]
}
operator `/`*(a, b: uint16): uint16 {
#pragma[magic: "DivUInt16", pure]
}
operator `/`*(a, b: int8): int8 {
#pragma[magic: "DivInt8", pure]
}
operator `/`*(a, b: uint8): uint8 {
#pragma[magic: "DivUInt8", pure]
}
operator `/`*(a, b: float64): float64 {
#pragma[magic: "DivFloat64", pure]
}
operator `/`*(a, b: float32): float32 {
#pragma[magic: "DivFloat32", pure]
}
operator `**`*(a, b: int64): int64 {
#pragma[magic: "PowInt64", pure]
}
# Comparison operators
operator `>`*(a, b: int): bool {
#pragma[magic: "GreaterThanInt64", pure]
}
operator `<`*(a, b: int): bool {
#pragma[magic: "LessThanInt64", pure]
}
operator `==`*(a, b: int): bool {
#pragma[magic: "EqualInt64", pure]
}
operator `!=`*(a, b: int): bool {
#pragma[magic: "NotEqualInt64", pure]
}
operator `>`*(a, b: uint64): bool {
#pragma[magic: "GreaterThanUInt64", pure]
}
operator `<`*(a, b: uint64): bool {
#pragma[magic: "LessThanUInt64", pure]
}
operator `==`*(a, b: uint64): bool {
#pragma[magic: "EqualUInt64", pure]
}
operator `!=`*(a, b: uint64): bool {
#pragma[magic: "NotEqualUInt64", pure]
}
operator `>`*(a, b: int32): bool {
#pragma[magic: "GreaterThanInt32", pure]
}
operator `<`*(a, b: int32): bool {
#pragma[magic: "LessThanInt32", pure]
}
operator `==`*(a, b: int32): bool {
#pragma[magic: "EqualInt32", pure]
}
operator `!=`*(a, b: int32): bool {
#pragma[magic: "NotEqualInt32", pure]
}
operator `>`*(a, b: uint32): bool {
#pragma[magic: "GreaterThanUInt32", pure]
}
operator `<`*(a, b: uint32): bool {
#pragma[magic: "LessThanUInt32", pure]
}
operator `==`*(a, b: uint32): bool {
#pragma[magic: "EqualUInt32", pure]
}
operator `!=`*(a, b: uint32): bool {
#pragma[magic: "NotEqualUInt32", pure]
}
operator `>`*(a, b: int16): bool {
#pragma[magic: "GreaterThanInt16", pure]
}
operator `<`*(a, b: int16): bool {
#pragma[magic: "LessThanInt16", pure]
}
operator `==`*(a, b: int16): bool {
#pragma[magic: "EqualInt16", pure]
}
operator `!=`*(a, b: int16): bool {
#pragma[magic: "NotEqualInt16", pure]
}
operator `>`*(a, b: uint16): bool {
#pragma[magic: "GreaterThanUInt16", pure]
}
operator `<`*(a, b: uint16): bool {
#pragma[magic: "LessThanUInt16", pure]
}
operator `==`*(a, b: uint16): bool {
#pragma[magic: "EqualUInt16", pure]
}
operator `!=`*(a, b: uint16): bool {
#pragma[magic: "NotEqualUInt16", pure]
}
operator `>`*(a, b: int8): bool {
#pragma[magic: "GreaterThanInt8", pure]
}
operator `<`*(a, b: int8): bool {
#pragma[magic: "LessThanInt8", pure]
}
operator `==`*(a, b: int8): bool {
#pragma[magic: "EqualInt8", pure]
}
operator `!=`*(a, b: int8): bool {
#pragma[magic: "NotEqualInt8", pure]
}
operator `>`*(a, b: uint8): bool {
#pragma[magic: "GreaterThanUInt8", pure]
}
operator `<`*(a, b: uint8): bool {
#pragma[magic: "LessThanUInt8", pure]
}
operator `==`*(a, b: uint8): bool {
#pragma[magic: "EqualUInt8", pure]
}
operator `!=`*(a, b: uint8): bool {
#pragma[magic: "NotEqualUInt8", pure]
}
operator `>`*(a, b: float): bool {
#pragma[magic: "GreaterThanFloat64", pure]
}
operator `<`*(a, b: float): bool {
#pragma[magic: "LessThanFloat64", pure]
}
operator `==`*(a, b: float): bool {
#pragma[magic: "EqualFloat64", pure]
}
operator `!=`*(a, b: float): bool {
#pragma[magic: "NotEqualFloat64", pure]
}
operator `>`*(a, b: float32): bool {
#pragma[magic: "GreaterThanFloat32", pure]
}
operator `<`*(a, b: float32): bool {
#pragma[magic: "LessThanFloat32", pure]
}
operator `==`*(a, b: float32): bool {
#pragma[magic: "EqualFloat32", pure]
}
operator `!=`*(a, b: float32): bool {
#pragma[magic: "NotEqualFloat32", pure]
}
operator `>=`*(a, b: int): bool {
#pragma[magic: "GreaterOrEqualInt64", pure]
}
operator `<=`*(a, b: int): bool {
#pragma[magic: "LessOrEqualInt64", pure]
}
operator `>=`*(a, b: uint64): bool {
#pragma[magic: "GreaterOrEqualUInt64", pure]
}
operator `<=`*(a, b: uint64): bool {
#pragma[magic: "LessOrEqualUInt64", pure]
}
operator `>=`*(a, b: int32): bool {
#pragma[magic: "GreaterOrEqualInt32", pure]
}
operator `<=`*(a, b: int32): bool {
#pragma[magic: "LessOrEqualInt32", pure]
}
operator `>=`*(a, b: uint32): bool {
#pragma[magic: "GreaterOrEqualUInt32", pure]
}
operator `<=`*(a, b: uint32): bool {
#pragma[magic: "LessOrEqualUInt32", pure]
}
operator `>=`*(a, b: int16): bool {
#pragma[magic: "GreaterOrEqualInt16", pure]
}
operator `<=`*(a, b: int16): bool {
#pragma[magic: "LessOrEqualInt16", pure]
}
operator `>=`*(a, b: uint16): bool {
#pragma[magic: "GreaterOrEqualUInt16", pure]
}
operator `<=`*(a, b: uint16): bool {
#pragma[magic: "LessOrEqualUInt16", pure]
}
operator `>=`*(a, b: int8): bool {
#pragma[magic: "GreaterOrEqualInt8", pure]
}
operator `<=`*(a, b: int8): bool {
#pragma[magic: "LessOrEqualInt8", pure]
}
operator `>=`*(a, b: uint8): bool {
#pragma[magic: "GreaterOrEqualUInt8", pure]
}
operator `<=`*(a, b: uint8): bool {
#pragma[magic: "LessOrEqualUInt8", pure]
}
operator `>=`*(a, b: float): bool {
#pragma[magic: "GreaterOrEqualFloat64", pure]
}
operator `<=`*(a, b: float): bool {
#pragma[magic: "LessOrEqualFloat64", pure]
}
operator `>=`*(a, b: float32): bool {
#pragma[magic: "GreaterOrEqualFloat32", pure]
}
operator `<=`*(a, b: float32): bool {
#pragma[magic: "LessOrEqualFloat32", pure]
}
operator `and`*(a, b: bool): bool {
#pragma[magic: "LogicalAnd", pure]
}
operator `or`*(a, b: bool): bool {
#pragma[magic: "LogicalOr", pure]
}
# Assignment operators
operator `=`[T: Any](a: var T, b: T) {
#pragma[magic: "GenericAssign"]
}
# Some useful builtins
fn clock*: float {
#pragma[magic: "SysClock64", pure]
}
# TODO: Replace with generics
fn print*(x: float) {
#pragma[magic: "GenericPrint"]
}
fn print*(x: int) {
#pragma[magic: "GenericPrint"]
}
fn print*(x: string) {
#pragma[magic: "GenericPrint"]
}
fn print*(x: bool) {
#pragma[magic: "GenericPrint"]
}

View File

@ -1 +0,0 @@
# TODO