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

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.o
bin
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)

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
```
@ -38,6 +54,3 @@ end
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,6 +94,7 @@ 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 |

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,6 +57,7 @@ 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
*/

View File

@ -1,15 +1,19 @@
#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;
@ -20,49 +24,55 @@ void initChunk(CState* state, CChunk *chunk, size_t startCapacity) {
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) {
void writeu16Chunk(CState *state, CChunk *chunk, uint16_t i, int line)
{
static const int sz = sizeof(uint16_t) / sizeof(INSTRUCTION);
INSTRUCTION *buffer = (INSTRUCTION *)(&i);
int sz = sizeof(uint16_t) / sizeof(INSTRUCTION);
for (int i = 0; i < sz; i++) {
writeu8Chunk(state, chunk, buffer[i], line);

View File

@ -1,21 +1,19 @@
#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 {
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);
@ -23,16 +21,20 @@ void cleanChunk(CState* state, CChunk *chunk); // frees everything but the struc
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);
// 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) {
static inline uint16_t readu16Chunk(CChunk *chunk, int offset)
{
return *((uint16_t *)(&chunk->buf[offset]));
}

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);
@ -124,7 +140,8 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
}
// print the chunk
disasmChunk(&cobjFunc->chunk, cobjFunc->name == NULL ? UNNAMEDCHUNK : cobjFunc->name->str, indent+1);
disasmChunk(&cobjFunc->chunk, cobjFunc->name == NULL ? UNNAMEDCHUNK : cobjFunc->name->str,
indent + 1);
return offset;
}
case OP_CLOSE:
@ -161,6 +178,8 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
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:
@ -202,6 +221,5 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
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,4 +1,5 @@
#include "clex.h"
#include "cmem.h"
#include <string.h>
@ -13,7 +14,7 @@ CReservedWord reservedWords[] = {
{ TOKEN_END, "end", 3},
{ TOKEN_FALSE, "false", 5},
{ TOKEN_FOR, "for", 3},
{TOKEN_FUNCTION, "function", 8},
{ TOKEN_FUNC, "func", 4},
{ TOKEN_IF, "if", 2},
{ TOKEN_IN, "in", 2},
{ TOKEN_LOCAL, "local", 5},
@ -24,49 +25,57 @@ CReservedWord reservedWords[] = {
{ TOKEN_RETURN, "return", 6},
{ TOKEN_THEN, "then", 4},
{ TOKEN_TRUE, "true", 4},
{TOKEN_VAR, "var", 3},
{ TOKEN_LET, "let", 3},
{ TOKEN_WHILE, "while", 5}
};
// returns true if current token is a heap allocated buffer
static bool isBuffer(CLexState *state) {
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;
@ -101,7 +111,8 @@ static CToken makeToken(CLexState *state, CTokenType 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;
@ -114,19 +125,23 @@ static CToken makeError(CLexState *state, const char *msg) {
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) {
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
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,7 +192,8 @@ CTokenType identifierType(CLexState *state) {
return TOKEN_IDENTIFIER;
}
void skipWhitespace(CLexState *state) {
static void skipWhitespace(CLexState *state)
{
while (true) {
char c = peek(state);
switch (c) {
@ -184,14 +206,19 @@ void skipWhitespace(CLexState *state) {
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
// 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
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 '*/'
@ -208,7 +235,8 @@ void skipWhitespace(CLexState *state) {
}
}
CToken parseString(CLexState *state) {
static CToken parseString(CLexState *state)
{
makeBuffer(state); // buffer mode
while (peek(state) != '"' && !isEnd(state)) {
switch (peek(state)) {
@ -218,10 +246,19 @@ CToken parseString(CLexState *state) {
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 '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'
@ -282,7 +319,8 @@ CToken parseString(CLexState *state) {
break;
}
return makeError(state, "Unknown special character!"); // TODO: maybe a more descriptive error?
return makeError(
state, "Unknown special character!"); // TODO: maybe a more descriptive error?
}
}
@ -303,7 +341,8 @@ CToken parseString(CLexState *state) {
return makeToken(state, TOKEN_STRING);
}
CToken parseNumber(CLexState *state) {
static CToken parseNumber(CLexState *state)
{
switch (peek(state)) {
case 'x': // hexadecimal number
next(state);
@ -325,7 +364,6 @@ CToken parseNumber(CLexState *state) {
// if it is a number, fall through and parse normally
}
// consume number
while (isNumerical(peek(state))) {
next(state);
@ -342,7 +380,8 @@ 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))
next(state);
@ -350,8 +389,8 @@ CToken parseIdentifier(CLexState *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));
void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source)
{
state->startChar = (char *)source;
state->currentChar = (char *)source;
state->line = 1;
@ -360,15 +399,15 @@ CLexState *cosmoL_newLexState(CState *cstate, const char *source) {
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;
@ -380,37 +419,59 @@ CToken cosmoL_scanToken(CLexState *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);
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);
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);
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);
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);
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);
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);
return match(state, '=') ? makeToken(state, TOKEN_LESS_EQUAL)
: makeToken(state, TOKEN_LESS);
// literals
case '"': return parseString(state);
case '"':
return parseString(state);
default:
if (isNumerical(c))
return parseNumber(state);

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,30 +66,34 @@ 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
@ -98,8 +103,8 @@ typedef struct {
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);
@ -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,14 +114,17 @@ 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) {
// black = keep, white = discard
static void blackenObject(CState *state, CObj *obj)
{
markObject(state, (CObj *)obj->proto);
switch (obj->type) {
case COBJ_STRING:
@ -127,8 +165,9 @@ void blackenObject(CState *state, CObj *obj) {
markValue(state, err->err);
// mark callframes
for (int i = 0; i < err->frameCount; i++)
for (int i = 0; i < err->frameCount; i++) {
markObject(state, (CObj *)err->frames[i].closure);
}
break;
}
@ -151,7 +190,8 @@ void blackenObject(CState *state, CObj *obj) {
}
}
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;
@ -168,25 +208,29 @@ void markObject(CState *state, CObj *obj) {
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];
blackenObject(state, obj);
}
}
void sweep(CState *state) {
static void sweep(CState *state)
{
CObj *prev = NULL;
CObj *object = state->objects;
while (object != NULL) {
@ -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);
@ -238,84 +285,45 @@ void markRoots(CState *state) {
markObject(state, (CObj *)state->globals);
// mark all internal strings
for (int i = 0; i < ISTRING_MAX; 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++)
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",
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
#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,7 +2,6 @@
#define CMEME_C // meme lol
#include "cosmo.h"
#include "cstate.h"
// #define GC_STRESS
@ -13,15 +12,16 @@
#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__); \
# 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) \
# define cosmoM_freeArray(state, type, buf, capacity) \
cosmoM_reallocate(state, buf, sizeof(type) * capacity, 0)
#endif
#define cosmoM_growarray(state, type, buf, count, capacity) \
#define cosmoM_growArray(state, type, buf, count, capacity) \
if (count >= capacity || buf == NULL) { \
int old = capacity; \
capacity = old * GROW_FACTOR; \
@ -33,13 +33,12 @@
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) \
@ -53,8 +52,7 @@
#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) \
@ -64,18 +62,14 @@
#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);
}

View File

@ -1,24 +1,29 @@
#include "cobj.h"
#include "clex.h"
#include "cmem.h"
#include "cstate.h"
#include "ctable.h"
#include "cobj.h"
#include "cmem.h"
#include "cvm.h"
#include "clex.h"
#include <stdarg.h>
#include <string.h>
// we don't actually hash the whole string :eyes:
uint32_t hashString(const char *str, size_t sz) {
uint32_t hashString(const char *str, size_t sz)
{
uint32_t hash = sz;
size_t step = (sz >> 5) + 1;
for (size_t i = sz; i >= step; i-=step)
for (size_t i = sz; i >= step; i -= step) {
hash = ((hash << 5) + (hash >> 2)) + str[i - 1];
}
return hash;
}
CObj *cosmoO_allocateBase(CState *state, size_t sz, CObjType type) {
CObj *cosmoO_allocateBase(CState *state, size_t sz, CObjType type)
{
CObj *obj = (CObj *)cosmoM_xmalloc(state, sz);
obj->type = type;
obj->isMarked = false;
@ -27,30 +32,28 @@ CObj *cosmoO_allocateBase(CState *state, size_t sz, CObjType type) {
obj->next = state->objects;
state->objects = obj;
obj->nextRoot = NULL;
#ifdef GC_DEBUG
printf("allocated %p with OBJ_TYPE %d\n", obj, type);
printf("allocated %s %p\n", cosmoO_typeStr(obj), obj);
#endif
return obj;
}
void cosmoO_free(CState *state, CObj* obj) {
void cosmoO_free(CState *state, CObj *obj)
{
#ifdef GC_DEBUG
printf("freeing %p [", obj);
printObject(obj);
printf("]\n");
printf("freeing %s %p\n", cosmoO_typeStr(obj), obj);
#endif
switch (obj->type) {
case COBJ_STRING: {
CObjString *objStr = (CObjString *)obj;
cosmoM_freearray(state, char, objStr->str, objStr->length + 1);
cosmoM_freeArray(state, char, objStr->str, objStr->length + 1);
cosmoM_free(state, CObjString, objStr);
break;
}
case COBJ_OBJECT: {
CObjObject *objTbl = (CObjObject*)obj;
cosmoT_clearTable(state, &objTbl->tbl);
cosmoM_free(state, CObjObject, objTbl);
CObjObject *objObj = (CObjObject *)obj;
cosmoT_clearTable(state, &objObj->tbl);
cosmoM_free(state, CObjObject, objObj);
break;
}
case COBJ_TABLE: {
@ -79,51 +82,119 @@ void cosmoO_free(CState *state, CObj* obj) {
}
case COBJ_ERROR: {
CObjError *err = (CObjError *)obj;
cosmoM_freearray(state, CCallFrame, err->frames, err->frameCount);
cosmoM_freeArray(state, CCallFrame, err->frames, err->frameCount);
cosmoM_free(state, CObjError, obj);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure *)obj;
cosmoM_freearray(state, CObjUpval*, closure->upvalues, closure->upvalueCount);
cosmoM_freeArray(state, CObjUpval *, closure->upvalues, closure->upvalueCount);
cosmoM_free(state, CObjClosure, closure);
break;
}
case COBJ_MAX: { /* stubbed, should never happen */ }
case COBJ_MAX:
default: { /* stubbed, should never happen */
}
}
}
bool cosmoO_equal(CObj* obj1, CObj* obj2) {
bool cosmoO_equal(CState *state, CObj *obj1, CObj *obj2)
{
CObjObject *proto1, *proto2;
CValue eq1, eq2;
if (obj1 == obj2) // its the same reference, this compares strings for us since they're interned
// anyways :)
return true;
// its not the same type, maybe both <ref>'s have the same '__equal' metamethod in their protos?
if (obj1->type != obj2->type)
return false;
goto _eqFail;
switch (obj1->type) {
case COBJ_STRING:
return obj1 == obj2; // compare pointers because we already intern all strings :)
case COBJ_STRING: {
/*
we already compared the pointers at the top of the function, this prevents the `__equal`
metamethod from being checked. If you plan on using `__equal` with strings just remove
this case!
*/
return false;
}
case COBJ_CFUNCTION: {
CObjCFunction *cfunc1 = (CObjCFunction *)obj1;
CObjCFunction *cfunc2 = (CObjCFunction *)obj2;
return cfunc1->cfunc == cfunc2->cfunc;
if (cfunc1->cfunc == cfunc2->cfunc)
return true;
goto _eqFail;
}
case COBJ_METHOD: {
CObjMethod *method1 = (CObjMethod *)obj1;
CObjMethod *method2 = (CObjMethod *)obj2;
if (cosmoV_equal(state, method1->func, method2->func))
return true;
goto _eqFail;
}
case COBJ_CLOSURE: {
CObjClosure *closure1 = (CObjClosure *)obj1;
CObjClosure *closure2 = (CObjClosure *)obj2;
// we just compare the function pointer
if (closure1->function == closure2->function)
return true;
goto _eqFail;
}
default:
return false;
}
goto _eqFail;
}
CObjObject *cosmoO_newObject(CState *state) {
_eqFail:
// this is pretty expensive (bad lookup caching helps a lot), but it only all gets run if both
// objects have protos & both have the `__equal` metamethod defined so... it should stay light
// for the majority of cases
if ((proto1 = cosmoO_grabProto(obj1)) != NULL &&
(proto2 = cosmoO_grabProto(obj2)) != NULL && // make sure both protos exist
cosmoO_getIString(
state, proto1, ISTRING_EQUAL,
&eq1) && // grab the `__equal` metamethod from the first proto, if fail abort
cosmoO_getIString(
state, proto2, ISTRING_EQUAL,
&eq2) && // grab the `__equal` metamethod from the second proto, if fail abort
cosmoV_equal(state, eq1, eq2)) { // compare the two `__equal` metamethods
// now finally, call the `__equal` metamethod (<object>, <object>)
cosmoV_pushValue(state, eq1);
cosmoV_pushRef(state, obj1);
cosmoV_pushRef(state, obj2);
cosmoV_call(state, 2, 1);
// check return value and make sure it's a boolean
if (!IS_BOOLEAN(*cosmoV_getTop(state, 0))) {
cosmoV_error(state, "__equal expected to return <boolean>, got %s!",
cosmoV_typeStr(*cosmoV_pop(state)));
}
// return the result
return cosmoV_readBoolean(*cosmoV_pop(state));
}
return false;
}
CObjObject *cosmoO_newObject(CState *state)
{
CObjObject *obj = (CObjObject *)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT);
obj->istringFlags = 0;
obj->userP = NULL; // reserved for C API
obj->userT = 0;
obj->isLocked = false;
cosmoV_pushRef(state, (CObj *)obj); // so our GC can keep track of it
cosmoT_initTable(state, &obj->tbl, ARRAY_START);
cosmoV_pop(state);
return obj;
}
CObjTable *cosmoO_newTable(CState *state) {
CObjTable *cosmoO_newTable(CState *state)
{
CObjTable *obj = (CObjTable *)cosmoO_allocateBase(state, sizeof(CObjTable), COBJ_TABLE);
// init the table (might cause a GC event)
@ -134,8 +205,10 @@ CObjTable *cosmoO_newTable(CState *state) {
return obj;
}
CObjFunction *cosmoO_newFunction(CState *state) {
CObjFunction *func = (CObjFunction*)cosmoO_allocateBase(state, sizeof(CObjFunction), COBJ_FUNCTION);
CObjFunction *cosmoO_newFunction(CState *state)
{
CObjFunction *func =
(CObjFunction *)cosmoO_allocateBase(state, sizeof(CObjFunction), COBJ_FUNCTION);
func->args = 0;
func->upvals = 0;
func->variadic = false;
@ -146,36 +219,41 @@ CObjFunction *cosmoO_newFunction(CState *state) {
return func;
}
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) {
CObjCFunction *cfunc = (CObjCFunction*)cosmoO_allocateBase(state, sizeof(CObjCFunction), COBJ_CFUNCTION);
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func)
{
CObjCFunction *cfunc =
(CObjCFunction *)cosmoO_allocateBase(state, sizeof(CObjCFunction), COBJ_CFUNCTION);
cfunc->cfunc = func;
return cfunc;
}
CObjError *cosmoO_newError(CState *state, CValue err) {
CObjError *cosmoO_newError(CState *state, CValue err)
{
CCallFrame *frames = cosmoM_xmalloc(state, sizeof(CCallFrame) * state->frameCount);
CObjError *cerror = (CObjError *)cosmoO_allocateBase(state, sizeof(CObjError), COBJ_ERROR);
cerror->err = err;
cerror->frameCount = state->frameCount;
cerror->frames = frames;
cerror->parserError = false;
// allocate the callframe
cerror->frames = cosmoM_xmalloc(state, sizeof(CCallFrame) * cerror->frameCount);
// clone the call frame
for (int i = 0; i < state->frameCount; i++)
for (int i = 0; i < state->frameCount; i++) {
cerror->frames[i] = state->callFrame[i];
}
return cerror;
}
CObjMethod *cosmoO_newMethod(CState *state, CValue func, CObj *obj) {
CObjMethod *cosmoO_newMethod(CState *state, CValue func, CObj *obj)
{
CObjMethod *method = (CObjMethod *)cosmoO_allocateBase(state, sizeof(CObjMethod), COBJ_METHOD);
method->func = func;
method->obj = obj;
return method;
}
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) {
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func)
{
// initialize array of pointers
CObjUpval **upvalues = cosmoM_xmalloc(state, sizeof(CObjUpval *) * func->upvals);
@ -183,7 +261,8 @@ CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) {
upvalues[i] = NULL;
}
CObjClosure *closure = (CObjClosure*)cosmoO_allocateBase(state, sizeof(CObjClosure), COBJ_CLOSURE);
CObjClosure *closure =
(CObjClosure *)cosmoO_allocateBase(state, sizeof(CObjClosure), COBJ_CLOSURE);
closure->function = func;
closure->upvalues = upvalues;
closure->upvalueCount = func->upvals;
@ -191,7 +270,8 @@ CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) {
return closure;
}
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val) {
CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val)
{
CObjUpval *upval = (CObjUpval *)cosmoO_allocateBase(state, sizeof(CObjUpval), COBJ_UPVALUE);
upval->val = val;
upval->closed = cosmoV_newNil();
@ -200,7 +280,8 @@ CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val) {
return upval;
}
CObjString *cosmoO_copyString(CState *state, const char *str, size_t length) {
CObjString *cosmoO_copyString(CState *state, const char *str, size_t length)
{
uint32_t hash = hashString(str, length);
CObjString *lookup = cosmoT_lookupString(&state->strings, str, length, hash);
@ -215,29 +296,33 @@ CObjString *cosmoO_copyString(CState *state, const char *str, size_t length) {
return cosmoO_allocateString(state, buf, length, hash);
}
// 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) {
// 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)
{
uint32_t hash = hashString(str, length);
CObjString *lookup = cosmoT_lookupString(&state->strings, str, length, hash);
// have we already interned this string?
if (lookup != NULL) {
cosmoM_freearray(state, char, str, length + 1); // free our passed character array, it's unneeded!
cosmoM_freeArray(state, char, str,
length + 1); // free our passed character array, it's unneeded!
return lookup;
}
return cosmoO_allocateString(state, str, length, hash);
}
CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uint32_t hash) {
CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uint32_t hash)
{
CObjString *strObj = (CObjString *)cosmoO_allocateBase(state, sizeof(CObjString), COBJ_STRING);
strObj->isIString = false;
strObj->str = (char *)str;
strObj->length = sz;
strObj->hash = hash;
// we push & pop the string so our GC can find it (we don't use freezeGC/unfreezeGC because we *want* a GC event to happen)
// push/pop to make sure GC doesn't collect it
cosmoV_pushRef(state, (CObj *)strObj);
cosmoT_insert(state, &state->strings, cosmoV_newRef((CObj *)strObj));
cosmoV_pop(state);
@ -245,7 +330,8 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uin
return strObj;
}
CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args) {
CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args)
{
StkPtr start = state->top;
const char *end;
char c;
@ -290,12 +376,14 @@ CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args)
}
cosmoV_pushString(state, format); // push the rest of the string
cosmoV_concat(state, state->top - start); // use cosmoV_concat to concat all the strings on the stack
cosmoV_concat(state,
state->top - start); // use cosmoV_concat to concat all the strings on the stack
return cosmoV_readString(*start); // start should be state->top - 1
}
// walks the protos of obj and checks for proto
bool cosmoO_isDescendant(CObj *obj, CObjObject *proto) {
bool cosmoO_isDescendant(CObj *obj, CObjObject *proto)
{
CObjObject *curr = obj->proto;
while (curr != NULL) {
@ -310,28 +398,31 @@ bool cosmoO_isDescendant(CObj *obj, CObjObject *proto) {
}
// returns false if error thrown
bool cosmoO_getRawObject(CState *state, CObjObject *proto, CValue key, CValue *val, CObj *obj) {
if (!cosmoT_get(&proto->tbl, key, val)) { // if the field doesn't exist in the object, check the proto
if (cosmoO_getIString(state, proto, ISTRING_GETTER, val) && IS_TABLE(*val) && cosmoT_get(&cosmoV_readTable(*val)->tbl, key, val)) {
void cosmoO_getRawObject(CState *state, CObjObject *proto, CValue key, CValue *val, CObj *obj)
{
if (!cosmoT_get(state, &proto->tbl, key,
val)) { // if the field doesn't exist in the object, check the proto
if (cosmoO_getIString(state, proto, ISTRING_GETTER, val) && IS_TABLE(*val) &&
cosmoT_get(state, &cosmoV_readTable(*val)->tbl, key, val)) {
cosmoV_pushValue(state, *val); // push function
cosmoV_pushRef(state, (CObj *)obj); // push object
if (cosmoV_call(state, 1, 1) != COSMOVM_OK) // call the function with the 1 argument
return false;
cosmoV_call(state, 1, 1); // call the function with the 1 argument
*val = *cosmoV_pop(state); // set value to the return value of __index
return true;
return;
}
if (proto->_obj.proto != NULL && cosmoO_getRawObject(state, proto->_obj.proto, key, val, obj))
return true;
// maybe the field is defined in the proto?
if (proto->_obj.proto != NULL) {
cosmoO_getRawObject(state, proto->_obj.proto, key, val, obj);
return;
}
*val = cosmoV_newNil();
return true; // no protoobject to check against / key not found
}
}
return true;
}
void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue val, CObj *obj) {
void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue val, CObj *obj)
{
CValue ret;
// if the object is locked, throw an error
@ -341,7 +432,8 @@ void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue va
}
// check for __setters
if (cosmoO_getIString(state, proto, ISTRING_SETTER, &ret) && IS_TABLE(ret) && cosmoT_get(&cosmoV_readTable(ret)->tbl, key, &ret)) {
if (cosmoO_getIString(state, proto, ISTRING_SETTER, &ret) && IS_TABLE(ret) &&
cosmoT_get(state, &cosmoV_readTable(ret)->tbl, key, &ret)) {
cosmoV_pushValue(state, ret); // push function
cosmoV_pushRef(state, (CObj *)obj); // push object
cosmoV_pushValue(state, val); // push new value
@ -361,43 +453,53 @@ void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue va
}
}
void cosmoO_setUserP(CObjObject *object, void *p) {
void cosmoO_setUserP(CObjObject *object, void *p)
{
object->userP = p;
}
void *cosmoO_getUserP(CObjObject *object) {
void *cosmoO_getUserP(CObjObject *object)
{
return object->userP;
}
void cosmoO_setUserI(CObjObject *object, int i) {
void cosmoO_setUserI(CObjObject *object, int i)
{
object->userI = i;
}
int cosmoO_getUserI(CObjObject *object) {
int cosmoO_getUserI(CObjObject *object)
{
return object->userI;
}
void cosmoO_setUserT(CObjObject *object, int t) {
void cosmoO_setUserT(CObjObject *object, int t)
{
object->userT = t;
}
int cosmoO_getUserT(CObjObject *object) {
int cosmoO_getUserT(CObjObject *object)
{
return object->userT;
}
void cosmoO_lock(CObjObject *object) {
void cosmoO_lock(CObjObject *object)
{
object->isLocked = true;
}
void cosmoO_unlock(CObjObject *object) {
void cosmoO_unlock(CObjObject *object)
{
object->isLocked = false;
}
bool rawgetIString(CState *state, CObjObject *object, int flag, CValue *val) {
bool rawgetIString(CState *state, CObjObject *object, int flag, CValue *val)
{
if (readFlag(object->istringFlags, flag))
return false; // it's been cached as bad
if (!cosmoT_get(&object->tbl, cosmoV_newRef(state->iStrings[flag]), val)) {
if (!cosmoT_get(state, &object->tbl, cosmoV_newRef(state->iStrings[flag]), val)) {
// mark it bad!
setFlagOn(object->istringFlags, flag);
return false;
@ -406,7 +508,8 @@ bool rawgetIString(CState *state, CObjObject *object, int flag, CValue *val) {
return true; // :)
}
bool cosmoO_getIString(CState *state, CObjObject *object, int flag, CValue *val) {
bool cosmoO_getIString(CState *state, CObjObject *object, int flag, CValue *val)
{
CObjObject *obj = object;
do {
@ -417,23 +520,21 @@ bool cosmoO_getIString(CState *state, CObjObject *object, int flag, CValue *val)
return false; // obj->proto was false, the istring doesn't exist in this object chain
}
bool cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *val) {
void cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *val)
{
if (cosmoO_getIString(state, object, ISTRING_INDEX, val)) {
cosmoV_pushValue(state, *val); // push function
cosmoV_pushRef(state, (CObj *)object); // push object
cosmoV_pushValue(state, key); // push key
if (cosmoV_call(state, 2, 1) != COSMOVM_OK) // call the function with the 2 arguments
return false;
cosmoV_call(state, 2, 1); // call the function with the 2 arguments
*val = *cosmoV_pop(state); // set value to the return value of __index
return true;
} else { // there's no __index function defined!
cosmoV_error(state, "Couldn't index object without __index function!");
}
return false;
}
bool cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue val) {
void cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue val)
{
CValue ret; // return value for cosmoO_getIString
if (cosmoO_getIString(state, object, ISTRING_NEWINDEX, &ret)) {
@ -441,15 +542,14 @@ bool cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue
cosmoV_pushRef(state, (CObj *)object); // push object
cosmoV_pushValue(state, key); // push key & value pair
cosmoV_pushValue(state, val);
return cosmoV_call(state, 3, 0) == COSMOVM_OK;
cosmoV_call(state, 3, 0);
} else { // there's no __newindex function defined
cosmoV_error(state, "Couldn't set index on object without __newindex function!");
}
return false;
}
CObjString *cosmoO_toString(CState *state, CObj *obj) {
CObjString *cosmoO_toString(CState *state, CObj *obj)
{
CObjObject *protoObject = cosmoO_grabProto(obj);
CValue res;
@ -457,13 +557,13 @@ CObjString *cosmoO_toString(CState *state, CObj *obj) {
if (protoObject != NULL && cosmoO_getIString(state, protoObject, ISTRING_TOSTRING, &res)) {
cosmoV_pushValue(state, res);
cosmoV_pushRef(state, (CObj *)obj);
if (cosmoV_call(state, 1, 1) != COSMOVM_OK)
return cosmoO_copyString(state, "<err>", 5);
cosmoV_call(state, 1, 1);
// make sure the __tostring function returned a string
StkPtr ret = cosmoV_getTop(state, 0);
if (!IS_STRING(*ret)) {
cosmoV_error(state, "__tostring expected to return <string>, got %s!", cosmoV_typeStr(*ret));
cosmoV_error(state, "__tostring expected to return <string>, got %s!",
cosmoV_typeStr(*ret));
return cosmoO_copyString(state, "<err>", 5);
}
@ -482,12 +582,14 @@ CObjString *cosmoO_toString(CState *state, CObj *obj) {
}
case COBJ_FUNCTION: {
CObjFunction *func = (CObjFunction *)obj;
return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK));
return func->name != NULL ? func->name
: cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK));
}
case COBJ_CFUNCTION: {
CObjCFunction *cfunc = (CObjCFunction *)obj;
char buf[64];
int sz = sprintf(buf, "<c function> %p", (void*)cfunc->cfunc) + 1; // +1 for the null character
int sz =
sprintf(buf, "<c function> %p", (void *)cfunc->cfunc) + 1; // +1 for the null character
return cosmoO_copyString(state, buf, sz);
}
case COBJ_OBJECT: {
@ -512,20 +614,20 @@ CObjString *cosmoO_toString(CState *state, CObj *obj) {
}
}
cosmo_Number cosmoO_toNumber(CState *state, CObj *obj) {
cosmo_Number cosmoO_toNumber(CState *state, CObj *obj)
{
CObjObject *proto = cosmoO_grabProto(obj);
CValue res;
if (proto != NULL && cosmoO_getIString(state, proto, ISTRING_TONUMBER, &res)) {
cosmoV_pushValue(state, res);
cosmoV_pushRef(state, (CObj *)obj);
if (cosmoV_call(state, 1, 1) != COSMOVM_OK) // call res, expect 1 return val of <number>
return 0;
cosmoV_call(state, 1, 1); // call res, expect 1 return val of <number>
StkPtr temp = cosmoV_getTop(state, 0);
if (!IS_NUMBER(*temp)) {
cosmoV_error(state, "__tonumber expected to return <number>, got %s!", cosmoV_typeStr(*temp));
return 0;
cosmoV_error(state, "__tonumber expected to return <number>, got %s!",
cosmoV_typeStr(*temp));
}
// return number
@ -543,19 +645,20 @@ cosmo_Number cosmoO_toNumber(CState *state, CObj *obj) {
}
}
int cosmoO_count(CState *state, CObj *obj) {
int cosmoO_count(CState *state, CObj *obj)
{
CObjObject *proto = cosmoO_grabProto(obj);
CValue res;
if (proto != NULL && cosmoO_getIString(state, proto, ISTRING_COUNT, &res)) {
cosmoV_pushValue(state, res);
cosmoV_pushRef(state, (CObj *)obj);
if (cosmoV_call(state, 1, 1) != COSMOVM_OK) // call res, we expect 1 return value of type <number>
return 0;
cosmoV_call(state, 1, 1); // call res, we expect 1 return value of type <number>
StkPtr ret = cosmoV_getTop(state, 0);
if (!IS_NUMBER(*ret)) {
cosmoV_error(state, "__count expected to return <number>, got %s!", cosmoV_typeStr(*ret));
cosmoV_error(state, "__count expected to return <number>, got %s!",
cosmoV_typeStr(*ret));
return 0;
}
@ -579,74 +682,87 @@ int cosmoO_count(CState *state, CObj *obj) {
}
}
void printObject(CObj *o) {
void printObject(CObj *o)
{
printf("%s ", cosmoO_typeStr(o));
switch (o->type) {
case COBJ_STRING: {
CObjString *objStr = (CObjString *)o;
printf("%.*s", objStr->length, objStr->str);
printf("\"%.*s\"", objStr->length, objStr->str);
break;
}
case COBJ_OBJECT: {
printf("<obj> %p", (void*)o);
printf("%p", (void *)o);
break;
}
case COBJ_TABLE: {
CObjTable *tbl = (CObjTable *)o;
printf("<tbl> %p", (void*)tbl);
printf("%p", (void *)tbl);
break;
}
case COBJ_FUNCTION: {
CObjFunction *objFunc = (CObjFunction *)o;
if (objFunc->name != NULL)
printf("<function> %.*s", objFunc->name->length, objFunc->name->str);
printf("%.*s", objFunc->name->length, objFunc->name->str);
else
printf("<function> %s", UNNAMEDCHUNK);
printf("%s", UNNAMEDCHUNK);
break;
}
case COBJ_CFUNCTION: {
CObjCFunction *objCFunc = (CObjCFunction *)o;
printf("<c function> %p", (void*)objCFunc->cfunc);
printf("%p", (void *)objCFunc->cfunc);
break;
}
case COBJ_ERROR: {
CObjError *err = (CObjError *)o;
printf("<error> %p -> ", (void*)o);
printf("%p -> ", (void *)o);
printValue(err->err);
break;
}
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod *)o;
printf("<method> %p -> ", (void*)method);
printf("%p -> ", (void *)method);
printValue(method->func);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure *)o;
printf("<closure> %p -> ", (void*)closure);
printf("%p -> ", (void *)closure);
printObject((CObj *)closure->function); // just print the function
break;
}
case COBJ_UPVALUE: {
CObjUpval *upval = (CObjUpval *)o;
printf("<upvalue> %p -> ", (void*)upval->val);
printf("%p -> ", (void *)upval->val);
printValue(*upval->val);
break;
}
default:
printf("<unkn obj %p>", (void*)o);
printf("%p", (void *)o);
}
}
const char *cosmoO_typeStr(CObj* obj) {
const char *cosmoO_typeStr(CObj *obj)
{
switch (obj->type) {
case COBJ_STRING: return "<string>";
case COBJ_OBJECT: return "<object>";
case COBJ_TABLE: return "<table>";
case COBJ_FUNCTION: return "<function>";
case COBJ_CFUNCTION: return "<c function>";
case COBJ_METHOD: return "<method>";
case COBJ_CLOSURE: return "<closure>";
case COBJ_UPVALUE: return "<upvalue>";
case COBJ_STRING:
return "<string>";
case COBJ_OBJECT:
return "<object>";
case COBJ_TABLE:
return "<table>";
case COBJ_FUNCTION:
return "<function>";
case COBJ_CFUNCTION:
return "<c function>";
case COBJ_ERROR:
return "<error>";
case COBJ_METHOD:
return "<method>";
case COBJ_CLOSURE:
return "<closure>";
case COBJ_UPVALUE:
return "<upvalue>";
default:
return "<unkn obj>"; // TODO: maybe panic? could be a malformed object :eyes:

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,14 +20,10 @@ typedef enum CObjType {
COBJ_MAX
} CObjType;
#include "cstate.h"
#include "cchunk.h"
#include "cvalue.h"
#include "cstate.h"
#include "ctable.h"
typedef struct CState CState;
typedef struct CCallFrame CCallFrame;
typedef uint32_t cosmo_Flag;
#include "cvalue.h"
#define CommonHeader CObj _obj
#define readFlag(x, flag) (x & (1u << flag))
@ -32,49 +31,56 @@ typedef uint32_t cosmo_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 {
struct CObjString
{
CommonHeader; // "is a" CObj
char *str; // NULL termincated string
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
CCallFrame *frames;
int frameCount;
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,32 +88,36 @@ 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
@ -120,26 +130,30 @@ typedef struct CObjUpval {
#define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE)
#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_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);
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) {
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

View File

@ -5,7 +5,8 @@
// 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]]
@ -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 <assert.h>
#include <stdlib.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
/*
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 SAFE_STACK
// #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
// 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,49 +89,59 @@ 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);
cosmoT_clearTable(state, &state->registry);
// 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);
// 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);
@ -119,7 +153,50 @@ void cosmoV_register(CState *state, int pairs) {
}
}
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,21 +1,27 @@
#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;
};
typedef enum IStringEnum {
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
@ -24,42 +30,67 @@ typedef enum IStringEnum {
ISTRING_ITER, // __iter
ISTRING_NEXT, // __next
ISTRING_RESERVED, // __reserved
ISTRING_MAX
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
CObjUpval *openUpvalues; // tracks all of our still open (meaning still on the stack) upvalues
CTable strings;
CObjTable *globals;
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
struct CState
{
CCallFrame callFrame[FRAME_MAX]; // call frames
CValue stack[STACK_MAX]; // stack
} CState;
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
CObjTable *globals;
CValue *top; // top of the stack
CObj *objects; // tracks all of our allocated objects
CPanic *panic;
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,7 +1,8 @@
#include "ctable.h"
#include "cmem.h"
#include "cvalue.h"
#include "cobj.h"
#include "cvalue.h"
#include <string.h>
@ -10,12 +11,15 @@
#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,23 +45,27 @@ 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) {
static uint32_t getObjectHash(CObj *obj)
{
switch (obj->type) {
case COBJ_STRING:
return ((CObjString *)obj)->hash;
@ -64,7 +74,8 @@ uint32_t getObjectHash(CObj *obj) {
}
}
uint32_t getValueHash(CValue *val) {
static uint32_t getValueHash(CValue *val)
{
switch (GET_TYPE(*val)) {
case COSMO_TREF:
return getObjectHash(cosmoV_readRef(*val));
@ -76,7 +87,9 @@ uint32_t getValueHash(CValue *val) {
return 0;
memcpy(buf, &num, sizeof(buf));
for (size_t i = 0; i < sizeof(cosmo_Number)/sizeof(uint32_t); i++) buf[0] += buf[i];
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
@ -86,9 +99,11 @@ uint32_t getValueHash(CValue *val) {
}
// 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)) {
} else if (cosmoV_equal(state, entry->key, key)) {
return entry;
}
@ -112,7 +127,8 @@ 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;
@ -122,11 +138,12 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
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) {
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
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,7 +268,8 @@ 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);
}
@ -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,40 +1,50 @@
#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;
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];
@ -42,7 +52,8 @@ CObjString *cosmoV_toString(CState *state, CValue 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);
return cosmoV_readBoolean(val) ? cosmoO_copyString(state, "true", 4)
: cosmoO_copyString(state, "false", 5);
}
case COSMO_TREF: {
return cosmoO_toString(state, cosmoV_readRef(val));
@ -55,7 +66,8 @@ CObjString *cosmoV_toString(CState *state, CValue val) {
}
}
cosmo_Number cosmoV_toNumber(CState *state, CValue val) {
cosmo_Number cosmoV_toNumber(CState *state, CValue val)
{
switch (GET_TYPE(val)) {
case COSMO_TNUMBER: {
return cosmoV_readNumber(val);
@ -72,19 +84,25 @@ cosmo_Number cosmoV_toNumber(CState *state, CValue val) {
}
}
const char *cosmoV_typeStr(CValue val) {
const char *cosmoV_typeStr(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));
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) {
void printValue(CValue val)
{
switch (GET_TYPE(val)) {
case COSMO_TNUMBER:
printf("%g", cosmoV_readNumber(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,10 +28,11 @@ 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)
@ -43,7 +45,8 @@ typedef union CValue {
# define MASK_QUIETNAN ((uint64_t)0x7ff8000000000000)
# define GET_TYPE(x) \
((((x).data & MASK_QUIETNAN) == MASK_QUIETNAN) ? (((x).data & MASK_TYPE) >> 48) : COSMO_TNUMBER)
((((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))
@ -68,14 +71,16 @@ typedef union CValue {
/*
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)
@ -103,20 +108,21 @@ typedef struct CValue {
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

876
src/cvm.c

File diff suppressed because it is too large Load Diff

112
src/cvm.h
View File

@ -1,22 +1,32 @@
#ifndef COSMOVM_H
#define COSMOVM_H
#include <string.h>
#include "cosmo.h"
#include "cstate.h"
#include <string.h>
// #define VM_DEBUG
typedef enum {
COSMOVM_OK,
COSMOVM_RUNTIME_ERR,
COSMOVM_BUILDTIME_ERR
} COSMOVMRESULT;
/*
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);
@ -24,15 +34,21 @@ 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.
loads a <closure> from a dump. if successful, <closure> will be pushed onto the stack
otherwise the <error> will be pushed.
if returns false an error was thrown, if returns true the value was pushed onto the stack and the object and key were popped
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);
/*
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 bool cosmoV_getMethod(CState *state, CObj *obj, CValue key, CValue *val);
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) {
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) {
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 */