Compare commits

...

102 Commits

Author SHA1 Message Date
cpunch 7f5e3ae8dc lol oops 2023-11-03 22:59:31 -05:00
cpunch 7a54230cb9 hated the styling of this
consistency is nice to have
2023-11-03 22:36:59 -05:00
CPunch 1a7d6caec6 fix __proto object getter 2023-09-11 22:17:35 -05:00
CPunch 1678194941 minor refactoring 2023-09-06 20:23:18 -05:00
CPunch 3ea653b26d added file:write() & an optional `mode` param to os.open() 2023-09-06 17:29:38 -05:00
CPunch d3de4c0e66 removed more debug prints 2023-09-05 14:45:03 -05:00
CPunch d66d4807b3 removed debug prints, oops 2023-09-05 14:43:50 -05:00
CPunch 1fcb35168f fix makefile 2023-09-05 14:42:29 -05:00
CPunch 611162b3be documented the new os.open and file objects 2023-09-05 14:41:59 -05:00
CPunch b3587f48a2 added os.open(); new file object
eg.
```lua
local err, file = os.open("LICENSE.md")
if err then
    // do error handling stuff
end

// passing "a" to read() will read the whole file
print(file:read("a"))
```
2023-09-05 14:38:24 -05:00
CPunch bf36412699 removed 'roots', replaced with a registry table
- removed cosmoM_addRoot
- removed cosmoM_removeRoot
- renamed cosmoV_register to cosmoV_addGlobals
- added cosmoV_addRegistry
- added cosmoV_getRegistry
- added cosmoV_setProto
- added cosmoV_isValueUserType
2023-09-05 14:35:29 -05:00
CPunch 6701a63a63 capture freezeGC in CPanic 2023-09-05 02:23:31 -05:00
CPunch ffff01e9d1 build a release build for linux as well 2023-09-04 20:15:15 -05:00
CPunch 89be01aaf6 even more error handling refactoring
removing all of these useless checks has actually made cosmoV_execute just a
lil bit faster :)
2023-09-04 20:14:53 -05:00
CPunch cc9eb4a5ec lol oops 2023-09-01 17:16:10 -05:00
CPunch 789c5210b4 switched to a better linenoise fork
https://github.com/msteveb/linenoise

this version has several benefits, namely win32 support :D
2023-09-01 17:12:39 -05:00
CPunch dfdd97e739 fix artifacts path 2023-09-01 14:43:20 -05:00
CPunch 096d80d8df better repl input, using linenoise 2023-08-31 23:17:13 -05:00
CPunch f7bc8e0471 include util path 2023-08-31 23:16:28 -05:00
CPunch fce568addc whoops, wrong path to the workflow file 2023-08-30 21:30:20 -05:00
CPunch f5e75f09b9 ig that runner doesn't work lol 2023-08-30 21:28:50 -05:00
CPunch fe136f84b5 removed CERROR 2023-08-30 20:14:03 -05:00
CPunch de8cd481c3 allow manual runs 2023-08-30 20:12:06 -05:00
CPunch 6654c3b91c wrong output path for windows build 2023-08-30 20:09:06 -05:00
CPunch 21f7ea5c14 oops 2023-08-30 20:03:30 -05:00
CPunch e1591ae3fd switched from appveyor to github workflow 2023-08-30 20:02:04 -05:00
CPunch bfdd33e01d fix vm.collect()
we don't freeze the vm on entry to C functions now
2023-08-26 15:03:56 -05:00
CPunch c0893b8a14 more refactoring; things seem to work fine
all example scripts run fine with GC_STRESS enabled
2023-08-30 12:00:52 -05:00
CPunch d30bcace9a don't freezeGC during GC cycle 2023-08-29 23:32:25 -05:00
CPunch 6a47c82179 fix more GC bugs 2023-08-29 23:21:52 -05:00
CPunch d41126e75f fix cparse.c gc bug 2023-08-29 23:01:47 -05:00
CPunch 9f19fd4f31 fix this test script 2023-08-29 16:51:04 -05:00
CPunch 6126b50941 cosmoV_throw() now resets the vm stack as well
also a minor GC bug in cosmoO_newError was fixed.

i'm going to try to phase out cosmoM_freezeGC & friends
since that would cause hell with this new
error handling solution. the only thing still using it is the GC.
2023-08-29 16:48:38 -05:00
CPunch 7fa7eb8d94 fixed minor memory leak in cparse
we keep track of internal values used by the parser by pushing them onto the stack
and popping them off once complete.
2023-08-29 15:27:22 -05:00
CPunch 0633e87aa6 WIP: removed stale error handling
currently, scripts seem to run fine. however I'm a bit worried about stack related issues. maybe i'll need to reset state->top as well? but not entirely sure
2023-08-29 14:07:45 -05:00
CPunch 75d27afe2c WIP: major error handling refactoring
switching to setjmp instead of the really bad global 'panic' flag
2023-08-28 21:13:00 -05:00
CPunch 3f39211081 minor refactoring 2023-08-25 23:34:21 -05:00
CPunch c5e4305ef8 refactored cobj.c:printObject()
uses obj type strings from cosmoO_typeStr
2023-08-25 21:28:41 -05:00
CPunch 1a78a9ab5f finally fixed this memory bug
we were accidentally tracking frees of stuff that was never
allocated lol
2023-08-25 21:22:10 -05:00
CPunch 2f0f675159 more debug related refactoring 2023-08-25 20:44:24 -05:00
CPunch 7c5d2f6b65 minor CTable refactoring 2023-08-25 19:57:16 -05:00
CPunch f76f2ffa92 refactoring and cleanup
cosmoB_loadOSLib -> cosmoB_loadOS
2023-08-24 23:36:32 -05:00
CPunch 155e0829fb minor refactoring 2023-06-03 01:39:35 -05:00
CPunch 2b3825d258 implemented jump table dispatch
- currently only enabled on supported platforms (GNU C Compiler + Clang)
- when enabled, sped up examples/fibtest.cosmo by about 20% (avg of 11.179s prior and 8.799 after)

NOTE: malicious dumps can trivially cause crashes now by having junk function chunks
2023-06-03 01:17:28 -05:00
CPunch 7bca6927a9 fixed formatting 2023-06-01 22:28:07 -05:00
CPunch d3647c161b added vm.disassemble() 2023-06-01 19:04:12 -05:00
unknown d27d94975e fixed MSVC support 2023-06-01 22:22:44 -05:00
CPunch 2d0e63f706 forgot to update this a while back 2023-05-28 21:16:30 -05:00
CPunch dfcf0c92b5 whoops, need to update the command to run the testsuite 2023-05-28 21:13:51 -05:00
CPunch 447f874eff update README.md 2023-05-28 21:11:52 -05:00
CPunch 7b1bd1c9fc minor usage fix 2023-05-28 20:57:53 -05:00
CPunch 9537a2c7b8 major main.c refactoring
- now takes command line arguments:
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
2023-05-28 20:52:12 -05:00
CPunch c44dc88972 minor stack fixes 2023-05-28 12:19:52 -05:00
CPunch d581e68166 proper error handling for dump edgecases 2023-05-28 12:16:00 -05:00
CPunch 2271681cec undef macros 2023-05-28 12:03:49 -05:00
CPunch cf18bbbe54 fixed GC bug in cundump.c 2023-05-28 11:55:48 -05:00
CPunch 3a872fb83f these values can be defined at the top of the function 2023-05-28 00:08:28 -05:00
CPunch 4ed1c79b50 formatting changes 2023-05-28 00:03:50 -05:00
CPunch bc6eb9b6dc added cdump.[ch] and cundump.[ch] to makefile 2023-05-27 23:17:17 -05:00
CPunch 635f31863f cvm.c: added cosmoV_undump 2023-05-27 23:16:47 -05:00
CPunch 49a7f68239 cdump.c: major refactoring; various fixes 2023-05-27 23:15:58 -05:00
CPunch 8efecf71a4 cdump.c: fix instruction and line info dumping 2023-05-27 23:15:12 -05:00
CPunch 395f352c6e started cundump.c:cosmoD_undump(); minor fixes 2023-05-25 21:12:21 -05:00
CPunch 65d37838cd started cdump.c:cosmoD_dump() 2023-05-25 19:41:13 -05:00
CPunch 3b13ae1624 minor refactoring and typos 2023-05-25 19:40:15 -05:00
CPunch d1a16d990c removed stale COBJ_STREAM usage 2023-05-25 18:34:39 -05:00
CPunch 0a4d36f2f4 update Docs to reflect keyword changes 2023-02-10 20:46:05 -06:00
CPunch 8ac8085d20 syntax: 'var'->'let' 'function'->'func'
- 'var' has some weird scoping connotations with users of JS. better to change it to 'let', which will decide whether to make the variable a local or a global
- 'func' looks visually appealing lol
- some minor refactoring done in cparse.c
2023-02-09 15:58:25 -06:00
CPunch e335fd95d6 minor formatting fixes 2023-02-09 12:52:36 -06:00
CPunch b902ac90de removed CObjStream 2023-02-09 12:42:09 -06:00
CPunch 6056f8eb5b added clang-format 2023-02-09 12:32:48 -06:00
CPunch 88284a0b6e Removed '-Werror' to the Makefile 2021-06-11 15:01:53 -05:00
Inversion 7998c2ab41 Add documentation for the OS library 2023-11-03 22:55:24 -05:00
CPunch 7b5825668d Added boilerplate for CObjStream 2021-03-20 01:44:03 -05:00
CPunch d13cc398c8 Added os.system() to the os.* library 2021-03-20 01:02:13 -05:00
CPunch 012d3702bf Updated Appveyor to test testsuite 2021-03-19 22:25:23 -05:00
CPunch d761970f17 Added minimal testsuite for IC
- main.c will now report errors for passed scripts
2021-03-19 22:23:04 -05:00
CPunch 0e730b9c51 Added svg to README, whitelisted commits only effecting the binary 2021-03-16 15:05:20 -05:00
CPunch bff2799bb6 Added AppVeyor CI 2021-03-16 14:54:44 -05:00
cpunch 07ca82f968 Added '__equal' metamethod docs 2021-02-24 12:52:31 -06:00
cpunch b545e8e5e3 Added another example script 'compare.comso'
- This stresses the table implementation as well as cosmoV_equals
2021-02-23 11:50:41 -06:00
cpunch 55e6453589 Improved cosmoO_equals performance for strings 2021-02-23 11:49:57 -06:00
cpunch c83dca2ab2 Added '__equal' metamethod, slightly refactored cosmoO_equal
- ISTRING_EQUAL has been added
2021-02-20 12:42:13 -06:00
cpunch 3890c9dd1e Refactored cosmoO_equals
This sets up room for the '__equal' metamethod to be added

- cosmoO_equals now requires the state to be passed
- cosmoV_equals now requires the state to be passed
- cosmoT_get now requires the state to be passed
2021-02-19 17:04:23 -06:00
cpunch 40739e9bea cparse.c now throws an error on illegal statements 2021-02-19 12:47:12 -06:00
Inversion 1eec23035f Temporary fix for possible bug 2023-11-03 22:55:19 -05:00
Inversion c0274d1d77 Update standard library documentation 2023-11-03 22:55:14 -05:00
Inversion fec26ac380 Add optional custom error message to assert 2023-11-03 22:55:09 -05:00
cpunch 35466f691f Added C99 support, refactored headers 2021-02-15 16:20:04 -06:00
cpunch 71c8dc7e34 Added basic standard library docs 2021-02-15 14:25:08 -06:00
cpunch 7a6e00be41 Added math.rad() and math.deg() 2021-02-15 14:06:43 -06:00
cpunch 14b091b691 Added trig. functions to the math library 2021-02-15 14:00:26 -06:00
cpunch 5c71efbe40 Added OP_POW to cdebug.c; refactored cosmoV_registerProtoObject
- cosmoV_registerProtoObject now walks the object list and updates the proto's for objects of the objType which have a NULL proto.
- misc. comment changes in cvm.h as well.
2021-02-13 20:08:35 -06:00
cpunch 1fff6c7fe9 Added string.rep() to the string library 2021-02-13 19:07:47 -06:00
cpunch 1a96e411f2 Minor Makefile fix 2021-02-11 20:35:42 -06:00
cpunch fdd0d19308 Added CMake support
should make integration into visual studio easier for people
2021-02-11 20:34:04 -06:00
cpunch 33da88a18a Minor cbaselib.c cleanup 2021-02-11 00:58:12 -06:00
Inversion 50b19e9f4f Added argument type check to string.len 2023-11-03 22:55:03 -05:00
Inversion 472a0ea4c1 Updated baselib in accordance with cosmoV_readCString 2023-11-03 22:54:56 -05:00
Inversion 76574c7860 Added cosmoV_readCString for convenience 2023-11-03 22:54:51 -05:00
Inversion 8b931fa4a7 Add string.len to base library 2023-11-03 22:54:45 -05:00
Inversion ce844dc110 Added error to the base library 2023-11-03 22:53:59 -05:00
60 changed files with 8544 additions and 2633 deletions

26
.clang-format Normal file
View 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
View 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
View File

@ -1,3 +1,5 @@
*.o
bin
.vscode
build
.vscode
CMakeFiles

27
CMakeLists.txt Normal file
View 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)

View File

@ -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)

View File

@ -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.
```

View File

@ -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)

View File

@ -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.

View File

@ -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'

View File

@ -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
View 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'

View File

@ -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

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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"))

View File

@ -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

View File

@ -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

View File

@ -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
View 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!")

View File

@ -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)

View File

@ -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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -1,49 +1,63 @@
#include "cdebug.h"
#include "cvalue.h"
#include "cobj.h"
void printIndent(int indent) {
for (int i = 0; i < indent; i++)
#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 +69,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 +80,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 +95,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
View 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
View 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

View File

@ -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!");

View File

@ -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);

View File

@ -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,84 @@ 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);
break;
// mark callframes
for (int i = 0; i < err->frameCount; i++) {
markObject(state, (CObj *)err->frames[i].closure);
}
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;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure *)obj;
markObject(state, (CObj *)closure->function);
break;
// mark all upvalues
for (int i = 0; i < closure->upvalueCount; i++) {
markObject(state, (CObj *)closure->upvalues[i]);
}
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 +204,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 +248,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 +274,56 @@ 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]);
for (int i = 0; i < ISTRING_MAX; i++) {
markObject(state, (CObj *)state->iStrings[i]);
}
// mark the user defined roots
markUserRoots(state);
markTable(state, &state->registry);
// mark other misc. internally reserved objects
markObject(state, (CObj*)state->error);
for (int i = 0; i < COBJ_MAX; i++)
markObject(state, (CObj*)state->protoObjects[i]);
for (int i = 0; i < COBJ_MAX; 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;
}
}

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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,24 +53,27 @@ CState *cosmoV_newState() {
state->frameCount = 0;
state->openUpvalues = NULL;
state->error = NULL;
// set default proto objects
for (int i = 0; i < COBJ_MAX; i++)
for (int i = 0; i < COBJ_MAX; i++) {
state->protoObjects[i] = NULL;
}
for (int i = 0; i < ISTRING_MAX; i++)
for (int i = 0; i < ISTRING_MAX; i++) {
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);
@ -65,61 +89,114 @@ CState *cosmoV_newState() {
state->iStrings[ISTRING_RESERVED] = cosmoO_copyString(state, "__reserved", 10);
// set the IString flags
for (int i = 0; i < ISTRING_MAX; i++)
for (int i = 0; i < ISTRING_MAX; i++) {
state->iStrings[i]->isIString = true;
}
state->freezeGC = 0; // unfreeze the state
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;
}
// mark our internal VM strings NULL
for (int i = 0; i < ISTRING_MAX; i++)
for (int i = 0; i < ISTRING_MAX; i++) {
state->iStrings[i] = NULL;
}
// 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));

View File

@ -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

View File

@ -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,65 @@ 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 +115,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 +127,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 +159,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 +174,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 +189,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 +200,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 +213,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 +268,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 +279,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))) {

View File

@ -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
View 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
View 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

View File

@ -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>");
}
}

View File

@ -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

1076
src/cvm.c

File diff suppressed because it is too large Load Diff

124
src/cvm.h
View File

@ -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());
}

View File

@ -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
View 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
View 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(&current) == 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

File diff suppressed because it is too large Load Diff

152
util/linenoise.h Normal file
View 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 */