Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b78886e386 | |||
| bcfb4f04c3 | |||
| 4442890dae | |||
| 2eb6820da8 | |||
| 4282268f3d | |||
| 101833934b | |||
| f0e8bfeb02 | |||
| 575003433f | |||
| 119cd70f6c | |||
| f974308e9f | |||
| 67e095aedf | |||
| 820c7a4c88 | |||
| e0aaa3347f | |||
| 01a6ce1d60 | |||
| 31800175be | |||
| 8cbe4da825 | |||
| 9d4ce9b126 | |||
| 8e7eeb3b52 | |||
| c2f051c4fb | |||
| e8b27d61e3 | |||
| cc91a34121 | |||
| 2e3a33baff | |||
| 41ef105c9e | |||
| 6421e4ccd1 | |||
| d083b91198 | |||
| 4697933790 | |||
| 5751be23a0 | |||
| 4f3e3ff833 | |||
| 79eeb6113c | |||
| 8bcab9fc34 | |||
| 68a5a4eb56 | |||
| 4c7bc04196 | |||
| 39b6b831f9 | |||
| 690bca0667 | |||
| d6565febce | |||
| 74309439e7 | |||
| bef23c0ed4 | |||
| 40357d52a3 | |||
| 540dcd03cc | |||
| 29f5de650f | |||
| 3167555bc1 | |||
| 4465bfe40e | |||
| 84f729def8 | |||
| e58aa10d42 | |||
| f4bfb73f47 | |||
| 7a46c9fc9a | |||
| 744b74aed2 | |||
| 5e98df9846 | |||
| e935061b0e | |||
| 13bfaaf91e | |||
| 2a5ffe6eb6 | |||
| 0e4347c103 | |||
| 2aa46faa81 | |||
| 74606d3509 | |||
| 243e4f2929 | |||
| a8059fa3fd | |||
| bbbdf8a3b7 | |||
| 1fdfd0ad26 | |||
| 75781efb3a | |||
| 06599dbaee | |||
| 4eba9b0e03 | |||
| bfff0e54c7 | |||
| 3e52ae7d61 | |||
| 50db8e5bf6 | |||
| 3d317ff28e | |||
| 1ac49def37 | |||
| 2c594f726d | |||
| 047435a7f5 | |||
| 32c92a6cd1 | |||
| 423816a068 | |||
| f5f5e7d7d8 | |||
| 3c6e791dc0 | |||
| 923de375b3 | |||
| 9ae515d181 | |||
| 3518896a1f | |||
| e57d8ed209 | |||
| 9e30b99547 | |||
| a70149440a | |||
| 48dd1f9411 | |||
| c13409a407 | |||
| 0b885d0a30 | |||
| 329d085beb | |||
| 91c960cd70 | |||
| 7638a1f428 | |||
| 8d37079df2 | |||
| 26a7cd116a | |||
| bf890ac845 | |||
| e470dc21ff | |||
| c58f8678fc | |||
| 42d5324ed1 | |||
| d3ce0c38fa | |||
| 7cda6dfe6a | |||
| c4cdf407c7 | |||
| e1c825dd24 | |||
| 410af82bfe | |||
| 08de91e9ce | |||
| 1a945ad9c3 | |||
| 1cfcf7de5a | |||
| c326ae465d | |||
| 3cfffada97 | |||
| 663d080dc6 | |||
| 6368b35c1f | |||
| 6e895c6866 | |||
| 535c91c459 | |||
| 5d5565117b | |||
| a37e6df863 | |||
| e1f3e2791a | |||
| f686693550 | |||
| 537ec5be24 | |||
| f15e717500 | |||
| bccdab1a32 | |||
| 3f186c2422 | |||
| 4d68567283 | |||
| e4620684b3 | |||
| 227d7a33ff | |||
| f3a6c570e2 | |||
| 069e3d89e6 | |||
| f6f8dd2a34 | |||
| 805b6785b2 | |||
| b2dea4bac5 | |||
| a13a449480 | |||
| 89dd090d5d | |||
| b101b5a216 | |||
| 732baec305 | |||
| d91b6704a4 | |||
| 7012849c87 | |||
| 5978b73619 | |||
| 33efeb6eb4 | |||
| cc9c219588 | |||
| 6dfb239106 | |||
| 3a583fa6dc | |||
| 5dfbb8b973 | |||
| 6474dfd4a3 | |||
| beaf1bc343 | |||
| db868530c0 | |||
| b4c18cac12 | |||
| 3219540901 | |||
| b0f2767a8f | |||
| c996032522 | |||
| 42835ce4d7 | |||
| 32d1f06700 | |||
| 66e2d5d9f1 | |||
| 248b818b4f | |||
| 05981491e7 | |||
| 310bab36d1 | |||
| 757d37e8cc | |||
| 85048ea208 | |||
| 5ae32c5485 | |||
| c310235247 | |||
| f1cb3ce352 | |||
| bdab49144b | |||
| af14e39792 | |||
| cb9485677b | |||
| aca61ae944 | |||
| e96f2fe130 | |||
| c4e49c4679 | |||
| fb632b7763 | |||
| 84911dbb50 | |||
| 9e09279b87 | |||
| b34f84cc34 | |||
| e77cdc54d2 | |||
| e3e4750726 | |||
| 4981d37a54 | |||
| cf34445667 | |||
| 5ec181179c | |||
| 4653535c16 | |||
| c2073a49d5 | |||
| e23de6d51d | |||
| 727faad664 | |||
| 338f71abb0 | |||
| 9e4ea106ec | |||
| 17c43c1615 | |||
| a913b3ef0b | |||
| f60bf40a7f | |||
| 4a2c096ca4 | |||
| b24010de8f | |||
| 0c76da62f8 | |||
| f4d72b2cc9 | |||
| c216ca2998 | |||
| d2ab881166 | |||
| 6e8fbe90ea | |||
| 8a2720554b | |||
| 6e463682f8 | |||
| bb48bd9c14 | |||
| 0b8d6c1592 | |||
| e3eb38b827 | |||
| 3f6540d6e5 | |||
| 7d4f90fc24 | |||
| 42eac86c88 | |||
| 460e2f6ffb | |||
| 204a21f05d | |||
| d7a6ea1933 | |||
| 1b1efff798 | |||
| 08805d7f02 | |||
| 1e76a081bf | |||
| a7440f0a29 | |||
| d8a0936b1d |
@@ -1,5 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
patreon: krgamestudios
|
|
||||||
ko_fi: krgamestudios
|
|
||||||
custom: ["https://www.paypal.com/donate/?hosted_button_id=73Q82T2ZHV8AA"]
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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,46 @@
|
|||||||
|
name: Deploy from the mirror
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Runs on pushes targeting the docs branch
|
||||||
|
push:
|
||||||
|
branches: ["mirror"]
|
||||||
|
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
- name: Build with Jekyll
|
||||||
|
uses: actions/jekyll-build-pages@v1
|
||||||
|
with:
|
||||||
|
source: ./
|
||||||
|
destination: ./_site
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: Send to the pages mirror
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["v1-docs"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Unshallow the actions repo
|
||||||
|
run: git fetch --unshallow origin ${{ github.ref_name }}
|
||||||
|
- name: Making secret key file
|
||||||
|
run: touch ~/mirror_secret
|
||||||
|
- name: Protecting secret key file
|
||||||
|
run: chmod 600 ~/mirror_secret
|
||||||
|
- name: Writing key to secret key file
|
||||||
|
run: echo '${{ secrets.MIRROR_SECRET }}' >> ~/mirror_secret
|
||||||
|
- name: Tweaking git to use the secret key file
|
||||||
|
run: git config --add --local core.sshCommand 'ssh -i ~/mirror_secret'
|
||||||
|
- name: Adding git remote
|
||||||
|
run: git remote add mirror ${{ secrets.MIRROR_ADDRESS }}
|
||||||
|
- name: Renaming git branch
|
||||||
|
run: git branch -m mirror
|
||||||
|
- name: Pushing to git remote
|
||||||
|
run: git push -f mirror
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
This folder is full of development notes, and are probably out of date. Check the actual docs for the correct info.
|
|
||||||
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
===
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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')
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"gruntfuggly.todo-tree"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
No hating on other people, OK?
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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,75 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<image src="toylogo.png" alt="The Toy Logo" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# 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).
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
remote_theme: pages-themes/slate@v0.2.0
|
||||||
|
plugins:
|
||||||
|
- jekyll-remote-theme # add this line to the plugins list if you already have one
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<title>Toy | A Toy Programming Langauge</title>
|
||||||
|
<meta name="description" content="A Toy Programming Language" />
|
||||||
|
<meta name="author" content="Kayne Ruse, Ratstail91" />
|
||||||
|
<meta name="keywords" content="programming,coding" />
|
||||||
|
|
||||||
|
<meta property="og:url" content="https://toylang.com" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="https://toylang.com/repo-preview.png" />
|
||||||
|
<meta property="og:title" content="Toy | A Toy Programming Language" />
|
||||||
|
<meta property="og:description" content="A Toy Programming Language" />
|
||||||
|
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:url" content="https://toylang.com" />
|
||||||
|
<meta property="twitter:type" content="website" />
|
||||||
|
<meta property="twitter:image" content="https://toylang.com/repo-preview.png" />
|
||||||
|
<meta property="twitter:title" content="Toy | A Toy Programming Language" />
|
||||||
|
<meta property="twitter:description" content="A Toy Programming Language" />
|
||||||
|
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-57STKDE8LE"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'G-57STKDE8LE');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- tweak for markdown layouts -->
|
||||||
|
<style>
|
||||||
|
* {tab-size: 4;}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ site.lang | default: "en-US" }}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,maximum-scale=2">
|
||||||
|
<link rel="stylesheet" type="text/css" media="screen" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
|
||||||
|
|
||||||
|
{% include head-custom.html %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- HEADER -->
|
||||||
|
<div id="header_wrap" class="outer">
|
||||||
|
<header class="inner">
|
||||||
|
{% if site.github.is_project_page %}
|
||||||
|
<a id="forkme_banner" href="{{ site.github.repository_url }}">View on GitHub</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a rel="canonical" href="https://toylang.com/"><h1 id="project_title">{{ site.title | default: site.github.repository_name }}</h1></a>
|
||||||
|
<h2 id="project_tagline">{{ site.description | default: site.github.project_tagline }}</h2>
|
||||||
|
|
||||||
|
{% if site.show_downloads %}
|
||||||
|
<section id="downloads">
|
||||||
|
<a class="zip_download_link" href="{{ site.github.zip_url }}">Download this project as a .zip file</a>
|
||||||
|
<a class="tar_download_link" href="{{ site.github.tar_url }}">Download this project as a tar.gz file</a>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MAIN CONTENT -->
|
||||||
|
<div id="main_content_wrap" class="outer">
|
||||||
|
<section id="main_content" class="inner" style="max-width: 840px;">
|
||||||
|
{{ content }}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div id="footer_wrap" class="outer">
|
||||||
|
<footer class="inner">
|
||||||
|
{% if site.github.is_project_page %}
|
||||||
|
<p class="copyright">{{ site.title | default: site.github.repository_name }} maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
<p>Published with <a href="https://pages.github.com">GitHub Pages</a></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#da532c</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# drive_system.h
|
||||||
|
|
||||||
|
When accessing the file system through Toy (such as with the runner library), it's best practice to utilize the drive system - this system (tries to) prevent malicious accessing of files outside the designated folders. It does this by causing an error when a script tries to access a parent directory.
|
||||||
|
|
||||||
|
To use the drive system, first you must designate specific folders which can be accessed, like so:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "drive_system.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
//the drive system uses a LiteralDictionary, which must be initialized with this
|
||||||
|
Toy_initDriveSystem();
|
||||||
|
|
||||||
|
Toy_setDrivePath("scripts", "assets/scripts");
|
||||||
|
Toy_setDrivePath("sprites", "assets/sprites");
|
||||||
|
Toy_setDrivePath("fonts", "assets/fonts");
|
||||||
|
|
||||||
|
//TODO: do you stuff here
|
||||||
|
|
||||||
|
//clean up the drive dictionary when you're done
|
||||||
|
Toy_freeDriveSystem();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This utility is intended mainly for libraries to use - as such, the core of Toy does not utilize it.
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
The drive system uses a Toy's Dictionary structure to store the mappings between keys and values - this dictionary object is a static global which persists for the lifetime of the program.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_initDriveSystem()
|
||||||
|
|
||||||
|
This function initializes the drive system.
|
||||||
|
|
||||||
|
### void Toy_freeDriveSystem()
|
||||||
|
|
||||||
|
This function cleans up after the drive system is no longer needed.
|
||||||
|
|
||||||
|
### void Toy_setDrivePath(char* drive, char* path)
|
||||||
|
|
||||||
|
This function sets a key-value pair in the drive system. It uses C strings, since it is intended to be called directly from `main()`.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_getDrivePathLiteral(Toy_Interpreter* interpreter, Toy_Literal* drivePathLiteral)
|
||||||
|
|
||||||
|
This function, when given a string literal of the correct format, will return a new string literal containing the relative filepath to a specified file.
|
||||||
|
|
||||||
|
The correct format is `drive:/path/to/filename`, where `drive` is a drive that was specified with `Toy_setDrivePath()`.
|
||||||
|
|
||||||
|
On failure, this function returns a null literal.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# repl_tools.h
|
||||||
|
|
||||||
|
This header provides a number of tools for compiling and running Toy, and is used primarily by the REPL. However, it can also be modified and used by any host program with a little effort.
|
||||||
|
|
||||||
|
This is not a core part of Toy or a library, and as such `repl_tools.h` and `repl_tools.c` can both be found in the `repl/` folder.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### const char* Toy_readFile(const char* path, size_t* fileSize)
|
||||||
|
|
||||||
|
This function reads in a file, and returns it as a constant buffer. It also sets the variable pointed to by `fileSize` to the size of the given buffer.
|
||||||
|
|
||||||
|
On error, this function returns `NULL`.
|
||||||
|
|
||||||
|
### int Toy_writeFile(const char* path, const unsigned char* bytes, size_t size)
|
||||||
|
|
||||||
|
This function writes the buffer pointed to by `bytes` to a file specified by `path`. The buffer's size should be specified by `size`.
|
||||||
|
|
||||||
|
On error, this function returns a non-zero value.
|
||||||
|
|
||||||
|
### const unsigned char* Toy_compileString(const char* source, size_t* size)
|
||||||
|
|
||||||
|
This function takes a cstring of Toy source code, and returns a compiled buffer based on that source code. The variable pointed to by `size` is set to the size of the bytecode.
|
||||||
|
|
||||||
|
On error, this function returns `NULL`.
|
||||||
|
|
||||||
|
### void Toy_runBinary(const unsigned char* tb, size_t size)
|
||||||
|
|
||||||
|
This function takes a bytecode array of `size` size, and executes it. The libraries available to the code are currently:
|
||||||
|
|
||||||
|
* lib_about
|
||||||
|
* lib_standard
|
||||||
|
* lib_random
|
||||||
|
* lib_runner
|
||||||
|
|
||||||
|
### void Toy_runBinaryFile(const char* fname)
|
||||||
|
|
||||||
|
This function loads in the binary file specified by `fname`, and passes it to `Toy_runBinary()`.
|
||||||
|
|
||||||
|
### void Toy_runSource(const char* source)
|
||||||
|
|
||||||
|
This function compiles the source with `Toy_compileString()`, and passes it to `Toy_runBinary()`.
|
||||||
|
|
||||||
|
### void Toy_runSourceFile(const char* fname)
|
||||||
|
|
||||||
|
This function loads in the file specified by `fname`, compiles it, and passes it to `Toy_runBinary()`.
|
||||||
|
|
||||||
|
### void Toy_parseBinaryFileHeader(const char* fname)
|
||||||
|
|
||||||
|
This function parses the header information stored within the bytecode file `fname`.
|
||||||
|
|
||||||
|
This is only used for debugging and validation purposes.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_common.h
|
||||||
|
|
||||||
|
This file is generally included in most header files within Toy, as it is where the TOY_API macro is defined. It also has some utilities intended for use only by the REPL.
|
||||||
|
|
||||||
|
## Defined Macros
|
||||||
|
|
||||||
|
### TOY_API
|
||||||
|
|
||||||
|
This definition of this macro is platform-dependant, and used to enable cross-platform compilation of shared and static libraries.
|
||||||
|
|
||||||
|
### TOY_VERSION_MAJOR
|
||||||
|
|
||||||
|
The current major version of Toy. This value is embedded into the bytecode, and the interpreter will refuse to run bytecode with a major version that does not match its own version.
|
||||||
|
|
||||||
|
This value MUST fit into an unsigned char.
|
||||||
|
|
||||||
|
### TOY_VERSION_MINOR
|
||||||
|
|
||||||
|
The current minor version of Toy. This value is embedded into the bytecode, and the interpreter will refuse to run bytecode with a minor version that is greater than its own minor version.
|
||||||
|
|
||||||
|
This value MUST fit into an unsigned char.
|
||||||
|
|
||||||
|
### TOY_VERSION_PATCH
|
||||||
|
|
||||||
|
The current patch version of Toy. This value is embedded into the bytecode.
|
||||||
|
|
||||||
|
This value MUST fit into an unsigned char.
|
||||||
|
|
||||||
|
### TOY_VERSION_BUILD
|
||||||
|
|
||||||
|
The current build version of Toy. This value is embedded into the bytecode.
|
||||||
|
|
||||||
|
This evaluates to a c-string, which contains build information such as compilation date and time of the interpreter. When in verbose mode, the compiler will display a warning if the build version of the bytecode does not match the build version of the interpreter.
|
||||||
|
|
||||||
|
This macro may also be used to store additional information about forks of the Toy codebase.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
# toy_compiler.h
|
||||||
|
|
||||||
|
This header defines the compiler structure, which is used to transform abstract syntax trees into usable intermediate bytecode. There are two steps to generating bytecode - the writing step, and the collation step.
|
||||||
|
|
||||||
|
During the writing step, the core of the program is generated, along with a series of literals representing the values within the program; these values are compressed and flattened into semi-unrecognizable forms. If the same literal is used multiple times in a program, such as a variable name, the name itself is replaced by a reference to the flattened literals within the cache.
|
||||||
|
|
||||||
|
During the collation step, everything from the core program’s execution instructions, the flattened literals, the functions (which have their own sections and protocols within the bytecode) and version information (such as the macros defined in toy_common.h) are all combined into a single buffer of bytes, known as bytecode. This bytecode can then be safely saved to a file or immediately executed.
|
||||||
|
|
||||||
|
## Define Functions
|
||||||
|
|
||||||
|
Executing the following functions out-of-order causes undefined behaviour.
|
||||||
|
|
||||||
|
### void Toy_initCompiler(Toy_Compiler* compiler)
|
||||||
|
|
||||||
|
This function initializes the given compiler.
|
||||||
|
|
||||||
|
### void Toy_writeCompiler(Toy_Compiler* compiler, Toy_ASTNode* node)
|
||||||
|
|
||||||
|
This function writes the given `node` argument to the compiler. During the writing step, this function may be called repeatedly, with a stream of results from `Toy_scanParser()`, until `Toy_scanParser()` returns `NULL`.
|
||||||
|
|
||||||
|
### unsigned char* Toy_collateCompiler(Toy_Compiler* compiler, size_t* size)
|
||||||
|
|
||||||
|
This function returns a buffer of bytes, known as "bytecode", created from the given compiler; it also stores the size of the bytecode in the variable pointed to by `size`.
|
||||||
|
|
||||||
|
Calling `Toy_collateCompiler()` multiple times on the same compiler will produce undefined behaviour.
|
||||||
|
|
||||||
|
### void Toy_freeCompiler(Toy_Compiler* compiler)
|
||||||
|
|
||||||
|
This function frees a compiler. Calling this on a compiler which has not been collated will free that compiler as expected - anything written to it will be lost.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy.h - A Toy Programming Language
|
||||||
|
|
||||||
|
If you're looking how to use Toy directly, try https://toylang.com/
|
||||||
|
Otherwise, this header may help learn how Toy works internally.
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
These headers define a bunch of useful macros, based on what platform you build for.
|
||||||
|
|
||||||
|
The most important macro is `TOY_API`, which specifies functions intended for the end user.
|
||||||
|
|
||||||
|
* [toy_common.h](toy_common_h.md)
|
||||||
|
* [toy_console_colors.h](toy_console_colors_h.md)
|
||||||
|
* [toy_memory.h](toy_memory_h.md)
|
||||||
|
|
||||||
|
## Core Pipeline
|
||||||
|
|
||||||
|
From source to execution, each step is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
source -> lexer -> token
|
||||||
|
token -> parser -> AST
|
||||||
|
AST -> compiler -> bytecode
|
||||||
|
bytecode -> interpreter -> result
|
||||||
|
```
|
||||||
|
|
||||||
|
I should note that the parser -> compiler phase is actually made up of two steps - the write step and the collate step. See `Toy_compileString()` in `repl/repl_tools.c` for an example of how to compile properly.
|
||||||
|
|
||||||
|
* [toy_lexer.h](toy_lexer_h.md)
|
||||||
|
* [toy_parser.h](toy_parser_h.md)
|
||||||
|
* [toy_compiler.h](toy_compiler_h.md)
|
||||||
|
* [toy_interpreter.h](toy_interpreter_h.md)
|
||||||
|
|
||||||
|
## Building Block Structures
|
||||||
|
|
||||||
|
Literals represent any value within the language, including some internal ones that you never see.
|
||||||
|
|
||||||
|
Literal arrays are contiguous arrays within memory, and are the most heavily used structure in Toy.
|
||||||
|
|
||||||
|
Literal dictionaries are unordered key-value hashmaps, that use a running strategy for collisions.
|
||||||
|
|
||||||
|
* [toy_literal.h](toy_literal_h.md)
|
||||||
|
* [toy_literal_array.h](toy_literal_array_h.md)
|
||||||
|
* [toy_literal_dictionary.h](toy_literal_dictionary_h.md)
|
||||||
|
|
||||||
|
## Other Components
|
||||||
|
|
||||||
|
You probably won't use these directly, but they're a good learning opportunity.
|
||||||
|
|
||||||
|
`Toy_Scope` holds the variables of a specific scope within Toy - be it a script, a function, a block, etc. Scopes are also where the type system lives at runtime. They use identifier literals as keys, exclusively.
|
||||||
|
|
||||||
|
`Toy_RefString` is a utility class that wraps traditional C strings, making them less memory intensive and faster to copy and move. In reality, since strings are considered immutable, multiple variables can point to the same string to save memory, and you can just create a new one of these vars pointing to the original rather than copying entirely for a speed boost. This module has its own memory allocator system that is plugged into the main memory allocator.
|
||||||
|
|
||||||
|
`Toy_RefFunction` acts similarly to `Toy_RefString`, but instead operates on function bytecode.
|
||||||
|
|
||||||
|
* [toy_scope.h](toy_scope_h.md)
|
||||||
|
* [toy_refstring.h](toy_refstring_h.md)
|
||||||
|
* [toy_reffunction.h](toy_reffunction_h.md)
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
# toy_interpreter.h
|
||||||
|
|
||||||
|
This header defines the interpreter structure, which is the beating heart of Toy.
|
||||||
|
|
||||||
|
`Toy_Interpreter` is a stack-based, bytecode-driven interpreter with a number of customisation options, including "hooks"; native C functions wrapped in `Toy_Literal` instances, injected into the interpreter in order to give the Toy scripts access to libraries via the `import` keyword. The hooks, when invoked this way, can then inject further native functions into the interpreter's current scope. Exactly which hooks are made available varies by host program, but `standard` is the most commonly included one.
|
||||||
|
|
||||||
|
Another useful customisation feature is the ability to redirect output from the `print` and `assert` keywords, as well as any internal errors that occur. This can allow you to add in a logging system, or even hook the `print` statement up to some kind of HUD.
|
||||||
|
|
||||||
|
## Defined Interfaces
|
||||||
|
|
||||||
|
Note: These interfaces are *actually* defined in [toy_literal.h](toy_literal_h.md) but are documented here, because this is where it matters most.
|
||||||
|
|
||||||
|
### typedef void (*Toy_PrintFn)(const char*)
|
||||||
|
|
||||||
|
This is the interface used by "print functions" - that is, functions used to print messages from the `print` and `assert` keywords, as well as internal interpreter errors.
|
||||||
|
|
||||||
|
### typedef int (*Toy_NativeFn)(struct Toy_Interpreter* interpreter, struct Toy_LiteralArray* arguments)
|
||||||
|
|
||||||
|
This is the interface used by "native functions" - that is, functions written in C which can be called directly by Toy scripts.
|
||||||
|
|
||||||
|
The arguments to the function are passed in as a `Toy_LiteralArray`.
|
||||||
|
|
||||||
|
### typedef int (*Toy_HookFn)(struct Toy_Interpreter* interpreter, struct Toy_Literal identifier, struct Toy_Literal alias)
|
||||||
|
|
||||||
|
This is the interface used by "hook functions" - that is, functions written in C which are invoked by using the `import` keyword, and are intended to inject other native functions into the current scope. While hook functions are capable of doing other things, this is greatly discouraged.
|
||||||
|
|
||||||
|
The identifier of the library (its name) is passed in as a `Toy_Literal`, as is any given alias; if no alias is given, then `alias` will be a null literal. Here, the identifier is `standard`, while the alias is `std`.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard as std;
|
||||||
|
```
|
||||||
|
|
||||||
|
Conventionally, when an alias is given, all the functions should instead be inserted into a `Toy_LiteralDictionary` which is then inserted into the scope with the alias as its identifier.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_initInterpreter(Toy_Interpreter* interpreter)
|
||||||
|
|
||||||
|
This function initializes the interpreter. It allocates memory for internal systems such as the stack, and zeroes-out systems that have yet to be invoked. Internally, it also invokes `Toy_resetInterpreter` to initialize the environment.
|
||||||
|
|
||||||
|
### void Toy_runInterpreter(Toy_Interpreter* interpreter, const unsigned char* bytecode, size_t length)
|
||||||
|
|
||||||
|
This function takes a `Toy_Interpreter` and `bytecode` (as well as the `length` of the bytecode), checks its version information, parses and un-flattens the literal cache, and executes the compiled program stored in the bytecode. This function also consumes the bytecode, so the `bytecode` argument is no longer valid after calls.
|
||||||
|
|
||||||
|
If the given bytecode's embedded version is not compatible with the current interpreter, then this function will refuse to execute.
|
||||||
|
|
||||||
|
Re-using a `Toy_Interpreter` instance without first resetting it is possible (that's how the REPL works), however doing so may have unintended consequences if the scripts are not intended to be used in such a way. Any variables declared will persist.
|
||||||
|
|
||||||
|
### void Toy_resetInterpreter(Toy_Interpreter* interpreter)
|
||||||
|
|
||||||
|
This function frees any scopes that the scripts have built up, and generates a new one. It also injects several globally available functions:
|
||||||
|
|
||||||
|
* set
|
||||||
|
* get
|
||||||
|
* push
|
||||||
|
* pop
|
||||||
|
* length
|
||||||
|
* clear
|
||||||
|
|
||||||
|
### void Toy_freeInterpreter(Toy_Interpreter* interpreter)
|
||||||
|
|
||||||
|
This function frees a `Toy_Interpreter`, clearing all the memory used within. That interpreter is no longer valid for use, and must be re-initialized.
|
||||||
|
|
||||||
|
### bool Toy_injectNativeFn(Toy_Interpreter* interpreter, const char* name, Toy_NativeFn func)
|
||||||
|
|
||||||
|
This function will inject the given native function `func` into the `Toy_Interpreter`'s current scope, with the identifier as `name`. Both the name and function will be converted into literals internally before being stored. It will return true on success, otherwise it will return false.
|
||||||
|
|
||||||
|
The primary use of this function is within hooks.
|
||||||
|
|
||||||
|
### bool Toy_injectNativeHook(Toy_Interpreter* interpreter, const char* name, Toy_HookFn hook)
|
||||||
|
|
||||||
|
This function will inject the given native function `hook` into the `Toy_Interpreter`'s hook cache, with the identifier as `name`. Both the name and the function will be converted into literals internally before being stored. It will return true on success, otherwise it will return false.
|
||||||
|
|
||||||
|
Hooks are invoked with the `import` keyword within Toy's scripts.
|
||||||
|
|
||||||
|
### bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_LiteralArray* arguments, Toy_LiteralArray* returns)
|
||||||
|
|
||||||
|
This function calls a `Toy_Literal` which contains a function, with the arguments to that function passed in as `arguments` and the results stored in `returns`. It returns true on success, otherwise it returns false.
|
||||||
|
|
||||||
|
The literal `func` can be either a native function or a Toy function, but it won't execute a hook.
|
||||||
|
|
||||||
|
### bool Toy_callFn(Toy_Interpreter* interpreter, const char* name, Toy_LiteralArray* arguments, Toy_LiteralArray* returns)
|
||||||
|
|
||||||
|
This utility function will find a `Toy_literal` within the `Toy_Interpreter`'s scope with an identifier that matches `name`, and will invoke it using `Toy_callLiteralFn` (passing in `arguments` and `returns` as expected).
|
||||||
|
|
||||||
|
### bool Toy_parseIdentifierToValue(Toy_Interpreter* interpreter, Toy_Literal* literalPtr)
|
||||||
|
|
||||||
|
Sometimes, native functions will receive `Toy_Literal` identifiers instead of the values - the correct values can be retrieved from the given interpreter's scope using the following pattern:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Literal foobarIdn = foobar;
|
||||||
|
if (TOY_IS_IDENTIFIER(foobar) && Toy_parseIdentifierToValue(interpreter, &foobar)) {
|
||||||
|
freeLiteral(foobarIdn); //remember to free the identifier
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### void Toy_setInterpreterPrint(Toy_Interpreter* interpreter, Toy_PrintFn printOutput)
|
||||||
|
|
||||||
|
This function sets the function called by the `print` keyword. By default, the following wrapper is used:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void printWrapper(const char* output) {
|
||||||
|
printf("%s\n", output);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The above is a very minor lie - in reality, there are some preprocessor directives to allow the REPL's `-n` flag to work.
|
||||||
|
|
||||||
|
### void Toy_setInterpreterAssert(Toy_Interpreter* interpreter, Toy_PrintFn assertOutput)
|
||||||
|
|
||||||
|
This function sets the function called by the `assert` keyword on failure. By default, the following wrapper is used:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void assertWrapper(const char* output) {
|
||||||
|
fprintf(stderr, "Assertion failure: %s\n", output);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### void Toy_setInterpreterError(Toy_Interpreter* interpreter, Toy_PrintFn errorOutput)
|
||||||
|
|
||||||
|
This function sets the function called when an error occurs within the interpreter. By default, the following wrapper is used:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void errorWrapper(const char* output) {
|
||||||
|
fprintf(stderr, "%s", output); //no newline
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
# toy_lexer.h
|
||||||
|
|
||||||
|
This header defines the lexer and token structures, which can be bound to a piece of source code, and used to tokenize it within a parser.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_initLexer(Toy_Lexer* lexer, const char* source)
|
||||||
|
|
||||||
|
This function initializes a lexer, binding it to the `source` parameter; the lexer is now ready to be passed to the parser.
|
||||||
|
|
||||||
|
### Toy_Token Toy_private_scanLexer(Toy_Lexer* lexer)
|
||||||
|
|
||||||
|
This function "scans" the lexer, returning a token to the parser.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
|
|
||||||
|
### void Toy_private_printToken(Toy_Token* token)
|
||||||
|
|
||||||
|
This function prints a given token to stdout.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
|
|
||||||
|
### void Toy_private_setComments(Toy_Lexer* lexer, bool enabled)
|
||||||
|
|
||||||
|
This function sets whether comments are allowed within source code. By default, comments are allowed, and are only disabled in the REPL.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# literal_array.h
|
||||||
|
|
||||||
|
This header defines the array structure, which manages a series of `Toy_Literal` instances in sequential memory. The array does not take ownership of given literals, instead it makes an internal copy.
|
||||||
|
|
||||||
|
The array type is one of two fundamental data structures used throughout Toy - the other is the dictionary.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_freeLiteralArray(Toy_LiteralArray* array)
|
||||||
|
|
||||||
|
This function frees a `Toy_LiteralArray` pointed to by `array`. Every literal within is passed to `Toy_freeLiteral()` before its memory is released.
|
||||||
|
|
||||||
|
### int Toy_pushLiteralArray(Toy_LiteralArray* array, Toy_Literal literal)
|
||||||
|
|
||||||
|
This function adds a new `literal` to the end of the `array`, growing the array's internal buffer if needed.
|
||||||
|
|
||||||
|
This function returns the index of the inserted value.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_popLiteralArray(Toy_LiteralArray* array)
|
||||||
|
|
||||||
|
This function removes the literal at the end of the `array`, and returns it.
|
||||||
|
|
||||||
|
### bool Toy_setLiteralArray(Toy_LiteralArray* array, Toy_Literal index, Toy_Literal value)
|
||||||
|
|
||||||
|
This function frees the literal at the position represented by the integer literal `index`, and stores `value` in its place.
|
||||||
|
|
||||||
|
This function returns true on success, otherwise it returns false.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_getLiteralArray(Toy_LiteralArray* array, Toy_Literal index)
|
||||||
|
|
||||||
|
This function returns the literal at the position represented by the integer literal `index`, or returns a null literal if none is found.
|
||||||
|
|
||||||
|
If `index` is not an integer literal or is out of bounds, this function returns a null literal.
|
||||||
|
|
||||||
|
### int Toy_private_findLiteralIndex(Toy_LiteralArray* array, Toy_Literal literal)
|
||||||
|
|
||||||
|
This function scans through the array, and returns the index of the first element that matches the given `literal`, otherwise it returns -1.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_literal_dictionary.h
|
||||||
|
|
||||||
|
This header defines the dictionary structure (as well as the private entry structure), which manages a series of `Toy_Literal` instances stored in a key-value hash map. The dictionary does not take ownership of given literals, instead it makes an internal copy.
|
||||||
|
|
||||||
|
The dictionary type is one of two fundamental data structures used throughout Toy - the other is the array.
|
||||||
|
|
||||||
|
## Defined Macros
|
||||||
|
|
||||||
|
### TOY_DICTIONARY_MAX_LOAD
|
||||||
|
|
||||||
|
If the contents of a dictionary exceeds this percentage of its capacity, then a new buffer is created, the old contents are copied over one-by-one, and the original buffer is freed.
|
||||||
|
|
||||||
|
Since this process can be memory and time intensive, a configurable macro is used to allow for fine-grained control across the language.
|
||||||
|
|
||||||
|
The current default value is `0.75`, representing 75% capacity.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_initLiteralDictionary(Toy_LiteralDictionary* dictionary)
|
||||||
|
|
||||||
|
This function initializes the `Toy_LiteralDictionary` pointed to by `dictionary`.
|
||||||
|
|
||||||
|
### void Toy_freeLiteralDictionary(Toy_LiteralDictionary* dictionary)
|
||||||
|
|
||||||
|
This function frees a `Toy_LiteralDictionary` pointed to by `dictionary`. Every literal within is passed to `Toy_freeLiteral()` before its memory is released.
|
||||||
|
|
||||||
|
### void Toy_setLiteralDictionary(Toy_LiteralDictionary* dictionary, Toy_Literal key, Toy_Literal value)
|
||||||
|
|
||||||
|
This function inserts the given key-value pair of literals into `dictionary`, creating it if it doesn't exist, or freeing and overwriting it if `key` is already present. This function may also expand the memory buffer if needed.
|
||||||
|
|
||||||
|
When expanding the memory buffer, a full copy of the existing dictionary's contents is created - this can be memory intensive.
|
||||||
|
|
||||||
|
Literal functions and opaques cannot be used as keys.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_getLiteralDictionary(Toy_LiteralDictionary* dictionary, Toy_Literal key)
|
||||||
|
|
||||||
|
This function returns the value of the literal within `dictionary` identified by `key`, or a null literal if it doesn't exist.
|
||||||
|
|
||||||
|
Literal functions and opaques cannot be used as keys.
|
||||||
|
|
||||||
|
### void Toy_removeLiteralDictionary(Toy_LiteralDictionary* dictionary, Toy_Literal key)
|
||||||
|
|
||||||
|
This function removes the key-value pair of literals from `dictionary` identified by `key`, if it exists.
|
||||||
|
|
||||||
|
Literal functions and opaques cannot be used as keys.
|
||||||
|
|
||||||
|
### bool Toy_existsLiteralDictionary(Toy_LiteralDictionary* dictionary, Toy_Literal key)
|
||||||
|
|
||||||
|
This function returns true if the key-value pair identified by `key` exists within `dictionary`, otherwise it returns false.
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_literal.h
|
||||||
|
|
||||||
|
This header defines the literal structure, which is used extensively throughout Toy to represent values of some kind.
|
||||||
|
|
||||||
|
The main way of interacting with literals is to use a macro of some kind, as the exact implementation of `Toy_Literal` has and will change based on the needs of Toy.
|
||||||
|
|
||||||
|
User data can be passed around within Toy as an opaque type - use the tag value for determining what kind of opaque it is, or leave it as 0.
|
||||||
|
|
||||||
|
## Defined Enums
|
||||||
|
|
||||||
|
### Toy_LiteralType
|
||||||
|
|
||||||
|
* `TOY_LITERAL_NULL`
|
||||||
|
* `TOY_LITERAL_BOOLEAN`
|
||||||
|
* `TOY_LITERAL_INTEGER`
|
||||||
|
* `TOY_LITERAL_FLOAT`
|
||||||
|
* `TOY_LITERAL_STRING`
|
||||||
|
* `TOY_LITERAL_ARRAY`
|
||||||
|
* `TOY_LITERAL_DICTIONARY`
|
||||||
|
* `TOY_LITERAL_FUNCTION`
|
||||||
|
* `TOY_LITERAL_FUNCTION_NATIVE`
|
||||||
|
* `TOY_LITERAL_FUNCTION_HOOK`
|
||||||
|
* `TOY_LITERAL_IDENTIFIER`
|
||||||
|
* `TOY_LITERAL_TYPE`
|
||||||
|
* `TOY_LITERAL_OPAQUE`
|
||||||
|
* `TOY_LITERAL_ANY`
|
||||||
|
|
||||||
|
These are the main values of `Toy_LiteralType`, each of which represents a potential state of the `Toy_Literal` structure. Do not interact with a literal without determining its type with the `IS_*` macros first.
|
||||||
|
|
||||||
|
Other type values are possible, but are only used internally.
|
||||||
|
|
||||||
|
## Defined Macros
|
||||||
|
|
||||||
|
The following macros are used to determine if a given literal, passed in as `value`, is of a specific type. It should be noted that `TOY_IS_FUNCTION` will return false for native and hook functions.
|
||||||
|
|
||||||
|
* `TOY_IS_NULL(value)`
|
||||||
|
* `TOY_IS_BOOLEAN(value)`
|
||||||
|
* `TOY_IS_INTEGER(value)`
|
||||||
|
* `TOY_IS_FLOAT(value)`
|
||||||
|
* `TOY_IS_STRING(value)`
|
||||||
|
* `TOY_IS_ARRAY(value)`
|
||||||
|
* `TOY_IS_DICTIONARY(value)`
|
||||||
|
* `TOY_IS_FUNCTION(value)`
|
||||||
|
* `TOY_IS_FUNCTION_NATIVE(value)`
|
||||||
|
* `TOY_IS_FUNCTION_HOOK(value)`
|
||||||
|
* `TOY_IS_IDENTIFIER(value)`
|
||||||
|
* `TOY_IS_TYPE(value)`
|
||||||
|
* `TOY_IS_OPAQUE(value)`
|
||||||
|
|
||||||
|
The following macros are used to cast a literal to a specific C type to be used.
|
||||||
|
|
||||||
|
* `TOY_AS_BOOLEAN(value)`
|
||||||
|
* `TOY_AS_INTEGER(value)`
|
||||||
|
* `TOY_AS_FLOAT(value)`
|
||||||
|
* `TOY_AS_STRING(value)`
|
||||||
|
* `TOY_AS_ARRAY(value)`
|
||||||
|
* `TOY_AS_DICTIONARY(value)`
|
||||||
|
* `TOY_AS_FUNCTION(value)`
|
||||||
|
* `TOY_AS_FUNCTION_NATIVE(value)`
|
||||||
|
* `TOY_AS_FUNCTION_HOOK(value)`
|
||||||
|
* `TOY_AS_IDENTIFIER(value)`
|
||||||
|
* `TOY_AS_TYPE(value)`
|
||||||
|
* `TOY_AS_OPAQUE(value)`
|
||||||
|
|
||||||
|
The following macros are used to create a new literal, with the given `value` as it's internal value.
|
||||||
|
|
||||||
|
* `TOY_TO_NULL_LITERAL` - does not need parentheses
|
||||||
|
* `TOY_TO_BOOLEAN_LITERAL(value)`
|
||||||
|
* `TOY_TO_INTEGER_LITERAL(value)`
|
||||||
|
* `TOY_TO_FLOAT_LITERAL(value)`
|
||||||
|
* `TOY_TO_STRING_LITERAL(value)`
|
||||||
|
* `TOY_TO_ARRAY_LITERAL(value)`
|
||||||
|
* `TOY_TO_DICTIONARY_LITERAL(value)`
|
||||||
|
* `TOY_TO_FUNCTION_LITERAL(value, l)` - `l` represents the length of the bytecode passed as `value`
|
||||||
|
* `TOY_TO_FUNCTION_NATIVE_LITERAL(value)`
|
||||||
|
* `TOY_TO_FUNCTION_HOOK_LITERAL(value)`
|
||||||
|
* `TOY_TO_IDENTIFIER_LITERAL(value)`
|
||||||
|
* `TOY_TO_TYPE_LITERAL(value, c)` - `c` is the true of the type should be const
|
||||||
|
* `TOY_TO_OPAQUE_LITERAL(value, t)` - `t` is the integer tag
|
||||||
|
|
||||||
|
## More Defined Macros
|
||||||
|
|
||||||
|
The following macros are utilities used throughout Toy's internals, and are available for the user as well.
|
||||||
|
|
||||||
|
### TOY_IS_TRUTHY(x)
|
||||||
|
|
||||||
|
Returns true if the literal `x` is truthy, otherwise it returns false.
|
||||||
|
|
||||||
|
Currently, every value is considered truthy except `false`, which is falsy and `null`, which is neither true nor false.
|
||||||
|
|
||||||
|
### TOY_AS_FUNCTION_BYTECODE_LENGTH(lit)
|
||||||
|
|
||||||
|
Returns the length of a Toy function's bytecode.
|
||||||
|
|
||||||
|
This macro is only valid on `TOY_LITERAL_FUNCTION`.
|
||||||
|
|
||||||
|
### TOY_MAX_STRING_LENGTH
|
||||||
|
|
||||||
|
The maximum length of a string in Toy, which is 4096 bytes by default. This can be changed at compile time, but the results of doing so are not officially supported.
|
||||||
|
|
||||||
|
### TOY_HASH_I(lit)
|
||||||
|
|
||||||
|
Identifiers are the names of values within Toy; to speed up execution, their "hash value" is computed at compile time and stored within them. Use this to access it, if needed.
|
||||||
|
|
||||||
|
This macro is only valid on `TOY_LITERAL_IDENTIFIER`.
|
||||||
|
|
||||||
|
### TOY_TYPE_PUSH_SUBTYPE(lit, subtype)
|
||||||
|
|
||||||
|
When building a complex type, such as the type of an array or dictionary, you may need to specify inner types. Use this to push a `subtype`. Calling `Toy_freeLiteral()` on the outermost type should clean up all inner types, as expected.
|
||||||
|
|
||||||
|
This macro returns the index of the newly pushed value within its parent.
|
||||||
|
|
||||||
|
This macro is only valid on `TOY_LITERAL_TYPE`, for both `type` and `subtype`.
|
||||||
|
|
||||||
|
### TOY_GET_OPAQUE_TAG(o)
|
||||||
|
|
||||||
|
Returns the value of the opaque `o`'s tag.
|
||||||
|
|
||||||
|
This macro is only valid on `TOY_LITERAL_OPAQUE`.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_freeLiteral(Toy_Literal literal)
|
||||||
|
|
||||||
|
This function frees the given literal's memory. Any internal pointers are now invalid.
|
||||||
|
|
||||||
|
This function should be called on EVERY literal when it is no longer needed, regardless of type.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_copyLiteral(Toy_Literal original)
|
||||||
|
|
||||||
|
This function returns a copy of the given literal. Literals should never be copied without this function, as it handles a lot of internal memory allocations.
|
||||||
|
|
||||||
|
### bool Toy_literalsAreEqual(Toy_Literal lhs, Toy_Literal rhs)
|
||||||
|
|
||||||
|
This checks to see if two given literals are equal.
|
||||||
|
|
||||||
|
When an integer and a float are compared, the integer is coerced into a float for the duration of the call.
|
||||||
|
|
||||||
|
Arrays or dictionaries are equal only if their keys and values all equal. Likewise, types only equal if all subtypes are equal, in order.
|
||||||
|
|
||||||
|
Functions and opaques are never equal to anything, while values with the type `TOY_LITERAL_ANY` are always equal.
|
||||||
|
|
||||||
|
### int Toy_hashLiteral(Toy_Literal lit)
|
||||||
|
|
||||||
|
This finds the hash of a literal, for various purposes. Different hashing algorithms are used for different types, and some types can't be hashed at all.
|
||||||
|
|
||||||
|
Types that can't be hashed are
|
||||||
|
|
||||||
|
* all kinds of functions
|
||||||
|
* type
|
||||||
|
* opaque
|
||||||
|
* any
|
||||||
|
|
||||||
|
In the case of identifiers, their hashes are precomputed on creation and are stored within the literal.
|
||||||
|
|
||||||
|
### void Toy_printLiteral(Toy_Literal literal)
|
||||||
|
|
||||||
|
This wraps a call to `Toy_printLiteralCustom`, with a printf-stdout wrapper as `printFn`.
|
||||||
|
|
||||||
|
### void Toy_printLiteralCustom(Toy_Literal literal, PrintFn printFn)
|
||||||
|
|
||||||
|
This function passes the string representation of `literal` to `printFn`.
|
||||||
|
|
||||||
|
This function is not thread safe - due to the loopy and recursive nature of printing compound values, this function uses some globally persistent variables.
|
||||||
|
|
||||||
|
### bool Toy_private_isTruthy(Toy_Literal x)
|
||||||
|
|
||||||
|
Utilized by the `TOY_IS_TRUTHY` macro.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
|
|
||||||
|
### bool Toy_private_toIdentifierLiteral(Toy_RefString* ptr)
|
||||||
|
|
||||||
|
Utilized by the `TOY_TO_IDENTIFIER_LITERAL` macro.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
|
|
||||||
|
### bool Toy_private_typePushSubtype(Toy_Literal* lit, Toy_Literal subtype)
|
||||||
|
|
||||||
|
Utilized by the `TOY_TYPE_PUSH_SUBTYPE` macro.
|
||||||
|
|
||||||
|
Private functions are not intended for general use.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_memory.h
|
||||||
|
|
||||||
|
This header defines all the memory management utilities. Any and all heap-based memory management goes through these utilities.
|
||||||
|
|
||||||
|
A default memory allocator function is used internally, but it can be overwritten for diagnostic and platform related purposes.
|
||||||
|
|
||||||
|
## Defined Macros
|
||||||
|
|
||||||
|
### TOY_GROW_CAPACITY(capacity)
|
||||||
|
|
||||||
|
This macro calculates, in place, what size of memory should be allocated based on the previous size.
|
||||||
|
|
||||||
|
### TOY_GROW_CAPACITY_FAST(capacity)
|
||||||
|
|
||||||
|
This macro calculates, in place, what size of memory should be allocated based on the previous size. It grows faster than `TOY_GROW_CAPACITY`.
|
||||||
|
|
||||||
|
### TOY_FREE(type, pointer)
|
||||||
|
|
||||||
|
This macro wraps `Toy_reallocate()`, which itself calls the allocator function. `type` is the type that will be freed, and `pointer` is to what is being freed. This should only be used when a single element has been allocated, as opposed to an array.
|
||||||
|
|
||||||
|
### TOY_FREE_ARRAY(type, pointer, oldCount)
|
||||||
|
|
||||||
|
This macro wraps `Toy_reallocate()`, which itself calls the allocator function. `type` is the type that will be freed, `pointer` is a reference to what is being freed, and `oldCount` is the size of the array being freed. This should only be used when an array has been allocated, as opposed to a single element.
|
||||||
|
|
||||||
|
### TOY_GROW_ARRAY(type, pointer, oldCount, count)
|
||||||
|
|
||||||
|
This macro wraps `Toy_reallocate()`, which itself calls the allocator function. `type` is the type that is being operated on, `pointer` is what is being resized, `oldCount` is the previous size of the array and `count` is the new size of the array (usually calculated with `TOY_GROW_CAPACITY`).
|
||||||
|
|
||||||
|
This returns a pointer of `type`.
|
||||||
|
|
||||||
|
### TOY_SHRINK_ARRAY(type, pointer, oldCount, count)
|
||||||
|
|
||||||
|
This macro wraps `Toy_reallocate()`, which itself calls the allocator function. `type` is the type that is being operated on, `pointer` is what is being resized, `oldCount` is the previous size of the array and `count` is the new size of the array.
|
||||||
|
|
||||||
|
This returns a pointer of `type`.
|
||||||
|
|
||||||
|
## Defined Interfaces
|
||||||
|
|
||||||
|
### typedef void* (*Toy_MemoryAllocatorFn)(void* pointer, size_t oldSize, size_t newSize)
|
||||||
|
|
||||||
|
This function interface is used for defining any memory allocator functions.
|
||||||
|
|
||||||
|
Any and all memory allocator functions should:
|
||||||
|
|
||||||
|
* Take a `pointer` to a previously allocated block of memory, or `NULL`
|
||||||
|
* Take the `oldSize`, which is the previous size of the `pointer` allocated, in bytes (`oldSize` can be 0)
|
||||||
|
* Take the `newSize`, which is the new size of the buffer to be allocated, in bytes (`newSize` can be 0)
|
||||||
|
* Return the newly allocated buffer, or `NULL` if `newSize` is zero
|
||||||
|
* Return `NULL` on error
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### TOY_API void* Toy_reallocate(void* pointer, size_t oldSize, size_t newSize)
|
||||||
|
|
||||||
|
This function shouldn't be called directly. Instead, use one of the given macros.
|
||||||
|
|
||||||
|
This function wraps a call to the internal assigned memory allocator.
|
||||||
|
|
||||||
|
### void Toy_setMemoryAllocator(Toy_MemoryAllocatorFn)
|
||||||
|
|
||||||
|
This function sets the memory allocator, replacing the default memory allocator.
|
||||||
|
|
||||||
|
This function also overwrites any given refstring and reffunction memory allocators, see [toy_refstring.h](toy_refstring_h.md).
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
# toy_parser.h
|
||||||
|
|
||||||
|
This header defines the parser structure which, after being initialized with a lexer produces a series of abstract syntax trees to be passed to the compiler. The following is a utility function provided by [repl_tools.h](repl_tools_h.md), demonstrating how to use the parser.
|
||||||
|
|
||||||
|
```c
|
||||||
|
//generate bytecode from a given string
|
||||||
|
const unsigned char* Toy_compileString(const char* source, size_t* size) {
|
||||||
|
//declare the relevant instances
|
||||||
|
Toy_Lexer lexer;
|
||||||
|
Toy_Parser parser;
|
||||||
|
Toy_Compiler compiler;
|
||||||
|
|
||||||
|
//initialize each of them
|
||||||
|
Toy_initLexer(&lexer, source);
|
||||||
|
Toy_initParser(&parser, &lexer);
|
||||||
|
Toy_initCompiler(&compiler);
|
||||||
|
|
||||||
|
//when the parser returns NULL, it is finished
|
||||||
|
Toy_ASTNode* node = Toy_scanParser(&parser);
|
||||||
|
while(node != NULL) {
|
||||||
|
//if the parser returns an error node, clean up and exit gracefully
|
||||||
|
if (node->type == TOY_AST_NODE_ERROR) {
|
||||||
|
Toy_freeASTNode(node);
|
||||||
|
Toy_freeCompiler(&compiler);
|
||||||
|
Toy_freeParser(&parser);
|
||||||
|
//no need to clean the lexer
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//write the node to the compiler
|
||||||
|
Toy_writeCompiler(&compiler, node);
|
||||||
|
Toy_freeASTNode(node);
|
||||||
|
|
||||||
|
//grab the next node
|
||||||
|
node = Toy_scanParser(&parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the bytecode to be returned
|
||||||
|
const unsigned char* tb = Toy_collateCompiler(&compiler, size);
|
||||||
|
|
||||||
|
//cleanup
|
||||||
|
Toy_freeCompiler(&compiler);
|
||||||
|
Toy_freeParser(&parser);
|
||||||
|
//no need to clean the lexer
|
||||||
|
|
||||||
|
//finally
|
||||||
|
return tb;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_initParser(Toy_Parser* parser, Toy_Lexer* lexer)
|
||||||
|
|
||||||
|
This function initializes a `Toy_Parser`, binding the given `Toy_Lexer` to it.
|
||||||
|
|
||||||
|
### void Toy_freeParser(Toy_Parser* parser)
|
||||||
|
|
||||||
|
This function frees a `Toy_Parser` once its task is completed.
|
||||||
|
|
||||||
|
### Toy_ASTNode* Toy_scanParser(Toy_Parser* parser)
|
||||||
|
|
||||||
|
This function returns an abstract syntax tree representing part of the program, or an error node. The abstract syntax tree must be passed to `Toy_writeCompiler()` and/or `Toy_freeASTNode()`.
|
||||||
|
|
||||||
|
This function should be called repeatedly until it returns `NULL`, indicating the end of the program.
|
||||||
|
|
||||||
|
### void Toy_freeASTNode(Toy_ASTNode* node)
|
||||||
|
|
||||||
|
This function cleans up any valid instance of `Toy_ASTNode` pointer passed to it. It is most commonly used to clean up the values returned by `Toy_scanParser`, after they have been passed to `Toy_writeCompiler`, or when the node is an error node.
|
||||||
|
|
||||||
|
Note: this function is *actually* defined in toy_ast_node.h, but documented here, because this is where it matters most.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_reffunction.h
|
||||||
|
|
||||||
|
This header defines the Toy_RefFunction structure, as well as all the related utilities.
|
||||||
|
|
||||||
|
See [Toy_RefString](toy_refstring_h.md) for more information about the reference pattern.
|
||||||
|
|
||||||
|
This module reserves the right to instead preform a deep copy when it sees fit (this is for future debugging purposes).
|
||||||
|
|
||||||
|
## Defined Interfaces
|
||||||
|
|
||||||
|
### typedef void* (*Toy_RefFunctionAllocatorFn)(void* pointer, size_t oldSize, size_t newSize)
|
||||||
|
|
||||||
|
This interface conforms to Toy's memory API, and generally shouldn't be used without a good reason.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_setRefFunctionAllocatorFn(Toy_RefFunctionAllocatorFn)
|
||||||
|
|
||||||
|
This function conforms to and is invoked by Toy's memory API, and generally shouldn't be used without a good reason.
|
||||||
|
|
||||||
|
### Toy_RefFunction* Toy_createRefFunction(const void* data, size_t length)
|
||||||
|
|
||||||
|
This function returns a new `Toy_RefFunction`, containing a copy of `data`, or `NULL` on error.
|
||||||
|
|
||||||
|
This function also sets the returned `refFunction`'s reference counter to 1.
|
||||||
|
|
||||||
|
### void Toy_deleteRefFunction(Toy_RefFunction* refFunction)
|
||||||
|
|
||||||
|
This function reduces the `refFunction`'s reference counter by 1 and, if it reaches 0, frees the memory.
|
||||||
|
|
||||||
|
### int Toy_countRefFunction(Toy_RefFunction* refFunction)
|
||||||
|
|
||||||
|
This function returns the total number of references to `refFunction`, for debugging.
|
||||||
|
|
||||||
|
### size_t Toy_lengthRefFunction(Toy_RefFunction* refFunction)
|
||||||
|
|
||||||
|
This function returns the length of the underlying bytecode of `refFunction`.
|
||||||
|
|
||||||
|
### Toy_RefFunction* Toy_copyRefFunction(Toy_RefFunction* refFunction)
|
||||||
|
|
||||||
|
This function increases the reference counter of `refFunction` by 1, before returning the given pointer.
|
||||||
|
|
||||||
|
This function reserves the right to create a deep copy where needed.
|
||||||
|
|
||||||
|
### Toy_RefFunction* Toy_deepCopyRefFunction(Toy_RefFunction* refFunction)
|
||||||
|
|
||||||
|
This function behaves identically to `Toy_copyRefFunction`, except that it explicitly forces a deep copy of the internal memory. Using this function should be done carefully, as it incurs a performance penalty that negates the benefit of this module.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_refstring.h
|
||||||
|
|
||||||
|
This header defines the structure `Toy_RefString`, as well as all the related utilities.
|
||||||
|
|
||||||
|
[refstring](https://github.com/Ratstail91/refstring) is a stand-alone utility written to reduce the amount of memory manipulation used within Toy. It was independently written and tested, before being incorporated into Toy proper. As such, it has its own memory management API, which by default is tied into Toy's [core memory API](toy_memory_h.md).
|
||||||
|
|
||||||
|
Instances of `Toy_RefString` are reference counted - that is, rather than copying an existing string in memory, a pointer to the refstring is returned, and the internal reference counter is increased by 1. When the pointer is no longer needed, `Toy_DeleteRefString` can be called; this will decrement the internal reference counter by 1, and only free it when it reaches 0. This has multiple benefits, when used correctly:
|
||||||
|
|
||||||
|
* Reduced memory usage
|
||||||
|
* Faster program execution
|
||||||
|
|
||||||
|
This module reserves the right to instead preform a deep copy when it sees fit (this is for future debugging purposes).
|
||||||
|
|
||||||
|
## Defined Interfaces
|
||||||
|
|
||||||
|
### typedef void* (*Toy_RefStringAllocatorFn)(void* pointer, size_t oldSize, size_t newSize)
|
||||||
|
|
||||||
|
This interface conforms to Toy's memory API, and generally shouldn't be used without a good reason.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### void Toy_setRefStringAllocatorFn(Toy_RefStringAllocatorFn)
|
||||||
|
|
||||||
|
This function conforms to and is invoked by Toy's memory API, and generally shouldn't be used without a good reason.
|
||||||
|
|
||||||
|
### Toy_RefString* Toy_createRefString(const char* cstring)
|
||||||
|
|
||||||
|
This function wraps `Toy_CreateRefStringLength`, by determining the length of the given `cstring` and passing it to the other function.
|
||||||
|
|
||||||
|
### Toy_RefString* Toy_createRefStringLength(const char* cstring, size_t length)
|
||||||
|
|
||||||
|
This function returns a new `Toy_RefString`, containing a copy of `cstring`, or `NULL` on error.
|
||||||
|
|
||||||
|
This function also sets the returned refstring's reference counter to 1.
|
||||||
|
|
||||||
|
### void Toy_deleteRefString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function reduces the `refString`'s reference counter by 1 and, if it reaches 0, frees the memory.
|
||||||
|
|
||||||
|
### int Toy_countRefString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function returns the total number of references to `refString`, for debugging.
|
||||||
|
|
||||||
|
### size_t Toy_lengthRefString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function returns the length of the underlying cstring of `refString`.
|
||||||
|
|
||||||
|
### Toy_RefString* Toy_copyRefString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function increases the reference counter of `refString` by 1, before returning the given pointer.
|
||||||
|
|
||||||
|
This function reserves the right to create a deep copy where needed.
|
||||||
|
|
||||||
|
### Toy_RefString* Toy_deepCopyRefString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function behaves identically to `Toy_copyRefString`, except that it explicitly forces a deep copy of the internal memory. Using this function should be done carefully, as it incurs a performance penalty that negates the benefit of this module.
|
||||||
|
|
||||||
|
### const char* Toy_toCString(Toy_RefString* refString)
|
||||||
|
|
||||||
|
This function exposes the interal cstring of `refString`. Only use this function when dealing with external APIs.
|
||||||
|
|
||||||
|
### bool Toy_equalsRefString(Toy_RefString* lhs, Toy_RefString* rhs)
|
||||||
|
|
||||||
|
This function returns true when the two refstrings are either the same refstring, or contain the same value. Otherwise, it returns false.
|
||||||
|
|
||||||
|
### bool Toy_equalsRefStringCString(Toy_RefString* lhs, char* cstring)
|
||||||
|
|
||||||
|
This function returns true when the `refString` contains the same value as the `cstring`. Otherwise, it returns false.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# toy_scope.h
|
||||||
|
|
||||||
|
This header defines the scope structure, which stores all the variables used within a given block of code.
|
||||||
|
|
||||||
|
Scopes are arranged into a linked list of ancestors, each of which is reference counted. When a scope is popped off the end of the chain, every ancestor scope has its reference counter reduced by 1 and, if any reach 0, they are freed.
|
||||||
|
|
||||||
|
This is also where Toy's type system lives.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### Toy_Scope* Toy_pushScope(Toy_Scope* scope)
|
||||||
|
|
||||||
|
This function creates a new `Toy_scope` with `scope` as it's ancestor, and returns it.
|
||||||
|
|
||||||
|
### Toy_Scope* Toy_popScope(Toy_Scope* scope)
|
||||||
|
|
||||||
|
This function frees the given `scope`, and returns its ancestor.
|
||||||
|
|
||||||
|
### Toy_Scope* Toy_copyScope(Toy_Scope* original)
|
||||||
|
|
||||||
|
This function copies an existing scope, and returns the copy.
|
||||||
|
|
||||||
|
This copies the internal dictionaries, so it can be memory intensive.
|
||||||
|
|
||||||
|
### bool Toy_declareScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal type)
|
||||||
|
|
||||||
|
This function declares a new variable `key` within `scope`, giving it the type of `type`.
|
||||||
|
|
||||||
|
This function returns true on success, otherwise it returns failure (such as if the given key already exists).
|
||||||
|
|
||||||
|
### bool Toy_isDeclaredScopeVariable(Toy_Scope* scope, Toy_Literal key)
|
||||||
|
|
||||||
|
This function checks to see if a given variable with the name `key` has been previously declared.
|
||||||
|
|
||||||
|
### bool Toy_setScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal value, bool constCheck)
|
||||||
|
|
||||||
|
This function sets an existing variable named `key` to the value of `value`. This function fails if `constCheck` is true and the given key's type has the constant flag set. It also fails if the given key doesn't exist.
|
||||||
|
|
||||||
|
This function returns true on success, otherwise it returns false.
|
||||||
|
|
||||||
|
### bool Toy_getScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal* value)
|
||||||
|
|
||||||
|
This function sets the literal pointed to by `value` to equal the variable named `key`.
|
||||||
|
|
||||||
|
This function returns true on success, otherwise it returns false.
|
||||||
|
|
||||||
|
### Toy_Literal Toy_getScopeType(Toy_Scope* scope, Toy_Literal key)
|
||||||
|
|
||||||
|
This function returns a new `Toy_Literal` representing the type of the variable named `key`.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
# Building Toy
|
||||||
|
|
||||||
|
This tutorial assumes you're using git, GCC, and make.
|
||||||
|
|
||||||
|
To embed toy into your program, simply clone the [git repository](https://github.com/Ratstail91/Toy).
|
||||||
|
|
||||||
|
Toy's makefile uses the exported variable `TOY_OUTDIR` to define where the output of the build command will place the result. If you're building Toy as a submodule (which is recommended), then you MUST set this value to a directory name, relative to the root directory.
|
||||||
|
|
||||||
|
```make
|
||||||
|
export TOY_OUTDIR = out
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you'll want to run make the from within Toy's `source`, assuming the output directory has been created. There are two options for building Toy - `library` (default) or `static`; the former will create a shared library (and a .dll file on Windows), while the latter will create a static library.
|
||||||
|
|
||||||
|
```make
|
||||||
|
toy: $(OUTDIR)
|
||||||
|
$(MAKE) -C Toy/source
|
||||||
|
|
||||||
|
$(OUTDIR):
|
||||||
|
mkdir $(OUTDIR)
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, link against the outputted library, with the source directory as the location of the header files.
|
||||||
|
|
||||||
|
```make
|
||||||
|
all: $(OBJ) toy
|
||||||
|
$(CC) $(CFLAGS) -o $(OUT) $(OBJ) -L$(TOY_OUTDIR) -ltoy
|
||||||
|
```
|
||||||
|
|
||||||
|
These snippets of makefile are only an example - the repository has a more fully featured set of makefiles which can also produce a usable REPL program.
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
|
||||||
|
# Compiling Toy
|
||||||
|
|
||||||
|
This tutorial is a subsection of [Embedding Toy](deep-dive/embedding-toy) that has been spun off into its own page for the sake of brevity/sanity. It's recommended that you read the main article first.
|
||||||
|
|
||||||
|
The exact phases outlined here are entirely implementation-dependent - that is, they aren't required, and are simply how the canonical implementation of Toy works.
|
||||||
|
|
||||||
|
## How the Compilation works
|
||||||
|
|
||||||
|
There are four main phases to running a Toy source file. These are:
|
||||||
|
|
||||||
|
```
|
||||||
|
lexing -> parsing -> compiling -> interpreting
|
||||||
|
```
|
||||||
|
|
||||||
|
Each phase has a dedicated set of functions and structures, as well as intermediate structures between these that carry information.
|
||||||
|
|
||||||
|
```
|
||||||
|
source -> lexer -> token
|
||||||
|
token -> parser -> AST
|
||||||
|
AST -> compiler -> bytecode
|
||||||
|
bytecode -> interpreter -> result
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lexer
|
||||||
|
|
||||||
|
Exactly how the source code is loaded into a C-string is left up to the user, however once it's loaded, it can be bound to a `Toy_Lexer` structure.
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Lexer lexer;
|
||||||
|
Toy_initLexer(&lexer, source);
|
||||||
|
```
|
||||||
|
|
||||||
|
The lexer, when invoked, will break down the string of characters into individual `Tokens`.
|
||||||
|
|
||||||
|
The lexer does not need to be freed after use, however the source code does.
|
||||||
|
|
||||||
|
## Parser
|
||||||
|
|
||||||
|
The `Toy_Parser` structure takes a `Toy_Lexer` as an argument when initialized.
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Parser parser;
|
||||||
|
Toy_initParser(&parser, &lexer);
|
||||||
|
|
||||||
|
Toy_ASTNode* node = Toy_scanParser(&parser);
|
||||||
|
|
||||||
|
Toy_freeParser(&parser);
|
||||||
|
```
|
||||||
|
|
||||||
|
The parser pumps the lexer for tokens, one at a time, and converts them into structures called Abstract Syntax Trees (or ASTs for short). Each AST represents a single top-level statement within the Toy script. You'll know when the parser is finished with the lexer's source when `Toy_scanParser()` begins returning `NULL` pointers.
|
||||||
|
|
||||||
|
The AST Nodes produced by `Toy_scanParser()` must be freed manually, and the parser itself should not be used again.
|
||||||
|
|
||||||
|
## Compiler
|
||||||
|
|
||||||
|
The actual compilation phase has two steps - instruction writing and collation.
|
||||||
|
|
||||||
|
```c
|
||||||
|
size_t size;
|
||||||
|
Toy_Compiler compiler;
|
||||||
|
|
||||||
|
Toy_initCompiler(&compiler);
|
||||||
|
Toy_writeCompiler(&compiler, node); //node is an Toy_ASTNode
|
||||||
|
|
||||||
|
unsigned char* tb = Toy_collateCompiler(&compiler, &size);
|
||||||
|
|
||||||
|
Toy_freeCompiler(&compiler);
|
||||||
|
```
|
||||||
|
|
||||||
|
The writing step is the process in which AST nodes are compressed into bytecode instructions, while literal values are extracted and placed aside in a cache (usually in a compressed, intermediate state).
|
||||||
|
|
||||||
|
The collation phase, however, is when the bytecode instructions, along with the now flattened intermediate literals and function bodies are combined. The bytecode header specified in [Developing Toy](deep-dive/developing-toy) is placed at the beginning of this blob of bytes during this step.
|
||||||
|
|
||||||
|
The Toy bytecode (abbreviated to `tb`), along with the `size` variable indicating the size of the bytecode, are the result of the compilation. This bytecode can be saved into a file for later consumption by the host at runtime - you must ensure that any bytecode files have the `.tb` extension.
|
||||||
|
|
||||||
|
Alternatively, the bytecode in memory can be passed directly to the interpreter.
|
||||||
|
|
||||||
|
## Interpreter
|
||||||
|
|
||||||
|
The interpreter acts based on the contents of the bytecode given to it.
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Interpreter interpreter;
|
||||||
|
Toy_initInterpreter(&interpreter);
|
||||||
|
Toy_runInterpreter(&interpreter, tb, size);
|
||||||
|
Toy_freeInterpreter(&interpreter);
|
||||||
|
```
|
||||||
|
|
||||||
|
Exactly how it accomplishes this task is implementation dependant - as long as the results match expectations.
|
||||||
|
|
||||||
|
## REPL
|
||||||
|
|
||||||
|
An example program, called `toyrepl`, is provided alongside Toy's core. This program can handle many things, such as loading, compiling and executing Toy scripts; it's capable of compiling any valid Toy program for later use, even those that rely on non-standard libraries. It also has a number of commonly needed libraries provided.
|
||||||
|
|
||||||
|
To get a list of options, run `toyrepl -h`.
|
||||||
|
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
# Developing Toy
|
||||||
|
|
||||||
|
Here you'll find some of the implementation details.
|
||||||
|
|
||||||
|
# Bytecode
|
||||||
|
|
||||||
|
The output of Toy's compiler, and the input of the interpreter, is known as "bytecode". Here, I've attempted to fully document the layout of the canonical bytecode's structure, but since this was written after most of this was implemented, there may be small discrepancies present.
|
||||||
|
|
||||||
|
There are four main sections of the bytecode:
|
||||||
|
|
||||||
|
* Header
|
||||||
|
* Literal Cache
|
||||||
|
* Function Definitions
|
||||||
|
* Program Definition
|
||||||
|
|
||||||
|
## Bytecode Header Format
|
||||||
|
|
||||||
|
Note: The bytecode header format must not change.
|
||||||
|
|
||||||
|
This section is used to define what version of Toy is currently running, as well as to prevent any version/fork clashes.
|
||||||
|
|
||||||
|
The header consists of four values:
|
||||||
|
|
||||||
|
* TOY_VERSION_MAJOR
|
||||||
|
* TOY_VERSION_MINOR
|
||||||
|
* TOY_VERSION_PATCH
|
||||||
|
* TOY_VERSION_BUILD
|
||||||
|
|
||||||
|
The first three are single unsigned bytes, embedded at the beginning of the bytecode in sequence. These represent the major, minor and patch versions of the language. The fourth value is a null-terminated c-string of unspecified data, which is *intended* but not required to specify the time that the language's compiler was itself compiled. The build string can hold arbitrary data, such as the current maintainer's name, current fork of the language, or other versioning info.
|
||||||
|
|
||||||
|
There are some strict rules when interpreting these values (mimicking, but not conforming to [semver.org](https://semver.org/)):
|
||||||
|
|
||||||
|
* Under no circumstance, should you ever run bytecode whose major version is different - there are definitely broken APIs involved.
|
||||||
|
* Under no circumstance, should you ever run bytecode whose minor version is above the interpreter's minor version - the bytecode could potentially use unimplemented features.
|
||||||
|
* You may, at your own risk, attempt to run bytecode whose patch version is different.
|
||||||
|
* You may, at your own risk, attempt to run bytecode whose build version is different.
|
||||||
|
|
||||||
|
All interpreter implementations retain the right to reject any bytecode whose header data does not conform to the above specification.
|
||||||
|
|
||||||
|
The latest version information can be found in [toy_common.h](https://github.com/Ratstail91/Toy/blob/main/source/toy_common.h)
|
||||||
|
|
||||||
|
## Literal Cache
|
||||||
|
|
||||||
|
In Toy, a "Literal" is a value of some kind, be it an integer, or a dictionary, or even a variable name. Rather than embedding the same literal (potentially) many times within the bytecode, the "Literal Cache" was devised to act as an immutable, indexable repository of any literals needed. When bytecode is first loaded into the interpreter, the first thing that happens (after the header is parsed) is the reconstruction of the literal cache. The internal function `readInterpreterSections()` is responsible for this step.
|
||||||
|
|
||||||
|
The first `unsigned short` to be read from this section is `literalCount`, which defines the number of literals which are to be read. Once all literals have been read out of this section, the opcode `TOY_OP_SECTION_END` is expected to be consumed. Some preprocessor macros can also enable or disable debug printing functionality within the REPL.
|
||||||
|
|
||||||
|
The list of valid literal types are:
|
||||||
|
|
||||||
|
|
||||||
|
### TOY_LITERAL_NULL
|
||||||
|
|
||||||
|
This literal is simply inserted into the literal cache when encountered.
|
||||||
|
|
||||||
|
### TOY_LITERAL_BOOLEAN
|
||||||
|
|
||||||
|
This literal specifies that the next byte is its value, either true or false.
|
||||||
|
|
||||||
|
### TOY_LITERAL_INTEGER
|
||||||
|
|
||||||
|
This literal specifies that the next 4 bytes are its value, interpreted as a 32-bit integer.
|
||||||
|
|
||||||
|
### TOY_LITERAL_FLOAT
|
||||||
|
|
||||||
|
This literal specifies that the next 4 bytes are its value, interpreted as a 32-bit floating point integer.
|
||||||
|
|
||||||
|
### TOY_LITERAL_STRING
|
||||||
|
|
||||||
|
This literal specifies that the next collection of null terminated bytes are its value, interpreted as a null-terminated string.
|
||||||
|
|
||||||
|
### TOY_LITERAL_ARRAY_INTERMEDIATE
|
||||||
|
|
||||||
|
`TOY_LITERAL_ARRAY_INTERMEDIATE` specifies that the literal to be read is a flattened `LiteralArray`. A "flattened" compound literal does not actually store its contents, only references to its contents' positions within the literal cache.
|
||||||
|
|
||||||
|
To read this array, you must first read an `unsigned short` which specifies the size, then read that many additional `unsigned shorts`, which are indices. Finally, the original `LiteralArray` can be reconstructed using those indices, in order.
|
||||||
|
|
||||||
|
As the final step, the newly reconstructed `LiteralArray` is added to the literal cache.
|
||||||
|
|
||||||
|
### TOY_LITERAL_DICTIONARY_INTERMEDIATE
|
||||||
|
|
||||||
|
`TOY_LITERAL_DICTIONARY_INTERMEDIATE` specifies that the literal to be read is a flattened `LiteralDictionary`. A "flattened" compound literal does not actually store its contents, only references to its contents' positions within the literal cache.
|
||||||
|
|
||||||
|
To read this dictionary, you must first read an `unsigned short` which specifies the size (both keys and values), then read that many additional `unsigned shorts`, which are indices of keys and values. Finally, the original `LiteralDictionary` can be reconstructed using those key and value indices.
|
||||||
|
|
||||||
|
As the final step, the newly reconstructed `LiteralDictionary` is added to the literal cache.
|
||||||
|
|
||||||
|
### TOY_LITERAL_FUNCTION
|
||||||
|
|
||||||
|
When a `TOY_LITERAL_FUNCTION` is encountered, the next `unsigned short` to be read (the function index) should be converted into an integer literal, before having its type manually changed to `TOY_LITERAL_FUNCTION_INTERMEDIATE` for storage within the literal cache.
|
||||||
|
|
||||||
|
Functions will be processed properly in a later step - so this literal is added to the cache as a placeholder until that point.
|
||||||
|
|
||||||
|
### TOY_LITERAL_IDENTIFIER
|
||||||
|
|
||||||
|
This literal specifies that the next collection of null terminated bytes are its value, interpreted as a null-terminated string.
|
||||||
|
|
||||||
|
### TOY_LITERAL_TYPE
|
||||||
|
|
||||||
|
This literal specifies that the next byte is the type of a literal, and the following byte is a boolean specifying const-ness.
|
||||||
|
|
||||||
|
(This literal type may be integrated with `TOY_LITERAL_TYPE_INTERMEDIATE` at some point.)
|
||||||
|
|
||||||
|
### TOY_LITERAL_TYPE_INTERMEDIATE
|
||||||
|
|
||||||
|
This literal specifies that the next byte is the type of a literal, and the following byte is a boolean specifying const-ness.
|
||||||
|
|
||||||
|
Then, if the type is `TOY_LITERAL_ARRAY`, the following `unsigned short` is an index within the cache, representing the type of the contents.
|
||||||
|
|
||||||
|
Otherwise, if the type is `TOY_LITERAL_DICTIONARY`, the following two `unsigned short`s are indices within the cache, representing the types of the keys and values.
|
||||||
|
|
||||||
|
### TOY_LITERAL_INDEX_BLANK
|
||||||
|
|
||||||
|
This literal is simply inserted into the literal cache when encountered.
|
||||||
|
|
||||||
|
## Function Definitions
|
||||||
|
|
||||||
|
The second stage of `readInterpreterSections()` is used to read the third section of the given bytecode - the function definitions.
|
||||||
|
|
||||||
|
The first `unsigned short` is the number of functions present within this section. The second `unsigned short` is the length of this entire section (this one is not necessarily needed, and may be removed at some point).
|
||||||
|
|
||||||
|
For each `TOY_LITERAL_FUNCTION_INTERMEDIATE` within the cache, you must read an `unsigned short` as the size. Then, the following `size` block of bytecode is to be copied, wholesale, into the specified cached literal, before setting that literal's type to `TOY_LITERAL_FUNCTION`. While the function is not operational yet, it will be further processed when needed.
|
||||||
|
|
||||||
|
Once all function literals have been read out of this section, the opcode `TOY_OP_SECTION_END` is expected to be consumed.
|
||||||
|
|
||||||
|
## Program Definition
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Opcodes
|
||||||
|
|
||||||
|
TODO: finish these
|
||||||
|
|
||||||
|
|Toy_Opcode|Value|Description|
|
||||||
|
|---|---|---|
|
||||||
|
|TOY_OP_EOF|0|End of File signal. Used internally as an OK signal.|
|
||||||
|
|TOY_OP_PASS|1|Does nothing - a no-op.|
|
||||||
|
|TOY_OP_ASSERT|2|Pops `rhs`, `lhs`\* from the stack. `rhs` must be a string, identifiers are not accepted. If `lhs` is `null` or `false`, prints `rhs` to the assert output, and cancels the script.|
|
||||||
|
|TOY_OP_PRINT|3|Pops one\* literal from the stack, and prints it to the print output.|
|
||||||
|
|TOY_OP_LITERAL|4|Read one byte as an index; retrieve the given index's literal from the cache, and push it to the stack.|
|
||||||
|
|TOY_OP_LITERAL_LONG|5|Same as `TOY_OP_LITERAL`, except you read two bytes at a time.|
|
||||||
|
|TOY_OP_LITERAL_RAW|6|Pops one literal from the stack. If it's an identifier, explicitly replace it with it's value. Push the literal to the stack.|
|
||||||
|
|TOY_OP_NEGATE|7|Pops one\* literal from the stack. If it's an integer or float\*\*, push the negative value to the stack.|
|
||||||
|
|TOY_OP_ADDITION|8|Pops `rhs`\*, `lhs`\* from the stack. If both are strings, attempts to concatenate them, and push the result to the stack\*\*. Otherwise, will attempt\*\* to add `lhs` to `rhs` (coercing integers to floats if needed), and push the result to the stack.|
|
||||||
|
|TOY_OP_SUBTRACTION|9|Pops `rhs`\*, `lhs`\* from the stack. Attempts\*\* to subtract `rhs` from `lhs` (coercing integers to floats if needed), and push the result to the stack.|
|
||||||
|
|TOY_OP_MULTIPLICATION|10|Pops `rhs`\*, `lhs`\* from the stack. Attempts\*\* to multiply `lhs` by `rhs` (coercing integers to floats if needed), and push the result to the stack.|
|
||||||
|
|TOY_OP_DIVISION|11|Pops `rhs`\*, `lhs`\* from the stack. Attempts\*\* to divide `lhs` by `rhs` (coercing integers to floats if needed), and push the result to the stack.|
|
||||||
|
|TOY_OP_MODULO|12|Pops `rhs`\*, `lhs`\* from the stack. Attempts\*\* to modulo `lhs` by `rhs`, and push the result to the stack.|
|
||||||
|
|TOY_OP_GROUPING_BEGIN|13|Begin operating on a group; analagous to the `(` operator.|
|
||||||
|
|TOY_OP_GROUPING_END|14|Finish operating on a group; analagous to the `)` operator.|
|
||||||
|
|TOY_OP_SCOPE_BEGIN|15|Push an inner-scope onto the stack; analogous to the `{` operator.|
|
||||||
|
|TOY_OP_SCOPE_END|16|Push an inner-scope off of the stack; analogous to the `}` operator.|
|
||||||
|
|TOY_OP_TYPE_DECL|17|Not used.|
|
||||||
|
|TOY_OP_TYPE_DECL_LONG|18|Not used.|
|
||||||
|
|TOY_OP_VAR_DECL|19|Read one byte, retrieving that literal from the cache as `identifier`, and then read the next byte, retrieving that literal from the cache as `type`. Attempt\*\* to declare a variable with `identifier` as its name and `type` as its type, then pop the top of the stack\* as it's value (cooerce integer values to floats if `type` specifies a float).|
|
||||||
|
|TOY_OP_VAR_DECL_LONG|20|Same as `TOY_OP_VAR_DECL`, except you read two bytes at a time.|
|
||||||
|
|TOY_OP_FN_DECL|21|Read one byte, retrieving that literal from the cache as `identifier`, and then read the next byte, retrieving that literal from the cache as `function`. Attempt\*\* to declare a variable with `identifier` as its name and `fn const` as it's type, then set `function` as its value. The function's scope has the current scope as its ancestor.|
|
||||||
|
|TOY_OP_FN_DECL_LONG|22|Same as `TOY_OP_FN_DECL`, except you read two bytes at a time.|
|
||||||
|
|TOY_OP_VAR_ASSIGN|23|Pops `rhs`*, `lhs` from the stack. `lhs` must be an identifier. Attempts\*\* to store the value of `rhs`, with `lhs` as it's identifier.|
|
||||||
|
|TOY_OP_VAR_ADDITION_ASSIGN|24|Equivilent of `TOY_OP_ADDITION` followed by `TOY_OP_VAR_ASSIGN`.|
|
||||||
|
|TOY_OP_VAR_SUBTRACTION_ASSIGN|25|Equivilent of `TOY_OP_SUBTRACTION` followed by `TOY_OP_VAR_ASSIGN`.|
|
||||||
|
|TOY_OP_VAR_MULTIPLICATION_ASSIGN|26|Equivilent of `TOY_OP_MULTIPLICATION` followed by `TOY_OP_VAR_ASSIGN`.|
|
||||||
|
|TOY_OP_VAR_DIVISION_ASSIGN|27|Equivilent of `TOY_OP_DIVISION` followed by `TOY_OP_VAR_ASSIGN`.|
|
||||||
|
|TOY_OP_VAR_MODULO_ASSIGN|28|Equivilent of `TOY_OP_MODULO` followed by `TOY_OP_VAR_ASSIGN`.|
|
||||||
|
|TOY_OP_TYPE_CAST|29|Pops `val`\*, `type`\* from the stack. Attempts** to change the type of `val` to `type`, and push the result to the stack. Only works on `bool`, `int`, `float` and `string` types, and only if the value is in the correct format.|
|
||||||
|
|TOY_OP_TYPE_OF|30|Pops `rhs` from the stack. If `rhs` is an identifier, it determines the the type of the variable specified, otherwise it determines the type of `rhs` directly. The result is pushed to the stack.|
|
||||||
|
|TOY_OP_IMPORT|31|Pops `alias` and `identifier` from the stack. Attempts\*\* to invoke a hook specified by `identifier`, with the given `alias`.|
|
||||||
|
|TOY_OP_EXPORT_removed|32|Removed.|
|
||||||
|
|TOY_OP_INDEX|33|Pops `third`\*, `second`\*, `first`\* and `compound`\* from the stack. Pushes the value specified by `compound[first : second : third]` onto the stack.
|
||||||
|
|TOY_OP_INDEX_ASSIGN|34|Pops `assign`\*, `third`\*, `second`\*, `first`\* and `compound`\* from the stack. Sets the value specified by `compound[first : second : third]` to be `assign`.|
|
||||||
|
|TOY_OP_INDEX_ASSIGN_INTERMEDIATE|35|Due to the unfortunately bonkers nature of indexing, this is needed as part of `TOY_OP_INDEX_ASSIGN`. It acts the same as `TOY_OP_INDEX`, but leaves an additional copy of some variables on the stack for `TOY_OP_INDEX_ASSIGN` to process.
|
||||||
|
|TOY_OP_DOT|36|
|
||||||
|
|TOY_OP_COMPARE_EQUAL|37|
|
||||||
|
|TOY_OP_COMPARE_NOT_EQUAL|38|
|
||||||
|
|TOY_OP_COMPARE_LESS|39|
|
||||||
|
|TOY_OP_COMPARE_LESS_EQUAL|40|
|
||||||
|
|TOY_OP_COMPARE_GREATER|41|
|
||||||
|
|TOY_OP_COMPARE_GREATER_EQUAL|42|
|
||||||
|
|TOY_OP_INVERT|43|
|
||||||
|
|TOY_OP_AND|44|
|
||||||
|
|TOY_OP_OR|45|
|
||||||
|
|TOY_OP_JUMP|46|
|
||||||
|
|TOY_OP_IF_FALSE_JUMP|47|
|
||||||
|
|TOY_OP_FN_CALL|48|
|
||||||
|
|TOY_OP_FN_RETURN|49|
|
||||||
|
|TOY_OP_POP_STACK|50|
|
||||||
|
|TOY_OP_TERNARY|51|
|
||||||
|
|TOY_OP_FN_END|52|
|
||||||
|
|TOY_OP_SECTION_END|255|
|
||||||
|
|TOY_OP_PREFIX|256|Used internally.|
|
||||||
|
|TOY_OP_POSTFIX|257|Used internally.|
|
||||||
|
|
||||||
|
|
||||||
|
\*If this literal is an identifier, it is instead replaced with the correct given value from the current scope.
|
||||||
|
\*\*On failure, the script will print an error message to the error output and exit.
|
||||||
|
|
||||||
|
## Function Internal Structure
|
||||||
|
|
||||||
|
TODO: loose first argument, args & returns counters in the program space
|
||||||
|
|
||||||
|
# Parser Structure and Operations
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
# Compiler Structure and Operations
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
# Interpreter Structure and Operations
|
||||||
|
|
||||||
|
The Toy interpreter is, at it's core, just a big loop that reads bytes from memory and acts on them. Here, I'll break down exactly how it works, from a top-down perspective.
|
||||||
|
|
||||||
|
## Running the Interpreter
|
||||||
|
|
||||||
|
There are four main functions for running the interpreter:
|
||||||
|
|
||||||
|
* `Toy_initInterpreter`
|
||||||
|
* `Toy_runInterpreter`
|
||||||
|
* `Toy_resetInterpreter`
|
||||||
|
* `Toy_freeInterpreter`
|
||||||
|
|
||||||
|
First, `init` zeroes out the interpreter, sets up the printing functions, and delegates to `reset`, which in turn sets up the program's scope (and injects the default global functions). The initialization function is split into two this way so that `reset` can be used independently on a "dirty" interpreter to ready it for another script (or another run of the same script). `reset` is usually not needed and may be removed in future.
|
||||||
|
|
||||||
|
`free` simply frees the interpreter after execution.
|
||||||
|
|
||||||
|
Interestingly, `run` doesn't jump straight into execution. Instead, it first does its own bit of setup, before reading out the bytecode's header. If the header indicates an incompatible version, then the interpreter will refuse to run, to prevent mistakes from ruining the program.
|
||||||
|
|
||||||
|
`run` will also delegate to a function called `readInterpreterSections()`, which reads and reconstructs the "literalCache" - a collection of all values within the program (variable identifiers, variable values, function bytecode, etc.)
|
||||||
|
|
||||||
|
Next, `run` will pass to a function called `execInterpreter()`, which contains the program's loop.
|
||||||
|
|
||||||
|
Finally, `run` will automatically free the bytecode and associated literalCache (this may change at some point).
|
||||||
|
|
||||||
|
## Executing the Interpreter
|
||||||
|
|
||||||
|
Opcodes within the bytecode are 1 byte in length, and specify a single action to take. Each possible action is defined within the interpreter in a function that begins with `exec`, and are called from within a big looping switch statement. If any of these `exec` functions encounters an error, they can simply return false to break the loop.
|
||||||
|
|
||||||
|
The interpreter is stack-based; most, if not all, the actions are preformed on literals within a specially designated array called `stack`. For example:
|
||||||
|
|
||||||
|
```c
|
||||||
|
case TOY_OP_PRINT:
|
||||||
|
if (!execPrint(interpreter)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
|
||||||
|
When the opcode `TOY_OP_PRINT` is encountered, the top literal within the stack is popped off, and printed (more info on literals below).
|
||||||
|
|
||||||
|
```c
|
||||||
|
static bool execPrint(Toy_Interpreter* interpreter) {
|
||||||
|
//get the top literal
|
||||||
|
Toy_Literal lit = Toy_popLiteralArray(&interpreter->stack);
|
||||||
|
|
||||||
|
//if the top literal is an identifier, get it's value
|
||||||
|
Toy_Literal idn = lit;
|
||||||
|
if (TOY_IS_IDENTIFIER(lit) && Toy_parseIdentifierToValue(interpreter, &lit)) {
|
||||||
|
Toy_freeLiteral(idn);
|
||||||
|
}
|
||||||
|
|
||||||
|
//print as a string to the current print method
|
||||||
|
Toy_printLiteralCustom(lit, interpreter->printOutput);
|
||||||
|
|
||||||
|
//free the literal
|
||||||
|
Toy_freeLiteral(lit);
|
||||||
|
|
||||||
|
//continue the loop
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Identity Crisis
|
||||||
|
|
||||||
|
As in most programming languages, variables can be represented by names specified by the programmer; in Toy, these are called "identifiers". These identifiers can be passed around in place of their actual values, but can't be used directly. To retrieve a value, you must first "parse" it, like so:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Literal idn = literal; //cache the literal, just in case it's an identifier
|
||||||
|
if (TOY_IS_IDENTIFIER(literal) && Toy_parseIdentifierToValue(interpreter, &literal)) { //if it is an identifier, parse it...
|
||||||
|
Toy_freeLiteral(idn); //always remember to free the original identifier, otherwise you'll have a memory leak!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You will often see this pattern throughout the codebase.
|
||||||
|
|
||||||
|
## Other Utility Functions
|
||||||
|
|
||||||
|
Other functions are available at the top of the interpreter source file:
|
||||||
|
|
||||||
|
* printing utilities
|
||||||
|
* injection utilities
|
||||||
|
* parsing utilities
|
||||||
|
* bytecode utilities
|
||||||
|
* function utilities (these are at the very bottom of the source file)
|
||||||
|
|
||||||
|
# Literals
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
# Arrays & Dictionaries
|
||||||
|
|
||||||
|
TODO
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
# Embedding Toy
|
||||||
|
|
||||||
|
This tutorial assumes that you've managed to embed Toy into your program by following the tutorial [Building Toy](deep-dive/building-toy).
|
||||||
|
|
||||||
|
Here, we'll look at some ways in which you can utilize Toy's C API within your host program.
|
||||||
|
|
||||||
|
Be aware that when you create a new Literal object, you must call `Toy_freeLiteral()` on it afterwards! If you don't, your program will leak memory as Toy has no internal tracker for such things.
|
||||||
|
|
||||||
|
## Embedded API Macros
|
||||||
|
|
||||||
|
The functions intended for usage by the API are prepended with the C macro `TOY_API`. The exact value of this macro can vary by platform, or even be empty. In addition, the macros defined in [literal.h](https://github.com/Ratstail91/Toy/blob/main/source/toy_literal.h) are available for use when manipulating literals. These include:
|
||||||
|
|
||||||
|
* `TOY_IS_*` - check if a literal is a specific type
|
||||||
|
* `TOY_AS_*` - cast the literal to a specific type
|
||||||
|
* `TOY_TO_*_LITERAL` - create a literal of a specific type
|
||||||
|
* `TOY_IS_TRUTHY` - check if a literal is truthy
|
||||||
|
* `TOY_MAX_STRING_LENGTH` - the maximum length of a string in Toy (can be altered if needed)
|
||||||
|
|
||||||
|
## Structures Used Throughout Toy
|
||||||
|
|
||||||
|
The main unit of data within Toy's internals is `Toy_Literal`, which can contain any value that can exist within the Toy language - even identifiers. The exact implementation of `Toy_Literal` may change or evolve as time goes on, so it's recommended that you only interact with literals directly by using the macros and functions outlined [above](#embedded-api-macros). See the [types](getting-started/types) page for information on exactly what data types exist in Toy.
|
||||||
|
|
||||||
|
There are two main "compound structures" used within Toy's internals - the `Toy_LiteralArray` and `Toy_LiteralDictionary`. The former is an array of `Toy_Literal` instances stored sequentially in memory for fast lookups, while the latter is a key-value hashmap designed for efficient lookups based on a `Toy_Literal` key. These are both accessible via the language as well.
|
||||||
|
|
||||||
|
These compound structures hold **copies** of literals given to them, rather than taking ownership of existing literals.
|
||||||
|
|
||||||
|
## Compiling Toy Scripts
|
||||||
|
|
||||||
|
Please see [Compiling Toy](deep-dive/compiling-toy) for more information on the process of turning scripts into bytecode.
|
||||||
|
|
||||||
|
## Interpreting Toy
|
||||||
|
|
||||||
|
The `Toy_Interpreter` structure is the beating heart of Toy - You'll usually only need one interpreter, as it can be reset as needed.
|
||||||
|
|
||||||
|
The four basic functions are used as follows:
|
||||||
|
|
||||||
|
```c
|
||||||
|
//assume "tb" and "size" are the results of compilation
|
||||||
|
Toy_Interpreter interpreter;
|
||||||
|
|
||||||
|
Toy_initInterpreter(&interpreter);
|
||||||
|
Toy_runInterpreter(&interpreter, tb, size);
|
||||||
|
Toy_resetInterpreter(&interpreter); //You usually want to reset between runs with the same interpreter
|
||||||
|
Toy_freeInterpreter(&interpreter);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to this, you might also wish to "inject" a series of usable libraries into the interpreter, which can be `import`-ed within the language itself. This process only needs to be done once, after initialization, but before the first run.
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_injectNativeHook(&interpreter, "standard", Toy_hookStandard);
|
||||||
|
```
|
||||||
|
|
||||||
|
A "hook" is a callback function which is invoked when the given library is imported. `standard` is the most commonly used library available.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
```
|
||||||
|
|
||||||
|
Hooks can simply inject native functions into the current scope, or they can do other, more esoteric things (though this is not recommended).
|
||||||
|
|
||||||
|
```c
|
||||||
|
//a utility structure for storing the native C functions
|
||||||
|
typedef struct Natives {
|
||||||
|
char* name;
|
||||||
|
Toy_NativeFn fn;
|
||||||
|
} Natives;
|
||||||
|
|
||||||
|
int Toy_hookStandard(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Literal alias) {
|
||||||
|
//the list of available native C functions that can be called from Toy
|
||||||
|
Natives natives[] = {
|
||||||
|
{"clock", nativeClock},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
//inject each native C functions into the current scope
|
||||||
|
for (int i = 0; natives[i].name; i++) {
|
||||||
|
Toy_injectNativeFn(interpreter, natives[i].name, natives[i].fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Calling Toy from C
|
||||||
|
|
||||||
|
In some situations, you may find it convenient to call a function written in Toy from the host program. For this, a pair of utility functions have been provided:
|
||||||
|
|
||||||
|
```c
|
||||||
|
TOY_API bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_LiteralArray* arguments, Toy_LiteralArray* returns);
|
||||||
|
TOY_API bool Toy_callFn (Toy_Interpreter* interpreter, char* name, Toy_LiteralArray* arguments, Toy_LiteralArray* returns);
|
||||||
|
```
|
||||||
|
|
||||||
|
The first argument must be an interpreter. The third argument is a pointer to a `Toy_LiteralArray` containing a list of arguments to pass to the function, and the fourth is a pointer to a `Toy_LiteralArray` where the return values can be stored (an array is used here for a potential future feature). The contents of the argument array are consumed and left in an indeterminate state (but is safe to free), while the returns array always has one value - if the function did not return a value, then it contains a `null` literal.
|
||||||
|
|
||||||
|
The second arguments to these functions are either the function to be called as a `Toy_Literal`, or the name of the function within the interpreter's scope. The latter API simply finds the specified `Toy_Literal` if it exists and calls the former. As with most APIs, these return `false` if something went wrong.
|
||||||
|
|
||||||
|
## Memory Allocation
|
||||||
|
|
||||||
|
Depending on your platform of choice, you may want to alter how the memory is allocated within Toy. You can do this with the simple memory API:
|
||||||
|
|
||||||
|
```c
|
||||||
|
//signature returns the new pointer to be used
|
||||||
|
typedef void* (*Toy_MemoryAllocatorFn)(void* pointer, size_t oldSize, size_t newSize);
|
||||||
|
TOY_API void Toy_setMemoryAllocator(Toy_MemoryAllocatorFn);
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass it a function which matches the above signature, and it'll be callable via the following macros:
|
||||||
|
|
||||||
|
* `TOY_ALLOCATE(type, count)`
|
||||||
|
* `TOY_FREE(type, pointer)`
|
||||||
|
* `TOY_GROW_ARRAY(type, pointer, oldCount, newCount)`
|
||||||
|
* `TOY_SHRINK_ARRAY(type, pointer, oldCount, newCount)`
|
||||||
|
* `TOY_FREE_ARRAY(type, pointer, oldCount)`
|
||||||
|
|
||||||
|
Also, the following macros are provided to calculate the ideal array capacities (the latter of which is for rapidly growing structures):
|
||||||
|
|
||||||
|
* `TOY_GROW_CAPACITY(capacity)`
|
||||||
|
* `TOY_GROW_CAPACITY_FAST(capacity)`
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
# Roadmapping Toy
|
||||||
|
|
||||||
|
## Game And Game Engine
|
||||||
|
|
||||||
|
The Toy programming language was designed from the beginning as though it was supposed to be embedded into an imaginary game engine. Development on said engine and an associated game have proceeded smoothly so far.
|
||||||
|
|
||||||
|
## Microprocessor Support
|
||||||
|
|
||||||
|
A fork of the language intended to run on microprocessors is currently under development by 3rd parties. Support and advice will be provided to them where needed.
|
||||||
|
|
||||||
|
## Nice To Have Features
|
||||||
|
|
||||||
|
Potential future additions include:
|
||||||
|
|
||||||
|
* A threading library
|
||||||
|
* A timer library
|
||||||
|
* More random generation libraries (numbers, perlin noise, wave function collapse?)
|
||||||
|
* Multiple return values from functions
|
||||||
|
* Interpolated strings
|
||||||
|
|
||||||
|
Some of these have always been planned, but were sidelined or are incomplete for one reason or another.
|
||||||
|
|
||||||
|
## Nope Features
|
||||||
|
|
||||||
|
Some things that simply will not be added in the foreseeable future are:
|
||||||
|
|
||||||
|
* Classes & Structures
|
||||||
|
* Do-while loops
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Testing Toy
|
||||||
|
|
||||||
|
Toy uses GitHub CI/CD for comprehensive automated testing - however, all the tests are under `test/`, and can be executed by running `make test`. Doing so on Linux will attempt to use valgrind; to disable using valgrind, pass in `DISABLE_VALGRIND=true` as an environment variable. GitHub CI also has access to the option `make test-sanitized` which attempts to use memory sanitation.
|
||||||
|
|
||||||
|
The tests consist of a number of different situations and edge cases that have been discovered, and should probably be thoroughly tested one way or another. There are also several "-bugfix.toy" scripts that explicitly test a bug that has been encountered in one way or another to prevent regressions. The libs that are stored in `repl/` are also tested - their tests are under `/tests/scripts/lib`; some error cases are also checked by the mustfail tests in `/test/scripts/mustfail`.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Theorizing Toy
|
||||||
|
|
||||||
|
Sooner or later, every coder will try to create their own programming language. In my case, it took me over a decade and a half to realize that was even an option, but once I did, I read through a fantastic book called [Crafting Interpreters](https://craftinginterpreters.com/). This sent me down the rabbit hole, so to speak.
|
||||||
|
|
||||||
|
The main driving idea behind the Toy programming language has remained the same from the very beginning - I wanted a scripting language that could be embedded into a larger host program to allow for easy modification by the end user. Specifically, I wanted to enable easy modding of video games made in an imaginary game engine.
|
||||||
|
|
||||||
|
At the time of writing, I've started working on said engine, building it around Toy, and adjusting Toy to fit the engine as needed. I've also begun working on a game within that engine, as I believe the best way to build an engine is to build a game with it first. The engine has been dubbed "Box", and the game is called "Skylands".
|
||||||
|
|
||||||
|
But this post isn't about the engine; it's about Toy - I want to explain, in some detail, my thought processes when developing it. Let's start at the beginning.
|
||||||
|
|
||||||
|
```toy
|
||||||
|
print "Hello world";
|
||||||
|
```
|
||||||
|
|
||||||
|
I've drawn the `print` keyword from Crafting Interpreter's Lox language, for much the same reason as explained in the book - it's a simple and easy way to debug issues. You'll be able to print out any kind of value or variable from this statement - but it loses some context, like function implementations and the values of `opaque` literals.
|
||||||
|
|
||||||
|
Let's touch on variables quickly. There's about a dozen variable types that can be used, depending on how you count them. They include `bool`, `int`, `float`, `string` and a couple of compound types - but strict typing in Toy is completely optional (`any` is used by default). There are also functions, which are reusable chunks of code, and a pretty standard set of operators with their traditional precedences.
|
||||||
|
|
||||||
|
One way in which Toy stands out is the bytecode compilation step. Before execution, the source code must be compiled into an intermediate bytecode format (a trait also inherited from Lox) before it can be executed by the interpreter. The exact specifications of the bytecode formatting are not currently documented (yet). The intermediate bytecode stage and the independence of the interpreter from the compiler also allow unique features, such as the possibility of operating on a microcontroller.
|
||||||
|
|
||||||
|
One major native feature that is missing from Toy is an input system, such as from stdin. Instead, Toy is intended to receive its instructions from the host program, including any input needed. One such example would be a game controller library - something that takes in button presses and calls certain Toy functions to move a character around the game world. Toy is almost infinitely extensible via the C API's hook injection system.
|
||||||
|
|
||||||
|
I would like to keep the core language nice and simple, as much as possible - something you can explain with just the quick-start page. However, feedback and criticism are always welcome.
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[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"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# 404
|
|
||||||
|
|
||||||
Nobody here but us chickens!
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<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).
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Summary
|
|
||||||
|
|
||||||
- [Front Page](./README.md)
|
|
||||||
- [Quick Start](./quickstart.md)
|
|
||||||
- [Cheat Sheet](./cheatsheet.md)
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# 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: 11 KiB |
@@ -1,186 +0,0 @@
|
|||||||
# 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.*
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<!-- 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 }}" />
|
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
# Box Version Info Library
|
||||||
|
|
||||||
|
The box_version_info library simply provides version info about the current build of Box.
|
||||||
|
|
||||||
|
The box_version_info library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import box_version_info;
|
||||||
|
import box_version_info as box_version_info; //can be aliased
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Variables
|
||||||
|
|
||||||
|
### major
|
||||||
|
|
||||||
|
This variable is the major version number of `Box` at the time of compilation.
|
||||||
|
|
||||||
|
### minor
|
||||||
|
|
||||||
|
This variable is the minor version number of `Box` at the time of compilation.
|
||||||
|
|
||||||
|
### patch
|
||||||
|
|
||||||
|
This variable is the patch version number of `Box` at the time of compilation.
|
||||||
|
|
||||||
|
### build
|
||||||
|
|
||||||
|
This variable is a string representing the date and time that the engine was compiled.
|
||||||
|
|
||||||
|
### author
|
||||||
|
|
||||||
|
This variable contains the name of `Box`'s lead author, and his game studio.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
|
||||||
|
# Preface
|
||||||
|
|
||||||
|
The game engine is incomplete and still evolving, as such this page should be considered outdated at all times...
|
||||||
|
|
||||||
|
# Game Engine
|
||||||
|
|
||||||
|
The Toy programming language was designed from the beginning as an embedded scripting language for some kind of game engine. Different iterations have existed with different implementations, some of which could charitably be said to function. The current version, and the most stable and feature-complete so far, has reached a point where it needs some kind of concrete engine to improve any further.
|
||||||
|
|
||||||
|
Currently, the engine exists within its own repository, which can be found here:
|
||||||
|
|
||||||
|
[https://github.com/Ratstail91/Box](https://github.com/Ratstail91/Box)
|
||||||
|
|
||||||
|
## Engine Structure
|
||||||
|
|
||||||
|
The engine's APIs depend heavily on Toy's drive system, so that must be initialized at the beginning of the `main()` function.
|
||||||
|
|
||||||
|
The engine proper is invoked with just three lifecycle functions`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "box_engine.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
//initialize the drive system
|
||||||
|
Toy_initDriveSystem();
|
||||||
|
Toy_setDrivePath("scripts", "assets/scripts");
|
||||||
|
|
||||||
|
//invoke the engine
|
||||||
|
Box_initEngine("scripts:/init.toy"); //passing in the specified init file
|
||||||
|
Box_execEngine();
|
||||||
|
Box_freeEngine();
|
||||||
|
|
||||||
|
//clean up the drive system when you're done
|
||||||
|
Toy_freeDriveSystem();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The engine proper holds the following elements:
|
||||||
|
|
||||||
|
* SDL2 video and audio elements
|
||||||
|
* Frame rate controls
|
||||||
|
* Toy interpreter
|
||||||
|
* Input keyboard mapping dictionaries
|
||||||
|
* The root node
|
||||||
|
|
||||||
|
Video and audio in the game engine are handled by SDL2 and SDL2_mixer - this allows for the game to, in theory, be built on multiple platforms. For the time being, however, we're just targeting Windows due to a lack of testing machines.
|
||||||
|
|
||||||
|
The engine is calibrated to run at 60 FPS - if the "simulation time" falls too far behind the real time, then frame rendering is skipped in favour of speeding up the game logic. Likewise, if the simulation time runs too fast, then the simulation step is skipped instead and an extra frame is drawn. This process can lead to multiple skips in a row, in both directions.
|
||||||
|
|
||||||
|
The engine's interpreter is the core of the scripting system. Other interpreters may be generated and cleared during Toy's internal processes, but this one lasts for the duration of the program.
|
||||||
|
|
||||||
|
The keyboard inputs can be remapped using API functions - these mappings are stored and accessed within a pair of Toy dictionaries that have simply been embedded directly into the engine for easy access.
|
||||||
|
|
||||||
|
The nodes form a deep tree-like structure, with the "root node" at it's base.
|
||||||
|
|
||||||
|
## Node Structure
|
||||||
|
|
||||||
|
The fundamental building block of the engine's logic is the node structure - nodes can represent anything within the game world, from entities to abstract global systems. You can think of entities as having a 1:1 mapping to Toy scripts, as each one is given bytecode on initialization that populates its internals (external libraries like the runner library are still possible).
|
||||||
|
|
||||||
|
A node holds the following elements:
|
||||||
|
|
||||||
|
* A reference to it's parent
|
||||||
|
* An array of references to its children, and bookkeeping variables for tracking them
|
||||||
|
* A dictionary of functions defined in the Toy script
|
||||||
|
* A single SDL texture reference, and controls for rendering it (including as an animated sprite sheet)
|
||||||
|
* Position, motion and scale values
|
||||||
|
|
||||||
|
The nodes are deeply integrated with Toy scripts, while Toy was written specifically for this purpose. The tree-like structure of the nodes all exist entirely within the computer's heap memory as a result of Toy's memory model - this comes with performance drawbacks and clean up requirements. Child nodes should never be referenced directly, as they may be `NULL` references that have been released, but not yet pruned.
|
||||||
|
|
||||||
|
The rules of execution for scripts and functions is as follows:
|
||||||
|
|
||||||
|
* The script is executed during node initialization
|
||||||
|
* All functions (regardless of name) are stored within the node - effectively preserving the scope of the script as a whole
|
||||||
|
* These functions can now be invoked from elsewhere in the program
|
||||||
|
* Certain function names (listed below) are invoked at specific times during the game loop throughout the entire node tree
|
||||||
|
* If the specially named functions do not exist, the node is simply skipped
|
||||||
|
* Every function, which is intended to be called through `callNodeFn()` or at specific times in the loop, must take the `opaque` node as its first argument
|
||||||
|
|
||||||
|
## Special Function Names
|
||||||
|
|
||||||
|
The following functions, which are defined within the node scripts, are invoked at specific times within the game loop. Note that if you want code to execute *during* node creation, place it within the script's root scope. Variables that you want to persist between calls should also be placed in the script's root.
|
||||||
|
|
||||||
|
* `onLoad(node: opaque)`
|
||||||
|
* `onInit(node: opaque)`
|
||||||
|
* `onFrameStart(node: opaque)`
|
||||||
|
* `onUpdate(node: opaque, delta: int)`
|
||||||
|
* `onFrameEnd(node: opaque)`
|
||||||
|
* `onStep(node: opaque)`
|
||||||
|
* `onFree(node: opaque)`
|
||||||
|
* `onDraw(node: opaque)`
|
||||||
|
* `onKeyDown(node: opaque, event: string)`
|
||||||
|
* `onKeyUp(node: opaque, event: string)`
|
||||||
|
* `onMouseMotion(node: opaque, x: int, y: int, xrel: int, yrel: int)`
|
||||||
|
* `onMouseButtonDown(node: opaque, x: int, y: int, button: string)`
|
||||||
|
* `onMouseButtonUp(node: opaque, x: int, y: int, button: string)`
|
||||||
|
* `onMouseWheel(node: opaque, xrel: int, yrel: int)`
|
||||||
|
|
||||||
|
(These may change or expand as more input devices are added, and the engine matures.)
|
||||||
|
|
||||||
|
NOTE: `onLoad()` is invoked every time a node is loaded - but `onInit()` is only invoked once by the engine. After that, `initNode()` must be called manually on any node children that are loaded later.
|
||||||
|
|
||||||
|
# Engine Libraries
|
||||||
|
|
||||||
|
A series of libraries are provided to allow Toy to interface and control the engine. In addition, the libraries stored within Toy's `repl/` directory are also available (see the main page for the list).
|
||||||
|
|
||||||
|
During startup, the script named `init.toy` in the assets/scripts directory is executed. This file can be used to configure input mappings, as well as initializing the window and node tree.
|
||||||
|
|
||||||
|
* Engine Library
|
||||||
|
* Node Library
|
||||||
|
* Input Library
|
||||||
|
* Music Library
|
||||||
|
* Sound Library
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Math Library
|
||||||
|
|
||||||
|
The math library is a collection of mathematical functions and constants that provide a wide range of calculations that are commonly used. All functions in this library take either integers or floats as parameters and will return the result as a float.
|
||||||
|
|
||||||
|
The math library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import math;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Constants
|
||||||
|
|
||||||
|
### PI: float
|
||||||
|
|
||||||
|
This constant represents the ratio of a circle's circumference to its diameter. Its value is approximately `3.14159265358979323846`.
|
||||||
|
|
||||||
|
### E: float
|
||||||
|
|
||||||
|
This constant represents Euler's number, the base of natural logarithms. Its value is approximately `2.71828182845904523536`.
|
||||||
|
|
||||||
|
### EPSILON: float
|
||||||
|
|
||||||
|
This constant represents the acceptable amount of error when comparing floats with the functions provided by this library (see [Defined Comparison Functions](#defined-comparison-functions)). Its default value is `0.000001`.
|
||||||
|
|
||||||
|
### NAN: float
|
||||||
|
|
||||||
|
This constant represents "Not-a-Number", often returned when a calculation is impossible, e.g. `sqrt(-1)`.
|
||||||
|
|
||||||
|
### INFINITY: float
|
||||||
|
|
||||||
|
This constant represents an uncountable value.
|
||||||
|
|
||||||
|
## Defined Power Functions
|
||||||
|
|
||||||
|
### pow(x, y): float
|
||||||
|
|
||||||
|
This function returns `x` to the power of `y`.
|
||||||
|
|
||||||
|
### sqrt(x): float
|
||||||
|
|
||||||
|
This function returns the square root of `x`.
|
||||||
|
|
||||||
|
### qbrt(x): float
|
||||||
|
|
||||||
|
This function returns the cube root of `x`.
|
||||||
|
|
||||||
|
### hypot(x, y): float
|
||||||
|
|
||||||
|
This function returns the length of the hypotenuse, assuming `x` and `y` are the legs in a right-angle triangle.
|
||||||
|
|
||||||
|
## Defined Trigonometric Functions
|
||||||
|
|
||||||
|
### toRadians(d): float
|
||||||
|
|
||||||
|
This function converts `d` into radians.
|
||||||
|
|
||||||
|
### toDegrees(r): float
|
||||||
|
|
||||||
|
This function converts `r` into degrees.
|
||||||
|
|
||||||
|
### sin(x): float
|
||||||
|
|
||||||
|
This function returns the sine of `x`.
|
||||||
|
|
||||||
|
### cos(x): float
|
||||||
|
|
||||||
|
This function returns the cosine of `x`.
|
||||||
|
|
||||||
|
### tan(x): float
|
||||||
|
|
||||||
|
This function returns the tangent of `x`.
|
||||||
|
|
||||||
|
### asin(x): float
|
||||||
|
|
||||||
|
This function returns the arc sine of `x`.
|
||||||
|
|
||||||
|
### acos(x): float
|
||||||
|
|
||||||
|
This function returns the arc cosine of `x`.
|
||||||
|
|
||||||
|
### atan(x): float
|
||||||
|
|
||||||
|
This function returns the arc tangent of `x`.
|
||||||
|
|
||||||
|
## Defined Hyperbolic Functions
|
||||||
|
|
||||||
|
### sinh(x): float
|
||||||
|
|
||||||
|
This function returns the hyperbolic sine of `x`
|
||||||
|
|
||||||
|
### cosh(x): float
|
||||||
|
|
||||||
|
This function returns the hyperbolic cosine of `x`
|
||||||
|
|
||||||
|
### tanh(x): float
|
||||||
|
|
||||||
|
This function returns the hyperbolic tangent of `x`
|
||||||
|
|
||||||
|
### asinh(x): float
|
||||||
|
|
||||||
|
This function returns the inverse hyperbolic sine of `x`
|
||||||
|
|
||||||
|
### acosh(x): float
|
||||||
|
|
||||||
|
This function returns the inverse hyperbolic cosine of `x`
|
||||||
|
|
||||||
|
### atanh(x): float
|
||||||
|
|
||||||
|
This function returns the inverse hyperbolic tangent of `x`
|
||||||
|
|
||||||
|
## Defined Comparison Functions
|
||||||
|
|
||||||
|
### checkIsNaN(x): bool
|
||||||
|
|
||||||
|
This function returns true if `x` is NaN, otherwise it returns false.
|
||||||
|
|
||||||
|
### checkIsFinite(x): bool
|
||||||
|
|
||||||
|
This function returns true if `x` is finite, otherwise it returns false.
|
||||||
|
|
||||||
|
### checkIsInfinite(x): bool
|
||||||
|
|
||||||
|
This function returns true if `x` is Infinite, otherwise it returns false.
|
||||||
|
|
||||||
|
### epsilionCompare(x, y): bool
|
||||||
|
|
||||||
|
This function returns true if `x` and `y` are within `EPSILON` of each other, otherwise it returns false. This is very useful for comparing floating point values.
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
# Quick Start Guide
|
||||||
|
|
||||||
|
This guide is intended to get you writing Toy code as fast as possible. As such, it's more of a reference for experienced coders to know what is available and what isn't.
|
||||||
|
|
||||||
|
Toy programs begin at the top of the file, and continue until the end, unless an error is encountered.
|
||||||
|
|
||||||
|
## Print Keyword
|
||||||
|
|
||||||
|
This keyword prints values to stdout for debugging (this can be altered by the host program).
|
||||||
|
|
||||||
|
```
|
||||||
|
print "Hello World";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Names and Variables
|
||||||
|
|
||||||
|
Variables can store data of any kind, unless a type is specified; see [types](getting-started/types). Names can be up to 256 characters long; see [Reserved Keywords](#reserved-keywords) for a list of keywords that can't be used as a name.
|
||||||
|
|
||||||
|
```
|
||||||
|
var b = true;
|
||||||
|
var i = 42;
|
||||||
|
var f = 3.14;
|
||||||
|
var s = "Hello world";
|
||||||
|
```
|
||||||
|
|
||||||
|
Numbers (both integers and floats) can be delimited with underscores (`_`), to break them up visually, e.g. `100_000`.
|
||||||
|
|
||||||
|
Strings can be 4096 characters long, and the following characters can be escaped: `\n`, `\t`, `\\` and `\"`.
|
||||||
|
|
||||||
|
## Compounds
|
||||||
|
|
||||||
|
Larger containers of data are available - arrays and dictionaries. Arrays are collections of data stored sequentially, while dictionaries are hash-maps of key-value pairs:
|
||||||
|
|
||||||
|
```
|
||||||
|
var array = []; //define an array
|
||||||
|
var dict = [:]; //define a dictionary
|
||||||
|
|
||||||
|
dict["foo"] = "bar"; //you can use indexing to add to a dictionary
|
||||||
|
array.push(42); //you must use a function to push to an array
|
||||||
|
```
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
You can control the program flow with either `if`, `while` or `for`. The only falsy value is `false`.
|
||||||
|
|
||||||
|
```
|
||||||
|
if (check()) {
|
||||||
|
//do this
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//otherwise do this
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < 10) {
|
||||||
|
print i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
print i;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`continue` and `break` both behave as you'd expect.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
Functions are defined with the `fn` keyword, and can take any number of arguments. They can return a value as well.
|
||||||
|
|
||||||
|
```
|
||||||
|
fn combine(a, b, c) {
|
||||||
|
return [a, b, c];
|
||||||
|
}
|
||||||
|
|
||||||
|
print combine(1, 2, 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Variable number of parameters, called rest parameters, can be passed in as an array.
|
||||||
|
|
||||||
|
```
|
||||||
|
fn combine(...rest) {
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
print combine(1, 2, 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## UFCS and Global Functions
|
||||||
|
|
||||||
|
Functions can be called using the universal function call syntax, which is just syntactic sugar for a normal function call:
|
||||||
|
|
||||||
|
```
|
||||||
|
fn printMe(self) {
|
||||||
|
print self;
|
||||||
|
}
|
||||||
|
|
||||||
|
array.printMe();
|
||||||
|
```
|
||||||
|
|
||||||
|
There are several globally available functions provided by default:
|
||||||
|
|
||||||
|
```
|
||||||
|
set(self, key, value) //array, dictionary
|
||||||
|
get(self, key) //array, dictionary
|
||||||
|
push(self, value) //array
|
||||||
|
pop(self) //array
|
||||||
|
length(self) //array, dictionary, string
|
||||||
|
clear(self) //array, dictionary
|
||||||
|
```
|
||||||
|
|
||||||
|
## Slice Notation
|
||||||
|
|
||||||
|
When indexing a compound value, you can use slice notation to manipulate it's elements:
|
||||||
|
|
||||||
|
```
|
||||||
|
var greeting = "Hello world";
|
||||||
|
|
||||||
|
print greeting[::-1]; //dlrow olleH
|
||||||
|
|
||||||
|
greeting[0:4] = "Goodnight"; //changes greeting to equal "Goodnight world"
|
||||||
|
```
|
||||||
|
|
||||||
|
## External Libraries
|
||||||
|
|
||||||
|
The host may, at its own discretion, make external libraries available to the scripts. To access these, you can use the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
|
||||||
|
print clock(); //made available by "standard"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assertion Tests
|
||||||
|
|
||||||
|
For testing purposes, there is the `assert` keyword. `assert` takes two arguments, separated by a comma; if the first resolves to a truthy value, then the whole statement is a no-op. Otherwise, the second argument, which MUST be a string, is displayed as an error and the script exits.
|
||||||
|
|
||||||
|
```
|
||||||
|
var answer = 42;
|
||||||
|
|
||||||
|
assert answer == 42, "This will not be seen";
|
||||||
|
|
||||||
|
//both false and null trigger assert's exit condition
|
||||||
|
assert null, "This will be seen before the script exits";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reserved Keywords
|
||||||
|
|
||||||
|
The following list cannot be used as names, due to their significance (or potential later use) in the language.
|
||||||
|
|
||||||
|
* any
|
||||||
|
* as
|
||||||
|
* astype
|
||||||
|
* assert
|
||||||
|
* bool
|
||||||
|
* break
|
||||||
|
* class (reserved)
|
||||||
|
* const
|
||||||
|
* continue
|
||||||
|
* do (reserved)
|
||||||
|
* else
|
||||||
|
* export (reserved)
|
||||||
|
* false
|
||||||
|
* float
|
||||||
|
* fn
|
||||||
|
* for
|
||||||
|
* foreach (reserved)
|
||||||
|
* if
|
||||||
|
* import
|
||||||
|
* in (reserved)
|
||||||
|
* int
|
||||||
|
* null
|
||||||
|
* of (reserved)
|
||||||
|
* opaque
|
||||||
|
* print
|
||||||
|
* return
|
||||||
|
* string
|
||||||
|
* true
|
||||||
|
* type
|
||||||
|
* typeof
|
||||||
|
* var
|
||||||
|
* while
|
||||||
|
|
||||||
|
## Full List of Operators
|
||||||
|
|
||||||
|
The following mathematical operators are available. A definition is omitted here, as they are commonly used in most programming languages.
|
||||||
|
|
||||||
|
```
|
||||||
|
+ - * / % += -= *= /= %= ++(prefix) --(prefix) (postfix)++ (postfix)--
|
||||||
|
```
|
||||||
|
|
||||||
|
Likewise, the following logical operators are available (`&&` is more tightly bound than `||` due to historical reasons):
|
||||||
|
|
||||||
|
```
|
||||||
|
( ) [ ] { } ! != == < > <= >= && || ?:
|
||||||
|
```
|
||||||
|
|
||||||
|
Other operators used throughout the language are: the assignment, colon, semicolon, comma, dot and rest operators:
|
||||||
|
|
||||||
|
```
|
||||||
|
= : ; , . ...
|
||||||
|
```
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
# Random Library
|
||||||
|
|
||||||
|
The random library offers a number of functions geared towards producing pseudorandom values. This library has a concept called "generators", which are opaque objects used to generate a sequence of numbers from an initial integer seed. A seed can be generated from most values using the standard library `hash` function.
|
||||||
|
|
||||||
|
The random library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
import random;
|
||||||
|
|
||||||
|
var generator: opaque = createRandomGenerator(clock().hash());
|
||||||
|
```
|
||||||
|
|
||||||
|
The current implementation is minimal in nature, and will be expanded or replaced in future.
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### createRandomGenerator(seed: int): opaque
|
||||||
|
|
||||||
|
This function creates a new generator opaque based on the given seed. The same seed will produce the same sequence of pseudorandom outputs from different generators using `generateRandomNumber`.
|
||||||
|
|
||||||
|
Every generator must also be freed with `freeRandomGenerator`.
|
||||||
|
|
||||||
|
### generateRandomNumber(self: opaque): int
|
||||||
|
|
||||||
|
This function takes in a generator opaque, and returns a pseudorandom integer value.
|
||||||
|
|
||||||
|
This function also mutates the generator's internal state.
|
||||||
|
|
||||||
|
### freeRandomGenerator(self: opaque)
|
||||||
|
|
||||||
|
This function frees an existing generator opaque.
|
||||||
|
|
||||||
|
This function must be called on all generators before the program ends.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
# Runner Library
|
||||||
|
|
||||||
|
The runner library is used to execute one script from inside another. It also has functions that allow you to retrieve variables from the other script.
|
||||||
|
|
||||||
|
The runner library has a concept called a "dirty" script - dirty scripts are those which have already been run, and whose variables can be accessed. Dirty scripts must be reset before it is run again.
|
||||||
|
|
||||||
|
The runner library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```toy
|
||||||
|
import runner;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Functions
|
||||||
|
|
||||||
|
### loadScript(path: string): opaque
|
||||||
|
|
||||||
|
This is used to load an external script into an opaque variable.
|
||||||
|
|
||||||
|
This function does a lot of work:
|
||||||
|
|
||||||
|
* It validates the file path using the drive syntax
|
||||||
|
* It reads in the source code of the script file
|
||||||
|
* It compiles the source script into bytecode
|
||||||
|
* It constructs and initializes an Interpreter
|
||||||
|
* It packages it all into an opaque variable and returns it
|
||||||
|
|
||||||
|
### loadScriptBytecode(path: string): opaque
|
||||||
|
|
||||||
|
This is used to load an external bytecode file into an opaque variable.
|
||||||
|
|
||||||
|
This function does a lot of work:
|
||||||
|
|
||||||
|
* It validates the file path using the drive syntax
|
||||||
|
* It constructs and initializes an Interpreter
|
||||||
|
* It packages it all into an opaque variable and returns it
|
||||||
|
|
||||||
|
Note: This function resembles `loadScript()`, but skips the compilation step.
|
||||||
|
|
||||||
|
### runScript(self: opaque)
|
||||||
|
|
||||||
|
This function executes an external script, which must first be loaded into an opaque variable with either `loadScript()` or `loadScriptBytecode()`.
|
||||||
|
|
||||||
|
### getScriptVar(self: opaque, name: string): any
|
||||||
|
|
||||||
|
This function retrieves a variable from the top level of a script's environment.
|
||||||
|
|
||||||
|
## callScriptFn(self: opaque, name: string, ...rest)
|
||||||
|
|
||||||
|
This function retrieves a function from the top level of a script's environment, and calls it with `rest` as the argument list.
|
||||||
|
|
||||||
|
### resetScript(self: opaque)
|
||||||
|
|
||||||
|
This function resets the script so that it is no longer in a "dirty" state, and can be re-run using `runScript()`.
|
||||||
|
|
||||||
|
### freeScript(self: opaque)
|
||||||
|
|
||||||
|
This function frees a script's resources, cleaning up any memory that is no longer needed. Failing to call this will result in a memory leak.
|
||||||
|
|
||||||
|
### checkScriptDirty(self: opaque): bool
|
||||||
|
|
||||||
|
This function returns true if the script is "dirty", otherwise it returns false.
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
|
||||||
|
# Standard Library
|
||||||
|
|
||||||
|
The standard library offers a number of miscellaneous utility functions, which can be used for various purposes. These are the most commonly used functions, so the standard library is almost certain to be included in the host program.
|
||||||
|
|
||||||
|
The standard library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Misc. Functions
|
||||||
|
|
||||||
|
### clock(): string
|
||||||
|
|
||||||
|
This function returns a string representation of the current timestamp.
|
||||||
|
|
||||||
|
### hash(self: any): int
|
||||||
|
|
||||||
|
This function returns a hashed value of `self`.
|
||||||
|
|
||||||
|
This function uses the internal literal hashing algorithms. As such, the following can't be hashed:
|
||||||
|
|
||||||
|
* functions
|
||||||
|
* types
|
||||||
|
* opaques
|
||||||
|
* `null`
|
||||||
|
|
||||||
|
Any attempt to hash these will return -1, except `null` which returns 0.
|
||||||
|
|
||||||
|
## Defined Maths Functions
|
||||||
|
|
||||||
|
### abs(self): any
|
||||||
|
|
||||||
|
This function returns the absolute value of any integer or float passed in.
|
||||||
|
|
||||||
|
### ceil(self): int
|
||||||
|
|
||||||
|
This function returns the value of any integer or float passed in, rounded up.
|
||||||
|
|
||||||
|
### floor(self): int
|
||||||
|
|
||||||
|
This function returns the value of any integer or float passed in, rounded down.
|
||||||
|
|
||||||
|
### max(...): any
|
||||||
|
|
||||||
|
This function returns the value of the highest integer or float passed in. It can take any number of arguments.
|
||||||
|
|
||||||
|
### min(...): any
|
||||||
|
|
||||||
|
This function returns the value of the lowest integer or float passed in. It can take any number of arguments.
|
||||||
|
|
||||||
|
### round(self): int
|
||||||
|
|
||||||
|
This function returns the value of any integer or float passed in, rounded to the nearest whole number.
|
||||||
|
|
||||||
|
### sign(self): int
|
||||||
|
|
||||||
|
This function expects an integer or float as the value for `self`.
|
||||||
|
|
||||||
|
If `self` is below 0, this function returns -1. Otherwise, it returns 1.
|
||||||
|
|
||||||
|
### normalize(self): int
|
||||||
|
|
||||||
|
This function expects an integer or float as the value for `self`.
|
||||||
|
|
||||||
|
If `self` is below 0, this function returns -1. Otherwise, if `self` is above 0, this function returns 1. Otherwise it returns 0.
|
||||||
|
|
||||||
|
### clamp(value, min, max): any
|
||||||
|
|
||||||
|
This function expects integers or floats as the values for `value`, `min`, and `max`.
|
||||||
|
|
||||||
|
If `value` is smaller than `min`, this function will return `min`. Otherwise, if `value` larger than `max`, it will return `max`. Otherwise, it will return `value`.
|
||||||
|
|
||||||
|
### lerp(start, end, amount): any
|
||||||
|
|
||||||
|
This function expects integers or floats as the values for `value`, `min`, and `max`.
|
||||||
|
|
||||||
|
This function will return the value of `start` adjusted towards the value of `end` by `amount`, by interpreting `amount` as a fraction.
|
||||||
|
|
||||||
|
## Defined Compound Functions
|
||||||
|
|
||||||
|
### concat(self, other): any
|
||||||
|
|
||||||
|
This function only works when self and others are matching compounds (both arrays, dictionaries or strings). It returns a new compound of that kind, with the content of `other` appended to the content of `self`.
|
||||||
|
|
||||||
|
### containsKey(self: dictionary, key): bool
|
||||||
|
|
||||||
|
This function returns `true` if `self` contains the given `key`; otherwise, it returns false.
|
||||||
|
|
||||||
|
### containsValue(self, value): bool
|
||||||
|
|
||||||
|
This function returns `true` if `self` contains the given `value`; otherwise, it returns false.
|
||||||
|
|
||||||
|
### every(self, func: fn): bool
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument and a function as `func`. The argument `func` must take two arguments - the first is the index/key of the array/dictionary, and the second is the value. The contents of `self` are passed into `func`, one element at a time, until `func` returns `false`, at which point this function returns `false`. Otherwise, this function returns `true`.
|
||||||
|
|
||||||
|
### forEach(self, func: fn)
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument and a function as `func`. The argument `func` must take two arguments - the first is the index/key of the array/dictionary, and the second is the value. The contents of `self` are passed into `func` one element at a time.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
|
||||||
|
fn p(i, x) {
|
||||||
|
print x;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = [1, 3, 5];
|
||||||
|
|
||||||
|
a.forEach(p); //prints 1, 3, and 5 to stdout
|
||||||
|
```
|
||||||
|
|
||||||
|
### filter(self, func: fn): any
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument and a function as `func`. The argument `func` must take two arguments - the first is the index/key of the array/dictionary, and the second is the value. The contents of `self` are passed into `func`, one element at a time, and the function returns a new compound for every element that `func` returned a truthy value for.
|
||||||
|
|
||||||
|
### getKeys(self: dictionary): [any]
|
||||||
|
|
||||||
|
This function returns an array of all non-null keys stored within the dictionary. The order is undefined.
|
||||||
|
|
||||||
|
### getValues(self: dictionary): [any]
|
||||||
|
|
||||||
|
This function returns an array of all values with non-null keys stored within the dictionary. The order is undefined.
|
||||||
|
|
||||||
|
### indexOf(self: array, value): int
|
||||||
|
|
||||||
|
This function returns the first index within `self` that is equal to `value`, or `null` if none are found.
|
||||||
|
|
||||||
|
### map(self, func: fn): any
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument, and a function as `func`. The argument `func` must take two arguments - the first is the index/key of the array/dictionary, and the second is the value. It returns an array with the results of each call - the order of the results when called on a dictionary is undefined.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
|
||||||
|
fn increment(k, v) {
|
||||||
|
return v + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = [1, 2, 3];
|
||||||
|
|
||||||
|
print a.map(increment); //prints [2,3,4];
|
||||||
|
```
|
||||||
|
|
||||||
|
### reduce(self, default: any, func: fn): any
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument, a default value, and a function as `func`. The argument `func` takes three arguments - the first is the accumulator, the second is the index/key, and the third is the value. It applies the given function to every element of the array/dictionary, passing the result of each call as the accumulator to the next (the default value is used for the first call). Finally, the final value of the accumulator is returned to the caller.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
|
||||||
|
fn f(acc, k, v) {
|
||||||
|
return acc + v;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = [1, 2, 3, 4];
|
||||||
|
|
||||||
|
print a.reduce(0, f); //prints "10"
|
||||||
|
```
|
||||||
|
|
||||||
|
### some(self, func: fn): bool
|
||||||
|
|
||||||
|
This function takes either an array or a dictionary as the `self` argument, and a function as `func`. The argument `func` must take two arguments - the first is the index/key of the array/dictionary, and the second is the value. The contents of `self` are passed into `func`, one element at a time, until `func` returns `true`, at which point this function returns `true`. Otherwise, this function returns `false`.
|
||||||
|
|
||||||
|
### sort(self: array, func: fn)
|
||||||
|
|
||||||
|
This function takes an array as the `self` argument, and a comparison function as `func`. The argument `func` must take two arguments and return a truthy or falsy value. The contents of the array in `self` are sorted based on the results of `func`, as though the function was the less comparator function.
|
||||||
|
|
||||||
|
```
|
||||||
|
import standard;
|
||||||
|
|
||||||
|
fn less(a, b) {
|
||||||
|
return a < b;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = [4, 1, 3, 2];
|
||||||
|
|
||||||
|
print a.sort(less); //prints "[1, 2, 3, 4]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### toLower(self: string): string
|
||||||
|
|
||||||
|
This function returns a new string which is identical to the string `self`, except any uppercase letters are replaced with the corresponding lowercase letters.
|
||||||
|
|
||||||
|
### toString(self): string
|
||||||
|
|
||||||
|
This function returns a string representation of `self`. This is intended for arrays and dictionaries, but can theoretically work on any printable value.
|
||||||
|
|
||||||
|
If the resulting string is longer than `TOY_MAX_STRING_LENGTH` - 1, then it is truncated.
|
||||||
|
|
||||||
|
### toUpper(self: string):string
|
||||||
|
|
||||||
|
This function returns a new string which is identical to the string `self`, except any lowercase letters are replaced with the corresponding uppercase letters.
|
||||||
|
|
||||||
|
### trim(self: string, trimChars: string = " \t\n\r"): string
|
||||||
|
|
||||||
|
This function returns a new string which is identical to the string `self`, except any characters at the beginning or end of `self` which are present in the argument `trimChars` are removed. The argument `trimChars` is optional, and has the following characters as the default value:
|
||||||
|
|
||||||
|
* The space character
|
||||||
|
* The horizontal tab character
|
||||||
|
* The newline character
|
||||||
|
* The carriage return character
|
||||||
|
|
||||||
|
These characters used because they are the only control characters currently supported by Toy.
|
||||||
|
|
||||||
|
### trimBegin(self: string, trimChars: string = " \t\n\r"): string
|
||||||
|
|
||||||
|
This function is identical to `trim(self, trimChars)`, except it is only applied to the beginning of the first argument.
|
||||||
|
|
||||||
|
### trimEnd(self: string, trimChars: string = " \t\n\r"): string
|
||||||
|
|
||||||
|
This function is identical to `trim(self, trimChars)`, except it is only applied to the end of the first argument.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Toy Version Info Library
|
||||||
|
|
||||||
|
The toy_version_info library simply provides version info about the current build of Toy.
|
||||||
|
|
||||||
|
The toy_version_info library can usually be accessed with the `import` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
import toy_version_info;
|
||||||
|
import toy_version_info as toy_version_info; //can be aliased
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defined Variables
|
||||||
|
|
||||||
|
### major
|
||||||
|
|
||||||
|
This variable is the major version number of Toy at the time of compilation.
|
||||||
|
|
||||||
|
### minor
|
||||||
|
|
||||||
|
This variable is the minor version number of Toy at the time of compilation.
|
||||||
|
|
||||||
|
### patch
|
||||||
|
|
||||||
|
This variable is the patch version number of Toy at the time of compilation.
|
||||||
|
|
||||||
|
### build
|
||||||
|
|
||||||
|
This variable is a string representing the date and time that the interpreter was compiled.
|
||||||
|
|
||||||
|
### author
|
||||||
|
|
||||||
|
This variable contains the name of Toy's lead author, and his game studio.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Types
|
||||||
|
|
||||||
|
The type system in Toy is opt-in, but allows a lot of robust checks at runtime when needed. Types themselves are first-class citizens. To retrieve the type of an existing variable, use the `typeof` keyword.
|
||||||
|
|
||||||
|
```
|
||||||
|
print typeof value;
|
||||||
|
```
|
||||||
|
|
||||||
|
The types available are:
|
||||||
|
|
||||||
|
| Type | Signature | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| null | null | Represents a lack of any meaningful value |
|
||||||
|
| boolean | bool | Either true or false |
|
||||||
|
| integer | int | Any whole number. The limits are implementation dependent |
|
||||||
|
| float | float | Any floating point number. The limits are implementation dependent |
|
||||||
|
| string | string | A series of characters, forming text |
|
||||||
|
| array | n/a | A series of values arranged sequentially in memory, indexable with an integer |
|
||||||
|
| dictionary | n/a | A series of key-value pairs stored in a hash-table, indexable with the keys |
|
||||||
|
| function | fn | A chunk of reusable code, which can potentially return a value of some kind |
|
||||||
|
| type | type | The type of types |
|
||||||
|
| opaque | opaque | Arbitrary data passed from the host, which Toy can't natively understand |
|
||||||
|
| any | any | Can hold any value |
|
||||||
|
|
||||||
|
## Specifying Types For Variables
|
||||||
|
|
||||||
|
To specify a type for a variable, use `:` followed by the signature. In this example, the variable `total` can only ever hold integers (or `null`):
|
||||||
|
|
||||||
|
```
|
||||||
|
var total: int = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
To specify the type of an array or dictionary, use some variation of these signatures:
|
||||||
|
|
||||||
|
```
|
||||||
|
var array: [int] = [1, 2, 3]; //an array of integers
|
||||||
|
|
||||||
|
var dictionary: [string : int] = ["key":42]; //a dictionary of key-value pairs
|
||||||
|
```
|
||||||
|
|
||||||
|
Complex, hard-to-write types can be stored in variables, like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
//define a variable called "entry"
|
||||||
|
var entry: type = astype [string: [string]];
|
||||||
|
|
||||||
|
//define a phonebook which follows the above signature
|
||||||
|
var phonebook: entry = [
|
||||||
|
"Lucy": ["1234", "Cabbage Ln"],
|
||||||
|
"Bob": ["5678", "Candy Rd"]
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Const
|
||||||
|
|
||||||
|
Const-ness, or the ability to fix the value of a variable, is part of the type system. To define a constant, follow the type signature with the `const` keyword:
|
||||||
|
|
||||||
|
```
|
||||||
|
var ANSWER: int const = 42; //answer will never change
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also set the members of an array or dictionary as const, or the entire compound:
|
||||||
|
|
||||||
|
```
|
||||||
|
var members: [int const] = [1, 2, 3]; //1, 2 and 3 cannot be changed, but "members" can be modified or re-assigned
|
||||||
|
|
||||||
|
var everything: [int] const = [4, 5, 6]; //everything is now const
|
||||||
|
```
|
||||||
|
|
||||||
|
## Astype
|
||||||
|
|
||||||
|
Due to the syntax of Toy, when storing a complex type into a variable, you may need to use the `astype` keyword to differentiate the value from an array or dictionary.
|
||||||
|
|
||||||
|
```
|
||||||
|
var t: type = astype [int]; //t is a type, representing an array of integers
|
||||||
|
var u: type = [int]; //Error! it tried to assign an array with the sole entry "int"
|
||||||
|
```
|
||||||
|
|
||||||
|
## First-Class Citizens
|
||||||
|
|
||||||
|
Types are first-class citizens. What this means is that they can be used just like any other value, as well as being stored in variables and even returning from functions.
|
||||||
|
|
||||||
|
```
|
||||||
|
fn decide(question) {
|
||||||
|
if (question) {
|
||||||
|
return int;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var t = decide(true);
|
||||||
|
|
||||||
|
var number: t = 0; //what if it had been false?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Opaque Data
|
||||||
|
|
||||||
|
Sometimes, you may need to pass data through Toy that Toy can't normally handle. This data is called "opaque" data and is passed around by reference rather than value. Anything can be passed in as opaque data as long as it's represented as a void pointer in C.
|
||||||
|
|
||||||
|
```c
|
||||||
|
Toy_Literal opaque = TOY_TO_OPAQUE_LITERAL(&data, 0);
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
void* dataPtr = TOY_AS_OPAQUE(opaque);
|
||||||
|
|
||||||
|
int tag = TOY_GET_OPAQUE_TAG(opaque); //for identifying the data in the host
|
||||||
|
```
|
||||||
|
|
||||||
|
Managing and cleaning up opaque data is a task left entirely to the host program - you can do this with the opaque literal's tag.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<div align="center">
|
||||||
|
<image src="toylogo.png" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[](https://github.com/krgamestudios/Toy/actions/workflows/continuous-integration-v1.yml)
|
||||||
|
|
||||||
|
***Notice**: These docs are valid for version 1.x only - the version 2 docs can be found at [https://toylang.com/](https://toylang.com/).*
|
||||||
|
|
||||||
|
# Preamble
|
||||||
|
|
||||||
|
The Toy programming language is an imperative bytecode-intermediate embedded scripting language. It isn't intended to operate on its own, but rather as part of another program, the "host". This process is intended to allow a decent amount of easy customisation by the host's end user, by exposing logic in script files. Alternatively, binary files in a custom format can be used as well.
|
||||||
|
|
||||||
|
The host will provide all of the extensions needed on a case-by-case basis. Script files have the `.toy` file extension, while binary files have the `.tb` file extension.
|
||||||
|
|
||||||
|
The Toy reference implementation can be found on [github](https://github.com/krgamestudios/Toy/tree/v1).
|
||||||
|
|
||||||
|
```
|
||||||
|
fn makeCounter() { //declare a function like this
|
||||||
|
var total: int = 0; //declare a variable with a type like this
|
||||||
|
|
||||||
|
fn counter(): int { //declare a return type like this
|
||||||
|
return ++total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return counter; //closures are explicitly supported
|
||||||
|
}
|
||||||
|
|
||||||
|
var tally = makeCounter();
|
||||||
|
|
||||||
|
print tally(); //1
|
||||||
|
print tally(); //2
|
||||||
|
print tally(); //3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nifty Features
|
||||||
|
|
||||||
|
* Simple C-like syntax
|
||||||
|
* Bytecode intermediate compilation
|
||||||
|
* Optional, but robust type system (including `opaque` for arbitrary data)
|
||||||
|
* Functions and types are first-class citizens
|
||||||
|
* Import native libraries from the host
|
||||||
|
* Fancy slice notation for strings, arrays and dictionaries
|
||||||
|
* Can re-direct output, error and assertion failure messages
|
||||||
|
* Open source under the zlib license
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
* [Quick Start Guide](getting-started/quick-start-guide)
|
||||||
|
* [Types](getting-started/types)
|
||||||
|
* [Toy Version Info Library](getting-started/toy-version-info-library)
|
||||||
|
* [Standard Library](getting-started/standard-library)
|
||||||
|
* [Math Library](getting-started/math.md)
|
||||||
|
* [Random Library](getting-started/random-library)
|
||||||
|
* [Runner Library](getting-started/runner-library)
|
||||||
|
|
||||||
|
## Deep Dive Document
|
||||||
|
|
||||||
|
* [Theorizing Toy](deep-dive/theorizing-toy)
|
||||||
|
* [Building Toy](deep-dive/building-toy)
|
||||||
|
* [Embedding Toy](deep-dive/embedding-toy)
|
||||||
|
* [Compiling Toy](deep-dive/compiling-toy)
|
||||||
|
* [Developing Toy](deep-dive/developing-toy) (incomplete, but being expanded)
|
||||||
|
* [Testing Toy](deep-dive/testing-toy)
|
||||||
|
* [Roadmapping Toy](deep-dive/roadmapping-toy)
|
||||||
|
|
||||||
|
## Public C API
|
||||||
|
|
||||||
|
* [repl_tools.h](c-api/repl_tools_h.md)
|
||||||
|
* [drive_system.h](c-api/drive_system_h.md)
|
||||||
|
* [toy.h](c-api/toy_h.md)
|
||||||
|
* [toy_common.h](c-api/toy_common_h.md)
|
||||||
|
* [toy_compiler.h](c-api/toy_compiler_h.md)
|
||||||
|
* [toy_interpreter.h](c-api/toy_interpreter_h.md)
|
||||||
|
* [toy_lexer.h](c-api/toy_lexer_h.md)
|
||||||
|
* [toy_literal_array.h](c-api/toy_literal_array_h.md)
|
||||||
|
* [toy_literal_dictionary.h](c-api/toy_literal_dictionary_h.md)
|
||||||
|
* [toy_literal.h](c-api/toy_literal_h.md)
|
||||||
|
* [toy_memory.h](c-api/toy_memory_h.md)
|
||||||
|
* [toy_parser.h](c-api/toy_parser_h.md)
|
||||||
|
* [toy_reffunction.h](c-api/toy_reffunction_h.md)
|
||||||
|
* [toy_refstring.h](c-api/toy_refstring_h.md)
|
||||||
|
* [toy_scope.h](c-api/toy_scope_h.md)
|
||||||
|
|
||||||
|
## Game Engine
|
||||||
|
|
||||||
|
Note: This section is WIP.
|
||||||
|
|
||||||
|
* [Game Engine](game-engine/game-engine.md)
|
||||||
|
* [Box Version Info Library](game-engine/box-version-info-library.md)
|
||||||
|
* Engine Library
|
||||||
|
* Node Library
|
||||||
|
* Input Library
|
||||||
|
* Music Library
|
||||||
|
* Sound Library
|
||||||
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#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
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -1,100 +0,0 @@
|
|||||||
#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");
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "toy_ast.h"
|
|
||||||
|
|
||||||
void inspect_ast(Toy_Ast* astHandle);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "toy_bucket.h"
|
|
||||||
|
|
||||||
int inspect_bucket(Toy_Bucket** bucketHandle);
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
int inspect_bytecode(unsigned char* bytecode);
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "toy_vm.h"
|
|
||||||
|
|
||||||
void initStandardLibrary(Toy_VM*);
|
|
||||||
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 454 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 730 B |
@@ -1,16 +0,0 @@
|
|||||||
//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;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
fn output(arg) {
|
|
||||||
print arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
var array = ["alpha", "bravo", "charlie"];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
array.forEach(echo);
|
|
||||||
array.forEach(output);
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
//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!
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//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());
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn a(x) {
|
|
||||||
print x;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn b() {
|
|
||||||
return 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
a(b(), b());
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
//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);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
var randi: Int = 69420;
|
|
||||||
fn rand() {
|
|
||||||
return randi = randi * 1664525 + 1013904223;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var a = rand();
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#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)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
#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);
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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);
|
|
||||||