Compare commits
283 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 813da3e1aa | |||
| 750ebd1f99 | |||
| f55f27726c | |||
| a0d75b3c70 | |||
| 414a9d6194 | |||
| 8013ccb1da | |||
| 645fc3a457 | |||
| 2eaf0a9b0f | |||
| 3ab18c7b14 | |||
| 1660dc8b53 | |||
| b3f427d80d | |||
| 6901b9a6c9 | |||
| c9d4b9965c | |||
| ff1ef1352a | |||
| 53d3606c7e | |||
| 83fb5222a2 | |||
| 3b813da1cf | |||
| 8b9b012bcc | |||
| 6c055a0435 | |||
| be84a8dfe2 | |||
| 185f3896c5 | |||
| 60a0fe8907 | |||
| 7cdb81e0bf | |||
| 18a4b33c4e | |||
| eb33775314 | |||
| 617a658de0 | |||
| 6ebbcc45a3 | |||
| b718b35097 | |||
| af30246e0c | |||
| efc9fe1406 | |||
| c9a34e2259 | |||
| 0c24a7609e | |||
| 88e9794952 | |||
| b589392b9e | |||
| 21819b2d62 | |||
| 4aec343b6c | |||
| 9a75226491 | |||
| d2ac1eeb8e | |||
| f8596806ee | |||
| 4957536e23 | |||
| 5a867ac627 | |||
| 991d29e3e6 | |||
| accb7f9fb4 | |||
| 63dfd33e5e | |||
| 9bb115f732 | |||
| 8875b6968b | |||
| 97d16c1184 | |||
| 47c5d49069 | |||
| 2c92f829e1 | |||
| 5b101d763e | |||
| 81c95ff69d | |||
| 88100b128a | |||
| 3a0f11ebb4 | |||
| f9790b99ce | |||
| dde52f9d8a | |||
| cda4bee6ee | |||
| 9a10fadada | |||
| 8eefbc8a0c | |||
| e24823924a | |||
| c0c03a4110 | |||
| b0d9c15d33 | |||
| 24e5d8081c | |||
| 42aef306a9 | |||
| 6f27d07829 | |||
| b32ea9f309 | |||
| 49a825aaf9 | |||
| baa81b1aa9 | |||
| 9553edef9c | |||
| 66155fa213 | |||
| 547229e150 | |||
| 211744535e | |||
| 3a24fbf6e1 | |||
| 842f041a50 | |||
| 7408a24a12 | |||
| 09fc6d5279 | |||
| f25e81cd09 | |||
| 48072f0dd1 | |||
| 522fc3e64b | |||
| f4ce6ad9f1 | |||
| 3f35502694 | |||
| f06218b9cd | |||
| 1ae3fcbf73 | |||
| abae97b6e5 | |||
| fbb7e1bc54 | |||
| 57fe9bb00d | |||
| 914ee6fcfa | |||
| ba9418f365 | |||
| 98208f4bb5 | |||
| 5a0012d73a | |||
| 24d111e52d | |||
| 4b21e61df0 | |||
| d3b59eb0da | |||
| 9fe6d6b218 | |||
| 3a82593e4d | |||
| 639250f028 | |||
| 02dfc996b4 | |||
| 76d89fe0ad | |||
| 15aea7c032 | |||
| 574502bf3e | |||
| 258968d7a4 | |||
| 7a8c415b3f | |||
| 344c265918 | |||
| f2660b95eb | |||
| fe64e876bd | |||
| 1006b6e216 | |||
| a1cfc095a7 | |||
| 7c054db9e6 | |||
| 72f4e4c143 | |||
| e2a284d9ce | |||
| c646904407 | |||
| 470836a390 | |||
| 212eca1825 | |||
| b93ea5006c | |||
| 336616a1bf | |||
| 63cc530899 | |||
| 3c0a50c4cd | |||
| 481d17f040 | |||
| bfed4e23f3 | |||
| 002651f95d | |||
| a1f6f147c5 | |||
| f9715c2016 | |||
| 6c293cfc62 | |||
| 05451af8d7 | |||
| 730353342d | |||
| 513d8f130c | |||
| 13daf95933 | |||
| 9141102f2e | |||
| ce03a342c9 | |||
| c6d8766bc3 | |||
| 14696833fd | |||
| 6f16c31f24 | |||
| 3aee2ba664 | |||
| b55192e513 | |||
| 90ffe9b40e | |||
| 9de9c85bea | |||
| 23eb3e45df | |||
| b84a70cc34 | |||
| 6d25beea03 | |||
| e96f87ff45 | |||
| 0192b60338 | |||
| c3a737eae5 | |||
| cc4ff3f6d8 | |||
| 153ab54be6 | |||
| 24cfe7f539 | |||
| 3ca816439e | |||
| 9cb138a7d6 | |||
| 9e2cbb1f59 | |||
| b092b8ce50 | |||
| 4faa0c0476 | |||
| 223db840c8 | |||
| 8b5cc3b493 | |||
| 04c799954c | |||
| 3e17916a4a | |||
| a28053d4e9 | |||
| 93fce94e9c | |||
| 7be63c8ccc | |||
| cf9affe190 | |||
| fce71a6cda | |||
| 5f4dfdccc5 | |||
| 476a79b746 | |||
| 5f75b5f1a3 | |||
| 1a36c14247 | |||
| 61a105db2d | |||
| 2324150fd5 | |||
| 03dce296cb | |||
| 62ca7a1fb7 | |||
| 12c6ac938c | |||
| 58cecafb3b | |||
| bb2e85e350 | |||
| 431893bf60 | |||
| 6cc331d462 | |||
| 0947430c29 | |||
| 1695f9d1c9 | |||
| 79c4374a1f | |||
| 37fb3927a6 | |||
| 0b559ecb74 | |||
| 7d4ea4881f | |||
| b29a87840d | |||
| b2b2ca7e53 | |||
| 34577ecfe1 | |||
| 7398898a61 | |||
| 2f9489d5fd | |||
| 04f0653595 | |||
| cd2113594d | |||
| be7e4ddd18 | |||
| b74aa63c1c | |||
| 935993ee8a | |||
| 436bd3ffca | |||
| 1608a13b43 | |||
| 1925d41940 | |||
| 5588986042 | |||
| 32727f986c | |||
| 92955d56dd | |||
| ef72415c21 | |||
| 7173f7770f | |||
| 3ad53c5e0e | |||
| 3cb2132bfa | |||
| feab02e790 | |||
| 163c8aa7f6 | |||
| d19ca1bcee | |||
| b30a092ab8 | |||
| e3bb7c0d25 | |||
| c5206daaea | |||
| d22b18ed17 | |||
| 2ee19c7c66 | |||
| 3148a56ce0 | |||
| 5b17c5e1e9 | |||
| ddd91e389e | |||
| 5d37d06343 | |||
| 787a1cca84 | |||
| 98ea0e5884 | |||
| c3ee92fcef | |||
| 09e4cb7b03 | |||
| 425ef7e3e0 | |||
| 694e262ee2 | |||
| a51c5591ee | |||
| 80734563b9 | |||
| 1ad6bdff70 | |||
| bfc8fe3717 | |||
| 7b1dbf25ff | |||
| c1d72adb71 | |||
| 87793b694a | |||
| 8d1e4d647b | |||
| e6fa345fe6 | |||
| ca4073a5ae | |||
| 9f45925072 | |||
| 0779798347 | |||
| 93f771dc8d | |||
| 4bcf8e84a9 | |||
| 14653a303f | |||
| d62ee2a9a3 | |||
| ff13b5cf38 | |||
| 956ebbeb28 | |||
| 4805c6757a | |||
| ad44eeac48 | |||
| d19a90f9bd | |||
| 29f5647e31 | |||
| 196b5f86f1 | |||
| 5cf2e70b7d | |||
| a0d616f412 | |||
| ab1c9b941f | |||
| 5db3e407b1 | |||
| 71c065a6c4 | |||
| 7b453bc35f | |||
| 53b0fc158c | |||
| a9759384cd | |||
| 7b8ff8f873 | |||
| 8d6bdb88b4 | |||
| c1f2e19e55 | |||
| 72cad02501 | |||
| 57fb1ece81 | |||
| 3d1d3b3b77 | |||
| c518960171 | |||
| 0504f4af8b | |||
| 7d92101c1f | |||
| c180984120 | |||
| a2625fa6d4 | |||
| 4567484038 | |||
| ac89f80b5b | |||
| 78320e53bb | |||
| 84f26d83c8 | |||
| ad6f1c3067 | |||
| e35af3d84b | |||
| 083ee950dd | |||
| 47ac1c5b30 | |||
| 898b8efc04 | |||
| b00a6838be | |||
| eca3350c64 | |||
| 5fd933a15e | |||
| 81417e7f32 | |||
| 023cf9c8b5 | |||
| 65087b18bd | |||
| e65555ca8a | |||
| 0ed676d79b | |||
| e6ad46f1ff | |||
| 190294e5d9 | |||
| 2370d5dc83 | |||
| a912b6a29c | |||
| 82f63013d8 | |||
| 0b8bf4119f | |||
| 607dc2c22a | |||
| fbcb2e0331 | |||
| 361fa78ffb |
@@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: krgamestudios
|
||||
ko_fi: krgamestudios
|
||||
custom: ["https://www.paypal.com/donate/?hosted_button_id=73Q82T2ZHV8AA"]
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve
|
||||
labels: bug
|
||||
---
|
||||
|
||||
## Describe the bug
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## To Reproduce
|
||||
|
||||
Steps to reproduce the behaviour:
|
||||
|
||||
1. run `git pull` on the repository
|
||||
2. run `make rebuild` on the code
|
||||
3. ...
|
||||
|
||||
You can include some screenshots here if you'd like!
|
||||
|
||||
## Versioning
|
||||
|
||||
- OS: [for example MacOS, Windows, iOS, Android]
|
||||
- Version: [What version of Toy was this running?]
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
### Describe the feature you’d like
|
||||
|
||||
A clear and concise description of what you’d like to be able to do with Toy.
|
||||
|
||||
### Describe alternatives you've considered
|
||||
|
||||
A clear and concise description of any alternative solutions or workarounds you've considered.
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context about the feature request here.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a Question
|
||||
labels: question
|
||||
---
|
||||
|
||||
### How can I help?
|
||||
|
||||
I'm always here to help with any inquiries you have regarding Toy and its related projects.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Continuous Integration v2.x
|
||||
|
||||
#trigger when these occur
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v2
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
branches:
|
||||
- v2
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
#CI workflows across all supported platforms
|
||||
standard:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platforms:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
runs-on: ${{ matrix.platforms }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Run all tests
|
||||
run: make tests
|
||||
|
||||
gdb:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install GDB if not present
|
||||
run: sudo apt update && sudo apt install gdb
|
||||
- name: Run all tests under gdb
|
||||
run: make tests-gdb
|
||||
@@ -0,0 +1,57 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
*.log
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
#mdbook files
|
||||
book
|
||||
mdbook
|
||||
@@ -0,0 +1,2 @@
|
||||
This folder is full of development notes, and are probably out of date. Check the actual docs for the correct info.
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
This file is messy and confusing, and makes sense to nobody but me - so don't worry about understanding it too much - better docs will come later.
|
||||
|
||||
===
|
||||
|
||||
SECD = State, Environment, Control, Dump
|
||||
|
||||
The idea of "Landin's SECD Machine" is to store the working memory in S, the variable-value bindings in E, the code/instructions in C, and the program stack in D.
|
||||
|
||||
Notes:
|
||||
DEFINE = DECLARE + SET
|
||||
|
||||
The environment, denoted with an E, is created on routine start, and destroyed on routine end - however, it uses the parent routine's environment as the starting point for it's creation, so closures work as expected
|
||||
|
||||
unlike version 1, identifiers are not a valid datatype - they're just an index representing a symbol, like "standard::clock"
|
||||
|
||||
meta opcodes - EOF, PASS, ERROR,
|
||||
|
||||
a "value" can be of any valid datatype, and may point to various parts of memory to define it's value
|
||||
|
||||
Symbols will be awkward... I suspect the symbol table might need to be rebuilt on startup, as the order of the modules will not necessarily be the same each time
|
||||
|
||||
The various instances of S could be the same array in memory, simply marked as "unused"? You could stick C on there as a value before "pushing" for a new routine
|
||||
|
||||
Things to consider later:
|
||||
type cast?
|
||||
rest parameter?
|
||||
index access and assign?
|
||||
|
||||
===
|
||||
|
||||
//variable instructions
|
||||
READ
|
||||
read one value from C onto S
|
||||
LOAD
|
||||
read one value from .data onto S
|
||||
DECLARE
|
||||
read two words from C, create a new entry in E with the key E[SYMBOL(word1)], the type defined by word2, the value 'null'
|
||||
DEFINE
|
||||
read one word from C, saves the pre-existing key E[SYMBOL(word)] to the value S(0), popping S(0)
|
||||
ACCESS
|
||||
read one word from C, finds the pre-existing value of E[SYMBOL(word)], leaves the value on S
|
||||
|
||||
//arithmetic instructions
|
||||
ADD
|
||||
performs the specified operation on S(-1) and S(0), popping both, leaving the result on S
|
||||
SUBTRACT
|
||||
performs the specified operation on S(-1) and S(0), popping both, leaving the result on S
|
||||
MULTIPLY
|
||||
performs the specified operation on S(-1) and S(0), popping both, leaving the result on S
|
||||
DIVIDE
|
||||
performs the specified operation on S(-1) and S(0), popping both, leaving the result on S
|
||||
MODULO
|
||||
performs the specified operation on S(-1) and S(0), popping both, leaving the result on S
|
||||
|
||||
|
||||
//comparison instructions
|
||||
COMPARE_EQUAL
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on equality
|
||||
COMPARE_LESS
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on comparison
|
||||
COMPARE_LESS_EQUAL
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on comparison
|
||||
COMPARE_GREATER
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on comparison
|
||||
COMPARE_GREATER_EQUAL
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on comparison
|
||||
|
||||
|
||||
//logical instructions
|
||||
AND
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on truthiness
|
||||
OR
|
||||
pops S(-1) and S(0), replacing it with TRUE or FALSE, depending on truthiness
|
||||
TRUTHY
|
||||
pops S(0), replacing it with TRUE or FALSE, depending on truthiness
|
||||
NEGATE
|
||||
pops S(0), replacing it with TRUE or FALSE, depending on truthiness
|
||||
|
||||
|
||||
//control instructions
|
||||
JUMP
|
||||
read one value from C, and move the program counter to that location (relative to the current position)
|
||||
JUMP_IF_FALSE
|
||||
read one value from C, pops S(0), and move the program counter to that location (relative to the current position) if the popped value is falsy
|
||||
FN_CALL
|
||||
*read a list of arguments specified in C into 'A', store (S, E, C, D) as D, push S, move the stack pointer to the specified routine, push a new E based on the contents of 'A'
|
||||
FN_RETURN
|
||||
*read a list of return values specified in C into 'R', pop S, restore (S, E, C, D) from D(0) popping it, store the contents of 'R' in E or S based on the next few parts of C
|
||||
|
||||
//various action instructions
|
||||
ASSERT
|
||||
if S(-1) is falsy, print S(0) and exit
|
||||
PRINT
|
||||
pop S(0), and print the output
|
||||
IMPORT
|
||||
//invoke an external library into the current scope
|
||||
CONCAT
|
||||
//combine two strings
|
||||
SCOPE_BEGIN
|
||||
//push an inner environment to E, which should be automatically popped at the routine's end
|
||||
SCOPE_END
|
||||
//pop an inner environment from E, only if it was created with SCOPE_BEGIN
|
||||
|
||||
===
|
||||
|
||||
FN_CALL
|
||||
read word: read the following N arguments
|
||||
|
||||
for 0 to N do:
|
||||
read word as match: # this allows literals and identifiers as arguments
|
||||
stack: then pop S(0) into 'A'
|
||||
**env: then read word, load E[SYMBOL(word)] into 'A'
|
||||
|
||||
read word:
|
||||
determine where the routine is (is it new or is it a value?) and hold it for a moment
|
||||
push E and C into a frame marker on S
|
||||
jump C to the routine
|
||||
|
||||
read word:
|
||||
read the following N parameter names, storing each member of 'A' as their value in E[SYMBOL(name)]
|
||||
|
||||
continue
|
||||
|
||||
FN_RETURN
|
||||
read word: read the following N return values
|
||||
|
||||
for 0 to N do:
|
||||
read word as match: # this allows literals and identifiers as arguments
|
||||
stack: then pop S(0) into 'R'
|
||||
**env: then read word, load E[SYMBOL(word)] into 'R'
|
||||
|
||||
pop E and S
|
||||
extract and restore E and C from the frame marker on S
|
||||
|
||||
read word: read the following N storage locations for the values within `R`
|
||||
|
||||
for 0 to N do:
|
||||
read word as match: # you're effectively reversing the prior reads
|
||||
stack: then push from 'R' onto S
|
||||
**env: then read word, save 'R' into E[SYMBOL(word)]
|
||||
|
||||
**This could work by listing the sources as e.g. "SSSExS" - three stacks and one environment variable loaded onto the stack, then one more stack for a total of four values
|
||||
|
||||
Notes:
|
||||
the bytecode of a funtion call would look like:
|
||||
|
||||
FN_CALL N [stack|env word]... N [stack|env word]...
|
||||
|
||||
the value of C stored in D points to the second N, while it waits to pick up where it left off
|
||||
|
||||
===
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
The bytecode format
|
||||
|
||||
===
|
||||
|
||||
NOTE: This datestamp header is currently not implemented
|
||||
|
||||
There are four components in the datestamp header:
|
||||
|
||||
TOY_VERSION_MAJOR
|
||||
TOY_VERSION_MINOR
|
||||
TOY_VERSION_PATCH
|
||||
TOY_VERSION_BUILD
|
||||
|
||||
The first three are each one unsigned byte, and the fourth is a null terminated C-string.
|
||||
|
||||
* Under no circumstance, should you ever run bytecode whose major version is different
|
||||
* Under no circumstance, should you ever run bytecode whose minor version is above the interpreter’s minor version
|
||||
* You may, at your own risk, attempt to run bytecode whose patch version is different from the interpreter’s patch version
|
||||
* You may, at your own risk, attempt to run bytecode whose build version is different from the interpreter’s build version
|
||||
|
||||
An additional note: The contents of the build string may be anything, such as:
|
||||
|
||||
* the compilation date and time of the interpreter
|
||||
* a marker identifying the current fork and/or branch
|
||||
* identification information, such as the developer's copyright
|
||||
* a link to Risk Astley's "Never Gonna Give You Up" on YouTube
|
||||
|
||||
Please note that in the final bytecode, if the null terminator of TOY_VERSION_BUILD is not 4-byte aligned, extra space will be allocated to round out the header's size to a multiple of 4. The contents of the extra bytes are undefined.
|
||||
|
||||
===
|
||||
|
||||
Bytecode Format Structure
|
||||
|
||||
.header:
|
||||
N total size # size of this routine, including all data and subroutines
|
||||
N .jumps count # the number of entries in the jump table (should be data count + routine count)
|
||||
N .param count # the number of parameter fields expected (a secondary jump table, used for subroutine parameters)
|
||||
N .data count # the number of data fields present
|
||||
N .subs count # the number of subroutines present
|
||||
.code start # absolute address of .code; mandatory
|
||||
.param start # absolute addess of .param; omitted if not needed
|
||||
.datatable start # absolute address of .datatable; omitted if not needed
|
||||
.data start # absolute address of .data; omitted if not needed
|
||||
.subs start # absolute address of .subs; omitted if not needed
|
||||
# additional metadata fields can be added later
|
||||
|
||||
.code:
|
||||
# opcode instructions read and 'executed' by the interpreter (aligned to 4-byte widths)
|
||||
[READ, TOY_VALUE_STRING, Toy_StringType, stringLength] [jumpIndex]
|
||||
|
||||
.jumps:
|
||||
# a layer of indirection for quickly looking up values in .data and .subs
|
||||
0 -> {string, 0x00}
|
||||
4 -> {fn, 0xFF}
|
||||
|
||||
.param:
|
||||
# a list of names, stored in .data, to be used for any provided function arguments
|
||||
|
||||
.data:
|
||||
# data that can't be cleanly embedded into .code, such as strings
|
||||
"Hello world\0"
|
||||
|
||||
.subs:
|
||||
# an extension of .data, used exclusively for subroutines (they also follow this spec, recursively)
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t hash (uint32_t x) {
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x);
|
||||
return x;
|
||||
}
|
||||
|
||||
uint32_t unhash ( uint32_t x ) {
|
||||
x = (( x >> 16) ^ x) * 0x119de1f3;
|
||||
x = (( x >> 16) ^ x) * 0x119de1f3;
|
||||
x = (( x >> 16) ^ x);
|
||||
return x;
|
||||
}
|
||||
|
||||
int main() {
|
||||
//I legit didn't know this algorithm could be reversed. Neat.
|
||||
uint32_t value = 42;
|
||||
printf("%u %u %u", value, hash(value), unhash(hash(value)));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
The default version of GCC that ships on Raspian has an issue. The file '/usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so' has a faulty implementation of 'memcpy()' and possibly 'memset()'. Changing to the newer versions doens't work, as they're just symlinks to v7.
|
||||
|
||||
To resolve this, download and build this shared object:
|
||||
|
||||
https://github.com/simonjhall/copies-and-fills
|
||||
|
||||
Then, set the linker's preload value to point to that '.so' file (You may need to edit '/etc/ld.so.preload')
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"gruntfuggly.todo-tree"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"todo-tree.filtering.includeGlobs": [
|
||||
"**/repl/**",
|
||||
"**/scripts/**",
|
||||
"**/source/**",
|
||||
"**/tests/**",
|
||||
"**/tools/**",
|
||||
],
|
||||
"todo-tree.filtering.excludeGlobs": [
|
||||
"**/obj/**",
|
||||
"**/out/**",
|
||||
],
|
||||
"todo-tree.general.tags": [
|
||||
"URGENT",
|
||||
"BUG",
|
||||
"TODO",
|
||||
"WARN",
|
||||
"BUGFIX",
|
||||
"WONTFIX",
|
||||
"NOTE"
|
||||
],
|
||||
"todo-tree.highlights.customHighlight": {
|
||||
"URGENT": {
|
||||
"icon": "alert",
|
||||
"type": "text",
|
||||
"iconColour": "#FF0000",
|
||||
"foreground": "#FF0000"
|
||||
},
|
||||
"BUG": {
|
||||
"icon": "bug",
|
||||
"type": "text",
|
||||
"iconColour": "#FF0000",
|
||||
"foreground": "#FF0000"
|
||||
},
|
||||
"TODO": {
|
||||
"icon": "alert",
|
||||
"type": "text",
|
||||
"iconColour": "#FFFF00",
|
||||
"foreground": "#FFFF00"
|
||||
},
|
||||
"WARN": {
|
||||
"icon": "alert",
|
||||
"type": "text",
|
||||
"iconColour": "#FFA500",
|
||||
"foreground": "#FFA500"
|
||||
},
|
||||
"BUGFIX": {
|
||||
"icon": "bug",
|
||||
"type": "text",
|
||||
"iconColour": "#00A000",
|
||||
"foreground": "#00A000"
|
||||
},
|
||||
"WONTFIX": {
|
||||
"icon": "bug",
|
||||
"type": "text",
|
||||
"iconColour": "#B64949",
|
||||
"foreground": "#B64949"
|
||||
},
|
||||
"NOTE": {
|
||||
"icon": "alert",
|
||||
"type": "text",
|
||||
"iconColour": "#00A000",
|
||||
"foreground": "#00A000"
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
No hating on other people, OK?
|
||||
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2020-2026 Kayne Ruse, KR Game Studios
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -1,4 +1,75 @@
|
||||
This git branch is the (now deprecated) documentation website for The Toy Programming Language version 2.x, which can be found at [https://github.com/krgamestudios/Toy](https://github.com/krgamestudios/Toy).
|
||||
<p align="center">
|
||||
<image src="toylogo.png" alt="The Toy Logo" />
|
||||
</p>
|
||||
|
||||
Toy v2.x is still under active development, so this documentation will change and evolve over time, and may not reflect the current reference implementation.
|
||||
# Toy v2.x
|
||||
|
||||
The Toy Programming Language is an imperative, bytecode-interpreted, embeddable scripting language. Rather than functioning independently, it serves as part of another program, the "host". This design allows for straightforward customization by both the host's developers and end users, achieved by exposing program logic through external scripts.
|
||||
|
||||
This repository holds the reference implementation for Toy version 2.x, written in C - alpha testing is currently underway.
|
||||
|
||||
# Nifty Features
|
||||
|
||||
* Simple C-like syntax
|
||||
* Intermediate AST and bytecode representations
|
||||
* Strong, but optional type system
|
||||
* First-class functions and closures
|
||||
* Extensible with native C-bindings
|
||||
* Can re-direct output, error and assertion messages
|
||||
* Open-Source under the zlib license
|
||||
|
||||
# Syntax
|
||||
|
||||
```toy
|
||||
fn makeCounter() {
|
||||
var counter: Int = 0;
|
||||
|
||||
fn increment() {
|
||||
return ++counter;
|
||||
}
|
||||
|
||||
return increment;
|
||||
}
|
||||
|
||||
var tally = makeCounter();
|
||||
|
||||
while (true) {
|
||||
var result = tally();
|
||||
|
||||
print result; //prints 1 to 10
|
||||
|
||||
if (result >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Building
|
||||
|
||||
This project requires `gcc` and `make` by default, but should also work in other environments. Officially supported platforms include `linux`, `windows` and `macOS`, see `source/toy_common.h` for implementation details.
|
||||
|
||||
Run `make` in the root directory to build the shared library named `libToy.so` and a useable REPL named `repl.out`.
|
||||
|
||||
# Documentation
|
||||
|
||||
The contents of `docs/` is also available on the official website [toylang.com](https://toylang.com/).
|
||||
|
||||
# License
|
||||
|
||||
This source code is covered by the Zlib license (see [LICENSE](LICENSE) for details).
|
||||
|
||||
# Contributors and Special Thanks
|
||||
|
||||
@NishiOwO - Unofficial NetBSD support
|
||||
@Gipson62 - v1 docs spell checking
|
||||
@8051Enthusiast - `fixAlignment()` trick
|
||||
@hiperiondev - v1 Disassembler, v1 porting support and feedback
|
||||
@add00 - v1 Library support
|
||||
@gruelingpine185 - Unofficial v1 MacOS support
|
||||
@solar-mist - v1 Minor bugfixes
|
||||
Various Anons - Feedback
|
||||
@munificent - For [writing the book](http://craftinginterpreters.com/) that sparked my interest in langdev
|
||||
|
||||
# Patreon Supporters
|
||||
|
||||
You can show your support and be listed here by joining my [Patreon](https://patreon.com/krgamestudios).
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
title: The Toy Programming Language
|
||||
description: Documentation For The Toy Programming Language
|
||||
keywords: programming,coding
|
||||
author: Kayne Ruse (Ratstail91)
|
||||
@@ -1,8 +0,0 @@
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-WQ8Q1JV8E8"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-WQ8Q1JV8E8');
|
||||
</script>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!-- site information -->
|
||||
<meta name="description" content="{{ site.description }}" />
|
||||
<meta name="author" content="{{ site.author }}" />
|
||||
<meta name="keywords" content="{{ site.keywords }}" />
|
||||
|
||||
<!-- facebook -->
|
||||
<meta property="og:url" content="{{ site.url }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="{{ site.baseurl }}/assets/repo-preview.png" />
|
||||
<meta property="og:title" content="{{ page.title }}" />
|
||||
<meta property="og:description" content="{{ page.description }}" />
|
||||
|
||||
<!-- twitter -->
|
||||
<meta name="twitter:card" content="{{ site.title }}" />
|
||||
<meta name="twitter:url" content="{{ site.url}}" />
|
||||
<meta name="twitter:type" content="website" />
|
||||
<meta name="twitter:image" content="{{ site.baseurl }}/assets/repo-preview.png" />
|
||||
<meta name="twitter:title" content="{{ page.title }}" />
|
||||
<meta name="twitter:description" content="{{ page.description }}" />
|
||||
|
||||
<link rel="icon" href="{{ site.baseurl }}/favicon.png">
|
||||
@@ -1,21 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<!-- device settings -->
|
||||
<meta charset = "UTF-8" />
|
||||
<meta name="Content-Type" content="text/html" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- page title -->
|
||||
<title>{{ page.title }}</title>
|
||||
|
||||
{% include metadata.html %}
|
||||
{% include analytics.html %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{ content }}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 463 B |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
@@ -0,0 +1,9 @@
|
||||
[book]
|
||||
title = "The Toy Programming Language"
|
||||
authors = ["Kayne Ruse (Ratstail91)"]
|
||||
description = "Documentation For The Toy Programming Language"
|
||||
language = "en"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/krgamestudios/Toy"
|
||||
git-repository-icon = "fab-github"
|
||||
@@ -0,0 +1,3 @@
|
||||
# 404
|
||||
|
||||
Nobody here but us chickens!
|
||||
@@ -0,0 +1,47 @@
|
||||
<p align="center">
|
||||
<image src="img/toylogo.png" alt="The Toy Logo" />
|
||||
</p>
|
||||
|
||||
The Toy Programming Language is an imperative, bytecode-interpreted, embeddable scripting language. Rather than functioning independently, it serves as part of another program, the "host". This design allows for straightforward customization by both the host's developers and end users, achieved by exposing program logic through external scripts.
|
||||
|
||||
## Nifty Features
|
||||
|
||||
* Simple C-like syntax
|
||||
* Intermediate AST and bytecode representations
|
||||
* Strong, but optional type system
|
||||
* First-class functions and closures
|
||||
* Extensible with native C-bindings
|
||||
* Can re-direct output, error and assertion messages
|
||||
* Open-Source under the zlib license
|
||||
|
||||
## Syntax
|
||||
|
||||
```toy
|
||||
fn makeCounter() {
|
||||
var counter: Int = 0;
|
||||
|
||||
fn increment() {
|
||||
return ++counter;
|
||||
}
|
||||
|
||||
return increment;
|
||||
}
|
||||
|
||||
var tally = makeCounter();
|
||||
|
||||
while (true) {
|
||||
var result = tally();
|
||||
|
||||
print result; //prints 1 to 10
|
||||
|
||||
if (result >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
|
||||
This website is a work in progress - for further info, see the official repository: [https://gitea.krgamestudios.com/krgamestudios/Toy](https://gitea.krgamestudios.com/krgamestudios/Toy), or the GitHub mirror: [https://github.com/krgamestudios/Toy](https://github.com/krgamestudios/Toy).
|
||||
|
||||
An example of Toy in action: [Vampire Toyvivors](https://gitea.krgamestudios.com/krgamestudios/VampireToyvivors) (a simple "game" used for testing).
|
||||
@@ -0,0 +1,5 @@
|
||||
# Summary
|
||||
|
||||
- [Front Page](./README.md)
|
||||
- [Quick Start](./quickstart.md)
|
||||
- [Cheat Sheet](./cheatsheet.md)
|
||||
@@ -0,0 +1,144 @@
|
||||
# Cheat Sheet
|
||||
|
||||
## Compile and Run A Snippet Of Code
|
||||
|
||||
```c
|
||||
#include "toy_lexer.h"
|
||||
#include "toy_parser.h"
|
||||
#include "toy_compiler.h"
|
||||
#include "toy_vm.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
//example code
|
||||
const char* source = "print \"Hello world!\";";
|
||||
|
||||
//buckets use the arena pattern for memory allocation
|
||||
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
|
||||
//compile the code
|
||||
Toy_Lexer lexer;
|
||||
Toy_bindLexer(&lexer, (char*)source);
|
||||
|
||||
Toy_Parser parser;
|
||||
Toy_bindParser(&parser, &lexer);
|
||||
|
||||
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
|
||||
unsigned char* bytecode = Toy_compileToBytecode(ast);
|
||||
|
||||
//the ast, which is stored in this bucket, is no longer needed
|
||||
Toy_freeBucket(&bucket);
|
||||
|
||||
//the virtual machine used at runtime
|
||||
Toy_VM vm;
|
||||
Toy_initVM(&vm);
|
||||
Toy_bindVM(&vm, bytecode, NULL);
|
||||
|
||||
//execute the given code
|
||||
Toy_runVM(&vm);
|
||||
|
||||
//cleanup after ourselves
|
||||
Toy_freeVM(&vm);
|
||||
free(bytecode);
|
||||
}
|
||||
```
|
||||
|
||||
## Quick and Dirty Compilation
|
||||
|
||||
```c
|
||||
unsigned char* compileSource(const char* source) {
|
||||
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
|
||||
Toy_Lexer lexer;
|
||||
Toy_bindLexer(&lexer, source);
|
||||
|
||||
Toy_Parser parser;
|
||||
Toy_bindParser(&parser, &lexer);
|
||||
|
||||
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
|
||||
unsigned char* bytecode = Toy_compileToBytecode(ast);
|
||||
|
||||
Toy_freeBucket(&bucket);
|
||||
return bytecode;
|
||||
}
|
||||
```
|
||||
|
||||
## API Functions
|
||||
|
||||
This is a rough outline of all API functions declared in Toy's headers. As a rule, functions that begin with `TOY_API` are useable and begin with `Toy_`, while functions that begin with `Toy_private_` are generally not intended for use, and only exposed for technical reasons.
|
||||
|
||||
*Note: This list is updated manually, if something is outdated let me know.*
|
||||
|
||||
```c
|
||||
TOY_API Toy_Array* Toy_resizeArray(Toy_Array* array, unsigned int capacity);
|
||||
TOY_API void Toy_setOpaqueAttributeHandler(Toy_OpaqueAttributeHandler cb);
|
||||
TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle) {
|
||||
TOY_API Toy_Bucket* Toy_allocateBucket(unsigned int capacity);
|
||||
TOY_API unsigned char* Toy_partitionBucket(Toy_Bucket** bucketHandle, unsigned int amount);
|
||||
TOY_API void Toy_releaseBucketPartition(unsigned char* ptr);
|
||||
TOY_API void Toy_freeBucket(Toy_Bucket** bucketHandle);
|
||||
TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle);
|
||||
TOY_API unsigned char* Toy_compileToBytecode(Toy_Ast* ast);
|
||||
TOY_API void Toy_freeFunction(Toy_Function* fn) {
|
||||
TOY_API Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode, Toy_Scope* parentScope);
|
||||
TOY_API Toy_Function* Toy_createFunctionFromCallback(Toy_Bucket** bucketHandle, Toy_nativeCallback callback);
|
||||
TOY_API Toy_Function* Toy_copyFunction(Toy_Bucket** bucketHandle, Toy_Function* fn);
|
||||
TOY_API void Toy_freeFunction(Toy_Function* fn);
|
||||
TOY_API void Toy_bindLexer(Toy_Lexer* lexer, const char* source);
|
||||
TOY_API void Toy_bindParser(Toy_Parser* parser, Toy_Lexer* lexer);
|
||||
TOY_API Toy_Ast* Toy_scanParser(Toy_Bucket** bucketHandle, Toy_Parser* parser);
|
||||
TOY_API void Toy_resetParser(Toy_Parser* parser);
|
||||
TOY_API void Toy_print(const char* msg);
|
||||
TOY_API void Toy_error(const char* msg);
|
||||
TOY_API void Toy_assertFailure(const char* msg);
|
||||
TOY_API void Toy_setPrintCallback(Toy_callbackType cb);
|
||||
TOY_API void Toy_setErrorCallback(Toy_callbackType cb);
|
||||
TOY_API void Toy_setAssertFailureCallback(Toy_callbackType cb);
|
||||
TOY_API void Toy_resetPrintCallback(void);
|
||||
TOY_API void Toy_resetErrorCallback(void);
|
||||
TOY_API void Toy_resetAssertFailureCallback(void);
|
||||
TOY_API Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope);
|
||||
TOY_API Toy_Scope* Toy_popScope(Toy_Scope* scope);
|
||||
TOY_API void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy_Value value, bool constant);
|
||||
TOY_API void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value);
|
||||
TOY_API Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key);
|
||||
TOY_API bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key);
|
||||
TOY_API Toy_Stack* Toy_allocateStack(void);
|
||||
TOY_API void Toy_freeStack(Toy_Stack* stack);
|
||||
TOY_API void Toy_resetStack(Toy_Stack** stackHandle);
|
||||
TOY_API void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value);
|
||||
TOY_API Toy_Value Toy_peekStack(Toy_Stack** stackHandle);
|
||||
TOY_API Toy_Value Toy_popStack(Toy_Stack** stackHandle);
|
||||
TOY_API Toy_String* Toy_toString(Toy_Bucket** bucketHandle, const char* cstring);
|
||||
TOY_API Toy_String* Toy_toStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length);
|
||||
TOY_API Toy_String* Toy_createStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length);
|
||||
TOY_API Toy_String* Toy_copyString(Toy_String* str);
|
||||
TOY_API Toy_String* Toy_concatStrings(Toy_Bucket** bucketHandle, Toy_String* left, Toy_String* right);
|
||||
TOY_API void Toy_freeString(Toy_String* str);
|
||||
TOY_API unsigned int Toy_getStringLength(Toy_String* str);
|
||||
TOY_API unsigned int Toy_getStringRefCount(Toy_String* str);
|
||||
TOY_API char* Toy_getStringRaw(Toy_String* str);
|
||||
TOY_API int Toy_compareStrings(Toy_String* left, Toy_String* right);
|
||||
TOY_API unsigned int Toy_hashString(Toy_String* string);
|
||||
TOY_API Toy_Table* Toy_allocateTable(unsigned int minCapacity);
|
||||
TOY_API void Toy_freeTable(Toy_Table* table);
|
||||
TOY_API void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value value);
|
||||
TOY_API Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key);
|
||||
TOY_API void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key);
|
||||
TOY_API Toy_Value Toy_unwrapValue(Toy_Value value);
|
||||
TOY_API unsigned int Toy_hashValue(Toy_Value value);
|
||||
TOY_API Toy_Value Toy_copyValue(struct Toy_Bucket** bucketHandle, Toy_Value value);
|
||||
TOY_API void Toy_freeValue(Toy_Value value);
|
||||
TOY_API bool Toy_checkValueIsTruthy(Toy_Value value);
|
||||
TOY_API bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right);
|
||||
TOY_API bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right);
|
||||
TOY_API int Toy_compareValues(Toy_Value left, Toy_Value right);
|
||||
TOY_API union Toy_String_t* Toy_stringifyValue(struct Toy_Bucket** bucketHandle, Toy_Value value);
|
||||
TOY_API const char* Toy_getValueTypeAsCString(Toy_ValueType type);
|
||||
TOY_API void Toy_resetVM(Toy_VM* vm, bool preserveScope, bool preserveStack);
|
||||
TOY_API void Toy_initVM(Toy_VM* vm);
|
||||
TOY_API void Toy_inheritVM(Toy_VM* parentVM, Toy_VM* subVM);
|
||||
TOY_API unsigned int Toy_runVM(Toy_VM* vm);
|
||||
TOY_API void Toy_freeVM(Toy_VM* vm);
|
||||
TOY_API Toy_Value Toy_getReturnValueFromVM(Toy_VM* parentVM, Toy_VM* subVM);
|
||||
```
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 454 KiB |
@@ -0,0 +1,186 @@
|
||||
# Toy v2 Quick-Start Guide
|
||||
|
||||
To help you start using Toy as fast as possible, here are the most useful elements of the language. Not everything available is listed, but this should let you start coding right away.
|
||||
|
||||
## Keyword 'print'
|
||||
|
||||
The `print` keyword prints a given value to stdout (or elsewhere if configured with the API).
|
||||
|
||||
```
|
||||
print "Hello World!";
|
||||
```
|
||||
|
||||
## Keyword 'assert'
|
||||
|
||||
The `assert` keyword takes two values separated by a comma. If the first value is falsy or `null` the optional second parameter is printed to stderr (or elsewhere if configured with the API). If no second parameter is provided a generic error message is used instead.
|
||||
|
||||
```
|
||||
//nothing happens
|
||||
assert 1 < 2;
|
||||
|
||||
//this assert will fail, and output the second parameter
|
||||
assert null, "Hello world!";
|
||||
```
|
||||
|
||||
## Variables and Types
|
||||
|
||||
Variables are declared with the `var` keyword with and an optional type from the list below. If no type is specified `Any` is used instead.
|
||||
|
||||
```
|
||||
var answer = 42;
|
||||
|
||||
var question: String = "How many roads must a man walk down?";
|
||||
```
|
||||
|
||||
To make a variable immutable put the `const` keyword after the type. If you do, it must be assigned a value.
|
||||
|
||||
```
|
||||
var quote: String const = "War. War never changes.";
|
||||
```
|
||||
|
||||
Toy's types are:
|
||||
|
||||
| type | name | description |
|
||||
| --- | --- | --- |
|
||||
| `Bool` | Boolean | Either `true` or `false`. |
|
||||
| `Int` | Integer | Any signed whole number (32-bits). |
|
||||
| `Float` | Float | Any signed decimal number (32-bits), using floating point arithmatic. |
|
||||
| `String` | String | Normal text, effectively utf-8. |
|
||||
| `Array` | Array | A series of values stored sequentially in memory. |
|
||||
| `Table` | Table | A series key-value pairs stored in a hash table. Booleans, functions, opaques and `null` can't be used as keys. |
|
||||
| `Function` | Function | A chunk of reusable code that takes zero or more parameters, and may return a result. Functions are declared with the `fn` keyword, or in the API. |
|
||||
| `Opaque` | Opaque | This value is unusable in Toy, but allows you to pass data between C bindings provided with the API. |
|
||||
| `Any` | Any | The default type when nothing is specified. It can hold any value. |
|
||||
|
||||
## Control Flow
|
||||
|
||||
Making a decision, or repeating a chunk of code multiple times, is essential for any language. Choosing between multiple options can be done with the `if-then-else` statement - if the condition is truthy, the 'then-branch' will be executed. Otherwise, the optional 'else-branch' is executed instead.
|
||||
|
||||
```
|
||||
var answer = 42;
|
||||
|
||||
if (answer < 56) {
|
||||
print "Cod dang it!";
|
||||
}
|
||||
else {
|
||||
print "Something's fishy here...";
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
var challenge = "hard";
|
||||
|
||||
if (challenge == "hard") {
|
||||
print "I choose to build a scripting language, not because it's easy, but because it's hard!";
|
||||
}
|
||||
|
||||
//the else-branch is optional
|
||||
```
|
||||
|
||||
To repeat a certain action, use the `while-then` loop, which repeats the body as long as the given condition remains true on each loop.
|
||||
|
||||
```
|
||||
var loops = 0;
|
||||
|
||||
while (loops++ < 8) {
|
||||
print "These episodes are endless.";
|
||||
}
|
||||
```
|
||||
|
||||
To break out of a loop, you can use the `break` keyword. Alternatively, to restart the loop early, use the `continue` keyword.
|
||||
|
||||
```
|
||||
var loops = 0;
|
||||
|
||||
while (true) {
|
||||
if (++loops < 15532) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break; //poor yuki ;_;
|
||||
}
|
||||
```
|
||||
|
||||
*Note: The `for` loop is coming soon, and will allow for iteration over an array or table, but isn't vital right now.*
|
||||
|
||||
## Arrays and Tables
|
||||
|
||||
Arrays are defined with a pair of brackets, and can contain a list of comma-separated values.
|
||||
|
||||
```
|
||||
//define an array
|
||||
var array = [1,2,3];
|
||||
|
||||
//specify the type
|
||||
var bray: Array = [4,5,6];
|
||||
|
||||
//define an empty array
|
||||
var craycray: Array = [];
|
||||
|
||||
//arrays are zero-indexed
|
||||
print array[0]; //'1'
|
||||
```
|
||||
|
||||
Tables are also defined with brackets, and contain a comma-separated list of key-value pairs defined by colons:
|
||||
|
||||
```
|
||||
//most types can be used as keys
|
||||
var table = ["alpha": 1, "beta": 2, "gamma": 3];
|
||||
|
||||
//the 'Table' keyword can define the type, and an empty table still has a colon
|
||||
var under: Table = [:];
|
||||
|
||||
//printing the whole table does NOT guarantee internal order
|
||||
print table["beta"];
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
Some values, including Strings, Arrays and Tables, have "attributes" which are accessible with the dot `.` operator. These can expose internal values or components for manipulating said values.
|
||||
|
||||
```
|
||||
var string = "Hello World";
|
||||
print string.length; //11
|
||||
print string.asUpper; //HELLO WORLD
|
||||
print string.asLower; //hello world
|
||||
|
||||
var array = [1,2,3];
|
||||
array.pushBack(4); //array = [1,2,3,4]
|
||||
var element = array.popBack(); //element = 4
|
||||
var emptyArray = [];
|
||||
|
||||
var table = ["alpha": 1, "beta":2];
|
||||
print table.length; //2
|
||||
table.insert("key",element); //table["key"] = 4
|
||||
print table.hasKey("alpha"); //true
|
||||
table.remove("alpha"); //table = ["beta":2,"key":4]
|
||||
var emptyTable = [:];
|
||||
```
|
||||
|
||||
Opaques can also be given attributes, but this requires some in-depth understanding of the API, so won't be covered here.
|
||||
|
||||
## Functions
|
||||
|
||||
Functions are defined with the `fn` keyword, and follow a c-like syntax, with optional types on each parameter:
|
||||
|
||||
```toy
|
||||
fn fib(n: Int) {
|
||||
if (n < 2) return n;
|
||||
return fib(n-1) + fib(n-2);
|
||||
}
|
||||
|
||||
print fib(12); //144
|
||||
```
|
||||
|
||||
```toy
|
||||
fn isLeapYear(n: Int) {
|
||||
if (n % 400 == 0) return true;
|
||||
if (n % 100 == 0) return false;
|
||||
return n % 4 == 0;
|
||||
}
|
||||
```
|
||||
|
||||
## External API and Extending Toy
|
||||
|
||||
*Note: Watch this space, docs for the C API are coming soon. For now, the [Cheat Sheet](/cheatsheet) can get you started.*
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,14 @@
|
||||
<!-- open graph protocol -->
|
||||
<meta property="og:url" content="{{ url }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="{{ base_url }}/img/toypreview.png" />
|
||||
<meta property="og:title" content="{{ title }}" />
|
||||
<meta property="og:description" content="{{ description }}" />
|
||||
|
||||
<!-- twitter has to be special -->
|
||||
<meta name="twitter:card" content="{{ title }}" />
|
||||
<meta name="twitter:url" content="{{ url}}" />
|
||||
<meta name="twitter:type" content="website" />
|
||||
<meta name="twitter:image" content="{{ base_url }}/img/toypreview.png" />
|
||||
<meta name="twitter:title" content="{{ title }}" />
|
||||
<meta name="twitter:description" content="{{ description }}" />
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,18 +0,0 @@
|
||||
---
|
||||
layout: page
|
||||
title: The Toy Programming Language
|
||||
---
|
||||
|
||||
<div style="justify-self: center;">
|
||||
<image src="assets/toylogo.png" width="250" height="250" />
|
||||
</div>
|
||||
|
||||
<div style="justify-self: center;">
|
||||
<a href="https://github.com/krgamestudios/Toy"><img src="https://github.com/krgamestudios/Toy/actions/workflows/continuous-integration-v2.yml/badge.svg"></a>
|
||||
</div>
|
||||
|
||||
The Toy Programming Language is an imperative, bytecode-interpreted, embeddable scripting language. Rather than functioning independently, it serves as part of another program, the "host". This design allows for straightforward customization by both the host's developers and end users, achieved by exposing program logic through text files.
|
||||
|
||||
The documentation on this website is under construction, for further information, see the repository: [https://gitea.krgamestudios.com/krgamestudios/Toy](https://gitea.krgamestudios.com/krgamestudios/Toy), or the GitHub mirror: [https://github.com/krgamestudios/Toy](https://github.com/krgamestudios/Toy).
|
||||
|
||||
An example of Toy in action: [Vampire Toyvivors](https://gitea.krgamestudios.com/krgamestudios/VampireToyvivors).
|
||||
@@ -0,0 +1,83 @@
|
||||
#compiler settings reference
|
||||
#CC=gcc
|
||||
#CFLAGS+=-std=c17 -g -Wall -Werror -Wextra -Wpedantic -Wformat=2 -Wno-newline-eof
|
||||
#LIBS+=-lm
|
||||
#LDFLAGS+=
|
||||
|
||||
#TODO: release builds should define the NDEBUG flag; double check it works
|
||||
|
||||
#directories
|
||||
export TOY_SOURCEDIR=source
|
||||
export TOY_REPLDIR=repl
|
||||
export TOY_OUTDIR=out
|
||||
export TOY_OBJDIR=obj
|
||||
|
||||
#targets
|
||||
all: source repl
|
||||
|
||||
.PHONY: source
|
||||
source:
|
||||
$(MAKE) -C source -k
|
||||
|
||||
.PHONY: repl
|
||||
repl: source
|
||||
$(MAKE) -C repl -k
|
||||
|
||||
.PHONY: tests tests-ci
|
||||
tests: clean
|
||||
$(MAKE) -C tests -k
|
||||
|
||||
tests-gdb: clean
|
||||
$(MAKE) -C tests -k gdb
|
||||
|
||||
#util targets
|
||||
$(TOY_OUTDIR):
|
||||
mkdir $(TOY_OUTDIR)
|
||||
|
||||
$(TOY_OBJDIR):
|
||||
mkdir $(TOY_OBJDIR)
|
||||
|
||||
#util commands
|
||||
.PHONY: clean
|
||||
clean:
|
||||
ifeq ($(shell uname),Linux)
|
||||
find . -type f -name '*.o' -delete
|
||||
find . -type f -name '*.a' -delete
|
||||
find . -type f -name '*.out' -delete
|
||||
find . -type f -name '*.exe' -delete
|
||||
find . -type f -name '*.dll' -delete
|
||||
find . -type f -name '*.lib' -delete
|
||||
find . -type f -name '*.so' -delete
|
||||
find . -type f -name '*.dylib' -delete
|
||||
find . -type d -name 'out' -delete
|
||||
find . -type d -name 'obj' -delete
|
||||
else ifeq ($(shell uname),NetBSD)
|
||||
find . -type f -name '*.o' -delete
|
||||
find . -type f -name '*.a' -delete
|
||||
find . -type f -name '*.out' -delete
|
||||
find . -type f -name '*.exe' -delete
|
||||
find . -type f -name '*.dll' -delete
|
||||
find . -type f -name '*.lib' -delete
|
||||
find . -type f -name '*.so' -delete
|
||||
find . -type f -name '*.dylib' -delete
|
||||
find . -type d -name 'out' -delete
|
||||
find . -type d -name 'obj' -delete
|
||||
else ifeq ($(OS),Windows_NT)
|
||||
$(RM) *.o *.a *.exe *.dll *.lib *.so *.dylib
|
||||
$(RM) out
|
||||
$(RM) obj
|
||||
else ifeq ($(shell uname),Darwin)
|
||||
find . -type f -name '*.o' -delete
|
||||
find . -type f -name '*.a' -delete
|
||||
find . -type f -name '*.out' -delete
|
||||
find . -type f -name '*.exe' -delete
|
||||
find . -type f -name '*.dll' -delete
|
||||
find . -type f -name '*.lib' -delete
|
||||
find . -type f -name '*.so' -delete
|
||||
find . -type f -name '*.dylib' -delete
|
||||
find . -type d -name 'out' -delete
|
||||
find . -type d -name 'obj' -delete
|
||||
else
|
||||
@echo "Deletion failed - what platform is this?"
|
||||
endif
|
||||
|
||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,100 @@
|
||||
#include "ast_inspector.h"
|
||||
#include "toy_console_colors.h"
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_string.h"
|
||||
#include "toy_value.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void inspect_by_type(Toy_Ast* ast, int depth);
|
||||
|
||||
void inspect_block(Toy_Ast* ast, int depth);
|
||||
void inspect_value(Toy_Ast* ast, int depth);
|
||||
void inspect_print(Toy_Ast* ast, int depth);
|
||||
|
||||
#define PRINTSTR(x) printf("%*s%s", depth*4, "", x);
|
||||
|
||||
static Toy_Bucket* bucket = NULL; //lazy
|
||||
|
||||
void inspect_ast(Toy_Ast* ast) {
|
||||
bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
inspect_by_type(ast, 0);
|
||||
Toy_freeBucket(&bucket);
|
||||
}
|
||||
|
||||
void inspect_by_type(Toy_Ast* ast, int depth) {
|
||||
switch(ast->type) {
|
||||
case TOY_AST_BLOCK:
|
||||
inspect_block(ast, depth);
|
||||
return;
|
||||
|
||||
case TOY_AST_VALUE:
|
||||
inspect_value(ast, depth);
|
||||
return;
|
||||
// case TOY_AST_UNARY:
|
||||
// case TOY_AST_BINARY:
|
||||
// case TOY_AST_BINARY_SHORT_CIRCUIT:
|
||||
// case TOY_AST_COMPARE:
|
||||
// case TOY_AST_GROUP:
|
||||
// case TOY_AST_COMPOUND:
|
||||
// case TOY_AST_AGGREGATE:
|
||||
|
||||
// case TOY_AST_ASSERT:
|
||||
// case TOY_AST_IF_THEN_ELSE:
|
||||
// case TOY_AST_WHILE_THEN:
|
||||
// case TOY_AST_BREAK:
|
||||
// case TOY_AST_CONTINUE:
|
||||
// case TOY_AST_RETURN:
|
||||
case TOY_AST_PRINT:
|
||||
inspect_print(ast, depth);
|
||||
return;
|
||||
|
||||
// case TOY_AST_VAR_DECLARE:
|
||||
// case TOY_AST_VAR_ASSIGN:
|
||||
// case TOY_AST_VAR_ACCESS:
|
||||
|
||||
// case TOY_AST_FN_DECLARE:
|
||||
// case TOY_AST_FN_INVOKE:
|
||||
|
||||
// case TOY_AST_STACK_POP:
|
||||
|
||||
default:
|
||||
printf(TOY_CC_WARN "%*sAST %s (unhandled by inspector)" TOY_CC_RESET "\n", depth*4, "", Toy_private_getAstTypeAsCString(ast->type));
|
||||
}
|
||||
}
|
||||
|
||||
void inspect_block(Toy_Ast* ast, int depth) {
|
||||
//show the block braces
|
||||
PRINTSTR("{\n");
|
||||
|
||||
if (ast->block.child) {
|
||||
inspect_by_type(ast->block.child, depth + 1);
|
||||
|
||||
if (ast->block.next) {
|
||||
inspect_block(ast->block.next, depth + 0);
|
||||
}
|
||||
}
|
||||
|
||||
PRINTSTR("}\n");
|
||||
}
|
||||
|
||||
void inspect_value(Toy_Ast* ast, int depth) {
|
||||
(void)depth;
|
||||
Toy_String* str = Toy_stringifyValue(&bucket, ast->value.value);
|
||||
|
||||
char* buffer = Toy_getStringRaw(str); //SLOW
|
||||
printf("%s '%s'", Toy_getValueTypeAsCString(ast->value.value.type), buffer);
|
||||
free(buffer);
|
||||
|
||||
Toy_freeString(str);
|
||||
}
|
||||
|
||||
void inspect_print(Toy_Ast* ast, int depth) {
|
||||
(void)depth;
|
||||
PRINTSTR("PRINT ");
|
||||
|
||||
inspect_by_type(ast->print.child, depth);
|
||||
|
||||
printf(";\n");
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_ast.h"
|
||||
|
||||
void inspect_ast(Toy_Ast* astHandle);
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "bucket_inspector.h"
|
||||
#include <toy_string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int inspect_bucket(Toy_Bucket** bucketHandle) {
|
||||
int depth = 0;
|
||||
|
||||
//for each bucket
|
||||
for (Toy_Bucket* iter = (*bucketHandle); iter != NULL; iter = iter->next) {
|
||||
int occupied = 0;
|
||||
int released = 0;
|
||||
unsigned char* ptr = iter->data;
|
||||
|
||||
|
||||
while ((ptr - iter->data < iter->count) && *((int*)ptr) != 0) { //for each partition
|
||||
if ( ( *((int*)ptr) & 1) == 0) { //is this partition still in use?
|
||||
occupied++;
|
||||
|
||||
//try to print as a string if possible
|
||||
Toy_String* str = (void*)(ptr + 4);
|
||||
|
||||
if (str->info.type == TOY_STRING_LEAF && str->info.length < 255) {
|
||||
printf("String Leaf (%d bytes, %d refCount): %.*s\n", *((int*)ptr), str->info.refCount, str->info.length, str->leaf.data);
|
||||
}
|
||||
else if (str->info.type == TOY_STRING_NODE) {
|
||||
printf("String Node (%d bytes, %d refCount): ...\n", *((int*)ptr), str->info.refCount);
|
||||
}
|
||||
}
|
||||
else {
|
||||
released++;
|
||||
}
|
||||
|
||||
//jump distance: ((*((int*)ptr) | 1) ^ 1) + 4
|
||||
// printf(" jump %d, ", ((*((int*)ptr) | 1) ^ 1) + 4);
|
||||
ptr += ((*((int*)ptr) | 1) ^ 1) + 4; //OR + XOR to remove the 'free' flag from the size
|
||||
}
|
||||
|
||||
printf("Bucket link %d: count %u, %d occupied, %d released\n", depth, iter->count, occupied, released);
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
return depth;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_bucket.h"
|
||||
|
||||
int inspect_bucket(Toy_Bucket** bucketHandle);
|
||||
@@ -0,0 +1,387 @@
|
||||
#include "bytecode_inspector.h"
|
||||
#include "toy_console_colors.h"
|
||||
#include "toy_opcodes.h"
|
||||
#include "toy_value.h"
|
||||
#include "toy_string.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int inspect_instruction(unsigned char* bytecode, unsigned int pc, unsigned int jumps_addr, unsigned int data_addr);
|
||||
int inspect_read(unsigned char* bytecode, unsigned int pc, unsigned int jumps_addr, unsigned int data_addr);
|
||||
|
||||
#define ISPRINT_SANITIZE(x) (isprint((int)x) > 0 ? (x) : '_')
|
||||
|
||||
#define MARKER_VALUE(pc, type) \
|
||||
((unsigned int)(pc * sizeof(type)))
|
||||
|
||||
#define MARKER "\t\033[" TOY_CC_FONT_BLACK "m" " %u\t" TOY_CC_RESET
|
||||
#define FONT_BLACK "\033[" TOY_CC_FONT_BLACK "m"
|
||||
|
||||
//exposed functions
|
||||
int inspect_bytecode(unsigned char* bytecode) {
|
||||
//TODO: handle version info
|
||||
|
||||
unsigned int const bytecodeSize = ((unsigned int*)(bytecode))[0];
|
||||
unsigned int const jumpsSize = ((unsigned int*)(bytecode))[1];
|
||||
unsigned int const paramSize = ((unsigned int*)(bytecode))[2];
|
||||
unsigned int const dataSize = ((unsigned int*)(bytecode))[3];
|
||||
unsigned int const subsSize = ((unsigned int*)(bytecode))[4];
|
||||
|
||||
printf(FONT_BLACK ".header:\r" TOY_CC_RESET);
|
||||
|
||||
//bytecode size
|
||||
printf(MARKER TOY_CC_NOTICE "Bytecode Size: \t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(0, unsigned int), bytecodeSize);
|
||||
|
||||
//header counts
|
||||
printf(MARKER TOY_CC_NOTICE "Jumps Size:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(1, unsigned int), jumpsSize);
|
||||
printf(MARKER TOY_CC_NOTICE "Param Size:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(2, unsigned int), paramSize);
|
||||
printf(MARKER TOY_CC_NOTICE "Data Size:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(3, unsigned int), dataSize);
|
||||
printf(MARKER TOY_CC_NOTICE "Subs Size:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(4, unsigned int), subsSize);
|
||||
|
||||
//some addresses may be absent
|
||||
unsigned int addr_pc = 4;
|
||||
unsigned int code_addr = 0;
|
||||
unsigned int jumps_addr = 0;
|
||||
unsigned int param_addr = 0;
|
||||
unsigned int data_addr = 0;
|
||||
unsigned int subs_addr = 0;
|
||||
|
||||
//bugfix
|
||||
unsigned int code_end = 0;
|
||||
|
||||
//header addresses
|
||||
if (true) {
|
||||
addr_pc++;
|
||||
printf(MARKER TOY_CC_NOTICE "Code Address:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(addr_pc, unsigned int), ((unsigned int*)(bytecode))[addr_pc]);
|
||||
code_addr = ((unsigned int*)(bytecode))[addr_pc];
|
||||
}
|
||||
|
||||
if (jumpsSize > 0) {
|
||||
addr_pc++;
|
||||
printf(MARKER TOY_CC_NOTICE "Jumps Address:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(addr_pc, unsigned int), ((unsigned int*)(bytecode))[addr_pc]);
|
||||
jumps_addr = ((unsigned int*)(bytecode))[addr_pc];
|
||||
if (code_end == 0) code_end = jumps_addr;
|
||||
}
|
||||
|
||||
if (paramSize > 0) {
|
||||
addr_pc++;
|
||||
printf(MARKER TOY_CC_NOTICE "Param Address:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(addr_pc, unsigned int), ((unsigned int*)(bytecode))[addr_pc]);
|
||||
param_addr = ((unsigned int*)(bytecode))[addr_pc];
|
||||
if (code_end == 0) code_end = param_addr;
|
||||
}
|
||||
|
||||
if (dataSize > 0) {
|
||||
addr_pc++;
|
||||
printf(MARKER TOY_CC_NOTICE "Data Address:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(addr_pc, unsigned int), ((unsigned int*)(bytecode))[addr_pc]);
|
||||
data_addr = ((unsigned int*)(bytecode))[addr_pc];
|
||||
if (code_end == 0) code_end = data_addr;
|
||||
}
|
||||
|
||||
if (subsSize > 0) {
|
||||
addr_pc++;
|
||||
printf(MARKER TOY_CC_NOTICE "Subs Address:\t\t%u" TOY_CC_RESET "\n", MARKER_VALUE(addr_pc, unsigned int), ((unsigned int*)(bytecode))[addr_pc]);
|
||||
subs_addr = ((unsigned int*)(bytecode))[addr_pc];
|
||||
if (code_end == 0) code_end = subs_addr;
|
||||
}
|
||||
|
||||
if (code_end == 0) code_end = bytecodeSize; //very hacky
|
||||
|
||||
printf(FONT_BLACK ".code:\r" TOY_CC_RESET);
|
||||
|
||||
unsigned int pc = code_addr;
|
||||
while(pc < code_end) {
|
||||
pc += inspect_instruction(bytecode, pc, jumps_addr, data_addr);
|
||||
}
|
||||
|
||||
//jumps
|
||||
if (jumpsSize > 0) {
|
||||
printf(FONT_BLACK ".jumps:\r" TOY_CC_RESET);
|
||||
|
||||
for (unsigned int i = 0; (i*4) < jumpsSize; i++) {
|
||||
printf(MARKER TOY_CC_NOTICE "%u (data %u)" TOY_CC_RESET "\n", MARKER_VALUE(jumps_addr + i, unsigned int),
|
||||
i,
|
||||
((unsigned int*)(bytecode + jumps_addr))[i] + data_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//param
|
||||
if (paramSize > 0) {
|
||||
printf(FONT_BLACK ".param:\r" TOY_CC_RESET);
|
||||
|
||||
for (unsigned int i = 0; (i*4) < paramSize; i += 2) {
|
||||
printf(MARKER TOY_CC_NOTICE "%u (type %s, data %u)" TOY_CC_RESET "\n", MARKER_VALUE(param_addr + i, unsigned int),
|
||||
i,
|
||||
Toy_getValueTypeAsCString(((unsigned int*)(bytecode + param_addr))[i + 1]),
|
||||
((unsigned int*)(bytecode + param_addr))[i] + data_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//data; assume there's only strings for now
|
||||
if (dataSize > 0) {
|
||||
printf(FONT_BLACK ".data:\r" TOY_CC_RESET);
|
||||
|
||||
for (unsigned int i = 0; (i*4) < dataSize; i++) {
|
||||
printf(MARKER TOY_CC_NOTICE "%c %c %c %c" TOY_CC_RESET "\n", MARKER_VALUE(data_addr + i, unsigned int),
|
||||
ISPRINT_SANITIZE(((char*)(bytecode + data_addr + (i*4)))[0]),
|
||||
ISPRINT_SANITIZE(((char*)(bytecode + data_addr + (i*4)))[1]),
|
||||
ISPRINT_SANITIZE(((char*)(bytecode + data_addr + (i*4)))[2]),
|
||||
ISPRINT_SANITIZE(((char*)(bytecode + data_addr + (i*4)))[3])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//subs
|
||||
if (subsSize > 0) {
|
||||
printf(FONT_BLACK ".subs:\n" TOY_CC_RESET);
|
||||
|
||||
unsigned int i = 0;
|
||||
while (i < subsSize) {
|
||||
i += inspect_bytecode(bytecode + subs_addr + i);
|
||||
}
|
||||
}
|
||||
|
||||
return bytecodeSize;
|
||||
}
|
||||
|
||||
int inspect_instruction(unsigned char* bytecode, unsigned int pc, unsigned int jumps_addr, unsigned int data_addr) {
|
||||
//read and print the opcode instruction at 'pc'
|
||||
|
||||
Toy_OpcodeType opcode = bytecode[pc];
|
||||
|
||||
switch(opcode) {
|
||||
case TOY_OPCODE_READ:
|
||||
return inspect_read(bytecode, pc, jumps_addr, data_addr);
|
||||
|
||||
case TOY_OPCODE_DECLARE: {
|
||||
unsigned int indexValue = *((unsigned int*)(bytecode + pc + 4));
|
||||
unsigned int jumpValue = *((unsigned int*)(bytecode + jumps_addr + indexValue));
|
||||
char* cstr = ((char*)(bytecode + data_addr + jumpValue));
|
||||
printf(MARKER "DECLARE %s: %s%s\n", MARKER_VALUE(pc, unsigned char),
|
||||
cstr,
|
||||
Toy_getValueTypeAsCString(bytecode[pc + 1]),
|
||||
bytecode[pc + 3] ? " const" : ""
|
||||
);
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_OPCODE_ASSIGN:
|
||||
printf(MARKER "ASSIGN %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] ? "(chained)" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ASSIGN_COMPOUND:
|
||||
printf(MARKER "ASSIGN COMPOUND %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] ? "(chained)" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ACCESS:
|
||||
printf(MARKER "ACCESS\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_INVOKE:
|
||||
printf(MARKER "INVOKE as '%s' (%d parameters)\n", MARKER_VALUE(pc, unsigned char),
|
||||
Toy_getValueTypeAsCString(bytecode[pc + 1]),
|
||||
bytecode[pc + 2]);
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ATTRIBUTE:
|
||||
printf(MARKER "ATTRIBUTE (accessed from a compound)\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_DUPLICATE:
|
||||
printf(MARKER "DUPLICATE %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] ? "and ACCESS" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ELIMINATE:
|
||||
printf(MARKER "ELIMINATE\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ADD:
|
||||
printf(MARKER "ADD %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_SUBTRACT:
|
||||
printf(MARKER "SUBTRACT %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_MULTIPLY:
|
||||
printf(MARKER "MULTIPLY %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_DIVIDE:
|
||||
printf(MARKER "DIVIDE %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_MODULO:
|
||||
printf(MARKER "MODULO %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_COMPARE_EQUAL:
|
||||
printf(MARKER "COMPARE %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] != TOY_OPCODE_NEGATE ? "==" : "!=");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_COMPARE_LESS:
|
||||
printf(MARKER "COMPARE '<'\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_COMPARE_LESS_EQUAL:
|
||||
printf(MARKER "COMPARE '<='\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_COMPARE_GREATER:
|
||||
printf(MARKER "COMPARE '>'\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_COMPARE_GREATER_EQUAL:
|
||||
printf(MARKER "COMPARE '>='\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_AND:
|
||||
printf(MARKER "LOGICAL '&&'\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_OR:
|
||||
printf(MARKER "LOGICAL '||'\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_TRUTHY:
|
||||
printf(MARKER "LOGICAL TRUTHY\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_NEGATE:
|
||||
printf(MARKER "LOGICAL NEGATE\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_RETURN:
|
||||
printf(MARKER "Keyword RETURN (%u)\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1]);
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_JUMP:
|
||||
printf(MARKER TOY_CC_DEBUG "JUMP %s%s (%s%d) (GOTO %u)\n" TOY_CC_RESET, MARKER_VALUE(pc, unsigned char),
|
||||
bytecode[pc + 1] == TOY_OP_PARAM_JUMP_ABSOLUTE ? "absolute" : "relative",
|
||||
bytecode[pc + 2] == TOY_OP_PARAM_JUMP_ALWAYS ? "" :
|
||||
bytecode[pc + 2] == TOY_OP_PARAM_JUMP_IF_TRUE ? " if true" : " if false",
|
||||
bytecode[pc + 4] > 0 ? "+" : "", //show a + sign when positive
|
||||
bytecode[pc + 4],
|
||||
bytecode[pc + 4] + pc + 8
|
||||
);
|
||||
return 8;
|
||||
|
||||
case TOY_OPCODE_ESCAPE:
|
||||
printf(MARKER TOY_CC_DEBUG "ESCAPE relative %s%d (GOTO %u) and pop %d\n" TOY_CC_RESET, MARKER_VALUE(pc, unsigned char),
|
||||
bytecode[pc + 4] > 0 ? "+" : "", //show a + sign when positive
|
||||
bytecode[pc + 4],
|
||||
bytecode[pc + 4] + pc + 12,
|
||||
bytecode[pc + 8]
|
||||
);
|
||||
return 12;
|
||||
|
||||
case TOY_OPCODE_SCOPE_PUSH:
|
||||
printf(MARKER "SCOPE PUSH\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_SCOPE_POP:
|
||||
printf(MARKER "SCOPE POP\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_ASSERT:
|
||||
printf(MARKER TOY_CC_WARN "Keyword ASSERT (cond%s)\n" TOY_CC_RESET, MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] > 1 ? ",msg" : "");
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_PRINT:
|
||||
printf(MARKER TOY_CC_NOTICE "Keyword PRINT\n" TOY_CC_RESET, MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_CONCAT:
|
||||
printf(MARKER "CONCATENATE strings\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
|
||||
case TOY_OPCODE_INDEX:
|
||||
printf(MARKER "INDEX (%d elements)\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1]);
|
||||
return 4;
|
||||
|
||||
// case TOY_OPCODE_UNUSED:
|
||||
// case TOY_OPCODE_PASS:
|
||||
// case TOY_OPCODE_ERROR:
|
||||
// case TOY_OPCODE_EOF:
|
||||
|
||||
default:
|
||||
printf(MARKER TOY_CC_WARN "Unknown Word: [%u, %u, %u, %u]" TOY_CC_RESET "\n", MARKER_VALUE(pc, unsigned char), bytecode[pc], bytecode[pc+1], bytecode[pc+2], bytecode[pc+3]);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
int inspect_read(unsigned char* bytecode, unsigned int pc, unsigned int jumps_addr, unsigned int data_addr) {
|
||||
Toy_ValueType type = bytecode[pc + 1];
|
||||
|
||||
switch(type) {
|
||||
case TOY_VALUE_NULL: {
|
||||
printf(MARKER "READ NULL\n", MARKER_VALUE(pc, unsigned char));
|
||||
return 4;
|
||||
}
|
||||
|
||||
case TOY_VALUE_BOOLEAN: {
|
||||
if (bytecode[pc + 2]) {
|
||||
printf(MARKER "READ BOOL true\n", MARKER_VALUE(pc, unsigned char));
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
case TOY_VALUE_INTEGER: {
|
||||
int i = *(int*)(bytecode + pc + 4);
|
||||
printf(MARKER "READ INTEGER %d\n", MARKER_VALUE(pc, unsigned char), i);
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FLOAT: {
|
||||
float i = *(float*)(bytecode + pc + 4);
|
||||
printf(MARKER "READ FLOAT %f\n", MARKER_VALUE(pc, unsigned char), i);
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_VALUE_STRING: {
|
||||
Toy_StringType stringType = (Toy_StringType)(*(bytecode + pc + 2)); //Probably not needed
|
||||
int len = bytecode[pc + 3]; //only used for names?
|
||||
(void)len;
|
||||
|
||||
(void)stringType;
|
||||
|
||||
unsigned int indexValue = *((unsigned int*)(bytecode + pc + 4));
|
||||
unsigned int jumpValue = *((unsigned int*)(bytecode + jumps_addr + indexValue));
|
||||
char* cstr = ((char*)(bytecode + data_addr + jumpValue));
|
||||
|
||||
printf(MARKER "READ STRING [%u] '%s'\n", MARKER_VALUE(pc, unsigned char), indexValue, cstr);
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
printf(MARKER "READ FUNCTION '%u' (%d params)\n", MARKER_VALUE(pc, unsigned char), *((unsigned int*)(bytecode + pc + 4)), bytecode[pc + 2]);
|
||||
return 8;
|
||||
|
||||
case TOY_VALUE_ARRAY: {
|
||||
unsigned int count = *((unsigned int*)(bytecode + pc + 4));
|
||||
printf(MARKER "READ ARRAY %u elements\n", MARKER_VALUE(pc, unsigned char), count);
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_VALUE_TABLE: {
|
||||
unsigned int count = *((unsigned int*)(bytecode + pc + 4));
|
||||
printf(MARKER "READ TABLE %u elements (consuming %u values)\n", MARKER_VALUE(pc, unsigned char), count / 2, count);
|
||||
return 8;
|
||||
}
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
default: {
|
||||
printf(MARKER TOY_CC_WARN "READ %s (unhandled by inspector)" TOY_CC_RESET "\n", MARKER_VALUE(pc, unsigned char), Toy_getValueTypeAsCString(type));
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int inspect_bytecode(unsigned char* bytecode);
|
||||
@@ -0,0 +1,532 @@
|
||||
#include "ast_inspector.h"
|
||||
#include "bytecode_inspector.h"
|
||||
#include "bucket_inspector.h"
|
||||
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include "toy_lexer.h"
|
||||
#include "toy_parser.h"
|
||||
#include "toy_compiler.h"
|
||||
#include "toy_vm.h"
|
||||
|
||||
//NOTE: for testing
|
||||
#include "standard_library.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
unsigned char* readFile(char* path, int* size) {
|
||||
//open the file
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
*size = -1; //missing file error
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//determine the file's length
|
||||
fseek(file, 0L, SEEK_END);
|
||||
*size = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
//make some space
|
||||
unsigned char* buffer = malloc(*size + 1);
|
||||
if (buffer == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//read the file
|
||||
if (fread(buffer, sizeof(unsigned char), *size, file) < (unsigned int)(*size)) {
|
||||
fclose(file);
|
||||
free(buffer);
|
||||
*size = -2; //singal a read error
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[(*size)++] = '\0';
|
||||
|
||||
//clean up and return
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int getFileName(char* dest, const char* src, size_t destLength) {
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
char* p = NULL;
|
||||
|
||||
//find the last slash, regardless of platform
|
||||
p = strrchr(src, '\\');
|
||||
if (p == NULL) {
|
||||
p = strrchr(src, '/');
|
||||
}
|
||||
if (p == NULL) {
|
||||
int len = MIN(strlen(src), destLength-1);
|
||||
strncpy(dest, src, len);
|
||||
dest[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
p++; //skip the slash
|
||||
|
||||
//determine length of the file name
|
||||
int len = MIN(strlen(src), destLength-1);
|
||||
|
||||
//copy to the dest
|
||||
strncpy(dest, p, len);
|
||||
dest[len] = '\0';
|
||||
|
||||
return len;
|
||||
#undef MIN
|
||||
}
|
||||
|
||||
//error callbacks
|
||||
static int errorAndExitCallback(const char* msg) {
|
||||
fprintf(stderr, TOY_CC_ERROR "Error: %s" TOY_CC_RESET "\n", msg);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static int errorAndContinueCallback(const char* msg) {
|
||||
return fprintf(stderr, TOY_CC_ERROR "Error: %s" TOY_CC_RESET "\n", msg);
|
||||
}
|
||||
|
||||
static int assertFailureAndExitCallback(const char* msg) {
|
||||
fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s" TOY_CC_RESET "\n", msg);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static int assertFailureAndContinueCallback(const char* msg) {
|
||||
return fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s" TOY_CC_RESET "\n", msg);
|
||||
}
|
||||
|
||||
static int noOpCallback(const char* msg) {
|
||||
//NO-OP
|
||||
(void)msg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int silentExitCallback(const char* msg) {
|
||||
//NO-OP
|
||||
(void)msg;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
//handle command line arguments
|
||||
typedef struct CmdLine {
|
||||
bool error;
|
||||
bool help;
|
||||
bool version;
|
||||
char* infile;
|
||||
int infileLength;
|
||||
bool silentPrint;
|
||||
bool silentAssert;
|
||||
bool removeAssert;
|
||||
bool verbose;
|
||||
} CmdLine;
|
||||
|
||||
void usageCmdLine(int argc, const char* argv[]) {
|
||||
(void)argc;
|
||||
printf("Usage: %s [ -h | -v | -f source.toy ]\n\n", argv[0]);
|
||||
}
|
||||
|
||||
void helpCmdLine(int argc, const char* argv[]) {
|
||||
usageCmdLine(argc, argv);
|
||||
|
||||
printf("The Toy Programming Language, leave arguments blank for an interactive REPL.\n\n");
|
||||
|
||||
printf(" -h, --help\t\t\tShow this help then exit.\n");
|
||||
printf(" -v, --version\t\t\tShow version and copyright information then exit.\n");
|
||||
printf(" -f, --file infile\t\tParse, compile and execute the source file then exit.\n");
|
||||
printf(" --silent-print\t\tSuppress output from the print keyword.\n");
|
||||
printf(" --silent-assert\t\tSuppress output from the assert keyword.\n");
|
||||
printf(" --remove-assert\t\tDo not include the assert statement in the bytecode.\n");
|
||||
printf(" -d, --verbose\t\tPrint debugging information about Toy's internals.\n");
|
||||
}
|
||||
|
||||
void versionCmdLine(int argc, const char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
printf("The Toy Programming Language, Version %d.%d.%d %s\n\n", TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, TOY_VERSION_BUILD);
|
||||
|
||||
//copy/pasted from the license file - there's a way to include it directly, but it's too finnicky to bother
|
||||
const char* license =
|
||||
"Copyright (c) 2020-2026 Kayne Ruse, KR Game Studios\n"
|
||||
"\n"
|
||||
"This software is provided 'as-is', without any express or implied\n"
|
||||
"warranty. In no event will the authors be held liable for any damages\n"
|
||||
"arising from the use of this software.\n"
|
||||
"\n"
|
||||
"Permission is granted to anyone to use this software for any purpose,\n"
|
||||
"including commercial applications, and to alter it and redistribute it\n"
|
||||
"freely, subject to the following restrictions:\n"
|
||||
"\n"
|
||||
"1. The origin of this software must not be misrepresented; you must not\n"
|
||||
"claim that you wrote the original software. If you use this software\n"
|
||||
"in a product, an acknowledgment in the product documentation would be\n"
|
||||
"appreciated but is not required.\n"
|
||||
"2. Altered source versions must be plainly marked as such, and must not be\n"
|
||||
"misrepresented as being the original software.\n"
|
||||
"3. This notice may not be removed or altered from any source distribution.\n"
|
||||
"\n"
|
||||
;
|
||||
|
||||
printf("%s",license);
|
||||
}
|
||||
|
||||
CmdLine parseCmdLine(int argc, const char* argv[]) {
|
||||
CmdLine cmd = {
|
||||
.error = false,
|
||||
.help = false,
|
||||
.version = false,
|
||||
.infile = NULL,
|
||||
.infileLength = 0,
|
||||
.silentPrint = false,
|
||||
.silentAssert = false,
|
||||
.removeAssert = false,
|
||||
.verbose = false,
|
||||
};
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
cmd.help = true;
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
|
||||
cmd.version = true;
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file")) {
|
||||
if (argc <= i + 1) {
|
||||
cmd.error = true;
|
||||
}
|
||||
else {
|
||||
if (cmd.infile != NULL) { //don't leak
|
||||
free(cmd.infile);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
//total space to reserve
|
||||
cmd.infileLength = strlen(argv[i]) + 1;
|
||||
cmd.infileLength = (cmd.infileLength + 3) & ~3; //BUGFIX: align to word size
|
||||
cmd.infile = malloc(cmd.infileLength);
|
||||
|
||||
if (cmd.infile == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate space while parsing the command line, exiting\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int len = strlen(argv[i]);
|
||||
strncpy(cmd.infile, argv[i], len);
|
||||
cmd.infile[len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "--silent-print")) {
|
||||
cmd.silentPrint = true;
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "--silent-assert")) {
|
||||
cmd.silentAssert = true;
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "--remove-assert")) {
|
||||
cmd.removeAssert = true;
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--verbose")) {
|
||||
cmd.verbose = true;
|
||||
}
|
||||
|
||||
else {
|
||||
cmd.error = true;
|
||||
}
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
//debugging
|
||||
static void debugStackPrint(Toy_Stack* stack) {
|
||||
//DEBUG: if there's anything on the stack, print it
|
||||
if (stack->count > 0) {
|
||||
Toy_Bucket* stringBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
|
||||
printf("\n" TOY_CC_NOTICE "Stack Dump" TOY_CC_RESET "\n" TOY_CC_NOTICE "%-20s%-20s" TOY_CC_RESET "\n", "type", "value");
|
||||
for (unsigned int i = 0; i < stack->count; i++) {
|
||||
Toy_Value v = ((Toy_Value*)(stack + 1))[i]; //'stack + 1' is a naughty trick
|
||||
|
||||
//print type
|
||||
printf("%-20s", Toy_getValueTypeAsCString(v.type));
|
||||
|
||||
//print value
|
||||
Toy_String* string = Toy_stringifyValue(&stringBucket, Toy_unwrapValue(v));
|
||||
char* buffer = Toy_getStringRaw(string);
|
||||
printf("%-20s", buffer);
|
||||
free(buffer);
|
||||
Toy_freeString(string);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
Toy_freeBucket(&stringBucket);
|
||||
}
|
||||
}
|
||||
|
||||
static void debugScopePrint(Toy_Scope* scope, int depth) {
|
||||
//DEBUG: if there's anything in the scope, print it
|
||||
if (scope->count > 0) {
|
||||
Toy_Bucket* stringBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
|
||||
printf("\n" TOY_CC_NOTICE "Scope Dump [%d]" TOY_CC_RESET "\n" TOY_CC_NOTICE "%-20s%-20s%-20s" TOY_CC_RESET "\n", depth, "type", "name", "value");
|
||||
for (unsigned int i = 0; i < scope->capacity; i++) {
|
||||
if (scope->data[i].key == NULL || scope->data[i].key->info.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Toy_String* k = scope->data[i].key;
|
||||
Toy_Value v = scope->data[i].value;
|
||||
|
||||
printf("%-10s%-10s%-20s", Toy_getValueTypeAsCString(scope->data[i].type), scope->data[i].constant ? "const" : "", k != NULL ? k->leaf.data : "");
|
||||
|
||||
//print value
|
||||
Toy_String* string = Toy_stringifyValue(&stringBucket, Toy_unwrapValue(v));
|
||||
char* buffer = Toy_getStringRaw(string);
|
||||
printf("%-20s", buffer);
|
||||
free(buffer);
|
||||
Toy_freeString(string);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
Toy_freeBucket(&stringBucket);
|
||||
}
|
||||
|
||||
if (scope->next != NULL) {
|
||||
debugScopePrint(scope->next, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
//repl function
|
||||
int repl(const char* filepath, bool verbose) {
|
||||
//output options
|
||||
Toy_setPrintCallback(puts);
|
||||
Toy_setErrorCallback(errorAndContinueCallback);
|
||||
Toy_setAssertFailureCallback(assertFailureAndContinueCallback);
|
||||
|
||||
//vars to use
|
||||
char prompt[256];
|
||||
getFileName(prompt, filepath, 256);
|
||||
unsigned int INPUT_BUFFER_SIZE = 4096;
|
||||
char inputBuffer[INPUT_BUFFER_SIZE];
|
||||
memset(inputBuffer, 0, INPUT_BUFFER_SIZE);
|
||||
|
||||
Toy_VM vm;
|
||||
Toy_initVM(&vm);
|
||||
|
||||
printf("%s> ", prompt); //shows the terminal prompt and begin
|
||||
|
||||
//read from the terminal
|
||||
while(fgets(inputBuffer, INPUT_BUFFER_SIZE, stdin)) {
|
||||
//work around fgets() adding a newline
|
||||
unsigned int length = strlen(inputBuffer);
|
||||
if (inputBuffer[length - 1] == '\n') {
|
||||
inputBuffer[--length] = '\0';
|
||||
}
|
||||
|
||||
if (length == 0 || !inputBuffer[ strspn(inputBuffer, " \r\n\t") ]) {
|
||||
printf("%s> ", prompt); //shows the terminal prompt and restart
|
||||
continue;
|
||||
}
|
||||
|
||||
//end
|
||||
if (strlen(inputBuffer) == 4 && (strncmp(inputBuffer, "exit", 4) == 0 || strncmp(inputBuffer, "quit", 4) == 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
//parse the input, prep the VM for execution
|
||||
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
Toy_Lexer lexer;
|
||||
Toy_bindLexer(&lexer, inputBuffer);
|
||||
Toy_Parser parser;
|
||||
Toy_bindParser(&parser, &lexer);
|
||||
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
|
||||
|
||||
//parsing error, retry
|
||||
if (parser.error || ast == NULL) {
|
||||
Toy_freeBucket(&bucket);
|
||||
printf("%s> ", prompt); //shows the terminal prompt
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
inspect_ast(ast);
|
||||
}
|
||||
|
||||
unsigned char* bytecode = Toy_compileToBytecode(ast);
|
||||
Toy_freeBucket(&bucket); //no need to for the GC here
|
||||
|
||||
if (bytecode == NULL) {
|
||||
printf("%s> ", prompt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
inspect_bytecode(bytecode);
|
||||
}
|
||||
|
||||
//WARN: Hacky debugging
|
||||
if (vm.scope == NULL) {
|
||||
Toy_bindVM(&vm, bytecode, NULL);
|
||||
initStandardLibrary(&vm);
|
||||
}
|
||||
else {
|
||||
Toy_bindVM(&vm, bytecode, NULL);
|
||||
}
|
||||
|
||||
//run
|
||||
Toy_runVM(&vm);
|
||||
|
||||
int depthBeforeGC = 0;
|
||||
int depthAfterGC = 0;
|
||||
|
||||
//print the debug info
|
||||
if (verbose) {
|
||||
debugStackPrint(vm.stack);
|
||||
debugScopePrint(vm.scope, 0);
|
||||
|
||||
depthBeforeGC = inspect_bucket(&vm.memoryBucket);
|
||||
}
|
||||
|
||||
//free the memory, and leave the VM ready for the next loop
|
||||
Toy_resetVM(&vm, true, true);
|
||||
|
||||
if (verbose) {
|
||||
depthAfterGC = inspect_bucket(&vm.memoryBucket);
|
||||
|
||||
printf("GC Report: %d -> %d\n", depthBeforeGC, depthAfterGC);
|
||||
}
|
||||
|
||||
free(bytecode);
|
||||
|
||||
printf("%s> ", prompt); //shows the terminal prompt
|
||||
}
|
||||
|
||||
//cleanup all memory
|
||||
Toy_freeVM(&vm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//main file
|
||||
int main(int argc, const char* argv[]) {
|
||||
Toy_setPrintCallback(puts);
|
||||
Toy_setErrorCallback(errorAndExitCallback);
|
||||
Toy_setAssertFailureCallback(assertFailureAndExitCallback);
|
||||
|
||||
//if there's args, process them
|
||||
CmdLine cmd = parseCmdLine(argc, argv);
|
||||
|
||||
//output options
|
||||
if (cmd.silentPrint) {
|
||||
Toy_setPrintCallback(noOpCallback);
|
||||
}
|
||||
|
||||
if (cmd.silentAssert) {
|
||||
Toy_setAssertFailureCallback(silentExitCallback);
|
||||
}
|
||||
|
||||
//process
|
||||
if (cmd.error) {
|
||||
usageCmdLine(argc, argv);
|
||||
}
|
||||
else if (cmd.help) {
|
||||
helpCmdLine(argc, argv);
|
||||
}
|
||||
else if (cmd.version) {
|
||||
versionCmdLine(argc, argv);
|
||||
}
|
||||
else if (cmd.infile != NULL) {
|
||||
//read the source file
|
||||
int size;
|
||||
unsigned char* source = readFile(cmd.infile, &size);
|
||||
|
||||
//check the file
|
||||
if (source == NULL) {
|
||||
if (size == 0) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Could not parse an empty file '%s', exiting\n" TOY_CC_RESET, cmd.infile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
else if (size == -1) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: File not found '%s', exiting\n" TOY_CC_RESET, cmd.infile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
else {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown error while reading file '%s', exiting\n" TOY_CC_RESET, cmd.infile);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
free(cmd.infile);
|
||||
|
||||
cmd.infile = NULL;
|
||||
cmd.infileLength = 0;
|
||||
|
||||
//compile the source code
|
||||
Toy_Lexer lexer;
|
||||
Toy_bindLexer(&lexer, (char*)source);
|
||||
|
||||
Toy_Parser parser;
|
||||
Toy_bindParser(&parser, &lexer);
|
||||
|
||||
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
||||
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
|
||||
|
||||
if (ast == NULL) {
|
||||
Toy_freeBucket(&bucket);
|
||||
free(source);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cmd.verbose) {
|
||||
inspect_ast(ast);
|
||||
}
|
||||
|
||||
unsigned char* bytecode = Toy_compileToBytecode(ast);
|
||||
Toy_freeBucket(&bucket);
|
||||
free(source);
|
||||
|
||||
if (bytecode == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cmd.verbose) {
|
||||
inspect_bytecode(bytecode);
|
||||
}
|
||||
|
||||
//run the compiled code
|
||||
Toy_VM vm;
|
||||
Toy_initVM(&vm);
|
||||
Toy_bindVM(&vm, bytecode, NULL);
|
||||
initStandardLibrary(&vm); //WARN: Hacky debugging
|
||||
|
||||
Toy_runVM(&vm);
|
||||
|
||||
//print the debug info
|
||||
if (cmd.verbose) {
|
||||
debugStackPrint(vm.stack);
|
||||
debugScopePrint(vm.scope, 0);
|
||||
}
|
||||
|
||||
//cleanup
|
||||
Toy_freeVM(&vm);
|
||||
free(bytecode);
|
||||
}
|
||||
else {
|
||||
repl(argv[0], cmd.verbose);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#compiler settings
|
||||
CC=gcc
|
||||
CFLAGS+=-std=c17 -g -Wall -Werror -Wextra -Wpedantic -Wformat=2 -Wno-newline-eof
|
||||
LIBS+=-lm -lToy
|
||||
LDFLAGS+=-Wl,-rpath,'$$ORIGIN'
|
||||
|
||||
ifeq ($(shell uname),Darwin) #make sure there's enough space for the dylib fix
|
||||
LDFLAGS+=-Wl,-headerpad_max_install_names
|
||||
endif
|
||||
|
||||
|
||||
#directories
|
||||
REPL_ROOTDIR=..
|
||||
REPL_REPLDIR=.
|
||||
REPL_SOURCEDIR=$(REPL_ROOTDIR)/$(TOY_SOURCEDIR)
|
||||
|
||||
REPL_OUTDIR=$(REPL_ROOTDIR)/$(TOY_OUTDIR)
|
||||
REPL_OBJDIR=$(TOY_OBJDIR)
|
||||
|
||||
#file names
|
||||
REPL_REPLFILES=$(wildcard $(REPL_REPLDIR)/*.c)
|
||||
REPL_OBJFILES=$(addprefix $(REPL_OBJDIR)/,$(notdir $(REPL_REPLFILES:.c=.o)))
|
||||
REPL_TARGETNAME=repl
|
||||
|
||||
#file extensions
|
||||
ifeq ($(OS),Windows_NT)
|
||||
REPL_TARGETEXT=.exe
|
||||
else
|
||||
REPL_TARGETEXT=.out
|
||||
endif
|
||||
|
||||
#linker fix
|
||||
LDFLAGS+=-L$(realpath $(REPL_OUTDIR))
|
||||
|
||||
#build the object files, compile the test cases, and run
|
||||
all: build link
|
||||
|
||||
#targets for each step
|
||||
.PHONY: build
|
||||
build: $(REPL_OBJDIR) $(REPL_OBJFILES)
|
||||
|
||||
.PHONY: link
|
||||
link: $(REPL_OUTDIR) $(REPL_OUTDIR)/$(REPL_TARGETNAME)$(REPL_TARGETEXT)
|
||||
|
||||
#util targets
|
||||
$(REPL_OUTDIR):
|
||||
mkdir $(REPL_OUTDIR)
|
||||
|
||||
$(REPL_OBJDIR):
|
||||
mkdir $(REPL_OBJDIR)
|
||||
|
||||
#compilation steps
|
||||
$(REPL_OBJDIR)/%.o: $(REPL_REPLDIR)/%.c
|
||||
$(CC) -c -o $@ $< $(addprefix -I,$(REPL_REPLDIR)) $(addprefix -I,$(REPL_SOURCEDIR)) $(CFLAGS)
|
||||
|
||||
$(REPL_OUTDIR)/$(REPL_TARGETNAME)$(REPL_TARGETEXT): $(REPL_OBJFILES)
|
||||
$(CC) -DTOY_IMPORT $(CFLAGS) -o $@ $(REPL_OBJFILES) $(LDFLAGS) $(LIBS)
|
||||
ifeq ($(shell uname),Darwin) #dylib fix
|
||||
otool -L $@
|
||||
install_name_tool -add_rpath @executable_path/. $@
|
||||
install_name_tool -change $(REPL_OUTDIR)/libToy.dylib @executable_path/libToy.dylib $@
|
||||
otool -L $@
|
||||
endif
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "standard_library.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include "toy_print.h"
|
||||
#include "toy_scope.h"
|
||||
#include "toy_stack.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct CallbackPairs {
|
||||
const char* name;
|
||||
Toy_nativeCallback callback;
|
||||
} CallbackPairs;
|
||||
|
||||
//example callbacks
|
||||
static void answer(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)vm;
|
||||
(void)self;
|
||||
Toy_print(TOY_CC_DEBUG "This function returns the integer '42' to the calling scope." TOY_CC_RESET);
|
||||
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_INTEGER(42));
|
||||
}
|
||||
|
||||
static void identity(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
//does nothing, but any arguements are left on the stack as results
|
||||
(void)vm;
|
||||
(void)self;
|
||||
}
|
||||
|
||||
static void echo(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
//pops one argument, and prints it
|
||||
Toy_Value value = Toy_popStack(&vm->stack);
|
||||
Toy_String* string = Toy_stringifyValue(&vm->memoryBucket, value);
|
||||
char* cstr = Toy_getStringRaw(string);
|
||||
|
||||
Toy_print(cstr);
|
||||
|
||||
free(cstr);
|
||||
Toy_freeString(string);
|
||||
Toy_freeValue(value);
|
||||
}
|
||||
|
||||
static void next(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
//used by 'range'
|
||||
if (self->meta2 < self->meta1) {
|
||||
Toy_Value result = TOY_VALUE_FROM_INTEGER(self->meta2);
|
||||
Toy_pushStack(&vm->stack, result);
|
||||
self->meta2++;
|
||||
}
|
||||
else {
|
||||
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
|
||||
}
|
||||
}
|
||||
|
||||
static void range(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
//one arg to represent the number of iterations
|
||||
Toy_Value value = Toy_popStack(&vm->stack);
|
||||
|
||||
//check types
|
||||
if (!TOY_VALUE_IS_INTEGER(value)) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Expected Integer argument in 'range', found '%s'", Toy_getValueTypeAsCString(value.type));
|
||||
Toy_error(buffer);
|
||||
Toy_freeValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
//make the callback
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, next);
|
||||
fn->native.meta1 = TOY_VALUE_AS_INTEGER(value); //fake a closure
|
||||
fn->native.meta2 = 0; //counter
|
||||
|
||||
Toy_Value result = TOY_VALUE_FROM_FUNCTION(fn);
|
||||
|
||||
Toy_pushStack(&vm->stack, result);
|
||||
}
|
||||
|
||||
|
||||
CallbackPairs callbackPairs[] = {
|
||||
{"dbg_answer", answer},
|
||||
{"dbg_identity", identity},
|
||||
{"dbg_echo", echo},
|
||||
{"range", range},
|
||||
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
//exposed functions
|
||||
void initStandardLibrary(Toy_VM* vm) {
|
||||
if (vm == NULL || vm->scope == NULL || vm->memoryBucket == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't initialize standard library, exiting\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
//declare each pair
|
||||
for (int i = 0; callbackPairs[i].name; i++) {
|
||||
Toy_String* key = Toy_createStringLength(&vm->memoryBucket, callbackPairs[i].name, strlen(callbackPairs[i].name));
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&(vm->memoryBucket), callbackPairs[i].callback);
|
||||
|
||||
Toy_declareScope(vm->scope, key, TOY_VALUE_FUNCTION, TOY_VALUE_FROM_FUNCTION(fn), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_vm.h"
|
||||
|
||||
void initStandardLibrary(Toy_VM*);
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="454.000000pt" height="454.000000pt" viewBox="0 0 454.000000 454.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,454.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1178 4533 c-3 -5 -4 -508 -3 -1118 l0 -1111 -565 0 -565 0 -3 -1152
|
||||
-2 -1152 2230 0 2230 0 -2 1152 -3 1152 -522 0 c-359 0 -523 3 -524 10 0 6 0
|
||||
509 0 1119 l-1 1107 -1133 0 c-624 0 -1135 -3 -1137 -7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 730 B |
@@ -0,0 +1,16 @@
|
||||
//calculate the nth fibonacci number, and print it
|
||||
|
||||
var counter: Int = 0;
|
||||
|
||||
var first: Int = 1;
|
||||
var second: Int = 0;
|
||||
|
||||
while (counter < 100_000) {
|
||||
var third: Int = first + second;
|
||||
first = second;
|
||||
second = third;
|
||||
|
||||
print third;
|
||||
|
||||
++counter;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
fn output(arg) {
|
||||
print arg;
|
||||
}
|
||||
|
||||
var array = ["alpha", "bravo", "charlie"];
|
||||
|
||||
|
||||
|
||||
array.forEach(echo);
|
||||
array.forEach(output);
|
||||
@@ -0,0 +1,13 @@
|
||||
//tentatively functional
|
||||
|
||||
//fibonacci sequence
|
||||
fn fib(n) {
|
||||
if (n < 2) return n;
|
||||
return fib(n-1) + fib(n-2);
|
||||
}
|
||||
|
||||
print fib(12);
|
||||
|
||||
//Note to my future self: yes, the base case in 'fib()' is 'n < 2', stop second guessing yourself!
|
||||
//Note to my past self: don't tell me what to do!
|
||||
//Note to both of you: keep it down you young whipper snappers!
|
||||
@@ -0,0 +1,24 @@
|
||||
//standard example, using 'while' instead of 'for', because it's not ready yet
|
||||
|
||||
var counter: Int = 0;
|
||||
|
||||
while (++counter <= 100) {
|
||||
var result: String = "";
|
||||
|
||||
if (counter % 3 == 0) {
|
||||
result = result .. "fizz";
|
||||
}
|
||||
|
||||
if (counter % 5 == 0) {
|
||||
result = result .. "buzz";
|
||||
}
|
||||
|
||||
//finally
|
||||
if (result != "") {
|
||||
print result;
|
||||
}
|
||||
else {
|
||||
print counter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//WARN: This is just a scratch pad, don't use it
|
||||
//TODO: table.hasValue or table.getKeyFromValue?
|
||||
|
||||
|
||||
//for (var i in array) print i;
|
||||
//for (var i in table) print i;
|
||||
//for (var i in range(10)) print i;
|
||||
//for (range(10)) print "ha";
|
||||
|
||||
|
||||
//example of a `range`-like function
|
||||
fn range(limit: Int) {
|
||||
var counter: Int = 0;
|
||||
|
||||
fn next() {
|
||||
if (counter >= limit) {
|
||||
return null;
|
||||
}
|
||||
else return counter++;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
var next = range(10);
|
||||
|
||||
|
||||
fn log(x) {
|
||||
if (x == null) return;
|
||||
print x;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
log(next());
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
|
||||
fn a(x) {
|
||||
print x;
|
||||
}
|
||||
|
||||
fn b() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
a(b(), b());
|
||||
@@ -0,0 +1,19 @@
|
||||
//find the leap years
|
||||
fn isLeapYear(n: Int) {
|
||||
if (n % 400 == 0) return true;
|
||||
if (n % 100 == 0) return false;
|
||||
return n % 4 == 0;
|
||||
}
|
||||
|
||||
//check for string reuse
|
||||
{
|
||||
print isLeapYear(1999);
|
||||
}
|
||||
|
||||
{
|
||||
print isLeapYear(2000);
|
||||
}
|
||||
|
||||
{
|
||||
print isLeapYear(2004);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
|
||||
var randi: Int = 69420;
|
||||
fn rand() {
|
||||
return randi = randi * 1664525 + 1013904223;
|
||||
}
|
||||
|
||||
|
||||
var a = rand();
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
fn makeCounter() {
|
||||
var counter: Int = 0;
|
||||
|
||||
fn increment() {
|
||||
return ++counter;
|
||||
}
|
||||
|
||||
return increment;
|
||||
}
|
||||
|
||||
var tally = makeCounter();
|
||||
|
||||
while (true) {
|
||||
var result = tally();
|
||||
|
||||
print result;
|
||||
|
||||
if (result >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#compiler settings
|
||||
CC=gcc
|
||||
CFLAGS+=-std=c17 -g -Wall -Werror -Wextra -Wpedantic -Wformat=2 -Wno-newline-eof
|
||||
LIBS+=-lm
|
||||
LDFLAGS+=
|
||||
|
||||
#directories
|
||||
SRC_ROOTDIR=..
|
||||
SRC_SOURCEDIR=.
|
||||
|
||||
SRC_OUTDIR=$(SRC_ROOTDIR)/$(TOY_OUTDIR)
|
||||
SRC_OBJDIR=$(TOY_OBJDIR)
|
||||
|
||||
#file names
|
||||
SRC_SOURCEFILES=$(wildcard $(SRC_SOURCEDIR)/*.c)
|
||||
SRC_OBJFILES=$(addprefix $(SRC_OBJDIR)/,$(notdir $(SRC_SOURCEFILES:.c=.o)))
|
||||
SRC_TARGETNAME=Toy
|
||||
|
||||
#SRC_LIBLINE is a fancy way of making the linker work correctly
|
||||
ifeq ($(shell uname),Linux)
|
||||
SRC_TARGETEXT=.so
|
||||
SRC_LIBLINE=-shared -Wl,-rpath,. -Wl,--out-implib=$(SRC_OUTDIR)/lib$(SRC_TARGETNAME).a -Wl,--whole-archive $(SRC_OBJFILES) -Wl,--no-whole-archive
|
||||
CFLAGS+=-fPIC
|
||||
else ifeq ($(shell uname),NetBSD)
|
||||
SRC_TARGETEXT=.so
|
||||
SRC_LIBLINE=-shared -Wl,-rpath,. -Wl,--out-implib=$(SRC_OUTDIR)/lib$(SRC_TARGETNAME).a -Wl,--whole-archive $(SRC_OBJFILES) -Wl,--no-whole-archive
|
||||
CFLAGS+=-fPIC
|
||||
else ifeq ($(OS),Windows_NT)
|
||||
SRC_TARGETEXT=.dll
|
||||
SRC_LIBLINE=-shared -Wl,-rpath,. -Wl,--out-implib=$(SRC_OUTDIR)/lib$(SRC_TARGETNAME).a -Wl,--whole-archive $(SRC_OBJFILES) -Wl,--no-whole-archive -Wl,--export-all-symbols -Wl,--enable-auto-import
|
||||
else ifeq ($(shell uname),Darwin)
|
||||
SRC_TARGETEXT=.dylib
|
||||
SRC_LIBLINE=-shared -Wl,-rpath,. $(SRC_OBJFILES)
|
||||
else
|
||||
@echo "Platform test failed - what platform is this?"
|
||||
exit 1
|
||||
endif
|
||||
|
||||
#build the object files, compile the test cases, and run
|
||||
all: build link
|
||||
|
||||
#targets for each step
|
||||
.PHONY: build
|
||||
build: $(SRC_OUTDIR) $(SRC_OBJDIR) $(SRC_OBJFILES)
|
||||
|
||||
.PHONY: link
|
||||
link: $(SRC_OUTDIR)
|
||||
$(CC) -DTOY_EXPORT $(CFLAGS) -o $(SRC_OUTDIR)/lib$(SRC_TARGETNAME)$(SRC_TARGETEXT) $(SRC_LIBLINE)
|
||||
|
||||
#util targets
|
||||
$(SRC_OUTDIR):
|
||||
mkdir $(SRC_OUTDIR)
|
||||
|
||||
$(SRC_OBJDIR):
|
||||
mkdir $(SRC_OBJDIR)
|
||||
|
||||
#compilation steps
|
||||
$(SRC_OBJDIR)/%.o: $(SRC_SOURCEDIR)/%.c
|
||||
$(CC) -c -o $@ $< $(addprefix -I,$(SRC_SOURCEDIR)) $(CFLAGS)
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "toy_array.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) {
|
||||
//if some values will be removed, free them first
|
||||
if (paramArray != NULL && paramArray->count > capacity) {
|
||||
for (unsigned int i = capacity; i < paramArray->count; i++) {
|
||||
Toy_freeValue(paramArray->data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//if you're freeing everything, just return
|
||||
if (capacity == 0) {
|
||||
free(paramArray);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int originalCapacity = paramArray == NULL ? 0 : paramArray->capacity;
|
||||
|
||||
Toy_Array* array = realloc(paramArray, capacity * sizeof(Toy_Value) + sizeof(Toy_Array));
|
||||
|
||||
if (array == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to resize a 'Toy_Array' from %d to %d capacity\n" TOY_CC_RESET, (int)originalCapacity, (int)capacity);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
array->capacity = capacity;
|
||||
array->count = paramArray == NULL ? 0 :
|
||||
(array->count > capacity ? capacity : array->count); //truncate lost data
|
||||
|
||||
return array;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_value.h"
|
||||
|
||||
//standard generic array
|
||||
typedef struct Toy_Array { //32 | 64 BITNESS
|
||||
unsigned int capacity; //4 | 4
|
||||
unsigned int count; //4 | 4
|
||||
Toy_Value data[]; //- | -
|
||||
} Toy_Array; //8 | 8
|
||||
|
||||
TOY_API Toy_Array* Toy_resizeArray(Toy_Array* array, unsigned int capacity);
|
||||
|
||||
//some useful sizes, could be swapped out as needed
|
||||
#ifndef TOY_ARRAY_INITIAL_CAPACITY
|
||||
#define TOY_ARRAY_INITIAL_CAPACITY 8
|
||||
#endif
|
||||
|
||||
#ifndef TOY_ARRAY_EXPANSION_RATE
|
||||
#define TOY_ARRAY_EXPANSION_RATE 2
|
||||
#endif
|
||||
@@ -0,0 +1,339 @@
|
||||
#include "toy_ast.h"
|
||||
|
||||
void Toy_private_initAstBlock(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_BLOCK;
|
||||
tmp->block.innerScope = false;
|
||||
tmp->block.child = NULL;
|
||||
tmp->block.next = NULL;
|
||||
tmp->block.tail = NULL;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_appendAstBlock(Toy_Bucket** bucketHandle, Toy_Ast* block, Toy_Ast* child) {
|
||||
//first, check if we're an empty head
|
||||
if (block->block.child == NULL) {
|
||||
block->block.child = child;
|
||||
return; //First call on an empty head skips any memory allocations
|
||||
}
|
||||
|
||||
//run (or jump) until we hit the current tail
|
||||
Toy_Ast* iter = block->block.tail ? block->block.tail : block;
|
||||
|
||||
while(iter->block.next != NULL) {
|
||||
iter = iter->block.next;
|
||||
}
|
||||
|
||||
//append a new link to the chain
|
||||
Toy_private_initAstBlock(bucketHandle, &(iter->block.next));
|
||||
|
||||
//store the child in the new link, prep the tail pointer
|
||||
iter->block.next->block.child = child;
|
||||
block->block.tail = iter->block.next;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstValue(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Value value) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_VALUE;
|
||||
tmp->value.value = value;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstUnary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_UNARY;
|
||||
tmp->unary.flag = flag;
|
||||
tmp->unary.child = *astHandle;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstBinary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_BINARY;
|
||||
tmp->binary.flag = flag;
|
||||
tmp->binary.left = *astHandle; //left-recursive
|
||||
tmp->binary.right = right;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstBinaryShortCircuit(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_BINARY_SHORT_CIRCUIT;
|
||||
tmp->binary.flag = flag;
|
||||
tmp->binary.left = *astHandle; //left-recursive
|
||||
tmp->binary.right = right;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstCompare(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_COMPARE;
|
||||
tmp->compare.flag = flag;
|
||||
tmp->compare.left = *astHandle; //left-recursive
|
||||
tmp->compare.right = right;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstGroup(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_GROUP;
|
||||
tmp->group.child = (*astHandle);
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_COMPOUND;
|
||||
tmp->compound.flag = flag;
|
||||
tmp->compound.child = *astHandle;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstAggregate(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_AGGREGATE;
|
||||
tmp->aggregate.flag = flag;
|
||||
tmp->aggregate.left = *astHandle; //left-recursive
|
||||
tmp->aggregate.right = right;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* child, Toy_Ast* msg) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_ASSERT;
|
||||
tmp->assert.child = child;
|
||||
tmp->assert.message = msg;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstIfThenElse(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch, Toy_Ast* elseBranch) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_IF_THEN_ELSE;
|
||||
tmp->ifThenElse.condBranch = condBranch;
|
||||
tmp->ifThenElse.thenBranch = thenBranch;
|
||||
tmp->ifThenElse.elseBranch = elseBranch;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstWhileThen(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_WHILE_THEN;
|
||||
tmp->whileThen.condBranch = condBranch;
|
||||
tmp->whileThen.thenBranch = thenBranch;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstForThen(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_FOR_THEN;
|
||||
tmp->forThen.condBranch = condBranch;
|
||||
tmp->forThen.thenBranch = thenBranch;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstBreak(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_BREAK;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstContinue(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_CONTINUE;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstReturn(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_RETURN;
|
||||
tmp->fnReturn.child = (*astHandle);
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_PRINT;
|
||||
tmp->print.child = (*astHandle);
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstVariableDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_ValueType valueType, bool constant, Toy_Ast* expr) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_VAR_DECLARE;
|
||||
tmp->varDeclare.name = name;
|
||||
tmp->varDeclare.valueType = valueType;
|
||||
tmp->varDeclare.constant = constant;
|
||||
tmp->varDeclare.expr = expr;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstVariableAssignment(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* expr) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_VAR_ASSIGN;
|
||||
tmp->varAssign.flag = flag;
|
||||
tmp->varAssign.target = (*astHandle);
|
||||
tmp->varAssign.expr = expr;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstVariableAccess(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_VAR_ACCESS;
|
||||
tmp->varAccess.child = (*astHandle);
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstFunctionDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_Ast* params, Toy_Ast* body) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_FN_DECLARE;
|
||||
tmp->fnDeclare.name = name;
|
||||
tmp->fnDeclare.params = params;
|
||||
tmp->fnDeclare.body = body;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstFunctionInvokation(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* args) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_FN_INVOKE;
|
||||
tmp->fnInvoke.function = (*astHandle);
|
||||
tmp->fnInvoke.args = args;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstAttribute(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* expr) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_ATTRIBUTE;
|
||||
tmp->attribute.left = (*astHandle);
|
||||
tmp->attribute.right = expr;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstIterable(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* expr) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_ITERABLE;
|
||||
tmp->iterable.left = (*astHandle);
|
||||
tmp->iterable.right = expr;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstStackPop(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_STACK_POP;
|
||||
tmp->stackPop.child = (*astHandle);
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstPass(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_PASS;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstError(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_ERROR;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
void Toy_private_emitAstEnd(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) {
|
||||
Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast));
|
||||
|
||||
tmp->type = TOY_AST_END;
|
||||
|
||||
(*astHandle) = tmp;
|
||||
}
|
||||
|
||||
const char* Toy_private_getAstTypeAsCString(Toy_AstType type) {
|
||||
switch(type) {
|
||||
case TOY_AST_BLOCK: return "BLOCK";
|
||||
|
||||
case TOY_AST_VALUE: return "VALUE";
|
||||
case TOY_AST_UNARY: return "UNARY";
|
||||
case TOY_AST_BINARY: return "BINARY";
|
||||
case TOY_AST_BINARY_SHORT_CIRCUIT: return "BINARY_SHORT_CIRCUIT";
|
||||
case TOY_AST_COMPARE: return "COMPARE";
|
||||
case TOY_AST_GROUP: return "GROUP";
|
||||
case TOY_AST_COMPOUND: return "COMPOUND";
|
||||
case TOY_AST_AGGREGATE: return "AGGREGATE";
|
||||
|
||||
case TOY_AST_ASSERT: return "ASSERT";
|
||||
case TOY_AST_IF_THEN_ELSE: return "IF_THEN_ELSE";
|
||||
case TOY_AST_WHILE_THEN: return "WHILE_THEN";
|
||||
case TOY_AST_FOR_THEN: return "FOR_THEN";
|
||||
case TOY_AST_BREAK: return "BREAK";
|
||||
case TOY_AST_CONTINUE: return "CONTINUE";
|
||||
case TOY_AST_RETURN: return "RETURN";
|
||||
case TOY_AST_PRINT: return "PRINT";
|
||||
|
||||
case TOY_AST_VAR_DECLARE: return "DECLARE";
|
||||
case TOY_AST_VAR_ASSIGN: return "ASSIGN";
|
||||
case TOY_AST_VAR_ACCESS: return "ACCESS";
|
||||
|
||||
case TOY_AST_FN_DECLARE: return "FN_DECLARE";
|
||||
case TOY_AST_FN_INVOKE: return "FN_INVOKE";
|
||||
case TOY_AST_ATTRIBUTE: return "ATTRIBUTE";
|
||||
case TOY_AST_ITERABLE: return "ITERABLE";
|
||||
|
||||
case TOY_AST_STACK_POP: return "STACK_POP";
|
||||
|
||||
case TOY_AST_PASS: return "PASS";
|
||||
case TOY_AST_ERROR: return "ERROR";
|
||||
case TOY_AST_END: return "END";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_value.h"
|
||||
#include "toy_string.h"
|
||||
|
||||
//each major type
|
||||
typedef enum Toy_AstType {
|
||||
TOY_AST_BLOCK,
|
||||
|
||||
TOY_AST_VALUE,
|
||||
TOY_AST_UNARY,
|
||||
TOY_AST_BINARY,
|
||||
TOY_AST_BINARY_SHORT_CIRCUIT,
|
||||
TOY_AST_COMPARE,
|
||||
TOY_AST_GROUP,
|
||||
TOY_AST_COMPOUND,
|
||||
TOY_AST_AGGREGATE,
|
||||
|
||||
TOY_AST_ASSERT,
|
||||
TOY_AST_IF_THEN_ELSE,
|
||||
TOY_AST_WHILE_THEN,
|
||||
TOY_AST_FOR_THEN,
|
||||
TOY_AST_BREAK,
|
||||
TOY_AST_CONTINUE,
|
||||
TOY_AST_RETURN,
|
||||
TOY_AST_PRINT,
|
||||
|
||||
TOY_AST_VAR_DECLARE,
|
||||
TOY_AST_VAR_ASSIGN,
|
||||
TOY_AST_VAR_ACCESS,
|
||||
|
||||
TOY_AST_FN_DECLARE,
|
||||
TOY_AST_FN_INVOKE,
|
||||
TOY_AST_ATTRIBUTE,
|
||||
TOY_AST_ITERABLE,
|
||||
|
||||
TOY_AST_STACK_POP, //BUGFIX: force a single stack pop for expression statements
|
||||
|
||||
TOY_AST_PASS,
|
||||
TOY_AST_ERROR,
|
||||
TOY_AST_END,
|
||||
} Toy_AstType;
|
||||
|
||||
//flags are handled differently by different types
|
||||
typedef enum Toy_AstFlag {
|
||||
TOY_AST_FLAG_NONE = 0,
|
||||
|
||||
//binary flags
|
||||
TOY_AST_FLAG_ADD = 1,
|
||||
TOY_AST_FLAG_SUBTRACT = 2,
|
||||
TOY_AST_FLAG_MULTIPLY = 3,
|
||||
TOY_AST_FLAG_DIVIDE = 4,
|
||||
TOY_AST_FLAG_MODULO = 5,
|
||||
|
||||
TOY_AST_FLAG_AND = 6,
|
||||
TOY_AST_FLAG_OR = 7,
|
||||
TOY_AST_FLAG_CONCAT = 8,
|
||||
|
||||
TOY_AST_FLAG_ASSIGN = 10,
|
||||
TOY_AST_FLAG_ADD_ASSIGN = 11,
|
||||
TOY_AST_FLAG_SUBTRACT_ASSIGN = 12,
|
||||
TOY_AST_FLAG_MULTIPLY_ASSIGN = 13,
|
||||
TOY_AST_FLAG_DIVIDE_ASSIGN = 14,
|
||||
TOY_AST_FLAG_MODULO_ASSIGN = 15,
|
||||
|
||||
TOY_AST_FLAG_COMPARE_EQUAL = 20,
|
||||
TOY_AST_FLAG_COMPARE_NOT = 21,
|
||||
TOY_AST_FLAG_COMPARE_LESS = 22,
|
||||
TOY_AST_FLAG_COMPARE_LESS_EQUAL = 23,
|
||||
TOY_AST_FLAG_COMPARE_GREATER = 24,
|
||||
TOY_AST_FLAG_COMPARE_GREATER_EQUAL = 25,
|
||||
|
||||
TOY_AST_FLAG_COMPOUND_ARRAY = 30,
|
||||
TOY_AST_FLAG_COMPOUND_TABLE = 31,
|
||||
TOY_AST_FLAG_COLLECTION = 32,
|
||||
TOY_AST_FLAG_PAIR = 33,
|
||||
TOY_AST_FLAG_INDEX = 34,
|
||||
TOY_AST_FLAG_FN_ARGUMENTS = 35,
|
||||
|
||||
//unary flags
|
||||
TOY_AST_FLAG_NEGATE = 40,
|
||||
TOY_AST_FLAG_PREFIX_INCREMENT = 41,
|
||||
TOY_AST_FLAG_PREFIX_DECREMENT = 42,
|
||||
TOY_AST_FLAG_POSTFIX_INCREMENT = 43,
|
||||
TOY_AST_FLAG_POSTFIX_DECREMENT = 44,
|
||||
|
||||
TOY_AST_FLAG_INVOKATION = 45,
|
||||
TOY_AST_FLAG_ATTRIBUTE = 46,
|
||||
|
||||
// TOY_AST_FLAG_TERNARY,
|
||||
} Toy_AstFlag;
|
||||
|
||||
//the root AST type
|
||||
typedef union Toy_Ast Toy_Ast;
|
||||
|
||||
typedef struct Toy_AstBlock {
|
||||
Toy_AstType type;
|
||||
bool innerScope;
|
||||
Toy_Ast* child; //begin encoding the line
|
||||
Toy_Ast* next; //'next' is either an AstBlock or null
|
||||
Toy_Ast* tail; //'tail' - either points to the tail of the current list, or null; only used as an optimisation in toy_ast.c
|
||||
} Toy_AstBlock;
|
||||
|
||||
typedef struct Toy_AstValue {
|
||||
Toy_AstType type;
|
||||
Toy_Value value;
|
||||
} Toy_AstValue;
|
||||
|
||||
typedef struct Toy_AstUnary {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstUnary;
|
||||
|
||||
typedef struct Toy_AstBinary {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstBinary;
|
||||
|
||||
typedef struct Toy_AstBinaryShortCircuit {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstBinaryShortCircuit;
|
||||
|
||||
typedef struct Toy_AstCompare {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstCompare;
|
||||
|
||||
typedef struct Toy_AstGroup {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstGroup;
|
||||
|
||||
typedef struct Toy_AstCompound {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstCompound;
|
||||
|
||||
typedef struct Toy_AstAggregate {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstAggregate;
|
||||
|
||||
typedef struct Toy_AstAssert {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
Toy_Ast* message;
|
||||
} Toy_AstAssert;
|
||||
|
||||
typedef struct Toy_AstIfThenElse {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* condBranch;
|
||||
Toy_Ast* thenBranch;
|
||||
Toy_Ast* elseBranch;
|
||||
} Toy_AstIfThenElse;
|
||||
|
||||
typedef struct Toy_AstWhileThen {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* condBranch;
|
||||
Toy_Ast* thenBranch;
|
||||
} Toy_AstWhileThen;
|
||||
|
||||
typedef struct Toy_AstForThen {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* condBranch;
|
||||
Toy_Ast* thenBranch;
|
||||
} Toy_AstForThen;
|
||||
|
||||
typedef struct Toy_AstBreak {
|
||||
Toy_AstType type;
|
||||
} Toy_AstBreak;
|
||||
|
||||
typedef struct Toy_AstContinue {
|
||||
Toy_AstType type;
|
||||
} Toy_AstContinue;
|
||||
|
||||
typedef struct Toy_AstReturn {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstReturn;
|
||||
|
||||
typedef struct Toy_AstPrint {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstPrint;
|
||||
|
||||
typedef struct Toy_AstVarDeclare {
|
||||
Toy_AstType type;
|
||||
Toy_String* name;
|
||||
Toy_Ast* expr;
|
||||
Toy_ValueType valueType;
|
||||
bool constant;
|
||||
} Toy_AstVarDeclare;
|
||||
|
||||
typedef struct Toy_AstVarAssign {
|
||||
Toy_AstType type;
|
||||
Toy_AstFlag flag;
|
||||
Toy_Ast* target;
|
||||
Toy_Ast* expr;
|
||||
} Toy_AstVarAssign;
|
||||
|
||||
typedef struct Toy_AstVarAccess {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstVarAccess;
|
||||
|
||||
typedef struct Toy_AstFnDeclare {
|
||||
Toy_AstType type;
|
||||
Toy_String* name;
|
||||
Toy_Ast* params;
|
||||
Toy_Ast* body;
|
||||
} Toy_AstFnDeclare;
|
||||
|
||||
typedef struct Toy_AstFnInvoke {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* function;
|
||||
Toy_Ast* args;
|
||||
} Toy_AstFnInvoke;
|
||||
|
||||
typedef struct Toy_AstAttribute {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstAttribute;
|
||||
|
||||
typedef struct Toy_AstIterable {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* left;
|
||||
Toy_Ast* right;
|
||||
} Toy_AstIterable;
|
||||
|
||||
typedef struct Toy_AstStackPop {
|
||||
Toy_AstType type;
|
||||
Toy_Ast* child;
|
||||
} Toy_AstStackPop;
|
||||
|
||||
typedef struct Toy_AstPass {
|
||||
Toy_AstType type;
|
||||
} Toy_AstPass;
|
||||
|
||||
typedef struct Toy_AstError {
|
||||
Toy_AstType type;
|
||||
} Toy_AstError;
|
||||
|
||||
typedef struct Toy_AstEnd {
|
||||
Toy_AstType type;
|
||||
} Toy_AstEnd;
|
||||
|
||||
union Toy_Ast { //see 'test_ast.c' for bitness tests
|
||||
Toy_AstType type;
|
||||
Toy_AstBlock block;
|
||||
Toy_AstValue value;
|
||||
Toy_AstUnary unary;
|
||||
Toy_AstBinary binary;
|
||||
Toy_AstBinaryShortCircuit binaryShortCircuit;
|
||||
Toy_AstCompare compare;
|
||||
Toy_AstGroup group;
|
||||
Toy_AstCompound compound;
|
||||
Toy_AstAggregate aggregate;
|
||||
Toy_AstAssert assert;
|
||||
Toy_AstIfThenElse ifThenElse;
|
||||
Toy_AstWhileThen whileThen;
|
||||
Toy_AstForThen forThen;
|
||||
Toy_AstBreak breakPoint;
|
||||
Toy_AstContinue continuePoint;
|
||||
Toy_AstReturn fnReturn;
|
||||
Toy_AstPrint print;
|
||||
Toy_AstVarDeclare varDeclare;
|
||||
Toy_AstVarAssign varAssign;
|
||||
Toy_AstVarAccess varAccess;
|
||||
Toy_AstFnDeclare fnDeclare;
|
||||
Toy_AstFnInvoke fnInvoke;
|
||||
Toy_AstAttribute attribute;
|
||||
Toy_AstIterable iterable;
|
||||
Toy_AstStackPop stackPop;
|
||||
Toy_AstPass pass;
|
||||
Toy_AstError error;
|
||||
Toy_AstEnd end;
|
||||
};
|
||||
|
||||
void Toy_private_initAstBlock(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
void Toy_private_appendAstBlock(Toy_Bucket** bucketHandle, Toy_Ast* block, Toy_Ast* child);
|
||||
|
||||
void Toy_private_emitAstValue(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Value value);
|
||||
void Toy_private_emitAstUnary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag);
|
||||
void Toy_private_emitAstBinary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right);
|
||||
void Toy_private_emitAstBinaryShortCircuit(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right);
|
||||
void Toy_private_emitAstCompare(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right);
|
||||
void Toy_private_emitAstGroup(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag);
|
||||
void Toy_private_emitAstAggregate(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right);
|
||||
|
||||
void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* child, Toy_Ast* msg);
|
||||
void Toy_private_emitAstIfThenElse(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch, Toy_Ast* elseBranch);
|
||||
void Toy_private_emitAstWhileThen(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch);
|
||||
void Toy_private_emitAstForThen(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch);
|
||||
void Toy_private_emitAstBreak(Toy_Bucket** bucketHandle, Toy_Ast** rootHandle);
|
||||
void Toy_private_emitAstContinue(Toy_Bucket** bucketHandle, Toy_Ast** rootHandle);
|
||||
void Toy_private_emitAstReturn(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
|
||||
void Toy_private_emitAstVariableDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_ValueType valueType, bool constant, Toy_Ast* expr);
|
||||
void Toy_private_emitAstVariableAssignment(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* expr);
|
||||
void Toy_private_emitAstVariableAccess(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
|
||||
void Toy_private_emitAstFunctionDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_Ast* params, Toy_Ast* body);
|
||||
void Toy_private_emitAstFunctionInvokation(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* params);
|
||||
void Toy_private_emitAstAttribute(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* expr);
|
||||
void Toy_private_emitAstIterable(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* expr);
|
||||
|
||||
void Toy_private_emitAstStackPop(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
|
||||
void Toy_private_emitAstPass(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
void Toy_private_emitAstError(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
void Toy_private_emitAstEnd(Toy_Bucket** bucketHandle, Toy_Ast** astHandle);
|
||||
|
||||
const char* Toy_private_getAstTypeAsCString(Toy_AstType type);
|
||||
@@ -0,0 +1,289 @@
|
||||
#include "toy_attributes.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
//if set, used for delegating to user-defined code
|
||||
static Toy_OpaqueAttributeHandler opaqueAttributeCallback = NULL;
|
||||
|
||||
//utils
|
||||
#define MATCH_VALUE_AND_CSTRING(value, cstring) \
|
||||
((TOY_VALUE_AS_STRING(value)->info.length == strlen(cstring)) && \
|
||||
(strncmp(cstring, TOY_VALUE_AS_STRING(value)->leaf.data, TOY_VALUE_AS_STRING(value)->info.length) == 0))
|
||||
|
||||
//NOTE: there is no need to call 'Toy_freeValue' on the arguments, as the VM assumes you don't
|
||||
Toy_Value Toy_private_handleStringAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
|
||||
if (MATCH_VALUE_AND_CSTRING(attribute, "length")) {
|
||||
return TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_STRING(compound)->info.length);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "asUpper")) {
|
||||
char* buffer = Toy_getStringRaw(TOY_VALUE_AS_STRING(compound));
|
||||
for (int i = 0; buffer[i] != '\0'; i++) {
|
||||
buffer[i] = toupper(buffer[i]);
|
||||
}
|
||||
Toy_String* str = Toy_createStringLength(&vm->memoryBucket, buffer, strlen(buffer));
|
||||
free(buffer);
|
||||
return TOY_VALUE_FROM_STRING(str);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "asLower")) {
|
||||
char* buffer = Toy_getStringRaw(TOY_VALUE_AS_STRING(compound));
|
||||
for (int i = 0; buffer[i] != '\0'; i++) {
|
||||
buffer[i] = tolower(buffer[i]);
|
||||
}
|
||||
Toy_String* str = Toy_createStringLength(&vm->memoryBucket, buffer, strlen(buffer));
|
||||
free(buffer);
|
||||
return TOY_VALUE_FROM_STRING(str);
|
||||
}
|
||||
else {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown attribute '%s' of type '%s'", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type));
|
||||
Toy_error(buffer);
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
}
|
||||
|
||||
static void attr_arrayPushBack(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
Toy_Value element = Toy_popStack(&vm->stack);
|
||||
|
||||
Toy_Array* array = TOY_VALUE_AS_ARRAY(compound);
|
||||
|
||||
//BUGFIX: check the capacity limit
|
||||
if (array->count == array->capacity) {
|
||||
//correct the source value's pointer
|
||||
array = Toy_resizeArray(array, array->capacity * TOY_ARRAY_EXPANSION_RATE);
|
||||
if (TOY_VALUE_IS_REFERENCE(compound) && compound.as.reference->type == TOY_VALUE_ARRAY) {
|
||||
compound.as.reference->as.array = array;
|
||||
}
|
||||
else {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown error after expanding array size at %s %d", __FILE__, __LINE__);
|
||||
Toy_error(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
array->data[array->count] = element;
|
||||
array->count++;
|
||||
}
|
||||
|
||||
static void attr_arrayPopBack(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
|
||||
Toy_Array* array = TOY_VALUE_AS_ARRAY(compound);
|
||||
|
||||
//empty returns nothing
|
||||
if (array->count == 0) {
|
||||
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
|
||||
return;
|
||||
}
|
||||
|
||||
Toy_Value element = array->data[array->count-1];
|
||||
array->count--;
|
||||
|
||||
Toy_pushStack(&vm->stack, element);
|
||||
}
|
||||
|
||||
static void attr_arrayForEach(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
//URGENT: replace with for-loop
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
Toy_Value callback = Toy_popStack(&vm->stack);
|
||||
|
||||
if (TOY_VALUE_IS_FUNCTION(callback) != true) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Expected function, found '%s'", Toy_getValueTypeAsCString(callback.type));
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
Toy_Array* array = TOY_VALUE_AS_ARRAY(compound);
|
||||
Toy_Function* fn = TOY_VALUE_AS_FUNCTION(callback);
|
||||
|
||||
//this emulates 'processInvoke' a bit, but not entirely
|
||||
Toy_VM subVM;
|
||||
Toy_inheritVM(vm, &subVM);
|
||||
|
||||
switch(fn->type) {
|
||||
case TOY_FUNCTION_CUSTOM: {
|
||||
//push and run for each element
|
||||
for (unsigned int iterator = 0; iterator < array->count; iterator++) {
|
||||
//bind to the subVM (more expensive than I'd like)
|
||||
Toy_bindVM(&subVM, fn->bytecode.code, fn->bytecode.parentScope);
|
||||
|
||||
//get parameter name as a string
|
||||
unsigned int paramAddr = ((unsigned int*)(subVM.code + subVM.paramAddr))[0];
|
||||
Toy_ValueType paramType = (Toy_ValueType)(((unsigned int*)(subVM.code + subVM.paramAddr))[1]);
|
||||
const char* cstr = ((char*)(subVM.code + subVM.dataAddr)) + paramAddr;
|
||||
Toy_String* name = Toy_toStringLength(&subVM.memoryBucket, cstr, strlen(cstr));
|
||||
|
||||
Toy_declareScope(subVM.scope, Toy_copyString(name), paramType, Toy_copyValue(&subVM.memoryBucket, array->data[iterator]), true);
|
||||
Toy_freeString(name);
|
||||
|
||||
Toy_runVM(&subVM);
|
||||
|
||||
Toy_resetVM(&subVM, false, true);
|
||||
subVM.scope = NULL; //BUGFIX: need to clear the scope when iterating
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_FUNCTION_NATIVE: {
|
||||
//this uses a subVM for the native function, which is a slight difference than 'processInoke'
|
||||
for (unsigned int iterator = 0; iterator < array->count; iterator++) {
|
||||
Toy_pushStack(&subVM.stack, Toy_copyValue(&subVM.memoryBucket, array->data[iterator]));
|
||||
|
||||
fn->native.callback(&subVM, &fn->native); //NOTE: try not to leave anything on the stack afterwards
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Toy_error("Can't call an unknown function type in 'forEach'");
|
||||
break;
|
||||
}
|
||||
|
||||
//cleanup
|
||||
Toy_freeVM(&subVM);
|
||||
}
|
||||
|
||||
static void attr_arraySort(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)vm;
|
||||
(void)self;
|
||||
|
||||
//URGENT: attr_arraySort
|
||||
}
|
||||
|
||||
Toy_Value Toy_private_handleArrayAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
|
||||
if (MATCH_VALUE_AND_CSTRING(attribute, "length")) {
|
||||
return TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_ARRAY(compound)->count);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "pushBack")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_arrayPushBack);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "popBack")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_arrayPopBack);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "forEach")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_arrayForEach);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "sort")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_arraySort);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown attribute '%s' of type '%s'", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type));
|
||||
Toy_error(buffer);
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
}
|
||||
|
||||
static void attr_tableInsert(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
Toy_Value value = Toy_popStack(&vm->stack); //NOTE: the args are still backwards, except compound
|
||||
Toy_Value key = Toy_popStack(&vm->stack);
|
||||
|
||||
Toy_Table* table = TOY_VALUE_AS_TABLE(compound);
|
||||
Toy_insertTable(&table, key, value);
|
||||
|
||||
//BUGFIX: check the capacity limit (Toy_insertTable automatically alters the pointer value)
|
||||
if (TOY_VALUE_AS_TABLE(compound) != table) {
|
||||
//correct the source value's pointer
|
||||
if (TOY_VALUE_IS_REFERENCE(compound) && compound.as.reference->type == TOY_VALUE_TABLE) {
|
||||
compound.as.reference->as.table = table;
|
||||
}
|
||||
else {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown error after expanding table size at %s %d", __FILE__, __LINE__);
|
||||
Toy_error(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void attr_tableHasKey(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
Toy_Value key = Toy_popStack(&vm->stack);
|
||||
|
||||
Toy_Table* table = TOY_VALUE_AS_TABLE(compound);
|
||||
|
||||
Toy_TableEntry* entry = Toy_private_lookupTableEntryPtr(&table, key);
|
||||
Toy_Value result = TOY_VALUE_FROM_BOOLEAN(entry != NULL);
|
||||
|
||||
Toy_pushStack(&vm->stack, result);
|
||||
}
|
||||
|
||||
static void attr_tableRemove(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)self;
|
||||
|
||||
Toy_Value compound = Toy_popStack(&vm->stack);
|
||||
Toy_Value key = Toy_popStack(&vm->stack);
|
||||
|
||||
Toy_Table* table = TOY_VALUE_AS_TABLE(compound);
|
||||
|
||||
Toy_removeTable(&table, key);
|
||||
}
|
||||
|
||||
static void attr_tableForEach(Toy_VM* vm, Toy_FunctionNative* self) {
|
||||
(void)vm;
|
||||
(void)self;
|
||||
|
||||
//URGENT: replace with for-loop
|
||||
}
|
||||
|
||||
Toy_Value Toy_private_handleTableAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
|
||||
if (MATCH_VALUE_AND_CSTRING(attribute, "length")) {
|
||||
return TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_ARRAY(compound)->count);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "insert")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableInsert);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "hasKey")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableHasKey);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "remove")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableRemove);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else if (MATCH_VALUE_AND_CSTRING(attribute, "forEach")) {
|
||||
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableForEach);
|
||||
return TOY_VALUE_FROM_FUNCTION(fn);
|
||||
}
|
||||
else {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown attribute '%s' of type '%s'", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type));
|
||||
Toy_error(buffer);
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
}
|
||||
|
||||
Toy_Value Toy_private_handleOpaqueAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
|
||||
if (opaqueAttributeCallback == NULL) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, 256, "Unknown attribute '%s' of type '%s' (did you set the opaque callbacks?)", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type));
|
||||
Toy_error(buffer);
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
|
||||
return opaqueAttributeCallback(vm, compound, attribute);
|
||||
}
|
||||
|
||||
void Toy_setOpaqueAttributeHandler(Toy_OpaqueAttributeHandler cb) {
|
||||
opaqueAttributeCallback = cb;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_value.h"
|
||||
#include "toy_vm.h"
|
||||
|
||||
// [x] string.length
|
||||
// [x] string.asUpper
|
||||
// [x] string.asLower
|
||||
// [x] array.length
|
||||
// [x] array.pushBack(x)
|
||||
// [x] array.popBack()
|
||||
// [x] array.forEach(fn) // fn(x) -> void
|
||||
// [ ] array.sort(fn) // fn(a,b) -> int
|
||||
// [x] table.length
|
||||
// [x] table.insert(x, y)
|
||||
// [x] table.hasKey(x)
|
||||
// [x] table.remove(x)
|
||||
// [ ] table.forEach(fn) // fn(x,y) -> void
|
||||
|
||||
Toy_Value Toy_private_handleStringAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
|
||||
Toy_Value Toy_private_handleArrayAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
|
||||
Toy_Value Toy_private_handleTableAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
|
||||
Toy_Value Toy_private_handleOpaqueAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
|
||||
|
||||
//plug-and-play attributes for custom objects
|
||||
typedef Toy_Value (*Toy_OpaqueAttributeHandler)(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
|
||||
TOY_API void Toy_setOpaqueAttributeHandler(Toy_OpaqueAttributeHandler cb);
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//buckets of fun
|
||||
Toy_Bucket* Toy_allocateBucket(unsigned int capacity) {
|
||||
assert(capacity != 0 && "Cannot allocate a 'Toy_Bucket' with zero capacity");
|
||||
|
||||
Toy_Bucket* bucket = malloc(sizeof(Toy_Bucket) + capacity);
|
||||
|
||||
if (bucket == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Bucket' of %d capacity\n" TOY_CC_RESET, (int)capacity);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
memset(bucket, 0, sizeof(Toy_Bucket) + capacity); //zero the memory, to avoid broken header metadata
|
||||
|
||||
//initialize the bucket
|
||||
bucket->next = NULL;
|
||||
bucket->capacity = capacity;
|
||||
bucket->count = 0;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
unsigned char* Toy_partitionBucket(Toy_Bucket** bucketHandle, unsigned int amount) {
|
||||
//the endpoint must be aligned to the word size, otherwise you'll get a bus error from moving pointers
|
||||
amount = (amount + 3) & ~3; //NOTE: this also leaves the lowest two bits as zero
|
||||
|
||||
assert((*bucketHandle) != NULL && "Expected a 'Toy_Bucket', received NULL");
|
||||
assert((*bucketHandle)->capacity >= (amount + 4) && "ERROR: Failed to partition a 'Toy_Bucket', requested amount is too high");
|
||||
|
||||
//if you're out of space in this bucket, allocate another one
|
||||
if ((*bucketHandle)->capacity < (*bucketHandle)->count + amount + 4) { //+4 for the metadata header
|
||||
Toy_Bucket* tmp = Toy_allocateBucket((*bucketHandle)->capacity);
|
||||
tmp->next = (*bucketHandle); //it's buckets all the way down
|
||||
(*bucketHandle) = tmp;
|
||||
}
|
||||
|
||||
//use a 4-byte metadata header to hold the size of this partition, for GC
|
||||
*((unsigned int*)((*bucketHandle)->data + (*bucketHandle)->count)) = amount;
|
||||
|
||||
//track the new metadata, and return the requested memory space
|
||||
(*bucketHandle)->count += amount + 4;
|
||||
return ((*bucketHandle)->data + (*bucketHandle)->count - amount); //metadata is before the pointer's address
|
||||
}
|
||||
|
||||
void Toy_releaseBucketPartition(unsigned char* ptr) {
|
||||
*((int*)(ptr-4)) |= 1; //flips the low-bit within the header
|
||||
//no checks here, for technical reasons
|
||||
}
|
||||
|
||||
void Toy_freeBucket(Toy_Bucket** bucketHandle) {
|
||||
Toy_Bucket* iter = (*bucketHandle);
|
||||
|
||||
while (iter != NULL) {
|
||||
//run down the chain
|
||||
Toy_Bucket* last = iter;
|
||||
iter = iter->next;
|
||||
|
||||
//clear the previous bucket from memory
|
||||
free(last);
|
||||
}
|
||||
|
||||
//for safety
|
||||
(*bucketHandle) = NULL;
|
||||
}
|
||||
|
||||
TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle) {
|
||||
//clear whatever this handle is pointing to
|
||||
if ((*bucketHandle) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Toy_Bucket* link = *bucketHandle;
|
||||
while (link) {
|
||||
//find non-free partitions
|
||||
unsigned char* ptr = link->data;
|
||||
|
||||
bool gc = true;
|
||||
|
||||
while (ptr - link->data < link->count) { //for each partition
|
||||
if ( (*((int*)ptr) & 1) == 0) { //is this partition still in use?
|
||||
gc = false;
|
||||
break;
|
||||
}
|
||||
ptr += ((*((int*)ptr) | 1) ^ 1) + 4; //OR + XOR to remove the 'free' flag from the size
|
||||
}
|
||||
|
||||
//free this link, if its been entirely released
|
||||
if (gc) {
|
||||
//if link is the head
|
||||
if (link == (*bucketHandle)) {
|
||||
//if there's nowhere to go, don't delete the whole bucket
|
||||
if ((*bucketHandle)->next == NULL) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
(*bucketHandle) = (*bucketHandle)->next;
|
||||
free(link);
|
||||
link = (*bucketHandle);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//find the prev and free this link before continuing
|
||||
Toy_Bucket* it = (*bucketHandle);
|
||||
while (it->next != link) {
|
||||
it = it->next;
|
||||
}
|
||||
it->next = link->next;
|
||||
free(link);
|
||||
link = it->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
link = link->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
//NOTE: this is an 'arena allocator', and has restrictions on it's usage:
|
||||
// - It can only expand until it is freed
|
||||
// - It cannot be copied or moved around in memory
|
||||
// - It cannot allocate more memory than it has 'capacity'
|
||||
// If each of these rules are followed, this is actually more efficient than other options
|
||||
|
||||
//a custom allocator
|
||||
typedef struct Toy_Bucket { //32 | 64 BITNESS
|
||||
struct Toy_Bucket* next; //4 | 8
|
||||
unsigned int capacity; //4 | 4
|
||||
unsigned int count; //4 | 4
|
||||
unsigned char data[]; //- | -
|
||||
} Toy_Bucket; //12 | 16
|
||||
|
||||
TOY_API Toy_Bucket* Toy_allocateBucket(unsigned int capacity);
|
||||
TOY_API unsigned char* Toy_partitionBucket(Toy_Bucket** bucketHandle, unsigned int amount);
|
||||
TOY_API void Toy_releaseBucketPartition(unsigned char* ptr);
|
||||
TOY_API void Toy_freeBucket(Toy_Bucket** bucketHandle);
|
||||
|
||||
TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle);
|
||||
|
||||
//standard capacity sizes
|
||||
#ifndef TOY_BUCKET_1KB
|
||||
#define TOY_BUCKET_1KB (1 << 10)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_2KB
|
||||
#define TOY_BUCKET_2KB (1 << 11)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_4KB
|
||||
#define TOY_BUCKET_4KB (1 << 12)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_8KB
|
||||
#define TOY_BUCKET_8KB (1 << 13)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_16KB
|
||||
#define TOY_BUCKET_16KB (1 << 14)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_32KB
|
||||
#define TOY_BUCKET_32KB (1 << 15)
|
||||
#endif
|
||||
|
||||
#ifndef TOY_BUCKET_64KB
|
||||
#define TOY_BUCKET_64KB (1 << 16)
|
||||
#endif
|
||||
|
||||
//CPU L1 caches tend to be 64kb, but that's far from guaranteed
|
||||
#ifndef TOY_BUCKET_IDEAL
|
||||
#define TOY_BUCKET_IDEAL (TOY_BUCKET_64KB - sizeof(Toy_Bucket))
|
||||
#endif
|
||||
|
||||
//TODO: check for leaks when freeBucket is called, for debugging
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "toy_common.h"
|
||||
|
||||
//defined separately, as compilation can take several seconds, invalidating the comparisons of the given macros
|
||||
static const char* build = __DATE__ " " __TIME__ ", incomplete Toy v2.x";
|
||||
|
||||
const char* Toy_private_versionBuild(void) {
|
||||
return build;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
//for specified type sizes
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
//TOY_API is platform-dependant, and marks publicly usable API functions
|
||||
#if defined(__linux__)
|
||||
#define TOY_API extern
|
||||
#elif defined(_WIN32) || defined(_WIN64)
|
||||
#if defined(TOY_EXPORT)
|
||||
#define TOY_API __declspec(dllexport)
|
||||
#elif defined(TOY_IMPORT)
|
||||
#define TOY_API __declspec(dllimport)
|
||||
#else
|
||||
#define TOY_API extern
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#define TOY_API extern
|
||||
#else
|
||||
//generic solution
|
||||
#define TOY_API extern
|
||||
#endif
|
||||
|
||||
//TOY_BITNESS is used to encourage memory-cache friendliness
|
||||
#if defined(__linux__)
|
||||
#if defined(__LP64__)
|
||||
#define TOY_BITNESS 64
|
||||
#else
|
||||
#define TOY_BITNESS 32
|
||||
#endif
|
||||
#elif defined(__NetBSD__)
|
||||
#if defined(__LP64__)
|
||||
#define TOY_BITNESS 64
|
||||
#else
|
||||
#define TOY_BITNESS 32
|
||||
#endif
|
||||
#elif defined(_WIN32) || defined(_WIN64)
|
||||
#if defined(_WIN64)
|
||||
#define TOY_BITNESS 64
|
||||
#else
|
||||
#define TOY_BITNESS 32
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(__LP64__)
|
||||
#define TOY_BITNESS 64
|
||||
#else
|
||||
#define TOY_BITNESS 32
|
||||
#endif
|
||||
#else
|
||||
//generic solution
|
||||
#define TOY_BITNESS -1
|
||||
#endif
|
||||
|
||||
//version specifiers, embedded as the header
|
||||
#define TOY_VERSION_MAJOR 2
|
||||
#define TOY_VERSION_MINOR 1
|
||||
#define TOY_VERSION_PATCH 0
|
||||
|
||||
//defined as a function, for technical reasons
|
||||
#define TOY_VERSION_BUILD Toy_private_versionBuild()
|
||||
const char* Toy_private_versionBuild(void);
|
||||
|
||||
/*
|
||||
|
||||
Version validation rules:
|
||||
|
||||
* Under no circumstance, should you ever run code whose major version is different from the interpreter’s major version
|
||||
* Under no circumstance, should you ever run code whose minor version is above the interpreter’s minor version
|
||||
* You may, at your own risk, attempt to run code whose patch version is different from the interpreter’s patch version
|
||||
* You may, at your own risk, attempt to run code whose build version is different from the interpreter’s build version
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_ast.h"
|
||||
|
||||
//the 'escapes' are lists of data used for processing the 'break' and 'continue' keywords
|
||||
typedef struct Toy_private_EscapeEntry_t {
|
||||
unsigned int addr; //the address to write *to*
|
||||
unsigned int depth; //the current depth
|
||||
} Toy_private_EscapeEntry_t;
|
||||
|
||||
typedef struct Toy_private_EscapeArray {
|
||||
unsigned int capacity;
|
||||
unsigned int count;
|
||||
Toy_private_EscapeEntry_t data[];
|
||||
} Toy_private_EscapeArray;
|
||||
|
||||
//not needed at runtime, so they can be bigger
|
||||
#ifndef TOY_ESCAPE_INITIAL_CAPACITY
|
||||
#define TOY_ESCAPE_INITIAL_CAPACITY 32
|
||||
#endif
|
||||
|
||||
#ifndef TOY_ESCAPE_EXPANSION_RATE
|
||||
#define TOY_ESCAPE_EXPANSION_RATE 4
|
||||
#endif
|
||||
|
||||
Toy_private_EscapeArray* Toy_private_resizeEscapeArray(Toy_private_EscapeArray* ptr, unsigned int capacity);
|
||||
|
||||
//structure for holding the bytecode during compilation
|
||||
typedef struct Toy_Bytecode {
|
||||
unsigned char* code; //the instruction set
|
||||
unsigned int codeCapacity;
|
||||
unsigned int codeCount;
|
||||
|
||||
unsigned char* jumps; //each 'jump' is the starting address of an element within 'data'
|
||||
unsigned int jumpsCapacity;
|
||||
unsigned int jumpsCount;
|
||||
|
||||
unsigned char* param; //each 'param' is the starting address of a name string within 'data'
|
||||
unsigned int paramCapacity;
|
||||
unsigned int paramCount;
|
||||
|
||||
unsigned char* data; //a block of read-only data
|
||||
unsigned int dataCapacity;
|
||||
unsigned int dataCount;
|
||||
|
||||
unsigned char* subs; //subroutines etc, built recursively
|
||||
unsigned int subsCapacity;
|
||||
unsigned int subsCount;
|
||||
|
||||
//tools for handling the build process
|
||||
unsigned int currentScopeDepth;
|
||||
Toy_private_EscapeArray* breakEscapes;
|
||||
Toy_private_EscapeArray* continueEscapes;
|
||||
|
||||
//compilation errors
|
||||
bool panic;
|
||||
} Toy_Bytecode;
|
||||
|
||||
TOY_API unsigned char* Toy_compileToBytecode(Toy_Ast* ast);
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
/* toy_console_colors.h - console utility
|
||||
|
||||
This file provides a number of macros that can set the color of text in a console
|
||||
window. These are used for convenience only. They are supposed to be dropped into
|
||||
a printf()'s first argument, like so:
|
||||
|
||||
printf(TOY_CC_NOTICE "Hello world" TOY_CC_RESET);
|
||||
|
||||
reference: https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
|
||||
|
||||
*/
|
||||
|
||||
//platform/compiler-specific instructions
|
||||
#if defined(__linux__) || defined(__MINGW32__) || defined(__GNUC__)
|
||||
|
||||
//fonts color
|
||||
#define TOY_CC_FONT_BLACK "30"
|
||||
#define TOY_CC_FONT_RED "31"
|
||||
#define TOY_CC_FONT_GREEN "32"
|
||||
#define TOY_CC_FONT_YELLOW "33"
|
||||
#define TOY_CC_FONT_BLUE "34"
|
||||
#define TOY_CC_FONT_MAGENTA "35"
|
||||
#define TOY_CC_FONT_CYAN "36"
|
||||
#define TOY_CC_FONT_WHITE "37"
|
||||
#define TOY_CC_FONT_DEFAULT "39"
|
||||
|
||||
//background color
|
||||
#define TOY_CC_BACK_BLACK "40"
|
||||
#define TOY_CC_BACK_RED "41"
|
||||
#define TOY_CC_BACK_GREEN "42"
|
||||
#define TOY_CC_BACK_YELLOW "43"
|
||||
#define TOY_CC_BACK_BLUE "44"
|
||||
#define TOY_CC_BACK_MAGENTA "45"
|
||||
#define TOY_CC_BACK_CYAN "46"
|
||||
#define TOY_CC_BACK_WHITE "47"
|
||||
#define TOY_CC_BACK_DEFAULT "49"
|
||||
|
||||
//useful macros
|
||||
#define TOY_CC_DEBUG "\033[" TOY_CC_FONT_BLUE ";" TOY_CC_BACK_DEFAULT "m"
|
||||
#define TOY_CC_NOTICE "\033[" TOY_CC_FONT_GREEN ";" TOY_CC_BACK_DEFAULT "m"
|
||||
#define TOY_CC_WARN "\033[" TOY_CC_FONT_YELLOW ";" TOY_CC_BACK_DEFAULT "m"
|
||||
#define TOY_CC_ERROR "\033[" TOY_CC_FONT_RED ";" TOY_CC_BACK_DEFAULT "m"
|
||||
#define TOY_CC_ASSERT "\033[" TOY_CC_FONT_BLACK ";" TOY_CC_BACK_MAGENTA "m"
|
||||
#define TOY_CC_RESET "\033[" TOY_CC_FONT_DEFAULT ";" TOY_CC_BACK_DEFAULT "m"
|
||||
|
||||
//for unsupported platforms, these become no-ops
|
||||
#else
|
||||
|
||||
//fonts color
|
||||
#define TOY_CC_FONT_BLACK
|
||||
#define TOY_CC_FONT_RED
|
||||
#define TOY_CC_FONT_GREEN
|
||||
#define TOY_CC_FONT_YELLOW
|
||||
#define TOY_CC_FONT_BLUE
|
||||
#define TOY_CC_FONT_MAGENTA
|
||||
#define TOY_CC_FONT_CYAN
|
||||
#define TOY_CC_FONT_WHITE
|
||||
#define TOY_CC_FONT_DEFAULT
|
||||
|
||||
//background color
|
||||
#define TOY_CC_BACK_BLACK
|
||||
#define TOY_CC_BACK_RED
|
||||
#define TOY_CC_BACK_GREEN
|
||||
#define TOY_CC_BACK_YELLOW
|
||||
#define TOY_CC_BACK_BLUE
|
||||
#define TOY_CC_BACK_MAGENTA
|
||||
#define TOY_CC_BACK_CYAN
|
||||
#define TOY_CC_BACK_WHITE
|
||||
#define TOY_CC_BACK_DEFAULT
|
||||
|
||||
//useful
|
||||
#define TOY_CC_DEBUG
|
||||
#define TOY_CC_NOTICE
|
||||
#define TOY_CC_WARN
|
||||
#define TOY_CC_ERROR
|
||||
#define TOY_CC_ASSERT
|
||||
#define TOY_CC_RESET
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "toy_function.h"
|
||||
|
||||
Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode, Toy_Scope* parentScope) {
|
||||
Toy_Function* fn = (Toy_Function*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Function));
|
||||
|
||||
fn->type = TOY_FUNCTION_CUSTOM;
|
||||
fn->bytecode.code = bytecode;
|
||||
fn->bytecode.parentScope = parentScope;
|
||||
Toy_private_incrementScopeRefCount(fn->bytecode.parentScope);
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
Toy_Function* Toy_createFunctionFromCallback(Toy_Bucket** bucketHandle, Toy_nativeCallback callback) {
|
||||
Toy_Function* fn = (Toy_Function*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Function));
|
||||
|
||||
fn->type = TOY_FUNCTION_NATIVE;
|
||||
fn->native.callback = callback;
|
||||
fn->native.meta1 = 0; //BUGFIX: Workaround for native functions lacking access to a closure-like scope
|
||||
fn->native.meta2 = 0;
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
Toy_Function* Toy_copyFunction(Toy_Bucket** bucketHandle, Toy_Function* original) {
|
||||
Toy_Function* fn = (Toy_Function*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Function));
|
||||
|
||||
switch(original->type) {
|
||||
case TOY_FUNCTION_CUSTOM: {
|
||||
fn->type = original->type;
|
||||
fn->bytecode.code = original->bytecode.code;
|
||||
fn->bytecode.parentScope = original->bytecode.parentScope;
|
||||
Toy_private_incrementScopeRefCount(fn->bytecode.parentScope);
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_FUNCTION_NATIVE: {
|
||||
fn->type = original->type;
|
||||
fn->native.callback = original->native.callback;
|
||||
fn->native.meta1 = original->native.meta1;
|
||||
fn->native.meta2 = original->native.meta2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
TOY_API void Toy_freeFunction(Toy_Function* fn) {
|
||||
if (fn->type == TOY_FUNCTION_CUSTOM) {
|
||||
Toy_private_decrementScopeRefCount(fn->bytecode.parentScope);
|
||||
}
|
||||
else if (fn->type == TOY_FUNCTION_NATIVE) {
|
||||
fn->native.callback = NULL;
|
||||
}
|
||||
|
||||
Toy_releaseBucketPartition((void*)fn);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_scope.h"
|
||||
#include "toy_vm.h"
|
||||
|
||||
//forward declare
|
||||
struct Toy_VM;
|
||||
struct Toy_FunctionNative;
|
||||
|
||||
typedef void (*Toy_nativeCallback)(struct Toy_VM*, struct Toy_FunctionNative* self);
|
||||
|
||||
typedef enum Toy_FunctionType {
|
||||
TOY_FUNCTION_CUSTOM,
|
||||
TOY_FUNCTION_NATIVE,
|
||||
} Toy_FunctionType;
|
||||
|
||||
typedef struct Toy_FunctionBytecode {
|
||||
Toy_FunctionType type;
|
||||
unsigned char* code;
|
||||
Toy_Scope* parentScope;
|
||||
} Toy_FunctionBytecode;
|
||||
|
||||
typedef struct Toy_FunctionNative {
|
||||
Toy_FunctionType type;
|
||||
Toy_nativeCallback callback;
|
||||
int meta1;
|
||||
int meta2;
|
||||
} Toy_FunctionNative;
|
||||
|
||||
typedef union Toy_Function_t {
|
||||
Toy_FunctionType type;
|
||||
Toy_FunctionBytecode bytecode;
|
||||
Toy_FunctionNative native;
|
||||
} Toy_Function;
|
||||
|
||||
TOY_API Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode, Toy_Scope* parentScope);
|
||||
TOY_API Toy_Function* Toy_createFunctionFromCallback(Toy_Bucket** bucketHandle, Toy_nativeCallback callback);
|
||||
|
||||
TOY_API Toy_Function* Toy_copyFunction(Toy_Bucket** bucketHandle, Toy_Function* fn);
|
||||
TOY_API void Toy_freeFunction(Toy_Function* fn);
|
||||
@@ -0,0 +1,426 @@
|
||||
#include "toy_lexer.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
//keyword data
|
||||
typedef struct {
|
||||
const Toy_TokenType type;
|
||||
const char* keyword;
|
||||
} Toy_KeywordTypeTuple;
|
||||
|
||||
const Toy_KeywordTypeTuple keywordTuples[] = {
|
||||
//null
|
||||
{TOY_TOKEN_NULL, "null"},
|
||||
|
||||
//types
|
||||
{TOY_TOKEN_TYPE_BOOLEAN, "Bool"},
|
||||
{TOY_TOKEN_TYPE_INTEGER, "Int"},
|
||||
{TOY_TOKEN_TYPE_FLOAT, "Float"},
|
||||
{TOY_TOKEN_TYPE_STRING, "String"},
|
||||
{TOY_TOKEN_TYPE_ARRAY, "Array"},
|
||||
{TOY_TOKEN_TYPE_TABLE, "Table"},
|
||||
{TOY_TOKEN_TYPE_FUNCTION, "Function"},
|
||||
{TOY_TOKEN_TYPE_OPAQUE, "Opaque"},
|
||||
{TOY_TOKEN_TYPE_ANY, "Any"},
|
||||
|
||||
//keywords and reserved words
|
||||
{TOY_TOKEN_KEYWORD_AS, "as"},
|
||||
{TOY_TOKEN_KEYWORD_ASSERT, "assert"},
|
||||
{TOY_TOKEN_KEYWORD_BREAK, "break"},
|
||||
{TOY_TOKEN_KEYWORD_CLASS, "class"},
|
||||
{TOY_TOKEN_KEYWORD_CONST, "const"},
|
||||
{TOY_TOKEN_KEYWORD_CONTINUE, "continue"},
|
||||
{TOY_TOKEN_KEYWORD_DO, "do"},
|
||||
{TOY_TOKEN_KEYWORD_ELSE, "else"},
|
||||
{TOY_TOKEN_KEYWORD_EXPORT, "export"},
|
||||
{TOY_TOKEN_KEYWORD_FOR, "for"},
|
||||
{TOY_TOKEN_KEYWORD_FOREACH, "foreach"},
|
||||
{TOY_TOKEN_KEYWORD_FUNCTION, "fn"},
|
||||
{TOY_TOKEN_KEYWORD_IF, "if"},
|
||||
{TOY_TOKEN_KEYWORD_IMPORT, "import"},
|
||||
{TOY_TOKEN_KEYWORD_IN, "in"},
|
||||
{TOY_TOKEN_KEYWORD_OF, "of"},
|
||||
{TOY_TOKEN_KEYWORD_PASS, "pass"},
|
||||
{TOY_TOKEN_KEYWORD_PRINT, "print"},
|
||||
{TOY_TOKEN_KEYWORD_RETURN, "return"},
|
||||
{TOY_TOKEN_KEYWORD_VAR, "var"},
|
||||
{TOY_TOKEN_KEYWORD_WHILE, "while"},
|
||||
{TOY_TOKEN_KEYWORD_YIELD, "yield"},
|
||||
|
||||
//literal values
|
||||
{TOY_TOKEN_LITERAL_TRUE, "true"},
|
||||
{TOY_TOKEN_LITERAL_FALSE, "false"},
|
||||
|
||||
{TOY_TOKEN_EOF, NULL},
|
||||
};
|
||||
|
||||
const char* Toy_private_findKeywordByType(const Toy_TokenType type) {
|
||||
if (type == TOY_TOKEN_EOF) {
|
||||
return "EOF";
|
||||
}
|
||||
|
||||
for(int i = 0; keywordTuples[i].keyword; i++) {
|
||||
if (keywordTuples[i].type == type) {
|
||||
return keywordTuples[i].keyword;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Toy_TokenType Toy_private_findTypeByKeyword(const char* keyword) {
|
||||
const int length = strlen(keyword);
|
||||
|
||||
for (int i = 0; keywordTuples[i].keyword; i++) {
|
||||
if (!strncmp(keyword, keywordTuples[i].keyword, length)) {
|
||||
return keywordTuples[i].type;
|
||||
}
|
||||
}
|
||||
|
||||
return TOY_TOKEN_EOF;
|
||||
}
|
||||
|
||||
//static generic utility functions
|
||||
static void cleanLexer(Toy_Lexer* lexer) {
|
||||
lexer->start = 0;
|
||||
lexer->current = 0;
|
||||
lexer->line = 1;
|
||||
lexer->source = NULL;
|
||||
}
|
||||
|
||||
static bool isAtEnd(Toy_Lexer* lexer) {
|
||||
return lexer->source[lexer->current] == '\0';
|
||||
}
|
||||
|
||||
static char peek(Toy_Lexer* lexer) {
|
||||
return lexer->source[lexer->current];
|
||||
}
|
||||
|
||||
static char peekNext(Toy_Lexer* lexer) {
|
||||
if (isAtEnd(lexer)) return '\0';
|
||||
return lexer->source[lexer->current + 1];
|
||||
}
|
||||
|
||||
static char advance(Toy_Lexer* lexer) {
|
||||
if (isAtEnd(lexer)) {
|
||||
return '\0';
|
||||
}
|
||||
|
||||
//new line
|
||||
if (lexer->source[lexer->current] == '\n') {
|
||||
lexer->line++;
|
||||
}
|
||||
|
||||
lexer->current++;
|
||||
return lexer->source[lexer->current - 1];
|
||||
}
|
||||
|
||||
static void eatWhitespace(Toy_Lexer* lexer) {
|
||||
const char c = peek(lexer);
|
||||
|
||||
switch(c) {
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\t':
|
||||
advance(lexer);
|
||||
break;
|
||||
|
||||
//comments
|
||||
case '/':
|
||||
//eat the line
|
||||
if (peekNext(lexer) == '/') {
|
||||
while (!isAtEnd(lexer) && advance(lexer) != '\n');
|
||||
break;
|
||||
}
|
||||
|
||||
//eat the block
|
||||
if (peekNext(lexer) == '*') {
|
||||
advance(lexer);
|
||||
advance(lexer);
|
||||
while(!isAtEnd(lexer) && !(peek(lexer) == '*' && peekNext(lexer) == '/')) advance(lexer);
|
||||
advance(lexer);
|
||||
advance(lexer);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
//tail recursion
|
||||
eatWhitespace(lexer);
|
||||
}
|
||||
|
||||
static bool isDigit(Toy_Lexer* lexer) {
|
||||
return peek(lexer) >= '0' && peek(lexer) <= '9';
|
||||
}
|
||||
|
||||
static bool isAlpha(Toy_Lexer* lexer) {
|
||||
return
|
||||
(peek(lexer) >= 'A' && peek(lexer) <= 'Z') ||
|
||||
(peek(lexer) >= 'a' && peek(lexer) <= 'z') ||
|
||||
peek(lexer) == '_'
|
||||
;
|
||||
}
|
||||
|
||||
static bool match(Toy_Lexer* lexer, char c) {
|
||||
if (peek(lexer) == c) {
|
||||
advance(lexer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//token generators
|
||||
static Toy_Token makeErrorToken(Toy_Lexer* lexer, char* msg) {
|
||||
Toy_Token token;
|
||||
|
||||
token.type = TOY_TOKEN_ERROR;
|
||||
token.length = strlen(msg);
|
||||
token.line = lexer->line;
|
||||
token.lexeme = msg;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static Toy_Token makeToken(Toy_Lexer* lexer, Toy_TokenType type) {
|
||||
Toy_Token token;
|
||||
|
||||
token.type = type;
|
||||
token.length = lexer->current - lexer->start;
|
||||
token.line = lexer->line;
|
||||
token.lexeme = &lexer->source[lexer->current - token.length];
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static Toy_Token makeIntegerOrFloat(Toy_Lexer* lexer) {
|
||||
Toy_TokenType type = TOY_TOKEN_LITERAL_INTEGER; //assume we're reading an integer
|
||||
|
||||
//the character '_' can be inserted into numbers as a separator
|
||||
while(isDigit(lexer) || peek(lexer) == '_') advance(lexer);
|
||||
|
||||
if (peek(lexer) == '.' && (peekNext(lexer) >= '0' && peekNext(lexer) <= '9')) { //peekNext(lexer) == digit
|
||||
type = TOY_TOKEN_LITERAL_FLOAT; //change the assumption to reading a float
|
||||
advance(lexer); //eat the '.'
|
||||
|
||||
//'_' again
|
||||
while(isDigit(lexer) || peek(lexer) == '_') advance(lexer);
|
||||
}
|
||||
|
||||
//make the token
|
||||
Toy_Token token;
|
||||
|
||||
token.type = type;
|
||||
token.length = lexer->current - lexer->start;
|
||||
token.line = lexer->line;
|
||||
token.lexeme = &lexer->source[lexer->start];
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static bool isEscapableCharacter(char c) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
case 't':
|
||||
case '\\':
|
||||
case '"':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Toy_Token makeString(Toy_Lexer* lexer, char terminator) {
|
||||
while (!isAtEnd(lexer)) {
|
||||
//stop if you've hit the terminator
|
||||
if (peek(lexer) == terminator) {
|
||||
break;
|
||||
}
|
||||
|
||||
//skip escaped control characters
|
||||
if (peek(lexer) == '\\' && isEscapableCharacter(peekNext(lexer))) {
|
||||
advance(lexer);
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
|
||||
//otherwise
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
if (isAtEnd(lexer)) {
|
||||
return makeErrorToken(lexer, "Unterminated string");
|
||||
}
|
||||
|
||||
advance(lexer); //eat the terminator
|
||||
|
||||
//make the token
|
||||
Toy_Token token;
|
||||
|
||||
token.type = TOY_TOKEN_LITERAL_STRING;
|
||||
token.length = lexer->current - lexer->start - 2; //-1 to omit the quotes
|
||||
token.line = lexer->line;
|
||||
token.lexeme = &lexer->source[lexer->start + 1]; //+1 to omit the first quote
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static Toy_Token makeKeywordOrName(Toy_Lexer* lexer) {
|
||||
advance(lexer); //first letter can only be alpha
|
||||
|
||||
while(isDigit(lexer) || isAlpha(lexer)) {
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
//scan for a keyword
|
||||
for (int i = 0; keywordTuples[i].keyword; i++) {
|
||||
//WONTFIX: could squeeze miniscule performance gain from this, but ROI isn't worth it
|
||||
if (strlen(keywordTuples[i].keyword) == (lexer->current - lexer->start) && !strncmp(keywordTuples[i].keyword, &lexer->source[lexer->start], lexer->current - lexer->start)) {
|
||||
//make token (keyword)
|
||||
Toy_Token token;
|
||||
|
||||
token.type = keywordTuples[i].type;
|
||||
token.length = lexer->current - lexer->start;
|
||||
token.line = lexer->line;
|
||||
token.lexeme = &lexer->source[lexer->start];
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
//make token (variable name)
|
||||
Toy_Token token;
|
||||
|
||||
token.type = TOY_TOKEN_NAME;
|
||||
token.length = lexer->current - lexer->start;
|
||||
token.line = lexer->line;
|
||||
token.lexeme = &lexer->source[lexer->start];
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
//exposed functions
|
||||
void Toy_bindLexer(Toy_Lexer* lexer, const char* source) {
|
||||
cleanLexer(lexer);
|
||||
lexer->source = source;
|
||||
}
|
||||
|
||||
Toy_Token Toy_private_scanLexer(Toy_Lexer* lexer) {
|
||||
if (lexer->source == NULL) {
|
||||
return makeErrorToken(lexer, "Missing source code in lexer");
|
||||
}
|
||||
|
||||
eatWhitespace(lexer);
|
||||
|
||||
lexer->start = lexer->current;
|
||||
|
||||
if (isAtEnd(lexer)) return makeToken(lexer, TOY_TOKEN_EOF);
|
||||
|
||||
if (isDigit(lexer)) return makeIntegerOrFloat(lexer);
|
||||
if (isAlpha(lexer)) return makeKeywordOrName(lexer);
|
||||
|
||||
char c = advance(lexer);
|
||||
|
||||
switch(c) {
|
||||
case '(': return makeToken(lexer, TOY_TOKEN_OPERATOR_PAREN_LEFT);
|
||||
case ')': return makeToken(lexer, TOY_TOKEN_OPERATOR_PAREN_RIGHT);
|
||||
case '[': return makeToken(lexer, TOY_TOKEN_OPERATOR_BRACKET_LEFT);
|
||||
case ']': return makeToken(lexer, TOY_TOKEN_OPERATOR_BRACKET_RIGHT);
|
||||
case '{': return makeToken(lexer, TOY_TOKEN_OPERATOR_BRACE_LEFT);
|
||||
case '}': return makeToken(lexer, TOY_TOKEN_OPERATOR_BRACE_RIGHT);
|
||||
|
||||
case '+': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_ADD_ASSIGN : match(lexer, '+') ? TOY_TOKEN_OPERATOR_INCREMENT : TOY_TOKEN_OPERATOR_ADD);
|
||||
case '-': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_SUBTRACT_ASSIGN : match(lexer, '-') ? TOY_TOKEN_OPERATOR_DECREMENT : TOY_TOKEN_OPERATOR_SUBTRACT);
|
||||
case '*': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_MULTIPLY_ASSIGN : TOY_TOKEN_OPERATOR_MULTIPLY);
|
||||
case '/': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_DIVIDE_ASSIGN : TOY_TOKEN_OPERATOR_DIVIDE);
|
||||
case '%': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_MODULO_ASSIGN : TOY_TOKEN_OPERATOR_MODULO);
|
||||
|
||||
case '!': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_COMPARE_NOT : TOY_TOKEN_OPERATOR_NEGATE);
|
||||
case '=': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_COMPARE_EQUAL : TOY_TOKEN_OPERATOR_ASSIGN);
|
||||
|
||||
case '<': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_COMPARE_LESS_EQUAL : TOY_TOKEN_OPERATOR_COMPARE_LESS);
|
||||
case '>': return makeToken(lexer, match(lexer, '=') ? TOY_TOKEN_OPERATOR_COMPARE_GREATER_EQUAL : TOY_TOKEN_OPERATOR_COMPARE_GREATER);
|
||||
|
||||
case '&': //TOY_TOKEN_OPERATOR_AMPERSAND is unused
|
||||
if (match(lexer, '&')) {
|
||||
return makeToken(lexer, TOY_TOKEN_OPERATOR_AND);
|
||||
} else {
|
||||
return makeErrorToken(lexer, "Unexpected '&'");
|
||||
}
|
||||
|
||||
case '|': //TOY_TOKEN_OPERATOR_PIPE is unused
|
||||
if (match(lexer, '|')) {
|
||||
return makeToken(lexer, TOY_TOKEN_OPERATOR_OR);
|
||||
} else {
|
||||
return makeErrorToken(lexer, "Unexpected '|'");
|
||||
}
|
||||
|
||||
case '?': return makeToken(lexer, TOY_TOKEN_OPERATOR_QUESTION);
|
||||
case ':': return makeToken(lexer, TOY_TOKEN_OPERATOR_COLON);
|
||||
case ';': return makeToken(lexer, TOY_TOKEN_OPERATOR_SEMICOLON);
|
||||
case ',': return makeToken(lexer, TOY_TOKEN_OPERATOR_COMMA);
|
||||
|
||||
case '.':
|
||||
if (match(lexer, '.')) {
|
||||
if (match(lexer, '.')) {
|
||||
return makeToken(lexer, TOY_TOKEN_OPERATOR_REST); //three dots
|
||||
}
|
||||
else {
|
||||
return makeToken(lexer, TOY_TOKEN_OPERATOR_CONCAT); //two dots
|
||||
}
|
||||
}
|
||||
else {
|
||||
return makeToken(lexer, TOY_TOKEN_OPERATOR_DOT); //one dot
|
||||
}
|
||||
|
||||
case '"':
|
||||
return makeString(lexer, c);
|
||||
|
||||
default: {
|
||||
return makeErrorToken(lexer, "Unknown token value found in lexer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void trim(char** s, unsigned int* l) { //util
|
||||
while( isspace(( (*((unsigned char**)(s)))[(*l) - 1] )) ) (*l)--;
|
||||
while(**s && isspace( **(unsigned char**)(s)) ) { (*s)++; (*l)--; }
|
||||
}
|
||||
|
||||
//for debugging
|
||||
void Toy_private_printToken(Toy_Token* token) {
|
||||
//print errors
|
||||
if (token->type == TOY_TOKEN_ERROR) {
|
||||
printf(TOY_CC_ERROR "ERROR: \t%d\t%.*s\n" TOY_CC_RESET, (int)token->line, (int)token->length, token->lexeme);
|
||||
return;
|
||||
}
|
||||
|
||||
//print the line number
|
||||
printf("\t%d\t%d\t", token->type, (int)token->line);
|
||||
|
||||
//print based on type
|
||||
if (token->type == TOY_TOKEN_NAME || token->type == TOY_TOKEN_LITERAL_INTEGER || token->type == TOY_TOKEN_LITERAL_FLOAT || token->type == TOY_TOKEN_LITERAL_STRING) {
|
||||
printf("%.*s\t", (int)token->length, token->lexeme);
|
||||
} else {
|
||||
const char* keyword = Toy_private_findKeywordByType(token->type);
|
||||
|
||||
if (keyword != NULL) {
|
||||
printf("%s", keyword);
|
||||
} else {
|
||||
char* str = (char*)token->lexeme; //strip const-ness for trimming
|
||||
unsigned int length = token->length;
|
||||
trim(&str, &length);
|
||||
printf("%.*s", (int)length, str);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_token_types.h"
|
||||
|
||||
//lexers are bound to a string of code
|
||||
typedef struct {
|
||||
unsigned int start; //start of the current token
|
||||
unsigned int current; //current position of the lexer
|
||||
unsigned int line; //track this for error handling
|
||||
const char* source;
|
||||
} Toy_Lexer;
|
||||
|
||||
//tokens are intermediaries between lexers and parsers
|
||||
typedef struct {
|
||||
Toy_TokenType type;
|
||||
unsigned int length;
|
||||
unsigned int line;
|
||||
const char* lexeme;
|
||||
} Toy_Token;
|
||||
|
||||
TOY_API void Toy_bindLexer(Toy_Lexer* lexer, const char* source);
|
||||
Toy_Token Toy_private_scanLexer(Toy_Lexer* lexer);
|
||||
|
||||
const char* Toy_private_findKeywordByType(const Toy_TokenType type);
|
||||
Toy_TokenType Toy_private_findTypeByKeyword(const char* keyword);
|
||||
void Toy_private_printToken(Toy_Token* token);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum Toy_OpcodeType {
|
||||
//This offsets the opcode values, so I can see TOY_OPCODE_READ in GDB clearly
|
||||
TOY_OPCODE_UNUSED = 0,
|
||||
|
||||
//variable instructions
|
||||
TOY_OPCODE_READ,
|
||||
TOY_OPCODE_DECLARE,
|
||||
TOY_OPCODE_ASSIGN,
|
||||
TOY_OPCODE_ASSIGN_COMPOUND, //assign to a compound's internals
|
||||
TOY_OPCODE_ACCESS,
|
||||
TOY_OPCODE_INVOKE, //for calling functions
|
||||
TOY_OPCODE_ATTRIBUTE, //for accessing parts of compounds
|
||||
TOY_OPCODE_ITERABLE, //for operating on all members of a compound
|
||||
TOY_OPCODE_DUPLICATE, //duplicate the top of the stack
|
||||
TOY_OPCODE_ELIMINATE, //remove the top of the stack
|
||||
|
||||
//arithmetic instructions
|
||||
TOY_OPCODE_ADD,
|
||||
TOY_OPCODE_SUBTRACT,
|
||||
TOY_OPCODE_MULTIPLY,
|
||||
TOY_OPCODE_DIVIDE,
|
||||
TOY_OPCODE_MODULO,
|
||||
|
||||
//comparison instructions
|
||||
TOY_OPCODE_COMPARE_EQUAL,
|
||||
// TOY_OPCODE_COMPARE_NOT, //NOTE: optimized into a composite of TOY_OPCODE_COMPARE_EQUAL + TOY_OPCODE_NEGATE
|
||||
TOY_OPCODE_COMPARE_LESS,
|
||||
TOY_OPCODE_COMPARE_LESS_EQUAL,
|
||||
TOY_OPCODE_COMPARE_GREATER,
|
||||
TOY_OPCODE_COMPARE_GREATER_EQUAL,
|
||||
|
||||
//logical instructions
|
||||
TOY_OPCODE_AND,
|
||||
TOY_OPCODE_OR,
|
||||
TOY_OPCODE_TRUTHY,
|
||||
TOY_OPCODE_NEGATE,
|
||||
|
||||
//control instructions
|
||||
TOY_OPCODE_RETURN,
|
||||
TOY_OPCODE_JUMP, //JUMP, ADDR
|
||||
TOY_OPCODE_ESCAPE, //JUMP, ADDR, UNWIND
|
||||
|
||||
TOY_OPCODE_SCOPE_PUSH,
|
||||
TOY_OPCODE_SCOPE_POP,
|
||||
|
||||
//various action instructions
|
||||
TOY_OPCODE_ASSERT,
|
||||
TOY_OPCODE_PRINT,
|
||||
TOY_OPCODE_CONCAT,
|
||||
TOY_OPCODE_INDEX,
|
||||
|
||||
//meta instructions
|
||||
TOY_OPCODE_PASS,
|
||||
TOY_OPCODE_ERROR,
|
||||
TOY_OPCODE_EOF = 255,
|
||||
} Toy_OpcodeType;
|
||||
|
||||
//specific opcode flags
|
||||
typedef enum Toy_OpParamJumpType {
|
||||
TOY_OP_PARAM_JUMP_ABSOLUTE = 0, //from the start of the routine's code section
|
||||
TOY_OP_PARAM_JUMP_RELATIVE = 1,
|
||||
} Toy_OpParamJumpType;
|
||||
|
||||
typedef enum Toy_OpParamJumpConditional {
|
||||
TOY_OP_PARAM_JUMP_ALWAYS = 0,
|
||||
TOY_OP_PARAM_JUMP_IF_TRUE = 1,
|
||||
TOY_OP_PARAM_JUMP_IF_FALSE = 2,
|
||||
} Toy_OpParamJumpConditional;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_lexer.h"
|
||||
#include "toy_ast.h"
|
||||
|
||||
typedef struct Toy_Parser {
|
||||
Toy_Lexer* lexer;
|
||||
|
||||
//last two outputs
|
||||
Toy_Token current;
|
||||
Toy_Token previous;
|
||||
|
||||
bool error;
|
||||
bool panic; //currently processing an error
|
||||
} Toy_Parser;
|
||||
|
||||
TOY_API void Toy_bindParser(Toy_Parser* parser, Toy_Lexer* lexer);
|
||||
TOY_API Toy_Ast* Toy_scanParser(Toy_Bucket** bucketHandle, Toy_Parser* parser);
|
||||
TOY_API void Toy_resetParser(Toy_Parser* parser);
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "toy_print.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static Toy_callbackType printCallback = puts;
|
||||
static Toy_callbackType errorCallback = puts;
|
||||
static Toy_callbackType assertCallback = puts;
|
||||
|
||||
void Toy_print(const char* msg) {
|
||||
printCallback(msg);
|
||||
}
|
||||
|
||||
void Toy_error(const char* msg) {
|
||||
errorCallback(msg);
|
||||
}
|
||||
|
||||
void Toy_assertFailure(const char* msg) {
|
||||
assertCallback(msg);
|
||||
}
|
||||
|
||||
void Toy_setPrintCallback(Toy_callbackType cb) {
|
||||
printCallback = cb;
|
||||
}
|
||||
|
||||
void Toy_setErrorCallback(Toy_callbackType cb) {
|
||||
errorCallback = cb;
|
||||
}
|
||||
|
||||
void Toy_setAssertFailureCallback(Toy_callbackType cb) {
|
||||
assertCallback = cb;
|
||||
}
|
||||
|
||||
void Toy_resetPrintCallback(void) {
|
||||
printCallback = puts;
|
||||
}
|
||||
|
||||
void Toy_resetErrorCallback(void) {
|
||||
errorCallback = puts;
|
||||
}
|
||||
|
||||
void Toy_resetAssertFailureCallback(void) {
|
||||
assertCallback = puts;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
//handle callbacks for printing to the terminal, or elsewhere (signature matches 'puts')
|
||||
typedef int (*Toy_callbackType)(const char*);
|
||||
|
||||
TOY_API void Toy_print(const char* msg); //print keyword
|
||||
TOY_API void Toy_error(const char* msg); //runtime errors
|
||||
TOY_API void Toy_assertFailure(const char* msg); //assert keyword failures
|
||||
|
||||
TOY_API void Toy_setPrintCallback(Toy_callbackType cb);
|
||||
TOY_API void Toy_setErrorCallback(Toy_callbackType cb);
|
||||
TOY_API void Toy_setAssertFailureCallback(Toy_callbackType cb);
|
||||
|
||||
TOY_API void Toy_resetPrintCallback(void);
|
||||
TOY_API void Toy_resetErrorCallback(void);
|
||||
TOY_API void Toy_resetAssertFailureCallback(void);
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "toy_scope.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "toy_print.h"
|
||||
|
||||
//utils
|
||||
static Toy_ScopeEntry* lookupScopeEntryPtr(Toy_Scope* scope, Toy_String* key, unsigned int hash, bool recursive) {
|
||||
//terminate
|
||||
if (scope == NULL || scope->data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//probe for the correct location
|
||||
unsigned int probe = hash % scope->capacity;
|
||||
|
||||
while (true) {
|
||||
//found the entry
|
||||
if (scope->data[probe].key != NULL && Toy_compareStrings(scope->data[probe].key, key) == 0) {
|
||||
return &(scope->data[probe]);
|
||||
}
|
||||
|
||||
//if its an empty slot (didn't find it here)
|
||||
if (scope->data[probe].key == NULL) {
|
||||
return recursive ? lookupScopeEntryPtr(scope->next, key, hash, recursive) : NULL;
|
||||
}
|
||||
|
||||
//adjust and continue
|
||||
probe = (probe + 1) % scope->capacity;
|
||||
}
|
||||
}
|
||||
|
||||
static void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_ValueType type, bool constant) {
|
||||
//make the entry
|
||||
unsigned int probe = Toy_hashString(key) % scope->capacity;
|
||||
Toy_ScopeEntry entry = (Toy_ScopeEntry){ .key = key, .value = value, .type = type, .constant = constant, .psl = 1 };
|
||||
|
||||
//probe
|
||||
while (true) {
|
||||
//if we're overriding an existing value
|
||||
if (scope->data[probe].key != NULL && Toy_compareStrings(scope->data[probe].key, entry.key) == 0) {
|
||||
scope->data[probe] = entry;
|
||||
scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl;
|
||||
return;
|
||||
}
|
||||
|
||||
//if this spot is free, insert and return
|
||||
if (TOY_VALUE_IS_NULL(scope->data[probe].value)) {
|
||||
scope->data[probe] = entry;
|
||||
scope->count++;
|
||||
scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl;
|
||||
return;
|
||||
}
|
||||
|
||||
//if the new entry is "poorer", insert it and shift the old one
|
||||
if (scope->data[probe].psl < entry.psl) {
|
||||
Toy_ScopeEntry tmp = scope->data[probe];
|
||||
scope->data[probe] = entry;
|
||||
entry = tmp;
|
||||
}
|
||||
|
||||
//adjust and continue
|
||||
probe++;
|
||||
probe &= scope->capacity - 1; //DOOM hack
|
||||
entry.psl++;
|
||||
}
|
||||
}
|
||||
|
||||
static Toy_ScopeEntry* adjustScopeEntries(Toy_Scope* scope, unsigned int newCapacity) {
|
||||
//allocate and zero a new Toy_ScopeEntry array in memory
|
||||
Toy_ScopeEntry* newEntries = malloc(newCapacity * sizeof(Toy_ScopeEntry));
|
||||
|
||||
if (newEntries == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate space for 'Toy_Scope' entries\n" TOY_CC_RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//wipe the memory
|
||||
memset(newEntries, 0, newCapacity * sizeof(Toy_ScopeEntry));
|
||||
|
||||
if (scope == NULL) { //for initial allocations
|
||||
return newEntries;
|
||||
}
|
||||
|
||||
//move the old data into the new block of memory
|
||||
unsigned int oldCapacity = scope->capacity;
|
||||
Toy_ScopeEntry* oldEntries = scope->data;
|
||||
scope->capacity = newCapacity;
|
||||
scope->data = newEntries;
|
||||
scope->count = 0;
|
||||
|
||||
//for each existing entry in the old array, copy it into the new array
|
||||
for (unsigned int i = 0; i < oldCapacity; i++) {
|
||||
if (oldEntries[i].key != NULL && oldEntries[i].key->info.length > 0) {
|
||||
probeAndInsert(scope, oldEntries[i].key, oldEntries[i].value, oldEntries[i].type, oldEntries[i].constant);
|
||||
}
|
||||
}
|
||||
|
||||
//clean up and return
|
||||
free(oldEntries);
|
||||
return newEntries;
|
||||
}
|
||||
|
||||
Toy_Value coerceValueTypesIfAble(Toy_ValueType type, Toy_Value value) {
|
||||
//integer to float
|
||||
if (type == TOY_VALUE_FLOAT && value.type == TOY_VALUE_INTEGER) {
|
||||
value = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(value) );
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
//exposed functions
|
||||
Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) {
|
||||
Toy_Scope* newScope = (Toy_Scope*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Scope));
|
||||
|
||||
newScope->next = scope;
|
||||
newScope->refCount = 0;
|
||||
newScope->data = adjustScopeEntries(NULL, TOY_SCOPE_INITIAL_CAPACITY);
|
||||
newScope->capacity = TOY_SCOPE_INITIAL_CAPACITY;
|
||||
newScope->count = 0;
|
||||
newScope->maxPsl = 0;
|
||||
|
||||
Toy_private_incrementScopeRefCount(newScope);
|
||||
|
||||
return newScope;
|
||||
}
|
||||
|
||||
Toy_Scope* Toy_popScope(Toy_Scope* scope) {
|
||||
if (scope == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Toy_private_decrementScopeRefCount(scope);
|
||||
|
||||
Toy_Scope* next = scope->next;
|
||||
Toy_releaseBucketPartition((void*)scope);
|
||||
return next;
|
||||
}
|
||||
|
||||
void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy_Value value, bool constant) {
|
||||
Toy_ScopeEntry* entryPtr = lookupScopeEntryPtr(scope, key, Toy_hashString(key), false);
|
||||
|
||||
if (entryPtr != NULL) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Can't redefine a variable: %s", key->leaf.data);
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
//expand the table capacity if needed
|
||||
if (scope->count >= scope->capacity * 0.8f) {
|
||||
scope->data = adjustScopeEntries(scope, scope->capacity * TOY_SCOPE_EXPANSION_RATE);
|
||||
}
|
||||
|
||||
value = coerceValueTypesIfAble(type, value);
|
||||
|
||||
//type check
|
||||
if (type != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && type != value.type && value.type != TOY_VALUE_REFERENCE) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Incorrect value type in declaration of '%.*s' (expected %s, got %s)", key->info.length, key->leaf.data, Toy_getValueTypeAsCString(type), Toy_getValueTypeAsCString(value.type));
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
probeAndInsert(scope, Toy_copyString(key), value, type, constant);
|
||||
}
|
||||
|
||||
void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) {
|
||||
Toy_ScopeEntry* entryPtr = lookupScopeEntryPtr(scope, key, Toy_hashString(key), true);
|
||||
|
||||
if (entryPtr == NULL) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Undefined variable: %s\n", key->leaf.data);
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
value = coerceValueTypesIfAble(entryPtr->type, value);
|
||||
|
||||
//type check
|
||||
if (entryPtr->type != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && entryPtr->type != value.type && value.type != TOY_VALUE_REFERENCE) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Incorrect value type in assignment of '%.*s' (expected %s, got %s)", key->info.length, key->leaf.data, Toy_getValueTypeAsCString(entryPtr->type), Toy_getValueTypeAsCString(value.type));
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
//constness check
|
||||
if (entryPtr->constant) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Can't assign to a constant variable %s", key->leaf.data);
|
||||
Toy_error(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
Toy_freeValue(entryPtr->value);
|
||||
|
||||
entryPtr->value = value;
|
||||
}
|
||||
|
||||
Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key) {
|
||||
Toy_ScopeEntry* entryPtr = lookupScopeEntryPtr(scope, key, Toy_hashString(key), true);
|
||||
|
||||
if (entryPtr == NULL) {
|
||||
char buffer[key->info.length + 256];
|
||||
sprintf(buffer, "Undefined variable: %s\n", key->leaf.data);
|
||||
Toy_error(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &(entryPtr->value);
|
||||
}
|
||||
|
||||
bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key) {
|
||||
Toy_ScopeEntry* entryPtr = lookupScopeEntryPtr(scope, key, Toy_hashString(key), true);
|
||||
return entryPtr != NULL;
|
||||
}
|
||||
|
||||
void Toy_private_incrementScopeRefCount(Toy_Scope* scope) {
|
||||
for (Toy_Scope* iter = scope; iter; iter = iter->next) {
|
||||
//check for issues
|
||||
if (iter->next != NULL && iter->next->refCount == 0) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope's ancestor has a refcount of 0'\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
iter->refCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void Toy_private_decrementScopeRefCount(Toy_Scope* scope) {
|
||||
for (Toy_Scope* iter = scope; iter != NULL; iter = iter->next) {
|
||||
iter->refCount--;
|
||||
|
||||
//clean up our insides if needed
|
||||
if (iter->refCount == 0) {
|
||||
//free the data
|
||||
if (iter->data != NULL) {
|
||||
for (unsigned int i = 0; i < iter->capacity; i++) {
|
||||
if (iter->data[i].key != NULL) {
|
||||
Toy_freeString(iter->data[i].key);
|
||||
Toy_freeValue(iter->data[i].value);
|
||||
}
|
||||
}
|
||||
free(iter->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_string.h"
|
||||
#include "toy_value.h"
|
||||
|
||||
//keys are leaf-only strings
|
||||
typedef struct Toy_ScopeEntry {
|
||||
Toy_String* key;
|
||||
Toy_Value value;
|
||||
Toy_ValueType type;
|
||||
unsigned int psl; //psl '0' means empty
|
||||
bool constant;
|
||||
} Toy_ScopeEntry;
|
||||
|
||||
//holds a table-like collection of variables TODO: check bitness
|
||||
typedef struct Toy_Scope {
|
||||
struct Toy_Scope* next;
|
||||
unsigned int refCount;
|
||||
Toy_ScopeEntry* data;
|
||||
unsigned int capacity;
|
||||
unsigned int count;
|
||||
unsigned int maxPsl;
|
||||
} Toy_Scope;
|
||||
|
||||
//handle deep scopes - the scope is stored in the bucket, not the table
|
||||
TOY_API Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope);
|
||||
TOY_API Toy_Scope* Toy_popScope(Toy_Scope* scope);
|
||||
|
||||
//manage the contents
|
||||
TOY_API void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy_Value value, bool constant);
|
||||
TOY_API void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value);
|
||||
TOY_API Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key);
|
||||
|
||||
TOY_API bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key);
|
||||
|
||||
//manage refcounting
|
||||
void Toy_private_incrementScopeRefCount(Toy_Scope* scope);
|
||||
void Toy_private_decrementScopeRefCount(Toy_Scope* scope);
|
||||
|
||||
//some useful sizes, could be swapped out as needed
|
||||
#ifndef TOY_SCOPE_INITIAL_CAPACITY
|
||||
#define TOY_SCOPE_INITIAL_CAPACITY 8
|
||||
#endif
|
||||
|
||||
//NOTE: The DOOM hack needs a power of 2
|
||||
#ifndef TOY_SCOPE_EXPANSION_RATE
|
||||
#define TOY_SCOPE_EXPANSION_RATE 2
|
||||
#endif
|
||||
|
||||
//expand when the contents passes a certain percentage (80%) of the capacity
|
||||
#ifndef TOY_SCOPE_EXPANSION_THRESHOLD
|
||||
#define TOY_SCOPE_EXPANSION_THRESHOLD 0.7f
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "toy_stack.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Toy_Stack* Toy_allocateStack(void) {
|
||||
Toy_Stack* stack = malloc(TOY_STACK_INITIAL_CAPACITY * sizeof(Toy_Value) + sizeof(Toy_Stack));
|
||||
|
||||
if (stack == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Stack' of %d capacity (%d space in memory)\n" TOY_CC_RESET, TOY_STACK_INITIAL_CAPACITY, (int)(TOY_STACK_INITIAL_CAPACITY * sizeof(Toy_Value) + sizeof(Toy_Stack)));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
stack->capacity = TOY_STACK_INITIAL_CAPACITY;
|
||||
stack->count = 0;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
void Toy_freeStack(Toy_Stack* stack) {
|
||||
if (stack != NULL) {
|
||||
//if some values will be removed, free them first
|
||||
for (unsigned int i = 0; i < stack->count; i++) {
|
||||
Toy_freeValue(stack->data[i]);
|
||||
}
|
||||
|
||||
free(stack);
|
||||
}
|
||||
}
|
||||
|
||||
void Toy_resetStack(Toy_Stack** stackHandle) {
|
||||
if ((*stackHandle) == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//if some values will be removed, free them first
|
||||
for (unsigned int i = 0; i < (*stackHandle)->count; i++) {
|
||||
Toy_freeValue((*stackHandle)->data[i]);
|
||||
}
|
||||
|
||||
//reset to the stack's default state
|
||||
if ((*stackHandle)->capacity > TOY_STACK_INITIAL_CAPACITY) {
|
||||
(*stackHandle) = realloc((*stackHandle), TOY_STACK_INITIAL_CAPACITY * sizeof(Toy_Value) + sizeof(Toy_Stack));
|
||||
memset((*stackHandle), 0, TOY_STACK_INITIAL_CAPACITY * sizeof(Toy_Value) + sizeof(Toy_Stack));
|
||||
|
||||
(*stackHandle)->capacity = TOY_STACK_INITIAL_CAPACITY;
|
||||
}
|
||||
|
||||
(*stackHandle)->count = 0;
|
||||
}
|
||||
|
||||
void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value) {
|
||||
//don't go overboard
|
||||
if ((*stackHandle)->count >= TOY_STACK_OVERFLOW_THRESHOLD) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Stack overflow\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
//expand the capacity if needed
|
||||
if ((*stackHandle)->count + 1 > (*stackHandle)->capacity) {
|
||||
while ((*stackHandle)->count + 1 > (*stackHandle)->capacity) {
|
||||
(*stackHandle)->capacity = (*stackHandle)->capacity < TOY_STACK_INITIAL_CAPACITY ? TOY_STACK_INITIAL_CAPACITY : (*stackHandle)->capacity * TOY_STACK_EXPANSION_RATE;
|
||||
}
|
||||
|
||||
unsigned int newCapacity = (*stackHandle)->capacity;
|
||||
|
||||
(*stackHandle) = realloc((*stackHandle), newCapacity * sizeof(Toy_Value) + sizeof(Toy_Stack));
|
||||
|
||||
if ((*stackHandle) == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to reallocate a 'Toy_Stack' of %d capacity (%d space in memory)\n" TOY_CC_RESET, (int)newCapacity, (int)(newCapacity * sizeof(Toy_Value) + sizeof(Toy_Stack)));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Note: "pointer arithmetic in C/C++ is type-relative"
|
||||
((Toy_Value*)((*stackHandle) + 1))[(*stackHandle)->count++] = value;
|
||||
}
|
||||
|
||||
Toy_Value Toy_peekStack(Toy_Stack** stackHandle) {
|
||||
if ((*stackHandle)->count == 0) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow when peeking\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return ((Toy_Value*)((*stackHandle) + 1))[(*stackHandle)->count - 1];
|
||||
}
|
||||
|
||||
Toy_Value Toy_popStack(Toy_Stack** stackHandle) {
|
||||
if ((*stackHandle)->count == 0) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow when popping\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
//shrink if possible
|
||||
if ((*stackHandle)->capacity > TOY_STACK_INITIAL_CAPACITY && (*stackHandle)->count <= (*stackHandle)->capacity / TOY_STACK_CONTRACTION_THRESHOLD) {
|
||||
(*stackHandle)->capacity /= TOY_STACK_CONTRACTION_THRESHOLD;
|
||||
unsigned int newCapacity = (*stackHandle)->capacity; //cache for the msg
|
||||
|
||||
(*stackHandle) = realloc((*stackHandle), (*stackHandle)->capacity * sizeof(Toy_Value) + sizeof(Toy_Stack));
|
||||
|
||||
if ((*stackHandle) == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to reallocate a 'Toy_Stack' of %d capacity (%d space in memory)\n" TOY_CC_RESET, (int)newCapacity, (int)(newCapacity * sizeof(Toy_Value) + sizeof(Toy_Stack)));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return ((Toy_Value*)((*stackHandle) + 1))[--(*stackHandle)->count];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_value.h"
|
||||
|
||||
typedef struct Toy_Stack { //32 | 64 BITNESS
|
||||
unsigned int capacity; //4 | 4
|
||||
unsigned int count; //4 | 4
|
||||
Toy_Value data[]; //- | -
|
||||
} Toy_Stack; //8 | 8
|
||||
|
||||
TOY_API Toy_Stack* Toy_allocateStack(void);
|
||||
TOY_API void Toy_freeStack(Toy_Stack* stack);
|
||||
TOY_API void Toy_resetStack(Toy_Stack** stackHandle);
|
||||
|
||||
TOY_API void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value);
|
||||
TOY_API Toy_Value Toy_peekStack(Toy_Stack** stackHandle);
|
||||
TOY_API Toy_Value Toy_popStack(Toy_Stack** stackHandle);
|
||||
|
||||
//some useful sizes, could be swapped out as needed
|
||||
#ifndef TOY_STACK_INITIAL_CAPACITY
|
||||
#define TOY_STACK_INITIAL_CAPACITY 8
|
||||
#endif
|
||||
|
||||
#ifndef TOY_STACK_EXPANSION_RATE
|
||||
#define TOY_STACK_EXPANSION_RATE 2
|
||||
#endif
|
||||
|
||||
#ifndef TOY_STACK_CONTRACTION_THRESHOLD
|
||||
#define TOY_STACK_CONTRACTION_THRESHOLD 4 // this integer means 'shrink when count drops below one-forth of capacity'
|
||||
#endif
|
||||
|
||||
//prevent an infinite expansion, limited to 1MB
|
||||
#ifndef TOY_STACK_OVERFLOW_THRESHOLD
|
||||
#define TOY_STACK_OVERFLOW_THRESHOLD (1024 * 1024 / sizeof(Toy_Value) - sizeof(Toy_Stack))
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
#include "toy_string.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//utils
|
||||
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
static void incrementRefCount(Toy_String* str) {
|
||||
str->info.refCount++;
|
||||
if (str->info.type == TOY_STRING_NODE) {
|
||||
incrementRefCount(str->node.left);
|
||||
incrementRefCount(str->node.right);
|
||||
}
|
||||
}
|
||||
|
||||
static void decrementRefCount(Toy_String* str) {
|
||||
str->info.refCount--;
|
||||
if (str->info.type == TOY_STRING_NODE) {
|
||||
//the parent of this node triggered a decrement across the whole tree
|
||||
decrementRefCount(str->node.left);
|
||||
decrementRefCount(str->node.right);
|
||||
}
|
||||
if (str->info.refCount == 0) {
|
||||
if (str->info.type == TOY_STRING_NODE) {
|
||||
//THIS node has triggered the decrement, so run this again
|
||||
decrementRefCount(str->node.left);
|
||||
decrementRefCount(str->node.right);
|
||||
}
|
||||
|
||||
//mark this memory as unused
|
||||
Toy_releaseBucketPartition((void*)str);
|
||||
}
|
||||
}
|
||||
|
||||
//exposed functions
|
||||
Toy_String* Toy_toString(Toy_Bucket** bucketHandle, const char* cstring) {
|
||||
return Toy_toStringLength(bucketHandle, cstring, strlen(cstring));
|
||||
}
|
||||
|
||||
Toy_String* Toy_toStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length) {
|
||||
Toy_String* ret = (Toy_String*)Toy_partitionBucket(bucketHandle, sizeof(Toy_String));
|
||||
|
||||
ret->info.type = TOY_STRING_LEAF;
|
||||
ret->info.length = length;
|
||||
ret->info.refCount = 1;
|
||||
ret->info.cachedHash = 0; //don't calc until needed
|
||||
ret->leaf.data = cstring; //don't make a local copy
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Toy_String* Toy_createStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length) {
|
||||
Toy_String* ret = (Toy_String*)Toy_partitionBucket(bucketHandle, sizeof(Toy_String) + length + 1);
|
||||
|
||||
if (length > 0) {
|
||||
ret->leaf.data = (char*)(ret + 1); //increments by 1 'string', to the length +1
|
||||
strncpy((char*)(ret->leaf.data), cstring, length);
|
||||
((char*)(ret->leaf.data))[length] = '\0'; //don't forget the null
|
||||
ret->info.length = length;
|
||||
}
|
||||
else {
|
||||
ret->leaf.data = "";
|
||||
ret->info.length = length;
|
||||
}
|
||||
|
||||
ret->info.type = TOY_STRING_LEAF;
|
||||
ret->info.refCount = 1;
|
||||
ret->info.cachedHash = 0; //don't calc until needed
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Toy_String* Toy_copyString(Toy_String* str) {
|
||||
assert(str->info.refCount != 0 && "Can't copy a string with refcount of zero");
|
||||
incrementRefCount(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
Toy_String* Toy_concatStrings(Toy_Bucket** bucketHandle, Toy_String* left, Toy_String* right) {
|
||||
assert(left->info.refCount != 0 && right->info.refCount != 0 && "Can't concatenate a string with a refcount of zero");
|
||||
|
||||
Toy_String* ret = (Toy_String*)Toy_partitionBucket(bucketHandle, sizeof(Toy_String));
|
||||
|
||||
ret->info.type = TOY_STRING_NODE;
|
||||
ret->info.length = left->info.length + right->info.length;
|
||||
ret->info.refCount = 1;
|
||||
ret->info.cachedHash = 0; //don't calc until needed
|
||||
ret->node.left = left;
|
||||
ret->node.right = right;
|
||||
|
||||
incrementRefCount(left);
|
||||
incrementRefCount(right);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Toy_freeString(Toy_String* str) {
|
||||
assert(str->info.refCount > 0 && "Can't free a string with refcount 0");
|
||||
//memory is freed when the bucket is
|
||||
decrementRefCount(str);
|
||||
}
|
||||
|
||||
unsigned int Toy_getStringLength(Toy_String* str) {
|
||||
return str->info.length;
|
||||
}
|
||||
|
||||
unsigned int Toy_getStringRefCount(Toy_String* str) {
|
||||
return str->info.refCount;
|
||||
}
|
||||
|
||||
static void getStringRawUtil(char* dest, Toy_String* str) {
|
||||
//sometimes, "clever" can be a bad thing...
|
||||
if (str->info.type == TOY_STRING_NODE) {
|
||||
getStringRawUtil(dest, str->node.left);
|
||||
getStringRawUtil(dest + str->node.left->info.length, str->node.right);
|
||||
}
|
||||
|
||||
else {
|
||||
memcpy(dest, str->leaf.data, str->info.length);
|
||||
}
|
||||
}
|
||||
|
||||
char* Toy_getStringRaw(Toy_String* str) {
|
||||
assert(str->info.refCount != 0 && "Can't build a raw string from a string with refcount of zero");
|
||||
|
||||
//BUGFIX: Make sure it's aligned, and there's space for the null
|
||||
unsigned int len = (str->info.length + 3) & ~3;
|
||||
if (len == str->info.length) { //nulls aren't counted in a string's length
|
||||
len += 4;
|
||||
}
|
||||
|
||||
char* buffer = malloc(len);
|
||||
|
||||
getStringRawUtil(buffer, str);
|
||||
buffer[str->info.length] = '\0';
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static int deepCompareUtil(Toy_String* left, Toy_String* right, const char** leftHead, const char** rightHead) {
|
||||
//NOTE: this function can't handle strings of zero length
|
||||
int result = 0;
|
||||
|
||||
//if it's the same object, of course they match
|
||||
if (left == right) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//BUGFIX: if we're not currently iterating through the left leaf (and leftHead is not null), skip out
|
||||
if (left->info.type == TOY_STRING_LEAF && (*leftHead) != NULL && (**leftHead) != '\0' && ((*leftHead) < left->leaf.data || (*leftHead) > (left->leaf.data + left->info.length)) ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//BUGFIX: if we're not currently iterating through the right leaf (and rightHead is not null), skip out
|
||||
if (right->info.type == TOY_STRING_LEAF && (*rightHead) != NULL && (**rightHead) != '\0' && ((*rightHead) < right->leaf.data || (*rightHead) > (right->leaf.data + right->info.length)) ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//dig into left
|
||||
if (left->info.type == TOY_STRING_NODE) {
|
||||
if ((result = deepCompareUtil(left->node.left, right, leftHead, rightHead)) != 0) {
|
||||
return result;
|
||||
}
|
||||
if ((result = deepCompareUtil(left->node.right, right, leftHead, rightHead)) != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//return zero to keep going
|
||||
return result;
|
||||
}
|
||||
|
||||
//dig into right
|
||||
if (right->info.type == TOY_STRING_NODE) {
|
||||
if ((result = deepCompareUtil(left, right->node.left, leftHead, rightHead)) != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ((result = deepCompareUtil(left, right->node.right, leftHead, rightHead)) != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
//return zero to keep going
|
||||
return result;
|
||||
}
|
||||
|
||||
//keep comparing the leaves
|
||||
if (left->info.type == TOY_STRING_LEAF && right->info.type == TOY_STRING_LEAF) {
|
||||
//initial head states can be null, or null characters
|
||||
if ((*leftHead) == NULL || (**leftHead) == '\0') {
|
||||
(*leftHead) = left->leaf.data;
|
||||
}
|
||||
|
||||
if ((*rightHead) == NULL || (**rightHead) == '\0') {
|
||||
(*rightHead) = right->leaf.data;
|
||||
}
|
||||
|
||||
//compare and increment
|
||||
while (**leftHead && (**leftHead == **rightHead)) {
|
||||
(*leftHead)++;
|
||||
(*rightHead)++;
|
||||
}
|
||||
|
||||
//if there's a difference, and neither is null, than a result has (probably) been found
|
||||
if ((**leftHead != '\0' && **rightHead != '\0' && (**leftHead != **rightHead))) {
|
||||
result = *(const unsigned char*)(*leftHead) - *(const unsigned char*)(*rightHead);
|
||||
}
|
||||
}
|
||||
|
||||
//returning 0 means no difference found yet
|
||||
return result;
|
||||
}
|
||||
|
||||
int Toy_compareStrings(Toy_String* left, Toy_String* right) {
|
||||
//BUGFIX: since deepCompareUtil() can't handle strings of length zero, insert a check here
|
||||
if (left->info.length == 0 || right->info.length == 0) {
|
||||
return left->info.length - right->info.length;
|
||||
}
|
||||
|
||||
//BUGFIX: If both args are leaves, and one is a substring of the other, then deepCompareUtil() will return a wrong result
|
||||
if (left->info.type == TOY_STRING_LEAF && right->info.type == TOY_STRING_LEAF) {
|
||||
unsigned int maxLength = left->info.length > right->info.length ? left->info.length : right->info.length;
|
||||
return strncmp(left->leaf.data, right->leaf.data, maxLength);
|
||||
}
|
||||
|
||||
//util pointers
|
||||
const char* leftHead = NULL;
|
||||
const char* rightHead = NULL;
|
||||
|
||||
int result = deepCompareUtil(left, right, &leftHead, &rightHead);
|
||||
|
||||
//BUGFIX: deepCompareUtil() doesn't handle substrings correctly
|
||||
if (result == 0 && leftHead != NULL && rightHead != NULL) {
|
||||
return (int)(*leftHead - *rightHead);
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int hashCString(const char* string) {
|
||||
unsigned int hash = 2166136261u;
|
||||
|
||||
for (unsigned int i = 0; string[i]; i++) {
|
||||
hash *= string[i];
|
||||
hash ^= 16777619;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
unsigned int Toy_hashString(Toy_String* str) {
|
||||
if (str->info.cachedHash != 0) {
|
||||
return str->info.cachedHash;
|
||||
}
|
||||
else if (str->info.type == TOY_STRING_NODE) {
|
||||
char* buffer = Toy_getStringRaw(str);
|
||||
str->info.cachedHash = hashCString(buffer);
|
||||
free(buffer);
|
||||
}
|
||||
else {
|
||||
str->info.cachedHash = hashCString(str->leaf.data);
|
||||
}
|
||||
|
||||
return str->info.cachedHash;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_bucket.h"
|
||||
|
||||
//forward declare
|
||||
union Toy_String_t;
|
||||
|
||||
//rope pattern, conforming to the C spec - see #158
|
||||
typedef enum Toy_StringType {
|
||||
TOY_STRING_NODE,
|
||||
TOY_STRING_LEAF,
|
||||
} Toy_StringType;
|
||||
|
||||
typedef struct Toy_StringInfo {
|
||||
Toy_StringType type;
|
||||
unsigned int length;
|
||||
unsigned int refCount;
|
||||
unsigned int cachedHash;
|
||||
} Toy_StringInfo;
|
||||
|
||||
typedef struct Toy_StringNode {
|
||||
Toy_StringInfo _padding;
|
||||
union Toy_String_t* left;
|
||||
union Toy_String_t* right;
|
||||
} Toy_StringNode;
|
||||
|
||||
typedef struct Toy_StringLeaf {
|
||||
Toy_StringInfo _padding;
|
||||
const char* data;
|
||||
} Toy_StringLeaf;
|
||||
|
||||
typedef union Toy_String_t {
|
||||
Toy_StringInfo info;
|
||||
Toy_StringNode node;
|
||||
Toy_StringLeaf leaf;
|
||||
} Toy_String;
|
||||
|
||||
//
|
||||
TOY_API Toy_String* Toy_toString(Toy_Bucket** bucketHandle, const char* cstring);
|
||||
TOY_API Toy_String* Toy_toStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length);
|
||||
|
||||
TOY_API Toy_String* Toy_createStringLength(Toy_Bucket** bucketHandle, const char* cstring, unsigned int length);
|
||||
|
||||
TOY_API Toy_String* Toy_copyString(Toy_String* str);
|
||||
TOY_API Toy_String* Toy_concatStrings(Toy_Bucket** bucketHandle, Toy_String* left, Toy_String* right);
|
||||
|
||||
TOY_API void Toy_freeString(Toy_String* str);
|
||||
|
||||
TOY_API unsigned int Toy_getStringLength(Toy_String* str);
|
||||
TOY_API unsigned int Toy_getStringRefCount(Toy_String* str);
|
||||
|
||||
TOY_API char* Toy_getStringRaw(Toy_String* str); //allocates the buffer on the heap, needs to be freed
|
||||
TOY_API int Toy_compareStrings(Toy_String* left, Toy_String* right); //return value mimics strcmp()
|
||||
|
||||
TOY_API unsigned int Toy_hashString(Toy_String* string);
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "toy_table.h"
|
||||
#include "toy_console_colors.h"
|
||||
#include "toy_print.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//utils
|
||||
static void probeAndInsert(Toy_Table** tableHandle, Toy_Value key, Toy_Value value) {
|
||||
//make the entry
|
||||
unsigned int probe = Toy_hashValue(key) % (*tableHandle)->capacity;
|
||||
Toy_TableEntry entry = (Toy_TableEntry){ .key = key, .value = value, .psl = 0 };
|
||||
|
||||
//probe
|
||||
while (true) {
|
||||
//if we're overriding an existing value
|
||||
if (Toy_checkValuesAreEqual((*tableHandle)->data[probe].key, key)) {
|
||||
(*tableHandle)->data[probe] = entry;
|
||||
(*tableHandle)->maxPsl = entry.psl > (*tableHandle)->maxPsl ? entry.psl : (*tableHandle)->maxPsl;
|
||||
return;
|
||||
}
|
||||
|
||||
//if this spot is free, insert and return
|
||||
if (TOY_VALUE_IS_NULL((*tableHandle)->data[probe].key)) {
|
||||
(*tableHandle)->data[probe] = entry;
|
||||
(*tableHandle)->count++;
|
||||
(*tableHandle)->maxPsl = entry.psl > (*tableHandle)->maxPsl ? entry.psl : (*tableHandle)->maxPsl;
|
||||
return;
|
||||
}
|
||||
|
||||
//if the new entry is "poorer", insert it and shift the old one
|
||||
if ((*tableHandle)->data[probe].psl < entry.psl) {
|
||||
Toy_TableEntry tmp = (*tableHandle)->data[probe];
|
||||
(*tableHandle)->data[probe] = entry;
|
||||
entry = tmp;
|
||||
}
|
||||
|
||||
//adjust and continue
|
||||
probe++;
|
||||
probe &= (*tableHandle)->capacity - 1; //DOOM hack
|
||||
entry.psl++;
|
||||
}
|
||||
}
|
||||
|
||||
//exposed functions
|
||||
Toy_Table* Toy_private_adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity) {
|
||||
//allocate and zero a new table in memory
|
||||
Toy_Table* newTable = malloc(newCapacity * sizeof(Toy_TableEntry) + sizeof(Toy_Table));
|
||||
|
||||
if (newTable == NULL) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Table'\n" TOY_CC_RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
newTable->capacity = newCapacity;
|
||||
newTable->count = 0;
|
||||
newTable->maxPsl = 0;
|
||||
|
||||
//unlike other structures, the empty space in a table needs to be null
|
||||
memset(newTable + 1, 0, newTable->capacity * sizeof(Toy_TableEntry));
|
||||
|
||||
if (oldTable == NULL) { //for initial allocations
|
||||
return newTable;
|
||||
}
|
||||
|
||||
//for each entry in the old table, copy it into the new table
|
||||
for (unsigned int i = 0; i < oldTable->capacity; i++) {
|
||||
if (!TOY_VALUE_IS_NULL(oldTable->data[i].key)) {
|
||||
probeAndInsert(&newTable, oldTable->data[i].key, oldTable->data[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
//clean up and return
|
||||
free(oldTable);
|
||||
return newTable;
|
||||
}
|
||||
|
||||
Toy_Table* Toy_allocateTable(unsigned int minCapacity) {
|
||||
minCapacity = minCapacity > TOY_TABLE_INITIAL_CAPACITY ? minCapacity : TOY_TABLE_INITIAL_CAPACITY;
|
||||
|
||||
//neat trick to find the next power of two, inclusive
|
||||
minCapacity--;
|
||||
minCapacity |= minCapacity >> 1;
|
||||
minCapacity |= minCapacity >> 2;
|
||||
minCapacity |= minCapacity >> 4;
|
||||
minCapacity |= minCapacity >> 8;
|
||||
minCapacity |= minCapacity >> 16;
|
||||
minCapacity++;
|
||||
|
||||
return Toy_private_adjustTableCapacity(NULL, minCapacity);
|
||||
}
|
||||
|
||||
void Toy_freeTable(Toy_Table* table) {
|
||||
if (table != NULL) {
|
||||
//if some values will be removed, free them first
|
||||
for (unsigned int i = 0; i < table->capacity; i++) {
|
||||
Toy_freeValue(table->data[i].key);
|
||||
Toy_freeValue(table->data[i].value);
|
||||
}
|
||||
|
||||
free(table);
|
||||
}
|
||||
}
|
||||
|
||||
void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value value) {
|
||||
if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key) || TOY_VALUE_IS_FUNCTION(key) || TOY_VALUE_IS_OPAQUE(key)) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//expand the capacity
|
||||
if ((*tableHandle)->count >= (*tableHandle)->capacity * TOY_TABLE_EXPANSION_THRESHOLD) {
|
||||
(*tableHandle) = Toy_private_adjustTableCapacity((*tableHandle), (*tableHandle)->capacity * TOY_TABLE_EXPANSION_RATE);
|
||||
}
|
||||
|
||||
probeAndInsert(tableHandle, key, value);
|
||||
}
|
||||
|
||||
Toy_TableEntry* Toy_private_lookupTableEntryPtr(Toy_Table** tableHandle, Toy_Value key) {
|
||||
if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key) || TOY_VALUE_IS_FUNCTION(key) || TOY_VALUE_IS_OPAQUE(key)) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//lookup
|
||||
unsigned int probe = Toy_hashValue(key) % (*tableHandle)->capacity;
|
||||
|
||||
while (true) {
|
||||
//found the entry
|
||||
if (Toy_checkValuesAreEqual((*tableHandle)->data[probe].key, key)) {
|
||||
return (*tableHandle)->data + probe;
|
||||
}
|
||||
|
||||
//if its an empty slot
|
||||
if (TOY_VALUE_IS_NULL((*tableHandle)->data[probe].key)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//adjust and continue
|
||||
probe++;
|
||||
probe &= (*tableHandle)->capacity - 1; //DOOM hack
|
||||
}
|
||||
}
|
||||
|
||||
Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) {
|
||||
Toy_TableEntry* entry = Toy_private_lookupTableEntryPtr(tableHandle, key);
|
||||
|
||||
if (entry == NULL) {
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
else {
|
||||
return entry->value;
|
||||
}
|
||||
}
|
||||
|
||||
void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key) {
|
||||
if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key) || TOY_VALUE_IS_FUNCTION(key) || TOY_VALUE_IS_OPAQUE(key)) {
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//lookup
|
||||
unsigned int probe = Toy_hashValue(key) % (*tableHandle)->capacity;
|
||||
unsigned int wipe = probe; //wiped at the end
|
||||
|
||||
while (true) {
|
||||
//found the entry
|
||||
if (Toy_checkValuesAreEqual((*tableHandle)->data[probe].key, key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
//if its an empty slot
|
||||
if (TOY_VALUE_IS_NULL((*tableHandle)->data[probe].key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//adjust and continue
|
||||
probe++;
|
||||
probe &= (*tableHandle)->capacity - 1; //DOOM hack
|
||||
}
|
||||
|
||||
//shift along the later entries
|
||||
for (unsigned int i = 0; i < (*tableHandle)->maxPsl; i++) {
|
||||
//DOOM hack used twice
|
||||
unsigned int p = (probe + i + 0) & ((*tableHandle)->capacity-1); //prev
|
||||
unsigned int u = (probe + i + 1) & ((*tableHandle)->capacity-1); //current
|
||||
|
||||
(*tableHandle)->data[p] = (*tableHandle)->data[u];
|
||||
(*tableHandle)->data[p].psl--;
|
||||
|
||||
//if you hit something where it should be, or nothing at all, stop
|
||||
if (TOY_VALUE_IS_NULL((*tableHandle)->data[u].key) || (*tableHandle)->data[p].psl == 0) {
|
||||
wipe = u;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//finally, wipe the removed entry
|
||||
(*tableHandle)->data[wipe] = (Toy_TableEntry){ .key = TOY_VALUE_FROM_NULL(), .value = TOY_VALUE_FROM_NULL(), .psl = 0 };
|
||||
(*tableHandle)->count--;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_value.h"
|
||||
|
||||
//key-value entry, and probe sequence length - https://programming.guide/robin-hood-hashing.html
|
||||
typedef struct Toy_TableEntry { //32 | 64 BITNESS
|
||||
Toy_Value key; //8 | 8
|
||||
Toy_Value value; //8 | 8
|
||||
unsigned int psl; //4 | 4
|
||||
} Toy_TableEntry; //20 | 20
|
||||
|
||||
//key-value table
|
||||
typedef struct Toy_Table { //32 | 64 BITNESS
|
||||
unsigned int capacity; //4 | 4
|
||||
unsigned int count; //4 | 4
|
||||
unsigned int maxPsl; //4 | 4
|
||||
Toy_TableEntry data[]; //- | -
|
||||
} Toy_Table; //12 | 12
|
||||
|
||||
TOY_API Toy_Table* Toy_allocateTable(unsigned int minCapacity); //minCapacity of 0 uses the default
|
||||
TOY_API void Toy_freeTable(Toy_Table* table);
|
||||
TOY_API void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value value);
|
||||
TOY_API Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key);
|
||||
TOY_API void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key);
|
||||
|
||||
//NOTE: exposed to skip unnecessary allocations within Toy_Scope
|
||||
Toy_Table* Toy_private_adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity);
|
||||
Toy_TableEntry* Toy_private_lookupTableEntryPtr(Toy_Table** tableHandle, Toy_Value key);
|
||||
|
||||
//some useful sizes, could be swapped out as needed
|
||||
#ifndef TOY_TABLE_INITIAL_CAPACITY
|
||||
#define TOY_TABLE_INITIAL_CAPACITY 8
|
||||
#endif
|
||||
|
||||
//NOTE: The DOOM hack needs a power of 2
|
||||
#ifndef TOY_TABLE_EXPANSION_RATE
|
||||
#define TOY_TABLE_EXPANSION_RATE 2
|
||||
#endif
|
||||
|
||||
//expand when the contents passes a certain percentage (80%) of the capacity
|
||||
#ifndef TOY_TABLE_EXPANSION_THRESHOLD
|
||||
#define TOY_TABLE_EXPANSION_THRESHOLD 0.7f
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
//the types of tokens produced by the lexer
|
||||
typedef enum Toy_TokenType {
|
||||
//with apologies to Tony Hoare
|
||||
TOY_TOKEN_NULL,
|
||||
|
||||
//variable names
|
||||
TOY_TOKEN_NAME,
|
||||
|
||||
//types
|
||||
TOY_TOKEN_TYPE_BOOLEAN,
|
||||
TOY_TOKEN_TYPE_INTEGER,
|
||||
TOY_TOKEN_TYPE_FLOAT,
|
||||
TOY_TOKEN_TYPE_STRING,
|
||||
TOY_TOKEN_TYPE_ARRAY,
|
||||
TOY_TOKEN_TYPE_TABLE,
|
||||
TOY_TOKEN_TYPE_FUNCTION,
|
||||
TOY_TOKEN_TYPE_OPAQUE,
|
||||
TOY_TOKEN_TYPE_ANY,
|
||||
|
||||
//keywords and reserved words
|
||||
TOY_TOKEN_KEYWORD_AS, //unused
|
||||
TOY_TOKEN_KEYWORD_ASSERT,
|
||||
TOY_TOKEN_KEYWORD_BREAK,
|
||||
TOY_TOKEN_KEYWORD_CLASS, //unused
|
||||
TOY_TOKEN_KEYWORD_CONST,
|
||||
TOY_TOKEN_KEYWORD_CONTINUE,
|
||||
TOY_TOKEN_KEYWORD_DO, //unused
|
||||
TOY_TOKEN_KEYWORD_ELSE,
|
||||
TOY_TOKEN_KEYWORD_EXPORT, //unused
|
||||
TOY_TOKEN_KEYWORD_FOR,
|
||||
TOY_TOKEN_KEYWORD_FOREACH, //unused
|
||||
TOY_TOKEN_KEYWORD_FUNCTION, //remapped 'fn'
|
||||
TOY_TOKEN_KEYWORD_IF,
|
||||
TOY_TOKEN_KEYWORD_IMPORT, //unused
|
||||
TOY_TOKEN_KEYWORD_IN, //unused
|
||||
TOY_TOKEN_KEYWORD_OF, //unused
|
||||
TOY_TOKEN_KEYWORD_PASS,
|
||||
TOY_TOKEN_KEYWORD_PRINT,
|
||||
TOY_TOKEN_KEYWORD_RETURN,
|
||||
TOY_TOKEN_KEYWORD_VAR,
|
||||
TOY_TOKEN_KEYWORD_WHILE,
|
||||
TOY_TOKEN_KEYWORD_YIELD, //unused
|
||||
|
||||
//literal values
|
||||
TOY_TOKEN_LITERAL_TRUE,
|
||||
TOY_TOKEN_LITERAL_FALSE,
|
||||
TOY_TOKEN_LITERAL_INTEGER,
|
||||
TOY_TOKEN_LITERAL_FLOAT,
|
||||
TOY_TOKEN_LITERAL_STRING,
|
||||
|
||||
//math operators
|
||||
TOY_TOKEN_OPERATOR_ADD, // +
|
||||
TOY_TOKEN_OPERATOR_SUBTRACT, // -
|
||||
TOY_TOKEN_OPERATOR_MULTIPLY, // *
|
||||
TOY_TOKEN_OPERATOR_DIVIDE, // /
|
||||
TOY_TOKEN_OPERATOR_MODULO, // %
|
||||
TOY_TOKEN_OPERATOR_ADD_ASSIGN, // +=
|
||||
TOY_TOKEN_OPERATOR_SUBTRACT_ASSIGN, // -=
|
||||
TOY_TOKEN_OPERATOR_MULTIPLY_ASSIGN, // *=
|
||||
TOY_TOKEN_OPERATOR_DIVIDE_ASSIGN, // /=
|
||||
TOY_TOKEN_OPERATOR_MODULO_ASSIGN, // %=
|
||||
TOY_TOKEN_OPERATOR_INCREMENT, // ++
|
||||
TOY_TOKEN_OPERATOR_DECREMENT, // --
|
||||
TOY_TOKEN_OPERATOR_ASSIGN, // =
|
||||
|
||||
//comparator operators
|
||||
TOY_TOKEN_OPERATOR_COMPARE_EQUAL, // ==
|
||||
TOY_TOKEN_OPERATOR_COMPARE_NOT, // !=
|
||||
TOY_TOKEN_OPERATOR_COMPARE_LESS, // <
|
||||
TOY_TOKEN_OPERATOR_COMPARE_LESS_EQUAL, // <=
|
||||
TOY_TOKEN_OPERATOR_COMPARE_GREATER, // >
|
||||
TOY_TOKEN_OPERATOR_COMPARE_GREATER_EQUAL, // >=
|
||||
|
||||
//structural operators
|
||||
TOY_TOKEN_OPERATOR_PAREN_LEFT, // (
|
||||
TOY_TOKEN_OPERATOR_PAREN_RIGHT, // )
|
||||
TOY_TOKEN_OPERATOR_BRACKET_LEFT, // [
|
||||
TOY_TOKEN_OPERATOR_BRACKET_RIGHT, // ]
|
||||
TOY_TOKEN_OPERATOR_BRACE_LEFT, // {
|
||||
TOY_TOKEN_OPERATOR_BRACE_RIGHT, // }
|
||||
|
||||
//other operators
|
||||
TOY_TOKEN_OPERATOR_AND, // &&
|
||||
TOY_TOKEN_OPERATOR_OR, // ||
|
||||
TOY_TOKEN_OPERATOR_NEGATE, // ! (prefix)
|
||||
TOY_TOKEN_OPERATOR_QUESTION, // ?
|
||||
TOY_TOKEN_OPERATOR_COLON, // :
|
||||
|
||||
TOY_TOKEN_OPERATOR_SEMICOLON, // ;
|
||||
TOY_TOKEN_OPERATOR_COMMA, // ,
|
||||
|
||||
TOY_TOKEN_OPERATOR_DOT, // .
|
||||
TOY_TOKEN_OPERATOR_CONCAT, // ..
|
||||
TOY_TOKEN_OPERATOR_REST, // ...
|
||||
|
||||
//unused operators
|
||||
TOY_TOKEN_OPERATOR_AMPERSAND, // &
|
||||
TOY_TOKEN_OPERATOR_PIPE, // |
|
||||
|
||||
//meta tokens
|
||||
TOY_TOKEN_ERROR, //meta
|
||||
TOY_TOKEN_EOF, //meta
|
||||
} Toy_TokenType;
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
#include "toy_value.h"
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_string.h"
|
||||
#include "toy_array.h"
|
||||
#include "toy_table.h"
|
||||
#include "toy_function.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//utils
|
||||
static unsigned int hashUInt(unsigned int x) {
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = ((x >> 16) ^ x) * 0x45d9f3b;
|
||||
x = (x >> 16) ^ x;
|
||||
return x;
|
||||
}
|
||||
|
||||
#define MAYBE_UNWRAP(value) if (TOY_VALUE_IS_REFERENCE(value)) { value = Toy_unwrapValue(value); }
|
||||
|
||||
//exposed functions
|
||||
Toy_Value Toy_unwrapValue(Toy_Value value) {
|
||||
//turns out C doesn't have actual references
|
||||
if (value.type == TOY_VALUE_REFERENCE) {
|
||||
return Toy_unwrapValue(*(value.as.reference));
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Toy_hashValue(Toy_Value value) {
|
||||
MAYBE_UNWRAP(value);
|
||||
|
||||
switch(value.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
return 0;
|
||||
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
return value.as.boolean ? 1 : 0;
|
||||
|
||||
case TOY_VALUE_INTEGER:
|
||||
return hashUInt((unsigned int)value.as.integer);
|
||||
|
||||
case TOY_VALUE_FLOAT:
|
||||
return hashUInt( *((unsigned int*)(&value.as.number)) );
|
||||
|
||||
case TOY_VALUE_STRING:
|
||||
return Toy_hashString(value.as.string);
|
||||
|
||||
case TOY_VALUE_ARRAY: {
|
||||
//since array internals can change, recalc the hash each time it's needed
|
||||
Toy_Array* ptr = value.as.array;
|
||||
unsigned int hash = 0;
|
||||
|
||||
for (unsigned int i = 0; i < ptr->count; i++) {
|
||||
hash ^= Toy_hashValue(ptr->data[i]);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
case TOY_VALUE_TABLE: {
|
||||
//since table internals can change, recalc the hash each time it's needed
|
||||
Toy_Table* ptr = value.as.table;
|
||||
unsigned int hash = 0;
|
||||
|
||||
for (unsigned int i = 0; i < ptr->capacity; i++) {
|
||||
if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) {
|
||||
hash ^= Toy_hashValue(ptr->data[i].key);
|
||||
hash ^= Toy_hashValue(ptr->data[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
case TOY_VALUE_OPAQUE:
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't hash a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(value.type));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Toy_Value Toy_copyValue(Toy_Bucket** bucketHandle, Toy_Value value) {
|
||||
MAYBE_UNWRAP(value);
|
||||
|
||||
switch(value.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
case TOY_VALUE_INTEGER:
|
||||
case TOY_VALUE_FLOAT:
|
||||
return value;
|
||||
|
||||
case TOY_VALUE_STRING: {
|
||||
return TOY_VALUE_FROM_STRING(Toy_copyString(value.as.string));
|
||||
}
|
||||
|
||||
case TOY_VALUE_ARRAY: {
|
||||
//arrays probably won't get copied much
|
||||
Toy_Array* ptr = value.as.array;
|
||||
Toy_Array* result = Toy_resizeArray(NULL, ptr->capacity);
|
||||
|
||||
for (unsigned int i = 0; i < ptr->count; i++) {
|
||||
result->data[i] = Toy_copyValue(bucketHandle, ptr->data[i]);
|
||||
}
|
||||
|
||||
result->capacity = ptr->capacity;
|
||||
result->count = ptr->count;
|
||||
|
||||
return TOY_VALUE_FROM_ARRAY(result);
|
||||
}
|
||||
|
||||
case TOY_VALUE_TABLE: {
|
||||
//tables probably won't get copied much
|
||||
Toy_Table* ptr = value.as.table;
|
||||
Toy_Table* result = Toy_allocateTable(ptr->capacity);
|
||||
|
||||
for (unsigned int i = 0; i < ptr->capacity; i++) {
|
||||
if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) {
|
||||
result->data[i].key = Toy_copyValue(bucketHandle, ptr->data[i].key);
|
||||
result->data[i].value = Toy_copyValue(bucketHandle, ptr->data[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
result->capacity = ptr->capacity;
|
||||
result->count = ptr->count;
|
||||
|
||||
return TOY_VALUE_FROM_TABLE(result);
|
||||
}
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
return TOY_VALUE_FROM_FUNCTION(Toy_copyFunction(bucketHandle, value.as.function));
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
//copy opaques by value
|
||||
return value;
|
||||
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't copy a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(value.type));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
//dummy return
|
||||
return TOY_VALUE_FROM_NULL();
|
||||
}
|
||||
|
||||
void Toy_freeValue(Toy_Value value) {
|
||||
switch(value.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
case TOY_VALUE_INTEGER:
|
||||
case TOY_VALUE_FLOAT:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_STRING: {
|
||||
Toy_freeString(value.as.string);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_ARRAY:
|
||||
Toy_resizeArray(value.as.array, 0);
|
||||
break;
|
||||
|
||||
case TOY_VALUE_TABLE:
|
||||
Toy_freeTable(value.as.table);
|
||||
break;
|
||||
|
||||
case TOY_VALUE_REFERENCE:
|
||||
//don't free references
|
||||
return;
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
Toy_freeFunction(value.as.function);
|
||||
return;
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
//no-op
|
||||
return;
|
||||
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't free a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(value.type));
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
bool Toy_checkValueIsTruthy(Toy_Value value) {
|
||||
MAYBE_UNWRAP(value);
|
||||
|
||||
//null is an error
|
||||
if (value.type == TOY_VALUE_NULL) {
|
||||
Toy_error("'null' is neither true nor false");
|
||||
return false;
|
||||
}
|
||||
|
||||
//only 'false' is falsy
|
||||
if (value.type == TOY_VALUE_BOOLEAN) {
|
||||
return value.as.boolean;
|
||||
}
|
||||
|
||||
if (value.type == TOY_VALUE_INTEGER) {
|
||||
return value.as.integer != 0;
|
||||
}
|
||||
|
||||
if (value.type == TOY_VALUE_FLOAT) {
|
||||
return value.as.number != 0.0f;
|
||||
}
|
||||
|
||||
//anything else is truthy
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right) {
|
||||
MAYBE_UNWRAP(left);
|
||||
MAYBE_UNWRAP(right);
|
||||
|
||||
switch(left.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
return right.type == TOY_VALUE_NULL;
|
||||
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
return right.type == TOY_VALUE_BOOLEAN && left.as.boolean == right.as.boolean;
|
||||
|
||||
case TOY_VALUE_INTEGER:
|
||||
if (right.type == TOY_VALUE_INTEGER) {
|
||||
return left.as.integer == right.as.integer;
|
||||
}
|
||||
else if (right.type == TOY_VALUE_FLOAT) {
|
||||
return left.as.integer == right.as.number;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FLOAT:
|
||||
if (right.type == TOY_VALUE_INTEGER) {
|
||||
return left.as.number == right.as.integer;
|
||||
}
|
||||
else if (right.type == TOY_VALUE_FLOAT) {
|
||||
return left.as.number == right.as.number;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_STRING:
|
||||
if (right.type == TOY_VALUE_STRING) {
|
||||
return Toy_compareStrings(left.as.string, right.as.string) == 0;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_ARRAY: {
|
||||
if (right.type == TOY_VALUE_ARRAY) {
|
||||
Toy_Array* leftArray = left.as.array;
|
||||
Toy_Array* rightArray = right.as.array;
|
||||
|
||||
//different lengths is an easy way to check
|
||||
if (leftArray->count != rightArray->count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < leftArray->count; i++) {
|
||||
//any mismatch is an easy difference
|
||||
if (Toy_checkValuesAreEqual(leftArray->data[i], rightArray->data[i]) != true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
//finally
|
||||
return true;
|
||||
}
|
||||
|
||||
case TOY_VALUE_TABLE: {
|
||||
if (right.type == TOY_VALUE_TABLE) {
|
||||
Toy_Table* leftTable = left.as.table;
|
||||
Toy_Table* rightTable = right.as.table;
|
||||
|
||||
//different counts
|
||||
if (leftTable->count != rightTable->count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < leftTable->capacity; i++) {
|
||||
Toy_TableEntry* entry = leftTable->data + i;
|
||||
|
||||
if (TOY_VALUE_IS_NULL(entry->key) != true) {
|
||||
//any mismatch is an easy difference
|
||||
Toy_Value rightValue = Toy_lookupTable(&rightTable, entry->key);
|
||||
|
||||
if (TOY_VALUE_IS_NULL(rightValue) || Toy_checkValuesAreEqual(entry->value, rightValue) != true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
//finally
|
||||
return true;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
if (right.type == TOY_VALUE_FUNCTION && left.as.function->type == right.as.function->type) {
|
||||
if (left.as.function->type == TOY_FUNCTION_CUSTOM) {
|
||||
return left.as.function->bytecode.code == right.as.function->bytecode.code;
|
||||
}
|
||||
else {
|
||||
return left.as.function->native.callback == right.as.function->native.callback;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't find equality for a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(left.type));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right) {
|
||||
MAYBE_UNWRAP(left);
|
||||
MAYBE_UNWRAP(right);
|
||||
|
||||
//NOTE: "equal" and "comparable" are different - equal means they're identical, comparable is only possible for certain types
|
||||
switch(left.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
return false;
|
||||
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
return right.type == TOY_VALUE_BOOLEAN;
|
||||
|
||||
case TOY_VALUE_INTEGER:
|
||||
case TOY_VALUE_FLOAT:
|
||||
return right.type == TOY_VALUE_INTEGER || right.type == TOY_VALUE_FLOAT;
|
||||
|
||||
case TOY_VALUE_STRING:
|
||||
return right.type == TOY_VALUE_STRING;
|
||||
|
||||
case TOY_VALUE_ARRAY:
|
||||
//nothing is comparable with an array
|
||||
return false;
|
||||
|
||||
case TOY_VALUE_TABLE:
|
||||
//nothing is comparable with a table
|
||||
return false;
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
//nothing is comparable with a function
|
||||
return false;
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
//nothing compares with an opaque
|
||||
return false;
|
||||
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown types in value comparison check, exiting\n" TOY_CC_RESET);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Toy_compareValues(Toy_Value left, Toy_Value right) {
|
||||
MAYBE_UNWRAP(left);
|
||||
MAYBE_UNWRAP(right);
|
||||
|
||||
//comparison means there's a difference in value, with some kind of quantity - so null, bool, etc. aren't comparable
|
||||
switch(left.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_INTEGER:
|
||||
if (right.type == TOY_VALUE_INTEGER) {
|
||||
return left.as.integer - right.as.integer;
|
||||
}
|
||||
else if (right.type == TOY_VALUE_FLOAT) {
|
||||
return left.as.integer - right.as.number;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FLOAT:
|
||||
if (right.type == TOY_VALUE_INTEGER) {
|
||||
return left.as.number - right.as.integer;
|
||||
}
|
||||
else if (right.type == TOY_VALUE_FLOAT) {
|
||||
return left.as.number - right.as.number;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_VALUE_STRING:
|
||||
if (right.type == TOY_VALUE_STRING) {
|
||||
return Toy_compareStrings(left.as.string, right.as.string);
|
||||
}
|
||||
|
||||
case TOY_VALUE_ARRAY:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_TABLE:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
break;
|
||||
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't compare with a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(left.type));
|
||||
exit(-1);
|
||||
|
||||
return ~0;
|
||||
}
|
||||
|
||||
Toy_String* Toy_stringifyValue(Toy_Bucket** bucketHandle, Toy_Value value) {
|
||||
MAYBE_UNWRAP(value);
|
||||
|
||||
switch(value.type) {
|
||||
case TOY_VALUE_NULL:
|
||||
return Toy_createStringLength(bucketHandle, "<null>", 6);
|
||||
|
||||
case TOY_VALUE_BOOLEAN:
|
||||
if (value.as.boolean) {
|
||||
return Toy_createStringLength(bucketHandle, "<true>", 6);
|
||||
}
|
||||
else {
|
||||
return Toy_createStringLength(bucketHandle, "<false>", 7);
|
||||
}
|
||||
|
||||
case TOY_VALUE_INTEGER: {
|
||||
char buffer[16];
|
||||
sprintf(buffer, "%d", value.as.integer);
|
||||
return Toy_createStringLength(bucketHandle, buffer, strlen(buffer));
|
||||
}
|
||||
|
||||
case TOY_VALUE_FLOAT: {
|
||||
//using printf
|
||||
char buffer[16];
|
||||
sprintf(buffer, "%f", value.as.number);
|
||||
|
||||
//BUGFIX: printf format specificer '%f' will set the precision to 6 decimal places, which means there's trailing zeroes
|
||||
unsigned int length = strlen(buffer);
|
||||
|
||||
//find the decimal, if it exists
|
||||
unsigned int decimal = 0;
|
||||
while (decimal != length && buffer[decimal] != '.' && buffer[decimal] != ',') decimal++; //'.' and ',' supports more locales
|
||||
|
||||
//locales are hard, sorry!
|
||||
if (decimal != length && buffer[decimal] == ',') buffer[decimal] = '.';
|
||||
|
||||
//wipe the trailing zeros
|
||||
while(decimal != length && buffer[length-1] == '0') buffer[--length] = '\0';
|
||||
|
||||
return Toy_createStringLength(bucketHandle, buffer, length);
|
||||
}
|
||||
|
||||
case TOY_VALUE_STRING:
|
||||
return Toy_copyString(value.as.string);
|
||||
|
||||
case TOY_VALUE_ARRAY: {
|
||||
Toy_Array* ptr = value.as.array;
|
||||
|
||||
//if array is empty, skip below
|
||||
if (ptr->count == 0) {
|
||||
Toy_String* empty = Toy_createStringLength(bucketHandle, "[]", 2);
|
||||
return empty;
|
||||
}
|
||||
|
||||
Toy_String* open = Toy_createStringLength(bucketHandle, "[", 1);
|
||||
Toy_String* close = Toy_createStringLength(bucketHandle, "]", 1);
|
||||
Toy_String* comma = Toy_createStringLength(bucketHandle, ",", 1); //reusable
|
||||
Toy_String* quote = Toy_createStringLength(bucketHandle, "\"", 1); //reusable
|
||||
bool needsComma = false;
|
||||
|
||||
Toy_String* string = open;
|
||||
|
||||
for (unsigned int i = 0; i < ptr->count; i++) {
|
||||
if (needsComma) {
|
||||
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, comma); //increment ref
|
||||
Toy_freeString(string); //decrement ref
|
||||
string = tmp;
|
||||
}
|
||||
|
||||
//get the element
|
||||
Toy_String* element = Toy_stringifyValue(bucketHandle, ptr->data[i]);
|
||||
|
||||
//put quotemarks around internal string elements
|
||||
if (TOY_VALUE_IS_STRING(ptr->data[i])) {
|
||||
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, element);
|
||||
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
|
||||
|
||||
Toy_freeString(element);
|
||||
Toy_freeString(tmpA);
|
||||
element = tmpB;
|
||||
}
|
||||
|
||||
//append each element
|
||||
Toy_String* final = Toy_concatStrings(bucketHandle, string, element);
|
||||
|
||||
Toy_freeString(element);
|
||||
Toy_freeString(string);
|
||||
|
||||
string = final;
|
||||
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
//closing bracket
|
||||
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, close);
|
||||
Toy_freeString(string);
|
||||
string = tmp;
|
||||
|
||||
//clean up
|
||||
Toy_freeString(open);
|
||||
Toy_freeString(close);
|
||||
Toy_freeString(comma);
|
||||
Toy_freeString(quote);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
case TOY_VALUE_TABLE: {
|
||||
Toy_Table* ptr = value.as.table;
|
||||
|
||||
//if table is empty, skip below
|
||||
if (ptr->count == 0) {
|
||||
Toy_String* empty = Toy_createStringLength(bucketHandle, "[:]", 3);
|
||||
return empty;
|
||||
}
|
||||
|
||||
Toy_String* open = Toy_createStringLength(bucketHandle, "[", 1);
|
||||
Toy_String* close = Toy_createStringLength(bucketHandle, "]", 1);
|
||||
Toy_String* colon = Toy_createStringLength(bucketHandle, ":", 1); //reusable
|
||||
Toy_String* comma = Toy_createStringLength(bucketHandle, ",", 1); //reusable
|
||||
Toy_String* quote = Toy_createStringLength(bucketHandle, "\"", 1); //reusable
|
||||
bool needsComma = false;
|
||||
|
||||
Toy_String* string = open;
|
||||
|
||||
for (unsigned int i = 0; i < ptr->capacity; i++) {
|
||||
if (TOY_VALUE_IS_NULL(ptr->data[i].key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (needsComma) {
|
||||
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, comma); //increment ref
|
||||
Toy_freeString(string); //decrement ref
|
||||
string = tmp;
|
||||
}
|
||||
|
||||
//make the element pair
|
||||
Toy_String* k = Toy_stringifyValue(bucketHandle, ptr->data[i].key);
|
||||
Toy_String* v = Toy_stringifyValue(bucketHandle, ptr->data[i].value);
|
||||
|
||||
//put quotemarks around internal string elements (key)
|
||||
if (TOY_VALUE_IS_STRING(ptr->data[i].key)) {
|
||||
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, k);
|
||||
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
|
||||
|
||||
Toy_freeString(k);
|
||||
Toy_freeString(tmpA);
|
||||
k = tmpB;
|
||||
}
|
||||
|
||||
//put quotemarks around internal string elements (value)
|
||||
if (TOY_VALUE_IS_STRING(ptr->data[i].value)) {
|
||||
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, v);
|
||||
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
|
||||
|
||||
Toy_freeString(v);
|
||||
Toy_freeString(tmpA);
|
||||
v = tmpB;
|
||||
}
|
||||
|
||||
//stick the colon between, make the pair
|
||||
Toy_String* c = Toy_concatStrings(bucketHandle, k, colon);
|
||||
Toy_String* pair = Toy_concatStrings(bucketHandle, c, v);
|
||||
|
||||
//append the element pair
|
||||
Toy_String* final = Toy_concatStrings(bucketHandle, string, pair);
|
||||
|
||||
//do a bunch of freeing so the internal refCounts stay balanced
|
||||
Toy_freeString(k);
|
||||
Toy_freeString(v);
|
||||
Toy_freeString(c);
|
||||
Toy_freeString(pair);
|
||||
Toy_freeString(string);
|
||||
|
||||
//finally
|
||||
string = final;
|
||||
|
||||
//if there's more elements
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
//closing bracket
|
||||
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, close);
|
||||
Toy_freeString(string);
|
||||
string = tmp;
|
||||
|
||||
//clean up
|
||||
Toy_freeString(open);
|
||||
Toy_freeString(close);
|
||||
Toy_freeString(colon);
|
||||
Toy_freeString(comma);
|
||||
Toy_freeString(quote);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
case TOY_VALUE_FUNCTION:
|
||||
//dummy
|
||||
return Toy_createStringLength(bucketHandle, "<fn>", 4);
|
||||
|
||||
case TOY_VALUE_OPAQUE:
|
||||
//dummy
|
||||
return Toy_createStringLength(bucketHandle, "<opaque>", 8);
|
||||
|
||||
case TOY_VALUE_ANY:
|
||||
case TOY_VALUE_REFERENCE:
|
||||
case TOY_VALUE_UNKNOWN:
|
||||
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't stringify a given value type '%s', exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(value.type));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* Toy_getValueTypeAsCString(Toy_ValueType type) {
|
||||
switch (type) {
|
||||
case TOY_VALUE_NULL: return "Null";
|
||||
case TOY_VALUE_BOOLEAN: return "Bool";
|
||||
case TOY_VALUE_INTEGER: return "Int";
|
||||
case TOY_VALUE_FLOAT: return "Float";
|
||||
case TOY_VALUE_STRING: return "String";
|
||||
case TOY_VALUE_ARRAY: return "Array";
|
||||
case TOY_VALUE_TABLE: return "Table";
|
||||
case TOY_VALUE_FUNCTION: return "Function";
|
||||
case TOY_VALUE_OPAQUE: return "Opaque";
|
||||
case TOY_VALUE_ANY: return "Any";
|
||||
case TOY_VALUE_REFERENCE: return "Reference";
|
||||
case TOY_VALUE_UNKNOWN: return "Unknown";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
#include "toy_print.h"
|
||||
#include "toy_bucket.h"
|
||||
|
||||
//forward declarations
|
||||
union Toy_String_t;
|
||||
struct Toy_Array;
|
||||
struct Toy_Table;
|
||||
union Toy_Function_t;
|
||||
|
||||
typedef enum Toy_ValueType {
|
||||
TOY_VALUE_NULL,
|
||||
TOY_VALUE_BOOLEAN,
|
||||
TOY_VALUE_INTEGER,
|
||||
TOY_VALUE_FLOAT,
|
||||
TOY_VALUE_STRING,
|
||||
TOY_VALUE_ARRAY,
|
||||
TOY_VALUE_TABLE,
|
||||
TOY_VALUE_FUNCTION,
|
||||
TOY_VALUE_OPAQUE,
|
||||
TOY_VALUE_ANY,
|
||||
|
||||
TOY_VALUE_REFERENCE, //not a value itself, but pointing to one
|
||||
TOY_VALUE_UNKNOWN, //The correct type is unknown, but will be determined later
|
||||
} Toy_ValueType;
|
||||
|
||||
//8 bytes in size
|
||||
typedef struct Toy_Value { //32 | 64 BITNESS
|
||||
union {
|
||||
bool boolean; //1 | 1
|
||||
int integer; //4 | 4
|
||||
float number; //4 | 4
|
||||
union Toy_String_t* string; //4 | 8
|
||||
struct Toy_Array* array; //4 | 8
|
||||
struct Toy_Table* table; //4 | 8
|
||||
union Toy_Function_t* function;//4 | 8
|
||||
struct Toy_Value* reference; //4 | 8
|
||||
void* opaque; //4 | 8
|
||||
} as; //4 | 8
|
||||
|
||||
Toy_ValueType type; //4 | 4
|
||||
} Toy_Value; //8 | 16
|
||||
|
||||
#define TOY_VALUE_IS_NULL(value) ((value).type == TOY_VALUE_NULL)
|
||||
#define TOY_VALUE_IS_BOOLEAN(value) ((value).type == TOY_VALUE_BOOLEAN)
|
||||
#define TOY_VALUE_IS_INTEGER(value) ((value).type == TOY_VALUE_INTEGER)
|
||||
#define TOY_VALUE_IS_FLOAT(value) ((value).type == TOY_VALUE_FLOAT)
|
||||
#define TOY_VALUE_IS_STRING(value) ((value).type == TOY_VALUE_STRING)
|
||||
#define TOY_VALUE_IS_ARRAY(value) ((value).type == TOY_VALUE_ARRAY || (TOY_VALUE_IS_REFERENCE(value) && Toy_unwrapValue(value).type == TOY_VALUE_ARRAY))
|
||||
#define TOY_VALUE_IS_TABLE(value) ((value).type == TOY_VALUE_TABLE || (TOY_VALUE_IS_REFERENCE(value) && Toy_unwrapValue(value).type == TOY_VALUE_TABLE))
|
||||
#define TOY_VALUE_IS_FUNCTION(value) ((value).type == TOY_VALUE_FUNCTION || (TOY_VALUE_IS_REFERENCE(value) && Toy_unwrapValue(value).type == TOY_VALUE_FUNCTION))
|
||||
#define TOY_VALUE_IS_OPAQUE(value) ((value).type == TOY_VALUE_OPAQUE || (TOY_VALUE_IS_REFERENCE(value) && Toy_unwrapValue(value).type == TOY_VALUE_OPAQUE))
|
||||
#define TOY_VALUE_IS_REFERENCE(value) ((value).type == TOY_VALUE_REFERENCE)
|
||||
|
||||
#define TOY_VALUE_AS_BOOLEAN(value) ((value).as.boolean)
|
||||
#define TOY_VALUE_AS_INTEGER(value) ((value).as.integer)
|
||||
#define TOY_VALUE_AS_FLOAT(value) ((value).as.number)
|
||||
#define TOY_VALUE_AS_STRING(value) ((value).as.string)
|
||||
#define TOY_VALUE_AS_ARRAY(value) ((TOY_VALUE_IS_REFERENCE(value) ? Toy_unwrapValue(value) : value).as.array)
|
||||
#define TOY_VALUE_AS_TABLE(value) ((TOY_VALUE_IS_REFERENCE(value) ? Toy_unwrapValue(value) : value).as.table)
|
||||
#define TOY_VALUE_AS_FUNCTION(value) ((TOY_VALUE_IS_REFERENCE(value) ? Toy_unwrapValue(value) : value).as.function)
|
||||
#define TOY_VALUE_AS_OPAQUE(value) ((TOY_VALUE_IS_REFERENCE(value) ? Toy_unwrapValue(value) : value).as.opaque)
|
||||
|
||||
#define TOY_VALUE_FROM_NULL() ((Toy_Value){{ .integer = 0 }, TOY_VALUE_NULL})
|
||||
#define TOY_VALUE_FROM_BOOLEAN(value) ((Toy_Value){{ .boolean = value }, TOY_VALUE_BOOLEAN})
|
||||
#define TOY_VALUE_FROM_INTEGER(value) ((Toy_Value){{ .integer = value }, TOY_VALUE_INTEGER})
|
||||
#define TOY_VALUE_FROM_FLOAT(value) ((Toy_Value){{ .number = value }, TOY_VALUE_FLOAT})
|
||||
#define TOY_VALUE_FROM_STRING(value) ((Toy_Value){{ .string = value }, TOY_VALUE_STRING})
|
||||
#define TOY_VALUE_FROM_ARRAY(value) ((Toy_Value){{ .array = value }, TOY_VALUE_ARRAY})
|
||||
#define TOY_VALUE_FROM_TABLE(value) ((Toy_Value){{ .table = value }, TOY_VALUE_TABLE})
|
||||
#define TOY_VALUE_FROM_FUNCTION(value) ((Toy_Value){{ .function = value }, TOY_VALUE_FUNCTION})
|
||||
|
||||
#define TOY_OPAQUE_FROM_POINTER(ptr) ((Toy_Value){{ .opaque = ptr }, TOY_VALUE_OPAQUE})
|
||||
#define TOY_REFERENCE_FROM_POINTER(ptr) ((Toy_Value){{ .reference = ptr }, TOY_VALUE_REFERENCE})
|
||||
|
||||
//utilities
|
||||
TOY_API Toy_Value Toy_unwrapValue(Toy_Value value);
|
||||
TOY_API unsigned int Toy_hashValue(Toy_Value value);
|
||||
|
||||
TOY_API Toy_Value Toy_copyValue(struct Toy_Bucket** bucketHandle, Toy_Value value);
|
||||
TOY_API void Toy_freeValue(Toy_Value value);
|
||||
|
||||
TOY_API bool Toy_checkValueIsTruthy(Toy_Value value);
|
||||
TOY_API bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right);
|
||||
TOY_API bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right);
|
||||
TOY_API int Toy_compareValues(Toy_Value left, Toy_Value right);
|
||||
|
||||
//convert the value to a string - values that *are* strings are simply copied
|
||||
TOY_API union Toy_String_t* Toy_stringifyValue(struct Toy_Bucket** bucketHandle, Toy_Value value);
|
||||
|
||||
//for error messages
|
||||
TOY_API const char* Toy_getValueTypeAsCString(Toy_ValueType type);
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_common.h"
|
||||
|
||||
#include "toy_bucket.h"
|
||||
#include "toy_scope.h"
|
||||
|
||||
#include "toy_value.h"
|
||||
#include "toy_string.h"
|
||||
#include "toy_stack.h"
|
||||
#include "toy_array.h"
|
||||
#include "toy_table.h"
|
||||
#include "toy_function.h"
|
||||
|
||||
typedef struct Toy_VM {
|
||||
//raw instructions to be executed
|
||||
unsigned char* code;
|
||||
|
||||
//metadata
|
||||
unsigned int jumpsCount;
|
||||
unsigned int paramCount;
|
||||
unsigned int dataCount;
|
||||
unsigned int subsCount;
|
||||
|
||||
unsigned int codeAddr;
|
||||
unsigned int jumpsAddr;
|
||||
unsigned int paramAddr;
|
||||
unsigned int dataAddr;
|
||||
unsigned int subsAddr;
|
||||
|
||||
//execution utils
|
||||
unsigned int programCounter;
|
||||
|
||||
//scope - block-level key/value pairs
|
||||
Toy_Scope* scope;
|
||||
|
||||
//stack - immediate-level values only
|
||||
Toy_Stack* stack;
|
||||
|
||||
//easy access to memory
|
||||
Toy_Bucket* memoryBucket;
|
||||
Toy_Bucket** parentBucketHandle;
|
||||
} Toy_VM;
|
||||
|
||||
TOY_API void Toy_resetVM(Toy_VM* vm, bool preserveScope, bool preserveStack);
|
||||
|
||||
TOY_API void Toy_initVM(Toy_VM* vm); //creates memory
|
||||
TOY_API void Toy_inheritVM(Toy_VM* parentVM, Toy_VM* subVM); //inherits scope bucket
|
||||
|
||||
void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, Toy_Scope* parentScope);
|
||||
TOY_API unsigned int Toy_runVM(Toy_VM* vm);
|
||||
TOY_API void Toy_freeVM(Toy_VM* vm);
|
||||
|
||||
TOY_API Toy_Value Toy_getReturnValueFromVM(Toy_VM* parentVM, Toy_VM* subVM);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Tests
|
||||
|
||||
* benchmarks - for comparint Toy's performance against other languages
|
||||
* scripts - for checking Toy's implementation works as expected
|
||||
* units - for checking the functionality of a single unit of code
|
||||
@@ -0,0 +1,18 @@
|
||||
for (let counter = 1; counter <= 10000; counter++) {
|
||||
let result = "";
|
||||
|
||||
if (counter % 3 == 0) {
|
||||
result += "fizz";
|
||||
}
|
||||
|
||||
if (counter % 5 == 0) {
|
||||
result += "buzz";
|
||||
}
|
||||
|
||||
if (result != "") {
|
||||
console.log(result);
|
||||
}
|
||||
else {
|
||||
console.log(counter);
|
||||
}
|
||||
}
|
||||