mirror of
https://github.com/CPunch/Cosmo.git
synced 2024-12-04 22:46:31 +00:00
Compare commits
107 Commits
7f5e3ae8dc
...
3ce098953e
Author | SHA1 | Date | |
---|---|---|---|
3ce098953e | |||
aaede37839 | |||
b3f3ad992d | |||
f51e072922 | |||
b66858d286 | |||
34c55eee87 | |||
a2d0b79c2e | |||
4de274d760 | |||
074e8b52cc | |||
56161a32e7 | |||
1544332b90 | |||
b73d865447 | |||
5a00d61646 | |||
83dfd36c5c | |||
2d889a37d1 | |||
0f0a8fb085 | |||
c03df8f506 | |||
38d9c499ea | |||
79d40d4bb0 | |||
dc44adba6e | |||
7c0bec5e6c | |||
a44450ff22 | |||
58c857d2ea | |||
38136d028f | |||
420cd3e856 | |||
bfde2d25cf | |||
97d40765ce | |||
37e4653d40 | |||
1ae473383d | |||
6ed5589513 | |||
1408a07b23 | |||
27818b3788 | |||
37e42eb60b | |||
cd3047c271 | |||
f26376e6f5 | |||
409937c1fa | |||
1d2ba217af | |||
e28ffe1c6c | |||
9c5270124d | |||
5fc9af5564 | |||
a1c58647ba | |||
5c3e24fc39 | |||
dcf6a09dae | |||
e0faa14b35 | |||
861607d6a8 | |||
10c9f92a06 | |||
8dfd7744c2 | |||
7f7bc7c6ad | |||
5169aca6d0 | |||
c34c5850e2 | |||
07ba4c731e | |||
788911130d | |||
c464a76849 | |||
8c6ba18848 | |||
93a09698a9 | |||
be49ec5af5 | |||
4fe437ea4e | |||
e854c5dbb3 | |||
c945c56482 | |||
89d443d767 | |||
54a959438b | |||
355842989b | |||
45f36e6e87 | |||
819e76b711 | |||
f116efa02c | |||
465f4d5e4a | |||
3efee51224 | |||
2836de090b | |||
8e278e3a7d | |||
c8cae03604 | |||
5d805e258b | |||
8df4cc65e3 | |||
7279623e24 | |||
517b0b9532 | |||
1df2e212cb | |||
|
84ec5d2aee | ||
6859ec98ad | |||
057716e0d4 | |||
b9e9dedac6 | |||
1813bbeb1b | |||
471589d379 | |||
70f931df18 | |||
c13db54d7d | |||
da85d640ce | |||
6bc4ec6b04 | |||
f92ffcecbd | |||
66d77bc54b | |||
afac75753f | |||
92b2db9678 | |||
|
b30616bb3c | ||
|
9e6c6038f1 | ||
|
1200e2d512 | ||
2050359d2f | |||
47051575cb | |||
7c7a2ed8d9 | |||
d1ea5c9703 | |||
7c6c075c2a | |||
14539057aa | |||
461e1d0c15 | |||
2e07715a7d | |||
bc43eaaa75 | |||
43a278e12d | |||
|
44f1674b09 | ||
|
27aedd2969 | ||
|
7caa696aa2 | ||
|
2e395065f8 | ||
|
5b8dc30bb8 |
26
.clang-format
Normal file
26
.clang-format
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Mozilla
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: Right
|
||||
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: None
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AlwaysBreakAfterReturnType: None
|
||||
BreakBeforeBraces: Mozilla
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 100
|
||||
IncludeBlocks: Regroup
|
||||
IndentPPDirectives: AfterHash
|
||||
...
|
||||
|
45
.github/workflows/check_build.yaml
vendored
Normal file
45
.github/workflows/check_build.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Check Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- src/**
|
||||
- util/**
|
||||
- main.c
|
||||
- Makefile
|
||||
- CMakeLists.txt
|
||||
- .github/workflows/check_build.yaml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: CMake
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
- name: Build
|
||||
run: cmake --build build --config Release
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Cosmo-Ubuntu
|
||||
path: build/bin
|
||||
|
||||
windows-build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Create CMake build files
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=MinSizeRel
|
||||
- name: Check compilation
|
||||
run: cmake --build build --config MinSizeRel
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Cosmo-Windows
|
||||
path: build/bin
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
*.o
|
||||
bin
|
||||
.vscode
|
||||
build
|
||||
.vscode
|
||||
CMakeFiles
|
27
CMakeLists.txt
Normal file
27
CMakeLists.txt
Normal file
@ -0,0 +1,27 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(cosmo VERSION 0.1.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
|
||||
IF (NOT WIN32)
|
||||
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address")
|
||||
set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -Wall")
|
||||
ENDIF()
|
||||
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT cosmo)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
file(GLOB sources CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.c)
|
||||
add_executable(${PROJECT_NAME} main.c ${PROJECT_SOURCE_DIR}/util/linenoise.c)
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${sources})
|
||||
|
||||
IF (NOT WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} m)
|
||||
ENDIF()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/src)
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE c_std_99)
|
12
Makefile
12
Makefile
@ -1,7 +1,7 @@
|
||||
# make clean && make && ./bin/cosmo
|
||||
|
||||
CC=clang
|
||||
CFLAGS=-fPIE -Wall -O3 -std=c11
|
||||
CFLAGS=-fPIE -Wall -Isrc -O3 #-g -fsanitize=address
|
||||
LDFLAGS=-lm #-fsanitize=address
|
||||
OUT=bin/cosmo
|
||||
|
||||
@ -19,6 +19,9 @@ CHDR=\
|
||||
src/cvm.h\
|
||||
src/cobj.h\
|
||||
src/cbaselib.h\
|
||||
src/cdump.h\
|
||||
src/cundump.h\
|
||||
util/linenoise.h\
|
||||
|
||||
CSRC=\
|
||||
src/cchunk.c\
|
||||
@ -33,7 +36,10 @@ CSRC=\
|
||||
src/cvm.c\
|
||||
src/cobj.c\
|
||||
src/cbaselib.c\
|
||||
src/main.c\
|
||||
src/cdump.c\
|
||||
src/cundump.c\
|
||||
util/linenoise.c\
|
||||
main.c\
|
||||
|
||||
COBJ=$(CSRC:.c=.o)
|
||||
|
||||
@ -45,4 +51,4 @@ $(OUT): $(COBJ) $(CHDR)
|
||||
$(CC) $(COBJ) $(LDFLAGS) -o $(OUT)
|
||||
|
||||
clean:
|
||||
rm -rf $(COBJ) $(OUT)
|
||||
rm -rf $(COBJ) $(OUT)
|
||||
|
35
README.md
35
README.md
@ -1,33 +1,49 @@
|
||||
# Cosmo
|
||||
[![Check Build](https://github.com/CPunch/Cosmo/actions/workflows/check_build.yaml/badge.svg?branch=main)](https://github.com/CPunch/Cosmo/actions/workflows/check_build.yaml)
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Usage: ./bin/cosmo [-clsr] [args]
|
||||
|
||||
available options are:
|
||||
-c <in> <out> compile <in> and dump to <out>
|
||||
-l <in> load dump from <in>
|
||||
-s <in...> compile and run <in...> script(s)
|
||||
-r start the repl
|
||||
```
|
||||
|
||||
## What is a 'cosmo'?
|
||||
|
||||
Cosmo is a portable scripting language loosely based off of Lua. Cosmo easily allows the user to extend the language through the use of Proto objects, which describe the behavior of Objects. For example the following is a simple Vector Proto which describes behavior for a Vector-like object.
|
||||
|
||||
```lua
|
||||
proto Vector
|
||||
function __init(self)
|
||||
func __init(self)
|
||||
self.vector = []
|
||||
self.x = 0
|
||||
end
|
||||
|
||||
function __index(self, key)
|
||||
func __index(self, key)
|
||||
return self.vector[key]
|
||||
end
|
||||
|
||||
function push(self, val)
|
||||
func push(self, val)
|
||||
self.vector[self.x++] = val
|
||||
end
|
||||
|
||||
function pop(self)
|
||||
func pop(self)
|
||||
return self.vector[--self.x]
|
||||
end
|
||||
end
|
||||
|
||||
var vector = Vector()
|
||||
let vector = Vector()
|
||||
|
||||
for (var i = 0; i < 4; i++) do
|
||||
for (let i = 0; i < 4; i++) do
|
||||
vector:push(i)
|
||||
end
|
||||
|
||||
for (var i = 0; i < 4; i++) do
|
||||
for (let i = 0; i < 4; i++) do
|
||||
print(vector:pop() .. " : " .. vector[i])
|
||||
end
|
||||
```
|
||||
@ -37,7 +53,4 @@ end
|
||||
2 : 1
|
||||
1 : 2
|
||||
0 : 3
|
||||
```
|
||||
|
||||
# C API
|
||||
The Cosmo C API is currently undocumented, however as soon as development has reached a stable state documentation on full language features and the C API will start.
|
||||
```
|
@ -46,8 +46,8 @@ There are two main types of for loops, the traditional c-style for loops, and th
|
||||
The c-style for loops starts with the `for` keyword, followed by '(' and an initializer, a conditional expression, and an iterator statement each separated by a ';', followed by ')' then the `do` keyword. The loop body is ended by the matching `end` keyword. Like so:
|
||||
|
||||
```
|
||||
var total = 0
|
||||
for (var i = 0; i < 10; i++) do
|
||||
let total = 0
|
||||
for (let i = 0; i < 10; i++) do
|
||||
total = total + i
|
||||
end
|
||||
print(total)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
Cosmo is a lightweight embeddable scripting language written in C11. Cosmo has comparable syntax to Lua 5.1, so if you are familiar with that syntax, learning Cosmo should be trivial. Cosmo has eccentric support for object-oriented programming, procedural programming, and functional programming. To see some examples that highlight the syntax, please see the `examples/` directory.
|
||||
Cosmo is a lightweight embeddable scripting language written in C99. Cosmo has comparable syntax to Lua 5.1, so if you are familiar with that syntax, learning Cosmo should be trivial. To see some examples that highlight the syntax, please see the `examples/` directory.
|
||||
|
||||
As Cosmo is an embeddable scripting language, it is designed to be extended by the host program (from here on referenced as 'host'.) Cosmo provides extensive C API for the host to set up the Cosmo VM, modify state, add custom Proto objects, define custom globals and more.
|
||||
|
||||
|
@ -6,17 +6,17 @@ For example, the following is a proto description for a Range Iterator Object, m
|
||||
|
||||
```
|
||||
proto Range
|
||||
function __init(self, x)
|
||||
func __init(self, x)
|
||||
self.max = x
|
||||
end
|
||||
|
||||
// __iter expects an iterable object to be returned (an object with __next defined)
|
||||
function __iter(self)
|
||||
func __iter(self)
|
||||
self.i = 0
|
||||
return self
|
||||
end
|
||||
|
||||
function __next(self)
|
||||
func __next(self)
|
||||
if self.i >= self.max then
|
||||
return nil // exit iterator loop
|
||||
end
|
||||
@ -50,7 +50,7 @@ When an object is called using the `()` operator, `__init` is called and a new O
|
||||
Objects hold fields, these fields can be grabbed using the '.' operator. Conversely, fields can also be set using the '.' and '=' operators. For example:
|
||||
|
||||
```
|
||||
var object = {
|
||||
let object = {
|
||||
field = "Hello world"
|
||||
}
|
||||
|
||||
@ -63,17 +63,17 @@ Objects have two main ways of being declared, first was just shown in the above
|
||||
|
||||
```
|
||||
proto Test
|
||||
function __init(self)
|
||||
func __init(self)
|
||||
// __init is required for an object to be instantiated, the 'self' passed is the
|
||||
// newly allocated object with it's proto already set
|
||||
end
|
||||
|
||||
function print(self)
|
||||
func print(self)
|
||||
print(self)
|
||||
end
|
||||
end
|
||||
|
||||
var objTest = Test()
|
||||
let objTest = Test()
|
||||
|
||||
// the ':' operator is used to invoke a method. if the '.' operator is used instead, the
|
||||
// raw closure will be given meaning the 'self' parameter won't be populated
|
||||
@ -94,11 +94,12 @@ that are called on special operators.
|
||||
| __init | `(<object>, ...)` | Newly crafted object is passed, called on instantiation |
|
||||
| __newindex | `(<object>, key, newValue)` | Called on new index using the '[] = ' operator |
|
||||
| __index | `(<object>, key)` -> `value` | Called on index using the '[]' operator |
|
||||
| __equal | `(<object>, <object>)` -> `<boolean>` | Called on equality fail if both protos have the same `__equal` metamethod defined |
|
||||
| __tostring | `(<object>)` -> `<string>` | Called when tostring() is called on an object |
|
||||
| __tonumber | `(<object>)` -> `<number>` | Called when tonumber() is called on an object |
|
||||
| __count | `(<object>)` -> `<number>` | Called when object is used with the '#' count operator |
|
||||
| __iter | `(<object>)` -> `<object>` | Called when used in a for-each loop with the 'in' operator |
|
||||
| __next | `(<object>)` -> `...` | Called on each iteration in a for-each loop, return values are passed as parameters in the loop |
|
||||
| __next | `(<object>)` -> `...` | Called on each iteration in a for-each loop, return values are passed as parameters in the loop |
|
||||
| __getter | `[<string> fieldName : <function> getterMethod]` | Indexed & called on field get using the '.' operator |
|
||||
| __setter | `[<string> fieldName : <function> setterMethod]` | Indexed & Called on field set using the '.' & '=' operators |
|
||||
> -> means 'returns'
|
@ -17,8 +17,8 @@
|
||||
| -------- | ---------------------------- | -------------------------------------- |
|
||||
| `!` | "Not" logical operator, flips the logical polarity. | `print(!true)` -> `false` |
|
||||
| `#` | "Count" calls '__count' metamethod on objects or gives the count of entries in tables | `print(#[1,2,3])` -> `3`, `print(#{__count = function(self) return self.x end, x = 1337})` -> `1337` |
|
||||
| `++` | Increment operator. | `var i = 0 print(++i .. ", " .. i++ .. ", " .. i)` -> `1, 1, 2` |
|
||||
| `--` | Decrement operator. | `var i = 0 print(--i .. ", " .. i-- .. ", " .. i)` -> `-1, -1, -2` |
|
||||
| `++` | Increment operator. | `let i = 0 print(++i .. ", " .. i++ .. ", " .. i)` -> `1, 1, 2` |
|
||||
| `--` | Decrement operator. | `let i = 0 print(--i .. ", " .. i-- .. ", " .. i)` -> `-1, -1, -2` |
|
||||
| `( ... )` | Call operator. Arguments should be separated using `,`. | `print("Hello", " ", "world!")` -> `Hello world!` |
|
||||
> -> means 'outputs'
|
||||
|
||||
|
72
docs/stdlib.md
Normal file
72
docs/stdlib.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Standard Library
|
||||
|
||||
Cosmo comes with a standard library which is broken into separate modules where each can be selectively loaded using the C API.
|
||||
|
||||
## Base Library
|
||||
|
||||
Includes misc. functions. The "junk drawer" of the standard library. Without these functions however, Cosmo would be severely limited in functionality.
|
||||
|
||||
| Name | Type | Behavior | Example |
|
||||
| ------------ | ------------------------------------------------ | ----------------------------------------------------------- | ---------------- |
|
||||
| print | `(...)` | Writes primitives to stdout, if a `<ref>` is passed, tostring() is invoked before outputting | `print("Hello world!")` |
|
||||
| type | `(<ANY>)` -> `<string>` | Returns the passed arguments datatype as a string | `type(1)` -> `"<number>"` |
|
||||
| tonumber | `(<ANY>)` -> `<number>` | Converts the datatype to a `<number>`, if a `<ref>` is passed `__tonumber` metamethod is invoked | `tonumber("12")` -> `12` |
|
||||
| tostring | `(<ANY>)` -> `<string>` | Converts the datatype to a `<string>`, if a `<ref>` is passed `__tostring` metamethod is invoked | `tostring(12)` -> `"12"` |
|
||||
| error | `(<string>)` | Throws an error with the passed `<string>` | `error("error!")` |
|
||||
| pcall | `(<callable>)` -> `<bool>, <error> or <ANY>` | Tries a protected call on the passed function, if an error is thrown, `<bool>` will be false and the 2nd result will be the error message | `pcall(error("Hello world!"))` -> `false, "Hello world!"` |
|
||||
| assert | `(<bool>, <string>)` | If the passed `<bool>` is false, an error is thrown, optionally uses custom error message | `assert(1 == 1, "Error Message!")` |
|
||||
| loadstring | `(<string>)` -> `<boolean>, <function> or <error>` | If the `<string>` compiled successfully, 1st result will be true and the 2nd result will be the newly compiled function. If there was a compiler/lexer error, the 1st result will be false and the 2nd result will be the error | `loadstring("print(\"hi\")")()` |
|
||||
> -> means 'returns'
|
||||
|
||||
## String Library
|
||||
|
||||
Includes functions and methods to manipulate strings. When this library is loaded all <string> objects have their proto's set to the string.* object. Enabling
|
||||
you to invoke the API directly on <string> objects without using `string.*`, eg. `"Hello":len()` is the same as `string.len("Hello")`.
|
||||
|
||||
| Name | Type | Behavior | Example |
|
||||
| ------------ | ------------------------------------------------ | ----------------------------------------------------------- | ---------------- |
|
||||
| string.len | `(<string>)` -> `<number>` | Returns the length of the passed string | `"hi":len()` -> `2` |
|
||||
| string.sub | `(str<string>, start<number>[, length<number>])` -> `<string>` | Makes a substring of `str` starting at `start` with length of `length or str:len() - start` | `"Hello World":sub(6)` -> `"World"` |
|
||||
| string.find | `(str<string>, find<string>[, start<number>])` -> `index<number>` | Searches for first occurrence of `find` in `str` starting at `start or 0`. Returns index or -1 if failed | `"Hello world":find("wo")` -> `6` |
|
||||
| string.split | `(str<string>, splitter<string>)` -> `[<string>, ...]` | Splits `str` into substrings using `splitter` as the splitter. Returns an array (table) starting at 0 of the strings. | `"Hello world":split(" ")` -> `["Hello", "world"]` |
|
||||
| string.byte | `(str<string>)` -> `<number>` | Returns the byte of the first character in the string. | `"A":byte()` -> `65` |
|
||||
| string.char | `(byte<number>)` -> `<string>` | Returns a 1 character string of the byte passed. | `string.char(65)` -> `"A"` |
|
||||
| string.rep | `(str<string>, times<number>)` -> `<string>` | Repeats the string and returns the newly allocated string | `("A" .. "B"):rep(2)` -> "ABAB" |
|
||||
> -> means 'returns'
|
||||
|
||||
## Math Library
|
||||
|
||||
Includes functions to do some common algebraic operations.
|
||||
|
||||
| Name | Type | Behavior | Example |
|
||||
| ------------ | ------------------------------------------------ | --------------------------------------------------- | ------------------------ |
|
||||
| math.abs | `(X<number>)` -> `<number>` | Returns the absolute value of X | `math.abs(-2)` -> `2` |
|
||||
| math.floor | `(X<number>)` -> `<number>` | Rounds down to the nearest integer | `math.floor(5.3)` -> `5` |
|
||||
| math.ceil | `(X<number>)` -> `<number>` | Rounds up to the nearest integer | `math.ceil(5.3)` -> `6` |
|
||||
| math.rad | `(Deg<number>)` -> `<number>` | Converts degrees to radians | `math.rad(180)` -> `3.14159` |
|
||||
| math.deg | `(Rad<number>)` -> `<number>` | Converts radians to degrees | `math.deg(3.14159)` -> `180` |
|
||||
| math.sin | `(Rad<number>)` -> `<number>` | Returns the sine of radian `Rad` | `math.sin(math.rad(90))` -> `1` |
|
||||
| math.cos | `(Rad<number>)` -> `<number>` | Returns the cosine of radian `Rad` | `math.cos(math.rad(180))` -> `-1` |
|
||||
| math.tan | `(Rad<number>)` -> `<number>` | Returns the tangent of radian `Rad` | `math.tan(math.rad(45))` -> `1` |
|
||||
| math.asin | `(sin<number>)` -> `<number>` | Returns the arc sine of radian `Rad` | `math.deg(math.asin(1))` -> `90` |
|
||||
| math.acos | `(cos<number>)` -> `<number>` | Returns the arc cosine of radian `Rad` | `math.deg(math.acos(-1))` -> `180` |
|
||||
| math.atan | `(tan<number>)` -> `<number>` | Returns the arc tangent of radian `Rad` | `math.deg(math.atan(1))` -> `45` |
|
||||
> -> means 'returns'
|
||||
|
||||
## OS Library
|
||||
|
||||
Includes functions that interact with the operating system.
|
||||
|
||||
| Name | Type | Behavior | Example |
|
||||
| ------------ | ------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------ |
|
||||
| os.open | `(path<string>[, mode<string>])` -> `<bool>, <obj>` | Opens a file at `path` and returns a file object. Specify mode to be "r" or "w" optionally, defaults to "r". | `os.open("test.txt")` -> `true, <file>` |
|
||||
| os.time | `()` -> `<number>` | Returns the system time in Epoch format | `os.time()` -> `1.61691e+09` |
|
||||
| os.system | `(cmd<string>)` -> `<number>` | Runs a system command as if it were a terminal and returns the exit code | `os.system("mkdir test")` -> `0` |
|
||||
> -> means 'returns'
|
||||
|
||||
File objects have the following methods:
|
||||
| Name | Type | Behavior | Example |
|
||||
| ------------ | ------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------ |
|
||||
| file:read | `(amt<number> or "a")` -> `<string>` | Reads `amt` bytes from the file and returns them as a string. If `"a"` is passed, the entire file is read. | `file:read("a")` -> `"Hello world!"` |
|
||||
| file:write | `(data<string>)` -> `<nil>` | Writes `data` to file. | `file:write("hello world!")` -> `<nil>` |
|
||||
> -> means 'returns'
|
@ -17,5 +17,5 @@ There are two main types of datatypes in Cosmo, primitives and references. Primi
|
||||
| String | A string of characters | `"ABC"`, `"\x41\x42\x43"`, `"\b1000001\b1000010\b1000011"` |
|
||||
| Object | A stateful data structure. See `objects.md`. | `{x = 3}`, `proto Test end` |
|
||||
| Table | A generic data structure. | `[1,2,3]`, `[1 = "hello", "two" = "world"]` |
|
||||
| Function | A callable routine. | `function() print("Hello world!") end` |
|
||||
| func | A callable routine. | `function() print("Hello world!") end` |
|
||||
> There are some other reference datatypes that are used internally, however these will remain undocumented until they are accessible by the user
|
@ -1,8 +1,8 @@
|
||||
// just testing continues and breaks
|
||||
|
||||
for (var x = 0; x < 700; x++) do
|
||||
for (var i = 0; true; i++) do
|
||||
var str = i .. "." .. x
|
||||
for (let x = 0; x < 700; x++) do
|
||||
for (let i = 0; true; i++) do
|
||||
let str = i .. "." .. x
|
||||
if (i == 998) then
|
||||
print(i .. " reached")
|
||||
break // exits the loop
|
||||
@ -13,9 +13,9 @@ for (var x = 0; x < 700; x++) do
|
||||
end
|
||||
|
||||
// same example as the for loop but done manually using a while loop
|
||||
var i = 0
|
||||
let i = 0
|
||||
while true do
|
||||
var str = i .. "." .. x
|
||||
let str = i .. "." .. x
|
||||
if (i++ == 998) then
|
||||
print("done")
|
||||
break
|
||||
|
40
examples/compare.cosmo
Normal file
40
examples/compare.cosmo
Normal file
@ -0,0 +1,40 @@
|
||||
let strtable = []
|
||||
let strLen = 4 // length of all strings to generate
|
||||
let AByte = "A":byte() // grabs the ascii value of 'A'
|
||||
|
||||
proto stringBuilder
|
||||
func __init(self, length)
|
||||
self.len = length
|
||||
end
|
||||
|
||||
// we are the iterator object lol
|
||||
func __iter(self)
|
||||
self.x = 0
|
||||
return self
|
||||
end
|
||||
|
||||
func __next(self)
|
||||
let x = self.x++
|
||||
|
||||
// if we've generated all the possible strings, return nil ending the loop
|
||||
if x >= 26 ^ self.len then
|
||||
return nil
|
||||
end
|
||||
|
||||
// generate the string
|
||||
let str = ""
|
||||
for (let i = 0; i < self.len; i++) do
|
||||
str = string.char(AByte + (x % 26)) .. str
|
||||
|
||||
x = math.floor(x / 26)
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
// generate a bunch of strings & populate the table
|
||||
print("generating " .. 26 ^ strLen .. " strings...")
|
||||
for str in stringBuilder(strLen) do
|
||||
strtable[str] = true
|
||||
end
|
@ -1,4 +1,4 @@
|
||||
local function fib(num)
|
||||
local func fib(num)
|
||||
if num <= 1 then
|
||||
return num
|
||||
else
|
||||
@ -6,6 +6,6 @@ local function fib(num)
|
||||
end
|
||||
end
|
||||
|
||||
for (var i = 1; i < 40; i++) do
|
||||
for (let i = 1; i < 40; i++) do
|
||||
print("The fib number of " .. i .. " is " .. fib(i))
|
||||
end
|
@ -1,13 +1,13 @@
|
||||
var object = {
|
||||
let object = {
|
||||
__setter = [
|
||||
"field1" = function(self, val)
|
||||
"field1" = func(self, val)
|
||||
print("setter for field1 called!")
|
||||
self.x = val
|
||||
end
|
||||
],
|
||||
|
||||
__getter = [
|
||||
"field1" = function(self)
|
||||
"field1" = func(self)
|
||||
print("getter for field1 called!")
|
||||
|
||||
return self.x + 1
|
||||
|
@ -1,22 +1,22 @@
|
||||
proto Vector
|
||||
function __init(self)
|
||||
func __init(self)
|
||||
self.vector = []
|
||||
self.x = 0
|
||||
end
|
||||
|
||||
function push(self, val)
|
||||
func push(self, val)
|
||||
self.vector[self.x++] = val
|
||||
end
|
||||
|
||||
function pop(self)
|
||||
func pop(self)
|
||||
return self.vector[--self.x]
|
||||
end
|
||||
|
||||
function __index(self, key)
|
||||
func __index(self, key)
|
||||
return self.vector[key]
|
||||
end
|
||||
|
||||
function __iter(self)
|
||||
func __iter(self)
|
||||
// you don't *have* to make a new object, i just wanted to show off anonymous functions
|
||||
return {__next = (function(self)
|
||||
return self.vector[self.iterIndex++]
|
||||
@ -27,9 +27,9 @@ proto Vector
|
||||
end
|
||||
end
|
||||
|
||||
var vector = Vector()
|
||||
let vector = Vector()
|
||||
|
||||
for (var i = 0; i < 100000; i++) do
|
||||
for (let i = 0; i < 100000; i++) do
|
||||
vector:push(i)
|
||||
end
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
proto Range
|
||||
function __init(self, x)
|
||||
func __init(self, x)
|
||||
self.max = x
|
||||
end
|
||||
|
||||
function __iter(self)
|
||||
func __iter(self)
|
||||
self.i = 0
|
||||
return self
|
||||
end
|
||||
|
||||
function __next(self)
|
||||
func __next(self)
|
||||
if self.i >= self.max then
|
||||
return nil // exit iterator loop
|
||||
end
|
||||
|
6
examples/reader.cosmo
Normal file
6
examples/reader.cosmo
Normal file
@ -0,0 +1,6 @@
|
||||
local err, file = os.open("LICENSE.md")
|
||||
if err then
|
||||
print("failed to open file")
|
||||
end
|
||||
|
||||
print(file:read("a"))
|
@ -1,15 +1,15 @@
|
||||
proto Test
|
||||
function __init(self, x)
|
||||
func __init(self, x)
|
||||
self.x = x
|
||||
end
|
||||
|
||||
function print(self)
|
||||
func print(self)
|
||||
print(self.x)
|
||||
end
|
||||
end
|
||||
|
||||
// stressing the GC
|
||||
for (var i = 0; ; i++) do
|
||||
var x = Test("Hello world " .. i)
|
||||
for (let i = 0; i < 100000; i++) do
|
||||
let x = Test("Hello world " .. i)
|
||||
x:print()
|
||||
end
|
@ -1,7 +1,7 @@
|
||||
var words = "hello world! this is a sentence with words separated by space":split(" ")
|
||||
let words = "hello world! this is a sentence with words separated by space":split(" ")
|
||||
|
||||
var str = ""
|
||||
for (var i = 0; i < #words; i++) do
|
||||
let str = ""
|
||||
for (let i = 0; i < #words; i++) do
|
||||
str = str .. words[i]
|
||||
print(words[i])
|
||||
end
|
||||
|
@ -1,12 +1,12 @@
|
||||
// crafts a dummy proto
|
||||
proto test
|
||||
function __init(self) end
|
||||
func __init(self) end
|
||||
end
|
||||
|
||||
// instance of test
|
||||
var obj = test()
|
||||
let obj = test()
|
||||
|
||||
test.__index = function(self, key)
|
||||
test.__index = func(self, key)
|
||||
print("__index called!")
|
||||
if (key == "lol") then
|
||||
return 9001
|
||||
|
45
examples/testsuite.cosmo
Normal file
45
examples/testsuite.cosmo
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
This script tests cosmo and makes sure everything still runs correctly. Pretty minimal for now
|
||||
*/
|
||||
|
||||
print("starting Testsuite...")
|
||||
|
||||
// tests the string.* library
|
||||
|
||||
assert("Hello world!":sub(6) == "world!", "string.sub() failed!")
|
||||
assert("A":rep(6) == "AAAAAA", "string.rep() failed!")
|
||||
|
||||
// tests some basic PEMDAS arithmetic
|
||||
|
||||
assert(2 * (2 + 6) == 16, "PEMDAS check #1 failed!")
|
||||
assert(2 / 5 + 3 / 5 == 1, "PEMDAS check #2 failed!")
|
||||
|
||||
// iterator test
|
||||
|
||||
proto Range
|
||||
func __init(self, x)
|
||||
self.max = x
|
||||
end
|
||||
|
||||
func __iter(self)
|
||||
self.i = 0
|
||||
return self
|
||||
end
|
||||
|
||||
func __next(self)
|
||||
if self.i >= self.max then
|
||||
return nil // exit iterator loop
|
||||
end
|
||||
|
||||
return self.i++
|
||||
end
|
||||
end
|
||||
|
||||
let total = 0
|
||||
for i in Range(100) do
|
||||
total = total + i
|
||||
end
|
||||
|
||||
assert(total == 4950, "Iterator check failed!")
|
||||
|
||||
print("Testsuite passed!")
|
@ -1,27 +1,27 @@
|
||||
proto test
|
||||
function __init(self, x)
|
||||
func __init(self, x)
|
||||
self:setArg(x)
|
||||
end
|
||||
|
||||
function __tostring(self)
|
||||
var total = 1
|
||||
func __tostring(self)
|
||||
let total = 1
|
||||
|
||||
for (var i = self.x; i > 0; i = i - 1) do
|
||||
for (let i = self.x; i > 0; i = i - 1) do
|
||||
total = total * i;
|
||||
end
|
||||
|
||||
return "The factorial of " .. self.x .. " is " .. total
|
||||
end
|
||||
|
||||
function setArg(self, x)
|
||||
func setArg(self, x)
|
||||
self.x = x
|
||||
end
|
||||
end
|
||||
|
||||
var t = test(1)
|
||||
let t = test(1)
|
||||
|
||||
for (var x = 1; x < 1000; x = x + 1) do
|
||||
for (var i = 1; i < 100; i = i + 1) do
|
||||
for (let x = 1; x < 1000; x = x + 1) do
|
||||
for (let i = 1; i < 100; i = i + 1) do
|
||||
t:setArg(i)
|
||||
|
||||
print(t)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// adds all args passed (expects numbers)
|
||||
function add(start, ...args)
|
||||
func add(start, ...args)
|
||||
// starting at `start`, add up all numbers passed
|
||||
local total = start
|
||||
for val in args do
|
||||
|
6
examples/writer.cosmo
Normal file
6
examples/writer.cosmo
Normal file
@ -0,0 +1,6 @@
|
||||
local err, file = os.open("test.md", "w")
|
||||
if err then
|
||||
print("failed to open file")
|
||||
end
|
||||
|
||||
file:write("hello world")
|
246
main.c
Normal file
246
main.c
Normal file
@ -0,0 +1,246 @@
|
||||
#include "cbaselib.h"
|
||||
#include "cchunk.h"
|
||||
#include "cdebug.h"
|
||||
#include "cdump.h"
|
||||
#include "cmem.h"
|
||||
#include "cosmo.h"
|
||||
#include "cparse.h"
|
||||
#include "cundump.h"
|
||||
#include "cvm.h"
|
||||
|
||||
#include "util/linenoise.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
# include "util/getopt.h"
|
||||
#else
|
||||
# include <getopt.h>
|
||||
#endif
|
||||
|
||||
static bool _ACTIVE = false;
|
||||
|
||||
int cosmoB_quitRepl(CState *state, int nargs, CValue *args)
|
||||
{
|
||||
_ACTIVE = false;
|
||||
|
||||
return 0; // we don't return anything
|
||||
}
|
||||
|
||||
int cosmoB_input(CState *state, int nargs, CValue *args)
|
||||
{
|
||||
// input() accepts the same params as print()!
|
||||
for (int i = 0; i < nargs; i++) {
|
||||
CObjString *str = cosmoV_toString(state, args[i]);
|
||||
printf("%s", cosmoO_readCString(str));
|
||||
}
|
||||
|
||||
// but, we return user input instead!
|
||||
char line[1024];
|
||||
fgets(line, sizeof(line), stdin);
|
||||
|
||||
cosmoV_pushRef(state,
|
||||
(CObj *)cosmoO_copyString(state, line, strlen(line) - 1)); // -1 for the \n
|
||||
|
||||
return 1; // 1 return value
|
||||
}
|
||||
|
||||
static bool interpret(CState *state, const char *script, const char *mod)
|
||||
{
|
||||
|
||||
// cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE)
|
||||
if (cosmoV_compileString(state, script, mod)) {
|
||||
// 0 args being passed, 0 results expected
|
||||
if (!cosmoV_pcall(state, 0, 0)) {
|
||||
cosmoV_printError(state, cosmoV_readError(*cosmoV_pop(state)));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
cosmoV_printError(state, cosmoV_readError(*cosmoV_pop(state)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void repl(CState *state)
|
||||
{
|
||||
char *line;
|
||||
_ACTIVE = true;
|
||||
|
||||
// add our custom REPL functions
|
||||
cosmoV_pushString(state, "quit");
|
||||
cosmoV_pushCFunction(state, cosmoB_quitRepl);
|
||||
|
||||
cosmoV_pushString(state, "input");
|
||||
cosmoV_pushCFunction(state, cosmoB_input);
|
||||
|
||||
cosmoV_addGlobals(state, 2);
|
||||
|
||||
while (_ACTIVE) {
|
||||
if (!(line = linenoise("> "))) { // better than gets()
|
||||
break;
|
||||
}
|
||||
|
||||
linenoiseHistoryAdd(line);
|
||||
interpret(state, line, "REPL");
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
|
||||
static char *readFile(const char *path)
|
||||
{
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Could not open file \"%s\".\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
// first, we need to know how big our file is
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
char *buffer = (char *)malloc(fileSize + 1); // make room for the null byte
|
||||
if (buffer == NULL) {
|
||||
fprintf(stderr, "failed to allocate!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||
|
||||
if (bytesRead < fileSize) {
|
||||
printf("failed to read file \"%s\"!\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
buffer[bytesRead] = '\0'; // place our null terminator
|
||||
|
||||
// close the file handler and return the script buffer
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static bool runFile(CState *state, const char *fileName)
|
||||
{
|
||||
bool ret;
|
||||
char *script = readFile(fileName);
|
||||
|
||||
// add our input() function to the global table
|
||||
cosmoV_pushString(state, "input");
|
||||
cosmoV_pushCFunction(state, cosmoB_input);
|
||||
|
||||
cosmoV_addGlobals(state, 1);
|
||||
|
||||
ret = interpret(state, script, fileName);
|
||||
|
||||
free(script);
|
||||
return ret; // let the caller know if the script failed
|
||||
}
|
||||
|
||||
int fileWriter(CState *state, const void *data, size_t size, const void *ud)
|
||||
{
|
||||
return !fwrite(data, size, 1, (FILE *)ud);
|
||||
}
|
||||
|
||||
int fileReader(CState *state, void *data, size_t size, const void *ud)
|
||||
{
|
||||
return fread(data, size, 1, (FILE *)ud) != 1;
|
||||
}
|
||||
|
||||
void compileScript(CState *state, const char *in, const char *out)
|
||||
{
|
||||
char *script = readFile(in);
|
||||
|
||||
FILE *fout = fopen(out, "wb");
|
||||
|
||||
if (cosmoV_compileString(state, script, in)) {
|
||||
CObjFunction *func = cosmoV_readClosure(*cosmoV_getTop(state, 0))->function;
|
||||
cosmoD_dump(state, func, fileWriter, (void *)fout);
|
||||
} else {
|
||||
cosmoV_printError(state, cosmoV_readError(*cosmoV_pop(state)));
|
||||
}
|
||||
|
||||
free(script);
|
||||
fclose(fout);
|
||||
|
||||
printf("[!] compiled %s to %s successfully!\n", in, out);
|
||||
}
|
||||
|
||||
void loadScript(CState *state, const char *in)
|
||||
{
|
||||
FILE *file = fopen(in, "rb");
|
||||
if (!cosmoV_undump(state, fileReader, file)) {
|
||||
cosmoV_printError(state, cosmoV_readError(*cosmoV_pop(state)));
|
||||
return;
|
||||
};
|
||||
|
||||
printf("[!] loaded %s!\n", in);
|
||||
if (!cosmoV_pcall(state, 0, 0))
|
||||
cosmoV_printError(state, cosmoV_readError(*cosmoV_pop(state)));
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void printUsage(const char *name)
|
||||
{
|
||||
printf("Usage: %s [-clsr] [args]\n\n", name);
|
||||
printf("available options are:\n"
|
||||
"-c <in> <out>\tcompile <in> and dump to <out>\n"
|
||||
"-l <in>\t\tload dump from <in>\n"
|
||||
"-s <in...>\tcompile and run <in...> script(s)\n"
|
||||
"-r\t\tstart the repl\n\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *const argv[])
|
||||
{
|
||||
CState *state = cosmoV_newState();
|
||||
cosmoB_loadLibrary(state);
|
||||
cosmoB_loadOS(state);
|
||||
cosmoB_loadVM(state);
|
||||
|
||||
int opt;
|
||||
bool isValid = false;
|
||||
while ((opt = getopt(argc, argv, "clsr")) != -1) {
|
||||
switch (opt) {
|
||||
case 'c':
|
||||
if (optind >= argc - 1) {
|
||||
printf("Usage: %s -c <in> <out>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
compileScript(state, argv[optind], argv[optind + 1]);
|
||||
}
|
||||
isValid = true;
|
||||
break;
|
||||
case 'l':
|
||||
if (optind >= argc) {
|
||||
printf("Usage: %s -l <in>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
loadScript(state, argv[optind]);
|
||||
}
|
||||
isValid = true;
|
||||
break;
|
||||
case 's':
|
||||
for (int i = optind; i < argc; i++) {
|
||||
if (!runFile(state, argv[i])) {
|
||||
printf("failed to run %s!\n", argv[i]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
isValid = true;
|
||||
break;
|
||||
case 'r':
|
||||
repl(state);
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
printUsage(argv[0]);
|
||||
}
|
||||
|
||||
cosmoV_freeState(state);
|
||||
return 0;
|
||||
}
|
56
src/_time.h
Normal file
56
src/_time.h
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef COSMO_TIME_H
|
||||
#define COSMO_TIME_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
# include <time.h>
|
||||
# include <windows.h>
|
||||
|
||||
# if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
|
||||
# define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64
|
||||
# else
|
||||
# define DELTA_EPOCH_IN_MICROSECS 116444736000000000ULL
|
||||
# endif
|
||||
|
||||
struct timezone
|
||||
{
|
||||
int tz_minuteswest; /* minutes W of Greenwich */
|
||||
int tz_dsttime; /* type of dst correction */
|
||||
};
|
||||
|
||||
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
||||
{
|
||||
FILETIME ft;
|
||||
unsigned __int64 tmpres = 0;
|
||||
static int tzflag;
|
||||
|
||||
if (NULL != tv) {
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
|
||||
tmpres |= ft.dwHighDateTime;
|
||||
tmpres <<= 32;
|
||||
tmpres |= ft.dwLowDateTime;
|
||||
|
||||
/*converting file time to unix epoch*/
|
||||
tmpres /= 10; /*convert into microseconds*/
|
||||
tmpres -= DELTA_EPOCH_IN_MICROSECS;
|
||||
tv->tv_sec = (long)(tmpres / 1000000UL);
|
||||
tv->tv_usec = (long)(tmpres % 1000000UL);
|
||||
}
|
||||
|
||||
if (NULL != tz) {
|
||||
if (!tzflag) {
|
||||
_tzset();
|
||||
tzflag++;
|
||||
}
|
||||
tz->tz_minuteswest = _timezone / 60;
|
||||
tz->tz_dsttime = _daylight;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
# include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#endif
|
829
src/cbaselib.c
829
src/cbaselib.c
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,13 @@
|
||||
|
||||
#include "cstate.h"
|
||||
|
||||
enum
|
||||
{
|
||||
COSMO_USER_NONE, // CObjObject is not a userdata object
|
||||
COSMO_USER_FILE, // CObjObject is a file object (see cosmoB_osOpen)
|
||||
COSMO_USER_START // the first user type for user-defined userdata
|
||||
};
|
||||
|
||||
/* loads all of the base library, including:
|
||||
- base library ("print", "assert", "type", "pcall", "loadstring", etc.)
|
||||
- object library
|
||||
@ -19,9 +26,10 @@ COSMO_API void cosmoB_loadObjLib(CState *state);
|
||||
|
||||
/* loads the os library, including:
|
||||
- os.read()
|
||||
- os.system()
|
||||
- os.time()
|
||||
*/
|
||||
COSMO_API void cosmoB_loadOSLib(CState *state);
|
||||
COSMO_API void cosmoB_loadOS(CState *state);
|
||||
|
||||
/* loads the base string library, including:
|
||||
- string.sub & <string>:sub()
|
||||
@ -29,8 +37,10 @@ COSMO_API void cosmoB_loadOSLib(CState *state);
|
||||
- string.split & <string>:split()
|
||||
- string.byte & <string>:byte()
|
||||
- string.char & <string>:char()
|
||||
- string.rep & <string>:rep()
|
||||
|
||||
The base proto object for strings is also set, allowing you to invoke the string.* api through string objects, eg.
|
||||
The base proto object for strings is also set, allowing you to invoke the string.* api through
|
||||
string objects, eg.
|
||||
`"hello world":split(" ")` is equivalent to `string.split("hello world", " ")`
|
||||
*/
|
||||
COSMO_API void cosmoB_loadStrLib(CState *state);
|
||||
@ -39,6 +49,7 @@ COSMO_API void cosmoB_loadStrLib(CState *state);
|
||||
- math.abs
|
||||
- math.floor
|
||||
- math.ceil
|
||||
- math.tan
|
||||
*/
|
||||
COSMO_API void cosmoB_loadMathLib(CState *state);
|
||||
|
||||
@ -46,12 +57,13 @@ COSMO_API void cosmoB_loadMathLib(CState *state);
|
||||
- manually setting/grabbing base protos of any object (vm.baseProtos)
|
||||
- manually setting/grabbing the global table (vm.globals)
|
||||
- manually invoking a garbage collection event (vm.collect())
|
||||
- printing closure disassemblies (vm.disassemble())
|
||||
|
||||
for this reason, it is recommended to NOT load this library in production
|
||||
*/
|
||||
COSMO_API void cosmoB_loadVM(CState *state);
|
||||
|
||||
#define cosmoV_typeError(state, name, expectedTypes, formatStr, ...) \
|
||||
cosmoV_error(state, name " expected (" expectedTypes "), got (" formatStr ")!", __VA_ARGS__);
|
||||
#define cosmoV_typeError(state, name, expectedTypes, formatStr, ...) \
|
||||
cosmoV_error(state, name " expected (" expectedTypes "), got (" formatStr ")!", __VA_ARGS__);
|
||||
|
||||
#endif
|
||||
|
48
src/cchunk.c
48
src/cchunk.c
@ -1,68 +1,78 @@
|
||||
#include "cmem.h"
|
||||
#include "cchunk.h"
|
||||
|
||||
#include "cmem.h"
|
||||
#include "cobj.h"
|
||||
#include "cvalue.h"
|
||||
#include "cvm.h"
|
||||
|
||||
CChunk *newChunk(CState* state, size_t startCapacity) {
|
||||
CChunk *newChunk(CState *state, size_t startCapacity)
|
||||
{
|
||||
CChunk *chunk = cosmoM_xmalloc(state, sizeof(CChunk));
|
||||
initChunk(state, chunk, startCapacity);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void initChunk(CState* state, CChunk *chunk, size_t startCapacity) {
|
||||
void initChunk(CState *state, CChunk *chunk, size_t startCapacity)
|
||||
{
|
||||
chunk->capacity = startCapacity;
|
||||
chunk->lineCapacity = startCapacity;
|
||||
chunk->count = 0;
|
||||
chunk->buf = NULL; // when writeByteChunk is called, it'll allocate the array for us
|
||||
chunk->lineInfo = NULL;
|
||||
|
||||
|
||||
// constants
|
||||
initValArray(state, &chunk->constants, ARRAY_START);
|
||||
}
|
||||
|
||||
void cleanChunk(CState* state, CChunk *chunk) {
|
||||
void cleanChunk(CState *state, CChunk *chunk)
|
||||
{
|
||||
// first, free the chunk buffer
|
||||
cosmoM_freearray(state, INSTRUCTION, chunk->buf, chunk->capacity);
|
||||
cosmoM_freeArray(state, INSTRUCTION, chunk->buf, chunk->capacity);
|
||||
// then the line info
|
||||
cosmoM_freearray(state, int, chunk->lineInfo, chunk->capacity);
|
||||
cosmoM_freeArray(state, int, chunk->lineInfo, chunk->capacity);
|
||||
// free the constants
|
||||
cleanValArray(state, &chunk->constants);
|
||||
}
|
||||
|
||||
void freeChunk(CState* state, CChunk *chunk) {
|
||||
void freeChunk(CState *state, CChunk *chunk)
|
||||
{
|
||||
cleanChunk(state, chunk);
|
||||
// now, free the wrapper struct
|
||||
cosmoM_free(state, CChunk, chunk);
|
||||
}
|
||||
|
||||
int addConstant(CState* state, CChunk *chunk, CValue value) {
|
||||
int addConstant(CState *state, CChunk *chunk, CValue value)
|
||||
{
|
||||
// before adding the constant, check if we already have it
|
||||
for (size_t i = 0; i < chunk->constants.count; i++) {
|
||||
if (cosmoV_equal(value, chunk->constants.values[i]))
|
||||
if (cosmoV_equal(state, value, chunk->constants.values[i]))
|
||||
return i; // we already have a matching constant!
|
||||
}
|
||||
|
||||
cosmoM_freezeGC(state); // so our GC doesn't free it
|
||||
cosmoV_pushValue(state, value); // push the value to the stack so our GC can see it
|
||||
appendValArray(state, &chunk->constants, value);
|
||||
cosmoM_unfreezeGC(state);
|
||||
cosmoV_pop(state);
|
||||
|
||||
return chunk->constants.count - 1; // return the index of the new constants
|
||||
}
|
||||
|
||||
// ================================================================ [WRITE TO CHUNK] ================================================================
|
||||
// ================================================================ [WRITE TO CHUNK]
|
||||
|
||||
void writeu8Chunk(CState* state, CChunk *chunk, INSTRUCTION i, int line) {
|
||||
void writeu8Chunk(CState *state, CChunk *chunk, INSTRUCTION i, int line)
|
||||
{
|
||||
// does the buffer need to be reallocated?
|
||||
cosmoM_growarray(state, INSTRUCTION, chunk->buf, chunk->count, chunk->capacity);
|
||||
cosmoM_growarray(state, int, chunk->lineInfo, chunk->count, chunk->lineCapacity);
|
||||
cosmoM_growArray(state, INSTRUCTION, chunk->buf, chunk->count, chunk->capacity);
|
||||
cosmoM_growArray(state, int, chunk->lineInfo, chunk->count, chunk->lineCapacity);
|
||||
|
||||
// write data to the chunk :)
|
||||
chunk->lineInfo[chunk->count] = line;
|
||||
chunk->buf[chunk->count++] = i;
|
||||
}
|
||||
|
||||
void writeu16Chunk(CState* state, CChunk *chunk, uint16_t i, int line) {
|
||||
INSTRUCTION *buffer = (INSTRUCTION*)(&i);
|
||||
int sz = sizeof(uint16_t) / sizeof(INSTRUCTION);
|
||||
void writeu16Chunk(CState *state, CChunk *chunk, uint16_t i, int line)
|
||||
{
|
||||
static const int sz = sizeof(uint16_t) / sizeof(INSTRUCTION);
|
||||
INSTRUCTION *buffer = (INSTRUCTION *)(&i);
|
||||
|
||||
for (int i = 0; i < sz; i++) {
|
||||
writeu8Chunk(state, chunk, buffer[i], line);
|
||||
|
40
src/cchunk.h
40
src/cchunk.h
@ -1,39 +1,41 @@
|
||||
#ifndef CCHUNK_H
|
||||
#define CCHUNK_H
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
#include "coperators.h"
|
||||
#include "cosmo.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
typedef struct CValueArray CValueArray;
|
||||
|
||||
typedef struct CChunk {
|
||||
size_t capacity; // the amount of space we've allocated for
|
||||
size_t count; // the space we're currently using
|
||||
INSTRUCTION *buf; // whole chunk
|
||||
struct CChunk
|
||||
{
|
||||
size_t capacity; // the amount of space we've allocated for
|
||||
size_t count; // the space we're currently using
|
||||
INSTRUCTION *buf; // whole chunk
|
||||
CValueArray constants; // holds constants
|
||||
size_t lineCapacity;
|
||||
int *lineInfo;
|
||||
} CChunk;
|
||||
};
|
||||
|
||||
CChunk *newChunk(CState* state, size_t startCapacity);
|
||||
void initChunk(CState* state, CChunk *chunk, size_t startCapacity);
|
||||
void cleanChunk(CState* state, CChunk *chunk); // frees everything but the struct
|
||||
void freeChunk(CState* state, CChunk *chunk); // frees everything including the struct
|
||||
int addConstant(CState* state, CChunk *chunk, CValue value);
|
||||
CChunk *newChunk(CState *state, size_t startCapacity);
|
||||
void initChunk(CState *state, CChunk *chunk, size_t startCapacity);
|
||||
void cleanChunk(CState *state, CChunk *chunk); // frees everything but the struct
|
||||
void freeChunk(CState *state, CChunk *chunk); // frees everything including the struct
|
||||
int addConstant(CState *state, CChunk *chunk, CValue value);
|
||||
|
||||
bool validateChunk(CState *state, CChunk *chunk);
|
||||
|
||||
// write to chunk
|
||||
void writeu8Chunk(CState* state, CChunk *chunk, INSTRUCTION i, int line);
|
||||
void writeu16Chunk(CState* state, CChunk *chunk, uint16_t i, int line);
|
||||
void writeu8Chunk(CState *state, CChunk *chunk, INSTRUCTION i, int line);
|
||||
void writeu16Chunk(CState *state, CChunk *chunk, uint16_t i, int line);
|
||||
|
||||
// read from chunk
|
||||
static inline INSTRUCTION readu8Chunk(CChunk *chunk, int offset) {
|
||||
static inline INSTRUCTION readu8Chunk(CChunk *chunk, int offset)
|
||||
{
|
||||
return chunk->buf[offset];
|
||||
}
|
||||
|
||||
static inline uint16_t readu16Chunk(CChunk *chunk, int offset) {
|
||||
return *((uint16_t*)(&chunk->buf[offset]));
|
||||
static inline uint16_t readu16Chunk(CChunk *chunk, int offset)
|
||||
{
|
||||
return *((uint16_t *)(&chunk->buf[offset]));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
287
src/cdebug.c
287
src/cdebug.c
@ -1,49 +1,62 @@
|
||||
#include "cdebug.h"
|
||||
#include "cvalue.h"
|
||||
#include "cobj.h"
|
||||
|
||||
void printIndent(int indent) {
|
||||
#include "cobj.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
void printIndent(int indent)
|
||||
{
|
||||
for (int i = 0; i < indent; i++)
|
||||
printf("\t");
|
||||
}
|
||||
|
||||
int simpleInstruction(const char *name, int offset) {
|
||||
static int simpleInstruction(const char *name, int offset)
|
||||
{
|
||||
printf("%s", name);
|
||||
return offset + 1; // consume opcode
|
||||
}
|
||||
|
||||
int u8OperandInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
static int u8OperandInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
printf("%-16s [%03d]", name, readu8Chunk(chunk, offset + 1));
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
int u16OperandInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
static int u16OperandInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
printf("%-16s [%05d]", name, readu16Chunk(chunk, offset + 1));
|
||||
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION));
|
||||
}
|
||||
|
||||
int JumpInstruction(const char *name, CChunk *chunk, int offset, int dir) {
|
||||
static int JumpInstruction(const char *name, CChunk *chunk, int offset, int dir)
|
||||
{
|
||||
int jmp = ((int)readu16Chunk(chunk, offset + 1)) * dir;
|
||||
printf("%-16s [%05d] - jumps to %04d", name, jmp, offset + 3 + jmp);
|
||||
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION));
|
||||
}
|
||||
|
||||
int u8u8OperandInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
printf("%-16s [%03d] [%03d]", name, readu8Chunk(chunk, offset + 1), readu8Chunk(chunk, offset + 2));
|
||||
static int u8u8OperandInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
printf("%-16s [%03d] [%03d]", name, readu8Chunk(chunk, offset + 1),
|
||||
readu8Chunk(chunk, offset + 2));
|
||||
return offset + 3; // op + u8 + u8
|
||||
}
|
||||
|
||||
int u8u16OperandInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
printf("%-16s [%03d] [%05d]", name, readu8Chunk(chunk, offset + 1), readu16Chunk(chunk, offset + 2));
|
||||
static int u8u16OperandInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
printf("%-16s [%03d] [%05d]", name, readu8Chunk(chunk, offset + 1),
|
||||
readu16Chunk(chunk, offset + 2));
|
||||
return offset + 4; // op + u8 + u16
|
||||
}
|
||||
|
||||
int u8u8u16OperandInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
printf("%-16s [%03d] [%03d] [%05d]", name, readu8Chunk(chunk, offset + 1), readu8Chunk(chunk, offset + 2), readu16Chunk(chunk, offset + 3));
|
||||
static int u8u8u16OperandInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
printf("%-16s [%03d] [%03d] [%05d]", name, readu8Chunk(chunk, offset + 1),
|
||||
readu8Chunk(chunk, offset + 2), readu16Chunk(chunk, offset + 3));
|
||||
return offset + 5; // op + u8 + u8 + u16
|
||||
}
|
||||
|
||||
int constInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
static int constInstruction(const char *name, CChunk *chunk, int offset)
|
||||
{
|
||||
int index = readu16Chunk(chunk, offset + 1);
|
||||
printf("%-16s [%05d] - ", name, index);
|
||||
CValue val = chunk->constants.values[index];
|
||||
@ -55,7 +68,8 @@ int constInstruction(const char *name, CChunk *chunk, int offset) {
|
||||
|
||||
// public methods in the cdebug.h header
|
||||
|
||||
void disasmChunk(CChunk *chunk, const char *name, int indent) {
|
||||
void disasmChunk(CChunk *chunk, const char *name, int indent)
|
||||
{
|
||||
printIndent(indent);
|
||||
printf("===[[ disasm for %s ]]===\n", name);
|
||||
|
||||
@ -65,7 +79,8 @@ void disasmChunk(CChunk *chunk, const char *name, int indent) {
|
||||
}
|
||||
}
|
||||
|
||||
int disasmInstr(CChunk *chunk, int offset, int indent) {
|
||||
int disasmInstr(CChunk *chunk, int offset, int indent)
|
||||
{
|
||||
printIndent(indent);
|
||||
printf("%04d ", offset);
|
||||
|
||||
@ -79,129 +94,131 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case OP_LOADCONST:
|
||||
return constInstruction("OP_LOADCONST", chunk, offset);
|
||||
case OP_SETGLOBAL:
|
||||
return constInstruction("OP_SETGLOBAL", chunk, offset);
|
||||
case OP_GETGLOBAL:
|
||||
return constInstruction("OP_GETGLOBAL", chunk, offset);
|
||||
case OP_SETLOCAL:
|
||||
return u8OperandInstruction("OP_SETLOCAL", chunk, offset);
|
||||
case OP_GETLOCAL:
|
||||
return u8OperandInstruction("OP_GETLOCAL", chunk, offset);
|
||||
case OP_SETUPVAL:
|
||||
return u8OperandInstruction("OP_SETUPVAL", chunk, offset);
|
||||
case OP_GETUPVAL:
|
||||
return u8OperandInstruction("OP_GETUPVAL", chunk, offset);
|
||||
case OP_PEJMP:
|
||||
return JumpInstruction("OP_PEJMP", chunk, offset, 1);
|
||||
case OP_EJMP:
|
||||
return JumpInstruction("OP_EJMP", chunk, offset, 1);
|
||||
case OP_JMP:
|
||||
return JumpInstruction("OP_JMP", chunk, offset, 1);
|
||||
case OP_JMPBACK:
|
||||
return JumpInstruction("OP_JMPBACK", chunk, offset, -1);
|
||||
case OP_POP:
|
||||
return u8OperandInstruction("OP_POP", chunk, offset);
|
||||
case OP_CALL:
|
||||
return u8u8OperandInstruction("OP_CALL", chunk, offset);
|
||||
case OP_CLOSURE: {
|
||||
int index = readu16Chunk(chunk, offset + 1);
|
||||
printf("%-16s [%05d] - ", "OP_CLOSURE", index);
|
||||
CValue val = chunk->constants.values[index];
|
||||
CObjFunction *cobjFunc = (CObjFunction*)cosmoV_readRef(val);
|
||||
offset += 3; // we consumed the opcode + u16
|
||||
case OP_LOADCONST:
|
||||
return constInstruction("OP_LOADCONST", chunk, offset);
|
||||
case OP_SETGLOBAL:
|
||||
return constInstruction("OP_SETGLOBAL", chunk, offset);
|
||||
case OP_GETGLOBAL:
|
||||
return constInstruction("OP_GETGLOBAL", chunk, offset);
|
||||
case OP_SETLOCAL:
|
||||
return u8OperandInstruction("OP_SETLOCAL", chunk, offset);
|
||||
case OP_GETLOCAL:
|
||||
return u8OperandInstruction("OP_GETLOCAL", chunk, offset);
|
||||
case OP_SETUPVAL:
|
||||
return u8OperandInstruction("OP_SETUPVAL", chunk, offset);
|
||||
case OP_GETUPVAL:
|
||||
return u8OperandInstruction("OP_GETUPVAL", chunk, offset);
|
||||
case OP_PEJMP:
|
||||
return JumpInstruction("OP_PEJMP", chunk, offset, 1);
|
||||
case OP_EJMP:
|
||||
return JumpInstruction("OP_EJMP", chunk, offset, 1);
|
||||
case OP_JMP:
|
||||
return JumpInstruction("OP_JMP", chunk, offset, 1);
|
||||
case OP_JMPBACK:
|
||||
return JumpInstruction("OP_JMPBACK", chunk, offset, -1);
|
||||
case OP_POP:
|
||||
return u8OperandInstruction("OP_POP", chunk, offset);
|
||||
case OP_CALL:
|
||||
return u8u8OperandInstruction("OP_CALL", chunk, offset);
|
||||
case OP_CLOSURE: {
|
||||
int index = readu16Chunk(chunk, offset + 1);
|
||||
printf("%-16s [%05d] - ", "OP_CLOSURE", index);
|
||||
CValue val = chunk->constants.values[index];
|
||||
CObjFunction *cobjFunc = (CObjFunction *)cosmoV_readRef(val);
|
||||
offset += 3; // we consumed the opcode + u16
|
||||
|
||||
printValue(val);
|
||||
printf("\n");
|
||||
|
||||
// list the upvalues/locals that are captured
|
||||
for (int i = 0; i < cobjFunc->upvals; i++) {
|
||||
uint8_t encoding = readu8Chunk(chunk, offset++);
|
||||
uint8_t index = readu8Chunk(chunk, offset++);
|
||||
printIndent(indent + 1);
|
||||
printf("references %s [%d]\n", encoding == OP_GETLOCAL ? "local" : "upvalue", index);
|
||||
}
|
||||
printValue(val);
|
||||
printf("\n");
|
||||
|
||||
// print the chunk
|
||||
disasmChunk(&cobjFunc->chunk, cobjFunc->name == NULL ? UNNAMEDCHUNK : cobjFunc->name->str, indent+1);
|
||||
return offset;
|
||||
// list the upvalues/locals that are captured
|
||||
for (int i = 0; i < cobjFunc->upvals; i++) {
|
||||
uint8_t encoding = readu8Chunk(chunk, offset++);
|
||||
uint8_t index = readu8Chunk(chunk, offset++);
|
||||
printIndent(indent + 1);
|
||||
printf("references %s [%d]\n", encoding == OP_GETLOCAL ? "local" : "upvalue", index);
|
||||
}
|
||||
case OP_CLOSE:
|
||||
return simpleInstruction("OP_CLOSE", offset);
|
||||
case OP_NEWTABLE:
|
||||
return u16OperandInstruction("OP_NEWTABLE", chunk, offset);
|
||||
case OP_NEWARRAY:
|
||||
return u16OperandInstruction("OP_NEWARRAY", chunk, offset);
|
||||
case OP_INDEX:
|
||||
return simpleInstruction("OP_INDEX", offset);
|
||||
case OP_NEWINDEX:
|
||||
return simpleInstruction("OP_NEWINDEX", offset);
|
||||
case OP_NEWOBJECT:
|
||||
return u16OperandInstruction("OP_NEWOBJECT", chunk, offset);
|
||||
case OP_SETOBJECT:
|
||||
return constInstruction("OP_SETOBJECT", chunk, offset);
|
||||
case OP_GETOBJECT:
|
||||
return constInstruction("OP_GETOBJECT", chunk, offset);
|
||||
case OP_GETMETHOD:
|
||||
return constInstruction("OP_GETMETHOD", chunk, offset);
|
||||
case OP_INVOKE:
|
||||
return u8u8u16OperandInstruction("OP_INVOKE", chunk, offset);
|
||||
case OP_ITER:
|
||||
return simpleInstruction("OP_ITER", offset);
|
||||
case OP_NEXT:
|
||||
return u8u16OperandInstruction("OP_NEXT", chunk, offset);
|
||||
case OP_ADD:
|
||||
return simpleInstruction("OP_ADD", offset);
|
||||
case OP_SUB:
|
||||
return simpleInstruction("OP_SUB", offset);
|
||||
case OP_MULT:
|
||||
return simpleInstruction("OP_MULT", offset);
|
||||
case OP_DIV:
|
||||
return simpleInstruction("OP_DIV", offset);
|
||||
case OP_MOD:
|
||||
return simpleInstruction("OP_MOD", offset);
|
||||
case OP_TRUE:
|
||||
return simpleInstruction("OP_TRUE", offset);
|
||||
case OP_FALSE:
|
||||
return simpleInstruction("OP_FALSE", offset);
|
||||
case OP_NIL:
|
||||
return simpleInstruction("OP_NIL", offset);
|
||||
case OP_NOT:
|
||||
return simpleInstruction("OP_NOT", offset);
|
||||
case OP_EQUAL:
|
||||
return simpleInstruction("OP_EQUAL", offset);
|
||||
case OP_GREATER:
|
||||
return simpleInstruction("OP_GREATER", offset);
|
||||
case OP_GREATER_EQUAL:
|
||||
return simpleInstruction("OP_GREATER_EQUAL", offset);
|
||||
case OP_LESS:
|
||||
return simpleInstruction("OP_LESS", offset);
|
||||
case OP_LESS_EQUAL:
|
||||
return simpleInstruction("OP_LESS_EQUAL", offset);
|
||||
case OP_NEGATE:
|
||||
return simpleInstruction("OP_NEGATE", offset);
|
||||
case OP_COUNT:
|
||||
return simpleInstruction("OP_COUNT", offset);
|
||||
case OP_CONCAT:
|
||||
return u8OperandInstruction("OP_CONCAT", chunk, offset);
|
||||
case OP_INCLOCAL:
|
||||
return u8u8OperandInstruction("OP_INCLOCAL", chunk, offset);
|
||||
case OP_INCGLOBAL:
|
||||
return u8u16OperandInstruction("OP_INCGLOBAL", chunk, offset);
|
||||
case OP_INCUPVAL:
|
||||
return u8u8OperandInstruction("OP_INCUPVAL", chunk, offset);
|
||||
case OP_INCINDEX:
|
||||
return u8OperandInstruction("OP_INCINDEX", chunk, offset);
|
||||
case OP_INCOBJECT:
|
||||
return u8u16OperandInstruction("OP_INCOBJECT", chunk, offset);
|
||||
case OP_RETURN:
|
||||
return u8OperandInstruction("OP_RETURN", chunk, offset);
|
||||
default:
|
||||
printf("Unknown opcode! [%d]\n", i);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// print the chunk
|
||||
disasmChunk(&cobjFunc->chunk, cobjFunc->name == NULL ? UNNAMEDCHUNK : cobjFunc->name->str,
|
||||
indent + 1);
|
||||
return offset;
|
||||
}
|
||||
case OP_CLOSE:
|
||||
return simpleInstruction("OP_CLOSE", offset);
|
||||
case OP_NEWTABLE:
|
||||
return u16OperandInstruction("OP_NEWTABLE", chunk, offset);
|
||||
case OP_NEWARRAY:
|
||||
return u16OperandInstruction("OP_NEWARRAY", chunk, offset);
|
||||
case OP_INDEX:
|
||||
return simpleInstruction("OP_INDEX", offset);
|
||||
case OP_NEWINDEX:
|
||||
return simpleInstruction("OP_NEWINDEX", offset);
|
||||
case OP_NEWOBJECT:
|
||||
return u16OperandInstruction("OP_NEWOBJECT", chunk, offset);
|
||||
case OP_SETOBJECT:
|
||||
return constInstruction("OP_SETOBJECT", chunk, offset);
|
||||
case OP_GETOBJECT:
|
||||
return constInstruction("OP_GETOBJECT", chunk, offset);
|
||||
case OP_GETMETHOD:
|
||||
return constInstruction("OP_GETMETHOD", chunk, offset);
|
||||
case OP_INVOKE:
|
||||
return u8u8u16OperandInstruction("OP_INVOKE", chunk, offset);
|
||||
case OP_ITER:
|
||||
return simpleInstruction("OP_ITER", offset);
|
||||
case OP_NEXT:
|
||||
return u8u16OperandInstruction("OP_NEXT", chunk, offset);
|
||||
case OP_ADD:
|
||||
return simpleInstruction("OP_ADD", offset);
|
||||
case OP_SUB:
|
||||
return simpleInstruction("OP_SUB", offset);
|
||||
case OP_MULT:
|
||||
return simpleInstruction("OP_MULT", offset);
|
||||
case OP_DIV:
|
||||
return simpleInstruction("OP_DIV", offset);
|
||||
case OP_MOD:
|
||||
return simpleInstruction("OP_MOD", offset);
|
||||
case OP_POW:
|
||||
return simpleInstruction("OP_POW", offset);
|
||||
case OP_TRUE:
|
||||
return simpleInstruction("OP_TRUE", offset);
|
||||
case OP_FALSE:
|
||||
return simpleInstruction("OP_FALSE", offset);
|
||||
case OP_NIL:
|
||||
return simpleInstruction("OP_NIL", offset);
|
||||
case OP_NOT:
|
||||
return simpleInstruction("OP_NOT", offset);
|
||||
case OP_EQUAL:
|
||||
return simpleInstruction("OP_EQUAL", offset);
|
||||
case OP_GREATER:
|
||||
return simpleInstruction("OP_GREATER", offset);
|
||||
case OP_GREATER_EQUAL:
|
||||
return simpleInstruction("OP_GREATER_EQUAL", offset);
|
||||
case OP_LESS:
|
||||
return simpleInstruction("OP_LESS", offset);
|
||||
case OP_LESS_EQUAL:
|
||||
return simpleInstruction("OP_LESS_EQUAL", offset);
|
||||
case OP_NEGATE:
|
||||
return simpleInstruction("OP_NEGATE", offset);
|
||||
case OP_COUNT:
|
||||
return simpleInstruction("OP_COUNT", offset);
|
||||
case OP_CONCAT:
|
||||
return u8OperandInstruction("OP_CONCAT", chunk, offset);
|
||||
case OP_INCLOCAL:
|
||||
return u8u8OperandInstruction("OP_INCLOCAL", chunk, offset);
|
||||
case OP_INCGLOBAL:
|
||||
return u8u16OperandInstruction("OP_INCGLOBAL", chunk, offset);
|
||||
case OP_INCUPVAL:
|
||||
return u8u8OperandInstruction("OP_INCUPVAL", chunk, offset);
|
||||
case OP_INCINDEX:
|
||||
return u8OperandInstruction("OP_INCINDEX", chunk, offset);
|
||||
case OP_INCOBJECT:
|
||||
return u8u16OperandInstruction("OP_INCOBJECT", chunk, offset);
|
||||
case OP_RETURN:
|
||||
return u8OperandInstruction("OP_RETURN", chunk, offset);
|
||||
default:
|
||||
printf("Unknown opcode! [%d]\n", i);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
204
src/cdump.c
Normal file
204
src/cdump.c
Normal file
@ -0,0 +1,204 @@
|
||||
#include "cdump.h"
|
||||
|
||||
#include "cdebug.h"
|
||||
#include "cmem.h"
|
||||
#include "cobj.h"
|
||||
#include "cvalue.h"
|
||||
#include "cvm.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CState *state;
|
||||
const void *userData;
|
||||
cosmo_Writer writer;
|
||||
int writerStatus;
|
||||
} DumpState;
|
||||
|
||||
static bool writeCValue(DumpState *dstate, CValue val);
|
||||
|
||||
#define check(e) \
|
||||
if (!e) { \
|
||||
return false; \
|
||||
}
|
||||
|
||||
static void initDumpState(CState *state, DumpState *dstate, cosmo_Writer writer,
|
||||
const void *userData)
|
||||
{
|
||||
dstate->state = state;
|
||||
dstate->userData = userData;
|
||||
dstate->writer = writer;
|
||||
dstate->writerStatus = 0;
|
||||
}
|
||||
|
||||
static bool writeBlock(DumpState *dstate, const void *data, size_t size)
|
||||
{
|
||||
if (dstate->writerStatus == 0) {
|
||||
dstate->writerStatus = dstate->writer(dstate->state, data, size, dstate->userData);
|
||||
}
|
||||
|
||||
return dstate->writerStatus == 0;
|
||||
}
|
||||
|
||||
static bool writeu8(DumpState *dstate, uint8_t d)
|
||||
{
|
||||
return writeBlock(dstate, &d, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
static bool writeu32(DumpState *dstate, uint32_t d)
|
||||
{
|
||||
return writeBlock(dstate, &d, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static bool writeSize(DumpState *dstate, size_t d)
|
||||
{
|
||||
return writeBlock(dstate, &d, sizeof(size_t));
|
||||
}
|
||||
|
||||
static bool writeVector(DumpState *dstate, const void *data, size_t size, size_t count)
|
||||
{
|
||||
check(writeSize(dstate, count));
|
||||
check(writeBlock(dstate, data, size * count));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool writeHeader(DumpState *dstate)
|
||||
{
|
||||
check(writeBlock(dstate, COSMO_MAGIC, COSMO_MAGIC_LEN));
|
||||
|
||||
/* after the magic, we write some platform information */
|
||||
check(writeu8(dstate, cosmoD_isBigEndian()));
|
||||
check(writeu8(dstate, sizeof(cosmo_Number)));
|
||||
check(writeu8(dstate, sizeof(size_t)));
|
||||
check(writeu8(dstate, sizeof(int)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool writeCObjString(DumpState *dstate, CObjString *obj)
|
||||
{
|
||||
if (obj == NULL) { /* this is in case cobjfunction's name or module strings are null */
|
||||
check(writeu32(dstate, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* write string length */
|
||||
check(writeu32(dstate, obj->length));
|
||||
|
||||
/* write string data */
|
||||
check(writeBlock(dstate, obj->str, obj->length));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool writeCObjFunction(DumpState *dstate, CObjFunction *obj)
|
||||
{
|
||||
check(writeCObjString(dstate, obj->name));
|
||||
check(writeCObjString(dstate, obj->module));
|
||||
|
||||
check(writeu32(dstate, obj->args));
|
||||
check(writeu32(dstate, obj->upvals));
|
||||
check(writeu8(dstate, obj->variadic));
|
||||
|
||||
/* write chunk info */
|
||||
check(writeVector(dstate, obj->chunk.buf, sizeof(uint8_t), obj->chunk.count));
|
||||
|
||||
/* write line info */
|
||||
check(writeVector(dstate, obj->chunk.lineInfo, sizeof(int), obj->chunk.count));
|
||||
|
||||
/* write constants */
|
||||
check(writeSize(dstate, obj->chunk.constants.count));
|
||||
for (int i = 0; i < obj->chunk.constants.count; i++) {
|
||||
check(writeCValue(dstate, obj->chunk.constants.values[i]));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool writeCObj(DumpState *dstate, CObj *obj)
|
||||
{
|
||||
/*
|
||||
we can kind of cheat here since our parser only emits a few very limited CObjs...
|
||||
CChunks will only ever have the following CObj's in their constant table:
|
||||
- COBJ_STRING
|
||||
- COBJ_FUNCTION
|
||||
|
||||
the rest of the objects are created during runtime. yay!
|
||||
*/
|
||||
CObjType t = cosmoO_readType(obj);
|
||||
|
||||
/* write cobj type */
|
||||
writeu8(dstate, t);
|
||||
|
||||
/* write object payload/body */
|
||||
switch (t) {
|
||||
case COBJ_STRING:
|
||||
check(writeCObjString(dstate, (CObjString *)obj));
|
||||
break;
|
||||
case COBJ_FUNCTION:
|
||||
check(writeCObjFunction(dstate, (CObjFunction *)obj));
|
||||
break;
|
||||
default:
|
||||
cosmoV_error(dstate->state, "invalid cobj type: %d", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define WRITE_VAR(dstate, type, expression) \
|
||||
{ \
|
||||
type _tmp = expression; \
|
||||
check(writeBlock(dstate, &_tmp, sizeof(_tmp))); \
|
||||
break; \
|
||||
}
|
||||
|
||||
static bool writeCValue(DumpState *dstate, CValue val)
|
||||
{
|
||||
CosmoType t = GET_TYPE(val);
|
||||
|
||||
/* write value type */
|
||||
writeu8(dstate, t);
|
||||
|
||||
/* write value payload/body */
|
||||
switch (t) {
|
||||
case COSMO_TNUMBER:
|
||||
WRITE_VAR(dstate, cosmo_Number, cosmoV_readNumber(val))
|
||||
case COSMO_TBOOLEAN:
|
||||
WRITE_VAR(dstate, bool, cosmoV_readBoolean(val))
|
||||
case COSMO_TREF:
|
||||
check(writeCObj(dstate, cosmoV_readRef(val)));
|
||||
break;
|
||||
case COSMO_TNIL: /* no body */
|
||||
break;
|
||||
default:
|
||||
cosmoV_error(dstate->state, "invalid value type: %d", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef WRITE_VAR
|
||||
|
||||
bool cosmoD_isBigEndian()
|
||||
{
|
||||
union
|
||||
{
|
||||
uint32_t i;
|
||||
uint8_t c[4];
|
||||
} _indxint = {0xDEADB33F};
|
||||
|
||||
return _indxint.c[0] == 0xDE;
|
||||
}
|
||||
|
||||
int cosmoD_dump(CState *state, CObjFunction *func, cosmo_Writer writer, const void *userData)
|
||||
{
|
||||
DumpState dstate;
|
||||
initDumpState(state, &dstate, writer, userData);
|
||||
|
||||
check(writeHeader(&dstate));
|
||||
check(writeCObjFunction(&dstate, func));
|
||||
|
||||
return dstate.writerStatus;
|
||||
}
|
17
src/cdump.h
Normal file
17
src/cdump.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef COSMO_DUMP_H
|
||||
#define COSMO_DUMP_H
|
||||
|
||||
#include "cobj.h"
|
||||
#include "cosmo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define COSMO_MAGIC "COS\x12"
|
||||
#define COSMO_MAGIC_LEN 4
|
||||
|
||||
bool cosmoD_isBigEndian();
|
||||
|
||||
/* returns non-zero on error */
|
||||
int cosmoD_dump(CState *state, CObjFunction *func, cosmo_Writer writer, const void *userData);
|
||||
|
||||
#endif
|
491
src/clex.c
491
src/clex.c
@ -1,72 +1,81 @@
|
||||
#include "clex.h"
|
||||
|
||||
#include "cmem.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
CReservedWord reservedWords[] = {
|
||||
{TOKEN_AND, "and", 3},
|
||||
{TOKEN_BREAK, "break", 5},
|
||||
{ TOKEN_AND, "and", 3},
|
||||
{ TOKEN_BREAK, "break", 5},
|
||||
{TOKEN_CONTINUE, "continue", 8},
|
||||
{TOKEN_DO, "do", 2},
|
||||
{TOKEN_ELSE, "else", 4},
|
||||
{TOKEN_ELSEIF, "elseif", 6},
|
||||
{TOKEN_END, "end", 3},
|
||||
{TOKEN_FALSE, "false", 5},
|
||||
{TOKEN_FOR, "for", 3},
|
||||
{TOKEN_FUNCTION, "function", 8},
|
||||
{TOKEN_IF, "if", 2},
|
||||
{TOKEN_IN, "in", 2},
|
||||
{TOKEN_LOCAL, "local", 5},
|
||||
{TOKEN_NIL, "nil", 3},
|
||||
{TOKEN_NOT, "not", 3},
|
||||
{TOKEN_OR, "or", 2},
|
||||
{TOKEN_PROTO, "proto", 5},
|
||||
{TOKEN_RETURN, "return", 6},
|
||||
{TOKEN_THEN, "then", 4},
|
||||
{TOKEN_TRUE, "true", 4},
|
||||
{TOKEN_VAR, "var", 3},
|
||||
{TOKEN_WHILE, "while", 5}
|
||||
{ TOKEN_DO, "do", 2},
|
||||
{ TOKEN_ELSE, "else", 4},
|
||||
{ TOKEN_ELSEIF, "elseif", 6},
|
||||
{ TOKEN_END, "end", 3},
|
||||
{ TOKEN_FALSE, "false", 5},
|
||||
{ TOKEN_FOR, "for", 3},
|
||||
{ TOKEN_FUNC, "func", 4},
|
||||
{ TOKEN_IF, "if", 2},
|
||||
{ TOKEN_IN, "in", 2},
|
||||
{ TOKEN_LOCAL, "local", 5},
|
||||
{ TOKEN_NIL, "nil", 3},
|
||||
{ TOKEN_NOT, "not", 3},
|
||||
{ TOKEN_OR, "or", 2},
|
||||
{ TOKEN_PROTO, "proto", 5},
|
||||
{ TOKEN_RETURN, "return", 6},
|
||||
{ TOKEN_THEN, "then", 4},
|
||||
{ TOKEN_TRUE, "true", 4},
|
||||
{ TOKEN_LET, "let", 3},
|
||||
{ TOKEN_WHILE, "while", 5}
|
||||
};
|
||||
|
||||
// returns true if current token is a heap allocated buffer
|
||||
static bool isBuffer(CLexState *state) {
|
||||
return state->buffer != NULL;
|
||||
static bool isBuffer(CLexState *state)
|
||||
{
|
||||
return state->buffer != NULL;
|
||||
}
|
||||
|
||||
// marks the current token as heap allocated & allocates the buffer
|
||||
static void makeBuffer(CLexState *state) {
|
||||
state->buffer = cosmoM_xmalloc(state->cstate, sizeof(char) * 32); // start with a 32 character long buffer
|
||||
static void makeBuffer(CLexState *state)
|
||||
{
|
||||
state->buffer =
|
||||
cosmoM_xmalloc(state->cstate, sizeof(char) * 32); // start with a 32 character long buffer
|
||||
state->bufCount = 0;
|
||||
state->bufCap = 32;
|
||||
}
|
||||
|
||||
static void resetBuffer(CLexState *state) {
|
||||
static void resetBuffer(CLexState *state)
|
||||
{
|
||||
state->buffer = NULL;
|
||||
state->bufCount = 0;
|
||||
state->bufCap = 0;
|
||||
}
|
||||
|
||||
// cancels the token heap buffer and frees it
|
||||
static void freeBuffer(CLexState *state) {
|
||||
cosmoM_freearray(state->cstate, char, state->buffer, state->bufCap);
|
||||
static void freeBuffer(CLexState *state)
|
||||
{
|
||||
cosmoM_freeArray(state->cstate, char, state->buffer, state->bufCap);
|
||||
|
||||
resetBuffer(state);
|
||||
}
|
||||
|
||||
// adds character to buffer
|
||||
static void appendBuffer(CLexState *state, char c) {
|
||||
cosmoM_growarray(state->cstate, char, state->buffer, state->bufCount, state->bufCap);
|
||||
static void appendBuffer(CLexState *state, char c)
|
||||
{
|
||||
cosmoM_growArray(state->cstate, char, state->buffer, state->bufCount, state->bufCap);
|
||||
|
||||
state->buffer[state->bufCount++] = c;
|
||||
}
|
||||
|
||||
// saves the current character to the buffer, grows the buffer as needed
|
||||
static void saveBuffer(CLexState *state) {
|
||||
static void saveBuffer(CLexState *state)
|
||||
{
|
||||
appendBuffer(state, *state->currentChar);
|
||||
}
|
||||
|
||||
// resets the lex state buffer & returns the allocated buffer as a null terminated string
|
||||
static char *cutBuffer(CLexState *state, int *length) {
|
||||
static char *cutBuffer(CLexState *state, int *length)
|
||||
{
|
||||
// append the null terminator
|
||||
appendBuffer(state, '\0');
|
||||
|
||||
@ -84,7 +93,8 @@ static char *cutBuffer(CLexState *state, int *length) {
|
||||
return cosmoM_reallocate(state->cstate, buf, cap, count);
|
||||
}
|
||||
|
||||
static CToken makeToken(CLexState *state, CTokenType type) {
|
||||
static CToken makeToken(CLexState *state, CTokenType type)
|
||||
{
|
||||
CToken token;
|
||||
token.type = type;
|
||||
token.line = state->line;
|
||||
@ -95,38 +105,43 @@ static CToken makeToken(CLexState *state, CTokenType type) {
|
||||
token.start = state->startChar;
|
||||
token.length = state->currentChar - state->startChar; // delta between start & current
|
||||
}
|
||||
|
||||
|
||||
state->lastType = type;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static CToken makeError(CLexState *state, const char *msg) {
|
||||
static CToken makeError(CLexState *state, const char *msg)
|
||||
{
|
||||
CToken token;
|
||||
token.type = TOKEN_ERROR;
|
||||
token.start = (char*)msg;
|
||||
token.start = (char *)msg;
|
||||
token.length = strlen(msg);
|
||||
token.line = state->line;
|
||||
|
||||
if (isBuffer(state))
|
||||
freeBuffer(state);
|
||||
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static inline bool isEnd(CLexState *state) {
|
||||
static inline bool isEnd(CLexState *state)
|
||||
{
|
||||
return *state->currentChar == '\0';
|
||||
}
|
||||
|
||||
static inline bool isNumerical(char c) {
|
||||
static inline bool isNumerical(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static bool isAlpha(char c) {
|
||||
static bool isAlpha(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; // identifiers can have '_'
|
||||
}
|
||||
|
||||
static bool match(CLexState *state, char expected) {
|
||||
static bool match(CLexState *state, char expected)
|
||||
{
|
||||
if (isEnd(state) || *state->currentChar != expected)
|
||||
return false;
|
||||
|
||||
@ -135,35 +150,41 @@ static bool match(CLexState *state, char expected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char peek(CLexState *state) {
|
||||
static char peek(CLexState *state)
|
||||
{
|
||||
return *state->currentChar;
|
||||
}
|
||||
|
||||
static char peekNext(CLexState *state) {
|
||||
if (isEnd(state))
|
||||
static char peekNext(CLexState *state)
|
||||
{
|
||||
if (isEnd(state))
|
||||
return '\0';
|
||||
|
||||
|
||||
return state->currentChar[1];
|
||||
}
|
||||
|
||||
char next(CLexState *state) {
|
||||
static char next(CLexState *state)
|
||||
{
|
||||
if (isEnd(state))
|
||||
return '\0'; // return a null terminator
|
||||
return '\0'; // return a null terminator
|
||||
state->currentChar++;
|
||||
return state->currentChar[-1];
|
||||
}
|
||||
|
||||
bool isHex(char c) {
|
||||
static bool isHex(char c)
|
||||
{
|
||||
return isNumerical(c) || ('A' <= c && 'F' >= c) || ('a' <= c && 'f' >= c);
|
||||
}
|
||||
|
||||
CTokenType identifierType(CLexState *state) {
|
||||
static CTokenType identifierType(CLexState *state)
|
||||
{
|
||||
int length = state->currentChar - state->startChar;
|
||||
|
||||
// check against reserved word list
|
||||
for (size_t i = 0; i < sizeof(reservedWords) / sizeof(CReservedWord); i++) {
|
||||
// it matches the reserved word
|
||||
if (reservedWords[i].len == length && memcmp(state->startChar, reservedWords[i].word, length) == 0)
|
||||
if (reservedWords[i].len == length &&
|
||||
memcmp(state->startChar, reservedWords[i].word, length) == 0)
|
||||
return reservedWords[i].type;
|
||||
}
|
||||
|
||||
@ -171,161 +192,178 @@ CTokenType identifierType(CLexState *state) {
|
||||
return TOKEN_IDENTIFIER;
|
||||
}
|
||||
|
||||
void skipWhitespace(CLexState *state) {
|
||||
static void skipWhitespace(CLexState *state)
|
||||
{
|
||||
while (true) {
|
||||
char c = peek(state);
|
||||
switch (c) {
|
||||
case '\n': // mark new line
|
||||
state->line++;
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
next(state); // consume the whitespace
|
||||
case '\n': // mark new line
|
||||
state->line++;
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
next(state); // consume the whitespace
|
||||
break;
|
||||
case '/': // consume comments
|
||||
if (peekNext(state) == '/') {
|
||||
// skip to next line (also let \n be consumed on the next iteration to properly
|
||||
// handle that)
|
||||
while (!isEnd(state) &&
|
||||
peek(state) != '\n') // if it's not a newline or the end of the source
|
||||
next(state);
|
||||
|
||||
// keep consuming whitespace
|
||||
break;
|
||||
case '/': // consume comments
|
||||
if (peekNext(state) == '/') {
|
||||
// skip to next line (also let \n be consumed on the next iteration to properly handle that)
|
||||
while (!isEnd(state) && peek(state) != '\n') // if it's not a newline or the end of the source
|
||||
next(state);
|
||||
|
||||
// keep consuming whitespace
|
||||
break;
|
||||
} else if (peekNext(state) == '*') { // multiline comments
|
||||
while (!isEnd(state) && !(peek(state) == '*' && peekNext(state) == '/')) // if it's the end of the comment or the end of the source
|
||||
next(state);
|
||||
|
||||
// consume the '*/'
|
||||
next(state);
|
||||
} else if (peekNext(state) == '*') { // multiline comments
|
||||
while (!isEnd(state) &&
|
||||
!(peek(state) == '*' &&
|
||||
peekNext(state) ==
|
||||
'/')) // if it's the end of the comment or the end of the source
|
||||
next(state);
|
||||
|
||||
// keep consuming whitespace
|
||||
break;
|
||||
}
|
||||
return; // it's a TOKEN_SLASH, let the main body handle that
|
||||
default: // it's no longer whitespace, return!
|
||||
return;
|
||||
// consume the '*/'
|
||||
next(state);
|
||||
next(state);
|
||||
|
||||
// keep consuming whitespace
|
||||
break;
|
||||
}
|
||||
return; // it's a TOKEN_SLASH, let the main body handle that
|
||||
default: // it's no longer whitespace, return!
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CToken parseString(CLexState *state) {
|
||||
static CToken parseString(CLexState *state)
|
||||
{
|
||||
makeBuffer(state); // buffer mode
|
||||
while (peek(state) != '"' && !isEnd(state)) {
|
||||
switch (peek(state)) {
|
||||
case '\n': // strings can't stretch across lines
|
||||
return makeError(state, "Unterminated string!");
|
||||
case '\\': { // special character
|
||||
next(state); // consume the '\' character
|
||||
case '\n': // strings can't stretch across lines
|
||||
return makeError(state, "Unterminated string!");
|
||||
case '\\': { // special character
|
||||
next(state); // consume the '\' character
|
||||
|
||||
switch (peek(state)) {
|
||||
case 'r': case 'n': appendBuffer(state, '\n'); break;
|
||||
case 't': appendBuffer(state, '\t'); break;
|
||||
case '\\': appendBuffer(state, '\\'); break;
|
||||
case '"': appendBuffer(state, '"'); break;
|
||||
case 'x': // hexadecimal character encoding
|
||||
next(state); // skip 'x'
|
||||
switch (peek(state)) {
|
||||
case 'r':
|
||||
case 'n':
|
||||
appendBuffer(state, '\n');
|
||||
break;
|
||||
case 't':
|
||||
appendBuffer(state, '\t');
|
||||
break;
|
||||
case '\\':
|
||||
appendBuffer(state, '\\');
|
||||
break;
|
||||
case '"':
|
||||
appendBuffer(state, '"');
|
||||
break;
|
||||
case 'x': // hexadecimal character encoding
|
||||
next(state); // skip 'x'
|
||||
|
||||
if (isHex(peek(state))) {
|
||||
char *numStart = state->currentChar;
|
||||
if (isHex(peek(state))) {
|
||||
char *numStart = state->currentChar;
|
||||
|
||||
// consume the hexnum
|
||||
while (isHex(peek(state)))
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 16);
|
||||
// consume the hexnum
|
||||
while (isHex(peek(state)))
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 16);
|
||||
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
return makeError(state, "Unknown hexadecimal character encoding!");
|
||||
case 'b': // binary character encoding
|
||||
next(state); // skip 'b'
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
|
||||
if (peek(state) == '0' || peek(state) == '1') {
|
||||
char *numStart = state->currentChar;
|
||||
|
||||
// consume the bin
|
||||
while (peek(state) == '0' || peek(state) == '1')
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 2);
|
||||
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
return makeError(state, "Unknown binary character encoding!");
|
||||
default: {
|
||||
if (isNumerical(peek(state))) {
|
||||
char *numStart = state->currentChar;
|
||||
|
||||
// consume the number
|
||||
while (isNumerical(peek(state)))
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 10);
|
||||
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
return makeError(state, "Unknown special character!"); // TODO: maybe a more descriptive error?
|
||||
}
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
next(state); // consume special character
|
||||
break;
|
||||
}
|
||||
return makeError(state, "Unknown hexadecimal character encoding!");
|
||||
case 'b': // binary character encoding
|
||||
next(state); // skip 'b'
|
||||
|
||||
if (peek(state) == '0' || peek(state) == '1') {
|
||||
char *numStart = state->currentChar;
|
||||
|
||||
// consume the bin
|
||||
while (peek(state) == '0' || peek(state) == '1')
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 2);
|
||||
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
return makeError(state, "Unknown binary character encoding!");
|
||||
default: {
|
||||
saveBuffer(state); // save the character!
|
||||
next(state); // consume
|
||||
if (isNumerical(peek(state))) {
|
||||
char *numStart = state->currentChar;
|
||||
|
||||
// consume the number
|
||||
while (isNumerical(peek(state)))
|
||||
next(state);
|
||||
state->currentChar--; // since next() is called after
|
||||
|
||||
unsigned int num = (unsigned int)strtoul(numStart, NULL, 10);
|
||||
|
||||
if (num > 255) // sanity check
|
||||
return makeError(state, "Character out of range! > 255!");
|
||||
|
||||
appendBuffer(state, num);
|
||||
break;
|
||||
}
|
||||
|
||||
return makeError(
|
||||
state, "Unknown special character!"); // TODO: maybe a more descriptive error?
|
||||
}
|
||||
}
|
||||
|
||||
next(state); // consume special character
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
saveBuffer(state); // save the character!
|
||||
next(state); // consume
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnd(state))
|
||||
return makeError(state, "Unterminated string!");
|
||||
|
||||
|
||||
next(state); // consume closing quote
|
||||
return makeToken(state, TOKEN_STRING);
|
||||
}
|
||||
|
||||
CToken parseNumber(CLexState *state) {
|
||||
static CToken parseNumber(CLexState *state)
|
||||
{
|
||||
switch (peek(state)) {
|
||||
case 'x': // hexadecimal number
|
||||
case 'x': // hexadecimal number
|
||||
next(state);
|
||||
|
||||
while (isHex(peek(state)))
|
||||
next(state);
|
||||
|
||||
while (isHex(peek(state)))
|
||||
next(state);
|
||||
return makeToken(state, TOKEN_HEXNUMBER);
|
||||
case 'b': // binary number
|
||||
next(state);
|
||||
|
||||
return makeToken(state, TOKEN_HEXNUMBER);
|
||||
case 'b': // binary number
|
||||
while (peek(state) == '0' || peek(state) == '1')
|
||||
next(state);
|
||||
|
||||
while (peek(state) == '0' || peek(state) == '1')
|
||||
next(state);
|
||||
|
||||
return makeToken(state, TOKEN_BINNUMBER);
|
||||
default: // it's a one digit number!!!!!
|
||||
if (!isNumerical(peek(state)) && !(peek(state) == '.'))
|
||||
return makeToken(state, TOKEN_NUMBER);
|
||||
// if it is a number, fall through and parse normally
|
||||
return makeToken(state, TOKEN_BINNUMBER);
|
||||
default: // it's a one digit number!!!!!
|
||||
if (!isNumerical(peek(state)) && !(peek(state) == '.'))
|
||||
return makeToken(state, TOKEN_NUMBER);
|
||||
// if it is a number, fall through and parse normally
|
||||
}
|
||||
|
||||
|
||||
// consume number
|
||||
while (isNumerical(peek(state))) {
|
||||
next(state);
|
||||
@ -342,80 +380,103 @@ CToken parseNumber(CLexState *state) {
|
||||
return makeToken(state, TOKEN_NUMBER);
|
||||
}
|
||||
|
||||
CToken parseIdentifier(CLexState *state) {
|
||||
static CToken parseIdentifier(CLexState *state)
|
||||
{
|
||||
// read literal
|
||||
while ((isAlpha(peek(state)) || isNumerical(peek(state))) && !isEnd(state))
|
||||
while ((isAlpha(peek(state)) || isNumerical(peek(state))) && !isEnd(state))
|
||||
next(state);
|
||||
|
||||
|
||||
return makeToken(state, identifierType(state)); // is it a reserved word?
|
||||
}
|
||||
|
||||
CLexState *cosmoL_newLexState(CState *cstate, const char *source) {
|
||||
CLexState *state = cosmoM_xmalloc(cstate, sizeof(CLexState));
|
||||
state->startChar = (char*)source;
|
||||
state->currentChar = (char*)source;
|
||||
void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source)
|
||||
{
|
||||
state->startChar = (char *)source;
|
||||
state->currentChar = (char *)source;
|
||||
state->line = 1;
|
||||
state->lastLine = 0;
|
||||
state->lastType = TOKEN_ERROR;
|
||||
state->cstate = cstate;
|
||||
|
||||
resetBuffer(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void cosmoL_freeLexState(CState *state, CLexState *lstate) {
|
||||
cosmoM_free(state, CLexState, lstate);
|
||||
void cosmoL_cleanupLexState(CState *state, CLexState *lstate)
|
||||
{
|
||||
// stubbed
|
||||
}
|
||||
|
||||
CToken cosmoL_scanToken(CLexState *state) {
|
||||
CToken cosmoL_scanToken(CLexState *state)
|
||||
{
|
||||
skipWhitespace(state);
|
||||
|
||||
state->startChar = state->currentChar;
|
||||
|
||||
if (isEnd(state))
|
||||
return makeToken(state, TOKEN_EOF);
|
||||
|
||||
|
||||
char c = next(state);
|
||||
|
||||
switch (c) {
|
||||
// single character tokens
|
||||
case '(': return makeToken(state, TOKEN_LEFT_PAREN);
|
||||
case ')': return makeToken(state, TOKEN_RIGHT_PAREN);
|
||||
case '{': return makeToken(state, TOKEN_LEFT_BRACE);
|
||||
case '}': return makeToken(state, TOKEN_RIGHT_BRACE);
|
||||
case '[': return makeToken(state, TOKEN_LEFT_BRACKET);
|
||||
case ']': return makeToken(state, TOKEN_RIGHT_BRACKET);
|
||||
case ';': return makeToken(state, TOKEN_EOS);
|
||||
case ',': return makeToken(state, TOKEN_COMMA);
|
||||
case ':': return makeToken(state, TOKEN_COLON);
|
||||
case '*': return makeToken(state, TOKEN_STAR);
|
||||
case '%': return makeToken(state, TOKEN_PERCENT);
|
||||
case '^': return makeToken(state, TOKEN_CARROT);
|
||||
case '#': return makeToken(state, TOKEN_POUND);
|
||||
case '/': return makeToken(state, TOKEN_SLASH);
|
||||
// two character tokens
|
||||
case '+':
|
||||
return match(state, '+') ? makeToken(state, TOKEN_PLUS_PLUS) : makeToken(state, TOKEN_PLUS);
|
||||
case '-':
|
||||
return match(state, '-') ? makeToken(state, TOKEN_MINUS_MINUS) : makeToken(state, TOKEN_MINUS);
|
||||
case '.':
|
||||
return match(state, '.') ? (match(state, '.') ? makeToken(state, TOKEN_DOT_DOT_DOT) : makeToken(state, TOKEN_DOT_DOT)) : makeToken(state, TOKEN_DOT);
|
||||
case '!':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_BANG_EQUAL) : makeToken(state, TOKEN_BANG);
|
||||
case '=':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_EQUAL_EQUAL) : makeToken(state, TOKEN_EQUAL);
|
||||
case '>':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_GREATER_EQUAL) : makeToken(state, TOKEN_GREATER);
|
||||
case '<':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_LESS_EQUAL) : makeToken(state, TOKEN_LESS);
|
||||
// literals
|
||||
case '"': return parseString(state);
|
||||
default:
|
||||
if (isNumerical(c))
|
||||
return parseNumber(state);
|
||||
if (isAlpha(c))
|
||||
return parseIdentifier(state);
|
||||
// single character tokens
|
||||
case '(':
|
||||
return makeToken(state, TOKEN_LEFT_PAREN);
|
||||
case ')':
|
||||
return makeToken(state, TOKEN_RIGHT_PAREN);
|
||||
case '{':
|
||||
return makeToken(state, TOKEN_LEFT_BRACE);
|
||||
case '}':
|
||||
return makeToken(state, TOKEN_RIGHT_BRACE);
|
||||
case '[':
|
||||
return makeToken(state, TOKEN_LEFT_BRACKET);
|
||||
case ']':
|
||||
return makeToken(state, TOKEN_RIGHT_BRACKET);
|
||||
case ';':
|
||||
return makeToken(state, TOKEN_EOS);
|
||||
case ',':
|
||||
return makeToken(state, TOKEN_COMMA);
|
||||
case ':':
|
||||
return makeToken(state, TOKEN_COLON);
|
||||
case '*':
|
||||
return makeToken(state, TOKEN_STAR);
|
||||
case '%':
|
||||
return makeToken(state, TOKEN_PERCENT);
|
||||
case '^':
|
||||
return makeToken(state, TOKEN_CARROT);
|
||||
case '#':
|
||||
return makeToken(state, TOKEN_POUND);
|
||||
case '/':
|
||||
return makeToken(state, TOKEN_SLASH);
|
||||
// two character tokens
|
||||
case '+':
|
||||
return match(state, '+') ? makeToken(state, TOKEN_PLUS_PLUS) : makeToken(state, TOKEN_PLUS);
|
||||
case '-':
|
||||
return match(state, '-') ? makeToken(state, TOKEN_MINUS_MINUS)
|
||||
: makeToken(state, TOKEN_MINUS);
|
||||
case '.':
|
||||
return match(state, '.') ? (match(state, '.') ? makeToken(state, TOKEN_DOT_DOT_DOT)
|
||||
: makeToken(state, TOKEN_DOT_DOT))
|
||||
: makeToken(state, TOKEN_DOT);
|
||||
case '!':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_BANG_EQUAL)
|
||||
: makeToken(state, TOKEN_BANG);
|
||||
case '=':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_EQUAL_EQUAL)
|
||||
: makeToken(state, TOKEN_EQUAL);
|
||||
case '>':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_GREATER_EQUAL)
|
||||
: makeToken(state, TOKEN_GREATER);
|
||||
case '<':
|
||||
return match(state, '=') ? makeToken(state, TOKEN_LESS_EQUAL)
|
||||
: makeToken(state, TOKEN_LESS);
|
||||
// literals
|
||||
case '"':
|
||||
return parseString(state);
|
||||
default:
|
||||
if (isNumerical(c))
|
||||
return parseNumber(state);
|
||||
if (isAlpha(c))
|
||||
return parseIdentifier(state);
|
||||
}
|
||||
|
||||
return makeError(state, "Unknown symbol!");
|
||||
|
27
src/clex.h
27
src/clex.h
@ -3,7 +3,8 @@
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
// single character tokens
|
||||
TOKEN_LEFT_PAREN,
|
||||
TOKEN_RIGHT_PAREN,
|
||||
@ -56,7 +57,7 @@ typedef enum {
|
||||
TOKEN_ELSEIF,
|
||||
TOKEN_END,
|
||||
TOKEN_FOR,
|
||||
TOKEN_FUNCTION,
|
||||
TOKEN_FUNC,
|
||||
TOKEN_PROTO,
|
||||
TOKEN_IF,
|
||||
TOKEN_IN,
|
||||
@ -65,41 +66,45 @@ typedef enum {
|
||||
TOKEN_OR,
|
||||
TOKEN_RETURN,
|
||||
TOKEN_THEN,
|
||||
TOKEN_VAR,
|
||||
TOKEN_LET,
|
||||
TOKEN_WHILE,
|
||||
|
||||
TOKEN_ERROR,
|
||||
TOKEN_EOF
|
||||
} CTokenType;
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
CTokenType type;
|
||||
const char *word;
|
||||
int len;
|
||||
} CReservedWord;
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
CTokenType type;
|
||||
char *start;
|
||||
int length;
|
||||
int line;
|
||||
} CToken;
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
char *currentChar;
|
||||
char *startChar;
|
||||
char *buffer; // if non-NULL & bufCount > 0, token->start & token->length will be set to buffer & bufCount respectively
|
||||
char *buffer; // if non-NULL & bufCount > 0, token->start & token->length will be set to buffer
|
||||
// & bufCount respectively. used exclusively for string literals
|
||||
size_t bufCount;
|
||||
size_t bufCap;
|
||||
int line; // current line
|
||||
size_t bufCap;
|
||||
int line; // current line
|
||||
int lastLine; // line of the previous consumed token
|
||||
bool isEnd;
|
||||
CTokenType lastType;
|
||||
CState *cstate;
|
||||
} CLexState;
|
||||
|
||||
CLexState *cosmoL_newLexState(CState *state, const char *source);
|
||||
void cosmoL_freeLexState(CState *state, CLexState *lstate);
|
||||
void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source);
|
||||
void cosmoL_cleanupLexState(CState *state, CLexState *lstate);
|
||||
|
||||
CToken cosmoL_scanToken(CLexState *state);
|
||||
|
||||
|
311
src/cmem.c
311
src/cmem.c
@ -1,14 +1,34 @@
|
||||
#include "cmem.h"
|
||||
#include "cstate.h"
|
||||
#include "cvalue.h"
|
||||
#include "ctable.h"
|
||||
#include "cparse.h"
|
||||
#include "cobj.h"
|
||||
|
||||
#include "cbaselib.h"
|
||||
#include "cobj.h"
|
||||
#include "cparse.h"
|
||||
#include "cstate.h"
|
||||
#include "ctable.h"
|
||||
#include "cvalue.h"
|
||||
#include "cvm.h"
|
||||
|
||||
// realloc wrapper
|
||||
void *cosmoM_reallocate(CState* state, void *buf, size_t oldSize, size_t newSize) {
|
||||
void *cosmoM_reallocate(CState *state, void *buf, size_t oldSize, size_t newSize)
|
||||
{
|
||||
if (buf == NULL)
|
||||
oldSize = 0;
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
printf("old allocated bytes: %ld\n", state->allocatedBytes);
|
||||
if (buf) {
|
||||
if (newSize == 0) {
|
||||
printf("freeing %p, reclaiming %ld bytes...\n", buf, oldSize);
|
||||
} else {
|
||||
printf("realloc %p, byte difference: %ld\n", buf, newSize - oldSize);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
state->allocatedBytes += newSize - oldSize;
|
||||
#ifdef GC_DEBUG
|
||||
printf("new allocated bytes: %ld\n", state->allocatedBytes);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
if (newSize == 0) { // it needs to be freed
|
||||
free(buf);
|
||||
@ -19,11 +39,11 @@ void *cosmoM_reallocate(CState* state, void *buf, size_t oldSize, size_t newSize
|
||||
if (!(cosmoM_isFrozen(state)) && newSize > oldSize) {
|
||||
cosmoM_collectGarbage(state);
|
||||
}
|
||||
#ifdef GC_DEBUG
|
||||
# ifdef GC_DEBUG
|
||||
else {
|
||||
printf("GC event ignored! state frozen! [%d]\n", state->freezeGC);
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
#else
|
||||
cosmoM_checkGarbage(state, 0);
|
||||
#endif
|
||||
@ -31,15 +51,21 @@ void *cosmoM_reallocate(CState* state, void *buf, size_t oldSize, size_t newSize
|
||||
// if NULL is passed, realloc() acts like malloc()
|
||||
void *newBuf = realloc(buf, newSize);
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
printf("allocating new buffer of size %ld at %p\n", newSize - oldSize, newBuf);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
if (newBuf == NULL) {
|
||||
CERROR("failed to allocate memory!");
|
||||
printf("[ERROR] failed to allocate memory!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
COSMO_API bool cosmoM_checkGarbage(CState *state, size_t needed) {
|
||||
COSMO_API bool cosmoM_checkGarbage(CState *state, size_t needed)
|
||||
{
|
||||
if (!(cosmoM_isFrozen(state)) && state->allocatedBytes + needed > state->nextGC) {
|
||||
cosmoM_collectGarbage(state); // cya lol
|
||||
return true;
|
||||
@ -48,14 +74,15 @@ COSMO_API bool cosmoM_checkGarbage(CState *state, size_t needed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void markObject(CState *state, CObj *obj);
|
||||
void markValue(CState *state, CValue val);
|
||||
static void markObject(CState *state, CObj *obj);
|
||||
static void markValue(CState *state, CValue val);
|
||||
|
||||
void markTable(CState *state, CTable *tbl) {
|
||||
static void markTable(CState *state, CTable *tbl)
|
||||
{
|
||||
if (tbl->table == NULL) // table is still being initialized
|
||||
return;
|
||||
|
||||
int cap = tbl->capacityMask + 1;
|
||||
|
||||
int cap = cosmoT_getCapacity(tbl);
|
||||
for (int i = 0; i < cap; i++) {
|
||||
CTableEntry *entry = &tbl->table[i];
|
||||
markValue(state, entry->key);
|
||||
@ -63,15 +90,23 @@ void markTable(CState *state, CTable *tbl) {
|
||||
}
|
||||
}
|
||||
|
||||
// frees white members from the table
|
||||
void tableRemoveWhite(CState *state, CTable *tbl) {
|
||||
// removes white members from the table
|
||||
static void tableRemoveWhite(CState *state, CTable *tbl)
|
||||
{
|
||||
if (tbl->table == NULL) // table is still being initialized
|
||||
return;
|
||||
|
||||
int cap = tbl->capacityMask + 1;
|
||||
|
||||
int cap = cosmoT_getCapacity(tbl);
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
printf("tableRemoveWhite: %p, cap: %d\n", tbl, cap);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < cap; i++) {
|
||||
CTableEntry *entry = &tbl->table[i];
|
||||
if (IS_REF(entry->key) && !(cosmoV_readRef(entry->key))->isMarked) { // if the key is a object and it's white (unmarked), remove it from the table
|
||||
if (IS_REF(entry->key) &&
|
||||
!(cosmoV_readRef(entry->key))->isMarked) { // if the key is a object and it's white
|
||||
// (unmarked), remove it from the table
|
||||
cosmoT_remove(state, tbl, entry->key);
|
||||
}
|
||||
}
|
||||
@ -79,79 +114,83 @@ void tableRemoveWhite(CState *state, CTable *tbl) {
|
||||
cosmoT_checkShrink(state, tbl); // recovers the memory we're no longer using
|
||||
}
|
||||
|
||||
void markArray(CState *state, CValueArray *array) {
|
||||
static void markArray(CState *state, CValueArray *array)
|
||||
{
|
||||
for (size_t i = 0; i < array->count; i++) {
|
||||
markValue(state, array->values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// mark all references associated with the object
|
||||
void blackenObject(CState *state, CObj *obj) {
|
||||
markObject(state, (CObj*)obj->proto);
|
||||
// black = keep, white = discard
|
||||
static void blackenObject(CState *state, CObj *obj)
|
||||
{
|
||||
markObject(state, (CObj *)obj->proto);
|
||||
switch (obj->type) {
|
||||
case COBJ_STRING:
|
||||
case COBJ_CFUNCTION:
|
||||
// stubbed
|
||||
break;
|
||||
case COBJ_OBJECT: {
|
||||
// mark everything this object is keeping track of
|
||||
CObjObject *cobj = (CObjObject*)obj;
|
||||
markTable(state, &cobj->tbl);
|
||||
break;
|
||||
}
|
||||
case COBJ_TABLE: { // tables are just wrappers for CTable
|
||||
CObjTable *tbl = (CObjTable*)obj;
|
||||
markTable(state, &tbl->tbl);
|
||||
break;
|
||||
}
|
||||
case COBJ_UPVALUE: {
|
||||
markValue(state, ((CObjUpval*)obj)->closed);
|
||||
break;
|
||||
}
|
||||
case COBJ_FUNCTION: {
|
||||
CObjFunction *func = (CObjFunction*)obj;
|
||||
markObject(state, (CObj*)func->name);
|
||||
markObject(state, (CObj*)func->module);
|
||||
markArray(state, &func->chunk.constants);
|
||||
case COBJ_STRING:
|
||||
case COBJ_CFUNCTION:
|
||||
// stubbed
|
||||
break;
|
||||
case COBJ_OBJECT: {
|
||||
// mark everything this object is keeping track of
|
||||
CObjObject *cobj = (CObjObject *)obj;
|
||||
markTable(state, &cobj->tbl);
|
||||
break;
|
||||
}
|
||||
case COBJ_TABLE: { // tables are just wrappers for CTable
|
||||
CObjTable *tbl = (CObjTable *)obj;
|
||||
markTable(state, &tbl->tbl);
|
||||
break;
|
||||
}
|
||||
case COBJ_UPVALUE: {
|
||||
markValue(state, ((CObjUpval *)obj)->closed);
|
||||
break;
|
||||
}
|
||||
case COBJ_FUNCTION: {
|
||||
CObjFunction *func = (CObjFunction *)obj;
|
||||
markObject(state, (CObj *)func->name);
|
||||
markObject(state, (CObj *)func->module);
|
||||
markArray(state, &func->chunk.constants);
|
||||
|
||||
break;
|
||||
}
|
||||
case COBJ_METHOD: {
|
||||
CObjMethod *method = (CObjMethod*)obj;
|
||||
markValue(state, method->func);
|
||||
markObject(state, (CObj*)method->obj);
|
||||
break;
|
||||
}
|
||||
case COBJ_ERROR: {
|
||||
CObjError *err = (CObjError*)obj;
|
||||
markValue(state, err->err);
|
||||
break;
|
||||
}
|
||||
case COBJ_METHOD: {
|
||||
CObjMethod *method = (CObjMethod *)obj;
|
||||
markValue(state, method->func);
|
||||
markObject(state, (CObj *)method->obj);
|
||||
break;
|
||||
}
|
||||
case COBJ_ERROR: {
|
||||
CObjError *err = (CObjError *)obj;
|
||||
markValue(state, err->err);
|
||||
|
||||
// mark callframes
|
||||
for (int i = 0; i < err->frameCount; i++)
|
||||
markObject(state, (CObj*)err->frames[i].closure);
|
||||
// mark callframes
|
||||
for (int i = 0; i < err->frameCount; i++)
|
||||
markObject(state, (CObj *)err->frames[i].closure);
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case COBJ_CLOSURE: {
|
||||
CObjClosure *closure = (CObjClosure *)obj;
|
||||
markObject(state, (CObj *)closure->function);
|
||||
|
||||
// mark all upvalues
|
||||
for (int i = 0; i < closure->upvalueCount; i++) {
|
||||
markObject(state, (CObj *)closure->upvalues[i]);
|
||||
}
|
||||
case COBJ_CLOSURE: {
|
||||
CObjClosure *closure = (CObjClosure*)obj;
|
||||
markObject(state, (CObj*)closure->function);
|
||||
|
||||
// mark all upvalues
|
||||
for (int i = 0; i < closure->upvalueCount; i++) {
|
||||
markObject(state, (CObj*)closure->upvalues[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
#ifdef GC_DEBUG
|
||||
printf("Unknown type in blackenObject with %p, type %d\n", (void*)obj, obj->type);
|
||||
printf("Unknown type in blackenObject with %p, type %d\n", (void *)obj, obj->type);
|
||||
#endif
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void markObject(CState *state, CObj *obj) {
|
||||
static void markObject(CState *state, CObj *obj)
|
||||
{
|
||||
if (obj == NULL || obj->isMarked) // skip if NULL or already marked
|
||||
return;
|
||||
|
||||
@ -164,33 +203,37 @@ void markObject(CState *state, CObj *obj) {
|
||||
#endif
|
||||
|
||||
// they don't need to be added to the gray stack, they don't reference any other CObjs
|
||||
if (obj->type == COBJ_CFUNCTION || obj->type == COBJ_STRING)
|
||||
if (obj->type == COBJ_CFUNCTION || obj->type == COBJ_STRING)
|
||||
return;
|
||||
|
||||
// we can use cosmoM_growarray because we lock the GC when we entered in cosmoM_collectGarbage
|
||||
cosmoM_growarray(state, CObj*, state->grayStack.array, state->grayStack.count, state->grayStack.capacity);
|
||||
cosmoM_growArray(state, CObj *, state->grayStack.array, state->grayStack.count,
|
||||
state->grayStack.capacity);
|
||||
|
||||
state->grayStack.array[state->grayStack.count++] = obj;
|
||||
}
|
||||
|
||||
void markValue(CState *state, CValue val) {
|
||||
static void markValue(CState *state, CValue val)
|
||||
{
|
||||
if (IS_REF(val))
|
||||
markObject(state, cosmoV_readRef(val));
|
||||
}
|
||||
|
||||
// trace our gray references
|
||||
void traceGrays(CState *state) {
|
||||
static void traceGrays(CState *state)
|
||||
{
|
||||
while (state->grayStack.count > 0) {
|
||||
CObj* obj = state->grayStack.array[--state->grayStack.count];
|
||||
CObj *obj = state->grayStack.array[--state->grayStack.count];
|
||||
blackenObject(state, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void sweep(CState *state) {
|
||||
static void sweep(CState *state)
|
||||
{
|
||||
CObj *prev = NULL;
|
||||
CObj *object = state->objects;
|
||||
while (object != NULL) {
|
||||
if (object->isMarked) { // skip over it
|
||||
if (object->isMarked) { // skip over it
|
||||
object->isMarked = false; // rest to white
|
||||
prev = object;
|
||||
object = object->next;
|
||||
@ -204,22 +247,25 @@ void sweep(CState *state) {
|
||||
prev->next = object;
|
||||
}
|
||||
|
||||
// call __gc on the object
|
||||
CObjObject *protoObject = cosmoO_grabProto(oldObj);
|
||||
CValue res;
|
||||
|
||||
// use user-defined __gc
|
||||
if (protoObject != NULL && cosmoO_getIString(state, protoObject, ISTRING_GC, &res)) {
|
||||
cosmoV_pushValue(state, res);
|
||||
cosmoV_pushRef(state, (CObj *)oldObj);
|
||||
cosmoV_call(state, 1, 0);
|
||||
}
|
||||
|
||||
|
||||
cosmoO_free(state, oldObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void markUserRoots(CState *state) {
|
||||
CObj *root = state->userRoots;
|
||||
|
||||
// traverse userRoots and mark all the object
|
||||
while (root != NULL) {
|
||||
markObject(state, root);
|
||||
root = root->nextRoot;
|
||||
}
|
||||
}
|
||||
|
||||
void markRoots(CState *state) {
|
||||
static void markRoots(CState *state)
|
||||
{
|
||||
// mark all values on the stack
|
||||
for (StkPtr value = state->stack; value < state->top; value++) {
|
||||
markValue(state, *value);
|
||||
@ -227,95 +273,54 @@ void markRoots(CState *state) {
|
||||
|
||||
// mark all active callframe closures
|
||||
for (int i = 0; i < state->frameCount; i++) {
|
||||
markObject(state, (CObj*)state->callFrame[i].closure);
|
||||
markObject(state, (CObj *)state->callFrame[i].closure);
|
||||
}
|
||||
|
||||
// mark all open upvalues
|
||||
for (CObjUpval *upvalue = state->openUpvalues; upvalue != NULL; upvalue = upvalue->next) {
|
||||
markObject(state, (CObj*)upvalue);
|
||||
markObject(state, (CObj *)upvalue);
|
||||
}
|
||||
|
||||
markObject(state, (CObj*)state->globals);
|
||||
markObject(state, (CObj *)state->globals);
|
||||
|
||||
// mark all internal strings
|
||||
for (int i = 0; i < ISTRING_MAX; i++)
|
||||
markObject(state, (CObj*)state->iStrings[i]);
|
||||
markObject(state, (CObj *)state->iStrings[i]);
|
||||
|
||||
// mark the user defined roots
|
||||
markUserRoots(state);
|
||||
|
||||
// mark other misc. internally reserved objects
|
||||
markObject(state, (CObj*)state->error);
|
||||
markTable(state, &state->registry);
|
||||
|
||||
for (int i = 0; i < COBJ_MAX; i++)
|
||||
markObject(state, (CObj*)state->protoObjects[i]);
|
||||
markObject(state, (CObj *)state->protoObjects[i]);
|
||||
|
||||
traceGrays(state);
|
||||
}
|
||||
|
||||
COSMO_API void cosmoM_collectGarbage(CState *state) {
|
||||
COSMO_API void cosmoM_collectGarbage(CState *state)
|
||||
{
|
||||
cosmoM_freezeGC(state);
|
||||
#ifdef GC_DEBUG
|
||||
printf("-- GC start\n");
|
||||
size_t start = state->allocatedBytes;
|
||||
#endif
|
||||
cosmoM_freezeGC(state); // we don't want a recursive garbage collection event!
|
||||
|
||||
markRoots(state);
|
||||
|
||||
tableRemoveWhite(state, &state->strings); // make sure we aren't referencing any strings that are about to be freed
|
||||
tableRemoveWhite(
|
||||
state,
|
||||
&state->strings); // make sure we aren't referencing any strings that are about to be freed
|
||||
// now finally, free all the unmarked objects
|
||||
sweep(state);
|
||||
|
||||
// set our next GC event
|
||||
cosmoM_updateThreshhold(state);
|
||||
|
||||
state->freezeGC--; // we don't want to use cosmoM_unfreezeGC because that might trigger a GC event (if GC_STRESS is defined)
|
||||
#ifdef GC_DEBUG
|
||||
printf("-- GC end, reclaimed %ld bytes (started at %ld, ended at %ld), next garbage collection scheduled at %ld bytes\n",
|
||||
start - state->allocatedBytes, start, state->allocatedBytes, state->nextGC);
|
||||
getchar(); // pauses execution
|
||||
printf("-- GC end, reclaimed %ld bytes (started at %ld, ended at %ld), next garbage collection "
|
||||
"scheduled at %ld bytes\n",
|
||||
start - state->allocatedBytes, start, state->allocatedBytes, state->nextGC);
|
||||
#endif
|
||||
cosmoM_unfreezeGC(state);
|
||||
}
|
||||
|
||||
COSMO_API void cosmoM_updateThreshhold(CState *state) {
|
||||
COSMO_API void cosmoM_updateThreshhold(CState *state)
|
||||
{
|
||||
state->nextGC = state->allocatedBytes * HEAP_GROW_FACTOR;
|
||||
}
|
||||
|
||||
COSMO_API void cosmoM_addRoot(CState *state, CObj *newRoot) {
|
||||
// first, check and make sure this root doesn't already exist in the list
|
||||
CObj *root = state->userRoots;
|
||||
while (root != NULL) {
|
||||
if (root == newRoot) // found in the list, abort
|
||||
return;
|
||||
|
||||
root = root->nextRoot;
|
||||
}
|
||||
|
||||
// adds root to userRoot linked list
|
||||
newRoot->nextRoot = state->userRoots;
|
||||
state->userRoots = newRoot;
|
||||
}
|
||||
|
||||
COSMO_API void cosmoM_removeRoot(CState *state, CObj *oldRoot) {
|
||||
CObj *prev = NULL;
|
||||
CObj *root = state->userRoots;
|
||||
|
||||
// traverse the userRoot linked list
|
||||
while (root != NULL) {
|
||||
if (root == oldRoot) { // found root in list
|
||||
|
||||
// remove from the linked list
|
||||
if (prev == NULL) {
|
||||
state->userRoots = root->nextRoot;
|
||||
} else {
|
||||
prev->nextRoot = root->nextRoot;
|
||||
}
|
||||
|
||||
root->nextRoot = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
prev = root;
|
||||
root = root->nextRoot;
|
||||
}
|
||||
}
|
||||
|
84
src/cmem.h
84
src/cmem.h
@ -2,80 +2,74 @@
|
||||
#define CMEME_C // meme lol
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
#include "cstate.h"
|
||||
|
||||
//#define GC_STRESS
|
||||
//#define GC_DEBUG
|
||||
// arrays *must* grow by a factor of 2
|
||||
#define GROW_FACTOR 2
|
||||
// #define GC_STRESS
|
||||
// #define GC_DEBUG
|
||||
// arrays *must* grow by a factor of 2
|
||||
#define GROW_FACTOR 2
|
||||
#define HEAP_GROW_FACTOR 2
|
||||
#define ARRAY_START 8
|
||||
#define ARRAY_START 8
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
#define cosmoM_freearray(state, type, buf, capacity) \
|
||||
printf("freeing array %p [size %lu] at %s:%d\n", buf, sizeof(type) * capacity, __FILE__, __LINE__); \
|
||||
cosmoM_reallocate(state, buf, sizeof(type) * capacity, 0)
|
||||
# define cosmoM_freeArray(state, type, buf, capacity) \
|
||||
printf("freeing array %p [size %lu] at %s:%d\n", buf, sizeof(type) * capacity, __FILE__, \
|
||||
__LINE__); \
|
||||
cosmoM_reallocate(state, buf, sizeof(type) * capacity, 0)
|
||||
#else
|
||||
#define cosmoM_freearray(state, type, buf, capacity) \
|
||||
cosmoM_reallocate(state, buf, sizeof(type) * capacity, 0)
|
||||
# define cosmoM_freeArray(state, type, buf, capacity) \
|
||||
cosmoM_reallocate(state, buf, sizeof(type) * capacity, 0)
|
||||
#endif
|
||||
|
||||
#define cosmoM_growarray(state, type, buf, count, capacity) \
|
||||
if (count >= capacity || buf == NULL) { \
|
||||
int old = capacity; \
|
||||
capacity = old * GROW_FACTOR; \
|
||||
buf = (type*)cosmoM_reallocate(state, buf, sizeof(type) *old, sizeof(type) *capacity); \
|
||||
#define cosmoM_growArray(state, type, buf, count, capacity) \
|
||||
if (count >= capacity || buf == NULL) { \
|
||||
int old = capacity; \
|
||||
capacity = old * GROW_FACTOR; \
|
||||
buf = (type *)cosmoM_reallocate(state, buf, sizeof(type) * old, sizeof(type) * capacity); \
|
||||
}
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
#define cosmoM_free(state, type, x) \
|
||||
printf("freeing %p [size %lu] at %s:%d\n", x, sizeof(type), __FILE__, __LINE__); \
|
||||
cosmoM_reallocate(state, x, sizeof(type), 0)
|
||||
# define cosmoM_free(state, type, x) \
|
||||
printf("freeing %p [size %lu] at %s:%d\n", x, sizeof(type), __FILE__, __LINE__); \
|
||||
cosmoM_reallocate(state, x, sizeof(type), 0)
|
||||
#else
|
||||
#define cosmoM_free(state, type, x) \
|
||||
cosmoM_reallocate(state, x, sizeof(type), 0)
|
||||
# define cosmoM_free(state, type, x) cosmoM_reallocate(state, x, sizeof(type), 0)
|
||||
#endif
|
||||
|
||||
#define cosmoM_isFrozen(state) \
|
||||
(state->freezeGC > 0)
|
||||
#define cosmoM_isFrozen(state) (state->freezeGC > 0)
|
||||
|
||||
// cosmoM_freezeGC should only be used in the garbage collector !
|
||||
// if debugging, print the locations of when the state is frozen/unfrozen
|
||||
#ifdef GC_DEBUG
|
||||
#define cosmoM_freezeGC(state) \
|
||||
state->freezeGC++; \
|
||||
printf("freezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC)
|
||||
# define cosmoM_freezeGC(state) \
|
||||
state->freezeGC++; \
|
||||
printf("freezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC)
|
||||
|
||||
#define cosmoM_unfreezeGC(state) \
|
||||
state->freezeGC--; \
|
||||
printf("unfreezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC); \
|
||||
cosmoM_checkGarbage(state, 0)
|
||||
# define cosmoM_unfreezeGC(state) \
|
||||
state->freezeGC--; \
|
||||
printf("unfreezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC); \
|
||||
cosmoM_checkGarbage(state, 0)
|
||||
#else
|
||||
|
||||
// freeze's the garbage collector until cosmoM_unfreezeGC is called
|
||||
#define cosmoM_freezeGC(state) \
|
||||
state->freezeGC++
|
||||
# define cosmoM_freezeGC(state) state->freezeGC++
|
||||
|
||||
// unfreeze's the garbage collector and tries to run a garbage collection cycle
|
||||
#define cosmoM_unfreezeGC(state) \
|
||||
state->freezeGC--; \
|
||||
cosmoM_checkGarbage(state, 0)
|
||||
# define cosmoM_unfreezeGC(state) \
|
||||
state->freezeGC--; \
|
||||
cosmoM_checkGarbage(state, 0)
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
COSMO_API void *cosmoM_reallocate(CState *state, void *buf, size_t oldSize, size_t newSize);
|
||||
COSMO_API bool cosmoM_checkGarbage(CState *state, size_t needed); // returns true if GC event was triggered
|
||||
COSMO_API bool cosmoM_checkGarbage(CState *state,
|
||||
size_t needed); // returns true if GC event was triggered
|
||||
COSMO_API void cosmoM_collectGarbage(CState *state);
|
||||
COSMO_API void cosmoM_updateThreshhold(CState *state);
|
||||
|
||||
// lets the VM know you are holding a reference to a CObj and to not free it
|
||||
COSMO_API void cosmoM_addRoot(CState *state, CObj *newRoot);
|
||||
|
||||
// lets the VM know this root is no longer held in a reference and is able to be freed
|
||||
COSMO_API void cosmoM_removeRoot(CState *state, CObj *oldRoot);
|
||||
|
||||
// wrapper for cosmoM_reallocate so we can track our memory usage (it's also safer :P)
|
||||
static inline void *cosmoM_xmalloc(CState *state, size_t sz) {
|
||||
// wrapper for cosmoM_reallocate so we can track our memory usage
|
||||
static inline void *cosmoM_xmalloc(CState *state, size_t sz)
|
||||
{
|
||||
return cosmoM_reallocate(state, NULL, 0, sz);
|
||||
}
|
||||
|
||||
|
774
src/cobj.c
774
src/cobj.c
File diff suppressed because it is too large
Load Diff
155
src/cobj.h
155
src/cobj.h
@ -3,7 +3,10 @@
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
typedef enum CObjType {
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef enum CObjType
|
||||
{
|
||||
COBJ_STRING,
|
||||
COBJ_OBJECT,
|
||||
COBJ_TABLE,
|
||||
@ -17,64 +20,67 @@ typedef enum CObjType {
|
||||
COBJ_MAX
|
||||
} CObjType;
|
||||
|
||||
#include "cstate.h"
|
||||
#include "cchunk.h"
|
||||
#include "cvalue.h"
|
||||
#include "cstate.h"
|
||||
#include "ctable.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
typedef struct CState CState;
|
||||
typedef struct CCallFrame CCallFrame;
|
||||
typedef uint32_t cosmo_Flag;
|
||||
|
||||
#define CommonHeader CObj _obj
|
||||
#define readFlag(x, flag) (x & (1u << flag))
|
||||
#define setFlagOn(x, flag) (x |= (1u << flag))
|
||||
#define CommonHeader CObj _obj
|
||||
#define readFlag(x, flag) (x & (1u << flag))
|
||||
#define setFlagOn(x, flag) (x |= (1u << flag))
|
||||
|
||||
typedef int (*CosmoCFunction)(CState *state, int argCount, CValue *args);
|
||||
|
||||
typedef struct CObj {
|
||||
struct CObj
|
||||
{
|
||||
struct CObj *next;
|
||||
struct CObj *nextRoot; // for the root linked list
|
||||
struct CObjObject *proto; // protoobject, describes the behavior of the object
|
||||
CObjType type;
|
||||
bool isMarked; // for the GC
|
||||
} CObj;
|
||||
};
|
||||
|
||||
typedef struct CObjString {
|
||||
CommonHeader; // "is a" CObj
|
||||
char *str; // NULL termincated string
|
||||
struct CObjString
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
char *str; // NULL terminated string
|
||||
uint32_t hash; // for hashtable lookup
|
||||
int length;
|
||||
bool isIString;
|
||||
} CObjString;
|
||||
};
|
||||
|
||||
typedef struct CObjError {
|
||||
struct CObjError
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CValue err; // error string
|
||||
CValue err; // error string
|
||||
CCallFrame *frames;
|
||||
int frameCount;
|
||||
int line; // reserved for parser errors
|
||||
int line; // reserved for parser errors
|
||||
bool parserError; // if true, cosmoV_printError will format the error to the lexer
|
||||
} CObjError;
|
||||
};
|
||||
|
||||
typedef struct CObjObject {
|
||||
struct CObjObject
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CTable tbl;
|
||||
cosmo_Flag istringFlags; // enables us to have a much faster lookup for reserved IStrings (like __init, __index, etc.)
|
||||
union { // userdata (NULL by default)
|
||||
cosmo_Flag istringFlags; // enables us to have a much faster lookup for reserved IStrings (like
|
||||
// __init, __index, etc.)
|
||||
union
|
||||
{ // userdata (NULL by default)
|
||||
void *userP;
|
||||
int userI;
|
||||
};
|
||||
int userT; // user-defined type (for describing the userdata pointer/integer)
|
||||
bool isLocked;
|
||||
} CObjObject;
|
||||
};
|
||||
|
||||
typedef struct CObjTable { // table, a wrapper for CTable
|
||||
struct CObjTable
|
||||
{ // table, a wrapper for CTable
|
||||
CommonHeader; // "is a" CObj
|
||||
CTable tbl;
|
||||
} CObjTable;
|
||||
};
|
||||
|
||||
typedef struct CObjFunction {
|
||||
struct CObjFunction
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CChunk chunk;
|
||||
CObjString *name;
|
||||
@ -82,64 +88,72 @@ typedef struct CObjFunction {
|
||||
int args;
|
||||
int upvals;
|
||||
bool variadic;
|
||||
} CObjFunction;
|
||||
};
|
||||
|
||||
typedef struct CObjCFunction {
|
||||
struct CObjCFunction
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CosmoCFunction cfunc;
|
||||
} CObjCFunction;
|
||||
};
|
||||
|
||||
typedef struct CObjClosure {
|
||||
struct CObjClosure
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CObjFunction *function;
|
||||
CObjUpval **upvalues;
|
||||
int upvalueCount;
|
||||
} CObjClosure;
|
||||
};
|
||||
|
||||
typedef struct CObjMethod {
|
||||
struct CObjMethod
|
||||
{
|
||||
CommonHeader; // "is a " CObj
|
||||
CValue func;
|
||||
CObj *obj; // obj this method is bound too
|
||||
} CObjMethod;
|
||||
};
|
||||
|
||||
typedef struct CObjUpval {
|
||||
struct CObjUpval
|
||||
{
|
||||
CommonHeader; // "is a" CObj
|
||||
CValue closed;
|
||||
CValue *val;
|
||||
struct CObjUpval *next;
|
||||
} CObjUpval;
|
||||
};
|
||||
|
||||
#undef CommonHeader
|
||||
|
||||
#define IS_STRING(x) isObjType(x, COBJ_STRING)
|
||||
#define IS_OBJECT(x) isObjType(x, COBJ_OBJECT)
|
||||
#define IS_TABLE(x) isObjType(x, COBJ_TABLE)
|
||||
#define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION)
|
||||
#define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION)
|
||||
#define IS_METHOD(x) isObjType(x, COBJ_METHOD)
|
||||
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
|
||||
#define IS_STRING(x) isObjType(x, COBJ_STRING)
|
||||
#define IS_OBJECT(x) isObjType(x, COBJ_OBJECT)
|
||||
#define IS_TABLE(x) isObjType(x, COBJ_TABLE)
|
||||
#define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION)
|
||||
#define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION)
|
||||
#define IS_METHOD(x) isObjType(x, COBJ_METHOD)
|
||||
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
|
||||
|
||||
#define cosmoV_readString(x) ((CObjString*)cosmoV_readRef(x))
|
||||
#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readRef(x))
|
||||
#define cosmoV_readTable(x) ((CObjTable*)cosmoV_readRef(x))
|
||||
#define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readRef(x))
|
||||
#define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readRef(x))->cfunc)
|
||||
#define cosmoV_readMethod(x) ((CObjMethod*)cosmoV_readRef(x))
|
||||
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readRef(x))
|
||||
#define cosmoV_readString(x) ((CObjString *)cosmoV_readRef(x))
|
||||
#define cosmoV_readCString(x) (((CObjString *)cosmoV_readRef(x))->str)
|
||||
#define cosmoV_readObject(x) ((CObjObject *)cosmoV_readRef(x))
|
||||
#define cosmoV_readTable(x) ((CObjTable *)cosmoV_readRef(x))
|
||||
#define cosmoV_readFunction(x) ((CObjFunction *)cosmoV_readRef(x))
|
||||
#define cosmoV_readCFunction(x) (((CObjCFunction *)cosmoV_readRef(x))->cfunc)
|
||||
#define cosmoV_readMethod(x) ((CObjMethod *)cosmoV_readRef(x))
|
||||
#define cosmoV_readClosure(x) ((CObjClosure *)cosmoV_readRef(x))
|
||||
#define cosmoV_readError(x) ((CObjError *)cosmoV_readRef(x))
|
||||
|
||||
#define cosmoO_readCString(x) ((CObjString*)x)->str
|
||||
#define cosmoO_readCString(x) ((CObjString *)x)->str
|
||||
#define cosmoO_readType(x) ((CObj *)x)->type
|
||||
|
||||
static inline bool isObjType(CValue val, CObjType type) {
|
||||
static inline bool isObjType(CValue val, CObjType type)
|
||||
{
|
||||
return IS_REF(val) && cosmoV_readRef(val)->type == type;
|
||||
}
|
||||
|
||||
// just protects against macro expansion
|
||||
static inline bool IS_CALLABLE(CValue val) {
|
||||
static inline bool IS_CALLABLE(CValue val)
|
||||
{
|
||||
return IS_CLOSURE(val) || IS_CFUNCTION(val) || IS_METHOD(val);
|
||||
}
|
||||
}
|
||||
|
||||
void cosmoO_free(CState *state, CObj* obj);
|
||||
bool cosmoO_equal(CObj* obj1, CObj* obj2);
|
||||
void cosmoO_free(CState *state, CObj *obj);
|
||||
bool cosmoO_equal(CState *state, CObj *obj1, CObj *obj2);
|
||||
|
||||
// walks the protos of obj and checks for proto
|
||||
bool cosmoO_isDescendant(CObj *obj, CObjObject *proto);
|
||||
@ -154,20 +168,23 @@ CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func);
|
||||
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val);
|
||||
|
||||
// grabs the base proto of the CObj* (if CObj is a CObjObject, that is returned)
|
||||
static inline CObjObject *cosmoO_grabProto(CObj *obj) {
|
||||
return obj->type == COBJ_OBJECT ? (CObjObject*)obj : obj->proto;
|
||||
static inline CObjObject *cosmoO_grabProto(CObj *obj)
|
||||
{
|
||||
return obj->type == COBJ_OBJECT ? (CObjObject *)obj : obj->proto;
|
||||
}
|
||||
|
||||
bool cosmoO_getRawObject(CState *state, CObjObject *proto, CValue key, CValue *val, CObj *obj);
|
||||
void cosmoO_getRawObject(CState *state, CObjObject *proto, CValue key, CValue *val, CObj *obj);
|
||||
void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue val, CObj *obj);
|
||||
bool cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *val);
|
||||
bool cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue val);
|
||||
void cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *val);
|
||||
void cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue val);
|
||||
|
||||
// sets the user-defined pointer, if a user-define integer is already defined it will be over written
|
||||
// sets the user-defined pointer, if a user-define integer is already defined it will be over
|
||||
// written
|
||||
void cosmoO_setUserP(CObjObject *object, void *p);
|
||||
// gets the user-defined pointer
|
||||
void *cosmoO_getUserP(CObjObject *object);
|
||||
// sets the user-defined integer, if a user-define pointer is already defined it will be over written
|
||||
// sets the user-defined integer, if a user-define pointer is already defined it will be over
|
||||
// written
|
||||
void cosmoO_setUserI(CObjObject *object, int i);
|
||||
// gets the user-defined integer
|
||||
int cosmoO_getUserI(CObjObject *object);
|
||||
@ -183,10 +200,12 @@ void cosmoO_unlock(CObjObject *object);
|
||||
// internal string
|
||||
bool cosmoO_getIString(CState *state, CObjObject *object, int flag, CValue *val);
|
||||
|
||||
// copies the *str buffer to the heap and returns a CObjString struct which is also on the heap (length should not include the null terminator)
|
||||
// copies the *str buffer to the heap and returns a CObjString struct which is also on the heap
|
||||
// (length should not include the null terminator)
|
||||
CObjString *cosmoO_copyString(CState *state, const char *str, size_t length);
|
||||
|
||||
// length shouldn't include the null terminator! str should be a null terminated string! (char array should also have been allocated using cosmoM_xmalloc!)
|
||||
// length shouldn't include the null terminator! str should be a null terminated string! (char array
|
||||
// should also have been allocated using cosmoM_xmalloc!)
|
||||
CObjString *cosmoO_takeString(CState *state, char *str, size_t length);
|
||||
|
||||
// allocates a CObjStruct pointing directly to *str
|
||||
@ -203,7 +222,7 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t length,
|
||||
CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args);
|
||||
|
||||
COSMO_API void printObject(CObj *o);
|
||||
const char *cosmoO_typeStr(CObj* obj);
|
||||
const char *cosmoO_typeStr(CObj *obj);
|
||||
|
||||
CObjString *cosmoO_toString(CState *state, CObj *obj);
|
||||
cosmo_Number cosmoO_toNumber(CState *state, CObj *obj);
|
||||
|
@ -3,24 +3,25 @@
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
// instructions
|
||||
// instructions
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
// STACK/STATE MANIPULATION
|
||||
OP_LOADCONST, // pushes const[uint8_t] to the stack
|
||||
OP_SETGLOBAL, // pops and sets global[const[uint16_t]]
|
||||
OP_GETGLOBAL, // pushes global[const[uint16_t]]
|
||||
OP_SETLOCAL, // pops and sets base[uint8_t]
|
||||
OP_GETLOCAL, // pushes base[uint8_t]
|
||||
OP_GETUPVAL, // pushes closure->upvals[uint8_t]
|
||||
OP_SETUPVAL, // pops and sets closure->upvals[uint8_t]
|
||||
OP_PEJMP, // pops, if false jumps uint16_t
|
||||
OP_EJMP, // if peek(0) is falsey jumps uint16_t
|
||||
OP_JMP, // always jumps uint16_t
|
||||
OP_JMPBACK, // jumps -uint16_t
|
||||
OP_POP, // - pops[uint8_t] from stack
|
||||
OP_CALL, // calls top[-uint8_t] expecting uint8_t results
|
||||
OP_CLOSURE,
|
||||
OP_SETLOCAL, // pops and sets base[uint8_t]
|
||||
OP_GETLOCAL, // pushes base[uint8_t]
|
||||
OP_GETUPVAL, // pushes closure->upvals[uint8_t]
|
||||
OP_SETUPVAL, // pops and sets closure->upvals[uint8_t]
|
||||
OP_PEJMP, // pops, if false jumps uint16_t
|
||||
OP_EJMP, // if peek(0) is falsey jumps uint16_t
|
||||
OP_JMP, // always jumps uint16_t
|
||||
OP_JMPBACK, // jumps -uint16_t
|
||||
OP_POP, // - pops[uint8_t] from stack
|
||||
OP_CALL, // calls top[-uint8_t] expecting uint8_t results
|
||||
OP_CLOSURE,
|
||||
OP_CLOSE,
|
||||
OP_NEWTABLE,
|
||||
OP_NEWARRAY, // really just a table
|
||||
@ -44,10 +45,10 @@ typedef enum {
|
||||
OP_NOT,
|
||||
OP_NEGATE,
|
||||
OP_COUNT,
|
||||
OP_CONCAT, // concats uint8_t vars on the stack
|
||||
OP_INCLOCAL, // pushes old value to stack, adds (uint8_t-128) to local[uint8_t]
|
||||
OP_CONCAT, // concats uint8_t vars on the stack
|
||||
OP_INCLOCAL, // pushes old value to stack, adds (uint8_t-128) to local[uint8_t]
|
||||
OP_INCGLOBAL, // pushes old value to stack, adds (uint8_t-128) to globals[const[uint16_t]]
|
||||
OP_INCUPVAL, // pushes old value to stack, adds (uint8_t-128) to closure->upval[uint8_t]
|
||||
OP_INCUPVAL, // pushes old value to stack, adds (uint8_t-128) to closure->upval[uint8_t]
|
||||
OP_INCINDEX,
|
||||
OP_INCOBJECT, // pushes old value to stack, adds (uint8_t-128) to obj[const[uint16_t]]
|
||||
|
||||
@ -63,7 +64,7 @@ typedef enum {
|
||||
OP_FALSE,
|
||||
OP_NIL,
|
||||
|
||||
OP_RETURN
|
||||
OP_RETURN,
|
||||
} COPCODE; // there can be a max of 256 instructions
|
||||
|
||||
#endif
|
||||
|
39
src/cosmo.h
39
src/cosmo.h
@ -1,45 +1,61 @@
|
||||
#ifndef COSMOMAIN_H
|
||||
#define COSMOMAIN_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
/*
|
||||
SAFE_STACK:
|
||||
if undefined, the stack will not be checked for stack overflows. This may improve performance, however
|
||||
this will produce undefined behavior as you reach the stack limit (and may cause a seg fault!). It is recommended to keep this enabled.
|
||||
if undefined, the stack will not be checked for stack overflows. This may improve
|
||||
performance, however this will produce undefined behavior as you reach the stack limit (and may
|
||||
cause a seg fault!). It is recommended to keep this enabled.
|
||||
*/
|
||||
#define SAFE_STACK
|
||||
//#define NAN_BOXXED
|
||||
// #define SAFE_STACK
|
||||
|
||||
/*
|
||||
NAN_BOXXED:
|
||||
if undefined, the interpreter will use a tagged union to store values. This is the default.
|
||||
Note that even though the sizeof(CValue) is 8 bytes for NAN_BOXXED (as opposed to 16 bytes for
|
||||
the tagged union) no performance benefits were measured. I recommend keeping this undefined for
|
||||
now.
|
||||
*/
|
||||
// #define NAN_BOXXED
|
||||
|
||||
// forward declare *most* stuff so our headers are cleaner
|
||||
typedef struct CState CState;
|
||||
typedef struct CChunk CChunk;
|
||||
typedef struct CCallFrame CCallFrame;
|
||||
|
||||
#ifdef NAN_BOXXED
|
||||
typedef union CValue CValue;
|
||||
#else
|
||||
typedef struct CValue CValue;
|
||||
#endif
|
||||
|
||||
typedef struct CValueArray CValueArray;
|
||||
typedef uint32_t cosmo_Flag;
|
||||
|
||||
// objs
|
||||
typedef struct CObj CObj;
|
||||
typedef struct CObjObject CObjObject;
|
||||
typedef struct CObjString CObjString;
|
||||
typedef struct CObjUpval CObjUpval;
|
||||
typedef struct CObjFunction CObjFunction;
|
||||
typedef struct CObjCFunction CObjCFunction;
|
||||
typedef struct CObjMethod CObjMethod;
|
||||
typedef struct CObjError CObjError;
|
||||
typedef struct CObjObject CObjObject;
|
||||
typedef struct CObjTable CObjTable;
|
||||
typedef struct CObjClosure CObjClosure;
|
||||
|
||||
typedef uint8_t INSTRUCTION;
|
||||
|
||||
typedef int (*cosmo_Reader)(CState *state, void *data, size_t size, const void *ud);
|
||||
typedef int (*cosmo_Writer)(CState *state, const void *data, size_t size, const void *ud);
|
||||
|
||||
#define COSMOMAX_UPVALS 80
|
||||
#define FRAME_MAX 64
|
||||
#define STACK_MAX (256 * FRAME_MAX)
|
||||
@ -48,7 +64,4 @@ typedef uint8_t INSTRUCTION;
|
||||
#define UNNAMEDCHUNK "_main"
|
||||
#define COSMOASSERT(x) assert(x)
|
||||
|
||||
#define CERROR(err) \
|
||||
printf("%s : %s\n", "[ERROR]", err)
|
||||
|
||||
#endif
|
||||
|
844
src/cparse.c
844
src/cparse.c
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
#ifndef CPARSE_H
|
||||
#define CPARSE_H
|
||||
|
||||
#include "cosmo.h"
|
||||
#include "clex.h"
|
||||
#include "cosmo.h"
|
||||
|
||||
// compiles source into CChunk, if NULL is returned, a syntaxical error has occurred and pushed onto the stack
|
||||
CObjFunction* cosmoP_compileString(CState *state, const char *source, const char *module);
|
||||
// compiles source into CChunk
|
||||
CObjFunction *cosmoP_compileString(CState *state, const char *source, const char *module);
|
||||
|
||||
#endif
|
||||
|
117
src/cstate.c
117
src/cstate.c
@ -1,30 +1,51 @@
|
||||
#include "cstate.h"
|
||||
|
||||
#include "cchunk.h"
|
||||
#include "cmem.h"
|
||||
#include "cobj.h"
|
||||
#include "cvm.h"
|
||||
#include "cmem.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
CState *cosmoV_newState() {
|
||||
CPanic *cosmoV_newPanic(CState *state)
|
||||
{
|
||||
CPanic *panic = cosmoM_xmalloc(state, sizeof(CPanic));
|
||||
panic->top = state->top;
|
||||
panic->frameCount = state->frameCount;
|
||||
panic->freezeGC = state->freezeGC;
|
||||
panic->prev = state->panic;
|
||||
state->panic = panic;
|
||||
|
||||
return panic;
|
||||
}
|
||||
|
||||
void cosmoV_freePanic(CState *state)
|
||||
{
|
||||
CPanic *panic = state->panic;
|
||||
state->panic = panic->prev;
|
||||
|
||||
cosmoM_free(state, CPanic, panic);
|
||||
}
|
||||
|
||||
CState *cosmoV_newState()
|
||||
{
|
||||
// we use C's malloc because we don't want to trigger a GC with an invalid state
|
||||
CState *state = malloc(sizeof(CState));
|
||||
|
||||
if (state == NULL) {
|
||||
CERROR("failed to allocate memory!");
|
||||
printf("[ERROR] failed to allocate memory!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
state->panic = false;
|
||||
state->freezeGC = 1; // we start frozen
|
||||
state->panic = NULL;
|
||||
|
||||
// GC
|
||||
state->objects = NULL;
|
||||
state->userRoots = NULL;
|
||||
state->grayStack.count = 0;
|
||||
state->grayStack.capacity = 2;
|
||||
state->grayStack.array = NULL;
|
||||
state->allocatedBytes = sizeof(CState);
|
||||
state->allocatedBytes = 0;
|
||||
state->nextGC = 1024 * 8; // threshhold starts at 8kb
|
||||
|
||||
// init stack
|
||||
@ -32,8 +53,6 @@ CState *cosmoV_newState() {
|
||||
state->frameCount = 0;
|
||||
state->openUpvalues = NULL;
|
||||
|
||||
state->error = NULL;
|
||||
|
||||
// set default proto objects
|
||||
for (int i = 0; i < COBJ_MAX; i++)
|
||||
state->protoObjects[i] = NULL;
|
||||
@ -42,14 +61,17 @@ CState *cosmoV_newState() {
|
||||
state->iStrings[i] = NULL;
|
||||
|
||||
cosmoT_initTable(state, &state->strings, 16); // init string table
|
||||
cosmoT_initTable(state, &state->registry, 16);
|
||||
|
||||
state->globals = cosmoO_newTable(state); // init global table
|
||||
|
||||
// setup all strings used by the VM
|
||||
state->iStrings[ISTRING_INIT] = cosmoO_copyString(state, "__init", 6);
|
||||
state->iStrings[ISTRING_GC] = cosmoO_copyString(state, "__gc", 4);
|
||||
state->iStrings[ISTRING_TOSTRING] = cosmoO_copyString(state, "__tostring", 10);
|
||||
state->iStrings[ISTRING_TONUMBER] = cosmoO_copyString(state, "__tonumber", 10);
|
||||
state->iStrings[ISTRING_INDEX] = cosmoO_copyString(state, "__index", 7);
|
||||
state->iStrings[ISTRING_EQUAL] = cosmoO_copyString(state, "__equal", 7);
|
||||
state->iStrings[ISTRING_NEWINDEX] = cosmoO_copyString(state, "__newindex", 10);
|
||||
state->iStrings[ISTRING_COUNT] = cosmoO_copyString(state, "__count", 7);
|
||||
|
||||
@ -72,16 +94,22 @@ CState *cosmoV_newState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
void cosmoV_freeState(CState *state) {
|
||||
void cosmoV_freeState(CState *state)
|
||||
{
|
||||
#ifdef GC_DEBUG
|
||||
printf("state %p is being free'd!\n", state);
|
||||
#endif
|
||||
cosmoM_freezeGC(state);
|
||||
|
||||
// frees all the objects
|
||||
CObj *objs = state->objects;
|
||||
while (objs != NULL) {
|
||||
CObj *next = objs->next;
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
printf("STATE FREEING %p\n", objs);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
cosmoO_free(state, objs);
|
||||
objs = next;
|
||||
}
|
||||
@ -92,34 +120,79 @@ void cosmoV_freeState(CState *state) {
|
||||
|
||||
// free our string table (the string table includes the internal VM strings)
|
||||
cosmoT_clearTable(state, &state->strings);
|
||||
|
||||
// free our gray stack & finally free the state structure
|
||||
cosmoM_freearray(state, CObj*, state->grayStack.array, state->grayStack.capacity);
|
||||
|
||||
// TODO: yeah idk, it looks like im missing 520 bytes somewhere? i'll look into it later
|
||||
/*#ifdef GC_DEBUG
|
||||
if (state->allocatedBytes != sizeof(CState)) {
|
||||
printf("state->allocatedBytes doesn't match expected value (%lu), got %lu!", sizeof(CState), state->allocatedBytes);
|
||||
exit(0);
|
||||
cosmoT_clearTable(state, &state->registry);
|
||||
|
||||
// free our gray stack & finally free the state structure
|
||||
cosmoM_freeArray(state, CObj *, state->grayStack.array, state->grayStack.capacity);
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
if (state->allocatedBytes != 0) {
|
||||
printf("state->allocatedBytes doesn't match, got %lu\n", state->allocatedBytes);
|
||||
}
|
||||
#endif*/
|
||||
#endif
|
||||
|
||||
free(state);
|
||||
}
|
||||
|
||||
// expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value
|
||||
void cosmoV_register(CState *state, int pairs) {
|
||||
void cosmoV_addGlobals(CState *state, int pairs)
|
||||
{
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
StkPtr key = cosmoV_getTop(state, 1);
|
||||
StkPtr val = cosmoV_getTop(state, 0);
|
||||
|
||||
CValue *oldVal = cosmoT_insert(state, &state->globals->tbl, *key);
|
||||
*oldVal = *val;
|
||||
|
||||
|
||||
cosmoV_setTop(state, 2); // pops the 2 values off the stack
|
||||
}
|
||||
}
|
||||
|
||||
void cosmoV_printStack(CState *state) {
|
||||
// expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value
|
||||
void cosmoV_addRegistry(CState *state, int pairs)
|
||||
{
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
StkPtr key = cosmoV_getTop(state, 1);
|
||||
StkPtr val = cosmoV_getTop(state, 0);
|
||||
|
||||
CValue *oldVal = cosmoT_insert(state, &state->registry, *key);
|
||||
*oldVal = *val;
|
||||
|
||||
cosmoV_setTop(state, 2); // pops the 2 values off the stack
|
||||
}
|
||||
}
|
||||
|
||||
// expects 1 key on the stack, pushes result
|
||||
void cosmoV_getRegistry(CState *state) {
|
||||
CValue key = *cosmoV_pop(state);
|
||||
CValue val;
|
||||
|
||||
if (!cosmoT_get(state, &state->registry, key, &val)) {
|
||||
cosmoV_error(state, "failed to grab %s from registry", cosmoV_typeStr(key));
|
||||
}
|
||||
|
||||
cosmoV_pushValue(state, val);
|
||||
}
|
||||
|
||||
void cosmoV_setProto(CState *state) {
|
||||
StkPtr objVal = cosmoV_getTop(state, 1);
|
||||
StkPtr protoVal = cosmoV_getTop(state, 0);
|
||||
|
||||
if (!IS_REF(*objVal) || !IS_OBJECT(*protoVal)) {
|
||||
cosmoV_error(state, "cannot set %s to proto of type %s", cosmoV_typeStr(*objVal), cosmoV_typeStr(*protoVal));
|
||||
}
|
||||
|
||||
// actually set the protos
|
||||
CObj *obj = cosmoV_readRef(*objVal);
|
||||
CObjObject *proto = cosmoV_readObject(*protoVal);
|
||||
obj->proto = proto;
|
||||
|
||||
cosmoV_setTop(state, 2);
|
||||
}
|
||||
|
||||
void cosmoV_printStack(CState *state)
|
||||
{
|
||||
printf("==== [[ stack dump ]] ====\n");
|
||||
for (CValue *top = state->top - 1; top >= state->stack; top--) {
|
||||
printf("%d: ", (int)(top - state->stack));
|
||||
|
105
src/cstate.h
105
src/cstate.h
@ -1,65 +1,96 @@
|
||||
#ifndef CSTATE_H
|
||||
#define CSTATE_H
|
||||
|
||||
#include "cosmo.h"
|
||||
#include "cobj.h"
|
||||
#include "cvalue.h"
|
||||
#include "cosmo.h"
|
||||
#include "ctable.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
typedef struct CCallFrame {
|
||||
#include <setjmp.h>
|
||||
|
||||
struct CCallFrame
|
||||
{
|
||||
CObjClosure *closure;
|
||||
INSTRUCTION *pc;
|
||||
CValue* base;
|
||||
} CCallFrame;
|
||||
CValue *base;
|
||||
};
|
||||
|
||||
typedef enum IStringEnum {
|
||||
ISTRING_INIT, // __init
|
||||
ISTRING_TOSTRING, // __tostring
|
||||
ISTRING_TONUMBER, // __tonumber
|
||||
ISTRING_INDEX, // __index
|
||||
ISTRING_NEWINDEX, // __newindex
|
||||
ISTRING_COUNT, // __count
|
||||
ISTRING_GETTER, // __getter
|
||||
ISTRING_SETTER, // __setter
|
||||
ISTRING_ITER, // __iter
|
||||
ISTRING_NEXT, // __next
|
||||
ISTRING_RESERVED, // __reserved
|
||||
ISTRING_MAX
|
||||
typedef enum IStringEnum
|
||||
{
|
||||
ISTRING_INIT, // __init
|
||||
ISTRING_GC, // __gc
|
||||
ISTRING_TOSTRING, // __tostring
|
||||
ISTRING_TONUMBER, // __tonumber
|
||||
ISTRING_EQUAL, // __equals
|
||||
ISTRING_INDEX, // __index
|
||||
ISTRING_NEWINDEX, // __newindex
|
||||
ISTRING_COUNT, // __count
|
||||
ISTRING_GETTER, // __getter
|
||||
ISTRING_SETTER, // __setter
|
||||
ISTRING_ITER, // __iter
|
||||
ISTRING_NEXT, // __next
|
||||
ISTRING_RESERVED, // __reserved
|
||||
ISTRING_MAX // if this becomes greater than 33, we are out of space in cosmo_Flag. you'll have
|
||||
// to change that to uint64_t
|
||||
} IStringEnum;
|
||||
|
||||
typedef struct ArrayCObj {
|
||||
typedef struct ArrayCObj
|
||||
{
|
||||
CObj **array;
|
||||
int count;
|
||||
int capacity;
|
||||
} ArrayCObj;
|
||||
|
||||
typedef struct CState {
|
||||
bool panic;
|
||||
int freezeGC; // when > 0, GC events will be ignored (for internal use)
|
||||
typedef struct CPanic
|
||||
{
|
||||
jmp_buf jmp;
|
||||
StkPtr top;
|
||||
struct CPanic *prev;
|
||||
int frameCount;
|
||||
int freezeGC;
|
||||
} CPanic;
|
||||
|
||||
CObjError *error; // NULL, unless panic is true
|
||||
CObj *objects; // tracks all of our allocated objects
|
||||
CObj *userRoots; // user definable roots, this holds CObjs that should be considered "roots", lets the VM know you are holding a reference to a CObj in your code
|
||||
ArrayCObj grayStack; // keeps track of which objects *haven't yet* been traversed in our GC, but *have been* found
|
||||
size_t allocatedBytes;
|
||||
size_t nextGC; // when allocatedBytes reaches this threshhold, trigger a GC event
|
||||
struct CState
|
||||
{
|
||||
CCallFrame callFrame[FRAME_MAX]; // call frames
|
||||
CValue stack[STACK_MAX]; // stack
|
||||
CObjObject *protoObjects[COBJ_MAX]; // proto object for each COBJ type [NULL = no default proto]
|
||||
CObjString *iStrings[ISTRING_MAX]; // strings used internally by the VM, eg. __init, __index
|
||||
CTable strings;
|
||||
CTable registry;
|
||||
ArrayCObj grayStack; // keeps track of which objects *haven't yet* been traversed in our GC, but
|
||||
// *have been* found
|
||||
|
||||
CObjUpval *openUpvalues; // tracks all of our still open (meaning still on the stack) upvalues
|
||||
CTable strings;
|
||||
CObjTable *globals;
|
||||
CValue *top; // top of the stack
|
||||
CObj *objects; // tracks all of our allocated objects
|
||||
CPanic *panic;
|
||||
|
||||
CValue *top; // top of the stack
|
||||
CObjObject *protoObjects[COBJ_MAX]; // proto object for each COBJ type [NULL = no default proto]
|
||||
CObjString *iStrings[ISTRING_MAX]; // strings used internally by the VM, eg. __init, __index & friends
|
||||
CCallFrame callFrame[FRAME_MAX]; // call frames
|
||||
CValue stack[STACK_MAX]; // stack
|
||||
} CState;
|
||||
size_t allocatedBytes;
|
||||
size_t nextGC; // when allocatedBytes reaches this threshhold, trigger a GC event
|
||||
int freezeGC; // when > 0, GC events will be ignored (for internal use)
|
||||
int frameCount;
|
||||
};
|
||||
|
||||
CPanic *cosmoV_newPanic(CState *state);
|
||||
void cosmoV_freePanic(CState *state);
|
||||
|
||||
COSMO_API CState *cosmoV_newState();
|
||||
// expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value
|
||||
COSMO_API void cosmoV_register(CState *state, int pairs);
|
||||
COSMO_API void cosmoV_freeState(CState *state);
|
||||
|
||||
// expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value
|
||||
COSMO_API void cosmoV_addGlobals(CState *state, int pairs);
|
||||
|
||||
// expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value
|
||||
COSMO_API void cosmoV_addRegistry(CState *state, int pairs);
|
||||
|
||||
// expects 1 key on the stack, pushes result
|
||||
COSMO_API void cosmoV_getRegistry(CState *state);
|
||||
|
||||
// expects <object>->proto = <object> (2 total) to be on the stack
|
||||
COSMO_API void cosmoV_setProto(CState *state);
|
||||
|
||||
COSMO_API void cosmoV_printStack(CState *state);
|
||||
|
||||
#endif
|
||||
|
171
src/ctable.c
171
src/ctable.c
@ -1,21 +1,25 @@
|
||||
#include "ctable.h"
|
||||
|
||||
#include "cmem.h"
|
||||
#include "cvalue.h"
|
||||
#include "cobj.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_TABLE_FILL 0.75
|
||||
#define MAX_TABLE_FILL 0.75
|
||||
// at 30% capacity with capacity > ARRAY_START, shrink the array
|
||||
#define MIN_TABLE_CAPACITY ARRAY_START
|
||||
|
||||
// bit-twiddling hacks, gets the next power of 2
|
||||
unsigned int nextPow2(unsigned int x) {
|
||||
if (x <= ARRAY_START - 1) return ARRAY_START; // sanity check
|
||||
static unsigned int nextPow2(unsigned int x)
|
||||
{
|
||||
if (x <= ARRAY_START - 1)
|
||||
return ARRAY_START; // sanity check
|
||||
x--;
|
||||
|
||||
int power = 2;
|
||||
while (x >>= 1) power <<= 1;
|
||||
while (x >>= 1)
|
||||
power <<= 1;
|
||||
|
||||
if (power < ARRAY_START)
|
||||
return ARRAY_START;
|
||||
@ -23,12 +27,14 @@ unsigned int nextPow2(unsigned int x) {
|
||||
return power;
|
||||
}
|
||||
|
||||
void cosmoT_initTable(CState *state, CTable *tbl, int startCap) {
|
||||
void cosmoT_initTable(CState *state, CTable *tbl, int startCap)
|
||||
{
|
||||
startCap = startCap != 0 ? startCap : ARRAY_START; // sanity check :P
|
||||
|
||||
tbl->capacityMask = startCap - 1;
|
||||
tbl->count = 0;
|
||||
tbl->tombstones = 0;
|
||||
tbl->tombThreshold = 32;
|
||||
tbl->table = NULL; // to let out GC know we're initalizing
|
||||
tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * startCap);
|
||||
|
||||
@ -39,56 +45,64 @@ void cosmoT_initTable(CState *state, CTable *tbl, int startCap) {
|
||||
}
|
||||
}
|
||||
|
||||
void cosmoT_addTable(CState *state, CTable *from, CTable *to) {
|
||||
int cap = from->capacityMask + 1;
|
||||
void cosmoT_addTable(CState *state, CTable *from, CTable *to)
|
||||
{
|
||||
CTableEntry *entry;
|
||||
int cap = cosmoT_getCapacity(from);
|
||||
|
||||
for (int i = 0; i < cap; i++) {
|
||||
CTableEntry *entry = &from->table[i];
|
||||
entry = &from->table[i];
|
||||
|
||||
if (!(IS_NIL(entry->key))) {
|
||||
CValue *newVal = cosmoT_insert(state, to, entry->key);
|
||||
*newVal = entry->val;
|
||||
*cosmoT_insert(state, to, entry->key) = entry->val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cosmoT_clearTable(CState *state, CTable *tbl) {
|
||||
cosmoM_freearray(state, CTableEntry, tbl->table, (tbl->capacityMask + 1));
|
||||
void cosmoT_clearTable(CState *state, CTable *tbl)
|
||||
{
|
||||
cosmoM_freeArray(state, CTableEntry, tbl->table, cosmoT_getCapacity(tbl));
|
||||
}
|
||||
|
||||
uint32_t getObjectHash(CObj *obj) {
|
||||
switch(obj->type) {
|
||||
case COBJ_STRING:
|
||||
return ((CObjString*)obj)->hash;
|
||||
default:
|
||||
return (uint32_t)obj; // just "hash" the pointer
|
||||
static uint32_t getObjectHash(CObj *obj)
|
||||
{
|
||||
switch (obj->type) {
|
||||
case COBJ_STRING:
|
||||
return ((CObjString *)obj)->hash;
|
||||
default:
|
||||
return (uint32_t)obj; // just "hash" the pointer
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getValueHash(CValue *val) {
|
||||
static uint32_t getValueHash(CValue *val)
|
||||
{
|
||||
switch (GET_TYPE(*val)) {
|
||||
case COSMO_TREF:
|
||||
return getObjectHash(cosmoV_readRef(*val));
|
||||
case COSMO_TNUMBER: {
|
||||
uint32_t buf[sizeof(cosmo_Number)/sizeof(uint32_t)];
|
||||
cosmo_Number num = cosmoV_readNumber(*val);
|
||||
case COSMO_TREF:
|
||||
return getObjectHash(cosmoV_readRef(*val));
|
||||
case COSMO_TNUMBER: {
|
||||
uint32_t buf[sizeof(cosmo_Number) / sizeof(uint32_t)];
|
||||
cosmo_Number num = cosmoV_readNumber(*val);
|
||||
|
||||
if (num == 0)
|
||||
return 0;
|
||||
|
||||
memcpy(buf, &num, sizeof(buf));
|
||||
for (size_t i = 0; i < sizeof(cosmo_Number)/sizeof(uint32_t); i++) buf[0] += buf[i];
|
||||
return buf[0];
|
||||
}
|
||||
// TODO: add support for other types
|
||||
default:
|
||||
if (num == 0)
|
||||
return 0;
|
||||
|
||||
memcpy(buf, &num, sizeof(buf));
|
||||
for (size_t i = 0; i < sizeof(cosmo_Number) / sizeof(uint32_t); i++)
|
||||
buf[0] += buf[i];
|
||||
return buf[0];
|
||||
}
|
||||
// TODO: add support for other types
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mask should always be (capacity - 1)
|
||||
static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) {
|
||||
static CTableEntry *findEntry(CState *state, CTableEntry *entries, int mask, CValue key)
|
||||
{
|
||||
uint32_t hash = getValueHash(&key);
|
||||
uint32_t indx = hash & mask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
|
||||
uint32_t indx = hash & mask; // since we know the capacity will *always* be a power of 2, we can
|
||||
// use bitwise & to perform a MUCH faster mod operation
|
||||
CTableEntry *tomb = NULL;
|
||||
|
||||
// keep looking for an open slot in the entries array
|
||||
@ -100,11 +114,11 @@ static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) {
|
||||
if (IS_NIL(entry->val)) {
|
||||
// it's empty! if we found a tombstone, return that so it'll be reused
|
||||
return tomb != NULL ? tomb : entry;
|
||||
} else {
|
||||
// its a tombstone!
|
||||
tomb = entry;
|
||||
}
|
||||
} else if (cosmoV_equal(entry->key, key)) {
|
||||
|
||||
// its a tombstone!
|
||||
tomb = entry;
|
||||
} else if (cosmoV_equal(state, entry->key, key)) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
@ -112,21 +126,23 @@ static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) {
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrink) {
|
||||
static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrink)
|
||||
{
|
||||
if (canShrink && cosmoT_checkShrink(state, tbl))
|
||||
return;
|
||||
|
||||
|
||||
size_t size = sizeof(CTableEntry) * newCapacity;
|
||||
int cachedCount = tbl->count;
|
||||
int newCount, oldCap;
|
||||
|
||||
cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC
|
||||
|
||||
if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it, ignore our resize event!
|
||||
if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it,
|
||||
// ignore our resize event!
|
||||
return;
|
||||
|
||||
CTableEntry *entries = cosmoM_xmalloc(state, size);
|
||||
oldCap = tbl->capacityMask + 1;
|
||||
oldCap = cosmoT_getCapacity(tbl);
|
||||
newCount = 0;
|
||||
|
||||
// set all nodes as NIL : NIL
|
||||
@ -142,14 +158,14 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
|
||||
continue; // skip empty keys
|
||||
|
||||
// get new entry location & update the node
|
||||
CTableEntry *newEntry = findEntry(entries, newCapacity - 1, oldEntry->key);
|
||||
CTableEntry *newEntry = findEntry(state, entries, newCapacity - 1, oldEntry->key);
|
||||
newEntry->key = oldEntry->key;
|
||||
newEntry->val = oldEntry->val;
|
||||
newCount++; // inc count
|
||||
}
|
||||
|
||||
// free the old table
|
||||
cosmoM_freearray(state, CTableEntry, tbl->table, oldCap);
|
||||
cosmoM_freeArray(state, CTableEntry, tbl->table, oldCap);
|
||||
|
||||
tbl->table = entries;
|
||||
tbl->capacityMask = newCapacity - 1;
|
||||
@ -157,10 +173,14 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
|
||||
tbl->tombstones = 0;
|
||||
}
|
||||
|
||||
bool cosmoT_checkShrink(CState *state, CTable *tbl) {
|
||||
// if count > 8 and active entries < tombstones
|
||||
if (tbl->count > MIN_TABLE_CAPACITY && (tbl->count - tbl->tombstones < tbl->tombstones || tbl->tombstones > 50)) { // TODO: 50 should be a threshhold
|
||||
resizeTbl(state, tbl, nextPow2(tbl->count - tbl->tombstones) * GROW_FACTOR, false); // shrink based on active entries to the next pow of 2
|
||||
bool cosmoT_checkShrink(CState *state, CTable *tbl)
|
||||
{
|
||||
// if count > 8 and active entries < tombstones
|
||||
if (tbl->count > MIN_TABLE_CAPACITY &&
|
||||
(tbl->count - tbl->tombstones < tbl->tombstones || tbl->tombstones > tbl->tombThreshold)) {
|
||||
// shrink based on active entries to the next pow of 2
|
||||
resizeTbl(state, tbl, nextPow2(tbl->count - tbl->tombstones) * GROW_FACTOR, false);
|
||||
tbl->tombThreshold = tbl->count / 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -168,9 +188,10 @@ bool cosmoT_checkShrink(CState *state, CTable *tbl) {
|
||||
}
|
||||
|
||||
// returns a pointer to the allocated value
|
||||
COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
|
||||
COSMO_API CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key)
|
||||
{
|
||||
// make sure we have enough space allocated
|
||||
int cap = tbl->capacityMask + 1;
|
||||
int cap = cosmoT_getCapacity(tbl);
|
||||
if (tbl->count + 1 > (int)(cap * MAX_TABLE_FILL)) {
|
||||
// grow table
|
||||
int newCap = cap * GROW_FACTOR;
|
||||
@ -178,7 +199,7 @@ COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
|
||||
}
|
||||
|
||||
// insert into the table
|
||||
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key); // -1 for our capacity mask
|
||||
CTableEntry *entry = findEntry(state, tbl->table, tbl->capacityMask, key);
|
||||
|
||||
if (IS_NIL(entry->key)) {
|
||||
if (IS_NIL(entry->val)) // is it empty?
|
||||
@ -191,43 +212,53 @@ COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
|
||||
return &entry->val;
|
||||
}
|
||||
|
||||
bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
|
||||
bool cosmoT_get(CState *state, CTable *tbl, CValue key, CValue *val)
|
||||
{
|
||||
// sanity check
|
||||
if (tbl->count == 0) {
|
||||
*val = cosmoV_newNil();
|
||||
return false;
|
||||
}
|
||||
|
||||
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key);
|
||||
|
||||
CTableEntry *entry = findEntry(state, tbl->table, tbl->capacityMask, key);
|
||||
*val = entry->val;
|
||||
|
||||
|
||||
// return if get was successful
|
||||
return !(IS_NIL(entry->key));
|
||||
}
|
||||
|
||||
bool cosmoT_remove(CState* state, CTable *tbl, CValue key) {
|
||||
if (tbl->count == 0) return 0; // sanity check
|
||||
bool cosmoT_remove(CState *state, CTable *tbl, CValue key)
|
||||
{
|
||||
if (tbl->count == 0)
|
||||
return 0; // sanity check
|
||||
|
||||
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key);
|
||||
CTableEntry *entry = findEntry(state, tbl->table, tbl->capacityMask, key);
|
||||
if (IS_NIL(entry->key)) // sanity check
|
||||
return false;
|
||||
|
||||
// crafts tombstone
|
||||
entry->key = cosmoV_newNil(); // this has to be nil
|
||||
entry->val = cosmoV_newBoolean(false); // doesn't really matter what this is, as long as it isn't nil
|
||||
entry->val =
|
||||
cosmoV_newBoolean(false); // doesn't really matter what this is, as long as it isn't nil
|
||||
tbl->tombstones++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns the active entry count
|
||||
COSMO_API int cosmoT_count(CTable *tbl) {
|
||||
COSMO_API int cosmoT_count(CTable *tbl)
|
||||
{
|
||||
return tbl->count - tbl->tombstones;
|
||||
}
|
||||
|
||||
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash) {
|
||||
if (tbl->count == 0) return 0; // sanity check
|
||||
uint32_t indx = hash & tbl->capacityMask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
|
||||
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash)
|
||||
{
|
||||
if (tbl->count == 0)
|
||||
return 0; // sanity check
|
||||
|
||||
// since we know the capacity will *always* be a power of 2, we
|
||||
// can use bitwise & to perform a MUCH faster mod operation
|
||||
uint32_t indx = hash & tbl->capacityMask;
|
||||
|
||||
// keep looking for an open slot in the entries array
|
||||
while (true) {
|
||||
@ -236,9 +267,10 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32
|
||||
// check if it's an empty slot (meaning we dont have it in the table)
|
||||
if (IS_NIL(entry->key) && IS_NIL(entry->val)) {
|
||||
return NULL;
|
||||
} else if (IS_STRING(entry->key) && cosmoV_readString(entry->key)->length == length && memcmp(cosmoV_readString(entry->key)->str, str, length) == 0) {
|
||||
} else if (IS_STRING(entry->key) && cosmoV_readString(entry->key)->length == length &&
|
||||
memcmp(cosmoV_readString(entry->key)->str, str, length) == 0) {
|
||||
// it's a match!
|
||||
return (CObjString*)cosmoV_readRef(entry->key);
|
||||
return (CObjString *)cosmoV_readRef(entry->key);
|
||||
}
|
||||
|
||||
indx = (indx + 1) & tbl->capacityMask; // fast mod here too
|
||||
@ -246,9 +278,10 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32
|
||||
}
|
||||
|
||||
// for debugging purposes
|
||||
void cosmoT_printTable(CTable *tbl, const char *name) {
|
||||
void cosmoT_printTable(CTable *tbl, const char *name)
|
||||
{
|
||||
printf("==== [[%s]] ====\n", name);
|
||||
int cap = tbl->capacityMask + 1;
|
||||
int cap = cosmoT_getCapacity(tbl);
|
||||
for (int i = 0; i < cap; i++) {
|
||||
CTableEntry *entry = &tbl->table[i];
|
||||
if (!(IS_NIL(entry->key))) {
|
||||
|
14
src/ctable.h
14
src/ctable.h
@ -1,23 +1,29 @@
|
||||
#ifndef CTABLE_H
|
||||
#define CTABLE_H
|
||||
|
||||
/* TODO: rewrite this table implementation. compared to other languages (including python!) this table is verrryyyy slow */
|
||||
/* TODO: rewrite this table implementation. compared to other languages (including python!) this
|
||||
* table is verrryyyy slow */
|
||||
|
||||
#include "cosmo.h"
|
||||
#include "cvalue.h"
|
||||
|
||||
typedef struct CTableEntry {
|
||||
typedef struct CTableEntry
|
||||
{
|
||||
CValue key;
|
||||
CValue val;
|
||||
} CTableEntry;
|
||||
|
||||
typedef struct CTable {
|
||||
typedef struct CTable
|
||||
{
|
||||
int count;
|
||||
int capacityMask; // +1 to get the capacity
|
||||
int tombstones;
|
||||
int tombThreshold;
|
||||
CTableEntry *table;
|
||||
} CTable;
|
||||
|
||||
#define cosmoT_getCapacity(tbl) ((tbl)->capacityMask + 1)
|
||||
|
||||
COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap);
|
||||
COSMO_API void cosmoT_clearTable(CState *state, CTable *tbl);
|
||||
COSMO_API int cosmoT_count(CTable *tbl);
|
||||
@ -26,7 +32,7 @@ bool cosmoT_checkShrink(CState *state, CTable *tbl);
|
||||
|
||||
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash);
|
||||
CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key);
|
||||
bool cosmoT_get(CTable *tbl, CValue key, CValue *val);
|
||||
bool cosmoT_get(CState *state, CTable *tbl, CValue key, CValue *val);
|
||||
bool cosmoT_remove(CState *state, CTable *tbl, CValue key);
|
||||
|
||||
void cosmoT_printTable(CTable *tbl, const char *name);
|
||||
|
219
src/cundump.c
Normal file
219
src/cundump.c
Normal file
@ -0,0 +1,219 @@
|
||||
#include "cundump.h"
|
||||
|
||||
#include "cchunk.h"
|
||||
#include "cdump.h"
|
||||
#include "cmem.h"
|
||||
#include "cvm.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CState *state;
|
||||
const void *userData;
|
||||
cosmo_Reader reader;
|
||||
int readerStatus;
|
||||
} UndumpState;
|
||||
|
||||
static bool readCValue(UndumpState *udstate, CValue *val);
|
||||
|
||||
#define check(e) \
|
||||
if (!e) { \
|
||||
return false; \
|
||||
}
|
||||
|
||||
static void initUndumpState(CState *state, UndumpState *udstate, cosmo_Reader reader,
|
||||
const void *userData)
|
||||
{
|
||||
udstate->state = state;
|
||||
udstate->userData = userData;
|
||||
udstate->reader = reader;
|
||||
udstate->readerStatus = 0;
|
||||
}
|
||||
|
||||
static bool readBlock(UndumpState *udstate, void *data, size_t size)
|
||||
{
|
||||
if (udstate->readerStatus == 0) {
|
||||
/* if reader returns 1, we expect an error was thrown */
|
||||
udstate->readerStatus = udstate->reader(udstate->state, data, size, udstate->userData);
|
||||
}
|
||||
|
||||
return udstate->readerStatus == 0;
|
||||
}
|
||||
|
||||
static bool readu8(UndumpState *udstate, uint8_t *d)
|
||||
{
|
||||
return readBlock(udstate, d, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
static bool readu32(UndumpState *udstate, uint32_t *d)
|
||||
{
|
||||
return readBlock(udstate, d, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static bool readSize(UndumpState *udstate, size_t *d)
|
||||
{
|
||||
return readBlock(udstate, d, sizeof(size_t));
|
||||
}
|
||||
|
||||
static bool readVector(UndumpState *udstate, void **data, size_t size, size_t *count)
|
||||
{
|
||||
check(readSize(udstate, count));
|
||||
*data = cosmoM_xmalloc(udstate->state, (*count) * size);
|
||||
return readBlock(udstate, *data, (*count) * size);
|
||||
}
|
||||
|
||||
#define checku8(udstate, d, tmp) \
|
||||
check(readu8(udstate, &tmp)); \
|
||||
if (d != tmp) { \
|
||||
cosmoV_error(udstate->state, "bad header!"); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
static bool checkHeader(UndumpState *udstate)
|
||||
{
|
||||
char magic[COSMO_MAGIC_LEN];
|
||||
uint8_t tmp;
|
||||
|
||||
/* check header */
|
||||
check(readBlock(udstate, magic, COSMO_MAGIC_LEN));
|
||||
if (memcmp(magic, COSMO_MAGIC, COSMO_MAGIC_LEN) != 0) {
|
||||
cosmoV_error(udstate->state, "bad header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* after the magic, we read some platform information */
|
||||
checku8(udstate, cosmoD_isBigEndian(), tmp);
|
||||
checku8(udstate, sizeof(cosmo_Number), tmp);
|
||||
checku8(udstate, sizeof(size_t), tmp);
|
||||
checku8(udstate, sizeof(int), tmp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef checku8
|
||||
|
||||
static bool readCObjString(UndumpState *udstate, CObjString **str)
|
||||
{
|
||||
uint32_t size;
|
||||
char *data;
|
||||
|
||||
check(readu32(udstate, (uint32_t *)&size));
|
||||
if (size == 0) { /* empty string */
|
||||
*str = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
data = cosmoM_xmalloc(udstate->state, size + 1);
|
||||
check(readBlock(udstate, (void *)data, size));
|
||||
data[size] = '\0'; /* add NULL-terminator */
|
||||
|
||||
*str = cosmoO_takeString(udstate->state, data, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readCObjFunction(UndumpState *udstate, CObjFunction **func)
|
||||
{
|
||||
size_t constants;
|
||||
CValue val;
|
||||
|
||||
*func = cosmoO_newFunction(udstate->state);
|
||||
|
||||
/* make sure our GC can see that we're currently using this function (and the values it uses) */
|
||||
cosmoV_pushRef(udstate->state, (CObj *)*func);
|
||||
|
||||
check(readCObjString(udstate, &(*func)->name));
|
||||
check(readCObjString(udstate, &(*func)->module));
|
||||
|
||||
check(readu32(udstate, (uint32_t *)&(*func)->args));
|
||||
check(readu32(udstate, (uint32_t *)&(*func)->upvals));
|
||||
check(readu8(udstate, (uint8_t *)&(*func)->variadic));
|
||||
|
||||
/* read chunk info */
|
||||
check(
|
||||
readVector(udstate, (void **)&(*func)->chunk.buf, sizeof(uint8_t), &(*func)->chunk.count));
|
||||
check(
|
||||
readVector(udstate, (void **)&(*func)->chunk.lineInfo, sizeof(int), &(*func)->chunk.count));
|
||||
|
||||
/* read constants */
|
||||
check(readSize(udstate, &constants));
|
||||
for (int i = 0; i < constants; i++) {
|
||||
check(readCValue(udstate, &val));
|
||||
addConstant(udstate->state, &(*func)->chunk, val);
|
||||
}
|
||||
|
||||
/* pop function off stack */
|
||||
cosmoV_pop(udstate->state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readCObj(UndumpState *udstate, CObj **obj)
|
||||
{
|
||||
uint8_t type;
|
||||
check(readu8(udstate, &type));
|
||||
|
||||
switch (type) {
|
||||
case COBJ_STRING:
|
||||
return readCObjString(udstate, (CObjString **)obj);
|
||||
case COBJ_FUNCTION:
|
||||
return readCObjFunction(udstate, (CObjFunction **)obj);
|
||||
default:
|
||||
cosmoV_error(udstate->state, "unknown object type!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define READ_VAR(udstate, val, type, creator) \
|
||||
{ \
|
||||
type _tmp; \
|
||||
check(readBlock(udstate, &_tmp, sizeof(type))); \
|
||||
*val = creator(_tmp); \
|
||||
break; \
|
||||
}
|
||||
|
||||
static bool readCValue(UndumpState *udstate, CValue *val)
|
||||
{
|
||||
uint8_t t;
|
||||
check(readu8(udstate, &t));
|
||||
|
||||
switch (t) {
|
||||
case COSMO_TNUMBER:
|
||||
READ_VAR(udstate, val, cosmo_Number, cosmoV_newNumber)
|
||||
case COSMO_TBOOLEAN:
|
||||
READ_VAR(udstate, val, bool, cosmoV_newBoolean)
|
||||
case COSMO_TREF: {
|
||||
CObj *obj;
|
||||
check(readCObj(udstate, (CObj **)&obj));
|
||||
*val = cosmoV_newRef(obj);
|
||||
break;
|
||||
}
|
||||
case COSMO_TNIL:
|
||||
*val = cosmoV_newNil();
|
||||
break;
|
||||
default:
|
||||
cosmoV_error(udstate->state, "invalid value type: %d", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef READ_VAR
|
||||
#undef check
|
||||
|
||||
int cosmoD_undump(CState *state, cosmo_Reader reader, const void *userData, CObjFunction **func)
|
||||
{
|
||||
UndumpState udstate;
|
||||
initUndumpState(state, &udstate, reader, userData);
|
||||
|
||||
if (!checkHeader(&udstate)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!readCObjFunction(&udstate, func)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
cosmoV_pushRef(state, (CObj *)*func);
|
||||
return udstate.readerStatus;
|
||||
}
|
12
src/cundump.h
Normal file
12
src/cundump.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef COSMO_UNDUMP_H
|
||||
#define COSMO_UNDUMP_H
|
||||
|
||||
#include "cobj.h"
|
||||
#include "cosmo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* returns non-zero on error */
|
||||
int cosmoD_undump(CState *state, cosmo_Reader reader, const void *userData, CObjFunction **func);
|
||||
|
||||
#endif
|
164
src/cvalue.c
164
src/cvalue.c
@ -1,105 +1,123 @@
|
||||
#include "cosmo.h"
|
||||
#include "cmem.h"
|
||||
#include "cvalue.h"
|
||||
#include "cobj.h"
|
||||
|
||||
void initValArray(CState *state, CValueArray *val, size_t startCapacity) {
|
||||
#include "cmem.h"
|
||||
#include "cobj.h"
|
||||
#include "cosmo.h"
|
||||
|
||||
void initValArray(CState *state, CValueArray *val, size_t startCapacity)
|
||||
{
|
||||
val->count = 0;
|
||||
val->capacity = startCapacity;
|
||||
val->values = NULL;
|
||||
}
|
||||
|
||||
void cleanValArray(CState *state, CValueArray *array) {
|
||||
cosmoM_freearray(state, CValue, array->values, array->capacity);
|
||||
void cleanValArray(CState *state, CValueArray *array)
|
||||
{
|
||||
cosmoM_freeArray(state, CValue, array->values, array->capacity);
|
||||
}
|
||||
|
||||
void appendValArray(CState *state, CValueArray *array, CValue val) {
|
||||
cosmoM_growarray(state, CValue, array->values, array->count, array->capacity);
|
||||
void appendValArray(CState *state, CValueArray *array, CValue val)
|
||||
{
|
||||
cosmoM_growArray(state, CValue, array->values, array->count, array->capacity);
|
||||
|
||||
array->values[array->count++] = val;
|
||||
}
|
||||
|
||||
bool cosmoV_equal(CValue valA, CValue valB) {
|
||||
bool cosmoV_equal(CState *state, CValue valA, CValue valB)
|
||||
{
|
||||
if (GET_TYPE(valA) != GET_TYPE(valB)) // are they the same type?
|
||||
return false;
|
||||
|
||||
// compare
|
||||
switch (GET_TYPE(valA)) {
|
||||
case COSMO_TBOOLEAN: return cosmoV_readBoolean(valA) == cosmoV_readBoolean(valB);
|
||||
case COSMO_TNUMBER: return cosmoV_readNumber(valA) == cosmoV_readNumber(valB);
|
||||
case COSMO_TREF: return cosmoO_equal(cosmoV_readRef(valA), cosmoV_readRef(valB));
|
||||
case COSMO_TNIL: return true;
|
||||
default:
|
||||
return false;
|
||||
case COSMO_TBOOLEAN:
|
||||
return cosmoV_readBoolean(valA) == cosmoV_readBoolean(valB);
|
||||
case COSMO_TNUMBER:
|
||||
return cosmoV_readNumber(valA) == cosmoV_readNumber(valB);
|
||||
case COSMO_TREF:
|
||||
return cosmoO_equal(state, cosmoV_readRef(valA), cosmoV_readRef(valB));
|
||||
case COSMO_TNIL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CObjString *cosmoV_toString(CState *state, CValue val) {
|
||||
CObjString *cosmoV_toString(CState *state, CValue val)
|
||||
{
|
||||
switch (GET_TYPE(val)) {
|
||||
case COSMO_TNUMBER: {
|
||||
char buf[32];
|
||||
int size = snprintf((char*)&buf, 32, "%.14g", cosmoV_readNumber(val));
|
||||
return cosmoO_copyString(state, (char*)&buf, size);
|
||||
}
|
||||
case COSMO_TBOOLEAN: {
|
||||
return cosmoV_readBoolean(val) ? cosmoO_copyString(state, "true", 4) : cosmoO_copyString(state, "false", 5);
|
||||
}
|
||||
case COSMO_TREF: {
|
||||
return cosmoO_toString(state, cosmoV_readRef(val));
|
||||
}
|
||||
case COSMO_TNIL: {
|
||||
return cosmoO_copyString(state, "nil", 3);
|
||||
}
|
||||
default:
|
||||
return cosmoO_copyString(state, "<unkn val>", 10);
|
||||
case COSMO_TNUMBER: {
|
||||
char buf[32];
|
||||
int size = snprintf((char *)&buf, 32, "%.14g", cosmoV_readNumber(val));
|
||||
return cosmoO_copyString(state, (char *)&buf, size);
|
||||
}
|
||||
case COSMO_TBOOLEAN: {
|
||||
return cosmoV_readBoolean(val) ? cosmoO_copyString(state, "true", 4)
|
||||
: cosmoO_copyString(state, "false", 5);
|
||||
}
|
||||
case COSMO_TREF: {
|
||||
return cosmoO_toString(state, cosmoV_readRef(val));
|
||||
}
|
||||
case COSMO_TNIL: {
|
||||
return cosmoO_copyString(state, "nil", 3);
|
||||
}
|
||||
default:
|
||||
return cosmoO_copyString(state, "<unkn val>", 10);
|
||||
}
|
||||
}
|
||||
|
||||
cosmo_Number cosmoV_toNumber(CState *state, CValue val) {
|
||||
switch(GET_TYPE(val)) {
|
||||
case COSMO_TNUMBER: {
|
||||
return cosmoV_readNumber(val);
|
||||
}
|
||||
case COSMO_TBOOLEAN: {
|
||||
return cosmoV_readBoolean(val) ? 1 : 0;
|
||||
}
|
||||
case COSMO_TREF: {
|
||||
return cosmoO_toNumber(state, cosmoV_readRef(val));
|
||||
}
|
||||
case COSMO_TNIL: // fall through
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cosmoV_typeStr(CValue val) {
|
||||
cosmo_Number cosmoV_toNumber(CState *state, CValue val)
|
||||
{
|
||||
switch (GET_TYPE(val)) {
|
||||
case COSMO_TNIL: return "<nil>";
|
||||
case COSMO_TBOOLEAN: return "<bool>";
|
||||
case COSMO_TNUMBER: return "<number>";
|
||||
case COSMO_TREF: return cosmoO_typeStr(cosmoV_readRef(val));
|
||||
|
||||
default:
|
||||
return "<unkn val>";
|
||||
case COSMO_TNUMBER: {
|
||||
return cosmoV_readNumber(val);
|
||||
}
|
||||
case COSMO_TBOOLEAN: {
|
||||
return cosmoV_readBoolean(val) ? 1 : 0;
|
||||
}
|
||||
case COSMO_TREF: {
|
||||
return cosmoO_toNumber(state, cosmoV_readRef(val));
|
||||
}
|
||||
case COSMO_TNIL: // fall through
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void printValue(CValue val) {
|
||||
const char *cosmoV_typeStr(CValue val)
|
||||
{
|
||||
switch (GET_TYPE(val)) {
|
||||
case COSMO_TNUMBER:
|
||||
printf("%g", cosmoV_readNumber(val));
|
||||
break;
|
||||
case COSMO_TBOOLEAN:
|
||||
printf(cosmoV_readBoolean(val) ? "true" : "false");
|
||||
break;
|
||||
case COSMO_TREF: {
|
||||
printObject(cosmoV_readRef(val));
|
||||
break;
|
||||
}
|
||||
case COSMO_TNIL:
|
||||
printf("nil");
|
||||
break;
|
||||
default:
|
||||
printf("<unkn val>");
|
||||
case COSMO_TNIL:
|
||||
return "<nil>";
|
||||
case COSMO_TBOOLEAN:
|
||||
return "<bool>";
|
||||
case COSMO_TNUMBER:
|
||||
return "<number>";
|
||||
case COSMO_TREF:
|
||||
return cosmoO_typeStr(cosmoV_readRef(val));
|
||||
|
||||
default:
|
||||
return "<unkn val>";
|
||||
}
|
||||
}
|
||||
|
||||
void printValue(CValue val)
|
||||
{
|
||||
switch (GET_TYPE(val)) {
|
||||
case COSMO_TNUMBER:
|
||||
printf("%g", cosmoV_readNumber(val));
|
||||
break;
|
||||
case COSMO_TBOOLEAN:
|
||||
printf(cosmoV_readBoolean(val) ? "true" : "false");
|
||||
break;
|
||||
case COSMO_TREF: {
|
||||
printObject(cosmoV_readRef(val));
|
||||
break;
|
||||
}
|
||||
case COSMO_TNIL:
|
||||
printf("nil");
|
||||
break;
|
||||
default:
|
||||
printf("<unkn val>");
|
||||
}
|
||||
}
|
||||
|
100
src/cvalue.h
100
src/cvalue.h
@ -3,7 +3,8 @@
|
||||
|
||||
#include "cosmo.h"
|
||||
|
||||
typedef enum {
|
||||
typedef enum
|
||||
{
|
||||
COSMO_TNUMBER, // number has to be 0 because NaN box
|
||||
COSMO_TBOOLEAN,
|
||||
COSMO_TREF,
|
||||
@ -18,8 +19,8 @@ typedef double cosmo_Number;
|
||||
|
||||
#ifdef NAN_BOXXED
|
||||
/*
|
||||
NaN box, this is great for fitting more in the cpu cache on x86_64 or ARM64 architectures. If you don't know how this works please reference these
|
||||
two articles:
|
||||
NaN box, this is great for fitting more in the cpu cache on x86_64 or ARM64 architectures.
|
||||
If you don't know how this works please reference these two articles:
|
||||
|
||||
https://leonardschuetz.ch/blog/nan-boxing/ and https://piotrduperas.com/posts/nan-boxing/
|
||||
|
||||
@ -27,96 +28,101 @@ typedef double cosmo_Number;
|
||||
|
||||
TL;DR: we can store payloads in the NaN value in the IEEE 754 standard.
|
||||
*/
|
||||
typedef union CValue {
|
||||
union CValue
|
||||
{
|
||||
uint64_t data;
|
||||
cosmo_Number num;
|
||||
} CValue;
|
||||
};
|
||||
|
||||
#define MASK_TYPE ((uint64_t)0x0007000000000000)
|
||||
#define MASK_PAYLOAD ((uint64_t)0x0000ffffffffffff)
|
||||
# define MASK_TYPE ((uint64_t)0x0007000000000000)
|
||||
# define MASK_PAYLOAD ((uint64_t)0x0000ffffffffffff)
|
||||
|
||||
// 3 bits (low bits) are reserved for the type
|
||||
#define MAKE_PAYLOAD(x) ((uint64_t)(x) & MASK_PAYLOAD)
|
||||
#define READ_PAYLOAD(x) ((x).data & MASK_PAYLOAD)
|
||||
# define MAKE_PAYLOAD(x) ((uint64_t)(x)&MASK_PAYLOAD)
|
||||
# define READ_PAYLOAD(x) ((x).data & MASK_PAYLOAD)
|
||||
|
||||
// The bits that must be set to indicate a quiet NaN.
|
||||
#define MASK_QUIETNAN ((uint64_t)0x7ff8000000000000)
|
||||
# define MASK_QUIETNAN ((uint64_t)0x7ff8000000000000)
|
||||
|
||||
#define GET_TYPE(x) \
|
||||
((((x).data & MASK_QUIETNAN) == MASK_QUIETNAN) ? (((x).data & MASK_TYPE) >> 48) : COSMO_TNUMBER)
|
||||
# define GET_TYPE(x) \
|
||||
((((x).data & MASK_QUIETNAN) == MASK_QUIETNAN) ? (((x).data & MASK_TYPE) >> 48) \
|
||||
: COSMO_TNUMBER)
|
||||
|
||||
#define SIG_MASK (MASK_QUIETNAN | MASK_TYPE)
|
||||
#define BOOL_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TBOOLEAN) << 48))
|
||||
#define OBJ_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TREF) << 48))
|
||||
#define NIL_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TNIL) << 48))
|
||||
# define SIG_MASK (MASK_QUIETNAN | MASK_TYPE)
|
||||
# define BOOL_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TBOOLEAN) << 48))
|
||||
# define OBJ_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TREF) << 48))
|
||||
# define NIL_SIG (MASK_QUIETNAN | ((uint64_t)(COSMO_TNIL) << 48))
|
||||
|
||||
#define cosmoV_newNumber(x) ((CValue){.num = x})
|
||||
#define cosmoV_newBoolean(x) ((CValue){.data = MAKE_PAYLOAD(x) | BOOL_SIG})
|
||||
#define cosmoV_newRef(x) ((CValue){.data = MAKE_PAYLOAD((uintptr_t)x) | OBJ_SIG})
|
||||
#define cosmoV_newNil() ((CValue){.data = NIL_SIG})
|
||||
# define cosmoV_newNumber(x) ((CValue){.num = x})
|
||||
# define cosmoV_newBoolean(x) ((CValue){.data = MAKE_PAYLOAD(x) | BOOL_SIG})
|
||||
# define cosmoV_newRef(x) ((CValue){.data = MAKE_PAYLOAD((uintptr_t)x) | OBJ_SIG})
|
||||
# define cosmoV_newNil() ((CValue){.data = NIL_SIG})
|
||||
|
||||
#define cosmoV_readNumber(x) ((x).num)
|
||||
#define cosmoV_readBoolean(x) ((bool)READ_PAYLOAD(x))
|
||||
#define cosmoV_readRef(x) ((CObj*)READ_PAYLOAD(x))
|
||||
# define cosmoV_readNumber(x) ((x).num)
|
||||
# define cosmoV_readBoolean(x) ((bool)READ_PAYLOAD(x))
|
||||
# define cosmoV_readRef(x) ((CObj *)READ_PAYLOAD(x))
|
||||
|
||||
#define IS_NUMBER(x) (((x).data & MASK_QUIETNAN) != MASK_QUIETNAN)
|
||||
#define IS_BOOLEAN(x) (((x).data & SIG_MASK) == BOOL_SIG)
|
||||
#define IS_NIL(x) (((x).data & SIG_MASK) == NIL_SIG)
|
||||
#define IS_REF(x) (((x).data & SIG_MASK) == OBJ_SIG)
|
||||
# define IS_NUMBER(x) (((x).data & MASK_QUIETNAN) != MASK_QUIETNAN)
|
||||
# define IS_BOOLEAN(x) (((x).data & SIG_MASK) == BOOL_SIG)
|
||||
# define IS_NIL(x) (((x).data & SIG_MASK) == NIL_SIG)
|
||||
# define IS_REF(x) (((x).data & SIG_MASK) == OBJ_SIG)
|
||||
|
||||
#else
|
||||
/*
|
||||
Tagged union, this is the best platform independent solution
|
||||
*/
|
||||
typedef struct CValue {
|
||||
struct CValue
|
||||
{
|
||||
CosmoType type;
|
||||
union {
|
||||
union
|
||||
{
|
||||
cosmo_Number num;
|
||||
bool b; // boolean
|
||||
CObj *obj;
|
||||
} val;
|
||||
} CValue;
|
||||
};
|
||||
|
||||
#define GET_TYPE(x) ((x).type)
|
||||
# define GET_TYPE(x) ((x).type)
|
||||
|
||||
// create CValues
|
||||
|
||||
#define cosmoV_newNumber(x) ((CValue){COSMO_TNUMBER, {.num = (x)}})
|
||||
#define cosmoV_newBoolean(x) ((CValue){COSMO_TBOOLEAN, {.b = (x)}})
|
||||
#define cosmoV_newRef(x) ((CValue){COSMO_TREF, {.obj = (CObj*)(x)}})
|
||||
#define cosmoV_newNil() ((CValue){COSMO_TNIL, {.num = 0}})
|
||||
# define cosmoV_newNumber(x) ((CValue){COSMO_TNUMBER, {.num = (x)}})
|
||||
# define cosmoV_newBoolean(x) ((CValue){COSMO_TBOOLEAN, {.b = (x)}})
|
||||
# define cosmoV_newRef(x) ((CValue){COSMO_TREF, {.obj = (CObj *)(x)}})
|
||||
# define cosmoV_newNil() ((CValue){COSMO_TNIL, {.num = 0}})
|
||||
|
||||
// read CValues
|
||||
|
||||
#define cosmoV_readNumber(x) ((cosmo_Number)(x).val.num)
|
||||
#define cosmoV_readBoolean(x) ((bool)(x).val.b)
|
||||
# define cosmoV_readNumber(x) ((cosmo_Number)(x).val.num)
|
||||
# define cosmoV_readBoolean(x) ((bool)(x).val.b)
|
||||
|
||||
// grabs the CObj* pointer from the CValue
|
||||
#define cosmoV_readRef(x) ((CObj*)(x).val.obj)
|
||||
# define cosmoV_readRef(x) ((CObj *)(x).val.obj)
|
||||
|
||||
#define IS_NUMBER(x) (GET_TYPE(x) == COSMO_TNUMBER)
|
||||
#define IS_BOOLEAN(x) (GET_TYPE(x) == COSMO_TBOOLEAN)
|
||||
#define IS_NIL(x) (GET_TYPE(x) == COSMO_TNIL)
|
||||
#define IS_REF(x) (GET_TYPE(x) == COSMO_TREF)
|
||||
# define IS_NUMBER(x) (GET_TYPE(x) == COSMO_TNUMBER)
|
||||
# define IS_BOOLEAN(x) (GET_TYPE(x) == COSMO_TBOOLEAN)
|
||||
# define IS_NIL(x) (GET_TYPE(x) == COSMO_TNIL)
|
||||
# define IS_REF(x) (GET_TYPE(x) == COSMO_TREF)
|
||||
|
||||
#endif
|
||||
|
||||
typedef CValue* StkPtr;
|
||||
typedef CValue *StkPtr;
|
||||
|
||||
typedef struct CValueArray {
|
||||
struct CValueArray
|
||||
{
|
||||
size_t capacity;
|
||||
size_t count;
|
||||
CValue *values;
|
||||
} CValueArray;
|
||||
};
|
||||
|
||||
void initValArray(CState *state, CValueArray *val, size_t startCapacity);
|
||||
void cleanValArray(CState *state, CValueArray *array); // cleans array
|
||||
void appendValArray(CState *state, CValueArray *array, CValue val);
|
||||
|
||||
void printValue(CValue val);
|
||||
COSMO_API bool cosmoV_equal(CValue valA, CValue valB);
|
||||
COSMO_API bool cosmoV_equal(CState *state, CValue valA, CValue valB);
|
||||
COSMO_API CObjString *cosmoV_toString(CState *state, CValue val);
|
||||
COSMO_API cosmo_Number cosmoV_toNumber(CState *state, CValue val);
|
||||
COSMO_API const char *cosmoV_typeStr(CValue val); // return constant char array for corresponding type
|
||||
COSMO_API const char *cosmoV_typeStr(CValue val);
|
||||
|
||||
#endif
|
||||
|
124
src/cvm.h
124
src/cvm.h
@ -1,38 +1,54 @@
|
||||
#ifndef COSMOVM_H
|
||||
#define COSMOVM_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "cosmo.h"
|
||||
#include "cstate.h"
|
||||
|
||||
//#define VM_DEBUG
|
||||
#include <string.h>
|
||||
|
||||
typedef enum {
|
||||
COSMOVM_OK,
|
||||
COSMOVM_RUNTIME_ERR,
|
||||
COSMOVM_BUILDTIME_ERR
|
||||
} COSMOVMRESULT;
|
||||
// #define VM_DEBUG
|
||||
|
||||
/*
|
||||
if we're using GNUC or clang, we can use computed gotos which speeds up
|
||||
cosmoV_execute by about 20% from benchmarking. of course, if you know
|
||||
your compiler supports computed gotos, you can define VM_JUMPTABLE
|
||||
|
||||
although, this is disabled when VM_DEBUG is defined, since it can cause
|
||||
issues with debugging
|
||||
|
||||
BTW: be weary of maliciously crafted cosmo dumps!! it's very easy to crash
|
||||
cosmo with this enabled and reading invalid opcodes due to us just using the
|
||||
opcode as an index into the jump table
|
||||
*/
|
||||
#if (defined(__GNUC__) || defined(__clang__)) && !defined(VM_DEBUG)
|
||||
# define VM_JUMPTABLE
|
||||
#endif
|
||||
|
||||
// args = # of pass parameters, nresults = # of expected results
|
||||
COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults);
|
||||
COSMO_API COSMOVMRESULT cosmoV_pcall(CState *state, int args, int nresults);
|
||||
COSMO_API void cosmoV_call(CState *state, int args, int nresults);
|
||||
COSMO_API bool cosmoV_pcall(CState *state, int args, int nresults);
|
||||
|
||||
// pushes new object onto the stack & returns a pointer to the new object
|
||||
COSMO_API CObjObject* cosmoV_makeObject(CState *state, int pairs);
|
||||
COSMO_API CObjObject *cosmoV_makeObject(CState *state, int pairs);
|
||||
COSMO_API void cosmoV_makeTable(CState *state, int pairs);
|
||||
COSMO_API void cosmoV_concat(CState *state, int vals);
|
||||
COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...);
|
||||
COSMO_API void cosmoV_printError(CState *state, CObjError *err);
|
||||
COSMO_API CObjError* cosmoV_throw(CState *state);
|
||||
COSMO_API void cosmoV_throw(CState *state);
|
||||
COSMO_API void cosmoV_error(CState *state, const char *format, ...);
|
||||
COSMO_API void cosmo_insert(CState *state, int indx, CValue val);
|
||||
COSMO_API void cosmoV_insert(CState *state, int indx, CValue val);
|
||||
|
||||
// returns true if replacing a previously registered proto object for this type
|
||||
/*
|
||||
Sets the default proto objects for the passed objType. Also walks through the object heap and
|
||||
updates protos for the passed objType if that CObj* has no proto.
|
||||
|
||||
returns true if replacing a previously registered proto object for this type
|
||||
*/
|
||||
COSMO_API bool cosmoV_registerProtoObject(CState *state, CObjType objType, CObjObject *obj);
|
||||
|
||||
/*
|
||||
compiles string into a <closure>, if successful, <closure> will be pushed onto the stack otherwise the <error> will be pushed.
|
||||
compiles string into a <closure>. if successful, <closure> will be pushed onto the stack
|
||||
otherwise the <error> will be pushed.
|
||||
|
||||
returns:
|
||||
false : <error> is at the top of the stack
|
||||
@ -41,38 +57,42 @@ COSMO_API bool cosmoV_registerProtoObject(CState *state, CObjType objType, CObjO
|
||||
COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name);
|
||||
|
||||
/*
|
||||
expects object to be pushed, then the key.
|
||||
|
||||
if returns false an error was thrown, if returns true the value was pushed onto the stack and the object and key were popped
|
||||
loads a <closure> from a dump. if successful, <closure> will be pushed onto the stack
|
||||
otherwise the <error> will be pushed.
|
||||
|
||||
returns:
|
||||
false : <error> is at the top of the stack
|
||||
true : <closure> is at the top of the stack
|
||||
*/
|
||||
COSMO_API bool cosmoV_get(CState *state);
|
||||
COSMO_API bool cosmoV_undump(CState *state, cosmo_Reader reader, const void *ud);
|
||||
|
||||
/*
|
||||
expects object to be pushed, then the key, and finally the new value.
|
||||
|
||||
if returns false an error was thrown, if returns true the value was set and the object key, and value were popped
|
||||
expects object to be pushed, then the key. pops the key & object and pushes the value
|
||||
*/
|
||||
COSMO_API bool cosmoV_set(CState *state);
|
||||
COSMO_API void cosmoV_get(CState *state);
|
||||
|
||||
// wraps the closure into a CObjMethod, so the function is called as an invoked method
|
||||
COSMO_API bool cosmoV_getMethod(CState *state, CObj *obj, CValue key, CValue *val);
|
||||
/*
|
||||
expects object to be pushed, then the key, and finally the new value. pops the object, key & value
|
||||
*/
|
||||
COSMO_API void cosmoV_set(CState *state);
|
||||
|
||||
// wraps the closure into a CObjMethod, so the function is called as an invoked method
|
||||
COSMO_API void cosmoV_getMethod(CState *state, CObj *obj, CValue key, CValue *val);
|
||||
|
||||
// check if the value at the top of the stack is a <obj> user type
|
||||
COSMO_API bool cosmoV_isValueUserType(CState *state, CValue val, int userType);
|
||||
|
||||
// nice to have wrappers
|
||||
|
||||
// pushes raw CValue to the stack
|
||||
static inline void cosmoV_pushValue(CState *state, CValue val) {
|
||||
// pushes a raw CValue to the stack, might throw an error if the stack is overflowed (with the
|
||||
// SAFE_STACK macro on)
|
||||
static inline void cosmoV_pushValue(CState *state, CValue val)
|
||||
{
|
||||
#ifdef SAFE_STACK
|
||||
ptrdiff_t stackSize = state->top - state->stack;
|
||||
|
||||
// we reserve 8 slots for the error string and whatever c api we might be in
|
||||
if (stackSize >= STACK_MAX - 8) {
|
||||
if (state->panic) { // we're in a panic state, let the 8 reserved slots be filled
|
||||
if (stackSize < STACK_MAX)
|
||||
*(state->top++) = val;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cosmoV_error(state, "Stack overflow!");
|
||||
return;
|
||||
}
|
||||
@ -82,51 +102,61 @@ static inline void cosmoV_pushValue(CState *state, CValue val) {
|
||||
}
|
||||
|
||||
// sets stack->top to stack->top - indx
|
||||
static inline StkPtr cosmoV_setTop(CState *state, int indx) {
|
||||
static inline StkPtr cosmoV_setTop(CState *state, int indx)
|
||||
{
|
||||
state->top -= indx;
|
||||
return state->top;
|
||||
}
|
||||
|
||||
// returns stack->top - indx - 1
|
||||
static inline StkPtr cosmoV_getTop(CState *state, int indx) {
|
||||
static inline StkPtr cosmoV_getTop(CState *state, int indx)
|
||||
{
|
||||
return &state->top[-(indx + 1)];
|
||||
}
|
||||
|
||||
// pops 1 value off the stack
|
||||
static inline StkPtr cosmoV_pop(CState *state) {
|
||||
// pops 1 value off the stack, returns the popped value
|
||||
static inline StkPtr cosmoV_pop(CState *state)
|
||||
{
|
||||
return cosmoV_setTop(state, 1);
|
||||
}
|
||||
|
||||
// pushes a cosmo_Number to the stack
|
||||
static inline void cosmoV_pushNumber(CState *state, cosmo_Number num) {
|
||||
static inline void cosmoV_pushNumber(CState *state, cosmo_Number num)
|
||||
{
|
||||
cosmoV_pushValue(state, cosmoV_newNumber(num));
|
||||
}
|
||||
|
||||
// pushes a boolean to the stack
|
||||
static inline void cosmoV_pushBoolean(CState *state, bool b) {
|
||||
static inline void cosmoV_pushBoolean(CState *state, bool b)
|
||||
{
|
||||
cosmoV_pushValue(state, cosmoV_newBoolean(b));
|
||||
}
|
||||
|
||||
static inline void cosmoV_pushRef(CState *state, CObj *obj) {
|
||||
static inline void cosmoV_pushRef(CState *state, CObj *obj)
|
||||
{
|
||||
cosmoV_pushValue(state, cosmoV_newRef(obj));
|
||||
}
|
||||
|
||||
// pushes a C Function to the stack
|
||||
static inline void cosmoV_pushCFunction(CState *state, CosmoCFunction func) {
|
||||
cosmoV_pushRef(state, (CObj*)cosmoO_newCFunction(state, func));
|
||||
static inline void cosmoV_pushCFunction(CState *state, CosmoCFunction func)
|
||||
{
|
||||
cosmoV_pushRef(state, (CObj *)cosmoO_newCFunction(state, func));
|
||||
}
|
||||
|
||||
// len is the length of the string without the NULL terminator
|
||||
static inline void cosmoV_pushLString(CState *state, const char *str, size_t len) {
|
||||
cosmoV_pushRef(state, (CObj*)cosmoO_copyString(state, str, len));
|
||||
static inline void cosmoV_pushLString(CState *state, const char *str, size_t len)
|
||||
{
|
||||
cosmoV_pushRef(state, (CObj *)cosmoO_copyString(state, str, len));
|
||||
}
|
||||
|
||||
// accepts a null terminated string and copys the buffer to the VM heap
|
||||
static inline void cosmoV_pushString(CState *state, const char *str) {
|
||||
static inline void cosmoV_pushString(CState *state, const char *str)
|
||||
{
|
||||
cosmoV_pushLString(state, str, strlen(str));
|
||||
}
|
||||
|
||||
static inline void cosmoV_pushNil(CState *state) {
|
||||
static inline void cosmoV_pushNil(CState *state)
|
||||
{
|
||||
cosmoV_pushValue(state, cosmoV_newNil());
|
||||
}
|
||||
|
||||
|
141
src/main.c
141
src/main.c
@ -1,141 +0,0 @@
|
||||
#include "cosmo.h"
|
||||
#include "cchunk.h"
|
||||
#include "cdebug.h"
|
||||
#include "cvm.h"
|
||||
#include "cparse.h"
|
||||
#include "cbaselib.h"
|
||||
|
||||
#include "cmem.h"
|
||||
|
||||
static bool _ACTIVE = false;
|
||||
|
||||
int cosmoB_quitRepl(CState *state, int nargs, CValue *args) {
|
||||
_ACTIVE = false;
|
||||
|
||||
return 0; // we don't return anything
|
||||
}
|
||||
|
||||
int cosmoB_input(CState *state, int nargs, CValue *args) {
|
||||
// input() accepts the same params as print()!
|
||||
for (int i = 0; i < nargs; i++) {
|
||||
CObjString *str = cosmoV_toString(state, args[i]);
|
||||
printf("%s", cosmoO_readCString(str));
|
||||
}
|
||||
|
||||
// but, we return user input instead!
|
||||
char line[1024];
|
||||
fgets(line, sizeof(line), stdin);
|
||||
|
||||
cosmoV_pushRef(state, (CObj*)cosmoO_copyString(state, line, strlen(line)-1)); // -1 for the \n
|
||||
|
||||
return 1; // 1 return value
|
||||
}
|
||||
|
||||
static void interpret(CState *state, const char *script, const char *mod) {
|
||||
// cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE)
|
||||
if (cosmoV_compileString(state, script, mod)) {
|
||||
COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
|
||||
|
||||
if (res == COSMOVM_RUNTIME_ERR)
|
||||
cosmoV_printError(state, state->error);
|
||||
} else {
|
||||
cosmoV_pop(state); // pop the error off the stack
|
||||
cosmoV_printError(state, state->error);
|
||||
}
|
||||
|
||||
state->panic = false; // so our repl isn't broken
|
||||
}
|
||||
|
||||
static void repl() {
|
||||
char line[1024];
|
||||
_ACTIVE = true;
|
||||
|
||||
CState *state = cosmoV_newState();
|
||||
cosmoB_loadLibrary(state);
|
||||
cosmoB_loadOSLib(state);
|
||||
cosmoB_loadVM(state);
|
||||
|
||||
// add our custom REPL functions
|
||||
cosmoV_pushString(state, "quit");
|
||||
cosmoV_pushCFunction(state, cosmoB_quitRepl);
|
||||
|
||||
cosmoV_pushString(state, "input");
|
||||
cosmoV_pushCFunction(state, cosmoB_input);
|
||||
|
||||
cosmoV_register(state, 2);
|
||||
|
||||
while (_ACTIVE) {
|
||||
printf("> ");
|
||||
|
||||
if (!fgets(line, sizeof(line), stdin)) { // better than gets()
|
||||
printf("\n> ");
|
||||
break;
|
||||
}
|
||||
|
||||
interpret(state, line, "REPL");
|
||||
}
|
||||
|
||||
cosmoV_freeState(state);
|
||||
}
|
||||
|
||||
static char *readFile(const char* path) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Could not open file \"%s\".\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
// first, we need to know how big our file is
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
char *buffer = (char*)malloc(fileSize + 1); // make room for the null byte
|
||||
if (buffer == NULL) {
|
||||
fprintf(stderr, "failed to allocate!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||
|
||||
if (bytesRead < fileSize) {
|
||||
printf("failed to read file \"%s\"!\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
buffer[bytesRead] = '\0'; // place our null terminator
|
||||
|
||||
// close the file handler and return the script buffer
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void runFile(const char* fileName) {
|
||||
char* script = readFile(fileName);
|
||||
CState *state = cosmoV_newState();
|
||||
cosmoB_loadLibrary(state);
|
||||
cosmoB_loadOSLib(state);
|
||||
|
||||
// add our input() function to the global table
|
||||
cosmoV_pushString(state, "input");
|
||||
cosmoV_pushCFunction(state, cosmoB_input);
|
||||
|
||||
cosmoV_register(state, 1);
|
||||
|
||||
interpret(state, script, fileName);
|
||||
|
||||
cosmoV_freeState(state);
|
||||
free(script);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
if (argc == 1) {
|
||||
repl();
|
||||
} else if (argc >= 2) { // they passed a file (or more lol)
|
||||
for (int i = 1; i < argc; i++) {
|
||||
runFile(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
653
util/getopt.h
Normal file
653
util/getopt.h
Normal file
@ -0,0 +1,653 @@
|
||||
#ifndef __GETOPT_H__
|
||||
/**
|
||||
* DISCLAIMER
|
||||
* This file is part of the mingw-w64 runtime package.
|
||||
*
|
||||
* The mingw-w64 runtime package and its code is distributed in the hope that it
|
||||
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
|
||||
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
|
||||
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* Sponsored in part by the Defense Advanced Research Projects
|
||||
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
||||
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
||||
*/
|
||||
/*-
|
||||
* Copyright (c) 2000 The NetBSD Foundation, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This code is derived from software contributed to The NetBSD Foundation
|
||||
* by Dieter Baron and Thomas Klausner.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma warning(disable:4996)
|
||||
|
||||
#define __GETOPT_H__
|
||||
|
||||
/* All the headers include this file. */
|
||||
#include <crtdefs.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
|
||||
|
||||
#ifdef REPLACE_GETOPT
|
||||
int opterr = 1; /* if error message should be printed */
|
||||
int optind = 1; /* index into parent argv vector */
|
||||
int optopt = '?'; /* character checked for validity */
|
||||
#undef optreset /* see getopt.h */
|
||||
#define optreset __mingw_optreset
|
||||
int optreset; /* reset getopt */
|
||||
char *optarg; /* argument associated with option */
|
||||
#endif
|
||||
|
||||
//extern int optind; /* index of first non-option in argv */
|
||||
//extern int optopt; /* single option character, as parsed */
|
||||
//extern int opterr; /* flag to enable built-in diagnostics... */
|
||||
// /* (user may set to zero, to suppress) */
|
||||
//
|
||||
//extern char *optarg; /* pointer to argument of current option */
|
||||
|
||||
#define PRINT_ERROR ((opterr) && (*options != ':'))
|
||||
|
||||
#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
|
||||
#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
|
||||
#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
|
||||
|
||||
/* return values */
|
||||
#define BADCH (int)'?'
|
||||
#define BADARG ((*options == ':') ? (int)':' : (int)'?')
|
||||
#define INORDER (int)1
|
||||
|
||||
#ifndef __CYGWIN__
|
||||
#define __progname __argv[0]
|
||||
#else
|
||||
extern char __declspec(dllimport) *__progname;
|
||||
#endif
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
static char EMSG[] = "";
|
||||
#else
|
||||
#define EMSG ""
|
||||
#endif
|
||||
|
||||
static int getopt_internal(int, char * const *, const char *,
|
||||
const struct option *, int *, int);
|
||||
static int parse_long_options(char * const *, const char *,
|
||||
const struct option *, int *, int);
|
||||
static int gcd(int, int);
|
||||
static void permute_args(int, int, int, char * const *);
|
||||
|
||||
static char *place = EMSG; /* option letter processing */
|
||||
|
||||
/* XXX: set optreset to 1 rather than these two */
|
||||
static int nonopt_start = -1; /* first non option argument (for permute) */
|
||||
static int nonopt_end = -1; /* first option after non options (for permute) */
|
||||
|
||||
/* Error messages */
|
||||
static const char recargchar[] = "option requires an argument -- %c";
|
||||
static const char recargstring[] = "option requires an argument -- %s";
|
||||
static const char ambig[] = "ambiguous option -- %.*s";
|
||||
static const char noarg[] = "option doesn't take an argument -- %.*s";
|
||||
static const char illoptchar[] = "unknown option -- %c";
|
||||
static const char illoptstring[] = "unknown option -- %s";
|
||||
|
||||
static void
|
||||
_vwarnx(const char *fmt,va_list ap)
|
||||
{
|
||||
(void)fprintf(stderr,"%s: ",__progname);
|
||||
if (fmt != NULL)
|
||||
(void)vfprintf(stderr,fmt,ap);
|
||||
(void)fprintf(stderr,"\n");
|
||||
}
|
||||
|
||||
static void
|
||||
warnx(const char *fmt,...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap,fmt);
|
||||
_vwarnx(fmt,ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the greatest common divisor of a and b.
|
||||
*/
|
||||
static int
|
||||
gcd(int a, int b)
|
||||
{
|
||||
int c;
|
||||
|
||||
c = a % b;
|
||||
while (c != 0) {
|
||||
a = b;
|
||||
b = c;
|
||||
c = a % b;
|
||||
}
|
||||
|
||||
return (b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Exchange the block from nonopt_start to nonopt_end with the block
|
||||
* from nonopt_end to opt_end (keeping the same order of arguments
|
||||
* in each block).
|
||||
*/
|
||||
static void
|
||||
permute_args(int panonopt_start, int panonopt_end, int opt_end,
|
||||
char * const *nargv)
|
||||
{
|
||||
int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
|
||||
char *swap;
|
||||
|
||||
/*
|
||||
* compute lengths of blocks and number and size of cycles
|
||||
*/
|
||||
nnonopts = panonopt_end - panonopt_start;
|
||||
nopts = opt_end - panonopt_end;
|
||||
ncycle = gcd(nnonopts, nopts);
|
||||
cyclelen = (opt_end - panonopt_start) / ncycle;
|
||||
|
||||
for (i = 0; i < ncycle; i++) {
|
||||
cstart = panonopt_end+i;
|
||||
pos = cstart;
|
||||
for (j = 0; j < cyclelen; j++) {
|
||||
if (pos >= panonopt_end)
|
||||
pos -= nnonopts;
|
||||
else
|
||||
pos += nopts;
|
||||
swap = nargv[pos];
|
||||
/* LINTED const cast */
|
||||
((char **) nargv)[pos] = nargv[cstart];
|
||||
/* LINTED const cast */
|
||||
((char **)nargv)[cstart] = swap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef REPLACE_GETOPT
|
||||
/*
|
||||
* getopt --
|
||||
* Parse argc/argv argument vector.
|
||||
*
|
||||
* [eventually this will replace the BSD getopt]
|
||||
*/
|
||||
int
|
||||
getopt(int nargc, char * const *nargv, const char *options)
|
||||
{
|
||||
|
||||
/*
|
||||
* We don't pass FLAG_PERMUTE to getopt_internal() since
|
||||
* the BSD getopt(3) (unlike GNU) has never done this.
|
||||
*
|
||||
* Furthermore, since many privileged programs call getopt()
|
||||
* before dropping privileges it makes sense to keep things
|
||||
* as simple (and bug-free) as possible.
|
||||
*/
|
||||
return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
|
||||
}
|
||||
#endif /* REPLACE_GETOPT */
|
||||
|
||||
//extern int getopt(int nargc, char * const *nargv, const char *options);
|
||||
|
||||
#ifdef _BSD_SOURCE
|
||||
/*
|
||||
* BSD adds the non-standard `optreset' feature, for reinitialisation
|
||||
* of `getopt' parsing. We support this feature, for applications which
|
||||
* proclaim their BSD heritage, before including this header; however,
|
||||
* to maintain portability, developers are advised to avoid it.
|
||||
*/
|
||||
# define optreset __mingw_optreset
|
||||
extern int optreset;
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* POSIX requires the `getopt' API to be specified in `unistd.h';
|
||||
* thus, `unistd.h' includes this header. However, we do not want
|
||||
* to expose the `getopt_long' or `getopt_long_only' APIs, when
|
||||
* included in this manner. Thus, close the standard __GETOPT_H__
|
||||
* declarations block, and open an additional __GETOPT_LONG_H__
|
||||
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
|
||||
* to declare the extended API.
|
||||
*/
|
||||
#endif /* !defined(__GETOPT_H__) */
|
||||
|
||||
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
|
||||
#define __GETOPT_LONG_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct option /* specification for a long form option... */
|
||||
{
|
||||
const char *name; /* option name, without leading hyphens */
|
||||
int has_arg; /* does it take an argument? */
|
||||
int *flag; /* where to save its status, or NULL */
|
||||
int val; /* its associated status value */
|
||||
};
|
||||
|
||||
enum /* permitted values for its `has_arg' field... */
|
||||
{
|
||||
no_argument = 0, /* option never takes an argument */
|
||||
required_argument, /* option always requires an argument */
|
||||
optional_argument /* option may take an argument */
|
||||
};
|
||||
|
||||
/*
|
||||
* parse_long_options --
|
||||
* Parse long options in argc/argv argument vector.
|
||||
* Returns -1 if short_too is set and the option does not match long_options.
|
||||
*/
|
||||
static int
|
||||
parse_long_options(char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx, int short_too)
|
||||
{
|
||||
char *current_argv, *has_equal;
|
||||
size_t current_argv_len;
|
||||
int i, ambiguous, match;
|
||||
|
||||
#define IDENTICAL_INTERPRETATION(_x, _y) \
|
||||
(long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
|
||||
long_options[(_x)].flag == long_options[(_y)].flag && \
|
||||
long_options[(_x)].val == long_options[(_y)].val)
|
||||
|
||||
current_argv = place;
|
||||
match = -1;
|
||||
ambiguous = 0;
|
||||
|
||||
optind++;
|
||||
|
||||
if ((has_equal = strchr(current_argv, '=')) != NULL) {
|
||||
/* argument found (--option=arg) */
|
||||
current_argv_len = has_equal - current_argv;
|
||||
has_equal++;
|
||||
} else
|
||||
current_argv_len = strlen(current_argv);
|
||||
|
||||
for (i = 0; long_options[i].name; i++) {
|
||||
/* find matching long option */
|
||||
if (strncmp(current_argv, long_options[i].name,
|
||||
current_argv_len))
|
||||
continue;
|
||||
|
||||
if (strlen(long_options[i].name) == current_argv_len) {
|
||||
/* exact match */
|
||||
match = i;
|
||||
ambiguous = 0;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* If this is a known short option, don't allow
|
||||
* a partial match of a single character.
|
||||
*/
|
||||
if (short_too && current_argv_len == 1)
|
||||
continue;
|
||||
|
||||
if (match == -1) /* partial match */
|
||||
match = i;
|
||||
else if (!IDENTICAL_INTERPRETATION(i, match))
|
||||
ambiguous = 1;
|
||||
}
|
||||
if (ambiguous) {
|
||||
/* ambiguous abbreviation */
|
||||
if (PRINT_ERROR)
|
||||
warnx(ambig, (int)current_argv_len,
|
||||
current_argv);
|
||||
optopt = 0;
|
||||
return (BADCH);
|
||||
}
|
||||
if (match != -1) { /* option found */
|
||||
if (long_options[match].has_arg == no_argument
|
||||
&& has_equal) {
|
||||
if (PRINT_ERROR)
|
||||
warnx(noarg, (int)current_argv_len,
|
||||
current_argv);
|
||||
/*
|
||||
* XXX: GNU sets optopt to val regardless of flag
|
||||
*/
|
||||
if (long_options[match].flag == NULL)
|
||||
optopt = long_options[match].val;
|
||||
else
|
||||
optopt = 0;
|
||||
return (BADARG);
|
||||
}
|
||||
if (long_options[match].has_arg == required_argument ||
|
||||
long_options[match].has_arg == optional_argument) {
|
||||
if (has_equal)
|
||||
optarg = has_equal;
|
||||
else if (long_options[match].has_arg ==
|
||||
required_argument) {
|
||||
/*
|
||||
* optional argument doesn't use next nargv
|
||||
*/
|
||||
optarg = nargv[optind++];
|
||||
}
|
||||
}
|
||||
if ((long_options[match].has_arg == required_argument)
|
||||
&& (optarg == NULL)) {
|
||||
/*
|
||||
* Missing argument; leading ':' indicates no error
|
||||
* should be generated.
|
||||
*/
|
||||
if (PRINT_ERROR)
|
||||
warnx(recargstring,
|
||||
current_argv);
|
||||
/*
|
||||
* XXX: GNU sets optopt to val regardless of flag
|
||||
*/
|
||||
if (long_options[match].flag == NULL)
|
||||
optopt = long_options[match].val;
|
||||
else
|
||||
optopt = 0;
|
||||
--optind;
|
||||
return (BADARG);
|
||||
}
|
||||
} else { /* unknown option */
|
||||
if (short_too) {
|
||||
--optind;
|
||||
return (-1);
|
||||
}
|
||||
if (PRINT_ERROR)
|
||||
warnx(illoptstring, current_argv);
|
||||
optopt = 0;
|
||||
return (BADCH);
|
||||
}
|
||||
if (idx)
|
||||
*idx = match;
|
||||
if (long_options[match].flag) {
|
||||
*long_options[match].flag = long_options[match].val;
|
||||
return (0);
|
||||
} else
|
||||
return (long_options[match].val);
|
||||
#undef IDENTICAL_INTERPRETATION
|
||||
}
|
||||
|
||||
/*
|
||||
* getopt_internal --
|
||||
* Parse argc/argv argument vector. Called by user level routines.
|
||||
*/
|
||||
static int
|
||||
getopt_internal(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx, int flags)
|
||||
{
|
||||
char *oli; /* option letter list index */
|
||||
int optchar, short_too;
|
||||
static int posixly_correct = -1;
|
||||
|
||||
if (options == NULL)
|
||||
return (-1);
|
||||
|
||||
/*
|
||||
* XXX Some GNU programs (like cvs) set optind to 0 instead of
|
||||
* XXX using optreset. Work around this braindamage.
|
||||
*/
|
||||
if (optind == 0)
|
||||
optind = optreset = 1;
|
||||
|
||||
/*
|
||||
* Disable GNU extensions if POSIXLY_CORRECT is set or options
|
||||
* string begins with a '+'.
|
||||
*
|
||||
* CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
|
||||
* optreset != 0 for GNU compatibility.
|
||||
*/
|
||||
if (posixly_correct == -1 || optreset != 0)
|
||||
posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
|
||||
if (*options == '-')
|
||||
flags |= FLAG_ALLARGS;
|
||||
else if (posixly_correct || *options == '+')
|
||||
flags &= ~FLAG_PERMUTE;
|
||||
if (*options == '+' || *options == '-')
|
||||
options++;
|
||||
|
||||
optarg = NULL;
|
||||
if (optreset)
|
||||
nonopt_start = nonopt_end = -1;
|
||||
start:
|
||||
if (optreset || !*place) { /* update scanning pointer */
|
||||
optreset = 0;
|
||||
if (optind >= nargc) { /* end of argument vector */
|
||||
place = EMSG;
|
||||
if (nonopt_end != -1) {
|
||||
/* do permutation, if we have to */
|
||||
permute_args(nonopt_start, nonopt_end,
|
||||
optind, nargv);
|
||||
optind -= nonopt_end - nonopt_start;
|
||||
}
|
||||
else if (nonopt_start != -1) {
|
||||
/*
|
||||
* If we skipped non-options, set optind
|
||||
* to the first of them.
|
||||
*/
|
||||
optind = nonopt_start;
|
||||
}
|
||||
nonopt_start = nonopt_end = -1;
|
||||
return (-1);
|
||||
}
|
||||
if (*(place = nargv[optind]) != '-' ||
|
||||
(place[1] == '\0' && strchr(options, '-') == NULL)) {
|
||||
place = EMSG; /* found non-option */
|
||||
if (flags & FLAG_ALLARGS) {
|
||||
/*
|
||||
* GNU extension:
|
||||
* return non-option as argument to option 1
|
||||
*/
|
||||
optarg = nargv[optind++];
|
||||
return (INORDER);
|
||||
}
|
||||
if (!(flags & FLAG_PERMUTE)) {
|
||||
/*
|
||||
* If no permutation wanted, stop parsing
|
||||
* at first non-option.
|
||||
*/
|
||||
return (-1);
|
||||
}
|
||||
/* do permutation */
|
||||
if (nonopt_start == -1)
|
||||
nonopt_start = optind;
|
||||
else if (nonopt_end != -1) {
|
||||
permute_args(nonopt_start, nonopt_end,
|
||||
optind, nargv);
|
||||
nonopt_start = optind -
|
||||
(nonopt_end - nonopt_start);
|
||||
nonopt_end = -1;
|
||||
}
|
||||
optind++;
|
||||
/* process next argument */
|
||||
goto start;
|
||||
}
|
||||
if (nonopt_start != -1 && nonopt_end == -1)
|
||||
nonopt_end = optind;
|
||||
|
||||
/*
|
||||
* If we have "-" do nothing, if "--" we are done.
|
||||
*/
|
||||
if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
|
||||
optind++;
|
||||
place = EMSG;
|
||||
/*
|
||||
* We found an option (--), so if we skipped
|
||||
* non-options, we have to permute.
|
||||
*/
|
||||
if (nonopt_end != -1) {
|
||||
permute_args(nonopt_start, nonopt_end,
|
||||
optind, nargv);
|
||||
optind -= nonopt_end - nonopt_start;
|
||||
}
|
||||
nonopt_start = nonopt_end = -1;
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check long options if:
|
||||
* 1) we were passed some
|
||||
* 2) the arg is not just "-"
|
||||
* 3) either the arg starts with -- we are getopt_long_only()
|
||||
*/
|
||||
if (long_options != NULL && place != nargv[optind] &&
|
||||
(*place == '-' || (flags & FLAG_LONGONLY))) {
|
||||
short_too = 0;
|
||||
if (*place == '-')
|
||||
place++; /* --foo long option */
|
||||
else if (*place != ':' && strchr(options, *place) != NULL)
|
||||
short_too = 1; /* could be short option too */
|
||||
|
||||
optchar = parse_long_options(nargv, options, long_options,
|
||||
idx, short_too);
|
||||
if (optchar != -1) {
|
||||
place = EMSG;
|
||||
return (optchar);
|
||||
}
|
||||
}
|
||||
|
||||
if ((optchar = (int)*place++) == (int)':' ||
|
||||
(optchar == (int)'-' && *place != '\0') ||
|
||||
(oli = (char*)strchr(options, optchar)) == NULL) {
|
||||
/*
|
||||
* If the user specified "-" and '-' isn't listed in
|
||||
* options, return -1 (non-option) as per POSIX.
|
||||
* Otherwise, it is an unknown option character (or ':').
|
||||
*/
|
||||
if (optchar == (int)'-' && *place == '\0')
|
||||
return (-1);
|
||||
if (!*place)
|
||||
++optind;
|
||||
if (PRINT_ERROR)
|
||||
warnx(illoptchar, optchar);
|
||||
optopt = optchar;
|
||||
return (BADCH);
|
||||
}
|
||||
if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
|
||||
/* -W long-option */
|
||||
if (*place) /* no space */
|
||||
/* NOTHING */;
|
||||
else if (++optind >= nargc) { /* no arg */
|
||||
place = EMSG;
|
||||
if (PRINT_ERROR)
|
||||
warnx(recargchar, optchar);
|
||||
optopt = optchar;
|
||||
return (BADARG);
|
||||
} else /* white space */
|
||||
place = nargv[optind];
|
||||
optchar = parse_long_options(nargv, options, long_options,
|
||||
idx, 0);
|
||||
place = EMSG;
|
||||
return (optchar);
|
||||
}
|
||||
if (*++oli != ':') { /* doesn't take argument */
|
||||
if (!*place)
|
||||
++optind;
|
||||
} else { /* takes (optional) argument */
|
||||
optarg = NULL;
|
||||
if (*place) /* no white space */
|
||||
optarg = place;
|
||||
else if (oli[1] != ':') { /* arg not optional */
|
||||
if (++optind >= nargc) { /* no arg */
|
||||
place = EMSG;
|
||||
if (PRINT_ERROR)
|
||||
warnx(recargchar, optchar);
|
||||
optopt = optchar;
|
||||
return (BADARG);
|
||||
} else
|
||||
optarg = nargv[optind];
|
||||
}
|
||||
place = EMSG;
|
||||
++optind;
|
||||
}
|
||||
/* dump back option letter */
|
||||
return (optchar);
|
||||
}
|
||||
|
||||
/*
|
||||
* getopt_long --
|
||||
* Parse argc/argv argument vector.
|
||||
*/
|
||||
int
|
||||
getopt_long(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx)
|
||||
{
|
||||
|
||||
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
||||
FLAG_PERMUTE));
|
||||
}
|
||||
|
||||
/*
|
||||
* getopt_long_only --
|
||||
* Parse argc/argv argument vector.
|
||||
*/
|
||||
int
|
||||
getopt_long_only(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx)
|
||||
{
|
||||
|
||||
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
||||
FLAG_PERMUTE|FLAG_LONGONLY));
|
||||
}
|
||||
|
||||
//extern int getopt_long(int nargc, char * const *nargv, const char *options,
|
||||
// const struct option *long_options, int *idx);
|
||||
//extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
|
||||
// const struct option *long_options, int *idx);
|
||||
/*
|
||||
* Previous MinGW implementation had...
|
||||
*/
|
||||
#ifndef HAVE_DECL_GETOPT
|
||||
/*
|
||||
* ...for the long form API only; keep this for compatibility.
|
||||
*/
|
||||
# define HAVE_DECL_GETOPT 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
|
379
util/linenoise-win32.c
Normal file
379
util/linenoise-win32.c
Normal file
@ -0,0 +1,379 @@
|
||||
|
||||
/* this code is not standalone
|
||||
* it is included into linenoise.c
|
||||
* for windows.
|
||||
* It is deliberately kept separate so that
|
||||
* applications that have no need for windows
|
||||
* support can omit this
|
||||
*/
|
||||
static DWORD orig_consolemode = 0;
|
||||
|
||||
static int flushOutput(struct current *current);
|
||||
static void outputNewline(struct current *current);
|
||||
|
||||
static void refreshStart(struct current *current)
|
||||
{
|
||||
(void)current;
|
||||
}
|
||||
|
||||
static void refreshEnd(struct current *current)
|
||||
{
|
||||
(void)current;
|
||||
}
|
||||
|
||||
static void refreshStartChars(struct current *current)
|
||||
{
|
||||
assert(current->output == NULL);
|
||||
/* We accumulate all output here */
|
||||
current->output = sb_alloc();
|
||||
#ifdef USE_UTF8
|
||||
current->ubuflen = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void refreshNewline(struct current *current)
|
||||
{
|
||||
DRL("<nl>");
|
||||
outputNewline(current);
|
||||
}
|
||||
|
||||
static void refreshEndChars(struct current *current)
|
||||
{
|
||||
assert(current->output);
|
||||
flushOutput(current);
|
||||
sb_free(current->output);
|
||||
current->output = NULL;
|
||||
}
|
||||
|
||||
static int enableRawMode(struct current *current) {
|
||||
DWORD n;
|
||||
INPUT_RECORD irec;
|
||||
|
||||
current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
current->inh = GetStdHandle(STD_INPUT_HANDLE);
|
||||
|
||||
if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
|
||||
return -1;
|
||||
}
|
||||
if (getWindowSize(current) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (GetConsoleMode(current->inh, &orig_consolemode)) {
|
||||
SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
|
||||
}
|
||||
#ifdef USE_UTF8
|
||||
/* XXX is this the right thing to do? */
|
||||
SetConsoleCP(65001);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void disableRawMode(struct current *current)
|
||||
{
|
||||
SetConsoleMode(current->inh, orig_consolemode);
|
||||
}
|
||||
|
||||
void linenoiseClearScreen(void)
|
||||
{
|
||||
/* XXX: This is ugly. Should just have the caller pass a handle */
|
||||
struct current current;
|
||||
|
||||
current.outh = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
if (getWindowSize(¤t) == 0) {
|
||||
COORD topleft = { 0, 0 };
|
||||
DWORD n;
|
||||
|
||||
FillConsoleOutputCharacter(current.outh, ' ',
|
||||
current.cols * current.rows, topleft, &n);
|
||||
FillConsoleOutputAttribute(current.outh,
|
||||
FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
|
||||
current.cols * current.rows, topleft, &n);
|
||||
SetConsoleCursorPosition(current.outh, topleft);
|
||||
}
|
||||
}
|
||||
|
||||
static void cursorToLeft(struct current *current)
|
||||
{
|
||||
COORD pos;
|
||||
DWORD n;
|
||||
|
||||
pos.X = 0;
|
||||
pos.Y = (SHORT)current->y;
|
||||
|
||||
FillConsoleOutputAttribute(current->outh,
|
||||
FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
|
||||
current->x = 0;
|
||||
}
|
||||
|
||||
#ifdef USE_UTF8
|
||||
static void flush_ubuf(struct current *current)
|
||||
{
|
||||
COORD pos;
|
||||
DWORD nwritten;
|
||||
pos.Y = (SHORT)current->y;
|
||||
pos.X = (SHORT)current->x;
|
||||
SetConsoleCursorPosition(current->outh, pos);
|
||||
WriteConsoleW(current->outh, current->ubuf, current->ubuflen, &nwritten, 0);
|
||||
current->x += current->ubufcols;
|
||||
current->ubuflen = 0;
|
||||
current->ubufcols = 0;
|
||||
}
|
||||
|
||||
static void add_ubuf(struct current *current, int ch)
|
||||
{
|
||||
/* This code originally by: Author: Mark E. Davis, 1994. */
|
||||
static const int halfShift = 10; /* used for shifting by 10 bits */
|
||||
|
||||
static const DWORD halfBase = 0x0010000UL;
|
||||
static const DWORD halfMask = 0x3FFUL;
|
||||
|
||||
#define UNI_SUR_HIGH_START 0xD800
|
||||
#define UNI_SUR_HIGH_END 0xDBFF
|
||||
#define UNI_SUR_LOW_START 0xDC00
|
||||
#define UNI_SUR_LOW_END 0xDFFF
|
||||
|
||||
#define UNI_MAX_BMP 0x0000FFFF
|
||||
|
||||
if (ch > UNI_MAX_BMP) {
|
||||
/* convert from unicode to utf16 surrogate pairs
|
||||
* There is always space for one extra word in ubuf
|
||||
*/
|
||||
ch -= halfBase;
|
||||
current->ubuf[current->ubuflen++] = (WORD)((ch >> halfShift) + UNI_SUR_HIGH_START);
|
||||
current->ubuf[current->ubuflen++] = (WORD)((ch & halfMask) + UNI_SUR_LOW_START);
|
||||
}
|
||||
else {
|
||||
current->ubuf[current->ubuflen++] = ch;
|
||||
}
|
||||
current->ubufcols += utf8_width(ch);
|
||||
if (current->ubuflen >= UBUF_MAX_CHARS) {
|
||||
flush_ubuf(current);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int flushOutput(struct current *current)
|
||||
{
|
||||
const char *pt = sb_str(current->output);
|
||||
int len = sb_len(current->output);
|
||||
|
||||
#ifdef USE_UTF8
|
||||
/* convert utf8 in current->output into utf16 in current->ubuf
|
||||
*/
|
||||
while (len) {
|
||||
int ch;
|
||||
int n = utf8_tounicode(pt, &ch);
|
||||
|
||||
pt += n;
|
||||
len -= n;
|
||||
|
||||
add_ubuf(current, ch);
|
||||
}
|
||||
flush_ubuf(current);
|
||||
#else
|
||||
DWORD nwritten;
|
||||
COORD pos;
|
||||
|
||||
pos.Y = (SHORT)current->y;
|
||||
pos.X = (SHORT)current->x;
|
||||
|
||||
SetConsoleCursorPosition(current->outh, pos);
|
||||
WriteConsoleA(current->outh, pt, len, &nwritten, 0);
|
||||
|
||||
current->x += len;
|
||||
#endif
|
||||
|
||||
sb_clear(current->output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int outputChars(struct current *current, const char *buf, int len)
|
||||
{
|
||||
if (len < 0) {
|
||||
len = strlen(buf);
|
||||
}
|
||||
assert(current->output);
|
||||
|
||||
sb_append_len(current->output, buf, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void outputNewline(struct current *current)
|
||||
{
|
||||
/* On the last row output a newline to force a scroll */
|
||||
if (current->y + 1 == current->rows) {
|
||||
outputChars(current, "\n", 1);
|
||||
}
|
||||
flushOutput(current);
|
||||
current->x = 0;
|
||||
current->y++;
|
||||
}
|
||||
|
||||
static void setOutputHighlight(struct current *current, const int *props, int nprops)
|
||||
{
|
||||
int colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
|
||||
int bold = 0;
|
||||
int reverse = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nprops; i++) {
|
||||
switch (props[i]) {
|
||||
case 0:
|
||||
colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
|
||||
bold = 0;
|
||||
reverse = 0;
|
||||
break;
|
||||
case 1:
|
||||
bold = FOREGROUND_INTENSITY;
|
||||
break;
|
||||
case 7:
|
||||
reverse = 1;
|
||||
break;
|
||||
case 30:
|
||||
colour = 0;
|
||||
break;
|
||||
case 31:
|
||||
colour = FOREGROUND_RED;
|
||||
break;
|
||||
case 32:
|
||||
colour = FOREGROUND_GREEN;
|
||||
break;
|
||||
case 33:
|
||||
colour = FOREGROUND_RED | FOREGROUND_GREEN;
|
||||
break;
|
||||
case 34:
|
||||
colour = FOREGROUND_BLUE;
|
||||
break;
|
||||
case 35:
|
||||
colour = FOREGROUND_RED | FOREGROUND_BLUE;
|
||||
break;
|
||||
case 36:
|
||||
colour = FOREGROUND_BLUE | FOREGROUND_GREEN;
|
||||
break;
|
||||
case 37:
|
||||
colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flushOutput(current);
|
||||
|
||||
if (reverse) {
|
||||
SetConsoleTextAttribute(current->outh, BACKGROUND_INTENSITY);
|
||||
}
|
||||
else {
|
||||
SetConsoleTextAttribute(current->outh, colour | bold);
|
||||
}
|
||||
}
|
||||
|
||||
static void eraseEol(struct current *current)
|
||||
{
|
||||
COORD pos;
|
||||
DWORD n;
|
||||
|
||||
pos.X = (SHORT) current->x;
|
||||
pos.Y = (SHORT) current->y;
|
||||
|
||||
FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
|
||||
}
|
||||
|
||||
static void setCursorXY(struct current *current)
|
||||
{
|
||||
COORD pos;
|
||||
|
||||
pos.X = (SHORT) current->x;
|
||||
pos.Y = (SHORT) current->y;
|
||||
|
||||
SetConsoleCursorPosition(current->outh, pos);
|
||||
}
|
||||
|
||||
|
||||
static void setCursorPos(struct current *current, int x)
|
||||
{
|
||||
current->x = x;
|
||||
setCursorXY(current);
|
||||
}
|
||||
|
||||
static void cursorUp(struct current *current, int n)
|
||||
{
|
||||
current->y -= n;
|
||||
setCursorXY(current);
|
||||
}
|
||||
|
||||
static void cursorDown(struct current *current, int n)
|
||||
{
|
||||
current->y += n;
|
||||
setCursorXY(current);
|
||||
}
|
||||
|
||||
static int fd_read(struct current *current)
|
||||
{
|
||||
while (1) {
|
||||
INPUT_RECORD irec;
|
||||
DWORD n;
|
||||
if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
|
||||
break;
|
||||
}
|
||||
if (!ReadConsoleInputW(current->inh, &irec, 1, &n)) {
|
||||
break;
|
||||
}
|
||||
if (irec.EventType == KEY_EVENT) {
|
||||
KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
|
||||
if (k->bKeyDown || k->wVirtualKeyCode == VK_MENU) {
|
||||
if (k->dwControlKeyState & ENHANCED_KEY) {
|
||||
switch (k->wVirtualKeyCode) {
|
||||
case VK_LEFT:
|
||||
return SPECIAL_LEFT;
|
||||
case VK_RIGHT:
|
||||
return SPECIAL_RIGHT;
|
||||
case VK_UP:
|
||||
return SPECIAL_UP;
|
||||
case VK_DOWN:
|
||||
return SPECIAL_DOWN;
|
||||
case VK_INSERT:
|
||||
return SPECIAL_INSERT;
|
||||
case VK_DELETE:
|
||||
return SPECIAL_DELETE;
|
||||
case VK_HOME:
|
||||
return SPECIAL_HOME;
|
||||
case VK_END:
|
||||
return SPECIAL_END;
|
||||
case VK_PRIOR:
|
||||
return SPECIAL_PAGE_UP;
|
||||
case VK_NEXT:
|
||||
return SPECIAL_PAGE_DOWN;
|
||||
case VK_RETURN:
|
||||
return k->uChar.UnicodeChar;
|
||||
}
|
||||
}
|
||||
/* Note that control characters are already translated in AsciiChar */
|
||||
else if (k->wVirtualKeyCode == VK_CONTROL)
|
||||
continue;
|
||||
else {
|
||||
return k->uChar.UnicodeChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int getWindowSize(struct current *current)
|
||||
{
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
|
||||
return -1;
|
||||
}
|
||||
current->cols = info.dwSize.X;
|
||||
current->rows = info.dwSize.Y;
|
||||
if (current->cols <= 0 || current->rows <= 0) {
|
||||
current->cols = 80;
|
||||
return -1;
|
||||
}
|
||||
current->y = info.dwCursorPosition.Y;
|
||||
current->x = info.dwCursorPosition.X;
|
||||
return 0;
|
||||
}
|
2786
util/linenoise.c
Normal file
2786
util/linenoise.c
Normal file
File diff suppressed because it is too large
Load Diff
152
util/linenoise.h
Normal file
152
util/linenoise.h
Normal file
@ -0,0 +1,152 @@
|
||||
/* linenoise.h -- guerrilla line editing library against the idea that a
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef NO_COMPLETION
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
/*
|
||||
* The callback type for tab completion handlers.
|
||||
*/
|
||||
typedef void(linenoiseCompletionCallback)(const char *prefix, linenoiseCompletions *comp, void *userdata);
|
||||
|
||||
/*
|
||||
* Sets the current tab completion handler and returns the previous one, or NULL
|
||||
* if no prior one has been set.
|
||||
*/
|
||||
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *comp, void *userdata);
|
||||
|
||||
/*
|
||||
* Adds a copy of the given string to the given completion list. The copy is owned
|
||||
* by the linenoiseCompletions object.
|
||||
*/
|
||||
void linenoiseAddCompletion(linenoiseCompletions *comp, const char *str);
|
||||
|
||||
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold, void *userdata);
|
||||
typedef void(linenoiseFreeHintsCallback)(void *hint, void *userdata);
|
||||
void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata);
|
||||
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback);
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Prompts for input using the given string as the input
|
||||
* prompt. Returns when the user has tapped ENTER or (on an empty
|
||||
* line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either
|
||||
* a copy of the entered string (for ENTER) or NULL (on EOF). The
|
||||
* caller owns the returned string and must eventually free() it.
|
||||
*/
|
||||
char *linenoise(const char *prompt);
|
||||
|
||||
/**
|
||||
* Like linenoise() but starts with an initial buffer.
|
||||
*/
|
||||
char *linenoiseWithInitial(const char *prompt, const char *initial);
|
||||
|
||||
/**
|
||||
* Clear the screen.
|
||||
*/
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
/*
|
||||
* Adds a copy of the given line of the command history.
|
||||
*/
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
|
||||
/*
|
||||
* Sets the maximum length of the command history, in lines.
|
||||
* If the history is currently longer, it will be trimmed,
|
||||
* retaining only the most recent entries. If len is 0 or less
|
||||
* then this function does nothing.
|
||||
*/
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
|
||||
/*
|
||||
* Returns the current maximum length of the history, in lines.
|
||||
*/
|
||||
int linenoiseHistoryGetMaxLen(void);
|
||||
|
||||
/*
|
||||
* Saves the current contents of the history to the given file.
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
|
||||
/*
|
||||
* Replaces the current history with the contents
|
||||
* of the given file. Returns 0 on success.
|
||||
*/
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
|
||||
/*
|
||||
* Frees all history entries, clearing the history.
|
||||
*/
|
||||
void linenoiseHistoryFree(void);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the list of history entries, writing its
|
||||
* length to *len if len is not NULL. The memory is owned by linenoise
|
||||
* and must not be freed.
|
||||
*/
|
||||
char **linenoiseHistory(int *len);
|
||||
|
||||
/*
|
||||
* Returns the number of display columns in the current terminal.
|
||||
*/
|
||||
int linenoiseColumns(void);
|
||||
|
||||
/**
|
||||
* Enable or disable multiline mode (disabled by default)
|
||||
*/
|
||||
void linenoiseSetMultiLine(int enableml);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINENOISE_H */
|
Loading…
Reference in New Issue
Block a user