Merge branch 'master' into struct-proto

This commit is contained in:
Calvin Rose 2021-10-29 16:42:34 -05:00
commit 03f99752a7
105 changed files with 5141 additions and 6403 deletions

View File

@ -9,4 +9,3 @@ tasks:
gmake
gmake test
sudo gmake install
gmake test-install

View File

@ -19,5 +19,3 @@ tasks:
ninja
ninja test
sudo ninja install
sudo jpm --verbose install circlet
sudo jpm --verbose install spork

View File

@ -29,5 +29,4 @@ tasks:
ninja
ninja test
doas ninja install
doas jpm --verbose install circlet

35
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
release:
name: Build release binaries
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Set the version
run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: Set the platform
run: echo "platform=$(tr '[A-Z]' '[a-z]' <<< $RUNNER_OS)" >> $GITHUB_ENV
- name: Compile the project
run: make clean && make
- name: Build the artifact
run: JANET_DIST_DIR=janet-${{ env.version }}-${{ env.platform }} make build/janet-${{ env.version }}-${{ env.platform }}-x64.tar.gz
- name: Draft the release
uses: softprops/action-gh-release@v1
with:
draft: true
files: |
build/*.gz
build/janet.h
build/c/janet.c
build/c/shell.c

34
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Test
on: [push, pull_request]
jobs:
test-posix:
name: Build and test on POSIX systems
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest ]
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Compile the project
run: make clean && make
- name: Test the project
run: make test
test-windows:
name: Build and test on Windows
runs-on: windows-latest
steps:
- name: Checkout the repository
uses: actions/checkout@master
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build the project
shell: cmd
run: build_win
- name: Test the project
shell: cmd
run: build_win test

3
.gitignore vendored
View File

@ -32,8 +32,9 @@ lockfile.janet
# Local directory for testing
local
# Common test file I use.
# Common test files I use.
temp.janet
scratch.janet
# Emscripten
*.bc

View File

@ -1,25 +0,0 @@
language: c
script:
- make
- make test
- sudo make install
- make test-install
- JANET_DIST_DIR=janet-${TRAVIS_TAG}-${TRAVIS_OS_NAME} make build/janet-${TRAVIS_TAG}-${TRAVIS_OS_NAME}.tar.gz
compiler:
- clang
- gcc
os:
- linux
- osx
before_deploy:
deploy:
provider: releases
api_key:
secure: JSqAOTH1jmfVlbOuPO3BbY1BhPq+ddiBNPCxuAyKHoVwfO4eNAmq9COI+UwCMWY3dg+YlspufRwkHj//B7QQ6hPbSsKu+Mapu6gr/CAE/jxbfO/E98LkIkUwbGjplwtzw2kiBkHN/Bu6J5X76cwo4D8nwQ1JIcV3nWtoG87t7H4W0R4AYQkbLGAPylgUFr11YMPx2cRBBqCdLAGIrny7kQ/0cRBfkN81R/gUJv/q3OjmUvY7sALXp7mFdZb75QPSilKIDuVUU5hLvPYTeRl6cWI/M+m5SmGZx1rjv5S9Qaw070XoNyt9JAADtbOUnADKvDguDZIP1FCuT1Gb+cnJPzrvk6+OBU9s8UjCTFtgV+LKlhmRZcwV5YQBE94PKRMJNC6VvIWM7UeQ8Zhm1jmQS6ONNWbuoUAlkZP57NtDQa2x0GT2wkubNSQKlaY+6/gwTD9KAJIzaZG7HYXH7b+4g7VbccCyhDAtDZtXgrOIS4WAkNc8rWezRO4H0qHMyON9aCEb0eTE8hWIufbx6ymG4gUxnYO+AkrEYMCwQvU6lS8BsevkaMTVtSShqlQtJ9FRlmJA3MA2ONyqzQXJENqRydyVbpFrKSv+0HbMyhEc5BoKbt0QcTh/slouNV4eASNar/GKN7aP8XKGUeMwIoCcRpP+3ehmwX9SUw7Ah5S42pA=
file: build/janet-${TRAVIS_TAG}-${TRAVIS_OS_NAME}.tar.gz
draft: true
skip_cleanup: true
on:
tags: true
repo: janet-lang/janet
condition: "$CC = clang"

View File

@ -2,9 +2,74 @@
All notable changes to this project will be documented in this file.
## ??? - Unreleased
- Deadlocked channels will no longer exit early - instead they will hang, which is more intuitive.
## 1.18.1 - 2021-10-16
- Fix some documentation typos
- Fix - Set pipes passed to subprocess to blocking mode.
- Fix `-r` switch in repl.
## 1.18.0 - 2021-10-10
- Allow `ev/cancel` to work on already scheduled fibers.
- Fix bugs with ev/ module.
- Add optional `base` argument to scan-number
- Add `-i` flag to janet binary to make it easier to run image files from the command line
- Remove `thread/` module.
- Add `(number ...)` pattern to peg for more efficient number parsing using Janet's
scan-number function without immediate string creation.
## 1.17.2 - 2021-09-18
- Remove include of windows.h from janet.h. This caused issues on certain projects.
- Fix formatting in doc-format to better handle special characters in signatures.
- Fix some marshalling bugs.
- Add optional Makefile target to install jpm as well.
- Supervisor channels in threads will no longer include a wasteful copy of the fiber in every
message across a thread.
- Allow passing a closure to `ev/thread` as well as a whole fiber.
- Allow passing a closure directly to `ev/go` to spawn fibers on the event loop.
## 1.17.1 - 2021-08-29
- Fix docstring typos
- Add `make install-jpm-git` to make jpm co-install simpler if using the Makefile.
- Fix bugs with starting ev/threads and fiber marshaling.
## 1.17.0 - 2021-08-21
- Add the `-E` flag for one-liners with the `short-fn` syntax for argument passing.
- Add support for threaded abstract types. Threaded abstract types can easily be shared between threads.
- Deprecate the `thread` library. Use threaded channels and ev instead.
- Channels can now be marshalled.
- Add the ability to close channels with `ev/chan-close` (or `:close`).
- Add threaded channels with `ev/thread-chan`.
- Add `JANET_FN` and `JANET_REG` macros to more easily define C functions that export their source mapping information.
- Add `janet_interpreter_interupt` and `janet_loop1_interrupt` to interrupt the interpreter while running.
- Add `table/clear`
- Add build option to disable the threading library without disabling all threads.
- Remove JPM from the main Janet distribution. Instead, JPM must be installed
separately like any other package.
- Fix issue with `ev/go` when called with an initial value and supervisor.
- Add the C API functions `janet_vm_save` and `janet_vm_load` to allow
saving and restoring the entire VM state.
## 1.16.1 - 2021-06-09
- Add `maclintf` - a utility for adding linting messages when inside macros.
- Print source code of offending line on compiler warnings and errors.
- Fix some issues with linting and re-add missing `make docs`.
- Allow controlling linting with dynamic bindings `:lint-warn`, `:lint-error`, and `:lint-levels`.
- Add `-w` and `-x` command line flags to the `janet` binary to set linting thresholds.
linting thresholds are as follows:
- :none - will never be trigger.
- :relaxed - will only trigger on `:relaxed` lints.
- :normal - will trigger on `:relaxed` and `:normal` lints.
- :strict - will trigger on `:strict`, `:normal`, and `:relaxed` lints. This will catch the most issues
but can be distracting.
## 1.16.0 - 2021-05-30
- Add color documentation to the `doc` macro - enable/disable with `(dyn :doc-color)`.
- Remove simpler HTML docs from distribution - use website or built-in documentation instead.
- Add compiler warnings and deprecation levels.
- Add `as-macro` to make using macros within quasiquote easier to do hygienically.
- Expose `JANET_OUT_OF_MEMORY` as part of the Janet API.
- Add `native-deps` option to `decalre-native` in `jpm`. This lets native libraries link to other
- Add `native-deps` option to `declare-native` in `jpm`. This lets native libraries link to other
native libraries when building with jpm.
- Remove the `tarray` module. The functionality of typed arrays will be moved to an external module
that can be installed via `jpm`.

View File

@ -1,4 +1,4 @@
Copyright (c) 2020 Calvin Rose and contributors
Copyright (c) 2021 Calvin Rose and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -36,6 +36,7 @@ JANET_PATH?=$(LIBDIR)/janet
JANET_MANPATH?=$(PREFIX)/share/man/man1/
JANET_PKG_CONFIG_PATH?=$(LIBDIR)/pkgconfig
JANET_DIST_DIR?=janet-dist
JPM_TAG?=master
DEBUGGER=gdb
SONAME_SETTER=-Wl,-soname,
@ -61,11 +62,18 @@ ifeq ($(UNAME), Darwin)
else ifeq ($(UNAME), Linux)
CLIBS:=$(CLIBS) -lrt -ldl
endif
# For other unix likes, add flags here!
ifeq ($(UNAME), Haiku)
LDCONFIG:=true
LDFLAGS=-Wl,--export-dynamic
endif
# For Android (termux)
ifeq ($(UNAME), Linux) # uname on Darwin doesn't recognise -o
ifeq ($(shell uname -o), Android)
CLIBS:=$(CLIBS) -landroid-spawn
endif
endif
$(shell mkdir -p build/core build/c build/boot)
all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h
@ -113,12 +121,12 @@ JANET_CORE_SOURCES=src/core/abstract.c \
src/core/regalloc.c \
src/core/run.c \
src/core/specials.c \
src/core/state.c \
src/core/string.c \
src/core/strtod.c \
src/core/struct.c \
src/core/symcache.c \
src/core/table.c \
src/core/thread.c \
src/core/tuple.c \
src/core/util.c \
src/core/value.c \
@ -157,7 +165,7 @@ build/c/janet.c: build/janet_boot src/boot/boot.janet
##### Amalgamation #####
########################
SONAME=libjanet.so.1.16
SONAME=libjanet.so.1.18
build/c/shell.c: src/mainclient/shell.c
cp $< $@
@ -205,12 +213,10 @@ valgrind: $(JANET_TARGET)
test: $(JANET_TARGET) $(TEST_PROGRAMS)
for f in test/suite*.janet; do ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done
./$(JANET_TARGET) -k jpm
valtest: $(JANET_TARGET) $(TEST_PROGRAMS)
for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done
$(VALGRIND_COMMAND) ./$(JANET_TARGET) -k jpm
callgrind: $(JANET_TARGET)
for f in test/suite*.janet; do valgrind --tool=callgrind ./$(JANET_TARGET) "$$f" || exit; done
@ -223,30 +229,33 @@ dist: build/janet-dist.tar.gz
build/janet-%.tar.gz: $(JANET_TARGET) \
build/janet.h \
jpm.1 janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) \
README.md build/c/janet.c build/c/shell.c jpm
janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) \
README.md build/c/janet.c build/c/shell.c
mkdir -p build/$(JANET_DIST_DIR)/bin
cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/
cp jpm build/$(JANET_DIST_DIR)/bin/
mkdir -p build/$(JANET_DIST_DIR)/include
cp build/janet.h build/$(JANET_DIST_DIR)/include/
mkdir -p build/$(JANET_DIST_DIR)/lib/
cp $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/$(JANET_DIST_DIR)/lib/
mkdir -p build/$(JANET_DIST_DIR)/man/man1/
cp janet.1 jpm.1 build/$(JANET_DIST_DIR)/man/man1/
mkdir -p build/$(JANET_DIST_DIR)/src/
cp build/c/janet.c build/c/shell.c build/$(JANET_DIST_DIR)/src/
cp CONTRIBUTING.md LICENSE README.md build/$(JANET_DIST_DIR)/
cd build && tar -czvf ../$@ ./$(JANET_DIST_DIR)
#########################
##### Documentation #####
#########################
docs: build/doc.html
build/doc.html: $(JANET_TARGET) tools/gendoc.janet
$(JANET_TARGET) tools/gendoc.janet > build/doc.html
########################
##### Installation #####
########################
build/jpm: jpm $(JANET_TARGET)
$(JANET_TARGET) tools/patch-jpm.janet jpm build/jpm "--libpath=$(LIBDIR)" "--headerpath=$(INCLUDEDIR)/janet" "--binpath=$(BINDIR)"
chmod +x build/jpm
.INTERMEDIATE: build/janet.pc
build/janet.pc: $(JANET_TARGET)
echo 'prefix=$(PREFIX)' > $@
@ -262,7 +271,7 @@ build/janet.pc: $(JANET_TARGET)
echo 'Libs: -L$${libdir} -ljanet' >> $@
echo 'Libs.private: $(CLIBS)' >> $@
install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/jpm build/janet.h
install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h
mkdir -p '$(DESTDIR)$(BINDIR)'
cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet'
mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet'
@ -273,22 +282,30 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc
cp $(JANET_STATIC_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.a'
ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so'
ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME)
cp -rf build/jpm '$(DESTDIR)$(BINDIR)'
mkdir -p '$(DESTDIR)$(JANET_MANPATH)'
cp janet.1 '$(DESTDIR)$(JANET_MANPATH)'
cp jpm.1 '$(DESTDIR)$(JANET_MANPATH)'
mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)'
cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc'
[ -z '$(DESTDIR)' ] && $(LDCONFIG) || true
install-jpm-git: $(JANET_TARGET)
mkdir -p build
rm -rf build/jpm
git clone --depth=1 --branch='$(JPM_TAG)' https://github.com/janet-lang/jpm.git build/jpm
cd build/jpm && PREFIX='$(PREFIX)' \
DESTDIR=$(DESTDIR) \
JANET_MANPATH='$(JANET_MANPATH)' \
JANET_HEADERPATH='$(INCLUDEDIR)/janet' \
JANET_BINPATH='$(BINDIR)' \
JANET_LIBPATH='$(LIBDIR)' \
../../$(JANET_TARGET) ./bootstrap.janet
uninstall:
-rm '$(DESTDIR)$(BINDIR)/janet'
-rm '$(DESTDIR)$(BINDIR)/jpm'
-rm -rf '$(DESTDIR)$(INCLUDEDIR)/janet'
-rm -rf '$(DESTDIR)$(LIBDIR)'/libjanet.*
-rm '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc'
-rm '$(DESTDIR)$(JANET_MANPATH)/janet.1'
-rm '$(DESTDIR)$(JANET_MANPATH)/jpm.1'
# -rm -rf '$(DESTDIR)$(JANET_PATH)'/* - err on the side of correctness here
#################
@ -311,18 +328,7 @@ clean:
-rm -rf test/install/build test/install/modpath
test-install:
cd test/install \
&& rm -rf build .cache .manifests \
&& jpm --verbose build \
&& jpm --verbose test \
&& build/testexec \
&& jpm --verbose quickbin testexec.janet build/testexec2 \
&& build/testexec2 \
&& mkdir -p modpath \
&& jpm --verbose --testdeps --modpath=./modpath install https://github.com/janet-lang/json.git
cd test/install && jpm --verbose --test --modpath=./modpath install https://github.com/janet-lang/jhydro.git
cd test/install && jpm --verbose --test --modpath=./modpath install https://github.com/janet-lang/path.git
cd test/install && jpm --verbose --test --modpath=./modpath install https://github.com/janet-lang/argparse.git
echo "JPM has been removed from default install."
help:
@echo

View File

@ -1,9 +1,9 @@
[![Join the chat](https://badges.gitter.im/janet-language/community.svg)](https://gitter.im/janet-language/community)
&nbsp;
[![Appveyor Status](https://ci.appveyor.com/api/projects/status/bjraxrxexmt3sxyv/branch/master?svg=true)](https://ci.appveyor.com/project/bakpakin/janet/branch/master)
[![Build Status](https://travis-ci.org/janet-lang/janet.svg?branch=master)](https://travis-ci.org/janet-lang/janet)
[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml?)
[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml?)
[![Actions Status](https://github.com/janet-lang/janet/actions/workflows/test.yml/badge.svg)](https://github.com/janet-lang/janet/actions/workflows/test.yml)
<img src="https://raw.githubusercontent.com/janet-lang/janet/master/assets/janet-w200.png" alt="Janet logo" width=200 align="left">
@ -30,6 +30,7 @@ Lua, but smaller than GNU Guile or Python.
## Features
* Configurable at build time - turn features on or off for a smaller or more featureful build
* Minimal setup - one binary and you are good to go!
* First-class closures
* Garbage collection
@ -39,6 +40,8 @@ Lua, but smaller than GNU Guile or Python.
* Mutable and immutable hashtables (table/struct)
* Mutable and immutable strings (buffer/string)
* Macros
* Multithreading
* Per-thread event loop for efficient evented IO
* Byte code interpreter with an assembly interface, as well as bytecode verification
* Tail call Optimization
* Direct interop with C via abstract types and C functions
@ -211,7 +214,7 @@ Options are:
-- : Stop handling options
```
If installed, you can also run `man janet` and `man jpm` to get usage information.
If installed, you can also run `man janet` to get usage information.
## Embedding
@ -238,23 +241,19 @@ Gitter provides Matrix and irc bridges as well.
## FAQ
### Why is my terminal spitting out junk when I run the REPL?
Make sure your terminal supports ANSI escape codes. Most modern terminals will
support these, but some older terminals, Windows consoles, or embedded terminals
will not. If your terminal does not support ANSI escape codes, run the REPL with
the `-n` flag, which disables color output. You can also try the `-s` if further issues
ensue.
### Where is (favorite feature from other language)?
It may exist, it may not. If you want to propose major language features, go ahead and open an issue, but
they will likely by closed as "will not implement". Often, such features make one usecase simpler at the expense
of 5 others by making the language more complicated.
### Where is the example code?
### Is there a language spec?
In the examples directory.
There is not currently a spec besides the documentation at https://janet-lang.org.
### Is this Scheme/Common Lisp? Where are the cons cells?
Nope. There are no cons cells here.
### Is this a Clojure port?
@ -266,14 +265,35 @@ Internally, Janet is not at all like Clojure.
No. They are immutable arrays and hash tables. Don't try and use them like Clojure's vectors
and maps, instead they work well as table keys or other identifiers.
### Can I do Object Oriented programming with Janet?
To some extent, yes. However, it is not the recommended method of abstraction, and performance may suffer.
That said, tables can be used to make mutable objects with inheritance and polymorphism, where object
methods are implemeted with keywords.
```
(def Car @{:honk (fn [self msg] (print "car " self " goes " msg)) })
(def my-car (table/setproto @{} Car))
(:honk my-car "Beep!")
```
### Why can't we add (feature from Clojure) into the core?
Usually, one of a few reasons:
- Often, it already exists in a different form and the Clojure port would be redundant.
- Clojure programs often generate a lot of garbage and rely on the JVM to clean it up.
Janet does not run on the JVM. We admittedly have a much more primitive GC.
Janet does not run on the JVM, and has a more primitive garbage collector.
- We want to keep the Janet core small. With Lisps, usually a feature can be added as a library
without feeling "bolted on", especially when compared to ALGOL like languages.
without feeling "bolted on", especially when compared to ALGOL like languages. Adding features
to the core also makes it a bit more difficult keep Janet maximally portable.
### Why is my terminal spitting out junk when I run the REPL?
Make sure your terminal supports ANSI escape codes. Most modern terminals will
support these, but some older terminals, Windows consoles, or embedded terminals
will not. If your terminal does not support ANSI escape codes, run the REPL with
the `-n` flag, which disables color output. You can also try the `-s` if further issues
ensue.
## Why is it called "Janet"?

View File

@ -20,10 +20,6 @@ init:
install:
- set JANET_BUILD=%appveyor_repo_commit:~0,7%
- build_win all
- refreshenv
# We need to reload vcvars after refreshing
- call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" %platform%
- build_win test-install
- set janet_outname=%appveyor_repo_tag_name%
- if "%janet_outname%"=="" set /P janet_outname=<build\version.txt
build: off

View File

@ -14,13 +14,18 @@
@if "%1"=="test" goto TEST
@if "%1"=="dist" goto DIST
@if "%1"=="install" goto INSTALL
@if "%1"=="test-install" goto TESTINSTALL
@if "%1"=="all" goto ALL
@rem Set compile and link options here
@setlocal
@rem Example use asan
@rem set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD /fsanitize=address /Zi
@rem set JANET_LINK=link /nologo clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
@set JANET_COMPILE=cl /nologo /Isrc\include /Isrc\conf /c /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /MD
@set JANET_LINK=link /nologo
@set JANET_LINK_STATIC=lib /nologo
@rem Add janet build tag
@ -82,7 +87,7 @@ exit /b 1
@echo command prompt.
exit /b 0
@rem Clean build artifacts
@rem Clean build artifacts
:CLEAN
del *.exe *.lib *.exp
rd /s /q build
@ -117,8 +122,6 @@ janet.exe tools\patch-header.janet src\include\janet.h src\conf\janetconf.h buil
copy build\janet.h dist\janet.h
copy build\libjanet.lib dist\libjanet.lib
copy .\jpm dist\jpm
@rem Create installer
janet.exe -e "(->> janet/version (peg/match ''(* :d+ `.` :d+ `.` :d+)) first print)" > build\version.txt
janet.exe -e "(print (os/arch))" > build\arch.txt
@ -147,34 +150,6 @@ FOR %%a in (janet-*-windows-*-installer.msi) DO (
)
exit /b 0
@rem Test the installation.
:TESTINSTALL
pushd test\install
call jpm clean
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm test
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm --verbose --modpath=. install https://github.com/janet-lang/json.git
@if errorlevel 1 goto :TESTINSTALLFAIL
call build\testexec
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm --verbose quickbin testexec.janet build\testexec2.exe
@if errorlevel 1 goto :TESTINSTALLFAIL
call build\testexec2.exe
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm --verbose --test --modpath=. install https://github.com/janet-lang/jhydro.git
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm --verbose --test --modpath=. install https://github.com/janet-lang/path.git
@if errorlevel 1 goto :TESTINSTALLFAIL
call jpm --verbose --test --modpath=. install https://github.com/janet-lang/argparse.git
@if errorlevel 1 goto :TESTINSTALLFAIL
popd
exit /b 0
:TESTINSTALLFAIL
popd
goto :TESTFAIL
@rem build, test, dist, install. Useful for local dev.
:ALL
call %0 build

View File

@ -10,3 +10,13 @@
(ev/call worker :b 5)
(ev/sleep 0.3)
(ev/call worker :c 12)
(defn worker2
[name]
(repeat 10
(ev/sleep 0.2)
(print name " working")))
(ev/go worker2 :bob)
(ev/go worker2 :joe)
(ev/go worker2 :sally)

View File

@ -0,0 +1,22 @@
(def chan (ev/thread-chan 10))
(ev/spawn
(ev/sleep 0)
(print "started fiber!")
(ev/give chan (math/random))
(ev/give chan (math/random))
(ev/give chan (math/random))
(ev/sleep 0.5)
(for i 0 10
(print "giving to channel...")
(ev/give chan (math/random))
(ev/sleep 1))
(print "finished fiber!")
(:close chan))
(ev/do-thread
(print "started thread!")
(ev/sleep 1)
(while (def x (do (print "taking from channel...") (ev/take chan)))
(print "got " x " from thread!"))
(print "finished thread!"))

View File

@ -1,68 +0,0 @@
(defn worker-main
"Sends 11 messages back to parent"
[parent]
(def name (thread/receive))
(def interval (thread/receive))
(for i 0 10
(os/sleep interval)
(:send parent (string/format "thread %s wakeup no. %d" name i)))
(:send parent name))
(defn make-worker
[name interval]
(-> (thread/new worker-main)
(:send name)
(:send interval)))
(def bob (make-worker "bob" 0.02))
(def joe (make-worker "joe" 0.03))
(def sam (make-worker "sam" 0.05))
# Receive out of order
(for i 0 33
(print (thread/receive)))
#
# Recursive Thread Tree - should pause for a bit, and then print a cool zigzag.
#
(def rng (math/rng (os/cryptorand 16)))
(defn choose [& xs]
(in xs (:int rng (length xs))))
(defn worker-tree
[parent]
(def name (thread/receive))
(def depth (thread/receive))
(if (< depth 5)
(do
(defn subtree []
(-> (thread/new worker-tree)
(:send (string name "/" (choose "bob" "marley" "harry" "suki" "anna" "yu")))
(:send (inc depth))))
(let [l (subtree)
r (subtree)
lrep (thread/receive)
rrep (thread/receive)]
(:send parent [name ;lrep ;rrep])))
(do
(:send parent [name]))))
(-> (thread/new worker-tree) (:send "adam") (:send 0))
(def lines (thread/receive))
(map print lines)
#
# Receive timeout
#
(def slow (make-worker "slow-loras" 0.5))
(for i 0 50
(try
(let [msg (thread/receive 0.1)]
(print "\n" msg))
([err] (prin ".") (:flush stdout))))
(print "\ndone timing, timeouts ending.")
(try (while true (print (thread/receive))) ([err] (print "done")))

28
janet.1
View File

@ -3,11 +3,14 @@
janet \- run the Janet language abstract machine
.SH SYNOPSIS
.B janet
[\fB\-hvsrpnqk\fR]
[\fB\-hvsrpnqik\fR]
[\fB\-e\fR \fISOURCE\fR]
[\fB\-E\fR \fISOURCE ...ARGUMENTS\fR]
[\fB\-l\fR \fIMODULE\fR]
[\fB\-m\fR \fIPATH\fR]
[\fB\-c\fR \fIMODULE JIMAGE\fR]
[\fB\-w\fR \fILEVEL\fR]
[\fB\-x\fR \fILEVEL\fR]
[\fB\-\-\fR]
.BR script
.BR args ...
@ -160,6 +163,11 @@ Read raw input from stdin and forgo prompt history and other readline-like featu
Execute a string of Janet source. Source code is executed in the order it is encountered, so earlier
arguments are executed before later ones.
.TP
.BR \-E\ code arguments
Execute a single Janet expression as a Janet short-fn, passing the remaining command line arguments to the expression. This allows
more concise one-liners with command line arguments.
.TP
.BR \-d
Enable debug mode. On all terminating signals as well the debug signal, this will
@ -205,12 +213,28 @@ Precompiles Janet source code into an image, a binary dump that can be efficient
Source should be a path to the Janet module to compile, and output should be the file path of
resulting image. Output should usually end with the .jimage extension.
.TP
.BR \-i
When this flag is passed, a script passed to the interpreter will be treated as a janet image file
rather than a janet source file.
.TP
.BR \-l\ lib
Import a Janet module before running a script or repl. Multiple files can be loaded
in this manner, and exports from each file will be made available to the script
or repl.
.TP
.BR \-w\ level
Set the warning linting level for Janet.
This linting level should be one of :relaxed, :none, :strict, :normal, or a
Janet number. Any linting message that is of a greater lint level than this setting will be displayed as
a warning, but not stop compilation or execution.
.TP
.BR \-x\ level
Set the error linting level for Janet.
This linting level should be one of :relaxed, :none, :strict, :normal, or a
Janet number. Any linting message that is of a greater lint level will cause a compilation error
and stop compilation.
.TP
.BR \-\-
Stop parsing command line arguments. All arguments after this one will be considered file names

1479
jpm

File diff suppressed because it is too large Load Diff

298
jpm.1
View File

@ -1,298 +0,0 @@
.TH JPM 1
.SH NAME
jpm \- the Janet Project Manager, a build tool for Janet
.SH SYNOPSIS
.B jpm
[\fB\-\-flag ...\fR]
[\fB\-\-option=value ...\fR]
.IR command
.IR args ...
.SH DESCRIPTION
jpm is the build tool that ships with a standard Janet install. It is
used for building Janet projects, installing dependencies, installing
projects, building native modules, and exporting your Janet project to a
standalone executable. Although not required for working with Janet, it
removes much of the boilerplate with installing dependencies and
building native modules. jpm requires only Janet to run, and uses git
to install dependencies (jpm will work without git installed).
.SH DOCUMENTATION
jpm has several subcommands, each used for managing either a single Janet project or
all Janet modules installed on the system. Global commands, those that manage modules
at the system level, do things like install and uninstall packages, as well as clear the cache.
More interesting are the local commands. For more information on jpm usage, see https://janet-lang.org/docs/index.html
.SH FLAGS
.TP
.BR \-\-nocolor
Disable color in the jpm debug repl.
.TP
.BR \-\-verbose
Print detailed messages of what jpm is doing, including compilation commands and other shell commands.
.TP
.BR \-\-test
If passed to jpm install, runs tests before installing. Will run tests recursively on dependencies.
.TP
.BR \-\-offline
Prevents jpm from going to network to get dependencies - all dependencies should be in the cache or this command will fail.
Use this flag with the deps and update-pkgs subcommands. This is not a surefire way to prevent a build script from accessing
the network, for example, a build script that invokes curl will still have network access.
.TP
.BR \-\-auto\-shebang
Prepends installed scripts with a generated shebang line, such that they will use a janet binary located in JANET_BINPATH.
.SH OPTIONS
.TP
.BR \-\-modpath=/some/path
Set the path to install modules to. Defaults to $JANET_MODPATH, $JANET_PATH, or (dyn :syspath) in that order. You most likely don't need this.
.TP
.BR \-\-headerpath=/some/path
Set the path the jpm will include when building C source code. This lets
you specify the location of janet.h and janetconf.h on your system. On a
normal install, this option is not needed.
.TP
.BR \-\-binpath=/some/path
Set the path that jpm will install scripts and standalone executables to. Executables
defined via declare-execuatble or scripts declared via declare-binscript will be installed
here when jpm install is run. Defaults to $JANET_BINPATH, or a reasonable default for the system.
See JANET_BINPATH for more.
.TP
.BR \-\-libpath=/some/path
Sets the path jpm will use to look for libjanet.a for building standalone executables. libjanet.so
is \fBnot\fR used for building native modules or standalone executables, only
for linking into applications that want to embed janet as a dynamic module.
Linking statically might be a better idea, even in that case. Defaults to
$JANET_LIBPATH, or a reasonable default. See JANET_LIBPATH for more.
.TP
.BR \-\-compiler=$CC
Sets the C compiler used for compiling native modules and standalone executables. Defaults
to cc.
.TP
.BR \-\-cpp\-compiler=$CXX
Sets the C++ compiler used for compiling native modules and standalone executables. Defaults
to c++..
.TP
.BR \-\-linker
Sets the linker used to create native modules and executables. Only used on windows, where
it defaults to link.exe.
.TP
.BR \-\-pkglist=https://github.com/janet-lang/pkgs.git
Sets the git repository for the package listing used to resolve shorthand package names.
.TP
.BR \-\-archiver=$AR
Sets the command used for creating static libraries, use for linking into the standalone executable.
Native modules are compiled twice, once a normal native module (shared object), and once as an
archive. Defaults to ar.
.SH COMMANDS
.TP
.BR help
Shows the usage text and exits immediately.
.TP
.BR build
Builds all artifacts specified in the project.janet file in the current directory. Artifacts will
be created in the ./build/ directory.
.TP
.BR install\ [\fBrepo...\fR]
When run with no arguments, installs all installable artifacts in the current project to
the current JANET_MODPATH for modules and JANET_BINPATH for executables and scripts. Can also
take an optional git repository URL and will install all artifacts in that repository instead.
When run with an argument, install does not need to be run from a jpm project directory. Will also
install multiple dependencies in one command.
.TP
.BR uninstall\ [\fBname...\fR]
Uninstall a project installed with install. uninstall expects the name of the project, not the
repository url, path to installed file, or executable name. The name of the project must be specified
at the top of the project.janet file in the declare-project form. If no name is given, uninstalls
the current project if installed. Will also uninstall multiple packages in one command.
.TP
.BR clean
Remove all artifacts created by jpm. This just deletes the build folder.
.TP
.BR test
Runs jpm tests. jpm will run all janet source files in the test directory as tests. A test
is considered failing if it exits with a non-zero exit code.
.TP
.BR deps
Install all dependencies that this project requires recursively. jpm does not
resolve dependency issues, like conflicting versions of the same module are required, or
different modules with the same name. Dependencies are installed with git, so deps requires
git to be on the PATH.
.TP
.BR clear-cache
jpm caches git repositories that are needed to install modules from a remote
source in a global cache ($JANET_PATH/.cache). If these dependencies are out of
date or too large, clear-cache will remove the cache and jpm will rebuild it
when needed. clear-cache is a global command, so a project.janet is not
required.
.TP
.BR list-installed
List all installed packages in the current syspath.
.TP
.BR list-pkgs\ [\fBsearch\fR]
List all package aliases in the current package listing that contain the given search string.
If no search string is given, prints the entire listing.
.TP
.BR clear-manifest
jpm creates a manifest directory that contains a list of all installed files.
By deleting this directory, jpm will think that nothing is installed and will
try reinstalling everything on the jpm deps or jpm load-lockfile commands. Be careful with
this command, as it may leave extra files on your system and shouldn't be needed
most of the time in a healthy install.
.TP
.BR run\ [\fBrule\fR]
Run a given rule defined in project.janet. Project definitions files (project.janet) usually
contain a few artifact declarations, which set up rules that jpm can then resolve, or execute.
A project.janet can also create custom rules to create arbitrary files or run arbitrary code, much
like make. run will run a single rule or build a single file.
.TP
.BR rules
List all rules that can be run via run. This is useful for exploring rules in the project.
.TP
.BR rule-tree\ [\fBroot\fR]\ [\fBdepth\fR]
Show rule dependency tree in a pretty format. Optionally provide a rule to use as the tree
root, as well as a max depth to print. By default, prints the full tree for all rules. This
can be quite long, so it is recommended to give a root rule.
.TP
.BR show-paths
Show all of the paths used when installing and building artifacts.
.TP
.BR update-pkgs
Update the package listing by installing the 'pkgs' package. Same as jpm install pkgs
.TP
.BR quickbin\ [\fBentry\fR]\ [\fBexecutable\fR]
Create a standalone, statically linked executable from a Janet source file that contains a main function.
The main function is the entry point of the program and will receive command line arguments
as function arguments. The entry file can import other modules, including native C modules, and
jpm will attempt to include the dependencies into the generated executable.
.TP
.BR debug-repl
Load the current project.janet file and start a repl in it's environment. This lets a user better
debug the project file, as well as run rules manually.
.TP
.BR make-lockfile\ [\fBfilename\fR]
Create a lockfile. A lockfile is a record that describes what dependencies were installed at the
time of the lockfile's creation, including exact versions. A lockfile can then be later used
to set up that environment on a different machine via load-lockfile. By default, the lockfile
is created at lockfile.jdn, although any path can be used.
.TP
.BR load-lockfile\ [\fBfilename\fR]
Install dependencies from a lockfile previously created with make-lockfile. By default, will look
for a lockfile at lockfile.jdn, although any path can be used.
.SH ENVIRONMENT
.B JANET_PATH
.RS
The location to look for Janet libraries. This is the only environment variable Janet needs to
find native and source code modules. If no JANET_PATH is set, Janet will look in
the default location set at compile time, which can be determined with (dyn :syspath)
.RE
.B JANET_MODPATH
.RS
The location that jpm will use to install libraries to. Defaults to JANET_PATH, but you could
set this to a different directory if you want to. Doing so would let you import Janet modules
on the normal system path (JANET_PATH or (dyn :syspath)), but install to a different directory. It is also a more reliable way to install.
This variable is overwritten by the --modpath=/some/path if it is provided.
.RE
.B JANET_HEADERPATH
.RS
The location that jpm will look for janet header files (janet.h and janetconf.h) that are used
to build native modules and standalone executables. If janet.h and janetconf.h are available as
default includes on your system, this value is not required. If not provided, will default to
<jpm script location>/../include/janet. The --headerpath=/some/path option will override this
variable.
.RE
.B JANET_LIBPATH
.RS
Similar to JANET_HEADERPATH, this path is where jpm will look for
libjanet.a for creating standalone executables. This does not need to be
set on a normal install.
If not provided, this will default to <jpm script location>/../lib.
The --libpath=/some/path option will override this variable.
.RE
.B JANET_BINPATH
.RS
The directory where jpm will install binary scripts and executables to.
Defaults to
(dyn :syspath)/bin
The --binpath=/some/path will override this variable.
.RE
.B JANET_PKGLIST
.RS
The git repository URL that contains a listing of packages. This allows installing packages with shortnames, which
is mostly a convenience. However, package dependencies can use short names, package listings
can be used to choose a particular set of dependency versions for a whole project.
.RE
.B JANET_GIT
.RS
An optional path to a git executable to use to clone git dependencies. By default, uses "git" on the current $PATH. You shouldn't need to set this
if you have a normal install of git.
.RE
.B JPM_OS_WHICH
.RS
Use this option to override the C compiler and build system auto-detection for the host operating system. For example, set this
environment variable to "posix" to make sure that on platforms like MinGW, you will use GCC instead of MSVC. On most platforms, users will not need to
set this environment variable. Set this to one of the following
strings:
.IP
\- windows
.IP
\- macos
.IP
\- linux
.IP
\- freebsd
.IP
\- openbsd
.IP
\- netbsd
.IP
\- bsd
.IP
\- posix
.RE
.SH AUTHOR
Written by Calvin Rose <calsrose@gmail.com>

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose and contributors
# Copyright (c) 2021 Calvin Rose and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -20,7 +20,7 @@
project('janet', 'c',
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
version : '1.16.0')
version : '1.18.2')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
@ -30,6 +30,7 @@ header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet'
cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required : false)
dl_dep = cc.find_library('dl', required : false)
android_spawn_dep = cc.find_library('android-spawn', required : false)
thread_dep = dependency('threads')
# Link options
@ -72,7 +73,9 @@ conf.set('JANET_NO_UMASK', not get_option('umask'))
conf.set('JANET_NO_REALPATH', not get_option('realpath'))
conf.set('JANET_NO_PROCESSES', not get_option('processes'))
conf.set('JANET_SIMPLE_GETLINE', get_option('simple_getline'))
conf.set('JANET_EV_EPOLL', get_option('epoll'))
conf.set('JANET_EV_NO_EPOLL', not get_option('epoll'))
conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue'))
conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt'))
if get_option('os_name') != ''
conf.set('JANET_OS_NAME', get_option('os_name'))
endif
@ -127,12 +130,12 @@ core_src = [
'src/core/regalloc.c',
'src/core/run.c',
'src/core/specials.c',
'src/core/state.c',
'src/core/string.c',
'src/core/strtod.c',
'src/core/struct.c',
'src/core/symcache.c',
'src/core/table.c',
'src/core/thread.c',
'src/core/tuple.c',
'src/core/util.c',
'src/core/value.c',
@ -158,7 +161,7 @@ mainclient_src = [
janet_boot = executable('janet-boot', core_src, boot_src,
include_directories : incdir,
c_args : '-DJANET_BOOTSTRAP',
dependencies : [m_dep, dl_dep, thread_dep],
dependencies : [m_dep, dl_dep, thread_dep, android_spawn_dep],
native : true)
# Build janet.c
@ -168,10 +171,10 @@ janetc = custom_target('janetc',
capture : true,
command : [
janet_boot, meson.current_source_dir(),
'JANET_PATH', janet_path, 'JANET_HEADERPATH', header_path
'JANET_PATH', janet_path
])
janet_dependencies = [m_dep, dl_dep]
janet_dependencies = [m_dep, dl_dep, android_spawn_dep]
if not get_option('single_threaded')
janet_dependencies += thread_dep
endif
@ -260,16 +263,3 @@ patched_janet = custom_target('patched-janeth',
build_by_default : true,
output : ['janet.h'],
command : [janet_nativeclient, '@INPUT@', '@OUTPUT@'])
if get_option('peg') and not get_option('reduced_os') and get_option('processes')
install_man('jpm.1')
patched_jpm = custom_target('patched-jpm',
input : ['tools/patch-jpm.janet', 'jpm'],
install : true,
install_dir : get_option('bindir'),
build_by_default : true,
output : ['jpm'],
command : [janet_nativeclient, '@INPUT@', '@OUTPUT@',
'--binpath=' + join_paths(get_option('prefix'), get_option('bindir')),
'--libpath=' + join_paths(get_option('prefix'), get_option('libdir')),
'--headerpath=' + join_paths(get_option('prefix'), get_option('includedir'))])
endif

View File

@ -17,6 +17,8 @@ option('umask', type : 'boolean', value : true)
option('realpath', type : 'boolean', value : true)
option('simple_getline', type : 'boolean', value : false)
option('epoll', type : 'boolean', value : false)
option('kqueue', type : 'boolean', value : false)
option('interpreter_interrupt', type : 'boolean', value : false)
option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024)
option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
# The core janet library
# Copyright 2020 © Calvin Rose
# Copyright 2021 © Calvin Rose
###
###
@ -51,7 +51,7 @@
``Use a function or macro literal `f` as a macro. This lets
any function be used as a macro. Inside a quasiquote, the
idiom `(as-macro ,my-custom-macro arg1 arg2...)` can be used
to avoid unwanted variable capture.``
to avoid unwanted variable capture of `my-custom-macro`.``
[f & args]
(f ;args))
@ -149,10 +149,15 @@
(defmacro /= "Shorthand for (set x (/ x n))." [x n] ~(set ,x (,/ ,x ,n)))
(defmacro %= "Shorthand for (set x (% x n))." [x n] ~(set ,x (,% ,x ,n)))
(defn assert
"Throw an error if x is not truthy."
(defmacro assert
"Throw an error if x is not truthy. Will not evaluate `err` if x is truthy."
[x &opt err]
(if x x (error (if err err "assert failure"))))
(def v (gensym))
~(do
(def ,v ,x)
(if ,v
,v
(,error ,(if err err "assert failure")))))
(defn errorf
"A combination of error and string/format. Equivalent to (error (string/format fmt ;args))"
@ -588,10 +593,15 @@
~(fiber/new (fn [] (loop ,head (yield (do ,;body)))) :yi))
(defmacro coro
"A wrapper for making fibers. Same as (fiber/new (fn [] ;body) :yi)."
"A wrapper for making fibers that may yield multiple values (coroutine). Same as (fiber/new (fn [] ;body) :yi)."
[& body]
(tuple fiber/new (tuple 'fn '[] ;body) :yi))
(defmacro fiber-fn
"A wrapper for making fibers. Same as (fiber/new (fn [] ;body) flags)."
[flags & body]
(tuple fiber/new (tuple 'fn '[] ;body) flags))
(defn sum
"Returns the sum of xs. If xs is empty, returns 0."
[xs]
@ -688,6 +698,14 @@
"Returns the numeric minimum of the arguments."
[& args] (extreme < args))
(defn max-of
"Returns the numeric maximum of the argument sequence."
[args] (extreme > args))
(defn min-of
"Returns the numeric minimum of the argument sequence."
[args] (extreme < args))
(defn first
"Get the first element from an indexed data structure."
[xs]
@ -1026,35 +1044,65 @@
(set k (next ind k)))
ret)
(defn take
"Take first n elements in an indexed type. Returns new indexed instance."
[n ind]
(def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice))
(defn- take-n-fallback
[n xs]
(def res @[])
(when (> n 0)
(var left n)
(each x xs
(array/push res x)
(-- left)
(if (= 0 left) (break))))
res)
(defn- take-until-fallback
[pred xs]
(def res @[])
(each x xs
(if (pred x) (break))
(array/push res x))
res)
(defn- slice-n
[f n ind]
(def len (length ind))
# make sure end is in [0, len]
(def m (if (> n 0) n 0))
(def end (if (> m len) len m))
(f ind 0 end))
(defn take-until
"Same as (take-while (complement pred) ind)."
[pred ind]
(def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice))
(defn take
"Take the first n elements of a fiber, indexed or bytes type. Returns a new array, tuple or string, respectively."
[n ind]
(cond
(bytes? ind) (slice-n string/slice n ind)
(indexed? ind) (slice-n tuple/slice n ind)
(take-n-fallback n ind)))
(defn- slice-until
[f pred ind]
(def len (length ind))
(def i (find-index pred ind))
(def end (if (nil? i) len i))
(f ind 0 end))
(defn take-until
"Same as `(take-while (complement pred) ind)`."
[pred ind]
(cond
(bytes? ind) (slice-until string/slice pred ind)
(indexed? ind) (slice-until tuple/slice pred ind)
(take-until-fallback pred ind)))
(defn take-while
`Given a predicate, take only elements from an indexed type that satisfy
the predicate, and abort on first failure. Returns a new array.`
`Given a predicate, take only elements from a fiber, indexed or bytes type that satisfy
the predicate, and abort on first failure. Returns a new array, tuple or string, respectively.`
[pred ind]
(take-until (complement pred) ind))
(defn drop
"Drop first n elements in an indexed type. Returns new indexed instance."
``Drop the first n elements in an indexed or bytes type. Returns a new tuple or string
instance, respectively.``
[n ind]
(def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice))
@ -1065,7 +1113,7 @@
(f ind start -1))
(defn drop-until
"Same as (drop-while (complement pred) ind)."
"Same as `(drop-while (complement pred) ind)`."
[pred ind]
(def use-str (bytes? ind))
(def f (if use-str string/slice tuple/slice))
@ -1075,8 +1123,8 @@
(f ind start))
(defn drop-while
`Given a predicate, remove elements from an indexed type that satisfy
the predicate, and abort on first failure. Returns a new array.`
`Given a predicate, remove elements from an indexed or bytes type that satisfy
the predicate, and abort on first failure. Returns a new tuple or string, respectively.`
[pred ind]
(drop-until (complement pred) ind))
@ -1316,9 +1364,10 @@
ret)
(defn invert
`Returns a table where the keys of an associative data structure
are the values, and the values of the keys. If multiple keys have the same
value, one key will be ignored.`
``Returns a table where the keys of an associative data structure
are the values, and the values are the keys. If multiple keys in `ds`
are mapped to the same value, only one of those values will
become a key in the returned table.``
[ds]
(def ret @{})
(loop [k :keys ds]
@ -1345,7 +1394,7 @@
a sequence of keys.`
[ds ks &opt dflt]
(var d ds)
(loop [k :in ks :while d] (set d (get d k)))
(loop [k :in ks :while (not (nil? d))] (set d (get d k)))
(if (= nil d) dflt d))
(defn update-in
@ -1628,9 +1677,13 @@
* tuple -- a tuple pattern will match if its first element matches, and the
following elements are treated as predicates and are true.
* `_` symbol -- the last special case is the `_` symbol, which is a wildcard
* `\_` symbol -- the last special case is the `\_` symbol, which is a wildcard
that will match any value without creating a binding.
While a symbol pattern will ordinarily match any value, the pattern `(@ <sym>)`,
where <sym> is any symbol, will attempt to match `x` against a value
already bound to `<sym>`, rather than matching and rebinding it.
Any other value pattern will only match if it is equal to `x`.
```
[x & cases]
@ -1784,6 +1837,20 @@
###
###
(defn maclintf
``When inside a macro, call this function to add a linter warning. Takes
a `fmt` argument like `string/format` which is used to format the message.``
[level fmt & args]
(def lints (dyn :macro-lints))
(when lints
(def form (dyn :macro-form))
(def [l c] (if (tuple? form) (tuple/sourcemap form) [nil nil]))
(def l (if-not (= -1 l) l))
(def c (if-not (= -1 c) c))
(def msg (string/format fmt ;args))
(array/push lints [level l c msg]))
nil)
(defn macex1
``Expand macros in a form, but do not recursively expand macros.
See `macex` docs for info on on-binding.``
@ -2065,11 +2132,10 @@
###
###
# Initialize syspath and header path
# Initialize syspath
(each [k v] (partition 2 (tuple/slice boot/args 2))
(case k
"JANET_PATH" (setdyn :syspath v)
"JANET_HEADERPATH" (setdyn :headerpath v)))
"JANET_PATH" (setdyn :syspath v)))
(defn make-env
`Create a new environment table. The new environment
@ -2097,6 +2163,43 @@
(if ec "\e[0m" ""))
(eflush))
(defn- print-line-col
"Print the source code at a line, column in a source file. If unable to open
the file, prints nothing."
[where line col]
(if-not line (break))
(when-with [f (file/open where :r)]
(def source-code (file/read f :all))
(var index 0)
(repeat (dec line)
(if-not index (break))
(set index (inc (string/find "\n" source-code index))))
(when index
(def line-end (string/find "\n" source-code index))
(eprint " " (string/slice source-code index line-end))
(when col
(+= index col)
(eprint (string/repeat " " (inc col)) "^"))
(eflush))))
(defn warn-compile
"Default handler for a compile warning"
[msg level where &opt line col]
(def ec (dyn :err-color))
(eprin
(if ec "\e[33m" "")
where
":"
line
":"
col
": compile warning (" level "): ")
(eprint msg)
(when ec
(print-line-col where line col)
(eprin "\e[0m"))
(eflush))
(defn bad-compile
"Default handler for a compile error."
[msg macrof where &opt line col]
@ -2111,7 +2214,10 @@
": compile error: ")
(if macrof
(debug/stacktrace macrof msg)
(eprint msg (if ec "\e[0m" "")))
(eprint msg))
(when ec
(print-line-col where line col)
(eprin "\e[0m"))
(eflush))
(defn curenv
@ -2122,6 +2228,13 @@
(if n (repeat n (if (= nil e) (break)) (set e (table/getproto e))))
e)
(def- lint-levels
{:none 0
:relaxed 1
:normal 2
:strict 3
:all math/inf})
(defn run-context
```
Run a context. This evaluates expressions in an environment,
@ -2134,6 +2247,7 @@
* `:env` - the environment to compile against - default is the current env
* `:source` - string path of source for better errors - default is "<anonymous>"
* `:on-compile-error` - callback when compilation fails - default is bad-compile
* `:on-compile-warning` - callback for any linting error - default is warn-compile
* `:evaluator` - callback that executes thunks. Signature is (evaluator thunk source env where)
* `:on-status` - callback when a value is evaluated - default is debug/stacktrace.
* `:fiber-flags` - what flags to wrap the compilation fiber with. Default is :ia.
@ -2148,6 +2262,7 @@
:chunks chunks
:on-status onstatus
:on-compile-error on-compile-error
:on-compile-warning on-compile-warning
:on-parse-error on-parse-error
:fiber-flags guard
:evaluator evaluator
@ -2159,6 +2274,7 @@
(default chunks (fn [buf p] (getline "" buf env)))
(default onstatus debug/stacktrace)
(default on-compile-error bad-compile)
(default on-compile-warning warn-compile)
(default on-parse-error bad-parse)
(default evaluator (fn evaluate [x &] (x)))
(default default-where "<anonymous>")
@ -2167,6 +2283,7 @@
(var where default-where)
# Evaluate 1 source form in a protected manner
(def lints @[])
(defn eval1 [source &opt l c]
(def source (if expand (expand source) source))
(var good true)
@ -2174,13 +2291,29 @@
(def f
(fiber/new
(fn []
(def res (compile source env where))
(if (= (type res) :function)
(evaluator res source env where)
(do
(set good false)
(def {:error err :line line :column column :fiber errf} res)
(on-compile-error err errf where (or line l) (or column c)))))
(array/clear lints)
(def res (compile source env where lints))
(unless (empty? lints)
# Convert lint levels to numbers.
(def levels (get env :lint-levels lint-levels))
(def lint-error (get env :lint-error))
(def lint-warning (get env :lint-warn))
(def lint-error (or (get levels lint-error lint-error) 0))
(def lint-warning (or (get levels lint-warning lint-warning) 2))
(each [level line col msg] lints
(def lvl (get lint-levels level 0))
(cond
(<= lvl lint-error) (do
(set good false)
(on-compile-error msg nil where (or line l) (or col c)))
(<= lvl lint-warning) (on-compile-warning msg level where (or line l) (or col c)))))
(when good
(if (= (type res) :function)
(evaluator res source env where)
(do
(set good false)
(def {:error err :line line :column column :fiber errf} res)
(on-compile-error err errf where (or line l) (or column c))))))
guard))
(fiber/setenv f env)
(while (fiber/can-resume? f)
@ -2254,6 +2387,7 @@
(when (= (p-status p) :error)
(parse-err p where)))
(put env :exit nil)
(in env :exit-value env))
(defn quit
@ -2298,7 +2432,7 @@
(def res (compile form (fiber/getenv (fiber/current)) "eval"))
(if (= (type res) :function)
(res)
(error (res :error))))
(error (get res :error))))
(defn parse
`Parse a string and return the first value. For complex parsing, such as for a repl with error handling,
@ -2660,8 +2794,8 @@
(def delimiters
(if has-color
{:underline ["\e[4m" "\e[24m"]
:code ["\e[3;97m" "\e[39;23m"]
:italics ["\e[3m" "\e[23m"]
:code ["\e[97m" "\e[39m"]
:italics ["\e[4m" "\e[24m"]
:bold ["\e[1m" "\e[22m"]}
{:underline ["_" "_"]
:code ["`" "`"]
@ -2694,7 +2828,7 @@
(c++)
(- cursor x))
# Detection helpers - return number of characters natched
# Detection helpers - return number of characters matched
(defn ul? []
(let [x (c) x1 (cn 1)]
(and
@ -2828,6 +2962,14 @@
(finish-p)
new-indent))
# Handle first line specially for defn, defmacro, etc.
(when (= (chr "(") (in str 0))
(skipline)
(def first-line (string/slice str 0 (- cursor 1)))
(def fl-open (if has-color "\e[97m" ""))
(def fl-close (if has-color "\e[39m" ""))
(push [[(string fl-open first-line fl-close) (length first-line)]]))
(parse-blocks 0)
# Emission state
@ -2965,10 +3107,10 @@
(print-index identity)))
(defmacro doc
`Shows documentation for the given symbol, or can show a list of available bindings.
If sym is a symbol, will look for documentation for that symbol. If sym is a string
or is not provided, will show all lexical and dynamic bindings in the current environment with
that prefix (all bindings will be shown if no prefix is given).`
``Shows documentation for the given symbol, or can show a list of available bindings.
If `sym` is a symbol, will look for documentation for that symbol. If `sym` is a string
or is not provided, will show all lexical and dynamic bindings in the current environment
containing that string (all bindings will be shown if no string is given).``
[&opt sym]
~(,doc* ',sym))
@ -3234,18 +3376,23 @@
Returns a fiber that is scheduled to run the function.
```
[f & args]
(ev/go (fiber/new (fn [&] (f ;args)) :tp)))
(ev/go (fn _call [&] (f ;args))))
(defmacro ev/spawn
"Run some code in a new fiber. This is shorthand for (ev/call (fn [] ;body))."
[& body]
~(,ev/go (fiber/new (fn _spawn [&] ,;body) :tp)))
~(,ev/go (fn _spawn [&] ,;body)))
(defmacro ev/do-thread
``Run some code in a new thread. Suspends the current fiber until the thread is complete, and
evaluates to nil.``
[& body]
~(,ev/thread (fiber/new (fn _thread [&] ,;body) :t)))
~(,ev/thread (fn _do-thread [&] ,;body)))
(defmacro ev/spawn-thread
``Run some code in a new thread. Like `ev/do-thread`, but returns nil immediately.``
[& body]
~(,ev/thread (fn _spawn-thread [&] ,;body) nil :n))
(defmacro ev/with-deadline
`Run a body of code with a deadline, such that if the code does not complete before
@ -3276,7 +3423,7 @@
(def ,res @[])
(,wait-for-fibers ,chan
,(seq [[i body] :pairs bodies]
~(,ev/go (,fiber/new (fn [] (put ,res ,i ,body)) :tp) nil ,chan)))
~(,ev/go (fn [] (put ,res ,i ,body)) nil ,chan)))
,res))))
(compwhen (dyn 'net/listen)
@ -3369,6 +3516,12 @@
# conditional compilation for reduced os
(def- getenv-alias (if-let [entry (in root-env 'os/getenv)] (entry :value) (fn [&])))
(defn- run-main
[env subargs arg]
(if-let [main (get (in env 'main) :value)]
(let [thunk (compile [main ;subargs] env arg)]
(if (function? thunk) (thunk) (error (thunk :error))))))
(defn cli-main
`Entrance for the Janet CLI tool. Call this function with the command line
arguments as an array or tuple of strings to invoke the CLI interface.`
@ -3376,20 +3529,27 @@
(setdyn :args args)
(var *should-repl* false)
(var *no-file* true)
(var *quiet* false)
(var *raw-stdin* false)
(var *handleopts* true)
(var *exit-on-error* true)
(var *colorize* true)
(var *debug* false)
(var *compile-only* false)
(var should-repl false)
(var no-file true)
(var quiet false)
(var raw-stdin false)
(var handleopts true)
(var exit-on-error true)
(var colorize true)
(var debug-flag false)
(var compile-only false)
(var warn-level nil)
(var error-level nil)
(var expect-image false)
(if-let [jp (getenv-alias "JANET_PATH")] (setdyn :syspath jp))
(if-let [jp (getenv-alias "JANET_HEADERPATH")] (setdyn :headerpath jp))
(if-let [jprofile (getenv-alias "JANET_PROFILE")] (setdyn :profilepath jprofile))
(defn- get-lint-level
[i]
(def x (in args (+ i 1)))
(or (scan-number x) (keyword x)))
# Flag handlers
(def handlers
{"h" (fn [&]
@ -3401,6 +3561,7 @@
-v : Print the version string
-s : Use raw stdin instead of getline like functionality
-e code : Execute a string of janet
-E code arguments... : Evaluate an expression as a short-fn with arguments
-d : Set the debug flag in the REPL
-r : Enter the REPL after running all scripts
-R : Disables loading profile.janet when JANET_PROFILE is present
@ -3409,35 +3570,51 @@
-k : Compile scripts but do not execute (flycheck)
-m syspath : Set system path for loading global modules
-c source output : Compile janet source code into an image
-i : Load the script argument as an image file instead of source code
-n : Disable ANSI color output in the REPL
-l lib : Import a module before processing more arguments
-l lib : Use a module before processing more arguments
-w level : Set the lint warning level - default is "normal"
-x level : Set the lint error level - default is "none"
-- : Stop handling options
```)
(os/exit 0)
1)
"v" (fn [&] (print janet/version "-" janet/build) (os/exit 0) 1)
"s" (fn [&] (set *raw-stdin* true) (set *should-repl* true) 1)
"r" (fn [&] (set *should-repl* true) 1)
"p" (fn [&] (set *exit-on-error* false) 1)
"q" (fn [&] (set *quiet* true) 1)
"k" (fn [&] (set *compile-only* true) (set *exit-on-error* false) 1)
"n" (fn [&] (set *colorize* false) 1)
"s" (fn [&] (set raw-stdin true) (set should-repl true) 1)
"r" (fn [&] (set should-repl true) 1)
"p" (fn [&] (set exit-on-error false) 1)
"q" (fn [&] (set quiet true) 1)
"i" (fn [&] (set expect-image true) 1)
"k" (fn [&] (set compile-only true) (set exit-on-error false) 1)
"n" (fn [&] (set colorize false) 1)
"m" (fn [i &] (setdyn :syspath (in args (+ i 1))) 2)
"c" (fn c-switch [i &]
(def e (dofile (in args (+ i 1))))
(def path (in args (+ i 1)))
(def e (dofile path))
(spit (in args (+ i 2)) (make-image e))
(set *no-file* false)
(set no-file false)
3)
"-" (fn [&] (set *handleopts* false) 1)
"-" (fn [&] (set handleopts false) 1)
"l" (fn l-switch [i &]
(import* (in args (+ i 1))
:prefix "" :exit *exit-on-error*)
:prefix "" :exit exit-on-error)
2)
"e" (fn e-switch [i &]
(set *no-file* false)
(set no-file false)
(eval-string (in args (+ i 1)))
2)
"d" (fn [&] (set *debug* true) 1)
"E" (fn E-switch [i &]
(set no-file false)
(def subargs (array/slice args (+ i 2)))
(def src ~|,(parse (in args (+ i 1))))
(def thunk (compile src))
(if (function? thunk)
((thunk) ;subargs)
(error (get thunk :error)))
math/inf)
"d" (fn [&] (set debug-flag true) 1)
"w" (fn [i &] (set warn-level (get-lint-level i)) 2)
"x" (fn [i &] (set error-level (get-lint-level i)) 2)
"R" (fn [&] (setdyn :profilepath nil) 1)})
(defn- dohandler [n i &]
@ -3449,27 +3626,37 @@
(def lenargs (length args))
(while (< i lenargs)
(def arg (in args i))
(if (and *handleopts* (= "-" (string/slice arg 0 1)))
(if (and handleopts (= "-" (string/slice arg 0 1)))
(+= i (dohandler (string/slice arg 1) i))
(do
(set *no-file* false)
(def env (make-env))
(def subargs (array/slice args i))
(put env :args subargs)
(if *compile-only*
(flycheck arg :exit *exit-on-error* :env env)
(set no-file false)
(if expect-image
(do
(dofile arg :exit *exit-on-error* :env env)
(if-let [main (get (in env 'main) :value)]
(let [thunk (compile [main ;(tuple/slice args i)] env arg)]
(if (function? thunk) (thunk) (error (thunk :error)))))))
(def env (load-image (slurp arg)))
(put env :args subargs)
(put env :lint-error error-level)
(put env :lint-warn warn-level)
(if debug-flag (put env :debug true))
(run-main env subargs arg))
(do
(def env (make-env))
(put env :args subargs)
(put env :lint-error error-level)
(put env :lint-warn warn-level)
(if debug-flag (put env :debug true))
(if compile-only
(flycheck arg :exit exit-on-error :env env)
(do
(dofile arg :exit exit-on-error :env env)
(run-main env subargs arg)))))
(set i lenargs))))
(if (or *should-repl* *no-file*)
(if (or should-repl no-file)
(if
*compile-only* (flycheck stdin :source "stdin" :exit *exit-on-error*)
compile-only (flycheck stdin :source "stdin" :exit exit-on-error)
(do
(if-not *quiet*
(if-not quiet
(print "Janet " janet/version "-" janet/build " " (os/which) "/" (os/arch) " - '(doc)' for help"))
(flush)
(defn getprompt [p]
@ -3483,13 +3670,15 @@
(when-let [profile.janet (dyn :profilepath)]
(def new-env (dofile profile.janet :exit true))
(merge-module env new-env "" false))
(if *debug* (put env :debug true))
(def getter (if *raw-stdin* getstdin getline))
(if debug-flag (put env :debug true))
(def getter (if raw-stdin getstdin getline))
(defn getchunk [buf p]
(getter (getprompt p) buf env))
(setdyn :pretty-format (if *colorize* "%.20Q" "%.20q"))
(setdyn :err-color (if *colorize* true))
(setdyn :doc-color (if *colorize* true))
(setdyn :pretty-format (if colorize "%.20Q" "%.20q"))
(setdyn :err-color (if colorize true))
(setdyn :doc-color (if colorize true))
(setdyn :lint-error error-level)
(setdyn :lint-warn error-level)
(repl getchunk nil env)))))
###
@ -3509,6 +3698,10 @@
(put into k (x k))))
into)
# Deprecate file/popen
(when-let [v (get root-env 'file/popen)]
(put v :deprecated true))
# Modify root-env to remove private symbols and
# flatten nested tables.
(loop [[k v] :in (pairs root-env)
@ -3518,6 +3711,9 @@
(put flat :doc nil))
(when (boot/config :no-sourcemaps)
(put flat :source-map nil))
# Fix directory separators on windows to make image identical between windows and non-windows
(when-let [sm (get flat :source-map)]
(put flat :source-map [(string/replace-all "\\" "/" (sm 0)) (sm 1) (sm 2)]))
(if (v :private)
(put root-env k nil)
(put root-env k flat)))
@ -3547,8 +3743,8 @@
(def feature-header "src/core/features.h")
(def local-headers
["src/core/util.h"
"src/core/state.h"
["src/core/state.h"
"src/core/util.h"
"src/core/gc.h"
"src/core/vector.h"
"src/core/fiber.h"
@ -3584,12 +3780,12 @@
"src/core/regalloc.c"
"src/core/run.c"
"src/core/specials.c"
"src/core/state.c"
"src/core/string.c"
"src/core/strtod.c"
"src/core/struct.c"
"src/core/symcache.c"
"src/core/table.c"
"src/core/thread.c"
"src/core/tuple.c"
"src/core/util.c"
"src/core/value.c"

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -4,10 +4,10 @@
#define JANETCONF_H
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 16
#define JANET_VERSION_PATCH 0
#define JANET_VERSION_MINOR 18
#define JANET_VERSION_PATCH 2
#define JANET_VERSION_EXTRA "-dev"
#define JANET_VERSION "1.16.0-dev"
#define JANET_VERSION "1.18.2-dev"
/* #define JANET_BUILD "local" */
@ -32,6 +32,7 @@
/* #define JANET_NO_REALPATH */
/* #define JANET_NO_SYMLINKS */
/* #define JANET_NO_UMASK */
/* #define JANET_NO_THREADS */
/* Other settings */
/* #define JANET_DEBUG */
@ -46,7 +47,9 @@
/* #define JANET_STACK_MAX 16384 */
/* #define JANET_OS_NAME my-custom-os */
/* #define JANET_ARCH_NAME pdp-8 */
/* #define JANET_EV_EPOLL */
/* #define JANET_EV_NO_EPOLL */
/* #define JANET_EV_NO_KQUEUE */
/* #define JANET_NO_INTERPRETER_INTERRUPT */
/* Custom vm allocator support */
/* #include <mimalloc.h> */

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -24,6 +24,12 @@
#include "features.h"
#include <janet.h>
#include "gc.h"
#include "state.h"
#ifdef JANET_EV
#ifdef JANET_WINDOWS
#include <windows.h>
#endif
#endif
#endif
/* Create new userdata */
@ -43,3 +49,100 @@ void *janet_abstract_end(void *x) {
void *janet_abstract(const JanetAbstractType *atype, size_t size) {
return janet_abstract_end(janet_abstract_begin(atype, size));
}
#ifdef JANET_EV
/*
* Threaded abstracts
*/
void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size) {
JanetAbstractHead *header = janet_malloc(sizeof(JanetAbstractHead) + size);
if (NULL == header) {
JANET_OUT_OF_MEMORY;
}
janet_vm.next_collection += size + sizeof(JanetAbstractHead);
header->gc.flags = JANET_MEMORY_THREADED_ABSTRACT;
header->gc.data.next = NULL; /* Clear memory for address sanitizers */
header->gc.data.refcount = 1;
header->size = size;
header->type = atype;
void *abstract = (void *) & (header->data);
janet_table_put(&janet_vm.threaded_abstracts, janet_wrap_abstract(abstract), janet_wrap_false());
return abstract;
}
void *janet_abstract_end_threaded(void *x) {
janet_gc_settype((void *)(janet_abstract_head(x)), JANET_MEMORY_THREADED_ABSTRACT);
return x;
}
void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size) {
return janet_abstract_end_threaded(janet_abstract_begin_threaded(atype, size));
}
/* Refcounting primitives and sync primitives */
#ifdef JANET_WINDOWS
static int32_t janet_incref(JanetAbstractHead *ab) {
return InterlockedIncrement(&ab->gc.data.refcount);
}
static int32_t janet_decref(JanetAbstractHead *ab) {
return InterlockedDecrement(&ab->gc.data.refcount);
}
void janet_os_mutex_init(JanetOSMutex *mutex) {
InitializeCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_deinit(JanetOSMutex *mutex) {
DeleteCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_lock(JanetOSMutex *mutex) {
EnterCriticalSection((CRITICAL_SECTION *) mutex);
}
void janet_os_mutex_unlock(JanetOSMutex *mutex) {
LeaveCriticalSection((CRITICAL_SECTION *) mutex);
}
#else
static int32_t janet_incref(JanetAbstractHead *ab) {
return __atomic_add_fetch(&ab->gc.data.refcount, 1, __ATOMIC_RELAXED);
}
static int32_t janet_decref(JanetAbstractHead *ab) {
return __atomic_add_fetch(&ab->gc.data.refcount, -1, __ATOMIC_RELAXED);
}
void janet_os_mutex_init(JanetOSMutex *mutex) {
pthread_mutex_init(mutex, NULL);
}
void janet_os_mutex_deinit(JanetOSMutex *mutex) {
pthread_mutex_destroy(mutex);
}
void janet_os_mutex_lock(JanetOSMutex *mutex) {
pthread_mutex_lock(mutex);
}
void janet_os_mutex_unlock(JanetOSMutex *mutex) {
pthread_mutex_unlock(mutex);
}
#endif
int32_t janet_abstract_incref(void *abst) {
return janet_incref(janet_abstract_head(abst));
}
int32_t janet_abstract_decref(void *abst) {
return janet_decref(janet_abstract_head(abst));
}
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -35,7 +35,7 @@ JanetArray *janet_array(int32_t capacity) {
JanetArray *array = janet_gcalloc(JANET_MEMORY_ARRAY, sizeof(JanetArray));
Janet *data = NULL;
if (capacity > 0) {
janet_vm_next_collection += capacity * sizeof(Janet);
janet_vm.next_collection += capacity * sizeof(Janet);
data = (Janet *) janet_malloc(sizeof(Janet) * (size_t) capacity);
if (NULL == data) {
JANET_OUT_OF_MEMORY;
@ -72,7 +72,7 @@ void janet_array_ensure(JanetArray *array, int32_t capacity, int32_t growth) {
if (NULL == newData) {
JANET_OUT_OF_MEMORY;
}
janet_vm_next_collection += (capacity - array->capacity) * sizeof(Janet);
janet_vm.next_collection += (capacity - array->capacity) * sizeof(Janet);
array->data = newData;
array->capacity = capacity;
}
@ -122,14 +122,19 @@ Janet janet_array_peek(JanetArray *array) {
/* C Functions */
static Janet cfun_array_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_new,
"(array/new capacity)",
"Creates a new empty array with a pre-allocated capacity. The same as "
"(array) but can be more efficient if the maximum size of an array is known.") {
janet_fixarity(argc, 1);
int32_t cap = janet_getinteger(argv, 0);
JanetArray *array = janet_array(cap);
return janet_wrap_array(array);
}
static Janet cfun_array_new_filled(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_new_filled,
"(array/new-filled count &opt value)",
"Creates a new array of `count` elements, all set to `value`, which defaults to nil. Returns the new array.") {
janet_arity(argc, 1, 2);
int32_t count = janet_getinteger(argv, 0);
Janet x = (argc == 2) ? argv[1] : janet_wrap_nil();
@ -141,7 +146,10 @@ static Janet cfun_array_new_filled(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_array_fill(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_fill,
"(array/fill arr &opt value)",
"Replace all elements of an array with `value` (defaulting to nil) without changing the length of the array. "
"Returns the modified array.") {
janet_arity(argc, 1, 2);
JanetArray *array = janet_getarray(argv, 0);
Janet x = (argc == 2) ? argv[1] : janet_wrap_nil();
@ -151,19 +159,26 @@ static Janet cfun_array_fill(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_pop(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_pop,
"(array/pop arr)",
"Remove the last element of the array and return it. If the array is empty, will return nil. Modifies "
"the input array.") {
janet_fixarity(argc, 1);
JanetArray *array = janet_getarray(argv, 0);
return janet_array_pop(array);
}
static Janet cfun_array_peek(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_peek,
"(array/peek arr)",
"Returns the last element of the array. Does not modify the array.") {
janet_fixarity(argc, 1);
JanetArray *array = janet_getarray(argv, 0);
return janet_array_peek(array);
}
static Janet cfun_array_push(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_push,
"(array/push arr x)",
"Insert an element in the end of an array. Modifies the input array and returns it.") {
janet_arity(argc, 1, -1);
JanetArray *array = janet_getarray(argv, 0);
if (INT32_MAX - argc + 1 <= array->count) {
@ -176,7 +191,12 @@ static Janet cfun_array_push(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_ensure(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_ensure,
"(array/ensure arr capacity growth)",
"Ensures that the memory backing the array is large enough for `capacity` "
"items at the given rate of growth. Capacity and growth must be integers. "
"If the backing capacity is already enough, then this function does nothing. "
"Otherwise, the backing memory will be reallocated so that there is enough space.") {
janet_fixarity(argc, 3);
JanetArray *array = janet_getarray(argv, 0);
int32_t newcount = janet_getinteger(argv, 1);
@ -186,7 +206,13 @@ static Janet cfun_array_ensure(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_slice,
"(array/slice arrtup &opt start end)",
"Takes a slice of array or tuple from `start` to `end`. The range is half open, "
"[start, end). Indexes can also be negative, indicating indexing from the "
"end of the array. By default, `start` is 0 and `end` is the length of the array. "
"Note that index -1 is synonymous with index `(length arrtup)` to allow a full "
"negative slice range. Returns a new array.") {
JanetView view = janet_getindexed(argv, 0);
JanetRange range = janet_getslice(argc, argv);
JanetArray *array = janet_array(range.end - range.start);
@ -196,7 +222,12 @@ static Janet cfun_array_slice(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_array_concat(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_concat,
"(array/concat arr & parts)",
"Concatenates a variable number of arrays (and tuples) into the first argument, "
"which must be an array. If any of the parts are arrays or tuples, their elements will "
"be inserted into the array. Otherwise, each part in `parts` will be appended to `arr` in order. "
"Return the modified array `arr`.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetArray *array = janet_getarray(argv, 0);
@ -210,6 +241,11 @@ static Janet cfun_array_concat(int32_t argc, Janet *argv) {
int32_t j, len = 0;
const Janet *vals = NULL;
janet_indexed_view(argv[i], &vals, &len);
if (array->data == vals) {
int32_t newcount = array->count + len;
janet_array_ensure(array, newcount, 2);
janet_indexed_view(argv[i], &vals, &len);
}
for (j = 0; j < len; j++)
janet_array_push(array, vals[j]);
}
@ -219,7 +255,12 @@ static Janet cfun_array_concat(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_array_insert(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_insert,
"(array/insert arr at & xs)",
"Insert all `xs` into array `arr` at index `at`. `at` should be an integer between "
"0 and the length of the array. A negative value for `at` will index backwards from "
"the end of the array, such that inserting at -1 appends to the array. "
"Returns the array.") {
size_t chunksize, restsize;
janet_arity(argc, 2, -1);
JanetArray *array = janet_getarray(argv, 0);
@ -245,7 +286,12 @@ static Janet cfun_array_insert(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_remove(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_remove,
"(array/remove arr at &opt n)",
"Remove up to `n` elements starting at index `at` in array `arr`. `at` can index from "
"the end of the array with a negative index, and `n` must be a non-negative integer. "
"By default, `n` is 1. "
"Returns the array.") {
janet_arity(argc, 2, 3);
JanetArray *array = janet_getarray(argv, 0);
int32_t at = janet_getinteger(argv, 1);
@ -270,7 +316,9 @@ static Janet cfun_array_remove(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_trim(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_trim,
"(array/trim arr)",
"Set the backing capacity of an array to its current length. Returns the modified array.") {
janet_fixarity(argc, 1);
JanetArray *array = janet_getarray(argv, 0);
if (array->count) {
@ -290,103 +338,33 @@ static Janet cfun_array_trim(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_array_clear(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_array_clear,
"(array/clear arr)",
"Empties an array, setting it's count to 0 but does not free the backing capacity. "
"Returns the modified array.") {
janet_fixarity(argc, 1);
JanetArray *array = janet_getarray(argv, 0);
array->count = 0;
return argv[0];
}
static const JanetReg array_cfuns[] = {
{
"array/new", cfun_array_new,
JDOC("(array/new capacity)\n\n"
"Creates a new empty array with a pre-allocated capacity. The same as "
"(array) but can be more efficient if the maximum size of an array is known.")
},
{
"array/new-filled", cfun_array_new_filled,
JDOC("(array/new-filled count &opt value)\n\n"
"Creates a new array of count elements, all set to value, which defaults to nil. Returns the new array.")
},
{
"array/fill", cfun_array_fill,
JDOC("(array/fill arr &opt value)\n\n"
"Replace all elements of an array with value (defaulting to nil) without changing the length of the array. "
"Returns the modified array.")
},
{
"array/pop", cfun_array_pop,
JDOC("(array/pop arr)\n\n"
"Remove the last element of the array and return it. If the array is empty, will return nil. Modifies "
"the input array.")
},
{
"array/peek", cfun_array_peek,
JDOC("(array/peek arr)\n\n"
"Returns the last element of the array. Does not modify the array.")
},
{
"array/push", cfun_array_push,
JDOC("(array/push arr x)\n\n"
"Insert an element in the end of an array. Modifies the input array and returns it.")
},
{
"array/ensure", cfun_array_ensure,
JDOC("(array/ensure arr capacity growth)\n\n"
"Ensures that the memory backing the array is large enough for capacity "
"items at the given rate of growth. Capacity and growth must be integers. "
"If the backing capacity is already enough, then this function does nothing. "
"Otherwise, the backing memory will be reallocated so that there is enough space.")
},
{
"array/slice", cfun_array_slice,
JDOC("(array/slice arrtup &opt start end)\n\n"
"Takes a slice of array or tuple from start to end. The range is half open, "
"[start, end). Indexes can also be negative, indicating indexing from the end of the "
"end of the array. By default, start is 0 and end is the length of the array. "
"Note that index -1 is synonymous with index (length arrtup) to allow a full "
"negative slice range. Returns a new array.")
},
{
"array/concat", cfun_array_concat,
JDOC("(array/concat arr & parts)\n\n"
"Concatenates a variable number of arrays (and tuples) into the first argument "
"which must be an array. If any of the parts are arrays or tuples, their elements will "
"be inserted into the array. Otherwise, each part in parts will be appended to arr in order. "
"Return the modified array arr.")
},
{
"array/insert", cfun_array_insert,
JDOC("(array/insert arr at & xs)\n\n"
"Insert all xs into array arr at index at. at should be an integer between "
"0 and the length of the array. A negative value for at will index backwards from "
"the end of the array, such that inserting at -1 appends to the array. "
"Returns the array.")
},
{
"array/remove", cfun_array_remove,
JDOC("(array/remove arr at &opt n)\n\n"
"Remove up to n elements starting at index at in array arr. at can index from "
"the end of the array with a negative index, and n must be a non-negative integer. "
"By default, n is 1. "
"Returns the array.")
},
{
"array/trim", cfun_array_trim,
JDOC("(array/trim arr)\n\n"
"Set the backing capacity of an array to its current length. Returns the modified array.")
},
{
"array/clear", cfun_array_clear,
JDOC("(array/clear arr)\n\n"
"Empties an array, setting it's count to 0 but does not free the backing capacity. "
"Returns the modified array.")
},
{NULL, NULL, NULL}
};
/* Load the array module */
void janet_lib_array(JanetTable *env) {
janet_core_cfuns(env, NULL, array_cfuns);
JanetRegExt array_cfuns[] = {
JANET_CORE_REG("array/new", cfun_array_new),
JANET_CORE_REG("array/new-filled", cfun_array_new_filled),
JANET_CORE_REG("array/fill", cfun_array_fill),
JANET_CORE_REG("array/pop", cfun_array_pop),
JANET_CORE_REG("array/peek", cfun_array_peek),
JANET_CORE_REG("array/push", cfun_array_push),
JANET_CORE_REG("array/ensure", cfun_array_ensure),
JANET_CORE_REG("array/slice", cfun_array_slice),
JANET_CORE_REG("array/concat", cfun_array_concat),
JANET_CORE_REG("array/insert", cfun_array_insert),
JANET_CORE_REG("array/remove", cfun_array_remove),
JANET_CORE_REG("array/trim", cfun_array_trim),
JANET_CORE_REG("array/clear", cfun_array_clear),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, array_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -942,8 +942,12 @@ Janet janet_disasm(JanetFuncDef *def) {
return janet_wrap_struct(janet_table_to_struct(ret));
}
/* C Function for assembly */
static Janet cfun_asm(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_asm,
"(asm assembly)",
"Returns a new function that is the compiled result of the assembly.\n"
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
"to the return value of disasm. Will throw an\n"
"error on invalid assembly.") {
janet_fixarity(argc, 1);
JanetAssembleResult res;
res = janet_asm(argv[0], 0);
@ -953,7 +957,24 @@ static Janet cfun_asm(int32_t argc, Janet *argv) {
return janet_wrap_function(janet_thunk(res.funcdef));
}
static Janet cfun_disasm(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_disasm,
"(disasm func &opt field)",
"Returns assembly that could be used to compile the given function. "
"func must be a function, not a c function. Will throw on error on a badly "
"typed argument. If given a field name, will only return that part of the function assembly. "
"Possible fields are:\n\n"
"* :arity - number of required and optional arguments.\n"
"* :min-arity - minimum number of arguments function can be called with.\n"
"* :max-arity - maximum number of arguments function can be called with.\n"
"* :vararg - true if function can take a variable number of arguments.\n"
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n"
"* :source - name of source file that this function was compiled from.\n"
"* :name - name of function.\n"
"* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n"
"* :constants - an array of constants referenced by this function.\n"
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n"
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n"
"* :defs - other function definitions that this function may instantiate.\n") {
janet_arity(argc, 1, 2);
JanetFunction *f = janet_getfunction(argv, 0);
if (argc == 2) {
@ -976,41 +997,14 @@ static Janet cfun_disasm(int32_t argc, Janet *argv) {
}
}
static const JanetReg asm_cfuns[] = {
{
"asm", cfun_asm,
JDOC("(asm assembly)\n\n"
"Returns a new function that is the compiled result of the assembly.\n"
"The syntax for the assembly can be found on the Janet website, and should correspond\n"
"to the return value of disasm. Will throw an\n"
"error on invalid assembly.")
},
{
"disasm", cfun_disasm,
JDOC("(disasm func &opt field)\n\n"
"Returns assembly that could be used to compile the given function.\n"
"func must be a function, not a c function. Will throw on error on a badly\n"
"typed argument. If given a field name, will only return that part of the function assembly.\n"
"Possible fields are:\n\n"
"* :arity - number of required and optional arguments.\n\n"
"* :min-arity - minimum number of arguments function can be called with.\n\n"
"* :max-arity - maximum number of arguments function can be called with.\n\n"
"* :vararg - true if function can take a variable number of arguments.\n\n"
"* :bytecode - array of parsed bytecode instructions. Each instruction is a tuple.\n\n"
"* :source - name of source file that this function was compiled from.\n\n"
"* :name - name of function.\n\n"
"* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n\n"
"* :constants - an array of constants referenced by this function.\n\n"
"* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n\n"
"* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n\n"
"* :defs - other function definitions that this function may instantiate.\n")
},
{NULL, NULL, NULL}
};
/* Load the library */
void janet_lib_asm(JanetTable *env) {
janet_core_cfuns(env, NULL, asm_cfuns);
JanetRegExt asm_cfuns[] = {
JANET_CORE_REG("asm", cfun_asm),
JANET_CORE_REG("disasm", cfun_disasm),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, asm_cfuns);
}
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -162,14 +162,20 @@ void janet_buffer_push_u64(JanetBuffer *buffer, uint64_t x) {
/* C functions */
static Janet cfun_buffer_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_new,
"(buffer/new capacity)",
"Creates a new, empty buffer with enough backing memory for capacity bytes. "
"Returns a new buffer of length 0.") {
janet_fixarity(argc, 1);
int32_t cap = janet_getinteger(argv, 0);
JanetBuffer *buffer = janet_buffer(cap);
return janet_wrap_buffer(buffer);
}
static Janet cfun_buffer_new_filled(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_new_filled,
"(buffer/new-filled count &opt byte)",
"Creates a new buffer of length count filled with byte. By default, byte is 0. "
"Returns the new buffer.") {
janet_arity(argc, 1, 2);
int32_t count = janet_getinteger(argv, 0);
int32_t byte = 0;
@ -183,7 +189,10 @@ static Janet cfun_buffer_new_filled(int32_t argc, Janet *argv) {
return janet_wrap_buffer(buffer);
}
static Janet cfun_buffer_fill(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_fill,
"(buffer/fill buffer &opt byte)",
"Fill up a buffer with bytes, defaulting to 0s. Does not change the buffer's length. "
"Returns the modified buffer.") {
janet_arity(argc, 1, 2);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
int32_t byte = 0;
@ -196,7 +205,10 @@ static Janet cfun_buffer_fill(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_trim(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_trim,
"(buffer/trim buffer)",
"Set the backing capacity of the buffer to the current length of the buffer. Returns the "
"modified buffer.") {
janet_fixarity(argc, 1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
if (buffer->count < buffer->capacity) {
@ -211,7 +223,10 @@ static Janet cfun_buffer_trim(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_u8(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_u8,
"(buffer/push-byte buffer & xs)",
"Append bytes to a buffer. Will expand the buffer as necessary. "
"Returns the modified buffer. Will throw an error if the buffer overflows.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
@ -221,7 +236,11 @@ static Janet cfun_buffer_u8(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_word(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_word,
"(buffer/push-word buffer & xs)",
"Append machine words to a buffer. The 4 bytes of the integer are appended "
"in twos complement, little endian order, unsigned for all x. Returns the modified buffer. Will "
"throw an error if the buffer overflows.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
@ -235,7 +254,12 @@ static Janet cfun_buffer_word(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_chars(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_chars,
"(buffer/push-string buffer & xs)",
"Push byte sequences onto the end of a buffer. "
"Will accept any of strings, keywords, symbols, and buffers. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
@ -250,7 +274,13 @@ static Janet cfun_buffer_chars(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_push(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_push,
"(buffer/push buffer & xs)",
"Push both individual bytes and byte sequences to a buffer. For each x in xs, "
"push the byte if x is an integer, otherwise push the bytesequence to the buffer. "
"Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.") {
int32_t i;
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
@ -270,14 +300,19 @@ static Janet cfun_buffer_push(int32_t argc, Janet *argv) {
}
static Janet cfun_buffer_clear(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_clear,
"(buffer/clear buffer)",
"Sets the size of a buffer to 0 and empties it. The buffer retains "
"its memory so it can be efficiently refilled. Returns the modified buffer.") {
janet_fixarity(argc, 1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
buffer->count = 0;
return argv[0];
}
static Janet cfun_buffer_popn(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_popn,
"(buffer/popn buffer n)",
"Removes the last n bytes from the buffer. Returns the modified buffer.") {
janet_fixarity(argc, 2);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
int32_t n = janet_getinteger(argv, 1);
@ -290,7 +325,12 @@ static Janet cfun_buffer_popn(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_slice,
"(buffer/slice bytes &opt start end)",
"Takes a slice of a byte sequence from start to end. The range is half open, "
"[start, end). Indexes can also be negative, indicating indexing from the end of the "
"end of the array. By default, start is 0 and end is the length of the buffer. "
"Returns a new buffer.") {
JanetByteView view = janet_getbytes(argv, 0);
JanetRange range = janet_getslice(argc, argv);
JanetBuffer *buffer = janet_buffer(range.end - range.start);
@ -314,7 +354,9 @@ static void bitloc(int32_t argc, Janet *argv, JanetBuffer **b, int32_t *index, i
*bit = which_bit;
}
static Janet cfun_buffer_bitset(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_bitset,
"(buffer/bit-set buffer index)",
"Sets the bit at the given bit-index. Returns the buffer.") {
int bit;
int32_t index;
JanetBuffer *buffer;
@ -323,7 +365,9 @@ static Janet cfun_buffer_bitset(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_bitclear(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_bitclear,
"(buffer/bit-clear buffer index)",
"Clears the bit at the given bit-index. Returns the buffer.") {
int bit;
int32_t index;
JanetBuffer *buffer;
@ -332,7 +376,9 @@ static Janet cfun_buffer_bitclear(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_bitget(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_bitget,
"(buffer/bit buffer index)",
"Gets the bit at the given bit-index. Returns true if the bit is set, false if not.") {
int bit;
int32_t index;
JanetBuffer *buffer;
@ -340,7 +386,9 @@ static Janet cfun_buffer_bitget(int32_t argc, Janet *argv) {
return janet_wrap_boolean(buffer->data[index] & (1 << bit));
}
static Janet cfun_buffer_bittoggle(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_bittoggle,
"(buffer/bit-toggle buffer index)",
"Toggles the bit at the given bit index in buffer. Returns the buffer.") {
int bit;
int32_t index;
JanetBuffer *buffer;
@ -349,7 +397,11 @@ static Janet cfun_buffer_bittoggle(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_blit(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_blit,
"(buffer/blit dest src &opt dest-start src-start src-end)",
"Insert the contents of src into dest. Can optionally take indices that "
"indicate which part of src to copy into which part of dest. Indices can be "
"negative to index from the end of src or dest. Returns dest.") {
janet_arity(argc, 2, 5);
JanetBuffer *dest = janet_getbuffer(argv, 0);
JanetByteView src = janet_getbytes(argv, 1);
@ -386,7 +438,10 @@ static Janet cfun_buffer_blit(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_buffer_format(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_buffer_format,
"(buffer/format buffer format & args)",
"Snprintf like functionality for printing values into a buffer. Returns "
" the modified buffer.") {
janet_arity(argc, 2, -1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
const char *strfrmt = (const char *) janet_getstring(argv, 1);
@ -394,116 +449,26 @@ static Janet cfun_buffer_format(int32_t argc, Janet *argv) {
return argv[0];
}
static const JanetReg buffer_cfuns[] = {
{
"buffer/new", cfun_buffer_new,
JDOC("(buffer/new capacity)\n\n"
"Creates a new, empty buffer with enough backing memory for capacity bytes. "
"Returns a new buffer of length 0.")
},
{
"buffer/new-filled", cfun_buffer_new_filled,
JDOC("(buffer/new-filled count &opt byte)\n\n"
"Creates a new buffer of length count filled with byte. By default, byte is 0. "
"Returns the new buffer.")
},
{
"buffer/fill", cfun_buffer_fill,
JDOC("(buffer/fill buffer &opt byte)\n\n"
"Fill up a buffer with bytes, defaulting to 0s. Does not change the buffer's length. "
"Returns the modified buffer.")
},
{
"buffer/trim", cfun_buffer_trim,
JDOC("(buffer/trim buffer)\n\n"
"Set the backing capacity of the buffer to the current length of the buffer. Returns the "
"modified buffer.")
},
{
"buffer/push-byte", cfun_buffer_u8,
JDOC("(buffer/push-byte buffer & xs)\n\n"
"Append bytes to a buffer. Will expand the buffer as necessary. "
"Returns the modified buffer. Will throw an error if the buffer overflows.")
},
{
"buffer/push-word", cfun_buffer_word,
JDOC("(buffer/push-word buffer & xs)\n\n"
"Append machine words to a buffer. The 4 bytes of the integer are appended "
"in twos complement, little endian order, unsigned for all x. Returns the modified buffer. Will "
"throw an error if the buffer overflows.")
},
{
"buffer/push-string", cfun_buffer_chars,
JDOC("(buffer/push-string buffer & xs)\n\n"
"Push byte sequences onto the end of a buffer. "
"Will accept any of strings, keywords, symbols, and buffers. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.")
},
{
"buffer/push", cfun_buffer_push,
JDOC("(buffer/push buffer & xs)\n\n"
"Push both individual bytes and byte sequences to a buffer. For each x in xs, "
"push the byte if x is an integer, otherwise push the bytesequence to the buffer. "
"Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. "
"Returns the modified buffer. "
"Will throw an error if the buffer overflows.")
},
{
"buffer/popn", cfun_buffer_popn,
JDOC("(buffer/popn buffer n)\n\n"
"Removes the last n bytes from the buffer. Returns the modified buffer.")
},
{
"buffer/clear", cfun_buffer_clear,
JDOC("(buffer/clear buffer)\n\n"
"Sets the size of a buffer to 0 and empties it. The buffer retains "
"its memory so it can be efficiently refilled. Returns the modified buffer.")
},
{
"buffer/slice", cfun_buffer_slice,
JDOC("(buffer/slice bytes &opt start end)\n\n"
"Takes a slice of a byte sequence from start to end. The range is half open, "
"[start, end). Indexes can also be negative, indicating indexing from the end of the "
"end of the array. By default, start is 0 and end is the length of the buffer. "
"Returns a new buffer.")
},
{
"buffer/bit-set", cfun_buffer_bitset,
JDOC("(buffer/bit-set buffer index)\n\n"
"Sets the bit at the given bit-index. Returns the buffer.")
},
{
"buffer/bit-clear", cfun_buffer_bitclear,
JDOC("(buffer/bit-clear buffer index)\n\n"
"Clears the bit at the given bit-index. Returns the buffer.")
},
{
"buffer/bit", cfun_buffer_bitget,
JDOC("(buffer/bit buffer index)\n\n"
"Gets the bit at the given bit-index. Returns true if the bit is set, false if not.")
},
{
"buffer/bit-toggle", cfun_buffer_bittoggle,
JDOC("(buffer/bit-toggle buffer index)\n\n"
"Toggles the bit at the given bit index in buffer. Returns the buffer.")
},
{
"buffer/blit", cfun_buffer_blit,
JDOC("(buffer/blit dest src &opt dest-start src-start src-end)\n\n"
"Insert the contents of src into dest. Can optionally take indices that "
"indicate which part of src to copy into which part of dest. Indices can be "
"negative to index from the end of src or dest. Returns dest.")
},
{
"buffer/format", cfun_buffer_format,
JDOC("(buffer/format buffer format & args)\n\n"
"Snprintf like functionality for printing values into a buffer. Returns "
" the modified buffer.")
},
{NULL, NULL, NULL}
};
void janet_lib_buffer(JanetTable *env) {
janet_core_cfuns(env, NULL, buffer_cfuns);
JanetRegExt buffer_cfuns[] = {
JANET_CORE_REG("buffer/new", cfun_buffer_new),
JANET_CORE_REG("buffer/new-filled", cfun_buffer_new_filled),
JANET_CORE_REG("buffer/fill", cfun_buffer_fill),
JANET_CORE_REG("buffer/trim", cfun_buffer_trim),
JANET_CORE_REG("buffer/push-byte", cfun_buffer_u8),
JANET_CORE_REG("buffer/push-word", cfun_buffer_word),
JANET_CORE_REG("buffer/push-string", cfun_buffer_chars),
JANET_CORE_REG("buffer/push", cfun_buffer_push),
JANET_CORE_REG("buffer/popn", cfun_buffer_popn),
JANET_CORE_REG("buffer/clear", cfun_buffer_clear),
JANET_CORE_REG("buffer/slice", cfun_buffer_slice),
JANET_CORE_REG("buffer/bit-set", cfun_buffer_bitset),
JANET_CORE_REG("buffer/bit-clear", cfun_buffer_bitclear),
JANET_CORE_REG("buffer/bit", cfun_buffer_bitget),
JANET_CORE_REG("buffer/bit-toggle", cfun_buffer_bittoggle),
JANET_CORE_REG("buffer/blit", cfun_buffer_blit),
JANET_CORE_REG("buffer/format", cfun_buffer_format),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, buffer_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -51,15 +51,15 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) {
}
void janet_signalv(JanetSignal sig, Janet message) {
if (janet_vm_return_reg != NULL) {
*janet_vm_return_reg = message;
if (NULL != janet_vm_fiber) {
janet_vm_fiber->flags |= JANET_FIBER_DID_LONGJUMP;
if (janet_vm.return_reg != NULL) {
*janet_vm.return_reg = message;
if (NULL != janet_vm.fiber) {
janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP;
}
#if defined(JANET_BSD) || defined(JANET_APPLE)
_longjmp(*janet_vm_jmp_buf, sig);
_longjmp(*janet_vm.signal_buf, sig);
#else
longjmp(*janet_vm_jmp_buf, sig);
longjmp(*janet_vm.signal_buf, sig);
#endif
} else {
const char *str = (const char *)janet_formatc("janet top level signal - %v\n", message);
@ -212,7 +212,7 @@ const char *janet_getcstring(const Janet *argv, int32_t n) {
const uint8_t *jstr = janet_getstring(argv, n);
const char *cstr = (const char *)jstr;
if (strlen(cstr) != (size_t) janet_string_length(jstr)) {
janet_panicf("string %v contains embedded 0s");
janet_panic("string contains embedded 0s");
}
return cstr;
}
@ -358,26 +358,26 @@ JanetRange janet_getslice(int32_t argc, const Janet *argv) {
}
Janet janet_dyn(const char *name) {
if (!janet_vm_fiber) {
if (!janet_vm_top_dyns) return janet_wrap_nil();
return janet_table_get(janet_vm_top_dyns, janet_ckeywordv(name));
if (!janet_vm.fiber) {
if (!janet_vm.top_dyns) return janet_wrap_nil();
return janet_table_get(janet_vm.top_dyns, janet_ckeywordv(name));
}
if (janet_vm_fiber->env) {
return janet_table_get(janet_vm_fiber->env, janet_ckeywordv(name));
if (janet_vm.fiber->env) {
return janet_table_get(janet_vm.fiber->env, janet_ckeywordv(name));
} else {
return janet_wrap_nil();
}
}
void janet_setdyn(const char *name, Janet value) {
if (!janet_vm_fiber) {
if (!janet_vm_top_dyns) janet_vm_top_dyns = janet_table(10);
janet_table_put(janet_vm_top_dyns, janet_ckeywordv(name), value);
if (!janet_vm.fiber) {
if (!janet_vm.top_dyns) janet_vm.top_dyns = janet_table(10);
janet_table_put(janet_vm.top_dyns, janet_ckeywordv(name), value);
} else {
if (!janet_vm_fiber->env) {
janet_vm_fiber->env = janet_table(1);
if (!janet_vm.fiber->env) {
janet_vm.fiber->env = janet_table(1);
}
janet_table_put(janet_vm_fiber->env, janet_ckeywordv(name), value);
janet_table_put(janet_vm.fiber->env, janet_ckeywordv(name), value);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -53,6 +53,36 @@ void janetc_cerror(JanetCompiler *c, const char *m) {
janetc_error(c, janet_cstring(m));
}
static const char *janet_lint_level_names[] = {
"relaxed",
"normal",
"strict"
};
/* Emit compiler linter messages */
void janetc_lintf(JanetCompiler *c, JanetCompileLintLevel level, const char *format, ...) {
if (NULL != c->lints) {
/* format message */
va_list args;
JanetBuffer buffer;
int32_t len = 0;
while (format[len]) len++;
janet_buffer_init(&buffer, len);
va_start(args, format);
janet_formatbv(&buffer, format, args);
va_end(args);
const uint8_t *str = janet_string(buffer.data, buffer.count);
janet_buffer_deinit(&buffer);
/* construct linting payload */
Janet *payload = janet_tuple_begin(4);
payload[0] = janet_ckeywordv(janet_lint_level_names[level]);
payload[1] = c->current_mapping.line == -1 ? janet_wrap_nil() : janet_wrap_integer(c->current_mapping.line);
payload[2] = c->current_mapping.column == -1 ? janet_wrap_nil() : janet_wrap_integer(c->current_mapping.column);
payload[3] = janet_wrap_string(str);
janet_array_push(c->lints, janet_wrap_tuple(janet_tuple_end(payload)));
}
}
/* Free a slot */
void janetc_freeslot(JanetCompiler *c, JanetSlot s) {
if (s.flags & (JANET_SLOT_CONSTANT | JANET_SLOT_REF | JANET_SLOT_NAMED)) return;
@ -199,24 +229,41 @@ JanetSlot janetc_resolve(
/* Symbol not found - check for global */
{
Janet check;
JanetBindingType btype = janet_resolve(c->env, sym, &check);
switch (btype) {
JanetBinding binding = janet_resolve_ext(c->env, sym);
switch (binding.type) {
default:
case JANET_BINDING_NONE:
janetc_error(c, janet_formatc("unknown symbol %q", janet_wrap_symbol(sym)));
return janetc_cslot(janet_wrap_nil());
case JANET_BINDING_DEF:
case JANET_BINDING_MACRO: /* Macro should function like defs when not in calling pos */
return janetc_cslot(check);
ret = janetc_cslot(binding.value);
break;
case JANET_BINDING_VAR: {
JanetSlot ret = janetc_cslot(check);
/* TODO save type info */
ret = janetc_cslot(binding.value);
ret.flags |= JANET_SLOT_REF | JANET_SLOT_NAMED | JANET_SLOT_MUTABLE | JANET_SLOTTYPE_ANY;
ret.flags &= ~JANET_SLOT_CONSTANT;
return ret;
break;
}
}
JanetCompileLintLevel depLevel = JANET_C_LINT_RELAXED;
switch (binding.deprecation) {
case JANET_BINDING_DEP_NONE:
break;
case JANET_BINDING_DEP_RELAXED:
depLevel = JANET_C_LINT_RELAXED;
break;
case JANET_BINDING_DEP_NORMAL:
depLevel = JANET_C_LINT_NORMAL;
break;
case JANET_BINDING_DEP_STRICT:
depLevel = JANET_C_LINT_STRICT;
break;
}
if (binding.deprecation != JANET_BINDING_DEP_NONE) {
janetc_lintf(c, depLevel, "%q is deprecated", janet_wrap_symbol(sym));
}
return ret;
}
/* Symbol was found */
@ -399,6 +446,7 @@ void janetc_throwaway(JanetFopts opts, Janet x) {
int32_t mapbufstart = janet_v_count(c->mapbuffer);
janetc_scope(&unusedScope, c, JANET_SCOPE_UNUSED, "unusued");
janetc_value(opts, x);
janetc_lintf(c, JANET_C_LINT_STRICT, "dead code, consider removing %.2q", x);
janetc_popscope(c);
if (c->buffer) {
janet_v__cnt(c->buffer) = bufstart;
@ -631,6 +679,9 @@ static int macroexpand1(
Janet tempOut;
JanetSignal status = janet_continue(fiberp, janet_wrap_nil(), &tempOut);
janet_table_put(c->env, mf_kw, janet_wrap_nil());
if (c->lints) {
janet_table_put(c->env, janet_ckeywordv("macro-lints"), janet_wrap_array(c->lints));
}
janet_gcunlock(lock);
if (status != JANET_SIGNAL_OK) {
const uint8_t *es = janet_formatc("(macro) %V", tempOut);
@ -825,7 +876,7 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) {
}
/* Initialize a compiler */
static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where) {
static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where, JanetArray *lints) {
c->scope = NULL;
c->buffer = NULL;
c->mapbuffer = NULL;
@ -834,6 +885,7 @@ static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where)
c->source = where;
c->current_mapping.line = -1;
c->current_mapping.column = -1;
c->lints = lints;
/* Init result */
c->result.error = NULL;
c->result.status = JANET_COMPILE_OK;
@ -851,12 +903,13 @@ static void janetc_deinit(JanetCompiler *c) {
}
/* Compile a form. */
JanetCompileResult janet_compile(Janet source, JanetTable *env, const uint8_t *where) {
JanetCompileResult janet_compile_lint(Janet source,
JanetTable *env, const uint8_t *where, JanetArray *lints) {
JanetCompiler c;
JanetScope rootscope;
JanetFopts fopts;
janetc_init(&c, env, where);
janetc_init(&c, env, where, lints);
/* Push a function scope */
janetc_scope(&rootscope, &c, JANET_SCOPE_FUNCTION | JANET_SCOPE_TOP, "root");
@ -884,19 +937,31 @@ JanetCompileResult janet_compile(Janet source, JanetTable *env, const uint8_t *w
return c.result;
}
JanetCompileResult janet_compile(Janet source, JanetTable *env, const uint8_t *where) {
return janet_compile_lint(source, env, where, NULL);
}
/* C Function for compiling */
static Janet cfun(int32_t argc, Janet *argv) {
janet_arity(argc, 1, 3);
JanetTable *env = argc > 1 ? janet_gettable(argv, 1) : janet_vm_fiber->env;
JANET_CORE_FN(cfun,
"(compile ast &opt env source lints)",
"Compiles an Abstract Syntax Tree (ast) into a function. "
"Pair the compile function with parsing functionality to implement "
"eval. Returns a new function and does not modify ast. Returns an error "
"struct with keys :line, :column, and :error if compilation fails. "
"If a `lints` array is given, linting messages will be appended to the array. "
"Each message will be a tuple of the form `(level line col message)`.") {
janet_arity(argc, 1, 4);
JanetTable *env = argc > 1 ? janet_gettable(argv, 1) : janet_vm.fiber->env;
if (NULL == env) {
env = janet_table(0);
janet_vm_fiber->env = env;
janet_vm.fiber->env = env;
}
const uint8_t *source = NULL;
if (argc == 3) {
if (argc >= 3) {
source = janet_getstring(argv, 2);
}
JanetCompileResult res = janet_compile(argv[0], env, source);
JanetArray *lints = (argc >= 4) ? janet_getarray(argv, 3) : NULL;
JanetCompileResult res = janet_compile_lint(argv[0], env, source, lints);
if (res.status == JANET_COMPILE_OK) {
return janet_wrap_function(janet_thunk(res.funcdef));
} else {
@ -915,18 +980,10 @@ static Janet cfun(int32_t argc, Janet *argv) {
}
}
static const JanetReg compile_cfuns[] = {
{
"compile", cfun,
JDOC("(compile ast &opt env source)\n\n"
"Compiles an Abstract Syntax Tree (ast) into a function. "
"Pair the compile function with parsing functionality to implement "
"eval. Returns a new function and does not modify ast. Returns an error "
"struct with keys :line, :column, and :error if compilation fails.")
},
{NULL, NULL, NULL}
};
void janet_lib_compile(JanetTable *env) {
janet_core_cfuns(env, NULL, compile_cfuns);
JanetRegExt cfuns[] = {
JANET_CORE_REG("compile", cfun),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -29,6 +29,13 @@
#include "regalloc.h"
#endif
/* Levels for compiler warnings */
typedef enum {
JANET_C_LINT_RELAXED,
JANET_C_LINT_NORMAL,
JANET_C_LINT_STRICT
} JanetCompileLintLevel;
/* Tags for some functions for the prepared inliner */
#define JANET_FUN_DEBUG 1
#define JANET_FUN_ERROR 2
@ -78,10 +85,10 @@ typedef struct JanetSpecial JanetSpecial;
#define JANET_SLOT_MUTABLE 0x40000
#define JANET_SLOT_REF 0x80000
#define JANET_SLOT_RETURNED 0x100000
/* Needed for handling single element arrays as global vars. */
/* Used for unquote-splicing */
#define JANET_SLOT_SPLICED 0x200000
#define JANET_SLOT_DEP_NOTE 0x200000
#define JANET_SLOT_DEP_WARN 0x400000
#define JANET_SLOT_DEP_ERROR 0x800000
#define JANET_SLOT_SPLICED 0x1000000
#define JANET_SLOTTYPE_ANY 0xFFFF
@ -164,6 +171,9 @@ struct JanetCompiler {
/* Prevent unbounded recursion */
int recursion_guard;
/* Collect linting results */
JanetArray *lints;
};
#define JANET_FOPTS_TAIL 0x10000
@ -230,6 +240,9 @@ JanetSlot janetc_return(JanetCompiler *c, JanetSlot s);
void janetc_error(JanetCompiler *c, const uint8_t *m);
void janetc_cerror(JanetCompiler *c, const char *m);
/* Linting */
void janetc_lintf(JanetCompiler *C, JanetCompileLintLevel level, const char *format, ...);
/* Dispatch to correct form compiler */
JanetSlot janetc_value(JanetFopts opts, Janet x);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -35,6 +35,13 @@ extern const unsigned char *janet_core_image;
extern size_t janet_core_image_size;
#endif
/* Docstrings should only exist during bootstrap */
#ifdef JANET_BOOTSTRAP
#define JDOC(x) (x)
#else
#define JDOC(x) NULL
#endif
/* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries
* with native code. */
#if defined(JANET_NO_DYNAMIC_MODULES)
@ -130,7 +137,7 @@ static const char *janet_dyncstring(const char *name, const char *dflt) {
const uint8_t *jstr = janet_unwrap_string(x);
const char *cstr = (const char *)jstr;
if (strlen(cstr) != (size_t) janet_string_length(jstr)) {
janet_panicf("string %v contains embedded 0s");
janet_panicf("string %v contains embedded 0s", x);
}
return cstr;
}
@ -143,7 +150,18 @@ static int is_path_sep(char c) {
}
/* Used for module system. */
static Janet janet_core_expand_path(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_expand_path,
"(module/expand-path path template)",
"Expands a path template as found in `module/paths` for `module/find`. "
"This takes in a path (the argument to require) and a template string, "
"to expand the path to a path that can be "
"used for importing files. The replacements are as follows:\n\n"
"* :all: -- the value of path verbatim\n\n"
"* :cur: -- the current file, or (dyn :current-file)\n\n"
"* :dir: -- the directory containing the current file\n\n"
"* :name: -- the name component of path, with extension if given\n\n"
"* :native: -- the extension used to load natives, .so or .dll\n\n"
"* :sys: -- the system path, or (dyn :syspath)") {
janet_fixarity(argc, 2);
const char *input = janet_getcstring(argv, 0);
const char *template = janet_getcstring(argv, 1);
@ -266,11 +284,13 @@ static Janet janet_core_expand_path(int32_t argc, Janet *argv) {
return janet_wrap_buffer(out);
}
static Janet janet_core_dyn(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_dyn,
"(dyn key &opt default)",
"Get a dynamic binding. Returns the default value (or nil) if no binding found.") {
janet_arity(argc, 1, 2);
Janet value;
if (janet_vm_fiber->env) {
value = janet_table_get(janet_vm_fiber->env, argv[0]);
if (janet_vm.fiber->env) {
value = janet_table_get(janet_vm.fiber->env, argv[0]);
} else {
value = janet_wrap_nil();
}
@ -280,16 +300,24 @@ static Janet janet_core_dyn(int32_t argc, Janet *argv) {
return value;
}
static Janet janet_core_setdyn(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_setdyn,
"(setdyn key value)",
"Set a dynamic binding. Returns value.") {
janet_fixarity(argc, 2);
if (!janet_vm_fiber->env) {
janet_vm_fiber->env = janet_table(2);
if (!janet_vm.fiber->env) {
janet_vm.fiber->env = janet_table(2);
}
janet_table_put(janet_vm_fiber->env, argv[0], argv[1]);
janet_table_put(janet_vm.fiber->env, argv[0], argv[1]);
return argv[1];
}
static Janet janet_core_native(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_native,
"(native path &opt env)",
"Load a native module from the given path. The path "
"must be an absolute or relative path on the file system, and is "
"usually a .so file on Unix systems, and a .dll file on Windows. "
"Returns an environment table that contains functions and other values "
"from the native module.") {
JanetModule init;
janet_arity(argc, 1, 2);
const uint8_t *path = janet_getstring(argv, 0);
@ -309,67 +337,104 @@ static Janet janet_core_native(int32_t argc, Janet *argv) {
return janet_wrap_table(env);
}
static Janet janet_core_describe(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_describe,
"(describe x)",
"Returns a string that is a human-readable description of a value x.") {
JanetBuffer *b = janet_buffer(0);
for (int32_t i = 0; i < argc; ++i)
janet_description_b(b, argv[i]);
return janet_stringv(b->data, b->count);
}
static Janet janet_core_string(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_string,
"(string & xs)",
"Creates a string by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new string.") {
JanetBuffer *b = janet_buffer(0);
for (int32_t i = 0; i < argc; ++i)
janet_to_string_b(b, argv[i]);
return janet_stringv(b->data, b->count);
}
static Janet janet_core_symbol(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_symbol,
"(symbol & xs)",
"Creates a symbol by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new symbol.") {
JanetBuffer *b = janet_buffer(0);
for (int32_t i = 0; i < argc; ++i)
janet_to_string_b(b, argv[i]);
return janet_symbolv(b->data, b->count);
}
static Janet janet_core_keyword(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_keyword,
"(keyword & xs)",
"Creates a keyword by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new keyword.") {
JanetBuffer *b = janet_buffer(0);
for (int32_t i = 0; i < argc; ++i)
janet_to_string_b(b, argv[i]);
return janet_keywordv(b->data, b->count);
}
static Janet janet_core_buffer(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_buffer,
"(buffer & xs)",
"Creates a buffer by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new buffer.") {
JanetBuffer *b = janet_buffer(0);
for (int32_t i = 0; i < argc; ++i)
janet_to_string_b(b, argv[i]);
return janet_wrap_buffer(b);
}
static Janet janet_core_is_abstract(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_is_abstract,
"(abstract? x)",
"Check if x is an abstract type.") {
janet_fixarity(argc, 1);
return janet_wrap_boolean(janet_checktype(argv[0], JANET_ABSTRACT));
}
static Janet janet_core_scannumber(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_scannumber,
"(scan-number str &opt base)",
"Parse a number from a byte sequence and return that number, either an integer "
"or a real. The number "
"must be in the same format as numbers in janet source code. Will return nil "
"on an invalid number. Optionally provide a base - if a base is provided, no "
"radix specifier is expected at the beginning of the number.") {
double number;
janet_fixarity(argc, 1);
janet_arity(argc, 1, 2);
JanetByteView view = janet_getbytes(argv, 0);
if (janet_scan_number(view.bytes, view.len, &number))
int32_t base = janet_optinteger(argv, argc, 1, 0);
int valid = base == 0 || (base >= 2 && base <= 36);
if (!valid) {
janet_panicf("expected base between 2 and 36, got %d", base);
}
if (janet_scan_number_base(view.bytes, view.len, base, &number))
return janet_wrap_nil();
return janet_wrap_number(number);
}
static Janet janet_core_tuple(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_tuple,
"(tuple & items)",
"Creates a new tuple that contains items. Returns the new tuple.") {
return janet_wrap_tuple(janet_tuple_n(argv, argc));
}
static Janet janet_core_array(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_array,
"(array & items)",
"Create a new array that contains items. Returns the new array.") {
JanetArray *array = janet_array(argc);
array->count = argc;
safe_memcpy(array->data, argv, argc * sizeof(Janet));
return janet_wrap_array(array);
}
static Janet janet_core_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_slice,
"(slice x &opt start end)",
"Extract a sub-range of an indexed data structure or byte sequence.") {
JanetRange range;
JanetByteView bview;
JanetView iview;
@ -384,7 +449,12 @@ static Janet janet_core_slice(int32_t argc, Janet *argv) {
}
}
static Janet janet_core_table(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_table,
"(table & kvs)",
"Creates a new table from a variadic number of keys and values. "
"kvs is a sequence k1, v1, k2, v2, k3, v3, ... If kvs has "
"an odd number of elements, an error will be thrown. Returns the "
"new table.") {
int32_t i;
if (argc & 1)
janet_panic("expected even number of arguments");
@ -395,7 +465,9 @@ static Janet janet_core_table(int32_t argc, Janet *argv) {
return janet_wrap_table(table);
}
static Janet janet_core_getproto(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_getproto,
"(getproto x)",
"Get the prototype of a table or struct. Will return nil if `x` has no prototype.") {
janet_fixarity(argc, 1);
if (janet_checktype(argv[0], JANET_TABLE)) {
JanetTable *t = janet_unwrap_table(argv[0]);
@ -412,10 +484,16 @@ static Janet janet_core_getproto(int32_t argc, Janet *argv) {
janet_panicf("expected struct|table, got %v", argv[0]);
}
static Janet janet_core_struct(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_struct,
"(struct & kvs)",
"Create a new struct from a sequence of key value pairs. "
"kvs is a sequence k1, v1, k2, v2, k3, v3, ... If kvs has "
"an odd number of elements, an error will be thrown. Returns the "
"new struct.") {
int32_t i;
if (argc & 1)
if (argc & 1) {
janet_panic("expected even number of arguments");
}
JanetKV *st = janet_struct_begin(argc >> 1);
for (i = 0; i < argc; i += 2) {
janet_struct_put(st, argv[i], argv[i + 1]);
@ -423,20 +501,30 @@ static Janet janet_core_struct(int32_t argc, Janet *argv) {
return janet_wrap_struct(janet_struct_end(st));
}
static Janet janet_core_gensym(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_gensym,
"(gensym)",
"Returns a new symbol that is unique across the runtime. This means it "
"will not collide with any already created symbols during compilation, so "
"it can be used in macros to generate automatic bindings.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_symbol(janet_symbol_gen());
}
static Janet janet_core_gccollect(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_gccollect,
"(gccollect)",
"Run garbage collection. You should probably not call this manually.") {
(void) argv;
(void) argc;
janet_collect();
return janet_wrap_nil();
}
static Janet janet_core_gcsetinterval(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_gcsetinterval,
"(gcsetinterval interval)",
"Set an integer number of bytes to allocate before running garbage collection. "
"Low values for interval will be slower but use less memory. "
"High values will be faster but use more memory.") {
janet_fixarity(argc, 1);
size_t s = janet_getsize(argv, 0);
/* limit interval to 48 bits */
@ -445,17 +533,37 @@ static Janet janet_core_gcsetinterval(int32_t argc, Janet *argv) {
janet_panic("interval too large");
}
#endif
janet_vm_gc_interval = s;
janet_vm.gc_interval = s;
return janet_wrap_nil();
}
static Janet janet_core_gcinterval(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_gcinterval,
"(gcinterval)",
"Returns the integer number of bytes to allocate before running an iteration "
"of garbage collection.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_number((double) janet_vm_gc_interval);
return janet_wrap_number((double) janet_vm.gc_interval);
}
static Janet janet_core_type(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_type,
"(type x)",
"Returns the type of `x` as a keyword. `x` is one of:\n\n"
"* :nil\n\n"
"* :boolean\n\n"
"* :number\n\n"
"* :array\n\n"
"* :tuple\n\n"
"* :table\n\n"
"* :struct\n\n"
"* :string\n\n"
"* :buffer\n\n"
"* :symbol\n\n"
"* :keyword\n\n"
"* :function\n\n"
"* :cfunction\n\n"
"* :fiber\n\n"
"or another keyword for an abstract type.") {
janet_fixarity(argc, 1);
JanetType t = janet_type(argv[0]);
if (t == JANET_ABSTRACT) {
@ -465,12 +573,21 @@ static Janet janet_core_type(int32_t argc, Janet *argv) {
}
}
static Janet janet_core_hash(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_hash,
"(hash value)",
"Gets a hash for any value. The hash is an integer can be used "
"as a cheap hash function for all values. If two values are strictly equal, "
"then they will have the same hash value.") {
janet_fixarity(argc, 1);
return janet_wrap_number(janet_hash(argv[0]));
}
static Janet janet_core_getline(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_getline,
"(getline &opt prompt buf env)",
"Reads a line of input into a buffer, including the newline character, using a prompt. "
"An optional environment table can be provided for auto-complete. "
"Returns the modified buffer. "
"Use this function to implement a simple interface for a terminal program.") {
FILE *in = janet_dynfile("in", stdin);
FILE *out = janet_dynfile("out", stdout);
janet_arity(argc, 0, 3);
@ -495,21 +612,27 @@ static Janet janet_core_getline(int32_t argc, Janet *argv) {
return janet_wrap_buffer(buf);
}
static Janet janet_core_trace(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_trace,
"(trace func)",
"Enable tracing on a function. Returns the function.") {
janet_fixarity(argc, 1);
JanetFunction *func = janet_getfunction(argv, 0);
func->gc.flags |= JANET_FUNCFLAG_TRACE;
return argv[0];
}
static Janet janet_core_untrace(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_untrace,
"(untrace func)",
"Disables tracing on a function. Returns the function.") {
janet_fixarity(argc, 1);
JanetFunction *func = janet_getfunction(argv, 0);
func->gc.flags &= ~JANET_FUNCFLAG_TRACE;
return argv[0];
}
static Janet janet_core_check_int(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_check_int,
"(int? x)",
"Check if x can be exactly represented as a 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
double num = janet_unwrap_number(argv[0]);
@ -518,7 +641,9 @@ ret_false:
return janet_wrap_false();
}
static Janet janet_core_check_nat(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_check_nat,
"(nat? x)",
"Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.") {
janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_NUMBER)) goto ret_false;
double num = janet_unwrap_number(argv[0]);
@ -527,7 +652,9 @@ ret_false:
return janet_wrap_false();
}
static Janet janet_core_signal(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_core_signal,
"(signal what x)",
"Raise a signal with payload x. ") {
janet_arity(argc, 1, 2);
int sig;
if (janet_checkint(argv[0])) {
@ -552,210 +679,6 @@ static Janet janet_core_signal(int32_t argc, Janet *argv) {
janet_signalv(sig, payload);
}
static const JanetReg corelib_cfuns[] = {
{
"native", janet_core_native,
JDOC("(native path &opt env)\n\n"
"Load a native module from the given path. The path "
"must be an absolute or relative path on the file system, and is "
"usually a .so file on Unix systems, and a .dll file on Windows. "
"Returns an environment table that contains functions and other values "
"from the native module.")
},
{
"describe", janet_core_describe,
JDOC("(describe x)\n\n"
"Returns a string that is a human-readable description of a value x.")
},
{
"string", janet_core_string,
JDOC("(string & xs)\n\n"
"Creates a string by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new string.")
},
{
"symbol", janet_core_symbol,
JDOC("(symbol & xs)\n\n"
"Creates a symbol by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new symbol.")
},
{
"keyword", janet_core_keyword,
JDOC("(keyword & xs)\n\n"
"Creates a keyword by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new keyword.")
},
{
"buffer", janet_core_buffer,
JDOC("(buffer & xs)\n\n"
"Creates a buffer by concatenating the elements of `xs` together. If an "
"element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new buffer.")
},
{
"abstract?", janet_core_is_abstract,
JDOC("(abstract? x)\n\n"
"Check if x is an abstract type.")
},
{
"table", janet_core_table,
JDOC("(table & kvs)\n\n"
"Creates a new table from a variadic number of keys and values. "
"kvs is a sequence k1, v1, k2, v2, k3, v3, ... If kvs has "
"an odd number of elements, an error will be thrown. Returns the "
"new table.")
},
{
"array", janet_core_array,
JDOC("(array & items)\n\n"
"Create a new array that contains items. Returns the new array.")
},
{
"scan-number", janet_core_scannumber,
JDOC("(scan-number str)\n\n"
"Parse a number from a byte sequence an return that number, either and integer "
"or a real. The number "
"must be in the same format as numbers in janet source code. Will return nil "
"on an invalid number.")
},
{
"tuple", janet_core_tuple,
JDOC("(tuple & items)\n\n"
"Creates a new tuple that contains items. Returns the new tuple.")
},
{
"struct", janet_core_struct,
JDOC("(struct & kvs)\n\n"
"Create a new struct from a sequence of key value pairs. "
"kvs is a sequence k1, v1, k2, v2, k3, v3, ... If kvs has "
"an odd number of elements, an error will be thrown. Returns the "
"new struct.")
},
{
"gensym", janet_core_gensym,
JDOC("(gensym)\n\n"
"Returns a new symbol that is unique across the runtime. This means it "
"will not collide with any already created symbols during compilation, so "
"it can be used in macros to generate automatic bindings.")
},
{
"gccollect", janet_core_gccollect,
JDOC("(gccollect)\n\n"
"Run garbage collection. You should probably not call this manually.")
},
{
"gcsetinterval", janet_core_gcsetinterval,
JDOC("(gcsetinterval interval)\n\n"
"Set an integer number of bytes to allocate before running garbage collection. "
"Low values for interval will be slower but use less memory. "
"High values will be faster but use more memory.")
},
{
"gcinterval", janet_core_gcinterval,
JDOC("(gcinterval)\n\n"
"Returns the integer number of bytes to allocate before running an iteration "
"of garbage collection.")
},
{
"type", janet_core_type,
JDOC("(type x)\n\n"
"Returns the type of `x` as a keyword. `x` is one of:\n\n"
"* :nil\n\n"
"* :boolean\n\n"
"* :number\n\n"
"* :array\n\n"
"* :tuple\n\n"
"* :table\n\n"
"* :struct\n\n"
"* :string\n\n"
"* :buffer\n\n"
"* :symbol\n\n"
"* :keyword\n\n"
"* :function\n\n"
"* :cfunction\n\n"
"* :fiber\n\n"
"or another keyword for an abstract type.")
},
{
"hash", janet_core_hash,
JDOC("(hash value)\n\n"
"Gets a hash for any value. The hash is an integer can be used "
"as a cheap hash function for all values. If two values are strictly equal, "
"then they will have the same hash value.")
},
{
"getline", janet_core_getline,
JDOC("(getline &opt prompt buf env)\n\n"
"Reads a line of input into a buffer, including the newline character, using a prompt. "
"An optional environment table can be provided for auto-complete. "
"Returns the modified buffer. "
"Use this function to implement a simple interface for a terminal program.")
},
{
"dyn", janet_core_dyn,
JDOC("(dyn key &opt default)\n\n"
"Get a dynamic binding. Returns the default value (or nil) if no binding found.")
},
{
"setdyn", janet_core_setdyn,
JDOC("(setdyn key value)\n\n"
"Set a dynamic binding. Returns value.")
},
{
"trace", janet_core_trace,
JDOC("(trace func)\n\n"
"Enable tracing on a function. Returns the function.")
},
{
"untrace", janet_core_untrace,
JDOC("(untrace func)\n\n"
"Disables tracing on a function. Returns the function.")
},
{
"module/expand-path", janet_core_expand_path,
JDOC("(module/expand-path path template)\n\n"
"Expands a path template as found in `module/paths` for `module/find`. "
"This takes in a path (the argument to require) and a template string, "
"to expand the path to a path that can be "
"used for importing files. The replacements are as follows:\n\n"
"* :all: -- the value of path verbatim\n\n"
"* :cur: -- the current file, or (dyn :current-file)\n\n"
"* :dir: -- the directory containing the current file\n\n"
"* :name: -- the name component of path, with extension if given\n\n"
"* :native: -- the extension used to load natives, .so or .dll\n\n"
"* :sys: -- the system path, or (dyn :syspath)")
},
{
"int?", janet_core_check_int,
JDOC("(int? x)\n\n"
"Check if x can be exactly represented as a 32 bit signed two's complement integer.")
},
{
"nat?", janet_core_check_nat,
JDOC("(nat? x)\n\n"
"Check if x can be exactly represented as a non-negative 32 bit signed two's complement integer.")
},
{
"slice", janet_core_slice,
JDOC("(slice x &opt start end)\n\n"
"Extract a sub-range of an indexed data structure or byte sequence.")
},
{
"signal", janet_core_signal,
JDOC("(signal what x)\n\n"
"Raise a signal with payload x. ")
},
{
"getproto", janet_core_getproto,
JDOC("(getproto x)\n\n"
"Get the prototype of a table or struct. Will return nil if `x` has no prototype.")
},
{NULL, NULL, NULL}
};
#ifdef JANET_BOOTSTRAP
/* Utility for inline assembly */
@ -1028,7 +951,39 @@ static const uint32_t cmp_asm[] = {
*/
static void janet_load_libs(JanetTable *env) {
janet_core_cfuns(env, NULL, corelib_cfuns);
JanetRegExt corelib_cfuns[] = {
JANET_CORE_REG("native", janet_core_native),
JANET_CORE_REG("describe", janet_core_describe),
JANET_CORE_REG("string", janet_core_string),
JANET_CORE_REG("symbol", janet_core_symbol),
JANET_CORE_REG("keyword", janet_core_keyword),
JANET_CORE_REG("buffer", janet_core_buffer),
JANET_CORE_REG("abstract?", janet_core_is_abstract),
JANET_CORE_REG("table", janet_core_table),
JANET_CORE_REG("array", janet_core_array),
JANET_CORE_REG("scan-number", janet_core_scannumber),
JANET_CORE_REG("tuple", janet_core_tuple),
JANET_CORE_REG("struct", janet_core_struct),
JANET_CORE_REG("gensym", janet_core_gensym),
JANET_CORE_REG("gccollect", janet_core_gccollect),
JANET_CORE_REG("gcsetinterval", janet_core_gcsetinterval),
JANET_CORE_REG("gcinterval", janet_core_gcinterval),
JANET_CORE_REG("type", janet_core_type),
JANET_CORE_REG("hash", janet_core_hash),
JANET_CORE_REG("getline", janet_core_getline),
JANET_CORE_REG("dyn", janet_core_dyn),
JANET_CORE_REG("setdyn", janet_core_setdyn),
JANET_CORE_REG("trace", janet_core_trace),
JANET_CORE_REG("untrace", janet_core_untrace),
JANET_CORE_REG("module/expand-path", janet_core_expand_path),
JANET_CORE_REG("int?", janet_core_check_int),
JANET_CORE_REG("nat?", janet_core_check_nat),
JANET_CORE_REG("slice", janet_core_slice),
JANET_CORE_REG("signal", janet_core_signal),
JANET_CORE_REG("getproto", janet_core_getproto),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, corelib_cfuns);
janet_lib_io(env);
janet_lib_math(env);
janet_lib_array(env);
@ -1052,9 +1007,6 @@ static void janet_load_libs(JanetTable *env) {
#ifdef JANET_INT_TYPES
janet_lib_inttypes(env);
#endif
#ifdef JANET_THREADS
janet_lib_thread(env);
#endif
#ifdef JANET_EV
janet_lib_ev(env);
#endif
@ -1238,8 +1190,8 @@ JanetTable *janet_core_env(JanetTable *replacements) {
JanetTable *janet_core_env(JanetTable *replacements) {
/* Memoize core env, ignoring replacements the second time around. */
if (NULL != janet_vm_core_env) {
return janet_vm_core_env;
if (NULL != janet_vm.core_env) {
return janet_vm.core_env;
}
JanetTable *dict = janet_core_lookup_table(replacements);
@ -1255,7 +1207,7 @@ JanetTable *janet_core_env(JanetTable *replacements) {
/* Memoize */
janet_gcroot(marsh_out);
JanetTable *env = janet_unwrap_table(marsh_out);
janet_vm_core_env = env;
janet_vm.core_env = env;
/* Invert image dict manually here. We can't do this in boot.janet as it
* breaks deterministic builds */
@ -1287,9 +1239,7 @@ JanetTable *janet_core_lookup_table(JanetTable *replacements) {
JanetKV kv = replacements->data[i];
if (!janet_checktype(kv.key, JANET_NIL)) {
janet_table_put(dict, kv.key, kv.value);
if (janet_checktype(kv.value, JANET_CFUNCTION)) {
janet_table_put(janet_vm_registry, kv.value, kv.key);
}
/* Add replacement functions to registry? */
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -55,7 +55,7 @@ void janet_debug_find(
JanetFuncDef **def_out, int32_t *pc_out,
const uint8_t *source, int32_t sourceLine, int32_t sourceColumn) {
/* Scan the heap for right func def */
JanetGCObject *current = janet_vm_blocks;
JanetGCObject *current = janet_vm.blocks;
/* Keep track of the best source mapping we have seen so far */
int32_t besti = -1;
int32_t best_line = -1;
@ -86,7 +86,7 @@ void janet_debug_find(
}
}
}
current = current->next;
current = current->data.next;
}
if (best_def) {
*def_out = best_def;
@ -118,6 +118,7 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) {
fiber = fibers[fi];
int32_t i = fiber->frame;
while (i > 0) {
JanetCFunRegistry *reg = NULL;
JanetStackFrame *frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE);
JanetFuncDef *def = NULL;
i = frame->prevframe;
@ -144,11 +145,19 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) {
} else {
JanetCFunction cfun = (JanetCFunction)(frame->pc);
if (cfun) {
Janet name = janet_table_get(janet_vm_registry, janet_wrap_cfunction(cfun));
if (!janet_checktype(name, JANET_NIL))
janet_eprintf(" %s", (const char *)janet_to_string(name));
else
reg = janet_registry_get(cfun);
if (NULL != reg && NULL != reg->name) {
if (reg->name_prefix) {
janet_eprintf(" %s/%s", reg->name_prefix, reg->name);
} else {
janet_eprintf(" %s", reg->name);
}
if (NULL != reg->source_file) {
janet_eprintf(" [%s]", reg->source_file);
}
} else {
janet_eprintf(" <cfunction>");
}
}
}
if (frame->flags & JANET_STACKFRAME_TAILCALL)
@ -161,6 +170,11 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) {
} else {
janet_eprintf(" pc=%d", off);
}
} else if (NULL != reg) {
/* C Function */
if (reg->source_line > 0) {
janet_eprintf(" on line %d", (long) reg->source_line);
}
}
janet_eprintf("\n");
}
@ -195,7 +209,13 @@ static void helper_find_fun(int32_t argc, Janet *argv, JanetFuncDef **def, int32
*bytecode_offset = offset;
}
static Janet cfun_debug_break(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_break,
"(debug/break source line col)",
"Sets a breakpoint in `source` at a given line and column. "
"Will throw an error if the breakpoint location "
"cannot be found. For example\n\n"
"\t(debug/break \"core.janet\" 10 4)\n\n"
"will set a breakpoint at line 10, 4th column of the file core.janet.") {
JanetFuncDef *def;
int32_t offset;
helper_find(argc, argv, &def, &offset);
@ -203,7 +223,11 @@ static Janet cfun_debug_break(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_debug_unbreak(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_unbreak,
"(debug/unbreak source line column)",
"Remove a breakpoint with a source key at a given line and column. "
"Will throw an error if the breakpoint "
"cannot be found.") {
JanetFuncDef *def;
int32_t offset = 0;
helper_find(argc, argv, &def, &offset);
@ -211,7 +235,11 @@ static Janet cfun_debug_unbreak(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_debug_fbreak(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_fbreak,
"(debug/fbreak fun &opt pc)",
"Set a breakpoint in a given function. pc is an optional offset, which "
"is in bytecode instructions. fun is a function value. Will throw an error "
"if the offset is too large or negative.") {
JanetFuncDef *def;
int32_t offset = 0;
helper_find_fun(argc, argv, &def, &offset);
@ -219,7 +247,9 @@ static Janet cfun_debug_fbreak(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_debug_unfbreak(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_unfbreak,
"(debug/unfbreak fun &opt pc)",
"Unset a breakpoint set with debug/fbreak.") {
JanetFuncDef *def;
int32_t offset;
helper_find_fun(argc, argv, &def, &offset);
@ -227,7 +257,12 @@ static Janet cfun_debug_unfbreak(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_debug_lineage(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_lineage,
"(debug/lineage fib)",
"Returns an array of all child fibers from a root fiber. This function "
"is useful when a fiber signals or errors to an ancestor fiber. Using this function, "
"the fiber handling the error can see which fiber raised the signal. This function should "
"be used mostly for debugging purposes.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
JanetArray *array = janet_array(0);
@ -252,9 +287,20 @@ static Janet doframe(JanetStackFrame *frame) {
} else {
JanetCFunction cfun = (JanetCFunction)(frame->pc);
if (cfun) {
Janet name = janet_table_get(janet_vm_registry, janet_wrap_cfunction(cfun));
if (!janet_checktype(name, JANET_NIL)) {
janet_table_put(t, janet_ckeywordv("name"), name);
JanetCFunRegistry *reg = janet_registry_get(cfun);
if (NULL != reg->name) {
if (NULL != reg->name_prefix) {
janet_table_put(t, janet_ckeywordv("name"), janet_wrap_string(janet_formatc("%s/%s", reg->name_prefix, reg->name)));
} else {
janet_table_put(t, janet_ckeywordv("name"), janet_cstringv(reg->name));
}
if (NULL != reg->source_file) {
janet_table_put(t, janet_ckeywordv("source"), janet_cstringv(reg->source_file));
}
if (reg->source_line > 0) {
janet_table_put(t, janet_ckeywordv("source-line"), janet_wrap_integer(reg->source_line));
janet_table_put(t, janet_ckeywordv("source-column"), janet_wrap_integer(1));
}
}
}
janet_table_put(t, janet_ckeywordv("c"), janet_wrap_true());
@ -284,7 +330,21 @@ static Janet doframe(JanetStackFrame *frame) {
return janet_wrap_table(t);
}
static Janet cfun_debug_stack(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_stack,
"(debug/stack fib)",
"Gets information about the stack as an array of tables. Each table "
"in the array contains information about a stack frame. The top-most, current "
"stack frame is the first table in the array, and the bottom-most stack frame "
"is the last value. Each stack frame contains some of the following attributes:\n\n"
"* :c - true if the stack frame is a c function invocation\n\n"
"* :column - the current source column of the stack frame\n\n"
"* :function - the function that the stack frame represents\n\n"
"* :line - the current source line of the stack frame\n\n"
"* :name - the human-friendly name of the function\n\n"
"* :pc - integer indicating the location of the program counter\n\n"
"* :source - string with the file path or other identifier for the source code\n\n"
"* :slots - array of all values in each slot\n\n"
"* :tail - boolean indicating a tail call") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
JanetArray *array = janet_array(0);
@ -300,7 +360,11 @@ static Janet cfun_debug_stack(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_debug_stacktrace(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_stacktrace,
"(debug/stacktrace fiber &opt err)",
"Prints a nice looking stacktrace for a fiber. Can optionally provide "
"an error value to print the stack trace with. If `err` is nil or not "
"provided, will skip the error line. Returns the fiber.") {
janet_arity(argc, 1, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
Janet x = argc == 1 ? janet_wrap_nil() : argv[1];
@ -308,7 +372,11 @@ static Janet cfun_debug_stacktrace(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_debug_argstack(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_argstack,
"(debug/arg-stack fiber)",
"Gets all values currently on the fiber's argument stack. Normally, "
"this should be empty unless the fiber signals while pushing arguments "
"to make a function call. Returns a new array.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
JanetArray *array = janet_array(fiber->stacktop - fiber->stackstart);
@ -317,7 +385,11 @@ static Janet cfun_debug_argstack(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_debug_step(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_debug_step,
"(debug/step fiber &opt x)",
"Run a fiber for one virtual instruction of the Janet machine. Can optionally "
"pass in a value that will be passed as the resuming value. Returns the signal value, "
"which will usually be nil, as breakpoints raise nil signals.") {
janet_arity(argc, 1, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
Janet out = janet_wrap_nil();
@ -325,85 +397,19 @@ static Janet cfun_debug_step(int32_t argc, Janet *argv) {
return out;
}
static const JanetReg debug_cfuns[] = {
{
"debug/break", cfun_debug_break,
JDOC("(debug/break source line col)\n\n"
"Sets a breakpoint in `source` at a given line and column. "
"Will throw an error if the breakpoint location "
"cannot be found. For example\n\n"
"\t(debug/break \"core.janet\" 10 4)\n\n"
"wil set a breakpoint at line 10, 4th column of the file core.janet.")
},
{
"debug/unbreak", cfun_debug_unbreak,
JDOC("(debug/unbreak source line column)\n\n"
"Remove a breakpoint with a source key at a given line and column. "
"Will throw an error if the breakpoint "
"cannot be found.")
},
{
"debug/fbreak", cfun_debug_fbreak,
JDOC("(debug/fbreak fun &opt pc)\n\n"
"Set a breakpoint in a given function. pc is an optional offset, which "
"is in bytecode instructions. fun is a function value. Will throw an error "
"if the offset is too large or negative.")
},
{
"debug/unfbreak", cfun_debug_unfbreak,
JDOC("(debug/unfbreak fun &opt pc)\n\n"
"Unset a breakpoint set with debug/fbreak.")
},
{
"debug/arg-stack", cfun_debug_argstack,
JDOC("(debug/arg-stack fiber)\n\n"
"Gets all values currently on the fiber's argument stack. Normally, "
"this should be empty unless the fiber signals while pushing arguments "
"to make a function call. Returns a new array.")
},
{
"debug/stack", cfun_debug_stack,
JDOC("(debug/stack fib)\n\n"
"Gets information about the stack as an array of tables. Each table "
"in the array contains information about a stack frame. The top-most, current "
"stack frame is the first table in the array, and the bottom-most stack frame "
"is the last value. Each stack frame contains some of the following attributes:\n\n"
"* :c - true if the stack frame is a c function invocation\n\n"
"* :column - the current source column of the stack frame\n\n"
"* :function - the function that the stack frame represents\n\n"
"* :line - the current source line of the stack frame\n\n"
"* :name - the human-friendly name of the function\n\n"
"* :pc - integer indicating the location of the program counter\n\n"
"* :source - string with the file path or other identifier for the source code\n\n"
"* :slots - array of all values in each slot\n\n"
"* :tail - boolean indicating a tail call")
},
{
"debug/stacktrace", cfun_debug_stacktrace,
JDOC("(debug/stacktrace fiber &opt err)\n\n"
"Prints a nice looking stacktrace for a fiber. Can optionally provide "
"an error value to print the stack trace with. If `err` is nil or not "
"provided, will skipp the error line. Returns the fiber.")
},
{
"debug/lineage", cfun_debug_lineage,
JDOC("(debug/lineage fib)\n\n"
"Returns an array of all child fibers from a root fiber. This function "
"is useful when a fiber signals or errors to an ancestor fiber. Using this function, "
"the fiber handling the error can see which fiber raised the signal. This function should "
"be used mostly for debugging purposes.")
},
{
"debug/step", cfun_debug_step,
JDOC("(debug/step fiber &opt x)\n\n"
"Run a fiber for one virtual instruction of the Janet machine. Can optionally "
"pass in a value that will be passed as the resuming value. Returns the signal value, "
"which will usually be nil, as breakpoints raise nil signals.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_debug(JanetTable *env) {
janet_core_cfuns(env, NULL, debug_cfuns);
JanetRegExt debug_cfuns[] = {
JANET_CORE_REG("debug/break", cfun_debug_break),
JANET_CORE_REG("debug/unbreak", cfun_debug_unbreak),
JANET_CORE_REG("debug/fbreak", cfun_debug_fbreak),
JANET_CORE_REG("debug/unfbreak", cfun_debug_unfbreak),
JANET_CORE_REG("debug/arg-stack", cfun_debug_argstack),
JANET_CORE_REG("debug/stack", cfun_debug_stack),
JANET_CORE_REG("debug/stacktrace", cfun_debug_stacktrace),
JANET_CORE_REG("debug/lineage", cfun_debug_lineage),
JANET_CORE_REG("debug/step", cfun_debug_step),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, debug_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -57,7 +57,7 @@ static JanetFiber *fiber_alloc(int32_t capacity) {
if (NULL == data) {
JANET_OUT_OF_MEMORY;
}
janet_vm_next_collection += sizeof(Janet) * capacity;
janet_vm.next_collection += sizeof(Janet) * capacity;
fiber->data = data;
return fiber;
}
@ -121,7 +121,7 @@ void janet_fiber_setcapacity(JanetFiber *fiber, int32_t n) {
}
fiber->data = newData;
fiber->capacity = n;
janet_vm_next_collection += sizeof(Janet) * diff;
janet_vm.next_collection += sizeof(Janet) * diff;
}
/* Grow fiber if needed */
@ -255,7 +255,7 @@ static void janet_env_detach(JanetFuncEnv *env) {
int32_t len = env->length;
size_t s = sizeof(Janet) * (size_t) len;
Janet *vmem = janet_malloc(s);
janet_vm_next_collection += (uint32_t) s;
janet_vm.next_collection += (uint32_t) s;
if (NULL == vmem) {
JANET_OUT_OF_MEMORY;
}
@ -442,16 +442,19 @@ JanetFiberStatus janet_fiber_status(JanetFiber *f) {
}
JanetFiber *janet_current_fiber(void) {
return janet_vm_fiber;
return janet_vm.fiber;
}
JanetFiber *janet_root_fiber(void) {
return janet_vm_root_fiber;
return janet_vm.root_fiber;
}
/* CFuns */
static Janet cfun_fiber_getenv(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_getenv,
"(fiber/getenv fiber)",
"Gets the environment for a fiber. Returns nil if no such table is "
"set yet.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
return fiber->env ?
@ -459,7 +462,10 @@ static Janet cfun_fiber_getenv(int32_t argc, Janet *argv) {
janet_wrap_nil();
}
static Janet cfun_fiber_setenv(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_setenv,
"(fiber/setenv fiber table)",
"Sets the environment table for a fiber. Set to nil to remove the current "
"environment.") {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
if (janet_checktype(argv[1], JANET_NIL)) {
@ -470,7 +476,30 @@ static Janet cfun_fiber_setenv(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_fiber_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_new,
"(fiber/new func &opt sigmask)",
"Create a new fiber with function body func. Can optionally "
"take a set of signals to block from the current parent fiber "
"when called. The mask is specified as a keyword where each character "
"is used to indicate a signal to block. If the ev module is enabled, and "
"this fiber is used as an argument to `ev/go`, these \"blocked\" signals "
"will result in messages being sent to the supervisor channel. "
"The default sigmask is :y. "
"For example,\n\n"
" (fiber/new myfun :e123)\n\n"
"blocks error signals and user signals 1, 2 and 3. The signals are "
"as follows:\n\n"
"* :a - block all signals\n"
"* :d - block debug signals\n"
"* :e - block error signals\n"
"* :t - block termination signals: error + user[0-4]\n"
"* :u - block user signals\n"
"* :y - block yield signals\n"
"* :0-9 - block a specific user signal\n\n"
"The sigmask argument also can take environment flags. If any mutually "
"exclusive flags are present, the last flag takes precedence.\n\n"
"* :i - inherit the environment from the current fiber\n"
"* :p - the environment table's prototype is the current environment table") {
janet_arity(argc, 1, 2);
JanetFunction *func = janet_getfunction(argv, 0);
JanetFiber *fiber;
@ -520,17 +549,17 @@ static Janet cfun_fiber_new(int32_t argc, Janet *argv) {
fiber->flags |= JANET_FIBER_MASK_YIELD;
break;
case 'i':
if (!janet_vm_fiber->env) {
janet_vm_fiber->env = janet_table(0);
if (!janet_vm.fiber->env) {
janet_vm.fiber->env = janet_table(0);
}
fiber->env = janet_vm_fiber->env;
fiber->env = janet_vm.fiber->env;
break;
case 'p':
if (!janet_vm_fiber->env) {
janet_vm_fiber->env = janet_table(0);
if (!janet_vm.fiber->env) {
janet_vm.fiber->env = janet_table(0);
}
fiber->env = janet_table(0);
fiber->env->proto = janet_vm_fiber->env;
fiber->env->proto = janet_vm.fiber->env;
break;
}
}
@ -539,32 +568,53 @@ static Janet cfun_fiber_new(int32_t argc, Janet *argv) {
return janet_wrap_fiber(fiber);
}
static Janet cfun_fiber_status(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_status,
"(fiber/status fib)",
"Get the status of a fiber. The status will be one of:\n\n"
"* :dead - the fiber has finished\n"
"* :error - the fiber has errored out\n"
"* :debug - the fiber is suspended in debug mode\n"
"* :pending - the fiber has been yielded\n"
"* :user(0-9) - the fiber is suspended by a user signal\n"
"* :alive - the fiber is currently running and cannot be resumed\n"
"* :new - the fiber has just been created and not yet run") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
uint32_t s = janet_fiber_status(fiber);
return janet_ckeywordv(janet_status_names[s]);
}
static Janet cfun_fiber_current(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_current,
"(fiber/current)",
"Returns the currently running fiber.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_fiber(janet_vm_fiber);
return janet_wrap_fiber(janet_vm.fiber);
}
static Janet cfun_fiber_root(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_root,
"(fiber/root)",
"Returns the current root fiber. The root fiber is the oldest ancestor "
"that does not have a parent.") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_fiber(janet_vm_root_fiber);
return janet_wrap_fiber(janet_vm.root_fiber);
}
static Janet cfun_fiber_maxstack(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_maxstack,
"(fiber/maxstack fib)",
"Gets the maximum stack size in janet values allowed for a fiber. While memory for "
"the fiber's stack is not allocated up front, the fiber will not allocated more "
"than this amount and will throw a stack-overflow error if more memory is needed. ") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
return janet_wrap_integer(fiber->maxstack);
}
static Janet cfun_fiber_setmaxstack(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_setmaxstack,
"(fiber/setmaxstack fib maxstack)",
"Sets the maximum stack size in janet values for a fiber. By default, the "
"maximum stack size is usually 8192.") {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
int32_t maxs = janet_getinteger(argv, 1);
@ -575,7 +625,9 @@ static Janet cfun_fiber_setmaxstack(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_fiber_can_resume(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_can_resume,
"(fiber/can-resume? fiber)",
"Check if a fiber is finished and cannot be resumed.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
JanetFiberStatus s = janet_fiber_status(fiber);
@ -589,101 +641,28 @@ static Janet cfun_fiber_can_resume(int32_t argc, Janet *argv) {
return janet_wrap_boolean(!isFinished);
}
static Janet cfun_fiber_last_value(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_fiber_last_value,
"(fiber/last-value)",
"Get the last value returned or signaled from the fiber.") {
janet_fixarity(argc, 1);
JanetFiber *fiber = janet_getfiber(argv, 0);
return fiber->last_value;
}
static const JanetReg fiber_cfuns[] = {
{
"fiber/new", cfun_fiber_new,
JDOC("(fiber/new func &opt sigmask)\n\n"
"Create a new fiber with function body func. Can optionally "
"take a set of signals to block from the current parent fiber "
"when called. The mask is specified as a keyword where each character "
"is used to indicate a signal to block. If the ev module is enabled, and "
"this fiber is used as an argument to `ev/go`, these \"blocked\" signals "
"will result in messages being sent to the supervisor channel. "
"The default sigmask is :y. "
"For example,\n\n"
" (fiber/new myfun :e123)\n\n"
"blocks error signals and user signals 1, 2 and 3. The signals are "
"as follows:\n\n"
"* :a - block all signals\n"
"* :d - block debug signals\n"
"* :e - block error signals\n"
"* :t - block termination signals: error + user[0-4]\n"
"* :u - block user signals\n"
"* :y - block yield signals\n"
"* :0-9 - block a specific user signal\n\n"
"The sigmask argument also can take environment flags. If any mutually "
"exclusive flags are present, the last flag takes precedence.\n\n"
"* :i - inherit the environment from the current fiber\n"
"* :p - the environment table's prototype is the current environment table")
},
{
"fiber/status", cfun_fiber_status,
JDOC("(fiber/status fib)\n\n"
"Get the status of a fiber. The status will be one of:\n\n"
"* :dead - the fiber has finished\n"
"* :error - the fiber has errored out\n"
"* :debug - the fiber is suspended in debug mode\n"
"* :pending - the fiber has been yielded\n"
"* :user(0-9) - the fiber is suspended by a user signal\n"
"* :alive - the fiber is currently running and cannot be resumed\n"
"* :new - the fiber has just been created and not yet run")
},
{
"fiber/root", cfun_fiber_root,
JDOC("(fiber/root)\n\n"
"Returns the current root fiber. The root fiber is the oldest ancestor "
"that does not have a parent.")
},
{
"fiber/current", cfun_fiber_current,
JDOC("(fiber/current)\n\n"
"Returns the currently running fiber.")
},
{
"fiber/maxstack", cfun_fiber_maxstack,
JDOC("(fiber/maxstack fib)\n\n"
"Gets the maximum stack size in janet values allowed for a fiber. While memory for "
"the fiber's stack is not allocated up front, the fiber will not allocated more "
"than this amount and will throw a stack-overflow error if more memory is needed. ")
},
{
"fiber/setmaxstack", cfun_fiber_setmaxstack,
JDOC("(fiber/setmaxstack fib maxstack)\n\n"
"Sets the maximum stack size in janet values for a fiber. By default, the "
"maximum stack size is usually 8192.")
},
{
"fiber/getenv", cfun_fiber_getenv,
JDOC("(fiber/getenv fiber)\n\n"
"Gets the environment for a fiber. Returns nil if no such table is "
"set yet.")
},
{
"fiber/setenv", cfun_fiber_setenv,
JDOC("(fiber/setenv fiber table)\n\n"
"Sets the environment table for a fiber. Set to nil to remove the current "
"environment.")
},
{
"fiber/can-resume?", cfun_fiber_can_resume,
JDOC("(fiber/can-resume? fiber)\n\n"
"Check if a fiber is finished and cannot be resumed.")
},
{
"fiber/last-value", cfun_fiber_last_value,
JDOC("(fiber/last-value\n\n"
"Get the last value returned or signaled from the fiber.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_fiber(JanetTable *env) {
janet_core_cfuns(env, NULL, fiber_cfuns);
JanetRegExt fiber_cfuns[] = {
JANET_CORE_REG("fiber/new", cfun_fiber_new),
JANET_CORE_REG("fiber/status", cfun_fiber_status),
JANET_CORE_REG("fiber/root", cfun_fiber_root),
JANET_CORE_REG("fiber/current", cfun_fiber_current),
JANET_CORE_REG("fiber/maxstack", cfun_fiber_maxstack),
JANET_CORE_REG("fiber/setmaxstack", cfun_fiber_setmaxstack),
JANET_CORE_REG("fiber/getenv", cfun_fiber_getenv),
JANET_CORE_REG("fiber/setenv", cfun_fiber_setenv),
JANET_CORE_REG("fiber/can-resume?", cfun_fiber_can_resume),
JANET_CORE_REG("fiber/last-value", cfun_fiber_last_value),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, fiber_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -47,7 +47,6 @@
#define JANET_FIBER_MASK_USER 0x3FF0
#define JANET_FIBER_STATUS_MASK 0x3F0000
#define JANET_FIBER_FLAG_SCHEDULED 0x800000
#define JANET_FIBER_RESUME_SIGNAL 0x400000
#define JANET_FIBER_STATUS_OFFSET 16
@ -57,8 +56,6 @@
#define JANET_FIBER_DID_LONGJUMP 0x8000000
#define JANET_FIBER_FLAG_MASK 0xF000000
extern JANET_THREAD_LOCAL JanetFiber *janet_vm_fiber;
#define janet_fiber_set_status(f, s) do {\
(f)->flags &= ~JANET_FIBER_STATUS_MASK;\
(f)->flags |= (s) << JANET_FIBER_STATUS_OFFSET;\

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -31,28 +31,6 @@
#include "vector.h"
#endif
struct JanetScratch {
JanetScratchFinalizer finalize;
long long mem[]; /* for proper alignment */
};
/* GC State */
JANET_THREAD_LOCAL void *janet_vm_blocks;
JANET_THREAD_LOCAL size_t janet_vm_gc_interval;
JANET_THREAD_LOCAL size_t janet_vm_next_collection;
JANET_THREAD_LOCAL size_t janet_vm_block_count;
JANET_THREAD_LOCAL int janet_vm_gc_suspend = 0;
/* Roots */
JANET_THREAD_LOCAL Janet *janet_vm_roots;
JANET_THREAD_LOCAL size_t janet_vm_root_count;
JANET_THREAD_LOCAL size_t janet_vm_root_capacity;
/* Scratch Memory */
JANET_THREAD_LOCAL JanetScratch **janet_scratch_mem;
JANET_THREAD_LOCAL size_t janet_scratch_cap;
JANET_THREAD_LOCAL size_t janet_scratch_len;
/* Helpers for marking the various gc types */
static void janet_mark_funcenv(JanetFuncEnv *env);
static void janet_mark_funcdef(JanetFuncDef *def);
@ -72,7 +50,7 @@ static JANET_THREAD_LOCAL size_t orig_rootcount;
/* Hint to the GC that we may need to collect */
void janet_gcpressure(size_t s) {
janet_vm_next_collection += s;
janet_vm.next_collection += s;
}
/* Mark a value */
@ -127,6 +105,14 @@ static void janet_mark_buffer(JanetBuffer *buffer) {
}
static void janet_mark_abstract(void *adata) {
#ifdef JANET_EV
/* Check if abstract type is a threaded abstract type. If it is, marking means
* updating the threaded_abstract table. */
if ((janet_abstract_head(adata)->gc.flags & JANET_MEM_TYPEBITS) == JANET_MEMORY_THREADED_ABSTRACT) {
janet_table_put(&janet_vm.threaded_abstracts, janet_wrap_abstract(adata), janet_wrap_true());
return;
}
#endif
if (janet_gc_reachable(janet_abstract_head(adata)))
return;
janet_gc_mark(janet_abstract_head(adata));
@ -137,6 +123,8 @@ static void janet_mark_abstract(void *adata) {
/* Mark a bunch of items in memory */
static void janet_mark_many(const Janet *values, int32_t n) {
if (values == NULL)
return;
const Janet *end = values + n;
while (values < end) {
janet_mark(*values);
@ -174,10 +162,13 @@ recur: /* Manual tail recursion */
}
static void janet_mark_struct(const JanetKV *st) {
recur:
if (janet_gc_reachable(janet_struct_head(st)))
return;
janet_gc_mark(janet_struct_head(st));
janet_mark_kvs(st, janet_struct_capacity(st));
st = janet_struct_proto(st);
if (st) goto recur;
}
static void janet_mark_tuple(const Janet *tuple) {
@ -332,25 +323,61 @@ static void janet_deinit_block(JanetGCObject *mem) {
* marked as reachable. Flip the gc color flag for next sweep. */
void janet_sweep() {
JanetGCObject *previous = NULL;
JanetGCObject *current = janet_vm_blocks;
JanetGCObject *current = janet_vm.blocks;
JanetGCObject *next;
while (NULL != current) {
next = current->next;
next = current->data.next;
if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) {
previous = current;
current->flags &= ~JANET_MEM_REACHABLE;
} else {
janet_vm_block_count--;
janet_vm.block_count--;
janet_deinit_block(current);
if (NULL != previous) {
previous->next = next;
previous->data.next = next;
} else {
janet_vm_blocks = next;
janet_vm.blocks = next;
}
janet_free(current);
}
current = next;
}
#ifdef JANET_EV
/* Sweep threaded abstract types for references to decrement */
JanetKV *items = janet_vm.threaded_abstracts.data;
for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) {
if (janet_checktype(items[i].key, JANET_ABSTRACT)) {
/* If item was not visited during the mark phase, then this
* abstract type isn't present in the heap and needs its refcount
* decremented, and shouuld be removed from table. If the refcount is
* then 0, the item will be collected. This ensures that only one interpreter
* will clean up the threaded abstract. */
/* If not visited... */
if (!janet_truthy(items[i].value)) {
void *abst = janet_unwrap_abstract(items[i].key);
if (0 == janet_abstract_decref(abst)) {
/* Run finalizer */
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
/* Mark as tombstone in place */
items[i].key = janet_wrap_nil();
items[i].value = janet_wrap_false();
janet_vm.threaded_abstracts.deleted++;
janet_vm.threaded_abstracts.count--;
/* Free memory */
janet_free(janet_abstract_head(abst));
}
}
/* Reset for next sweep */
items[i].value = janet_wrap_false();
}
}
#endif
}
/* Allocate some memory that is tracked for garbage collection */
@ -358,7 +385,7 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) {
JanetGCObject *mem;
/* Make sure everything is inited */
janet_assert(NULL != janet_vm_cache, "please initialize janet before use");
janet_assert(NULL != janet_vm.cache, "please initialize janet before use");
mem = janet_malloc(size);
/* Check for bad malloc */
@ -370,10 +397,10 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) {
mem->flags = type;
/* Prepend block to heap list */
janet_vm_next_collection += size;
mem->next = janet_vm_blocks;
janet_vm_blocks = mem;
janet_vm_block_count++;
janet_vm.next_collection += size;
mem->data.next = janet_vm.blocks;
janet_vm.blocks = mem;
janet_vm.block_count++;
return (void *)mem;
}
@ -387,10 +414,10 @@ static void free_one_scratch(JanetScratch *s) {
/* Free all allocated scratch memory */
static void janet_free_all_scratch(void) {
for (size_t i = 0; i < janet_scratch_len; i++) {
free_one_scratch(janet_scratch_mem[i]);
for (size_t i = 0; i < janet_vm.scratch_len; i++) {
free_one_scratch(janet_vm.scratch_mem[i]);
}
janet_scratch_len = 0;
janet_vm.scratch_len = 0;
}
static JanetScratch *janet_mem2scratch(void *mem) {
@ -401,29 +428,29 @@ static JanetScratch *janet_mem2scratch(void *mem) {
/* Run garbage collection */
void janet_collect(void) {
uint32_t i;
if (janet_vm_gc_suspend) return;
if (janet_vm.gc_suspend) return;
depth = JANET_RECURSION_GUARD;
/* Try and prevent many major collections back to back.
* A full collection will take O(janet_vm_block_count) time.
* A full collection will take O(janet_vm.block_count) time.
* If we have a large heap, make sure our interval is not too
* small so we won't make many collections over it. This is just a
* heuristic for automatically changing the gc interval */
if (janet_vm_block_count * 8 > janet_vm_gc_interval) {
janet_vm_gc_interval = janet_vm_block_count * sizeof(JanetGCObject);
if (janet_vm.block_count * 8 > janet_vm.gc_interval) {
janet_vm.gc_interval = janet_vm.block_count * sizeof(JanetGCObject);
}
orig_rootcount = janet_vm_root_count;
orig_rootcount = janet_vm.root_count;
#ifdef JANET_EV
janet_ev_mark();
#endif
janet_mark_fiber(janet_vm_root_fiber);
janet_mark_fiber(janet_vm.root_fiber);
for (i = 0; i < orig_rootcount; i++)
janet_mark(janet_vm_roots[i]);
while (orig_rootcount < janet_vm_root_count) {
Janet x = janet_vm_roots[--janet_vm_root_count];
janet_mark(janet_vm.roots[i]);
while (orig_rootcount < janet_vm.root_count) {
Janet x = janet_vm.roots[--janet_vm.root_count];
janet_mark(x);
}
janet_sweep();
janet_vm_next_collection = 0;
janet_vm.next_collection = 0;
janet_free_all_scratch();
}
@ -431,17 +458,17 @@ void janet_collect(void) {
* and all of its children. If gcroot is called on a value n times, unroot
* must also be called n times to remove it as a gc root. */
void janet_gcroot(Janet root) {
size_t newcount = janet_vm_root_count + 1;
if (newcount > janet_vm_root_capacity) {
size_t newcount = janet_vm.root_count + 1;
if (newcount > janet_vm.root_capacity) {
size_t newcap = 2 * newcount;
janet_vm_roots = janet_realloc(janet_vm_roots, sizeof(Janet) * newcap);
if (NULL == janet_vm_roots) {
janet_vm.roots = janet_realloc(janet_vm.roots, sizeof(Janet) * newcap);
if (NULL == janet_vm.roots) {
JANET_OUT_OF_MEMORY;
}
janet_vm_root_capacity = newcap;
janet_vm.root_capacity = newcap;
}
janet_vm_roots[janet_vm_root_count] = root;
janet_vm_root_count = newcount;
janet_vm.roots[janet_vm.root_count] = root;
janet_vm.root_count = newcount;
}
/* Identity equality for GC purposes */
@ -462,11 +489,11 @@ static int janet_gc_idequals(Janet lhs, Janet rhs) {
/* Remove a root value from the GC. This allows the gc to potentially reclaim
* a value and all its children. */
int janet_gcunroot(Janet root) {
Janet *vtop = janet_vm_roots + janet_vm_root_count;
Janet *vtop = janet_vm.roots + janet_vm.root_count;
/* Search from top to bottom as access is most likely LIFO */
for (Janet *v = janet_vm_roots; v < vtop; v++) {
for (Janet *v = janet_vm.roots; v < vtop; v++) {
if (janet_gc_idequals(root, *v)) {
*v = janet_vm_roots[--janet_vm_root_count];
*v = janet_vm.roots[--janet_vm.root_count];
return 1;
}
}
@ -475,12 +502,12 @@ int janet_gcunroot(Janet root) {
/* Remove a root value from the GC. This sets the effective reference count to 0. */
int janet_gcunrootall(Janet root) {
Janet *vtop = janet_vm_roots + janet_vm_root_count;
Janet *vtop = janet_vm.roots + janet_vm.root_count;
int ret = 0;
/* Search from top to bottom as access is most likely LIFO */
for (Janet *v = janet_vm_roots; v < vtop; v++) {
for (Janet *v = janet_vm.roots; v < vtop; v++) {
if (janet_gc_idequals(root, *v)) {
*v = janet_vm_roots[--janet_vm_root_count];
*v = janet_vm.roots[--janet_vm.root_count];
vtop--;
ret = 1;
}
@ -490,24 +517,39 @@ int janet_gcunrootall(Janet root) {
/* Free all allocated memory */
void janet_clear_memory(void) {
JanetGCObject *current = janet_vm_blocks;
#ifdef JANET_EV
JanetKV *items = janet_vm.threaded_abstracts.data;
for (int32_t i = 0; i < janet_vm.threaded_abstracts.capacity; i++) {
if (janet_checktype(items[i].key, JANET_ABSTRACT)) {
void *abst = janet_unwrap_abstract(items[i].key);
if (0 == janet_abstract_decref(abst)) {
JanetAbstractHead *head = janet_abstract_head(abst);
if (head->type->gc) {
janet_assert(!head->type->gc(head->data, head->size), "finalizer failed");
}
janet_free(janet_abstract_head(abst));
}
}
}
#endif
JanetGCObject *current = janet_vm.blocks;
while (NULL != current) {
janet_deinit_block(current);
JanetGCObject *next = current->next;
JanetGCObject *next = current->data.next;
janet_free(current);
current = next;
}
janet_vm_blocks = NULL;
janet_vm.blocks = NULL;
janet_free_all_scratch();
janet_free(janet_scratch_mem);
janet_free(janet_vm.scratch_mem);
}
/* Primitives for suspending GC. */
int janet_gclock(void) {
return janet_vm_gc_suspend++;
return janet_vm.gc_suspend++;
}
void janet_gcunlock(int handle) {
janet_vm_gc_suspend = handle;
janet_vm.gc_suspend = handle;
}
/* Scratch memory API */
@ -518,16 +560,16 @@ void *janet_smalloc(size_t size) {
JANET_OUT_OF_MEMORY;
}
s->finalize = NULL;
if (janet_scratch_len == janet_scratch_cap) {
size_t newcap = 2 * janet_scratch_cap + 2;
JanetScratch **newmem = (JanetScratch **) janet_realloc(janet_scratch_mem, newcap * sizeof(JanetScratch));
if (janet_vm.scratch_len == janet_vm.scratch_cap) {
size_t newcap = 2 * janet_vm.scratch_cap + 2;
JanetScratch **newmem = (JanetScratch **) janet_realloc(janet_vm.scratch_mem, newcap * sizeof(JanetScratch));
if (NULL == newmem) {
JANET_OUT_OF_MEMORY;
}
janet_scratch_cap = newcap;
janet_scratch_mem = newmem;
janet_vm.scratch_cap = newcap;
janet_vm.scratch_mem = newmem;
}
janet_scratch_mem[janet_scratch_len++] = s;
janet_vm.scratch_mem[janet_vm.scratch_len++] = s;
return (char *)(s->mem);
}
@ -544,14 +586,14 @@ void *janet_scalloc(size_t nmemb, size_t size) {
void *janet_srealloc(void *mem, size_t size) {
if (NULL == mem) return janet_smalloc(size);
JanetScratch *s = janet_mem2scratch(mem);
if (janet_scratch_len) {
for (size_t i = janet_scratch_len - 1; ; i--) {
if (janet_scratch_mem[i] == s) {
if (janet_vm.scratch_len) {
for (size_t i = janet_vm.scratch_len - 1; ; i--) {
if (janet_vm.scratch_mem[i] == s) {
JanetScratch *news = janet_realloc(s, size + sizeof(JanetScratch));
if (NULL == news) {
JANET_OUT_OF_MEMORY;
}
janet_scratch_mem[i] = news;
janet_vm.scratch_mem[i] = news;
return (char *)(news->mem);
}
if (i == 0) break;
@ -568,10 +610,10 @@ void janet_sfinalizer(void *mem, JanetScratchFinalizer finalizer) {
void janet_sfree(void *mem) {
if (NULL == mem) return;
JanetScratch *s = janet_mem2scratch(mem);
if (janet_scratch_len) {
for (size_t i = janet_scratch_len - 1; ; i--) {
if (janet_scratch_mem[i] == s) {
janet_scratch_mem[i] = janet_scratch_mem[--janet_scratch_len];
if (janet_vm.scratch_len) {
for (size_t i = janet_vm.scratch_len - 1; ; i--) {
if (janet_vm.scratch_mem[i] == s) {
janet_vm.scratch_mem[i] = janet_vm.scratch_mem[--janet_vm.scratch_len];
free_one_scratch(s);
return;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -55,10 +55,11 @@ enum JanetMemoryType {
JANET_MEMORY_FUNCTION,
JANET_MEMORY_ABSTRACT,
JANET_MEMORY_FUNCENV,
JANET_MEMORY_FUNCDEF
JANET_MEMORY_FUNCDEF,
JANET_MEMORY_THREADED_ABSTRACT,
};
/* To allocate collectable memory, one must calk janet_alloc, initialize the memory,
/* To allocate collectable memory, one must call janet_alloc, initialize the memory,
* and then call when janet_enablegc when it is initailize and reachable by the gc (on the JANET stack) */
void *janet_gcalloc(enum JanetMemoryType type, size_t size);

View File

@ -193,12 +193,16 @@ Janet janet_wrap_u64(uint64_t x) {
return janet_wrap_abstract(box);
}
static Janet cfun_it_s64_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_it_s64_new,
"(int/s64 value)",
"Create a boxed signed 64 bit integer from a string value.") {
janet_fixarity(argc, 1);
return janet_wrap_s64(janet_unwrap_s64(argv[0]));
}
static Janet cfun_it_u64_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_it_u64_new,
"(int/u64 value)",
"Create a boxed unsigned 64 bit integer from a string value.") {
janet_fixarity(argc, 1);
return janet_wrap_u64(janet_unwrap_u64(argv[0]));
}
@ -505,23 +509,14 @@ static int it_u64_get(void *p, Janet key, Janet *out) {
return janet_getmethod(janet_unwrap_keyword(key), it_u64_methods, out);
}
static const JanetReg it_cfuns[] = {
{
"int/s64", cfun_it_s64_new,
JDOC("(int/s64 value)\n\n"
"Create a boxed signed 64 bit integer from a string value.")
},
{
"int/u64", cfun_it_u64_new,
JDOC("(int/u64 value)\n\n"
"Create a boxed unsigned 64 bit integer from a string value.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_inttypes(JanetTable *env) {
janet_core_cfuns(env, NULL, it_cfuns);
JanetRegExt it_cfuns[] = {
JANET_CORE_REG("int/s64", cfun_it_s64_new),
JANET_CORE_REG("int/u64", cfun_it_u64_new),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, it_cfuns);
janet_register_abstract_type(&janet_s64_type);
janet_register_abstract_type(&janet_u64_type);
}

View File

@ -114,7 +114,12 @@ static void *makef(FILE *f, int32_t flags) {
/* Open a process */
#ifndef JANET_NO_PROCESSES
static Janet cfun_io_popen(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_popen,
"(file/popen command &opt mode) (DEPRECATED for os/spawn)",
"Open a file that is backed by a process. The file must be opened in either "
"the :r (read) or the :w (write) mode. In :r mode, the stdout of the "
"process can be read from the file. In :w mode, the stdin of the process "
"can be written to. Returns the new file.") {
janet_arity(argc, 1, 2);
const uint8_t *fname = janet_getstring(argv, 0);
const uint8_t *fmode = NULL;
@ -143,7 +148,10 @@ static Janet cfun_io_popen(int32_t argc, Janet *argv) {
}
#endif
static Janet cfun_io_temp(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_temp,
"(file/temp)",
"Open an anonymous temporary file that is removed on close. "
"Raises an error on failure.") {
(void)argv;
janet_fixarity(argc, 0);
// XXX use mkostemp when we can to avoid CLOEXEC race.
@ -153,7 +161,20 @@ static Janet cfun_io_temp(int32_t argc, Janet *argv) {
return janet_makefile(tmp, JANET_FILE_WRITE | JANET_FILE_READ | JANET_FILE_BINARY);
}
static Janet cfun_io_fopen(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fopen,
"(file/open path &opt mode)",
"Open a file. `path` is an absolute or relative path, and "
"`mode` is a set of flags indicating the mode to open the file in. "
"`mode` is a keyword where each character represents a flag. If the file "
"cannot be opened, returns nil, otherwise returns the new file handle. "
"Mode flags:\n\n"
"* r - allow reading from the file\n\n"
"* w - allow writing to the file\n\n"
"* a - append to the file\n\n"
"Following one of the initial flags, 0 or more of the following flags can be appended:\n\n"
"* b - open the file in binary mode (rather than text mode)\n\n"
"* + - append to the file instead of overwriting it\n\n"
"* n - error if the file cannot be opened instead of returning nil") {
janet_arity(argc, 1, 2);
const uint8_t *fname = janet_getstring(argv, 0);
const uint8_t *fmode;
@ -184,7 +205,16 @@ static void read_chunk(JanetFile *iof, JanetBuffer *buffer, int32_t nBytesMax) {
}
/* Read a certain number of bytes into memory */
static Janet cfun_io_fread(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fread,
"(file/read f what &opt buf)",
"Read a number of bytes from a file `f` into a buffer. A buffer `buf` can "
"be provided as an optional third argument, otherwise a new buffer "
"is created. `what` can either be an integer or a keyword. Returns the "
"buffer with file contents. "
"Values for `what`:\n\n"
"* :all - read the whole file\n\n"
"* :line - read up to and including the next newline character\n\n"
"* n (integer) - read up to n bytes from the file") {
janet_arity(argc, 2, 3);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED) janet_panic("file is closed");
@ -224,7 +254,10 @@ static Janet cfun_io_fread(int32_t argc, Janet *argv) {
}
/* Write bytes to a file */
static Janet cfun_io_fwrite(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fwrite,
"(file/write f bytes)",
"Writes to a file. 'bytes' must be string, buffer, or symbol. Returns the "
"file.") {
janet_arity(argc, 1, -1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
@ -247,7 +280,10 @@ static Janet cfun_io_fwrite(int32_t argc, Janet *argv) {
}
/* Flush the bytes in the file */
static Janet cfun_io_fflush(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fflush,
"(file/flush f)",
"Flush any buffered bytes to the file system. In most files, writes are "
"buffered for efficiency reasons. Returns the file handle.") {
janet_fixarity(argc, 1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
@ -291,7 +327,12 @@ static int cfun_io_gc(void *p, size_t len) {
}
/* Close a file */
static Janet cfun_io_fclose(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fclose,
"(file/close f)",
"Close a file and release all related resources. When you are "
"done reading a file, close it to prevent a resource leak and let "
"other processes read the file. If the file is the result of a file/popen "
"call, close waits for and returns the process exit status.") {
janet_fixarity(argc, 1);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
@ -318,7 +359,15 @@ static Janet cfun_io_fclose(int32_t argc, Janet *argv) {
}
/* Seek a file */
static Janet cfun_io_fseek(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_fseek,
"(file/seek f &opt whence n)",
"Jump to a relative location in the file `f`. `whence` must be one of:\n\n"
"* :cur - jump relative to the current file location\n\n"
"* :set - jump relative to the beginning of the file\n\n"
"* :end - jump relative to the end of the file\n\n"
"By default, `whence` is :cur. Optionally a value `n` may be passed "
"for the relative number of bytes to seek in the file. `n` may be a real "
"number to handle large files of more than 4GB. Returns the file handle.") {
janet_arity(argc, 2, 3);
JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type);
if (iof->flags & JANET_FILE_CLOSED)
@ -480,28 +529,47 @@ static Janet cfun_io_print_impl(int32_t argc, Janet *argv,
return cfun_io_print_impl_x(argc, argv, newline, dflt_file, 0, x);
}
static Janet cfun_io_print(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_print,
"(print & xs)",
"Print values to the console (standard out). Value are converted "
"to strings if they are not already. After printing all values, a "
"newline character is printed. Use the value of (dyn :out stdout) to determine "
"what to push characters to. Expects (dyn :out stdout) to be either a core/file or "
"a buffer. Returns nil.") {
return cfun_io_print_impl(argc, argv, 1, "out", stdout);
}
static Janet cfun_io_prin(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_prin,
"(prin & xs)",
"Same as print, but does not add trailing newline.") {
return cfun_io_print_impl(argc, argv, 0, "out", stdout);
}
static Janet cfun_io_eprint(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_eprint,
"(eprint & xs)",
"Same as print, but uses (dyn :err stderr) instead of (dyn :out stdout).") {
return cfun_io_print_impl(argc, argv, 1, "err", stderr);
}
static Janet cfun_io_eprin(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_eprin,
"(eprin & xs)",
"Same as prin, but uses (dyn :err stderr) instead of (dyn :out stdout).") {
return cfun_io_print_impl(argc, argv, 0, "err", stderr);
}
static Janet cfun_io_xprint(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_xprint,
"(xprint to & xs)",
"Print to a file or other value explicitly (no dynamic bindings) with a trailing "
"newline character. The value to print "
"to is the first argument, and is otherwise the same as print. Returns nil.") {
janet_arity(argc, 1, -1);
return cfun_io_print_impl_x(argc, argv, 1, NULL, 1, argv[0]);
}
static Janet cfun_io_xprin(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_xprin,
"(xprin to & xs)",
"Print to a file or other value explicitly (no dynamic bindings). The value to print "
"to is the first argument, and is otherwise the same as prin. Returns nil.") {
janet_arity(argc, 1, -1);
return cfun_io_print_impl_x(argc, argv, 0, NULL, 1, argv[0]);
}
@ -557,28 +625,40 @@ static Janet cfun_io_printf_impl(int32_t argc, Janet *argv, int newline,
}
static Janet cfun_io_printf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_printf,
"(printf fmt & xs)",
"Prints output formatted as if with (string/format fmt ;xs) to (dyn :out stdout) with a trailing newline.") {
return cfun_io_printf_impl(argc, argv, 1, "out", stdout);
}
static Janet cfun_io_prinf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_prinf,
"(prinf fmt & xs)",
"Like printf but with no trailing newline.") {
return cfun_io_printf_impl(argc, argv, 0, "out", stdout);
}
static Janet cfun_io_eprintf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_eprintf,
"(eprintf fmt & xs)",
"Prints output formatted as if with (string/format fmt ;xs) to (dyn :err stderr) with a trailing newline.") {
return cfun_io_printf_impl(argc, argv, 1, "err", stderr);
}
static Janet cfun_io_eprinf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_eprinf,
"(eprinf fmt & xs)",
"Like eprintf but with no trailing newline.") {
return cfun_io_printf_impl(argc, argv, 0, "err", stderr);
}
static Janet cfun_io_xprintf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_xprintf,
"(xprintf to fmt & xs)",
"Like printf but prints to an explicit file or value to. Returns nil.") {
janet_arity(argc, 2, -1);
return cfun_io_printf_impl_x(argc, argv, 1, NULL, 1, argv[0]);
}
static Janet cfun_io_xprinf(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_xprinf,
"(xprinf to fmt & xs)",
"Like prinf but prints to an explicit file or value to. Returns nil.") {
janet_arity(argc, 2, -1);
return cfun_io_printf_impl_x(argc, argv, 0, NULL, 1, argv[0]);
}
@ -601,14 +681,18 @@ static void janet_flusher(const char *name, FILE *dflt_file) {
}
}
static Janet cfun_io_flush(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_flush,
"(flush)",
"Flush (dyn :out stdout) if it is a file, otherwise do nothing.") {
janet_fixarity(argc, 0);
(void) argv;
janet_flusher("out", stdout);
return janet_wrap_nil();
}
static Janet cfun_io_eflush(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_io_eflush,
"(eflush)",
"Flush (dyn :err stderr) if it is a file, otherwise do nothing.") {
janet_fixarity(argc, 0);
(void) argv;
janet_flusher("err", stderr);
@ -651,162 +735,6 @@ void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...)
return;
}
static const JanetReg io_cfuns[] = {
{
"print", cfun_io_print,
JDOC("(print & xs)\n\n"
"Print values to the console (standard out). Value are converted "
"to strings if they are not already. After printing all values, a "
"newline character is printed. Use the value of (dyn :out stdout) to determine "
"what to push characters to. Expects (dyn :out stdout) to be either a core/file or "
"a buffer. Returns nil.")
},
{
"prin", cfun_io_prin,
JDOC("(prin & xs)\n\n"
"Same as print, but does not add trailing newline.")
},
{
"printf", cfun_io_printf,
JDOC("(printf fmt & xs)\n\n"
"Prints output formatted as if with (string/format fmt ;xs) to (dyn :out stdout) with a trailing newline.")
},
{
"prinf", cfun_io_prinf,
JDOC("(prinf fmt & xs)\n\n"
"Like printf but with no trailing newline.")
},
{
"eprin", cfun_io_eprin,
JDOC("(eprin & xs)\n\n"
"Same as prin, but uses (dyn :err stderr) instead of (dyn :out stdout).")
},
{
"eprint", cfun_io_eprint,
JDOC("(eprint & xs)\n\n"
"Same as print, but uses (dyn :err stderr) instead of (dyn :out stdout).")
},
{
"eprintf", cfun_io_eprintf,
JDOC("(eprintf fmt & xs)\n\n"
"Prints output formatted as if with (string/format fmt ;xs) to (dyn :err stderr) with a trailing newline.")
},
{
"eprinf", cfun_io_eprinf,
JDOC("(eprinf fmt & xs)\n\n"
"Like eprintf but with no trailing newline.")
},
{
"xprint", cfun_io_xprint,
JDOC("(xprint to & xs)\n\n"
"Print to a file or other value explicitly (no dynamic bindings) with a trailing "
"newline character. The value to print "
"to is the first argument, and is otherwise the same as print. Returns nil.")
},
{
"xprin", cfun_io_xprin,
JDOC("(xprin to & xs)\n\n"
"Print to a file or other value explicitly (no dynamic bindings). The value to print "
"to is the first argument, and is otherwise the same as prin. Returns nil.")
},
{
"xprintf", cfun_io_xprintf,
JDOC("(xprint to fmt & xs)\n\n"
"Like printf but prints to an explicit file or value to. Returns nil.")
},
{
"xprinf", cfun_io_xprinf,
JDOC("(xprin to fmt & xs)\n\n"
"Like prinf but prints to an explicit file or value to. Returns nil.")
},
{
"flush", cfun_io_flush,
JDOC("(flush)\n\n"
"Flush (dyn :out stdout) if it is a file, otherwise do nothing.")
},
{
"eflush", cfun_io_eflush,
JDOC("(eflush)\n\n"
"Flush (dyn :err stderr) if it is a file, otherwise do nothing.")
},
{
"file/temp", cfun_io_temp,
JDOC("(file/temp)\n\n"
"Open an anonymous temporary file that is removed on close. "
"Raises an error on failure.")
},
{
"file/open", cfun_io_fopen,
JDOC("(file/open path &opt mode)\n\n"
"Open a file. `path` is an absolute or relative path, and "
"`mode` is a set of flags indicating the mode to open the file in. "
"`mode` is a keyword where each character represents a flag. If the file "
"cannot be opened, returns nil, otherwise returns the new file handle. "
"Mode flags:\n\n"
"* r - allow reading from the file\n\n"
"* w - allow writing to the file\n\n"
"* a - append to the file\n\n"
"Following one of the initial flags, 0 or more of the following flags can be appended:\n\n"
"* b - open the file in binary mode (rather than text mode)\n\n"
"* + - append to the file instead of overwriting it\n\n"
"* n - error if the file cannot be opened instead of returning nil")
},
{
"file/close", cfun_io_fclose,
JDOC("(file/close f)\n\n"
"Close a file and release all related resources. When you are "
"done reading a file, close it to prevent a resource leak and let "
"other processes read the file. If the file is the result of a file/popen "
"call, close waits for and returns the process exit status.")
},
{
"file/read", cfun_io_fread,
JDOC("(file/read f what &opt buf)\n\n"
"Read a number of bytes from a file `f` into a buffer. A buffer `buf` can "
"be provided as an optional third argument, otherwise a new buffer "
"is created. `what` can either be an integer or a keyword. Returns the "
"buffer with file contents. "
"Values for `what`:\n\n"
"* :all - read the whole file\n\n"
"* :line - read up to and including the next newline character\n\n"
"* n (integer) - read up to n bytes from the file")
},
{
"file/write", cfun_io_fwrite,
JDOC("(file/write f bytes)\n\n"
"Writes to a file. 'bytes' must be string, buffer, or symbol. Returns the "
"file.")
},
{
"file/flush", cfun_io_fflush,
JDOC("(file/flush f)\n\n"
"Flush any buffered bytes to the file system. In most files, writes are "
"buffered for efficiency reasons. Returns the file handle.")
},
{
"file/seek", cfun_io_fseek,
JDOC("(file/seek f &opt whence n)\n\n"
"Jump to a relative location in the file `f`. `whence` must be one of:\n\n"
"* :cur - jump relative to the current file location\n\n"
"* :set - jump relative to the beginning of the file\n\n"
"* :end - jump relative to the end of the file\n\n"
"By default, `whence` is :cur. Optionally a value `n` may be passed "
"for the relative number of bytes to seek in the file. `n` may be a real "
"number to handle large files of more than 4GB. Returns the file handle.")
},
#ifndef JANET_NO_PROCESSES
{
"file/popen", cfun_io_popen,
JDOC("(file/popen command &opt mode) (DEPRECATED for os/spawn)\n\n"
"Open a file that is backed by a process. The file must be opened in either "
"the :r (read) or the :w (write) mode. In :r mode, the stdout of the "
"process can be read from the file. In :w mode, the stdin of the process "
"can be written to. Returns the new file.")
},
#endif
{NULL, NULL, NULL}
};
/* C API */
JanetFile *janet_getjfile(const Janet *argv, int32_t n) {
@ -839,20 +767,47 @@ FILE *janet_unwrapfile(Janet j, int *flags) {
/* Module entry point */
void janet_lib_io(JanetTable *env) {
janet_core_cfuns(env, NULL, io_cfuns);
JanetRegExt io_cfuns[] = {
JANET_CORE_REG("print", cfun_io_print),
JANET_CORE_REG("prin", cfun_io_prin),
JANET_CORE_REG("printf", cfun_io_printf),
JANET_CORE_REG("prinf", cfun_io_prinf),
JANET_CORE_REG("eprin", cfun_io_eprin),
JANET_CORE_REG("eprint", cfun_io_eprint),
JANET_CORE_REG("eprintf", cfun_io_eprintf),
JANET_CORE_REG("eprinf", cfun_io_eprinf),
JANET_CORE_REG("xprint", cfun_io_xprint),
JANET_CORE_REG("xprin", cfun_io_xprin),
JANET_CORE_REG("xprintf", cfun_io_xprintf),
JANET_CORE_REG("xprinf", cfun_io_xprinf),
JANET_CORE_REG("flush", cfun_io_flush),
JANET_CORE_REG("eflush", cfun_io_eflush),
JANET_CORE_REG("file/temp", cfun_io_temp),
JANET_CORE_REG("file/open", cfun_io_fopen),
JANET_CORE_REG("file/close", cfun_io_fclose),
JANET_CORE_REG("file/read", cfun_io_fread),
JANET_CORE_REG("file/write", cfun_io_fwrite),
JANET_CORE_REG("file/flush", cfun_io_fflush),
JANET_CORE_REG("file/seek", cfun_io_fseek),
#ifndef JANET_NO_PROCESSES
JANET_CORE_REG("file/popen", cfun_io_popen),
#endif
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, io_cfuns);
janet_register_abstract_type(&janet_file_type);
int default_flags = JANET_FILE_NOT_CLOSEABLE | JANET_FILE_SERIALIZABLE;
/* stdout */
janet_core_def(env, "stdout",
JANET_CORE_DEF(env, "stdout",
janet_makefile(stdout, JANET_FILE_APPEND | default_flags),
JDOC("The standard output file."));
"The standard output file.");
/* stderr */
janet_core_def(env, "stderr",
JANET_CORE_DEF(env, "stderr",
janet_makefile(stderr, JANET_FILE_APPEND | default_flags),
JDOC("The standard error file."));
"The standard error file.");
/* stdin */
janet_core_def(env, "stdin",
JANET_CORE_DEF(env, "stdin",
janet_makefile(stdin, JANET_FILE_READ | default_flags),
JDOC("The standard input file."));
"The standard input file.");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -65,6 +65,9 @@ enum {
LB_UNSAFE_CFUNCTION, /* 221 */
LB_UNSAFE_POINTER, /* 222 */
LB_STRUCT_PROTO, /* 223 */
#ifdef JANET_EV
LB_THREADED_ABSTRACT/* 224 */
#endif
} LeadBytes;
/* Helper to look inside an entry in an environment */
@ -326,6 +329,7 @@ static void marshal_one_fiber(MarshalState *st, JanetFiber *fiber, int flags) {
}
if (fiber->child)
marshal_one(st, janet_wrap_fiber(fiber->child), flags + 1);
marshal_one(st, fiber->last_value, flags + 1);
}
void janet_marshal_size(JanetMarshalContext *ctx, size_t value) {
@ -370,6 +374,21 @@ void janet_marshal_abstract(JanetMarshalContext *ctx, void *abstract) {
static void marshal_one_abstract(MarshalState *st, Janet x, int flags) {
void *abstract = janet_unwrap_abstract(x);
#ifdef JANET_EV
/* Threaded abstract types get passed through as pointers in the unsafe mode */
if ((flags & JANET_MARSHAL_UNSAFE) &&
(JANET_MEMORY_THREADED_ABSTRACT == (janet_abstract_head(abstract)->gc.flags & JANET_MEM_TYPEBITS))) {
/* Increment refcount before sending message. This prevents a "death in transit" problem
* where a message is garbage collected while in transit between two threads - i.e., the sending threads
* loses the reference and runs a garbage collection before the receiving thread gets the message. */
janet_abstract_incref(abstract);
pushbyte(st, LB_THREADED_ABSTRACT);
pushbytes(st, (uint8_t *) &abstract, sizeof(abstract));
MARK_SEEN();
return;
}
#endif
const JanetAbstractType *at = janet_abstract_type(abstract);
if (at->marshal) {
pushbyte(st, LB_ABSTRACT);
@ -377,7 +396,7 @@ static void marshal_one_abstract(MarshalState *st, Janet x, int flags) {
JanetMarshalContext context = {st, NULL, flags, NULL, at};
at->marshal(abstract, &context);
} else {
janet_panicf("try to marshal unregistered abstract type, cannot marshal %p", x);
janet_panicf("cannot marshal %p", x);
}
}
@ -545,9 +564,9 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
case JANET_FUNCTION: {
pushbyte(st, LB_FUNCTION);
JanetFunction *func = janet_unwrap_function(x);
pushint(st, func->def->environments_length);
/* Mark seen before reading def */
MARK_SEEN();
pushint(st, func->def->environments_length);
marshal_one_def(st, func->def, flags);
for (int32_t i = 0; i < func->def->environments_length; i++)
marshal_one_env(st, func->envs[i], flags + 1);
@ -938,6 +957,7 @@ static const uint8_t *unmarshal_one_fiber(
fiber->data = NULL;
fiber->child = NULL;
fiber->env = NULL;
fiber->last_value = janet_wrap_nil();
#ifdef JANET_EV
fiber->waiting = NULL;
fiber->sched_id = 0;
@ -1049,6 +1069,9 @@ static const uint8_t *unmarshal_one_fiber(
fiber->child = janet_unwrap_fiber(fiberv);
}
/* Get the fiber last value */
data = unmarshal_one(st, data, &fiber->last_value, flags + 1);
/* We have valid fiber, finally construct remaining fields. */
fiber->frame = frame;
fiber->flags = fiber_flags;
@ -1106,14 +1129,18 @@ Janet janet_unmarshal_janet(JanetMarshalContext *ctx) {
return ret;
}
void *janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size) {
void janet_unmarshal_abstract_reuse(JanetMarshalContext *ctx, void *p) {
UnmarshalState *st = (UnmarshalState *)(ctx->u_state);
if (ctx->at == NULL) {
janet_panicf("janet_unmarshal_abstract called more than once");
}
void *p = janet_abstract(ctx->at, size);
janet_v_push(st->lookup, janet_wrap_abstract(p));
ctx->at = NULL;
}
void *janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size) {
void *p = janet_abstract(ctx->at, size);
janet_unmarshal_abstract_reuse(ctx, p);
return p;
}
@ -1121,17 +1148,16 @@ static const uint8_t *unmarshal_one_abstract(UnmarshalState *st, const uint8_t *
Janet key;
data = unmarshal_one(st, data, &key, flags + 1);
const JanetAbstractType *at = janet_get_abstract_type(key);
if (at == NULL) goto oops;
if (at == NULL) janet_panic("unknown abstract type");
if (at->unmarshal) {
JanetMarshalContext context = {NULL, st, flags, data, at};
*out = janet_wrap_abstract(at->unmarshal(&context));
if (context.at != NULL) {
janet_panicf("janet_unmarshal_abstract not called");
janet_panic("janet_unmarshal_abstract not called");
}
return context.data;
}
oops:
janet_panic("invalid abstract type");
janet_panic("invalid abstract type - no unmarshal function pointer");
}
static const uint8_t *unmarshal_one(
@ -1236,18 +1262,16 @@ static const uint8_t *unmarshal_one(
data++;
int32_t len = readnat(st, &data);
if (len > 255) {
janet_panicf("invalid function");
janet_panicf("invalid function - too many environments (%d)", len);
}
func = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) +
len * sizeof(JanetFuncEnv));
func->def = NULL;
*out = janet_wrap_function(func);
janet_v_push(st->lookup, *out);
data = unmarshal_one_def(st, data, &def, flags + 1);
if (def->environments_length != len) {
janet_panicf("invalid function");
}
func->def = def;
for (int32_t i = 0; i < def->environments_length; i++) {
for (int32_t i = 0; i < len; i++) {
data = unmarshal_one_env(st, data, &(func->envs[i]), flags + 1);
}
return data;
@ -1367,6 +1391,42 @@ static const uint8_t *unmarshal_one(
janet_v_push(st->lookup, *out);
return data;
}
#ifdef JANET_EV
case LB_THREADED_ABSTRACT: {
MARSH_EOS(st, data + sizeof(void *));
data++;
if (!(flags & JANET_MARSHAL_UNSAFE)) {
janet_panicf("unsafe flag not given, "
"will not unmarshal threaded abstract pointer at index %d",
(int)(data - st->start));
}
union {
void *ptr;
uint8_t bytes[sizeof(void *)];
} u;
memcpy(u.bytes, data, sizeof(void *));
data += sizeof(void *);
if (flags & JANET_MARSHAL_DECREF) {
/* Decrement immediately and don't bother putting into heap */
janet_abstract_decref(u.ptr);
*out = janet_wrap_nil();
} else {
*out = janet_wrap_abstract(u.ptr);
Janet check = janet_table_get(&janet_vm.threaded_abstracts, *out);
if (janet_checktype(check, JANET_NIL)) {
/* Transfers reference from threaded channel buffer to current heap */
janet_table_put(&janet_vm.threaded_abstracts, *out, janet_wrap_false());
} else {
/* Heap reference already accounted for, remove threaded channel reference. */
janet_abstract_decref(u.ptr);
}
}
janet_v_push(st->lookup, *out);
return data;
}
#endif
default: {
janet_panicf("unknown byte %x at index %d",
*data,
@ -1374,7 +1434,6 @@ static const uint8_t *unmarshal_one(
return NULL;
}
}
#undef EXTRA
}
Janet janet_unmarshal(
@ -1401,13 +1460,24 @@ Janet janet_unmarshal(
/* C functions */
static Janet cfun_env_lookup(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_env_lookup,
"(env-lookup env)",
"Creates a forward lookup table for unmarshalling from an environment. "
"To create a reverse lookup table, use the invert function to swap keys "
"and values in the returned table.") {
janet_fixarity(argc, 1);
JanetTable *env = janet_gettable(argv, 0);
return janet_wrap_table(janet_env_lookup(env));
}
static Janet cfun_marshal(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_marshal,
"(marshal x &opt reverse-lookup buffer)",
"Marshal a value into a buffer and return the buffer. The buffer "
"can then later be unmarshalled to reconstruct the initial value. "
"Optionally, one can pass in a reverse lookup table to not marshal "
"aliased values that are found in the table. Then a forward "
"lookup table can be used to recover the original value when "
"unmarshalling.") {
janet_arity(argc, 1, 3);
JanetBuffer *buffer;
JanetTable *rreg = NULL;
@ -1423,7 +1493,11 @@ static Janet cfun_marshal(int32_t argc, Janet *argv) {
return janet_wrap_buffer(buffer);
}
static Janet cfun_unmarshal(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_unmarshal,
"(unmarshal buffer &opt lookup)",
"Unmarshal a value from a buffer. An optional lookup table "
"can be provided to allow for aliases to be resolved. Returns the value "
"unmarshalled from the buffer.") {
janet_arity(argc, 1, 2);
JanetByteView view = janet_getbytes(argv, 0);
JanetTable *reg = NULL;
@ -1433,35 +1507,13 @@ static Janet cfun_unmarshal(int32_t argc, Janet *argv) {
return janet_unmarshal(view.bytes, (size_t) view.len, 0, reg, NULL);
}
static const JanetReg marsh_cfuns[] = {
{
"marshal", cfun_marshal,
JDOC("(marshal x &opt reverse-lookup buffer)\n\n"
"Marshal a value into a buffer and return the buffer. The buffer "
"can then later be unmarshalled to reconstruct the initial value. "
"Optionally, one can pass in a reverse lookup table to not marshal "
"aliased values that are found in the table. Then a forward "
"lookup table can be used to recover the original value when "
"unmarshalling.")
},
{
"unmarshal", cfun_unmarshal,
JDOC("(unmarshal buffer &opt lookup)\n\n"
"Unmarshal a value from a buffer. An optional lookup table "
"can be provided to allow for aliases to be resolved. Returns the value "
"unmarshalled from the buffer.")
},
{
"env-lookup", cfun_env_lookup,
JDOC("(env-lookup env)\n\n"
"Creates a forward lookup table for unmarshalling from an environment. "
"To create a reverse lookup table, use the invert function to swap keys "
"and values in the returned table.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_marsh(JanetTable *env) {
janet_core_cfuns(env, NULL, marsh_cfuns);
JanetRegExt marsh_cfuns[] = {
JANET_CORE_REG("marshal", cfun_marshal),
JANET_CORE_REG("unmarshal", cfun_unmarshal),
JANET_CORE_REG("env-lookup", cfun_env_lookup),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, marsh_cfuns);
}

View File

@ -23,13 +23,12 @@
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include "state.h"
#include "util.h"
#endif
#include <math.h>
static JANET_THREAD_LOCAL JanetRNG janet_vm_rng = {0, 0, 0, 0, 0};
static int janet_rng_get(void *p, Janet key, Janet *out);
static Janet janet_rng_next(void *p, Janet key);
@ -69,7 +68,7 @@ const JanetAbstractType janet_rng_type = {
};
JanetRNG *janet_default_rng(void) {
return &janet_vm_rng;
return &janet_vm.rng;
}
void janet_rng_seed(JanetRNG *rng, uint32_t seed) {
@ -118,7 +117,12 @@ double janet_rng_double(JanetRNG *rng) {
return ldexp((double)(big >> (64 - 52)), -52);
}
static Janet cfun_rng_make(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_rng_make,
"(math/rng &opt seed)",
"Creates a Psuedo-Random number generator, with an optional seed. "
"The seed should be an unsigned 32 bit integer or a buffer. "
"Do not use this for cryptography. Returns a core/rng abstract type."
) {
janet_arity(argc, 0, 1);
JanetRNG *rng = janet_abstract(&janet_rng_type, sizeof(JanetRNG));
if (argc == 1) {
@ -135,13 +139,20 @@ static Janet cfun_rng_make(int32_t argc, Janet *argv) {
return janet_wrap_abstract(rng);
}
static Janet cfun_rng_uniform(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_rng_uniform,
"(math/rng-uniform rng)",
"Extract a random number in the range [0, 1) from the RNG."
) {
janet_fixarity(argc, 1);
JanetRNG *rng = janet_getabstract(argv, 0, &janet_rng_type);
return janet_wrap_number(janet_rng_double(rng));
}
static Janet cfun_rng_int(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_rng_int,
"(math/rng-int rng &opt max)",
"Extract a random random integer in the range [0, max] from the RNG. If "
"no max is given, the default is 2^31 - 1."
) {
janet_arity(argc, 1, 2);
JanetRNG *rng = janet_getabstract(argv, 0, &janet_rng_type);
if (argc == 1) {
@ -169,7 +180,11 @@ static void rng_get_4bytes(JanetRNG *rng, uint8_t *buf) {
buf[3] = (word >> 24) & 0xFF;
}
static Janet cfun_rng_buffer(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_rng_buffer,
"(math/rng-buffer rng n &opt buf)",
"Get n random bytes and put them in a buffer. Creates a new buffer if no buffer is "
"provided, otherwise appends to the given buffer. Returns the buffer."
) {
janet_arity(argc, 2, 3);
JanetRNG *rng = janet_getabstract(argv, 0, &janet_rng_type);
int32_t n = janet_getnat(argv, 1);
@ -214,314 +229,193 @@ static Janet janet_rng_next(void *p, Janet key) {
}
/* Get a random number */
static Janet janet_rand(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_rand,
"(math/random)",
"Returns a uniformly distributed random number between 0 and 1") {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_number(janet_rng_double(&janet_vm_rng));
return janet_wrap_number(janet_rng_double(&janet_vm.rng));
}
/* Seed the random number generator */
static Janet janet_srand(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_srand,
"(math/seedrandom seed)",
"Set the seed for the random number generator. seed should be "
"an integer or a buffer."
) {
janet_fixarity(argc, 1);
if (janet_checkint(argv[0])) {
uint32_t seed = (uint32_t)(janet_getinteger(argv, 0));
janet_rng_seed(&janet_vm_rng, seed);
janet_rng_seed(&janet_vm.rng, seed);
} else {
JanetByteView bytes = janet_getbytes(argv, 0);
janet_rng_longseed(&janet_vm_rng, bytes.bytes, bytes.len);
janet_rng_longseed(&janet_vm.rng, bytes.bytes, bytes.len);
}
return janet_wrap_nil();
}
#define JANET_DEFINE_MATHOP(name, fop)\
static Janet janet_##name(int32_t argc, Janet *argv) {\
#define JANET_DEFINE_MATHOP(name, fop, doc)\
JANET_CORE_FN(janet_##name, "(math/" #name " x)", doc) {\
janet_fixarity(argc, 1); \
double x = janet_getnumber(argv, 0); \
return janet_wrap_number(fop(x)); \
}
JANET_DEFINE_MATHOP(acos, acos)
JANET_DEFINE_MATHOP(asin, asin)
JANET_DEFINE_MATHOP(atan, atan)
JANET_DEFINE_MATHOP(cos, cos)
JANET_DEFINE_MATHOP(cosh, cosh)
JANET_DEFINE_MATHOP(acosh, acosh)
JANET_DEFINE_MATHOP(sin, sin)
JANET_DEFINE_MATHOP(sinh, sinh)
JANET_DEFINE_MATHOP(asinh, asinh)
JANET_DEFINE_MATHOP(tan, tan)
JANET_DEFINE_MATHOP(tanh, tanh)
JANET_DEFINE_MATHOP(atanh, atanh)
JANET_DEFINE_MATHOP(exp, exp)
JANET_DEFINE_MATHOP(exp2, exp2)
JANET_DEFINE_MATHOP(expm1, expm1)
JANET_DEFINE_MATHOP(log, log)
JANET_DEFINE_MATHOP(log10, log10)
JANET_DEFINE_MATHOP(log2, log2)
JANET_DEFINE_MATHOP(sqrt, sqrt)
JANET_DEFINE_MATHOP(cbrt, cbrt)
JANET_DEFINE_MATHOP(ceil, ceil)
JANET_DEFINE_MATHOP(fabs, fabs)
JANET_DEFINE_MATHOP(floor, floor)
JANET_DEFINE_MATHOP(trunc, trunc)
JANET_DEFINE_MATHOP(round, round)
JANET_DEFINE_MATHOP(gamma, lgamma)
JANET_DEFINE_MATHOP(log1p, log1p)
JANET_DEFINE_MATHOP(erf, erf)
JANET_DEFINE_MATHOP(erfc, erfc)
JANET_DEFINE_MATHOP(acos, acos, "Returns the arccosize of x.")
JANET_DEFINE_MATHOP(asin, asin, "Returns the arcsin of x.")
JANET_DEFINE_MATHOP(atan, atan, "Returns the arctangent of x.")
JANET_DEFINE_MATHOP(cos, cos, "Returns the cosine of x.")
JANET_DEFINE_MATHOP(cosh, cosh, "Returns the hyperbolic cosine of x.")
JANET_DEFINE_MATHOP(acosh, acosh, "Returns the hyperbolic arccosine of x.")
JANET_DEFINE_MATHOP(sin, sin, "Returns the sine of x.")
JANET_DEFINE_MATHOP(sinh, sinh, "Returns the hyperbolic sine of x.")
JANET_DEFINE_MATHOP(asinh, asinh, "Returns the hypberbolic arcsine of x.")
JANET_DEFINE_MATHOP(tan, tan, "Returns the tangent of x.")
JANET_DEFINE_MATHOP(tanh, tanh, "Returns the hyperbolic tangent of x.")
JANET_DEFINE_MATHOP(atanh, atanh, "Returns the hyperbolic arctangent of x.")
JANET_DEFINE_MATHOP(exp, exp, "Returns e to the power of x.")
JANET_DEFINE_MATHOP(exp2, exp2, "Returns 2 to the power of x.")
JANET_DEFINE_MATHOP(expm1, expm1, "Returns e to the power of x minus 1.")
JANET_DEFINE_MATHOP(log, log, "Returns the natural logarithm of x.")
JANET_DEFINE_MATHOP(log10, log10, "Returns the log base 10 of x.")
JANET_DEFINE_MATHOP(log2, log2, "Returns the log base 2 of x.")
JANET_DEFINE_MATHOP(sqrt, sqrt, "Returns the square root of x.")
JANET_DEFINE_MATHOP(cbrt, cbrt, "Returns the cube root of x.")
JANET_DEFINE_MATHOP(ceil, ceil, "Returns the smallest integer value number that is not less than x.")
JANET_DEFINE_MATHOP(fabs, fabs, "Return the absolute value of x.")
JANET_DEFINE_MATHOP(floor, floor, "Returns the largest integer value number that is not greater than x.")
JANET_DEFINE_MATHOP(trunc, trunc, "Returns the integer between x and 0 nearest to x.")
JANET_DEFINE_MATHOP(round, round, "Returns the integer nearest to x.")
JANET_DEFINE_MATHOP(gamma, lgamma, "Returns gamma(x).")
JANET_DEFINE_MATHOP(log1p, log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
JANET_DEFINE_MATHOP(erf, erf, "Returns the error function of x.")
JANET_DEFINE_MATHOP(erfc, erfc, "Returns the complementary error function of x.")
#define JANET_DEFINE_MATH2OP(name, fop)\
static Janet janet_##name(int32_t argc, Janet *argv) {\
#define JANET_DEFINE_MATH2OP(name, fop, signature, doc)\
JANET_CORE_FN(janet_##name, signature, doc) {\
janet_fixarity(argc, 2); \
double lhs = janet_getnumber(argv, 0); \
double rhs = janet_getnumber(argv, 1); \
return janet_wrap_number(fop(lhs, rhs)); \
}\
}
JANET_DEFINE_MATH2OP(atan2, atan2)
JANET_DEFINE_MATH2OP(pow, pow)
JANET_DEFINE_MATH2OP(hypot, hypot)
JANET_DEFINE_MATH2OP(nextafter, nextafter)
JANET_DEFINE_MATH2OP(atan2, atan2, "(math/atan2 y x)", "Returns the arctangent of y/x. Works even when x is 0.")
JANET_DEFINE_MATH2OP(pow, pow, "(math/pow a x)", "Returns a to the power of x.")
JANET_DEFINE_MATH2OP(hypot, hypot, "(math/hypot a b)", "Returns c from the equation c^2 = a^2 + b^2.")
JANET_DEFINE_MATH2OP(nextafter, nextafter, "(math/next x y)", "Returns the next representable floating point vaue after x in the direction of y.")
static Janet janet_not(int32_t argc, Janet *argv) {
JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") {
janet_fixarity(argc, 1);
return janet_wrap_boolean(!janet_truthy(argv[0]));
}
static const JanetReg math_cfuns[] = {
{
"not", janet_not,
JDOC("(not x)\n\nReturns the boolean inverse of x.")
},
{
"math/random", janet_rand,
JDOC("(math/random)\n\n"
"Returns a uniformly distributed random number between 0 and 1.")
},
{
"math/seedrandom", janet_srand,
JDOC("(math/seedrandom seed)\n\n"
"Set the seed for the random number generator. seed should be "
"an integer or a buffer.")
},
{
"math/cos", janet_cos,
JDOC("(math/cos x)\n\n"
"Returns the cosine of x.")
},
{
"math/sin", janet_sin,
JDOC("(math/sin x)\n\n"
"Returns the sine of x.")
},
{
"math/tan", janet_tan,
JDOC("(math/tan x)\n\n"
"Returns the tangent of x.")
},
{
"math/acos", janet_acos,
JDOC("(math/acos x)\n\n"
"Returns the arccosine of x.")
},
{
"math/asin", janet_asin,
JDOC("(math/asin x)\n\n"
"Returns the arcsine of x.")
},
{
"math/atan", janet_atan,
JDOC("(math/atan x)\n\n"
"Returns the arctangent of x.")
},
{
"math/exp", janet_exp,
JDOC("(math/exp x)\n\n"
"Returns e to the power of x.")
},
{
"math/log", janet_log,
JDOC("(math/log x)\n\n"
"Returns log base natural number of x.")
},
{
"math/log10", janet_log10,
JDOC("(math/log10 x)\n\n"
"Returns log base 10 of x.")
},
{
"math/log2", janet_log2,
JDOC("(math/log2 x)\n\n"
"Returns log base 2 of x.")
},
{
"math/sqrt", janet_sqrt,
JDOC("(math/sqrt x)\n\n"
"Returns the square root of x.")
},
{
"math/cbrt", janet_cbrt,
JDOC("(math/cbrt x)\n\n"
"Returns the cube root of x.")
},
{
"math/floor", janet_floor,
JDOC("(math/floor x)\n\n"
"Returns the largest integer value number that is not greater than x.")
},
{
"math/ceil", janet_ceil,
JDOC("(math/ceil x)\n\n"
"Returns the smallest integer value number that is not less than x.")
},
{
"math/pow", janet_pow,
JDOC("(math/pow a x)\n\n"
"Return a to the power of x.")
},
{
"math/abs", janet_fabs,
JDOC("(math/abs x)\n\n"
"Return the absolute value of x.")
},
{
"math/sinh", janet_sinh,
JDOC("(math/sinh x)\n\n"
"Return the hyperbolic sine of x.")
},
{
"math/cosh", janet_cosh,
JDOC("(math/cosh x)\n\n"
"Return the hyperbolic cosine of x.")
},
{
"math/tanh", janet_tanh,
JDOC("(math/tanh x)\n\n"
"Return the hyperbolic tangent of x.")
},
{
"math/atanh", janet_atanh,
JDOC("(math/atanh x)\n\n"
"Return the hyperbolic arctangent of x.")
},
{
"math/asinh", janet_asinh,
JDOC("(math/asinh x)\n\n"
"Return the hyperbolic arcsine of x.")
},
{
"math/acosh", janet_acosh,
JDOC("(math/acosh x)\n\n"
"Return the hyperbolic arccosine of x.")
},
{
"math/atan2", janet_atan2,
JDOC("(math/atan2 y x)\n\n"
"Return the arctangent of y/x. Works even when x is 0.")
},
{
"math/rng", cfun_rng_make,
JDOC("(math/rng &opt seed)\n\n"
"Creates a Psuedo-Random number generator, with an optional seed. "
"The seed should be an unsigned 32 bit integer or a buffer. "
"Do not use this for cryptography. Returns a core/rng abstract type.")
},
{
"math/rng-uniform", cfun_rng_uniform,
JDOC("(math/rng-seed rng seed)\n\n"
"Extract a random number in the range [0, 1) from the RNG.")
},
{
"math/rng-int", cfun_rng_int,
JDOC("(math/rng-int rng &opt max)\n\n"
"Extract a random random integer in the range [0, max] from the RNG. If "
"no max is given, the default is 2^31 - 1.")
},
{
"math/rng-buffer", cfun_rng_buffer,
JDOC("(math/rng-buffer rng n &opt buf)\n\n"
"Get n random bytes and put them in a buffer. Creates a new buffer if no buffer is "
"provided, otherwise appends to the given buffer. Returns the buffer.")
},
{
"math/hypot", janet_hypot,
JDOC("(math/hypot a b)\n\n"
"Returns the c from the equation c^2 = a^2 + b^2")
},
{
"math/exp2", janet_exp2,
JDOC("(math/exp2 x)\n\n"
"Returns 2 to the power of x.")
},
{
"math/log1p", janet_log1p,
JDOC("(math/log1p x)\n\n"
"Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)")
},
{
"math/gamma", janet_gamma,
JDOC("(math/gamma x)\n\n"
"Returns gamma(x).")
},
{
"math/erfc", janet_erfc,
JDOC("(math/erfc x)\n\n"
"Returns the complementary error function of x.")
},
{
"math/erf", janet_erf,
JDOC("(math/erf x)\n\n"
"Returns the error function of x.")
},
{
"math/expm1", janet_expm1,
JDOC("(math/expm1 x)\n\n"
"Returns e to the power of x minus 1.")
},
{
"math/trunc", janet_trunc,
JDOC("(math/trunc x)\n\n"
"Returns the integer between x and 0 nearest to x.")
},
{
"math/round", janet_round,
JDOC("(math/round x)\n\n"
"Returns the integer nearest to x.")
},
{
"math/next", janet_nextafter,
JDOC("(math/next x y)\n\n"
"Returns the next representable floating point value after x in the direction of y.")
},
{NULL, NULL, NULL}
};
static double janet_gcd(double x, double y) {
if (isnan(x) || isnan(y)) {
#ifdef NAN
return NAN;
#else
return 0.0 \ 0.0;
#endif
}
if (isinf(x) || isinf(y)) return INFINITY;
while (y != 0) {
double temp = y;
y = fmod(x, y);
x = temp;
}
return x;
}
static double janet_lcm(double x, double y) {
return (x / janet_gcd(x, y)) * y;
}
JANET_CORE_FN(janet_cfun_gcd, "(math/gcd x y)",
"Returns the greatest common divisor between x and y.") {
janet_fixarity(argc, 2);
double x = janet_getnumber(argv, 0);
double y = janet_getnumber(argv, 1);
return janet_wrap_number(janet_gcd(x, y));
}
JANET_CORE_FN(janet_cfun_lcm, "(math/lcm x y)",
"Returns the least common multiple of x and y.") {
janet_fixarity(argc, 2);
double x = janet_getnumber(argv, 0);
double y = janet_getnumber(argv, 1);
return janet_wrap_number(janet_lcm(x, y));
}
/* Module entry point */
void janet_lib_math(JanetTable *env) {
janet_core_cfuns(env, NULL, math_cfuns);
JanetRegExt math_cfuns[] = {
JANET_CORE_REG("not", janet_not),
JANET_CORE_REG("math/random", janet_rand),
JANET_CORE_REG("math/seedrandom", janet_srand),
JANET_CORE_REG("math/cos", janet_cos),
JANET_CORE_REG("math/sin", janet_sin),
JANET_CORE_REG("math/tan", janet_tan),
JANET_CORE_REG("math/acos", janet_acos),
JANET_CORE_REG("math/asin", janet_asin),
JANET_CORE_REG("math/atan", janet_atan),
JANET_CORE_REG("math/exp", janet_exp),
JANET_CORE_REG("math/log", janet_log),
JANET_CORE_REG("math/log10", janet_log10),
JANET_CORE_REG("math/log2", janet_log2),
JANET_CORE_REG("math/sqrt", janet_sqrt),
JANET_CORE_REG("math/cbrt", janet_cbrt),
JANET_CORE_REG("math/floor", janet_floor),
JANET_CORE_REG("math/ceil", janet_ceil),
JANET_CORE_REG("math/pow", janet_pow),
JANET_CORE_REG("math/abs", janet_fabs),
JANET_CORE_REG("math/sinh", janet_sinh),
JANET_CORE_REG("math/cosh", janet_cosh),
JANET_CORE_REG("math/tanh", janet_tanh),
JANET_CORE_REG("math/atanh", janet_atanh),
JANET_CORE_REG("math/asinh", janet_asinh),
JANET_CORE_REG("math/acosh", janet_acosh),
JANET_CORE_REG("math/atan2", janet_atan2),
JANET_CORE_REG("math/rng", cfun_rng_make),
JANET_CORE_REG("math/rng-uniform", cfun_rng_uniform),
JANET_CORE_REG("math/rng-int", cfun_rng_int),
JANET_CORE_REG("math/rng-buffer", cfun_rng_buffer),
JANET_CORE_REG("math/hypot", janet_hypot),
JANET_CORE_REG("math/exp2", janet_exp2),
JANET_CORE_REG("math/log1p", janet_log1p),
JANET_CORE_REG("math/gamma", janet_gamma),
JANET_CORE_REG("math/erfc", janet_erfc),
JANET_CORE_REG("math/erf", janet_erf),
JANET_CORE_REG("math/expm1", janet_expm1),
JANET_CORE_REG("math/trunc", janet_trunc),
JANET_CORE_REG("math/round", janet_round),
JANET_CORE_REG("math/next", janet_nextafter),
JANET_CORE_REG("math/gcd", janet_cfun_gcd),
JANET_CORE_REG("math/lcm", janet_cfun_lcm),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, math_cfuns);
janet_register_abstract_type(&janet_rng_type);
#ifdef JANET_BOOTSTRAP
janet_def(env, "math/pi", janet_wrap_number(3.1415926535897931),
JDOC("The value pi."));
janet_def(env, "math/e", janet_wrap_number(2.7182818284590451),
JDOC("The base of the natural log."));
janet_def(env, "math/inf", janet_wrap_number(INFINITY),
JDOC("The number representing positive infinity"));
janet_def(env, "math/-inf", janet_wrap_number(-INFINITY),
JDOC("The number representing negative infinity"));
janet_def(env, "math/int32-min", janet_wrap_number(INT32_MIN),
JDOC("The minimum contiguous integer representable by a 32 bit signed integer"));
janet_def(env, "math/int32-max", janet_wrap_number(INT32_MAX),
JDOC("The maximum contiguous integer represtenable by a 32 bit signed integer"));
janet_def(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE),
JDOC("The minimum contiguous integer representable by a double (2^53)"));
janet_def(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE),
JDOC("The maximum contiguous integer represtenable by a double (-(2^53))"));
JANET_CORE_DEF(env, "math/pi", janet_wrap_number(3.1415926535897931),
"The value pi.");
JANET_CORE_DEF(env, "math/e", janet_wrap_number(2.7182818284590451),
"The base of the natural log.");
JANET_CORE_DEF(env, "math/inf", janet_wrap_number(INFINITY),
"The number representing positive infinity");
JANET_CORE_DEF(env, "math/-inf", janet_wrap_number(-INFINITY),
"The number representing negative infinity");
JANET_CORE_DEF(env, "math/int32-min", janet_wrap_number(INT32_MIN),
"The minimum contiguous integer representable by a 32 bit signed integer");
JANET_CORE_DEF(env, "math/int32-max", janet_wrap_number(INT32_MAX),
"The maximum contiguous integer represtenable by a 32 bit signed integer");
JANET_CORE_DEF(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE),
"The minimum contiguous integer representable by a double (2^53)");
JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE),
"The maximum contiguous integer represtenable by a double (-(2^53))");
#ifdef NAN
janet_def(env, "math/nan", janet_wrap_number(NAN),
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)");
#else
janet_def(env, "math/nan", janet_wrap_number(0.0 / 0.0),
JANET_CORE_DEF(env, "math/nan", janet_wrap_number(0.0 / 0.0), "Not a number (IEEE-754 NaN)");
#endif
JDOC("Not a number (IEEE-754 NaN)"));
#endif
}

View File

@ -38,6 +38,7 @@
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "Advapi32.lib")
#else
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
@ -73,6 +74,15 @@ const JanetAbstractType janet_address_type = {
#endif
#endif
/* maximum number of bytes in a socket address host (post name resolution) */
#ifdef JANET_WINDOWS
#define SA_ADDRSTRLEN (INET6_ADDRSTRLEN + 1)
typedef unsigned short in_port_t;
#else
#define JANET_SA_MAX(a, b) (((a) > (b))? (a) : (b))
#define SA_ADDRSTRLEN JANET_SA_MAX(INET6_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1)
#endif
static JanetStream *make_stream(JSock handle, uint32_t flags);
/* We pass this flag to all send calls to prevent sigpipe */
@ -259,7 +269,8 @@ static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) {
}
/* Needs argc >= offset + 2 */
/* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1, otherwise 0 */
/* For unix paths, just rertuns a single sockaddr and sets *is_unix to 1,
* otherwise 0. Also, ignores is_bind when is a unix socket. */
static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int socktype, int passive, int *is_unix) {
/* Unix socket support - not yet supported on windows. */
#ifndef JANET_WINDOWS
@ -285,12 +296,12 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
}
#endif
/* Get host and port */
const char *host = janet_getcstring(argv, offset);
const char *port;
char *host = (char *)janet_getcstring(argv, offset);
char *port = NULL;
if (janet_checkint(argv[offset + 1])) {
port = (const char *)janet_to_string(argv[offset + 1]);
port = (char *)janet_to_string(argv[offset + 1]);
} else {
port = janet_optcstring(argv, offset + 2, offset + 1, NULL);
port = (char *)janet_optcstring(argv, offset + 2, offset + 1, NULL);
}
/* getaddrinfo */
struct addrinfo *ai = NULL;
@ -311,7 +322,14 @@ static struct addrinfo *janet_get_addrinfo(Janet *argv, int32_t offset, int sock
* C Funs
*/
static Janet cfun_net_sockaddr(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_net_sockaddr,
"(net/address host port &opt type multi)",
"Look up the connection information for a given hostname, port, and connection type. Returns "
"a handle that can be used to send datagrams over network without establishing a connection. "
"On Posix platforms, you can use :unix for host to connect to a unix domain socket, where the name is "
"given in the port argument. On Linux, abstract "
"unix domain sockets are specified with a leading '@' character in port. If `multi` is truthy, will "
"return all address that match in an array instead of just the first.") {
janet_arity(argc, 2, 4);
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
@ -350,13 +368,49 @@ static Janet cfun_net_sockaddr(int32_t argc, Janet *argv) {
}
}
static Janet cfun_net_connect(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 3);
JANET_CORE_FN(cfun_net_connect,
"(net/connect host port &opt type bindhost bindport)",
"Open a connection to communicate with a server. Returns a duplex stream "
"that can be used to communicate with the server. Type is an optional keyword "
"to specify a connection type, either :stream or :datagram. The default is :stream. "
"Bindhost is an optional string to select from what address to make the outgoing "
"connection, with the default being the same as using the OS's preferred address. ") {
janet_arity(argc, 2, 5);
/* Check arguments */
int socktype = janet_get_sockettype(argv, argc, 2);
int is_unix = 0;
char *bindhost = (char *) janet_optcstring(argv, argc, 3, NULL);
char *bindport = NULL;
if (janet_checkint(argv[4])) {
bindport = (char *)janet_to_string(argv[4]);
} else {
bindport = (char *)janet_optcstring(argv, argc, 4, NULL);
}
/* Where we're connecting to */
struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 0, &is_unix);
/* Check if we're binding address */
struct addrinfo *binding = NULL;
if (bindhost != NULL) {
if (is_unix) {
freeaddrinfo(ai);
janet_panic("bindhost not supported for unix domain sockets");
}
/* getaddrinfo */
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = socktype;
hints.ai_flags = 0;
int status = getaddrinfo(bindhost, bindport, &hints, &binding);
if (status) {
freeaddrinfo(ai);
janet_panicf("could not get address info for bindhost: %s", gai_strerror(status));
}
}
/* Create socket */
JSock sock = JSOCKDEFAULT;
void *addr = NULL;
@ -365,7 +419,9 @@ static Janet cfun_net_connect(int32_t argc, Janet *argv) {
if (is_unix) {
sock = socket(AF_UNIX, socktype | JSOCKFLAGS, 0);
if (!JSOCKVALID(sock)) {
janet_panicf("could not create socket: %V", janet_ev_lasterr());
Janet v = janet_ev_lasterr();
janet_free(ai);
janet_panicf("could not create socket: %V", v);
}
addr = (void *) ai;
addrlen = sizeof(struct sockaddr_un);
@ -386,17 +442,42 @@ static Janet cfun_net_connect(int32_t argc, Janet *argv) {
}
}
if (NULL == addr) {
Janet v = janet_ev_lasterr();
if (binding) freeaddrinfo(binding);
freeaddrinfo(ai);
janet_panicf("could not create socket: %V", janet_ev_lasterr());
janet_panicf("could not create socket: %V", v);
}
}
/* Bind to bindhost and bindport if given */
if (binding) {
struct addrinfo *rp = NULL;
int did_bind = 0;
for (rp = ai; rp != NULL; rp = rp->ai_next) {
if (bind(sock, rp->ai_addr, (int) rp->ai_addrlen) == 0) {
did_bind = 1;
break;
}
}
if (!did_bind) {
Janet v = janet_ev_lasterr();
freeaddrinfo(binding);
freeaddrinfo(ai);
JSOCKCLOSE(sock);
janet_panicf("could not bind outgoing address: %V", v);
} else {
freeaddrinfo(binding);
}
}
/* Connect to socket */
#ifdef JANET_WINDOWS
int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL);
Janet lasterr = janet_ev_lasterr();
freeaddrinfo(ai);
#else
int status = connect(sock, addr, addrlen);
Janet lasterr = janet_ev_lasterr();
if (is_unix) {
janet_free(ai);
} else {
@ -406,7 +487,7 @@ static Janet cfun_net_connect(int32_t argc, Janet *argv) {
if (status == -1) {
JSOCKCLOSE(sock);
janet_panicf("could not connect to socket: %V", janet_ev_lasterr());
janet_panicf("could not connect socket: %V", lasterr);
}
/* Set up the socket for non-blocking IO after connect - TODO - non-blocking connect? */
@ -442,7 +523,14 @@ static const char *serverify_socket(JSock sfd) {
#define JANET_SHUTDOWN_W SHUT_WR
#endif
static Janet cfun_net_shutdown(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_net_shutdown,
"(net/shutdown stream &opt mode)",
"Stop communication on this socket in a graceful manner, either in both directions or just "
"reading/writing from the stream. The `mode` parameter controls which communication to stop on the socket. "
"\n\n* `:wr` is the default and prevents both reading new data from the socket and writing new data to the socket.\n"
"* `:r` disables reading new data from the socket.\n"
"* `:w` disable writing data to the socket.\n\n"
"Returns the original socket.") {
janet_arity(argc, 1, 2);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_SOCKET);
@ -473,7 +561,13 @@ static Janet cfun_net_shutdown(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_net_listen(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_net_listen,
"(net/listen host port &opt type)",
"Creates a server. Returns a new stream that is neither readable nor "
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
"The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address.") {
janet_arity(argc, 2, 3);
/* Get host, port, and handler*/
@ -547,7 +641,98 @@ static Janet cfun_net_listen(int32_t argc, Janet *argv) {
}
}
static Janet cfun_stream_accept_loop(int32_t argc, Janet *argv) {
/* Types of socket's we need to deal with - relevant type puns below.
struct sockaddr *sa; // Common base structure
struct sockaddr_storage *ss; // Size of largest socket address type
struct sockaddr_in *sin; // IPv4 address + port
struct sockaddr_in6 *sin6; // IPv6 address + port
struct sockaddr_un *sun; // Unix Domain Socket Address
*/
/* Turn a socket address into a host, port pair (port is optional).
* For unix domain sockets, returned tuple will have only a single element, the path string. */
static Janet janet_so_getname(const void *sa_any) {
const struct sockaddr *sa = sa_any;
char buffer[SA_ADDRSTRLEN];
switch (sa->sa_family) {
default:
janet_panic("unknown address family");
case AF_INET: {
const struct sockaddr_in *sai = sa_any;
if (!inet_ntop(AF_INET, &(sai->sin_addr), buffer, sizeof(buffer))) {
janet_panic("unable to decode ipv4 host address");
}
Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai->sin_port))};
return janet_wrap_tuple(janet_tuple_n(pair, sai->sin_port ? 2 : 1));
}
case AF_INET6: {
const struct sockaddr_in6 *sai6 = sa_any;
if (!inet_ntop(AF_INET6, &(sai6->sin6_addr), buffer, sizeof(buffer))) {
janet_panic("unable to decode ipv4 host address");
}
Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai6->sin6_port))};
return janet_wrap_tuple(janet_tuple_n(pair, sai6->sin6_port ? 2 : 1));
}
#ifndef JANET_WINDOWS
case AF_UNIX: {
const struct sockaddr_un *sun = sa_any;
Janet pathname;
if (sun->sun_path[0] == '\0') {
memcpy(buffer, sun->sun_path, sizeof(sun->sun_path));
buffer[0] = '@';
pathname = janet_cstringv(buffer);
} else {
pathname = janet_cstringv(sun->sun_path);
}
return janet_wrap_tuple(janet_tuple_n(&pathname, 1));
}
#endif
}
}
JANET_CORE_FN(cfun_net_getsockname,
"(net/localname stream)",
"Gets the local address and port in a tuple in that order.") {
janet_fixarity(argc, 1);
JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type);
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
memset(&ss, 0, slen);
if (getsockname((JSock)js->handle, (struct sockaddr *) &ss, &slen)) {
janet_panicf("Failed to get localname on %v: %V", argv[0], janet_ev_lasterr());
}
janet_assert(slen <= sizeof(ss), "socket address truncated");
return janet_so_getname(&ss);
}
JANET_CORE_FN(cfun_net_getpeername,
"(net/peername stream)",
"Gets the remote peer's address and port in a tuple in that order.") {
janet_fixarity(argc, 1);
JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type);
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
memset(&ss, 0, slen);
if (getpeername((JSock)js->handle, (struct sockaddr *)&ss, &slen)) {
janet_panicf("Failed to get peername on %v: %V", argv[0], janet_ev_lasterr());
}
janet_assert(slen <= sizeof(ss), "socket address truncated");
return janet_so_getname(&ss);
}
JANET_CORE_FN(cfun_net_address_unpack,
"(net/address-unpack address)",
"Given an address returned by net/adress, return a host, port pair. Unix domain sockets "
"will have only the path in the returned tuple.") {
janet_fixarity(argc, 1);
struct sockaddr *sa = janet_getabstract(argv, 0, &janet_address_type);
return janet_so_getname(sa);
}
JANET_CORE_FN(cfun_stream_accept_loop,
"(net/accept-loop stream handler)",
"Shorthand for running a server stream that will continuously accept new connections. "
"Blocks the current fiber until the stream is closed, and will return the stream.") {
janet_fixarity(argc, 2);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_ACCEPTABLE | JANET_STREAM_SOCKET);
@ -555,7 +740,11 @@ static Janet cfun_stream_accept_loop(int32_t argc, Janet *argv) {
janet_sched_accept(stream, fun);
}
static Janet cfun_stream_accept(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_accept,
"(net/accept stream &opt timeout)",
"Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a new duplex stream which represents a connection to the client.") {
janet_arity(argc, 1, 2);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_ACCEPTABLE | JANET_STREAM_SOCKET);
@ -564,7 +753,13 @@ static Janet cfun_stream_accept(int32_t argc, Janet *argv) {
janet_sched_accept(stream, NULL);
}
static Janet cfun_stream_read(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_read,
"(net/read stream nbytes &opt buf timeout)",
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
"`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
@ -581,7 +776,10 @@ static Janet cfun_stream_read(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_stream_chunk(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_chunk,
"(net/chunk stream nbytes &opt buf timeout)",
"Same a net/read, but will wait for all n bytes to arrive rather than return early. "
"Takes an optional timeout in seconds, after which will return nil.") {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
@ -593,7 +791,10 @@ static Janet cfun_stream_chunk(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_stream_recv_from(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_recv_from,
"(net/recv-from stream nbytes buf &opt timoeut)",
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
"packet came from. Takes an optional timeout in seconds, after which will return nil.") {
janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET);
@ -605,7 +806,11 @@ static Janet cfun_stream_recv_from(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_stream_write(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_write,
"(net/write stream data &opt timeout)",
"Write data to a stream, suspending the current fiber until the write "
"completes. Takes an optional timeout in seconds, after which will return nil. "
"Returns nil, or raises an error if the write failed.") {
janet_arity(argc, 2, 3);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_WRITABLE | JANET_STREAM_SOCKET);
@ -621,7 +826,11 @@ static Janet cfun_stream_write(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_stream_send_to(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_send_to,
"(net/send-to stream dest data &opt timeout)",
"Writes a datagram to a server stream. dest is a the destination address of the packet. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns stream.") {
janet_arity(argc, 3, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET);
@ -638,7 +847,10 @@ static Janet cfun_stream_send_to(int32_t argc, Janet *argv) {
janet_await();
}
static Janet cfun_stream_flush(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_stream_flush,
"(net/flush stream)",
"Make sure that a stream is not buffering any data. This temporarily disables Nagle's algorithm. "
"Use this to make sure data is sent without delay. Returns stream.") {
janet_fixarity(argc, 1);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_WRITABLE | JANET_STREAM_SOCKET);
@ -660,7 +872,6 @@ static const JanetMethod net_stream_methods[] = {
{"accept-loop", cfun_stream_accept_loop},
{"send-to", cfun_stream_send_to},
{"recv-from", cfun_stream_recv_from},
{"recv-from", cfun_stream_recv_from},
{"evread", janet_cfun_stream_read},
{"evchunk", janet_cfun_stream_chunk},
{"evwrite", janet_cfun_stream_write},
@ -672,101 +883,27 @@ static JanetStream *make_stream(JSock handle, uint32_t flags) {
return janet_stream((JanetHandle) handle, flags | JANET_STREAM_SOCKET, net_stream_methods);
}
static const JanetReg net_cfuns[] = {
{
"net/address", cfun_net_sockaddr,
JDOC("(net/address host port &opt type)\n\n"
"Look up the connection information for a given hostname, port, and connection type. Returns "
"a handle that can be used to send datagrams over network without establishing a connection. "
"On Posix platforms, you can use :unix for host to connect to a unix domain socket, where the name is "
"given in the port argument. On Linux, abstract "
"unix domain sockets are specified with a leading '@' character in port.")
},
{
"net/listen", cfun_net_listen,
JDOC("(net/listen host port &opt type)\n\n"
"Creates a server. Returns a new stream that is neither readable nor "
"writeable. Use net/accept or net/accept-loop be to handle connections and start the server. "
"The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address.")
},
{
"net/accept", cfun_stream_accept,
JDOC("(net/accept stream &opt timeout)\n\n"
"Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a new duplex stream which represents a connection to the client.")
},
{
"net/accept-loop", cfun_stream_accept_loop,
JDOC("(net/accept-loop stream handler)\n\n"
"Shorthand for running a server stream that will continuously accept new connections. "
"Blocks the current fiber until the stream is closed, and will return the stream.")
},
{
"net/read", cfun_stream_read,
JDOC("(net/read stream nbytes &opt buf timeout)\n\n"
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
"`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.")
},
{
"net/chunk", cfun_stream_chunk,
JDOC("(net/chunk stream nbytes &opt buf timeout)\n\n"
"Same a net/read, but will wait for all n bytes to arrive rather than return early. "
"Takes an optional timeout in seconds, after which will return nil.")
},
{
"net/write", cfun_stream_write,
JDOC("(net/write stream data &opt timeout)\n\n"
"Write data to a stream, suspending the current fiber until the write "
"completes. Takes an optional timeout in seconds, after which will return nil. "
"Returns nil, or raises an error if the write failed.")
},
{
"net/send-to", cfun_stream_send_to,
JDOC("(net/send-to stream dest data &opt timeout)\n\n"
"Writes a datagram to a server stream. dest is a the destination address of the packet. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns stream.")
},
{
"net/recv-from", cfun_stream_recv_from,
JDOC("(net/recv-from stream nbytes buf &opt timoeut)\n\n"
"Receives data from a server stream and puts it into a buffer. Returns the socket-address the "
"packet came from. Takes an optional timeout in seconds, after which will return nil.")
},
{
"net/flush", cfun_stream_flush,
JDOC("(net/flush stream)\n\n"
"Make sure that a stream is not buffering any data. This temporarily disables Nagle's algorithm. "
"Use this to make sure data is sent without delay. Returns stream.")
},
{
"net/connect", cfun_net_connect,
JDOC("(net/connect host port &opt type)\n\n"
"Open a connection to communicate with a server. Returns a duplex stream "
"that can be used to communicate with the server. Type is an optional keyword "
"to specify a connection type, either :stream or :datagram. The default is :stream. ")
},
{
"net/shutdown", cfun_net_shutdown,
JDOC("(net/shutdown stream &opt mode)\n\n"
"Stop communication on this socket in a graceful manner, either in both directions or just "
"reading/writing from the stream. The `mode` parameter controls which communication to stop on the socket. "
"\n\n* `:wr` is the default and prevents both reading new data from the socket and writing new data to the socket.\n"
"* `:r` disables reading new data from the socket.\n"
"* `:w` disable writing data to the socket.\n\n"
"Returns the original socket.")
},
{NULL, NULL, NULL}
};
void janet_lib_net(JanetTable *env) {
janet_core_cfuns(env, NULL, net_cfuns);
JanetRegExt net_cfuns[] = {
JANET_CORE_REG("net/address", cfun_net_sockaddr),
JANET_CORE_REG("net/listen", cfun_net_listen),
JANET_CORE_REG("net/accept", cfun_stream_accept),
JANET_CORE_REG("net/accept-loop", cfun_stream_accept_loop),
JANET_CORE_REG("net/read", cfun_stream_read),
JANET_CORE_REG("net/chunk", cfun_stream_chunk),
JANET_CORE_REG("net/write", cfun_stream_write),
JANET_CORE_REG("net/send-to", cfun_stream_send_to),
JANET_CORE_REG("net/recv-from", cfun_stream_recv_from),
JANET_CORE_REG("net/flush", cfun_stream_flush),
JANET_CORE_REG("net/connect", cfun_net_connect),
JANET_CORE_REG("net/shutdown", cfun_net_shutdown),
JANET_CORE_REG("net/peername", cfun_net_getpeername),
JANET_CORE_REG("net/localname", cfun_net_getsockname),
JANET_CORE_REG("net/address-unpack", cfun_net_address_unpack),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, net_cfuns);
}
void janet_net_init(void) {

View File

@ -84,7 +84,6 @@ time_t timegm(struct tm *tm);
* setenv/getenv are not thread safe. */
#ifdef JANET_THREADS
# ifdef JANET_WINDOWS
static int env_lock_initialized = 0;
static CRITICAL_SECTION env_lock;
static void janet_lock_environ(void) {
EnterCriticalSection(&env_lock);
@ -117,7 +116,18 @@ static void janet_unlock_environ(void) {
#define janet_stringify1(x) #x
#define janet_stringify(x) janet_stringify1(x)
static Janet os_which(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_which,
"(os/which)",
"Check the current operating system. Returns one of:\n\n"
"* :windows\n\n"
"* :macos\n\n"
"* :web - Web assembly (emscripten)\n\n"
"* :linux\n\n"
"* :freebsd\n\n"
"* :openbsd\n\n"
"* :netbsd\n\n"
"* :posix - A POSIX compatible system (default)\n\n"
"May also return a custom keyword specified at build time.") {
janet_fixarity(argc, 0);
(void) argv;
#if defined(JANET_OS_NAME)
@ -144,7 +154,16 @@ static Janet os_which(int32_t argc, Janet *argv) {
}
/* Detect the ISA we are compiled for */
static Janet os_arch(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_arch,
"(os/arch)",
"Check the ISA that janet was compiled for. Returns one of:\n\n"
"* :x86\n\n"
"* :x64\n\n"
"* :arm\n\n"
"* :aarch64\n\n"
"* :sparc\n\n"
"* :wasm\n\n"
"* :unknown\n") {
janet_fixarity(argc, 0);
(void) argv;
/* Check 64-bit vs 32-bit */
@ -172,7 +191,10 @@ static Janet os_arch(int32_t argc, Janet *argv) {
#undef janet_stringify1
#undef janet_stringify
static Janet os_exit(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_exit,
"(os/exit &opt x)",
"Exit from janet with an exit code equal to x. If x is not an integer, "
"the exit with status equal the hash of x.") {
janet_arity(argc, 0, 1);
int status;
if (argc == 0) {
@ -353,6 +375,7 @@ static const JanetAbstractType ProcAT;
#define JANET_PROC_OWNS_STDIN 16
#define JANET_PROC_OWNS_STDOUT 32
#define JANET_PROC_OWNS_STDERR 64
#define JANET_PROC_ALLOW_ZOMBIE 128
typedef struct {
int flags;
#ifdef JANET_WINDOWS
@ -410,6 +433,7 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) {
/* Callback that is called in main thread when subroutine completes. */
static void janet_proc_wait_cb(JanetEVGenericMessage args) {
janet_ev_dec_refcount();
int status = args.argi;
JanetProc *proc = (JanetProc *) args.argp;
if (NULL != proc) {
@ -434,11 +458,14 @@ static int janet_proc_gc(void *p, size_t s) {
JanetProc *proc = (JanetProc *) p;
#ifdef JANET_WINDOWS
if (!(proc->flags & JANET_PROC_CLOSED)) {
if (!(proc->flags & JANET_PROC_ALLOW_ZOMBIE)) {
TerminateProcess(proc->pHandle, 1);
}
CloseHandle(proc->pHandle);
CloseHandle(proc->tHandle);
}
#else
if (!(proc->flags & JANET_PROC_WAITED)) {
if (!(proc->flags & (JANET_PROC_WAITED | JANET_PROC_ALLOW_ZOMBIE))) {
/* Kill and wait to prevent zombies */
kill(proc->pid, SIGKILL);
int status;
@ -497,7 +524,9 @@ os_proc_wait_impl(JanetProc *proc) {
#endif
}
static Janet os_proc_wait(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_proc_wait,
"(os/proc-wait proc)",
"Block until the subprocess completes. Returns the subprocess return code.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
@ -508,7 +537,11 @@ static Janet os_proc_wait(int32_t argc, Janet *argv) {
#endif
}
static Janet os_proc_kill(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_proc_kill,
"(os/proc-kill proc &opt wait)",
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process "
"handle on windows. If wait is truthy, will wait for the process to finsih and "
"returns the exit code. Otherwise, returns proc.") {
janet_arity(argc, 1, 2);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
if (proc->flags & JANET_PROC_WAITED) {
@ -519,6 +552,7 @@ static Janet os_proc_kill(int32_t argc, Janet *argv) {
janet_panicf("cannot close process handle that is already closed");
}
proc->flags |= JANET_PROC_CLOSED;
TerminateProcess(proc->pHandle, 1);
CloseHandle(proc->pHandle);
CloseHandle(proc->tHandle);
#else
@ -540,7 +574,10 @@ static Janet os_proc_kill(int32_t argc, Janet *argv) {
}
}
static Janet os_proc_close(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_proc_close,
"(os/proc-close proc)",
"Wait on a process if it has not been waited on, and close pipes created by `os/spawn` "
"if they have not been closed. Returns nil.") {
janet_fixarity(argc, 1);
JanetProc *proc = janet_getabstract(argv, 0, &ProcAT);
#ifdef JANET_EV
@ -757,7 +794,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
/* Get flags */
uint64_t flags = 0;
if (argc > 1) {
flags = janet_getflags(argv, 1, "epx");
flags = janet_getflags(argv, 1, "epxd");
}
/* Get environment */
@ -775,7 +812,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = 0;
int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
/* Get optional redirections */
if (argc > 2) {
@ -991,11 +1028,32 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) {
}
}
static Janet os_execute(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_execute,
"(os/execute args &opt flags env)",
"Execute a program on the system and pass it string arguments. `flags` "
"is a keyword that modifies how the program will execute.\n"
"* :e - enables passing an environment to the program. Without :e, the "
"current environment is inherited.\n"
"* :p - allows searching the current PATH for the binary to execute. "
"Without this flag, binaries must use absolute paths.\n"
"* :x - raise error if exit code is non-zero.\n"
"* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n"
"`env` is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
"These arguments should be core/file values. "
"One can also pass in the :pipe keyword "
"for these arguments to create files that will read (for :err and :out) or write (for :in) "
"to the file descriptor of the subprocess. This is only useful in `os/spawn`, which takes "
"the same parameters as `os/execute`, but will return an object that contains references to these "
"files via (return-value :in), (return-value :out), and (return-value :err). "
"Returns the exit status of the program.") {
return os_execute_impl(argc, argv, 0);
}
static Janet os_spawn(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_spawn,
"(os/spawn args &opt flags env)",
"Execute a program on the system and return a handle to the process. Otherwise, the "
"same arguments as os/execute. Does not wait for the process.") {
return os_execute_impl(argc, argv, 1);
}
@ -1014,7 +1072,9 @@ static JanetEVGenericMessage os_shell_subr(JanetEVGenericMessage args) {
}
#endif
static Janet os_shell(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_shell,
"(os/shell str)",
"Pass a command string str directly to the system shell.") {
janet_arity(argc, 0, 1);
const char *cmd = argc
? janet_getcstring(argv, 0)
@ -1031,7 +1091,9 @@ static Janet os_shell(int32_t argc, Janet *argv) {
#endif /* JANET_NO_PROCESSES */
static Janet os_environ(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_environ,
"(os/environ)",
"Get a copy of the os environment table.") {
(void) argv;
janet_fixarity(argc, 0);
int32_t nenv = 0;
@ -1060,7 +1122,9 @@ static Janet os_environ(int32_t argc, Janet *argv) {
return janet_wrap_table(t);
}
static Janet os_getenv(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_getenv,
"(os/getenv variable &opt dflt)",
"Get the string value of an environment variable.") {
janet_arity(argc, 1, 2);
const char *cstr = janet_getcstring(argv, 0);
const char *res = getenv(cstr);
@ -1074,7 +1138,9 @@ static Janet os_getenv(int32_t argc, Janet *argv) {
return ret;
}
static Janet os_setenv(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_setenv,
"(os/setenv variable value)",
"Set an environment variable.") {
#ifdef JANET_WINDOWS
#define SETENV(K,V) _putenv_s(K, V)
#define UNSETENV(K) _putenv_s(K, "")
@ -1095,14 +1161,20 @@ static Janet os_setenv(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_time(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_time,
"(os/time)",
"Get the current time expressed as the number of whole seconds since "
"January 1, 1970, the Unix epoch. Returns a real number.") {
janet_fixarity(argc, 0);
(void) argv;
double dtime = (double)(time(NULL));
return janet_wrap_number(dtime);
}
static Janet os_clock(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_clock,
"(os/clock)",
"Return the number of whole + fractional seconds since some fixed point in time. The clock "
"is guaranteed to be non decreasing in real time.") {
janet_fixarity(argc, 0);
(void) argv;
struct timespec tv;
@ -1111,7 +1183,10 @@ static Janet os_clock(int32_t argc, Janet *argv) {
return janet_wrap_number(dtime);
}
static Janet os_sleep(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_sleep,
"(os/sleep n)",
"Suspend the program for n seconds. 'nsec' can be a real number. Returns "
"nil.") {
janet_fixarity(argc, 1);
double delay = janet_getnumber(argv, 0);
if (delay < 0) janet_panic("invalid argument to sleep");
@ -1129,7 +1204,9 @@ static Janet os_sleep(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_cwd(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_cwd,
"(os/cwd)",
"Returns the current working directory.") {
janet_fixarity(argc, 0);
(void) argv;
char buf[FILENAME_MAX];
@ -1143,7 +1220,9 @@ static Janet os_cwd(int32_t argc, Janet *argv) {
return janet_cstringv(ptr);
}
static Janet os_cryptorand(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_cryptorand,
"(os/cryptorand n &opt buf)",
"Get or append n bytes of good quality random data provided by the OS. Returns a new buffer or buf.") {
JanetBuffer *buffer;
janet_arity(argc, 1, 2);
int32_t offset;
@ -1165,7 +1244,21 @@ static Janet os_cryptorand(int32_t argc, Janet *argv) {
return janet_wrap_buffer(buffer);
}
static Janet os_date(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_date,
"(os/date &opt time local)",
"Returns the given time as a date struct, or the current time if `time` is not given. "
"Returns a struct with following key values. Note that all numbers are 0-indexed. "
"Date is given in UTC unless `local` is truthy, in which case the date is formatted for "
"the local timezone.\n\n"
"* :seconds - number of seconds [0-61]\n\n"
"* :minutes - number of minutes [0-59]\n\n"
"* :hours - number of hours [0-23]\n\n"
"* :month-day - day of month [0-30]\n\n"
"* :month - month of year [0, 11]\n\n"
"* :year - years since year 0 (e.g. 2019)\n\n"
"* :week-day - day of the week [0-6]\n\n"
"* :year-day - day of the year [0-365]\n\n"
"* :dst - if Day Light Savings is in effect") {
janet_arity(argc, 0, 2);
(void) argv;
time_t t;
@ -1263,7 +1356,14 @@ static timeint_t entry_getint(Janet env_entry, char *field) {
return (timeint_t)janet_unwrap_number(i);
}
static Janet os_mktime(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_mktime,
"(os/mktime date-struct &opt local)",
"Get the broken down date-struct time expressed as the number "
" of seconds since January 1, 1970, the Unix epoch. "
"Returns a real number. "
"Date is given in UTC unless local is truthy, in which case the "
"date is computed for the local timezone.\n\n"
"Inverse function to os/date.") {
janet_arity(argc, 1, 2);
time_t t;
struct tm t_info;
@ -1309,7 +1409,12 @@ static Janet os_mktime(int32_t argc, Janet *argv) {
#define j_symlink symlink
#endif
static Janet os_link(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_link,
"(os/link oldpath newpath &opt symlink)",
"Create a link at newpath that points to oldpath and returns nil. "
"Iff symlink is truthy, creates a symlink. "
"Iff symlink is falsey or not provided, "
"creates a hard link. Does not work on Windows.") {
janet_arity(argc, 2, 3);
#ifdef JANET_WINDOWS
(void) argc;
@ -1325,7 +1430,9 @@ static Janet os_link(int32_t argc, Janet *argv) {
#endif
}
static Janet os_symlink(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_symlink,
"(os/symlink oldpath newpath)",
"Create a symlink from oldpath to newpath, returning nil. Same as (os/link oldpath newpath true).") {
janet_fixarity(argc, 2);
#ifdef JANET_WINDOWS
(void) argc;
@ -1343,7 +1450,11 @@ static Janet os_symlink(int32_t argc, Janet *argv) {
#undef j_symlink
static Janet os_mkdir(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_mkdir,
"(os/mkdir path)",
"Create a new directory. The path will be relative to the current directory if relative, otherwise "
"it will be an absolute path. Returns true if the directory was created, false if the directory already exists, and "
"errors otherwise.") {
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@ -1356,7 +1467,9 @@ static Janet os_mkdir(int32_t argc, Janet *argv) {
janet_panicf("%s: %s", strerror(errno), path);
}
static Janet os_rmdir(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_rmdir,
"(os/rmdir path)",
"Delete a directory. The directory must be empty to succeed.") {
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@ -1368,7 +1481,9 @@ static Janet os_rmdir(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_cd(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_cd,
"(os/cd path)",
"Change current directory to path. Returns nil on success, errors on failure.") {
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@ -1380,7 +1495,10 @@ static Janet os_cd(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_touch(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_touch,
"(os/touch path &opt actime modtime)",
"Update the access time and modification times for a file. By default, sets "
"times to the current time.") {
janet_arity(argc, 1, 3);
const char *path = janet_getcstring(argv, 0);
struct utimbuf timebuf, *bufp;
@ -1400,7 +1518,9 @@ static Janet os_touch(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_remove(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_remove,
"(os/rm path)",
"Delete a file. Returns nil.") {
janet_fixarity(argc, 1);
const char *path = janet_getcstring(argv, 0);
int status = remove(path);
@ -1409,7 +1529,9 @@ static Janet os_remove(int32_t argc, Janet *argv) {
}
#ifndef JANET_NO_SYMLINKS
static Janet os_readlink(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_readlink,
"(os/readlink path)",
"Read the contents of a symbolic link. Does not work on Windows.\n") {
janet_fixarity(argc, 1);
#ifdef JANET_WINDOWS
(void) argc;
@ -1674,15 +1796,39 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) {
}
}
static Janet os_stat(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_stat,
"(os/stat path &opt tab|key)",
"Gets information about a file or directory. Returns a table if the second argument is a keyword, returns "
" only that information from stat. If the file or directory does not exist, returns nil. The keys are:\n\n"
"* :dev - the device that the file is on\n\n"
"* :mode - the type of file, one of :file, :directory, :block, :character, :fifo, :socket, :link, or :other\n\n"
"* :int-permissions - A Unix permission integer like 8r744\n\n"
"* :permissions - A Unix permission string like \"rwxr--r--\"\n\n"
"* :uid - File uid\n\n"
"* :gid - File gid\n\n"
"* :nlink - number of links to file\n\n"
"* :rdev - Real device of file. 0 on windows.\n\n"
"* :size - size of file in bytes\n\n"
"* :blocks - number of blocks in file. 0 on windows\n\n"
"* :blocksize - size of blocks in file. 0 on windows\n\n"
"* :accessed - timestamp when file last accessed\n\n"
"* :changed - timestamp when file last changed (permissions changed)\n\n"
"* :modified - timestamp when file last modified (content changed)\n") {
return os_stat_or_lstat(0, argc, argv);
}
static Janet os_lstat(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_lstat,
"(os/lstat path &opt tab|key)",
"Like os/stat, but don't follow symlinks.\n") {
return os_stat_or_lstat(1, argc, argv);
}
static Janet os_chmod(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_chmod,
"(os/chmod path mode)",
"Change file permissions, where mode is a permission string as returned by "
"os/perm-string, or an integer as returned by os/perm-int. "
"When mode is an integer, it is interpreted as a Unix permission value, best specified in octal, like "
"8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil.") {
janet_fixarity(argc, 2);
const char *path = janet_getcstring(argv, 0);
#ifdef JANET_WINDOWS
@ -1695,7 +1841,9 @@ static Janet os_chmod(int32_t argc, Janet *argv) {
}
#ifndef JANET_NO_UMASK
static Janet os_umask(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_umask,
"(os/umask mask)",
"Set a new umask, returns the old umask.") {
janet_fixarity(argc, 1);
int mask = (int) os_getmode(argv, 0);
#ifdef JANET_WINDOWS
@ -1707,7 +1855,10 @@ static Janet os_umask(int32_t argc, Janet *argv) {
}
#endif
static Janet os_dir(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_dir,
"(os/dir dir &opt array)",
"Iterate over files and subdirectories in a directory. Returns an array of paths parts, "
"with only the file name or directory name and no prefix.") {
janet_arity(argc, 1, 2);
const char *dir = janet_getcstring(argv, 0);
JanetArray *paths = (argc == 2) ? janet_getarray(argv, 1) : janet_array(0);
@ -1742,7 +1893,9 @@ static Janet os_dir(int32_t argc, Janet *argv) {
return janet_wrap_array(paths);
}
static Janet os_rename(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_rename,
"(os/rename oldname newname)",
"Rename a file on disk to a new path. Returns nil.") {
janet_fixarity(argc, 2);
const char *src = janet_getcstring(argv, 0);
const char *dest = janet_getcstring(argv, 1);
@ -1753,7 +1906,10 @@ static Janet os_rename(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet os_realpath(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_realpath,
"(os/realpath path)",
"Get the absolute path for a given path, following ../, ./, and symlinks. "
"Returns an absolute path as a string. Will raise an error on Windows.") {
janet_fixarity(argc, 1);
const char *src = janet_getcstring(argv, 0);
#ifdef JANET_NO_REALPATH
@ -1771,12 +1927,19 @@ static Janet os_realpath(int32_t argc, Janet *argv) {
#endif
}
static Janet os_permission_string(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_permission_string,
"(os/perm-string int)",
"Convert a Unix octal permission value from a permission integer as returned by os/stat "
"to a human readable string, that follows the formatting "
"of unix tools like ls. Returns the string as a 9 character string of r, w, x and - characters. Does not "
"include the file/directory/symlink character as rendered by `ls`.") {
janet_fixarity(argc, 1);
return os_make_permstring(os_get_unix_mode(argv, 0));
}
static Janet os_permission_int(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_permission_int,
"(os/perm-int bytes)",
"Parse a 9 character permission string and return an integer that can be used by chmod.") {
janet_fixarity(argc, 1);
return janet_wrap_integer(os_get_unix_mode(argv, 0));
}
@ -1792,7 +1955,31 @@ static jmode_t os_optmode(int32_t argc, const Janet *argv, int32_t n, int32_t df
return janet_perm_from_unix(dflt);
}
static Janet os_open(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_open,
"(os/open path &opt flags mode)",
"Create a stream from a file, like the POSIX open system call. Returns a new stream. "
"mode should be a file mode as passed to os/chmod, but only if the create flag is given. "
"The default mode is 8r666. "
"Allowed flags are as follows:\n\n"
" * :r - open this file for reading\n"
" * :w - open this file for writing\n"
" * :c - create a new file (O_CREATE)\n"
" * :e - fail if the file exists (O_EXCL)\n"
" * :t - shorten an existing file to length 0 (O_TRUNC)\n\n"
"Posix only flags:\n\n"
" * :a - append to a file (O_APPEND)\n"
" * :x - O_SYNC\n"
" * :C - O_NOCTTY\n\n"
"Windows only flags:\n\n"
" * :R - share reads (FILE_SHARE_READ)\n"
" * :W - share writes (FILE_SHARE_WRITE)\n"
" * :D - share deletes (FILE_SHARE_DELETE)\n"
" * :H - FILE_ATTRIBUTE_HIDDEN\n"
" * :O - FILE_ATTRIBUTE_READONLY\n"
" * :F - FILE_ATTRIBUTE_OFFLINE\n"
" * :T - FILE_ATTRIBUTE_TEMPORARY\n"
" * :d - FILE_FLAG_DELETE_ON_CLOSE\n"
" * :b - FILE_FLAG_NO_BUFFERING\n") {
janet_arity(argc, 1, 3);
const char *path = janet_getcstring(argv, 0);
const uint8_t *opt_flags = janet_optkeyword(argv, argc, 1, (const uint8_t *) "r");
@ -1934,7 +2121,11 @@ static Janet os_open(int32_t argc, Janet *argv) {
return janet_wrap_abstract(janet_stream(fd, stream_flags, NULL));
}
static Janet os_pipe(int32_t argc, Janet *argv) {
JANET_CORE_FN(os_pipe,
"(os/pipe)",
"Create a readable stream and a writable stream that are connected. Returns a two element "
"tuple where the first element is a readable stream and the second element is the writable "
"stream.") {
(void) argv;
janet_fixarity(argc, 0);
JanetHandle fds[2];
@ -1949,330 +2140,75 @@ static Janet os_pipe(int32_t argc, Janet *argv) {
#endif /* JANET_REDUCED_OS */
static const JanetReg os_cfuns[] = {
{
"os/exit", os_exit,
JDOC("(os/exit &opt x)\n\n"
"Exit from janet with an exit code equal to x. If x is not an integer, "
"the exit with status equal the hash of x.")
},
{
"os/which", os_which,
JDOC("(os/which)\n\n"
"Check the current operating system. Returns one of:\n\n"
"* :windows\n\n"
"* :macos\n\n"
"* :web - Web assembly (emscripten)\n\n"
"* :linux\n\n"
"* :freebsd\n\n"
"* :openbsd\n\n"
"* :netbsd\n\n"
"* :posix - A POSIX compatible system (default)\n\n"
"May also return a custom keyword specified at build time.")
},
{
"os/arch", os_arch,
JDOC("(os/arch)\n\n"
"Check the ISA that janet was compiled for. Returns one of:\n\n"
"* :x86\n\n"
"* :x86-64\n\n"
"* :arm\n\n"
"* :aarch64\n\n"
"* :sparc\n\n"
"* :wasm\n\n"
"* :unknown\n")
},
#ifndef JANET_REDUCED_OS
{
"os/environ", os_environ,
JDOC("(os/environ)\n\n"
"Get a copy of the os environment table.")
},
{
"os/getenv", os_getenv,
JDOC("(os/getenv variable &opt dflt)\n\n"
"Get the string value of an environment variable.")
},
{
"os/dir", os_dir,
JDOC("(os/dir dir &opt array)\n\n"
"Iterate over files and subdirectories in a directory. Returns an array of paths parts, "
"with only the file name or directory name and no prefix.")
},
{
"os/stat", os_stat,
JDOC("(os/stat path &opt tab|key)\n\n"
"Gets information about a file or directory. Returns a table if the second argument is a keyword, returns "
" only that information from stat. If the file or directory does not exist, returns nil. The keys are:\n\n"
"* :dev - the device that the file is on\n\n"
"* :mode - the type of file, one of :file, :directory, :block, :character, :fifo, :socket, :link, or :other\n\n"
"* :int-permissions - A Unix permission integer like 8r744\n\n"
"* :permissions - A Unix permission string like \"rwxr--r--\"\n\n"
"* :uid - File uid\n\n"
"* :gid - File gid\n\n"
"* :nlink - number of links to file\n\n"
"* :rdev - Real device of file. 0 on windows.\n\n"
"* :size - size of file in bytes\n\n"
"* :blocks - number of blocks in file. 0 on windows\n\n"
"* :blocksize - size of blocks in file. 0 on windows\n\n"
"* :accessed - timestamp when file last accessed\n\n"
"* :changed - timestamp when file last changed (permissions changed)\n\n"
"* :modified - timestamp when file last modified (content changed)\n")
},
{
"os/lstat", os_lstat,
JDOC("(os/lstat path &opt tab|key)\n\n"
"Like os/stat, but don't follow symlinks.\n")
},
{
"os/chmod", os_chmod,
JDOC("(os/chmod path mode)\n\n"
"Change file permissions, where mode is a permission string as returned by "
"os/perm-string, or an integer as returned by os/perm-int. "
"When mode is an integer, it is interpreted as a Unix permission value, best specified in octal, like "
"8r666 or 8r400. Windows will not differentiate between user, group, and other permissions, and thus will combine all of these permissions. Returns nil.")
},
{
"os/touch", os_touch,
JDOC("(os/touch path &opt actime modtime)\n\n"
"Update the access time and modification times for a file. By default, sets "
"times to the current time.")
},
{
"os/cd", os_cd,
JDOC("(os/cd path)\n\n"
"Change current directory to path. Returns nil on success, errors on failure.")
},
#ifndef JANET_NO_UMASK
{
"os/umask", os_umask,
JDOC("(os/umask mask)\n\n"
"Set a new umask, returns the old umask.")
},
#endif
{
"os/mkdir", os_mkdir,
JDOC("(os/mkdir path)\n\n"
"Create a new directory. The path will be relative to the current directory if relative, otherwise "
"it will be an absolute path. Returns true if the directory was created, false if the directory already exists, and "
"errors otherwise.")
},
{
"os/rmdir", os_rmdir,
JDOC("(os/rmdir path)\n\n"
"Delete a directory. The directory must be empty to succeed.")
},
{
"os/rm", os_remove,
JDOC("(os/rm path)\n\n"
"Delete a file. Returns nil.")
},
{
"os/link", os_link,
JDOC("(os/link oldpath newpath &opt symlink)\n\n"
"Create a link at newpath that points to oldpath and returns nil. "
"Iff symlink is truthy, creates a symlink. "
"Iff symlink is falsey or not provided, "
"creates a hard link. Does not work on Windows.")
},
#ifndef JANET_NO_SYMLINKS
{
"os/symlink", os_symlink,
JDOC("(os/symlink oldpath newpath)\n\n"
"Create a symlink from oldpath to newpath, returning nil. Same as (os/link oldpath newpath true).")
},
{
"os/readlink", os_readlink,
JDOC("(os/readlink path)\n\n"
"Read the contents of a symbolic link. Does not work on Windows.\n")
},
#endif
#ifndef JANET_NO_PROCESSES
{
"os/execute", os_execute,
JDOC("(os/execute args &opt flags env)\n\n"
"Execute a program on the system and pass it string arguments. `flags` "
"is a keyword that modifies how the program will execute.\n\n"
"* :e - enables passing an environment to the program. Without :e, the "
"current environment is inherited.\n\n"
"* :p - allows searching the current PATH for the binary to execute. "
"Without this flag, binaries must use absolute paths.\n\n"
"* :x - raise error if exit code is non-zero.\n\n"
"`env` is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
"These arguments should be core/file values. "
"One can also pass in the :pipe keyword "
"for these arguments to create files that will read (for :err and :out) or write (for :in) "
"to the file descriptor of the subprocess. This is only useful in `os/spawn`, which takes "
"the same parameters as `os/execute`, but will return an object that contains references to these "
"files via (return-value :in), (return-value :out), and (return-value :err). "
"Returns the exit status of the program.")
},
{
"os/spawn", os_spawn,
JDOC("(os/spawn args &opt flags env)\n\n"
"Execute a program on the system and return a handle to the process. Otherwise, the "
"same arguments as os/execute. Does not wait for the process.")
},
{
"os/shell", os_shell,
JDOC("(os/shell str)\n\n"
"Pass a command string str directly to the system shell.")
},
{
"os/proc-wait", os_proc_wait,
JDOC("(os/proc-wait proc)\n\n"
"Block until the subprocess completes. Returns the subprocess return code.")
},
{
"os/proc-kill", os_proc_kill,
JDOC("(os/proc-kill proc &opt wait)\n\n"
"Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process "
"handle on windows. If wait is truthy, will wait for the process to finsih and "
"returns the exit code. Otherwise, returns proc.")
},
{
"os/proc-close", os_proc_close,
JDOC("(os/proc-close proc)\n\n"
"Wait on a process if it has not been waited on, and close pipes created by `os/spawn` "
"if they have not been closed. Returns nil.")
},
#endif
{
"os/setenv", os_setenv,
JDOC("(os/setenv variable value)\n\n"
"Set an environment variable.")
},
{
"os/time", os_time,
JDOC("(os/time)\n\n"
"Get the current time expressed as the number of seconds since "
"January 1, 1970, the Unix epoch. Returns a real number.")
},
{
"os/mktime", os_mktime,
JDOC("(os/mktime date-struct &opt local)\n\n"
"Get the broken down date-struct time expressed as the number "
" of seconds since January 1, 1970, the Unix epoch. "
"Returns a real number. "
"Date is given in UTC unless local is truthy, in which case the "
"date is computed for the local timezone.\n\n"
"Inverse function to os/date.")
},
{
"os/clock", os_clock,
JDOC("(os/clock)\n\n"
"Return the number of seconds since some fixed point in time. The clock "
"is guaranteed to be non decreasing in real time.")
},
{
"os/sleep", os_sleep,
JDOC("(os/sleep n)\n\n"
"Suspend the program for n seconds. 'nsec' can be a real number. Returns "
"nil.")
},
{
"os/cwd", os_cwd,
JDOC("(os/cwd)\n\n"
"Returns the current working directory.")
},
{
"os/cryptorand", os_cryptorand,
JDOC("(os/cryptorand n &opt buf)\n\n"
"Get or append n bytes of good quality random data provided by the OS. Returns a new buffer or buf.")
},
{
"os/date", os_date,
JDOC("(os/date &opt time local)\n\n"
"Returns the given time as a date struct, or the current time if `time` is not given. "
"Returns a struct with following key values. Note that all numbers are 0-indexed. "
"Date is given in UTC unless `local` is truthy, in which case the date is formatted for "
"the local timezone.\n\n"
"* :seconds - number of seconds [0-61]\n\n"
"* :minutes - number of minutes [0-59]\n\n"
"* :hours - number of hours [0-23]\n\n"
"* :month-day - day of month [0-30]\n\n"
"* :month - month of year [0, 11]\n\n"
"* :year - years since year 0 (e.g. 2019)\n\n"
"* :week-day - day of the week [0-6]\n\n"
"* :year-day - day of the year [0-365]\n\n"
"* :dst - if Day Light Savings is in effect")
},
{
"os/rename", os_rename,
JDOC("(os/rename oldname newname)\n\n"
"Rename a file on disk to a new path. Returns nil.")
},
{
"os/realpath", os_realpath,
JDOC("(os/realpath path)\n\n"
"Get the absolute path for a given path, following ../, ./, and symlinks. "
"Returns an absolute path as a string. Will raise an error on Windows.")
},
{
"os/perm-string", os_permission_string,
JDOC("(os/perm-string int)\n\n"
"Convert a Unix octal permission value from a permission integer as returned by os/stat "
"to a human readable string, that follows the formatting "
"of unix tools like ls. Returns the string as a 9 character string of r, w, x and - characters. Does not "
"include the file/directory/symlink character as rendered by `ls`.")
},
{
"os/perm-int", os_permission_int,
JDOC("(os/perm-int bytes)\n\n"
"Parse a 9 character permission string and return an integer that can be used by chmod.")
},
#ifdef JANET_EV
{
"os/open", os_open,
JDOC("(os/open path &opt flags mode)\n\n"
"Create a stream from a file, like the POSIX open system call. Returns a new stream. "
"mode should be a file mode as passed to os/chmod, but only if the create flag is given. "
"The default mode is 8r666. "
"Allowed flags are as follows:\n\n"
" * :r - open this file for reading\n"
" * :w - open this file for writing\n"
" * :c - create a new file (O_CREATE)\n"
" * :e - fail if the file exists (O_EXCL)\n"
" * :t - shorten an existing file to length 0 (O_TRUNC)\n\n"
"Posix only flags:\n\n"
" * :a - append to a file (O_APPEND)\n"
" * :x - O_SYNC\n"
" * :C - O_NOCTTY\n\n"
"Windows only flags:\n\n"
" * :R - share reads (FILE_SHARE_READ)\n"
" * :W - share writes (FILE_SHARE_WRITE)\n"
" * :D - share deletes (FILE_SHARE_DELETE)\n"
" * :H - FILE_ATTRIBUTE_HIDDEN\n"
" * :O - FILE_ATTRIBUTE_READONLY\n"
" * :F - FILE_ATTRIBUTE_OFFLINE\n"
" * :T - FILE_ATTRIBUTE_TEMPORARY\n"
" * :d - FILE_FLAG_DELETE_ON_CLOSE\n"
" * :b - FILE_FLAG_NO_BUFFERING\n")
},
{
"os/pipe", os_pipe,
JDOC("(os/pipe)\n\n"
"Create a readable stream and a writable stream that are connected. Returns a two element "
"tuple where the first element is a readable stream and the second element is the writable "
"stream.")
},
#endif
#endif
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_os(JanetTable *env) {
#if !defined(JANET_REDUCED_OS) && defined(JANET_WINDOWS) && defined(JANET_THREADS)
/* During start up, the top-most abstract machine (thread)
* in the thread tree sets up the critical section. */
if (!env_lock_initialized) {
static volatile long env_lock_initializing = 0;
static volatile long env_lock_initialized = 0;
if (!InterlockedExchange(&env_lock_initializing, 1)) {
InitializeCriticalSection(&env_lock);
env_lock_initialized = 1;
InterlockedOr(&env_lock_initialized, 1);
} else {
while (!InterlockedOr(&env_lock_initialized, 0)) {
Sleep(0);
}
}
#endif
#ifndef JANET_NO_PROCESSES
#endif
janet_core_cfuns(env, NULL, os_cfuns);
JanetRegExt os_cfuns[] = {
JANET_CORE_REG("os/exit", os_exit),
JANET_CORE_REG("os/which", os_which),
JANET_CORE_REG("os/arch", os_arch),
#ifndef JANET_REDUCED_OS
JANET_CORE_REG("os/environ", os_environ),
JANET_CORE_REG("os/getenv", os_getenv),
JANET_CORE_REG("os/dir", os_dir),
JANET_CORE_REG("os/stat", os_stat),
JANET_CORE_REG("os/lstat", os_lstat),
JANET_CORE_REG("os/chmod", os_chmod),
JANET_CORE_REG("os/touch", os_touch),
JANET_CORE_REG("os/cd", os_cd),
#ifndef JANET_NO_UMASK
JANET_CORE_REG("os/umask", os_umask),
#endif
JANET_CORE_REG("os/mkdir", os_mkdir),
JANET_CORE_REG("os/rmdir", os_rmdir),
JANET_CORE_REG("os/rm", os_remove),
JANET_CORE_REG("os/link", os_link),
#ifndef JANET_NO_SYMLINKS
JANET_CORE_REG("os/symlink", os_symlink),
JANET_CORE_REG("os/readlink", os_readlink),
#endif
#ifndef JANET_NO_PROCESSES
JANET_CORE_REG("os/execute", os_execute),
JANET_CORE_REG("os/spawn", os_spawn),
JANET_CORE_REG("os/shell", os_shell),
JANET_CORE_REG("os/proc-wait", os_proc_wait),
JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close),
#endif
JANET_CORE_REG("os/setenv", os_setenv),
JANET_CORE_REG("os/time", os_time),
JANET_CORE_REG("os/mktime", os_mktime),
JANET_CORE_REG("os/clock", os_clock),
JANET_CORE_REG("os/sleep", os_sleep),
JANET_CORE_REG("os/cwd", os_cwd),
JANET_CORE_REG("os/cryptorand", os_cryptorand),
JANET_CORE_REG("os/date", os_date),
JANET_CORE_REG("os/rename", os_rename),
JANET_CORE_REG("os/realpath", os_realpath),
JANET_CORE_REG("os/perm-string", os_permission_string),
JANET_CORE_REG("os/perm-int", os_permission_int),
#ifdef JANET_EV
JANET_CORE_REG("os/open", os_open),
JANET_CORE_REG("os/pipe", os_pipe),
#endif
#endif
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, os_cfuns);
}

View File

@ -51,15 +51,15 @@ static const uint32_t symchars[8] = {
};
/* Check if a character is a valid symbol character
* symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_~| */
static int is_symbol_char(uint8_t c) {
* symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_| */
int janet_is_symbol_char(uint8_t c) {
return symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F));
}
/* Validate some utf8. Useful for identifiers. Only validates
* the encoding, does not check for valid code points (they
* are less well defined than the encoding). */
static int valid_utf8(const uint8_t *str, int32_t len) {
int janet_valid_utf8(const uint8_t *str, int32_t len) {
int32_t i = 0;
int32_t j;
while (i < len) {
@ -411,7 +411,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
Janet ret;
double numval;
int32_t blen;
if (is_symbol_char(c)) {
if (janet_is_symbol_char(c)) {
push_buf(p, (uint8_t) c);
if (c > 127) state->argn = 1; /* Use to indicate non ascii */
return 1;
@ -422,7 +422,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
int start_num = start_dig || p->buf[0] == '-' || p->buf[0] == '+' || p->buf[0] == '.';
if (p->buf[0] == ':') {
/* Don't do full utf-8 check unless we have seen non ascii characters. */
int valid = (!state->argn) || valid_utf8(p->buf + 1, blen - 1);
int valid = (!state->argn) || janet_valid_utf8(p->buf + 1, blen - 1);
if (!valid) {
p->error = "invalid utf-8 in keyword";
return 0;
@ -442,7 +442,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) {
return 0;
} else {
/* Don't do full utf-8 check unless we have seen non ascii characters. */
int valid = (!state->argn) || valid_utf8(p->buf, blen);
int valid = (!state->argn) || janet_valid_utf8(p->buf, blen);
if (!valid) {
p->error = "invalid utf-8 in symbol";
return 0;
@ -582,7 +582,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) {
switch (c) {
default:
if (is_whitespace(c)) return 1;
if (!is_symbol_char(c)) {
if (!janet_is_symbol_char(c)) {
p->error = "unexpected character";
return 1;
}
@ -878,7 +878,10 @@ const JanetAbstractType janet_parser_type = {
};
/* C Function parser */
static Janet cfun_parse_parser(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_parser,
"(parser/new)",
"Creates and returns a new parser object. Parsers are state machines "
"that can receive bytes, and generate a stream of values.") {
(void) argv;
janet_fixarity(argc, 0);
JanetParser *p = janet_abstract(&janet_parser_type, sizeof(JanetParser));
@ -886,7 +889,11 @@ static Janet cfun_parse_parser(int32_t argc, Janet *argv) {
return janet_wrap_abstract(p);
}
static Janet cfun_parse_consume(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_consume,
"(parser/consume parser bytes &opt index)",
"Input bytes into the parser and parse them. Will not throw errors "
"if there is a parse error. Starts at the byte index given by index. Returns "
"the number of bytes read.") {
janet_arity(argc, 2, 3);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
JanetByteView view = janet_getbytes(argv, 1);
@ -911,14 +918,20 @@ static Janet cfun_parse_consume(int32_t argc, Janet *argv) {
return janet_wrap_integer(i);
}
static Janet cfun_parse_eof(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_eof,
"(parser/eof parser)",
"Indicate that the end of file was reached to the parser. This puts the parser in the :dead state.") {
janet_fixarity(argc, 1);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
janet_parser_eof(p);
return argv[0];
}
static Janet cfun_parse_insert(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_insert,
"(parser/insert parser value)",
"Insert a value into the parser. This means that the parser state can be manipulated "
"in between chunks of bytes. This would allow a user to add extra elements to arrays "
"and tuples, for example. Returns the parser.") {
janet_fixarity(argc, 2);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
JanetParseState *s = p->states + p->statecount - 1;
@ -957,13 +970,17 @@ static Janet cfun_parse_insert(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_parse_has_more(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_has_more,
"(parser/has-more parser)",
"Check if the parser has more values in the value queue.") {
janet_fixarity(argc, 1);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
return janet_wrap_boolean(janet_parser_has_more(p));
}
static Janet cfun_parse_byte(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_byte,
"(parser/byte parser b)",
"Input a single byte into the parser byte stream. Returns the parser.") {
janet_fixarity(argc, 2);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
int32_t i = janet_getinteger(argv, 1);
@ -971,7 +988,13 @@ static Janet cfun_parse_byte(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_parse_status(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_status,
"(parser/status parser)",
"Gets the current status of the parser state machine. The status will "
"be one of:\n\n"
"* :pending - a value is being parsed.\n\n"
"* :error - a parsing error was encountered.\n\n"
"* :root - the parser can either read more values or safely terminate.") {
janet_fixarity(argc, 1);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
const char *stat = NULL;
@ -992,7 +1015,12 @@ static Janet cfun_parse_status(int32_t argc, Janet *argv) {
return janet_ckeywordv(stat);
}
static Janet cfun_parse_error(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_error,
"(parser/error parser)",
"If the parser is in the error state, returns the message associated with "
"that error. Otherwise, returns nil. Also flushes the parser state and parser "
"queue, so be sure to handle everything in the queue before calling "
"parser/error.") {
janet_fixarity(argc, 1);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
const char *err = janet_parser_error(p);
@ -1004,7 +1032,13 @@ static Janet cfun_parse_error(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_parse_produce(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_produce,
"(parser/produce parser &opt wrap)",
"Dequeue the next value in the parse queue. Will return nil if "
"no parsed values are in the queue, otherwise will dequeue the "
"next value. If `wrap` is truthy, will return a 1-element tuple that "
"wraps the result. This tuple can be used for source-mapping "
"purposes.") {
janet_arity(argc, 1, 2);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
if (argc == 2 && janet_truthy(argv[1])) {
@ -1014,14 +1048,22 @@ static Janet cfun_parse_produce(int32_t argc, Janet *argv) {
}
}
static Janet cfun_parse_flush(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_flush,
"(parser/flush parser)",
"Clears the parser state and parse queue. Can be used to reset the parser "
"if an error was encountered. Does not reset the line and column counter, so "
"to begin parsing in a new context, create a new parser.") {
janet_fixarity(argc, 1);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
janet_parser_flush(p);
return argv[0];
}
static Janet cfun_parse_where(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_where,
"(parser/where parser &opt line col)",
"Returns the current line number and column of the parser's internal state. If line is "
"provided, the current line number of the parser is first set to that value. If column is "
"also provided, the current column number of the parser is also first set to that value.") {
janet_arity(argc, 1, 3);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
if (argc > 1) {
@ -1162,7 +1204,16 @@ static const struct ParserStateGetter parser_state_getters[] = {
{NULL, NULL}
};
static Janet cfun_parse_state(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_state,
"(parser/state parser &opt key)",
"Returns a representation of the internal state of the parser. If a key is passed, "
"only that information about the state is returned. Allowed keys are:\n\n"
"* :delimiters - Each byte in the string represents a nested data structure. For example, "
"if the parser state is '([\"', then the parser is in the middle of parsing a "
"string inside of square brackets inside parentheses. Can be used to augment a REPL prompt.\n\n"
"* :frames - Each table in the array represents a 'frame' in the parser state. Frames "
"contain information about the start of the expression being parsed as well as the "
"type of that expression and some type-specific information.") {
janet_arity(argc, 1, 2);
const uint8_t *key = NULL;
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
@ -1190,7 +1241,11 @@ static Janet cfun_parse_state(int32_t argc, Janet *argv) {
}
}
static Janet cfun_parse_clone(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_parse_clone,
"(parser/clone p)",
"Creates a deep clone of a parser that is identical to the input parser. "
"This cloned parser can be used to continue parsing from a good checkpoint "
"if parsing later fails. Returns a new parser.") {
janet_fixarity(argc, 1);
JanetParser *src = janet_getabstract(argv, 0, &janet_parser_type);
JanetParser *dest = janet_abstract(&janet_parser_type, sizeof(JanetParser));
@ -1225,105 +1280,23 @@ static Janet parsernext(void *p, Janet key) {
return janet_nextmethod(parser_methods, key);
}
static const JanetReg parse_cfuns[] = {
{
"parser/new", cfun_parse_parser,
JDOC("(parser/new)\n\n"
"Creates and returns a new parser object. Parsers are state machines "
"that can receive bytes, and generate a stream of values.")
},
{
"parser/clone", cfun_parse_clone,
JDOC("(parser/clone p)\n\n"
"Creates a deep clone of a parser that is identical to the input parser. "
"This cloned parser can be used to continue parsing from a good checkpoint "
"if parsing later fails. Returns a new parser.")
},
{
"parser/has-more", cfun_parse_has_more,
JDOC("(parser/has-more parser)\n\n"
"Check if the parser has more values in the value queue.")
},
{
"parser/produce", cfun_parse_produce,
JDOC("(parser/produce parser &opt wrap)\n\n"
"Dequeue the next value in the parse queue. Will return nil if "
"no parsed values are in the queue, otherwise will dequeue the "
"next value. If `wrap` is truthy, will return a 1-element tuple that "
"wraps the result. This tuple can be used for source-mapping "
"purposes.")
},
{
"parser/consume", cfun_parse_consume,
JDOC("(parser/consume parser bytes &opt index)\n\n"
"Input bytes into the parser and parse them. Will not throw errors "
"if there is a parse error. Starts at the byte index given by index. Returns "
"the number of bytes read.")
},
{
"parser/byte", cfun_parse_byte,
JDOC("(parser/byte parser b)\n\n"
"Input a single byte into the parser byte stream. Returns the parser.")
},
{
"parser/error", cfun_parse_error,
JDOC("(parser/error parser)\n\n"
"If the parser is in the error state, returns the message associated with "
"that error. Otherwise, returns nil. Also flushes the parser state and parser "
"queue, so be sure to handle everything in the queue before calling "
"parser/error.")
},
{
"parser/status", cfun_parse_status,
JDOC("(parser/status parser)\n\n"
"Gets the current status of the parser state machine. The status will "
"be one of:\n\n"
"* :pending - a value is being parsed.\n\n"
"* :error - a parsing error was encountered.\n\n"
"* :root - the parser can either read more values or safely terminate.")
},
{
"parser/flush", cfun_parse_flush,
JDOC("(parser/flush parser)\n\n"
"Clears the parser state and parse queue. Can be used to reset the parser "
"if an error was encountered. Does not reset the line and column counter, so "
"to begin parsing in a new context, create a new parser.")
},
{
"parser/state", cfun_parse_state,
JDOC("(parser/state parser &opt key)\n\n"
"Returns a representation of the internal state of the parser. If a key is passed, "
"only that information about the state is returned. Allowed keys are:\n\n"
"* :delimiters - Each byte in the string represents a nested data structure. For example, "
"if the parser state is '([\"', then the parser is in the middle of parsing a "
"string inside of square brackets inside parentheses. Can be used to augment a REPL prompt.\n\n"
"* :frames - Each table in the array represents a 'frame' in the parser state. Frames "
"contain information about the start of the expression being parsed as well as the "
"type of that expression and some type-specific information.")
},
{
"parser/where", cfun_parse_where,
JDOC("(parser/where parser &opt line col)\n\n"
"Returns the current line number and column of the parser's internal state. If line is "
"provided, the current line number of the parser is first set to that value. If column is "
"also provided, the current column number of the parser is also first set to that value.")
},
{
"parser/eof", cfun_parse_eof,
JDOC("(parser/eof parser)\n\n"
"Indicate that the end of file was reached to the parser. This puts the parser in the :dead state.")
},
{
"parser/insert", cfun_parse_insert,
JDOC("(parser/insert parser value)\n\n"
"Insert a value into the parser. This means that the parser state can be manipulated "
"in between chunks of bytes. This would allow a user to add extra elements to arrays "
"and tuples, for example. Returns the parser.")
},
{NULL, NULL, NULL}
};
/* Load the library */
void janet_lib_parse(JanetTable *env) {
janet_core_cfuns(env, NULL, parse_cfuns);
JanetRegExt parse_cfuns[] = {
JANET_CORE_REG("parser/new", cfun_parse_parser),
JANET_CORE_REG("parser/clone", cfun_parse_clone),
JANET_CORE_REG("parser/has-more", cfun_parse_has_more),
JANET_CORE_REG("parser/produce", cfun_parse_produce),
JANET_CORE_REG("parser/consume", cfun_parse_consume),
JANET_CORE_REG("parser/byte", cfun_parse_byte),
JANET_CORE_REG("parser/error", cfun_parse_error),
JANET_CORE_REG("parser/status", cfun_parse_status),
JANET_CORE_REG("parser/flush", cfun_parse_flush),
JANET_CORE_REG("parser/state", cfun_parse_state),
JANET_CORE_REG("parser/where", cfun_parse_where),
JANET_CORE_REG("parser/eof", cfun_parse_eof),
JANET_CORE_REG("parser/insert", cfun_parse_insert),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, parse_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -387,6 +387,25 @@ tail:
return result;
}
case RULE_CAPTURE_NUM: {
down1(s);
const uint8_t *result = peg_rule(s, s->bytecode + rule[1], text);
up1(s);
if (!result) return NULL;
/* check number parsing */
double x = 0.0;
int32_t base = (int32_t) rule[2];
if (janet_scan_number_base(text, (int32_t)(result - text), base, &x)) return NULL;
/* Specialized pushcap - avoid intermediate string creation */
if (!s->has_backref && s->mode == PEG_MODE_ACCUMULATE) {
janet_buffer_push_bytes(s->scratch, text, (int32_t)(result - text));
} else {
uint32_t tag = rule[3];
pushcap(s, janet_wrap_number(x), tag);
}
return result;
}
case RULE_ACCUMULATE: {
uint32_t tag = rule[2];
int oldmode = s->mode;
@ -975,6 +994,25 @@ static void spec_unref(Builder *b, int32_t argc, const Janet *argv) {
spec_cap1(b, argc, argv, RULE_UNREF);
}
static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 1, 3);
Reserve r = reserve(b, 4);
uint32_t base = 0;
if (argc >= 2) {
if (!janet_checktype(argv[1], JANET_NIL)) {
if (!janet_checkint(argv[1])) goto error;
base = (uint32_t) janet_unwrap_integer(argv[1]);
if (base < 2 || base > 36) goto error;
}
}
uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0;
uint32_t rule = peg_compile1(b, argv[0]);
emit_3(r, RULE_CAPTURE_NUM, rule, base, tag);
return;
error:
peg_panicf(b, "expected integer between 2 and 36, got %v", argv[2]);
}
static void spec_reference(Builder *b, int32_t argc, const Janet *argv) {
peg_arity(b, argc, 1, 2);
Reserve r = reserve(b, 3);
@ -1118,6 +1156,7 @@ static const SpecialPair peg_specials[] = {
{"line", spec_line},
{"look", spec_look},
{"not", spec_not},
{"number", spec_capture_number},
{"opt", spec_opt},
{"position", spec_position},
{"quote", spec_capture},
@ -1214,6 +1253,18 @@ static uint32_t peg_compile1(Builder *b, Janet peg) {
emit_bytes(b, RULE_LITERAL, len, str);
break;
}
case JANET_TABLE: {
/* Build grammar table */
JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg));
new_grammar->proto = grammar;
b->grammar = grammar = new_grammar;
/* Run the main rule */
Janet main_rule = janet_table_rawget(grammar, janet_ckeywordv("main"));
if (janet_checktype(main_rule, JANET_NIL))
peg_panic(b, "grammar requires :main rule");
rule = peg_compile1(b, main_rule);
break;
}
case JANET_STRUCT: {
/* Build grammar table */
const JanetKV *st = janet_unwrap_struct(peg);
@ -1419,6 +1470,12 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) {
if (rule[1] >= clen) goto bad;
i += 3;
break;
case RULE_CAPTURE_NUM:
/* [rule, base, tag] */
if (rule[1] >= blen) goto bad;
op_flags[rule[1]] |= 0x01;
i += 4;
break;
case RULE_ACCUMULATE:
case RULE_GROUP:
case RULE_CAPTURE:
@ -1541,7 +1598,11 @@ static JanetPeg *compile_peg(Janet x) {
* C Functions
*/
static Janet cfun_peg_compile(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_compile,
"(peg/compile peg)",
"Compiles a peg source data structure into a <core/peg>. This will speed up matching "
"if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment "
"the grammar of the peg for otherwise undefined peg keywords.") {
janet_fixarity(argc, 1);
JanetPeg *peg = compile_peg(argv[0]);
return janet_wrap_abstract(peg);
@ -1604,13 +1665,18 @@ static void peg_call_reset(PegCall *c) {
c->s.tags->count = 0;
}
static Janet cfun_peg_match(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_match,
"(peg/match peg text &opt start & args)",
"Match a Parsing Expression Grammar to a byte string and return an array of captured values. "
"Returns nil if text does not match the language defined by peg. The syntax of PEGs is documented on the Janet website.") {
PegCall c = peg_cfun_init(argc, argv, 0);
const uint8_t *result = peg_rule(&c.s, c.s.bytecode, c.bytes.bytes + c.start);
return result ? janet_wrap_array(c.s.captures) : janet_wrap_nil();
}
static Janet cfun_peg_find(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_find,
"(peg/find peg text &opt start & args)",
"Find first index where the peg matches in text. Returns an integer, or nil if not found.") {
PegCall c = peg_cfun_init(argc, argv, 0);
for (int32_t i = c.start; i < c.bytes.len; i++) {
peg_call_reset(&c);
@ -1620,7 +1686,9 @@ static Janet cfun_peg_find(int32_t argc, Janet *argv) {
return janet_wrap_nil();
}
static Janet cfun_peg_find_all(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_find_all,
"(peg/find-all peg text &opt start & args)",
"Find all indexes where the peg matches in text. Returns an array of integers.") {
PegCall c = peg_cfun_init(argc, argv, 0);
JanetArray *ret = janet_array(0);
for (int32_t i = c.start; i < c.bytes.len; i++) {
@ -1659,11 +1727,16 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) {
return janet_wrap_buffer(ret);
}
static Janet cfun_peg_replace_all(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_replace_all,
"(peg/replace-all peg repl text &opt start & args)",
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.") {
return cfun_peg_replace_generic(argc, argv, 0);
}
static Janet cfun_peg_replace(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_peg_replace,
"(peg/replace peg repl text &opt start & args)",
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. "
"If no matches are found, returns the input string in a new buffer.") {
return cfun_peg_replace_generic(argc, argv, 1);
}
@ -1688,47 +1761,18 @@ static Janet peg_next(void *p, Janet key) {
return janet_nextmethod(peg_methods, key);
}
static const JanetReg peg_cfuns[] = {
{
"peg/compile", cfun_peg_compile,
JDOC("(peg/compile peg)\n\n"
"Compiles a peg source data structure into a <core/peg>. This will speed up matching "
"if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment "
"the grammar of the peg for otherwise undefined peg keywords.")
},
{
"peg/match", cfun_peg_match,
JDOC("(peg/match peg text &opt start & args)\n\n"
"Match a Parsing Expression Grammar to a byte string and return an array of captured values. "
"Returns nil if text does not match the language defined by peg. The syntax of PEGs is documented on the Janet website.")
},
{
"peg/find", cfun_peg_find,
JDOC("(peg/find peg text &opt start & args)\n\n"
"Find first index where the peg matches in text. Returns an integer, or nil if not found.")
},
{
"peg/find-all", cfun_peg_find_all,
JDOC("(peg/find-all peg text &opt start & args)\n\n"
"Find all indexes where the peg matches in text. Returns an array of integers.")
},
{
"peg/replace", cfun_peg_replace,
JDOC("(peg/replace peg repl text &opt start & args)\n\n"
"Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. "
"If no matches are found, returns the input string in a new buffer.")
},
{
"peg/replace-all", cfun_peg_replace_all,
JDOC("(peg/replace-all peg repl text &opt start & args)\n\n"
"Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.")
},
{NULL, NULL, NULL}
};
/* Load the peg module */
void janet_lib_peg(JanetTable *env) {
janet_core_cfuns(env, NULL, peg_cfuns);
JanetRegExt cfuns[] = {
JANET_CORE_REG("peg/compile", cfun_peg_compile),
JANET_CORE_REG("peg/match", cfun_peg_match),
JANET_CORE_REG("peg/find", cfun_peg_find),
JANET_CORE_REG("peg/find-all", cfun_peg_find_all),
JANET_CORE_REG("peg/replace", cfun_peg_replace),
JANET_CORE_REG("peg/replace-all", cfun_peg_replace_all),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, cfuns);
janet_register_abstract_type(&janet_peg_type);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -227,12 +227,14 @@ void janet_to_string_b(JanetBuffer *buffer, Janet x) {
}
return;
case JANET_CFUNCTION: {
Janet check = janet_table_get(janet_vm_registry, x);
if (janet_checktype(check, JANET_SYMBOL)) {
JanetCFunRegistry *reg = janet_registry_get(janet_unwrap_cfunction(x));
if (NULL != reg) {
janet_buffer_push_cstring(buffer, "<cfunction ");
janet_buffer_push_bytes(buffer,
janet_unwrap_symbol(check),
janet_string_length(janet_unwrap_symbol(check)));
if (NULL != reg->name_prefix) {
janet_buffer_push_cstring(buffer, reg->name_prefix);
janet_buffer_push_u8(buffer, '/');
}
janet_buffer_push_cstring(buffer, reg->name);
janet_buffer_push_u8(buffer, '>');
break;
}
@ -259,21 +261,13 @@ void janet_to_string_b(JanetBuffer *buffer, Janet x) {
/* See parse.c for full table */
static const uint32_t pp_symchars[8] = {
0x00000000, 0xf7ffec72, 0xc7ffffff, 0x07fffffe,
0x00000000, 0x00000000, 0x00000000, 0x00000000
};
static int pp_is_symbol_char(uint8_t c) {
return pp_symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F));
}
/* Check if a symbol or keyword contains no symbol characters */
static int contains_bad_chars(const uint8_t *sym, int issym) {
int32_t len = janet_string_length(sym);
if (len && issym && sym[0] >= '0' && sym[0] <= '9') return 1;
if (!janet_valid_utf8(sym, len)) return 1;
for (int32_t i = 0; i < len; i++) {
if (!pp_is_symbol_char(sym[i])) return 1;
if (!janet_is_symbol_char(sym[i])) return 1;
}
return 0;
}
@ -898,7 +892,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) {
}
}
if (nb >= MAX_ITEM)
janet_panicf("format buffer overflow", form);
janet_panic("format buffer overflow");
if (nb > 0)
janet_buffer_push_bytes(b, (uint8_t *) item, nb);
}
@ -1050,7 +1044,7 @@ void janet_buffer_format(
}
}
if (nb >= MAX_ITEM)
janet_panicf("format buffer overflow", form);
janet_panic("format buffer overflow");
if (nb > 0)
janet_buffer_push_bytes(b, (uint8_t *) item, nb);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -79,7 +79,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char
const char *e = janet_parser_error(&parser);
errflags |= 0x04;
ret = janet_cstringv(e);
janet_eprintf("parse error in %s: %s\n", sourcePath, e);
int32_t line = parser.line;
int32_t col = parser.column;
janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e);
done = 1;
break;
}
@ -108,3 +110,19 @@ int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Jan
return janet_dobytes(env, (const uint8_t *)str, len, sourcePath, out);
}
/* Run a fiber to completion (use event loop if enabled). Return the status. */
int janet_loop_fiber(JanetFiber *fiber) {
int status;
#ifdef JANET_EV
janet_schedule(fiber, janet_wrap_nil());
janet_loop();
status = janet_fiber_status(fiber);
#else
Janet out;
status = janet_continue(fiber, janet_wrap_nil(), &out);
if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) {
janet_stacktrace(fiber, out);
}
#endif
return status;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -410,7 +410,9 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) {
right = janetc_value(bodyopts, truebody);
if (!drop && !tail) janetc_copy(c, target, right);
janetc_popscope(c);
janetc_throwaway(bodyopts, falsebody);
if (!janet_checktype(falsebody, JANET_NIL)) {
janetc_throwaway(bodyopts, falsebody);
}
janetc_popscope(c);
return target;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose and contributors
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -20,21 +20,42 @@
* IN THE SOFTWARE.
*/
/* A very simple native module */
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include "state.h"
#endif
static Janet cfun_get_five(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_number(5.0);
JANET_THREAD_LOCAL JanetVM janet_vm;
JanetVM *janet_local_vm(void) {
return &janet_vm;
}
static const JanetReg array_cfuns[] = {
{"get5", cfun_get_five, NULL},
{NULL, NULL, NULL}
};
JANET_MODULE_ENTRY(JanetTable *env) {
janet_cfuns(env, NULL, array_cfuns);
JanetVM *janet_vm_alloc(void) {
JanetVM *mem = janet_malloc(sizeof(JanetVM));
if (NULL == mem) {
JANET_OUT_OF_MEMORY;
}
return mem;
}
void janet_vm_free(JanetVM *vm) {
janet_free(vm);
}
void janet_vm_save(JanetVM *into) {
*into = janet_vm;
}
void janet_vm_load(JanetVM *from) {
janet_vm = *from;
}
/* Trigger suspension of the Janet vm by trying to
* exit the interpeter loop when convenient. You can optionally
* use NULL to interrupt the current VM when convenient */
void janet_interpreter_interrupt(JanetVM *vm) {
vm = vm ? vm : &janet_vm;
vm->auto_suspend = 1;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -25,81 +25,151 @@
#include <stdint.h>
/* The VM state. Rather than a struct that is passed
* around, the vm state is global for simplicity. If
* at some point a global state object, or context,
* is required to be passed around, this is what would
* be in it. However, thread local global variables for interpreter
* state should allow easy multi-threading. */
typedef int64_t JanetTimestamp;
typedef struct JanetScratch JanetScratch;
typedef struct JanetScratch {
JanetScratchFinalizer finalize;
long long mem[]; /* for proper alignment */
} JanetScratch;
/* Top level dynamic bindings */
extern JANET_THREAD_LOCAL JanetTable *janet_vm_top_dyns;
/* Cache the core environment */
extern JANET_THREAD_LOCAL JanetTable *janet_vm_core_env;
/* How many VM stacks have been entered */
extern JANET_THREAD_LOCAL int janet_vm_stackn;
/* The current running fiber on the current thread.
* Set and unset by janet_run. */
extern JANET_THREAD_LOCAL JanetFiber *janet_vm_fiber;
extern JANET_THREAD_LOCAL JanetFiber *janet_vm_root_fiber;
/* The current pointer to the inner most jmp_buf. The current
* return point for panics. */
extern JANET_THREAD_LOCAL jmp_buf *janet_vm_jmp_buf;
extern JANET_THREAD_LOCAL Janet *janet_vm_return_reg;
/* The global registry for c functions. Used to store meta-data
* along with otherwise bare c function pointers. */
extern JANET_THREAD_LOCAL JanetTable *janet_vm_registry;
/* Registry for abstract abstract types that can be marshalled.
* We need this to look up the constructors when unmarshalling. */
extern JANET_THREAD_LOCAL JanetTable *janet_vm_abstract_registry;
/* Immutable value cache */
extern JANET_THREAD_LOCAL const uint8_t **janet_vm_cache;
extern JANET_THREAD_LOCAL uint32_t janet_vm_cache_capacity;
extern JANET_THREAD_LOCAL uint32_t janet_vm_cache_count;
extern JANET_THREAD_LOCAL uint32_t janet_vm_cache_deleted;
/* Garbage collection */
extern JANET_THREAD_LOCAL void *janet_vm_blocks;
extern JANET_THREAD_LOCAL size_t janet_vm_gc_interval;
extern JANET_THREAD_LOCAL size_t janet_vm_next_collection;
extern JANET_THREAD_LOCAL size_t janet_vm_block_count;
extern JANET_THREAD_LOCAL int janet_vm_gc_suspend;
/* GC roots */
extern JANET_THREAD_LOCAL Janet *janet_vm_roots;
extern JANET_THREAD_LOCAL size_t janet_vm_root_count;
extern JANET_THREAD_LOCAL size_t janet_vm_root_capacity;
/* Scratch memory */
extern JANET_THREAD_LOCAL JanetScratch **janet_scratch_mem;
extern JANET_THREAD_LOCAL size_t janet_scratch_cap;
extern JANET_THREAD_LOCAL size_t janet_scratch_len;
/* Recursionless traversal of data structures */
typedef struct {
JanetGCObject *self;
JanetGCObject *other;
int32_t index;
int32_t index2;
} JanetTraversalNode;
extern JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal;
extern JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_top;
extern JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_base;
/* Setup / teardown */
#ifdef JANET_THREADS
void janet_threads_init(void);
void janet_threads_deinit(void);
typedef struct {
int32_t capacity;
int32_t head;
int32_t tail;
void *data;
} JanetQueue;
typedef struct {
JanetTimestamp when;
JanetFiber *fiber;
JanetFiber *curr_fiber;
uint32_t sched_id;
int is_error;
} JanetTimeout;
/* Registry table for C functions - containts metadata that can
* be looked up by cfunction pointer. All strings here are pointing to
* static memory not managed by Janet. */
typedef struct {
JanetCFunction cfun;
const char *name;
const char *name_prefix;
const char *source_file;
int32_t source_line;
/* int32_t min_arity; */
/* int32_t max_arity; */
} JanetCFunRegistry;
struct JanetVM {
/* Place for user data */
void *user;
/* Top level dynamic bindings */
JanetTable *top_dyns;
/* Cache the core environment */
JanetTable *core_env;
/* How many VM stacks have been entered */
int stackn;
/* If this flag is true, suspend on function calls and backwards jumps.
* When this occurs, this flag will be reset to 0. */
int auto_suspend;
/* The current running fiber on the current thread.
* Set and unset by janet_run. */
JanetFiber *fiber;
JanetFiber *root_fiber;
/* The current pointer to the inner most jmp_buf. The current
* return point for panics. */
jmp_buf *signal_buf;
Janet *return_reg;
/* The global registry for c functions. Used to store meta-data
* along with otherwise bare c function pointers. */
JanetCFunRegistry *registry;
size_t registry_cap;
size_t registry_count;
int registry_dirty;
/* Registry for abstract abstract types that can be marshalled.
* We need this to look up the constructors when unmarshalling. */
JanetTable *abstract_registry;
/* Immutable value cache */
const uint8_t **cache;
uint32_t cache_capacity;
uint32_t cache_count;
uint32_t cache_deleted;
uint8_t gensym_counter[8];
/* Garbage collection */
void *blocks;
size_t gc_interval;
size_t next_collection;
size_t block_count;
int gc_suspend;
/* GC roots */
Janet *roots;
size_t root_count;
size_t root_capacity;
/* Scratch memory */
JanetScratch **scratch_mem;
size_t scratch_cap;
size_t scratch_len;
/* Random number generator */
JanetRNG rng;
/* Traversal pointers */
JanetTraversalNode *traversal;
JanetTraversalNode *traversal_top;
JanetTraversalNode *traversal_base;
/* Event loop and scheduler globals */
#ifdef JANET_EV
size_t tq_count;
size_t tq_capacity;
JanetQueue spawn;
JanetTimeout *tq;
JanetRNG ev_rng;
JanetListenerState **listeners;
size_t listener_count;
size_t listener_cap;
size_t extra_listeners;
JanetTable threaded_abstracts; /* All abstract types that can be shared between threads (used in this thread) */
#ifdef JANET_WINDOWS
void **iocp;
#elif defined(JANET_EV_EPOLL)
JanetHandle selfpipe[2];
int epoll;
int timerfd;
int timer_enabled;
#elif defined(JANET_EV_KQUEUE)
JanetHandle selfpipe[2];
int kq;
int timer;
int timer_enabled;
#else
JanetHandle selfpipe[2];
struct pollfd *fds;
#endif
#endif
};
extern JANET_THREAD_LOCAL JanetVM janet_vm;
#ifdef JANET_NET
void janet_net_init(void);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -170,25 +170,37 @@ static int32_t kmp_next(struct kmp_state *state) {
/* CFuns */
static Janet cfun_string_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_slice,
"(string/slice bytes &opt start end)",
"Returns a substring from a byte sequence. The substring is from "
"index start inclusive to index end exclusive. All indexing "
"is from 0. 'start' and 'end' can also be negative to indicate indexing "
"from the end of the string. Note that index -1 is synonymous with "
"index (length bytes) to allow a full negative slice range. ") {
JanetByteView view = janet_getbytes(argv, 0);
JanetRange range = janet_getslice(argc, argv);
return janet_stringv(view.bytes + range.start, range.end - range.start);
}
static Janet cfun_symbol_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_symbol_slice,
"(symbol/slice bytes &opt start end)",
"Same a string/slice, but returns a symbol.") {
JanetByteView view = janet_getbytes(argv, 0);
JanetRange range = janet_getslice(argc, argv);
return janet_symbolv(view.bytes + range.start, range.end - range.start);
}
static Janet cfun_keyword_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_keyword_slice,
"(keyword/slice bytes &opt start end)",
"Same a string/slice, but returns a keyword.") {
JanetByteView view = janet_getbytes(argv, 0);
JanetRange range = janet_getslice(argc, argv);
return janet_keywordv(view.bytes + range.start, range.end - range.start);
}
static Janet cfun_string_repeat(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_repeat,
"(string/repeat bytes n)",
"Returns a string that is n copies of bytes concatenated.") {
janet_fixarity(argc, 2);
JanetByteView view = janet_getbytes(argv, 0);
int32_t rep = janet_getinteger(argv, 1);
@ -204,7 +216,9 @@ static Janet cfun_string_repeat(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(newbuf));
}
static Janet cfun_string_bytes(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_bytes,
"(string/bytes str)",
"Returns a tuple of integers that are the byte values of the string.") {
janet_fixarity(argc, 1);
JanetByteView view = janet_getbytes(argv, 0);
Janet *tup = janet_tuple_begin(view.len);
@ -215,7 +229,10 @@ static Janet cfun_string_bytes(int32_t argc, Janet *argv) {
return janet_wrap_tuple(janet_tuple_end(tup));
}
static Janet cfun_string_frombytes(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_frombytes,
"(string/from-bytes & byte-vals)",
"Creates a string from integer parameters with byte values. All integers "
"will be coerced to the range of 1 byte 0-255.") {
int32_t i;
uint8_t *buf = janet_string_begin(argc);
for (i = 0; i < argc; i++) {
@ -225,7 +242,11 @@ static Janet cfun_string_frombytes(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(buf));
}
static Janet cfun_string_asciilower(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_asciilower,
"(string/ascii-lower str)",
"Returns a new string where all bytes are replaced with the "
"lowercase version of themselves in ASCII. Does only a very simple "
"case check, meaning no unicode support.") {
janet_fixarity(argc, 1);
JanetByteView view = janet_getbytes(argv, 0);
uint8_t *buf = janet_string_begin(view.len);
@ -240,7 +261,11 @@ static Janet cfun_string_asciilower(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(buf));
}
static Janet cfun_string_asciiupper(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_asciiupper,
"(string/ascii-upper str)",
"Returns a new string where all bytes are replaced with the "
"uppercase version of themselves in ASCII. Does only a very simple "
"case check, meaning no unicode support.") {
janet_fixarity(argc, 1);
JanetByteView view = janet_getbytes(argv, 0);
uint8_t *buf = janet_string_begin(view.len);
@ -255,7 +280,9 @@ static Janet cfun_string_asciiupper(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(buf));
}
static Janet cfun_string_reverse(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_reverse,
"(string/reverse str)",
"Returns a string that is the reversed version of str.") {
janet_fixarity(argc, 1);
JanetByteView view = janet_getbytes(argv, 0);
uint8_t *buf = janet_string_begin(view.len);
@ -279,7 +306,11 @@ static void findsetup(int32_t argc, Janet *argv, struct kmp_state *s, int32_t ex
s->i = start;
}
static Janet cfun_string_find(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_find,
"(string/find patt str &opt start-index)",
"Searches for the first instance of pattern patt in string "
"str. Returns the index of the first character in patt if found, "
"otherwise returns nil.") {
int32_t result;
struct kmp_state state;
findsetup(argc, argv, &state, 0);
@ -290,7 +321,9 @@ static Janet cfun_string_find(int32_t argc, Janet *argv) {
: janet_wrap_integer(result);
}
static Janet cfun_string_hasprefix(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_hasprefix,
"(string/has-prefix? pfx str)",
"Tests whether str starts with pfx.") {
janet_fixarity(argc, 2);
JanetByteView prefix = janet_getbytes(argv, 0);
JanetByteView str = janet_getbytes(argv, 1);
@ -299,7 +332,9 @@ static Janet cfun_string_hasprefix(int32_t argc, Janet *argv) {
: janet_wrap_boolean(memcmp(prefix.bytes, str.bytes, prefix.len) == 0);
}
static Janet cfun_string_hassuffix(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_hassuffix,
"(string/has-suffix? sfx str)",
"Tests whether str ends with sfx.") {
janet_fixarity(argc, 2);
JanetByteView suffix = janet_getbytes(argv, 0);
JanetByteView str = janet_getbytes(argv, 1);
@ -310,7 +345,12 @@ static Janet cfun_string_hassuffix(int32_t argc, Janet *argv) {
suffix.len) == 0);
}
static Janet cfun_string_findall(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_findall,
"(string/find-all patt str &opt start-index)",
"Searches for all instances of pattern patt in string "
"str. Returns an array of all indices of found patterns. Overlapping "
"instances of the pattern are counted individually, meaning a byte in str "
"may contribute to multiple found patterns.") {
int32_t result;
struct kmp_state state;
findsetup(argc, argv, &state, 0);
@ -344,7 +384,10 @@ static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) {
s->substlen = subst.len;
}
static Janet cfun_string_replace(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_replace,
"(string/replace patt subst str)",
"Replace the first occurrence of patt with subst in the string str. "
"Will return the new string if patt is found, otherwise returns str.") {
int32_t result;
struct replace_state s;
uint8_t *buf;
@ -364,7 +407,11 @@ static Janet cfun_string_replace(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(buf));
}
static Janet cfun_string_replaceall(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_replaceall,
"(string/replace-all patt subst str)",
"Replace all instances of patt with subst in the string str. Overlapping "
"matches will not be counted, only the first match in such a span will be replaced. "
"Will return the new string if patt is found, otherwise returns str.") {
int32_t result;
struct replace_state s;
JanetBuffer b;
@ -384,7 +431,13 @@ static Janet cfun_string_replaceall(int32_t argc, Janet *argv) {
return janet_wrap_string(ret);
}
static Janet cfun_string_split(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_split,
"(string/split delim str &opt start limit)",
"Splits a string str with delimiter delim and returns an array of "
"substrings. The substrings will not contain the delimiter delim. If delim "
"is not found, the returned array will have one element. Will start searching "
"for delim at the index start (if provided), and return up to a maximum "
"of limit results (if provided).") {
int32_t result;
JanetArray *array;
struct kmp_state state;
@ -406,7 +459,11 @@ static Janet cfun_string_split(int32_t argc, Janet *argv) {
return janet_wrap_array(array);
}
static Janet cfun_string_checkset(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_checkset,
"(string/check-set set str)",
"Checks that the string str only contains bytes that appear in the string set. "
"Returns true if all bytes in str appear in set, false if some bytes in str do "
"not appear in set.") {
uint32_t bitset[8] = {0, 0, 0, 0, 0, 0, 0, 0};
janet_fixarity(argc, 2);
JanetByteView set = janet_getbytes(argv, 0);
@ -428,7 +485,10 @@ static Janet cfun_string_checkset(int32_t argc, Janet *argv) {
return janet_wrap_true();
}
static Janet cfun_string_join(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_join,
"(string/join parts &opt sep)",
"Joins an array of strings into one string, optionally separated by "
"a separator string sep.") {
janet_arity(argc, 1, 2);
JanetView parts = janet_getindexed(argv, 0);
JanetByteView joiner;
@ -468,7 +528,10 @@ static Janet cfun_string_join(int32_t argc, Janet *argv) {
return janet_wrap_string(janet_string_end(buf));
}
static Janet cfun_string_format(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_format,
"(string/format format & values)",
"Similar to snprintf, but specialized for operating with Janet values. Returns "
"a new string.") {
janet_arity(argc, 1, -1);
JanetBuffer *buffer = janet_buffer(0);
const char *strfrmt = (const char *) janet_getstring(argv, 0);
@ -508,7 +571,10 @@ static void trim_help_args(int32_t argc, Janet *argv, JanetByteView *str, JanetB
}
}
static Janet cfun_string_trim(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_trim,
"(string/trim str &opt set)",
"Trim leading and trailing whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.") {
JanetByteView str, set;
trim_help_args(argc, argv, &str, &set);
int32_t left_edge = trim_help_leftedge(str, set);
@ -518,163 +584,52 @@ static Janet cfun_string_trim(int32_t argc, Janet *argv) {
return janet_stringv(str.bytes + left_edge, right_edge - left_edge);
}
static Janet cfun_string_triml(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_triml,
"(string/triml str &opt set)",
"Trim leading whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.") {
JanetByteView str, set;
trim_help_args(argc, argv, &str, &set);
int32_t left_edge = trim_help_leftedge(str, set);
return janet_stringv(str.bytes + left_edge, str.len - left_edge);
}
static Janet cfun_string_trimr(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_string_trimr,
"(string/trimr str &opt set)",
"Trim trailing whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.") {
JanetByteView str, set;
trim_help_args(argc, argv, &str, &set);
int32_t right_edge = trim_help_rightedge(str, set);
return janet_stringv(str.bytes, right_edge);
}
static const JanetReg string_cfuns[] = {
{
"string/slice", cfun_string_slice,
JDOC("(string/slice bytes &opt start end)\n\n"
"Returns a substring from a byte sequence. The substring is from "
"index start inclusive to index end exclusive. All indexing "
"is from 0. 'start' and 'end' can also be negative to indicate indexing "
"from the end of the string. Note that index -1 is synonymous with "
"index (length bytes) to allow a full negative slice range. ")
},
{
"keyword/slice", cfun_keyword_slice,
JDOC("(keyword/slice bytes &opt start end)\n\n"
"Same a string/slice, but returns a keyword.")
},
{
"symbol/slice", cfun_symbol_slice,
JDOC("(symbol/slice bytes &opt start end)\n\n"
"Same a string/slice, but returns a symbol.")
},
{
"string/repeat", cfun_string_repeat,
JDOC("(string/repeat bytes n)\n\n"
"Returns a string that is n copies of bytes concatenated.")
},
{
"string/bytes", cfun_string_bytes,
JDOC("(string/bytes str)\n\n"
"Returns an array of integers that are the byte values of the string.")
},
{
"string/from-bytes", cfun_string_frombytes,
JDOC("(string/from-bytes & byte-vals)\n\n"
"Creates a string from integer parameters with byte values. All integers "
"will be coerced to the range of 1 byte 0-255.")
},
{
"string/ascii-lower", cfun_string_asciilower,
JDOC("(string/ascii-lower str)\n\n"
"Returns a new string where all bytes are replaced with the "
"lowercase version of themselves in ASCII. Does only a very simple "
"case check, meaning no unicode support.")
},
{
"string/ascii-upper", cfun_string_asciiupper,
JDOC("(string/ascii-upper str)\n\n"
"Returns a new string where all bytes are replaced with the "
"uppercase version of themselves in ASCII. Does only a very simple "
"case check, meaning no unicode support.")
},
{
"string/reverse", cfun_string_reverse,
JDOC("(string/reverse str)\n\n"
"Returns a string that is the reversed version of str.")
},
{
"string/find", cfun_string_find,
JDOC("(string/find patt str &opt start-index)\n\n"
"Searches for the first instance of pattern patt in string "
"str. Returns the index of the first character in patt if found, "
"otherwise returns nil.")
},
{
"string/find-all", cfun_string_findall,
JDOC("(string/find-all patt str &opt start-index)\n\n"
"Searches for all instances of pattern patt in string "
"str. Returns an array of all indices of found patterns. Overlapping "
"instances of the pattern are counted individually, meaning a byte in str "
"may contribute to multiple found patterns.")
},
{
"string/has-prefix?", cfun_string_hasprefix,
JDOC("(string/has-prefix? pfx str)\n\n"
"Tests whether str starts with pfx.")
},
{
"string/has-suffix?", cfun_string_hassuffix,
JDOC("(string/has-suffix? sfx str)\n\n"
"Tests whether str ends with sfx.")
},
{
"string/replace", cfun_string_replace,
JDOC("(string/replace patt subst str)\n\n"
"Replace the first occurrence of patt with subst in the string str. "
"Will return the new string if patt is found, otherwise returns str.")
},
{
"string/replace-all", cfun_string_replaceall,
JDOC("(string/replace-all patt subst str)\n\n"
"Replace all instances of patt with subst in the string str. Overlapping "
"matches will not be counted, only the first match in such a span will be replaced. "
"Will return the new string if patt is found, otherwise returns str.")
},
{
"string/split", cfun_string_split,
JDOC("(string/split delim str &opt start limit)\n\n"
"Splits a string str with delimiter delim and returns an array of "
"substrings. The substrings will not contain the delimiter delim. If delim "
"is not found, the returned array will have one element. Will start searching "
"for delim at the index start (if provided), and return up to a maximum "
"of limit results (if provided).")
},
{
"string/check-set", cfun_string_checkset,
JDOC("(string/check-set set str)\n\n"
"Checks that the string str only contains bytes that appear in the string set. "
"Returns true if all bytes in str appear in set, false if some bytes in str do "
"not appear in set.")
},
{
"string/join", cfun_string_join,
JDOC("(string/join parts &opt sep)\n\n"
"Joins an array of strings into one string, optionally separated by "
"a separator string sep.")
},
{
"string/format", cfun_string_format,
JDOC("(string/format format & values)\n\n"
"Similar to snprintf, but specialized for operating with Janet values. Returns "
"a new string.")
},
{
"string/trim", cfun_string_trim,
JDOC("(string/trim str &opt set)\n\n"
"Trim leading and trailing whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.")
},
{
"string/triml", cfun_string_triml,
JDOC("(string/triml str &opt set)\n\n"
"Trim leading whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.")
},
{
"string/trimr", cfun_string_trimr,
JDOC("(string/trimr str &opt set)\n\n"
"Trim trailing whitespace from a byte sequence. If the argument "
"set is provided, consider only characters in set to be whitespace.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_string(JanetTable *env) {
janet_core_cfuns(env, NULL, string_cfuns);
JanetRegExt string_cfuns[] = {
JANET_CORE_REG("string/slice", cfun_string_slice),
JANET_CORE_REG("keyword/slice", cfun_keyword_slice),
JANET_CORE_REG("symbol/slice", cfun_symbol_slice),
JANET_CORE_REG("string/repeat", cfun_string_repeat),
JANET_CORE_REG("string/bytes", cfun_string_bytes),
JANET_CORE_REG("string/from-bytes", cfun_string_frombytes),
JANET_CORE_REG("string/ascii-lower", cfun_string_asciilower),
JANET_CORE_REG("string/ascii-upper", cfun_string_asciiupper),
JANET_CORE_REG("string/reverse", cfun_string_reverse),
JANET_CORE_REG("string/find", cfun_string_find),
JANET_CORE_REG("string/find-all", cfun_string_findall),
JANET_CORE_REG("string/has-prefix?", cfun_string_hasprefix),
JANET_CORE_REG("string/has-suffix?", cfun_string_hassuffix),
JANET_CORE_REG("string/replace", cfun_string_replace),
JANET_CORE_REG("string/replace-all", cfun_string_replaceall),
JANET_CORE_REG("string/split", cfun_string_split),
JANET_CORE_REG("string/check-set", cfun_string_checkset),
JANET_CORE_REG("string/join", cfun_string_join),
JANET_CORE_REG("string/format", cfun_string_format),
JANET_CORE_REG("string/trim", cfun_string_trim),
JANET_CORE_REG("string/triml", cfun_string_triml),
JANET_CORE_REG("string/trimr", cfun_string_trimr),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, string_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -246,15 +246,15 @@ static double convert(
}
/* Scan a real (double) from a string. If the string cannot be converted into
* and integer, set *err to 1 and return 0. */
int janet_scan_number(
* and integer, return 0. */
int janet_scan_number_base(
const uint8_t *str,
int32_t len,
int32_t base,
double *out) {
const uint8_t *end = str + len;
int seenadigit = 0;
int ex = 0;
int base = 10;
int seenpoint = 0;
int foundexp = 0;
int neg = 0;
@ -278,21 +278,28 @@ int janet_scan_number(
}
/* Check for leading 0x or digit digit r */
if (str + 1 < end && str[0] == '0' && str[1] == 'x') {
base = 16;
str += 2;
} else if (str + 1 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] == 'r') {
base = str[0] - '0';
str += 2;
} else if (str + 2 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] >= '0' && str[1] <= '9' &&
str[2] == 'r') {
base = 10 * (str[0] - '0') + (str[1] - '0');
if (base < 2 || base > 36) goto error;
str += 3;
if (base == 0) {
if (str + 1 < end && str[0] == '0' && str[1] == 'x') {
base = 16;
str += 2;
} else if (str + 1 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] == 'r') {
base = str[0] - '0';
str += 2;
} else if (str + 2 < end &&
str[0] >= '0' && str[0] <= '9' &&
str[1] >= '0' && str[1] <= '9' &&
str[2] == 'r') {
base = 10 * (str[0] - '0') + (str[1] - '0');
if (base < 2 || base > 36) goto error;
str += 3;
}
}
/* If still base is 0, set to default (10) */
if (base == 0) {
base = 10;
}
/* Skip leading zeros */
@ -376,6 +383,13 @@ error:
return 1;
}
int janet_scan_number(
const uint8_t *str,
int32_t len,
double *out) {
return janet_scan_number_base(str, len, 0, out);
}
#ifdef JANET_INT_TYPES
static int scan_uint64(

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -167,17 +167,12 @@ Janet janet_struct_rawget(const JanetKV *st, Janet key) {
/* Get an item from a struct */
Janet janet_struct_get(const JanetKV *st, Janet key) {
const JanetKV *kv = janet_struct_find(st, key);
if (NULL != kv)
return kv->value;
/* Check prototypes */
{
int i = JANET_MAX_PROTO_DEPTH;
for (st = janet_struct_proto(st); st && i; st = janet_struct_proto(st), --i) {
kv = janet_struct_find(st, key);
if (NULL != kv)
return kv->value;
}
int i = JANET_MAX_PROTO_DEPTH;
for (; st && i; --i) {
const JanetKV *kv = janet_struct_find(st, key);
if (NULL != kv)
return kv->value;
st = janet_struct_proto(st);
}
return janet_wrap_nil();
}
@ -216,20 +211,25 @@ JanetTable *janet_struct_to_table(const JanetKV *st) {
/* C Functions */
static Janet cfun_struct_with_proto(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_struct_with_proto,
"(struct/with-proto proto & kvs)",
"Create a structure, as with the usual struct constructor but set the "
"struct prototype as well.") {
janet_arity(argc, 1, -1);
JanetStruct proto = janet_optstruct(argv, argc, 0, NULL);
if (!(argc & 1))
janet_panic("expected odd number of arguments");
JanetKV *st = janet_struct_begin(argc / 2);
janet_struct_proto(st) = proto;
for (int32_t i = 1; i < argc; i += 2) {
janet_struct_put(st, argv[i], argv[i + 1]);
}
janet_struct_proto(st) = proto;
return janet_wrap_struct(janet_struct_end(st));
}
static Janet cfun_struct_getproto(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_struct_getproto,
"(struct/getproto st)",
"Return the prototype of a struct, or nil if it doesn't have one.") {
janet_fixarity(argc, 1);
JanetStruct st = janet_getstruct(argv, 0);
return janet_struct_proto(st)
@ -237,7 +237,10 @@ static Janet cfun_struct_getproto(int32_t argc, Janet *argv) {
: janet_wrap_nil();
}
static Janet cfun_struct_flatten(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_struct_flatten,
"(struct/proto-flatten st)",
"Convert a struct with prototypes to a struct with no prototypes by merging "
"all key value pairs from recursive prototypes into one new struct.") {
janet_fixarity(argc, 1);
JanetStruct st = janet_getstruct(argv, 0);
@ -267,7 +270,10 @@ static Janet cfun_struct_flatten(int32_t argc, Janet *argv) {
return janet_wrap_struct(janet_struct_end(accum));
}
static Janet cfun_struct_to_table(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_struct_to_table,
"(struct/to-table st &opt recursive)",
"Convert a struct to a table. If recursive is true, also convert the "
"table's prototypes into the new struct's prototypes as well.") {
janet_arity(argc, 1, 2);
JanetStruct st = janet_getstruct(argv, 0);
int recursive = argc > 1 && janet_truthy(argv[1]);
@ -295,34 +301,14 @@ static Janet cfun_struct_to_table(int32_t argc, Janet *argv) {
return janet_wrap_table(tab);
}
static const JanetReg struct_cfuns[] = {
{
"struct/with-proto", cfun_struct_with_proto,
JDOC("(struct/with-proto proto & kvs)\n\n"
"Create a structure, as with the usual struct constructor but set the "
"struct prototype as well.")
},
{
"struct/getproto", cfun_struct_getproto,
JDOC("(struct/getproto st)\n\n"
"Get the prototype of a struct, or nil if it doesn't have one.")
},
{
"struct/proto-flatten", cfun_struct_flatten,
JDOC("(struct/proto-flatten st)\n\n"
"Convert a struct with prototypes to a struct with no prototypes by merging "
"all key value pairs from recursive prototypes into one new struct.")
},
{
"struct/to-table", cfun_struct_to_table,
JDOC("(struct/to-table st &opt recursive)\n\n"
"Convert a struct to a table. If recursive is true, also convert the "
"table's prototypes into the new struct's prototypes as well.")
},
{NULL, NULL, NULL}
};
/* Load the struct module */
void janet_lib_struct(JanetTable *env) {
janet_core_cfuns(env, NULL, struct_cfuns);
JanetRegExt struct_cfuns[] = {
JANET_CORE_REG("struct/with-proto", cfun_struct_with_proto),
JANET_CORE_REG("struct/getproto", cfun_struct_getproto),
JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten),
JANET_CORE_REG("struct/to-table", cfun_struct_to_table),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, struct_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -36,30 +36,26 @@
#include <string.h>
/* Cache state */
JANET_THREAD_LOCAL const uint8_t **janet_vm_cache = NULL;
JANET_THREAD_LOCAL uint32_t janet_vm_cache_capacity = 0;
JANET_THREAD_LOCAL uint32_t janet_vm_cache_count = 0;
JANET_THREAD_LOCAL uint32_t janet_vm_cache_deleted = 0;
/* Initialize the cache (allocate cache memory) */
void janet_symcache_init() {
janet_vm_cache_capacity = 1024;
janet_vm_cache = janet_calloc(1, (size_t) janet_vm_cache_capacity * sizeof(const uint8_t *));
if (NULL == janet_vm_cache) {
janet_vm.cache_capacity = 1024;
janet_vm.cache = janet_calloc(1, (size_t) janet_vm.cache_capacity * sizeof(const uint8_t *));
if (NULL == janet_vm.cache) {
JANET_OUT_OF_MEMORY;
}
janet_vm_cache_count = 0;
janet_vm_cache_deleted = 0;
memset(&janet_vm.gensym_counter, '0', sizeof(janet_vm.gensym_counter));
janet_vm.gensym_counter[0] = '_';
janet_vm.cache_count = 0;
janet_vm.cache_deleted = 0;
}
/* Deinitialize the cache (free the cache memory) */
void janet_symcache_deinit() {
janet_free((void *)janet_vm_cache);
janet_vm_cache = NULL;
janet_vm_cache_capacity = 0;
janet_vm_cache_count = 0;
janet_vm_cache_deleted = 0;
janet_free((void *)janet_vm.cache);
janet_vm.cache = NULL;
janet_vm.cache_capacity = 0;
janet_vm.cache_count = 0;
janet_vm.cache_deleted = 0;
}
/* Mark an entry in the table as deleted. */
@ -79,24 +75,24 @@ static const uint8_t **janet_symcache_findmem(
/* We will search two ranges - index to the end,
* and 0 to the index. */
index = (uint32_t)hash & (janet_vm_cache_capacity - 1);
index = (uint32_t)hash & (janet_vm.cache_capacity - 1);
bounds[0] = index;
bounds[1] = janet_vm_cache_capacity;
bounds[1] = janet_vm.cache_capacity;
bounds[2] = 0;
bounds[3] = index;
for (j = 0; j < 4; j += 2)
for (i = bounds[j]; i < bounds[j + 1]; ++i) {
const uint8_t *test = janet_vm_cache[i];
const uint8_t *test = janet_vm.cache[i];
/* Check empty spots */
if (NULL == test) {
if (NULL == firstEmpty)
firstEmpty = janet_vm_cache + i;
firstEmpty = janet_vm.cache + i;
goto notfound;
}
/* Check for marked deleted */
if (JANET_SYMCACHE_DELETED == test) {
if (firstEmpty == NULL)
firstEmpty = janet_vm_cache + i;
firstEmpty = janet_vm.cache + i;
continue;
}
if (janet_string_equalconst(test, str, len, hash)) {
@ -104,10 +100,10 @@ static const uint8_t **janet_symcache_findmem(
*success = 1;
if (firstEmpty != NULL) {
*firstEmpty = test;
janet_vm_cache[i] = JANET_SYMCACHE_DELETED;
janet_vm.cache[i] = JANET_SYMCACHE_DELETED;
return firstEmpty;
}
return janet_vm_cache + i;
return janet_vm.cache + i;
}
}
notfound:
@ -121,15 +117,15 @@ notfound:
/* Resize the cache. */
static void janet_cache_resize(uint32_t newCapacity) {
uint32_t i, oldCapacity;
const uint8_t **oldCache = janet_vm_cache;
const uint8_t **oldCache = janet_vm.cache;
const uint8_t **newCache = janet_calloc(1, (size_t) newCapacity * sizeof(const uint8_t *));
if (newCache == NULL) {
JANET_OUT_OF_MEMORY;
}
oldCapacity = janet_vm_cache_capacity;
janet_vm_cache = newCache;
janet_vm_cache_capacity = newCapacity;
janet_vm_cache_deleted = 0;
oldCapacity = janet_vm.cache_capacity;
janet_vm.cache = newCache;
janet_vm.cache_capacity = newCapacity;
janet_vm.cache_deleted = 0;
/* Add all of the old cache entries back */
for (i = 0; i < oldCapacity; ++i) {
int status;
@ -150,13 +146,13 @@ static void janet_cache_resize(uint32_t newCapacity) {
/* Add an item to the cache */
static void janet_symcache_put(const uint8_t *x, const uint8_t **bucket) {
if ((janet_vm_cache_count + janet_vm_cache_deleted) * 2 > janet_vm_cache_capacity) {
if ((janet_vm.cache_count + janet_vm.cache_deleted) * 2 > janet_vm.cache_capacity) {
int status;
janet_cache_resize(janet_tablen((2 * janet_vm_cache_count + 1)));
janet_cache_resize(janet_tablen((2 * janet_vm.cache_count + 1)));
bucket = janet_symcache_find(x, &status);
}
/* Add x to the cache */
janet_vm_cache_count++;
janet_vm.cache_count++;
*bucket = x;
}
@ -165,8 +161,8 @@ void janet_symbol_deinit(const uint8_t *sym) {
int status = 0;
const uint8_t **bucket = janet_symcache_find(sym, &status);
if (status) {
janet_vm_cache_count--;
janet_vm_cache_deleted++;
janet_vm.cache_count--;
janet_vm.cache_deleted++;
*bucket = JANET_SYMCACHE_DELETED;
}
}
@ -194,22 +190,19 @@ const uint8_t *janet_csymbol(const char *cstr) {
return janet_symbol((const uint8_t *)cstr, (int32_t) strlen(cstr));
}
/* Store counter for genysm to avoid quadratic behavior */
JANET_THREAD_LOCAL uint8_t gensym_counter[8] = {'_', '0', '0', '0', '0', '0', '0', 0};
/* Increment the gensym buffer */
static void inc_gensym(void) {
for (int i = sizeof(gensym_counter) - 2; i; i--) {
if (gensym_counter[i] == '9') {
gensym_counter[i] = 'a';
for (int i = sizeof(janet_vm.gensym_counter) - 2; i; i--) {
if (janet_vm.gensym_counter[i] == '9') {
janet_vm.gensym_counter[i] = 'a';
break;
} else if (gensym_counter[i] == 'z') {
gensym_counter[i] = 'A';
} else if (janet_vm.gensym_counter[i] == 'z') {
janet_vm.gensym_counter[i] = 'A';
break;
} else if (gensym_counter[i] == 'Z') {
gensym_counter[i] = '0';
} else if (janet_vm.gensym_counter[i] == 'Z') {
janet_vm.gensym_counter[i] = '0';
} else {
gensym_counter[i]++;
janet_vm.gensym_counter[i]++;
break;
}
}
@ -227,19 +220,19 @@ const uint8_t *janet_symbol_gen(void) {
* is enough for resolving collisions. */
do {
hash = janet_string_calchash(
gensym_counter,
sizeof(gensym_counter) - 1);
janet_vm.gensym_counter,
sizeof(janet_vm.gensym_counter) - 1);
bucket = janet_symcache_findmem(
gensym_counter,
sizeof(gensym_counter) - 1,
janet_vm.gensym_counter,
sizeof(janet_vm.gensym_counter) - 1,
hash,
&status);
} while (status && (inc_gensym(), 1));
JanetStringHead *head = janet_gcalloc(JANET_MEMORY_SYMBOL, sizeof(JanetStringHead) + sizeof(gensym_counter));
head->length = sizeof(gensym_counter) - 1;
JanetStringHead *head = janet_gcalloc(JANET_MEMORY_SYMBOL, sizeof(JanetStringHead) + sizeof(janet_vm.gensym_counter));
head->length = sizeof(janet_vm.gensym_counter) - 1;
head->hash = hash;
sym = (uint8_t *)(head->data);
memcpy(sym, gensym_counter, sizeof(gensym_counter));
memcpy(sym, janet_vm.gensym_counter, sizeof(janet_vm.gensym_counter));
janet_symcache_put((const uint8_t *)sym, bucket);
return (const uint8_t *)sym;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -67,14 +67,23 @@ static JanetTable *janet_table_init_impl(JanetTable *table, int32_t capacity, in
return table;
}
/* Initialize a table */
/* Initialize a table (for use withs scratch memory) */
JanetTable *janet_table_init(JanetTable *table, int32_t capacity) {
return janet_table_init_impl(table, capacity, 1);
}
/* Initialize a table without using scratch memory */
JanetTable *janet_table_init_raw(JanetTable *table, int32_t capacity) {
return janet_table_init_impl(table, capacity, 0);
}
/* Deinitialize a table */
void janet_table_deinit(JanetTable *table) {
janet_sfree(table->data);
if (table->gc.flags & JANET_TABLE_FLAG_STACK) {
janet_sfree(table->data);
} else {
janet_free(table->data);
}
}
/* Create a new table */
@ -300,13 +309,21 @@ JanetTable *janet_table_proto_flatten(JanetTable *t) {
/* C Functions */
static Janet cfun_table_new(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_new,
"(table/new capacity)",
"Creates a new empty table with pre-allocated memory "
"for capacity entries. This means that if one knows the number of "
"entries going to go in a table on creation, extra memory allocation "
"can be avoided. Returns the new table.") {
janet_fixarity(argc, 1);
int32_t cap = janet_getinteger(argv, 0);
return janet_wrap_table(janet_table(cap));
}
static Janet cfun_table_getproto(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_getproto,
"(table/getproto tab)",
"Get the prototype table of a table. Returns nil if a table "
"has no prototype, otherwise returns the prototype.") {
janet_fixarity(argc, 1);
JanetTable *t = janet_gettable(argv, 0);
return t->proto
@ -314,7 +331,9 @@ static Janet cfun_table_getproto(int32_t argc, Janet *argv) {
: janet_wrap_nil();
}
static Janet cfun_table_setproto(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_setproto,
"(table/setproto tab proto)",
"Set the prototype of a table. Returns the original table tab.") {
janet_fixarity(argc, 2);
JanetTable *table = janet_gettable(argv, 0);
JanetTable *proto = NULL;
@ -325,78 +344,63 @@ static Janet cfun_table_setproto(int32_t argc, Janet *argv) {
return argv[0];
}
static Janet cfun_table_tostruct(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_tostruct,
"(table/to-struct tab)",
"Convert a table to a struct. Returns a new struct. This function "
"does not take into account prototype tables.") {
janet_fixarity(argc, 1);
JanetTable *t = janet_gettable(argv, 0);
return janet_wrap_struct(janet_table_to_struct(t));
}
static Janet cfun_table_rawget(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_rawget,
"(table/rawget tab key)",
"Gets a value from a table without looking at the prototype table. "
"If a table tab does not contain t directly, the function will return "
"nil without checking the prototype. Returns the value in the table.") {
janet_fixarity(argc, 2);
JanetTable *table = janet_gettable(argv, 0);
return janet_table_rawget(table, argv[1]);
}
static Janet cfun_table_clone(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_clone,
"(table/clone tab)",
"Create a copy of a table. Updates to the new table will not change the old table, "
"and vice versa.") {
janet_fixarity(argc, 1);
JanetTable *table = janet_gettable(argv, 0);
return janet_wrap_table(janet_table_clone(table));
}
static Janet cfun_table_proto_flatten(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_table_clear,
"(table/clear tab)",
"Remove all key-value pairs in a table and return the modified table `tab`.") {
janet_fixarity(argc, 1);
JanetTable *table = janet_gettable(argv, 0);
janet_table_clear(table);
return janet_wrap_table(table);
}
JANET_CORE_FN(cfun_table_proto_flatten,
"(table/proto-flatten tab)",
"Create a new table that is the result of merging all prototypes into a new table.") {
janet_fixarity(argc, 1);
JanetTable *table = janet_gettable(argv, 0);
return janet_wrap_table(janet_table_proto_flatten(table));
}
static const JanetReg table_cfuns[] = {
{
"table/new", cfun_table_new,
JDOC("(table/new capacity)\n\n"
"Creates a new empty table with pre-allocated memory "
"for capacity entries. This means that if one knows the number of "
"entries going to go in a table on creation, extra memory allocation "
"can be avoided. Returns the new table.")
},
{
"table/to-struct", cfun_table_tostruct,
JDOC("(table/to-struct tab)\n\n"
"Convert a table to a struct. Returns a new struct. This function "
"does not take into account prototype tables.")
},
{
"table/getproto", cfun_table_getproto,
JDOC("(table/getproto tab)\n\n"
"Get the prototype table of a table. Returns nil if a table "
"has no prototype, otherwise returns the prototype.")
},
{
"table/setproto", cfun_table_setproto,
JDOC("(table/setproto tab proto)\n\n"
"Set the prototype of a table. Returns the original table tab.")
},
{
"table/rawget", cfun_table_rawget,
JDOC("(table/rawget tab key)\n\n"
"Gets a value from a table without looking at the prototype table. "
"If a table tab does not contain t directly, the function will return "
"nil without checking the prototype. Returns the value in the table.")
},
{
"table/clone", cfun_table_clone,
JDOC("(table/clone tab)\n\n"
"Create a copy of a table. Updates to the new table will not change the old table, "
"and vice versa.")
},
{
"table/proto-flatten", cfun_table_proto_flatten,
JDOC("(table/proto-flatten tab)\n\n"
"Create a new table that is the result of merging all prototypes into a new table.")
},
{NULL, NULL, NULL}
};
/* Load the table module */
void janet_lib_table(JanetTable *env) {
janet_core_cfuns(env, NULL, table_cfuns);
JanetRegExt table_cfuns[] = {
JANET_CORE_REG("table/new", cfun_table_new),
JANET_CORE_REG("table/to-struct", cfun_table_tostruct),
JANET_CORE_REG("table/getproto", cfun_table_getproto),
JANET_CORE_REG("table/setproto", cfun_table_setproto),
JANET_CORE_REG("table/rawget", cfun_table_rawget),
JANET_CORE_REG("table/clone", cfun_table_clone),
JANET_CORE_REG("table/clear", cfun_table_clear),
JANET_CORE_REG("table/proto-flatten", cfun_table_proto_flatten),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, table_cfuns);
}

View File

@ -1,781 +0,0 @@
/*
* Copyright (c) 2020 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include "gc.h"
#include "util.h"
#include "state.h"
#endif
#ifdef JANET_THREADS
#include <math.h>
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <setjmp.h>
#include <time.h>
#include <pthread.h>
#endif
/* typedefed in janet.h */
struct JanetMailbox {
/* Synchronization */
#ifdef JANET_WINDOWS
CRITICAL_SECTION lock;
CONDITION_VARIABLE cond;
#else
pthread_mutex_t lock;
pthread_cond_t cond;
#endif
/* Memory management - reference counting */
int refCount;
int closed;
/* Store messages */
uint16_t messageCapacity;
uint16_t messageCount;
uint16_t messageFirst;
uint16_t messageNext;
/* Buffers to store messages. These buffers are manually allocated, so
* are not owned by any thread's GC. */
JanetBuffer messages[];
};
#define JANET_THREAD_HEAVYWEIGHT 0x1
#define JANET_THREAD_ABSTRACTS 0x2
#define JANET_THREAD_CFUNCTIONS 0x4
static const char janet_thread_flags[] = "hac";
typedef struct {
JanetMailbox *original;
JanetMailbox *newbox;
uint64_t flags;
} JanetMailboxPair;
static JANET_THREAD_LOCAL JanetMailbox *janet_vm_mailbox = NULL;
static JANET_THREAD_LOCAL JanetThread *janet_vm_thread_current = NULL;
static JANET_THREAD_LOCAL JanetTable *janet_vm_thread_decode = NULL;
static JanetTable *janet_thread_get_decode(void) {
if (janet_vm_thread_decode == NULL) {
janet_vm_thread_decode = janet_get_core_table("load-image-dict");
if (NULL == janet_vm_thread_decode) {
janet_vm_thread_decode = janet_table(0);
}
janet_gcroot(janet_wrap_table(janet_vm_thread_decode));
}
return janet_vm_thread_decode;
}
static JanetMailbox *janet_mailbox_create(int refCount, uint16_t capacity) {
JanetMailbox *mailbox = janet_malloc(sizeof(JanetMailbox) + sizeof(JanetBuffer) * (size_t) capacity);
if (NULL == mailbox) {
JANET_OUT_OF_MEMORY;
}
#ifdef JANET_WINDOWS
InitializeCriticalSection(&mailbox->lock);
InitializeConditionVariable(&mailbox->cond);
#else
pthread_mutex_init(&mailbox->lock, NULL);
pthread_cond_init(&mailbox->cond, NULL);
#endif
mailbox->refCount = refCount;
mailbox->closed = 0;
mailbox->messageCount = 0;
mailbox->messageCapacity = capacity;
mailbox->messageFirst = 0;
mailbox->messageNext = 0;
for (uint16_t i = 0; i < capacity; i++) {
janet_buffer_init(mailbox->messages + i, 0);
}
return mailbox;
}
static void janet_mailbox_destroy(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
DeleteCriticalSection(&mailbox->lock);
#else
pthread_mutex_destroy(&mailbox->lock);
pthread_cond_destroy(&mailbox->cond);
#endif
for (uint16_t i = 0; i < mailbox->messageCapacity; i++) {
janet_buffer_deinit(mailbox->messages + i);
}
janet_free(mailbox);
}
static void janet_mailbox_lock(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
EnterCriticalSection(&mailbox->lock);
#else
pthread_mutex_lock(&mailbox->lock);
#endif
}
static void janet_mailbox_unlock(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
LeaveCriticalSection(&mailbox->lock);
#else
pthread_mutex_unlock(&mailbox->lock);
#endif
}
/* Assumes you have the mailbox lock already */
static void janet_mailbox_ref_with_lock(JanetMailbox *mailbox, int delta) {
mailbox->refCount += delta;
if (mailbox->refCount <= 0) {
janet_mailbox_unlock(mailbox);
janet_mailbox_destroy(mailbox);
} else {
janet_mailbox_unlock(mailbox);
}
}
static void janet_mailbox_ref(JanetMailbox *mailbox, int delta) {
janet_mailbox_lock(mailbox);
janet_mailbox_ref_with_lock(mailbox, delta);
}
static void janet_close_thread(JanetThread *thread) {
if (thread->mailbox) {
janet_mailbox_ref(thread->mailbox, -1);
thread->mailbox = NULL;
}
}
static int thread_gc(void *p, size_t size) {
(void) size;
JanetThread *thread = (JanetThread *)p;
janet_close_thread(thread);
return 0;
}
static int thread_mark(void *p, size_t size) {
(void) size;
JanetThread *thread = (JanetThread *)p;
if (thread->encode) {
janet_mark(janet_wrap_table(thread->encode));
}
return 0;
}
static JanetMailboxPair *make_mailbox_pair(JanetMailbox *original, uint64_t flags) {
JanetMailboxPair *pair = janet_malloc(sizeof(JanetMailboxPair));
if (NULL == pair) {
JANET_OUT_OF_MEMORY;
}
pair->original = original;
janet_mailbox_ref(original, 1);
pair->newbox = janet_mailbox_create(1, 16);
pair->flags = flags;
return pair;
}
static void destroy_mailbox_pair(JanetMailboxPair *pair) {
janet_mailbox_ref(pair->original, -1);
janet_mailbox_ref(pair->newbox, -1);
janet_free(pair);
}
/* Abstract waiting for timeout across windows/posix */
typedef struct {
int timedwait;
int nowait;
#ifdef JANET_WINDOWS
DWORD interval;
DWORD ticksLeft;
#else
struct timespec ts;
#endif
} JanetWaiter;
static void janet_waiter_init(JanetWaiter *waiter, double sec) {
waiter->timedwait = 0;
waiter->nowait = 0;
if (sec <= 0.0 || isnan(sec)) {
waiter->nowait = 1;
return;
}
waiter->timedwait = sec > 0.0 && !isinf(sec);
/* Set maximum wait time to 30 days */
if (sec > (60.0 * 60.0 * 24.0 * 30.0)) {
sec = 60.0 * 60.0 * 24.0 * 30.0;
}
#ifdef JANET_WINDOWS
if (waiter->timedwait) {
waiter->ticksLeft = waiter->interval = (DWORD) floor(1000.0 * sec);
}
#else
if (waiter->timedwait) {
/* N seconds -> timespec of (now + sec) */
struct timespec now;
janet_gettime(&now);
time_t tvsec = (time_t) floor(sec);
long tvnsec = (long) floor(1000000000.0 * (sec - ((double) tvsec)));
tvsec += now.tv_sec;
tvnsec += now.tv_nsec;
if (tvnsec >= 1000000000L) {
tvnsec -= 1000000000L;
tvsec += 1;
}
waiter->ts.tv_sec = tvsec;
waiter->ts.tv_nsec = tvnsec;
}
#endif
}
static int janet_waiter_wait(JanetWaiter *wait, JanetMailbox *mailbox) {
if (wait->nowait) return 1;
#ifdef JANET_WINDOWS
if (wait->timedwait) {
if (wait->ticksLeft == 0) return 1;
DWORD startTime = GetTickCount();
int status = !SleepConditionVariableCS(&mailbox->cond, &mailbox->lock, wait->ticksLeft);
DWORD dTick = GetTickCount() - startTime;
/* Be careful about underflow */
wait->ticksLeft = dTick > wait->ticksLeft ? 0 : dTick;
return status;
} else {
SleepConditionVariableCS(&mailbox->cond, &mailbox->lock, INFINITE);
return 0;
}
#else
if (wait->timedwait) {
return pthread_cond_timedwait(&mailbox->cond, &mailbox->lock, &wait->ts);
} else {
pthread_cond_wait(&mailbox->cond, &mailbox->lock);
return 0;
}
#endif
}
static void janet_mailbox_wakeup(JanetMailbox *mailbox) {
#ifdef JANET_WINDOWS
WakeConditionVariable(&mailbox->cond);
#else
pthread_cond_signal(&mailbox->cond);
#endif
}
static int mailbox_at_capacity(JanetMailbox *mailbox) {
return mailbox->messageCount >= mailbox->messageCapacity;
}
/* Returns 1 if could not send (encode error or timeout), 2 for mailbox closed, and
* 0 otherwise. Will not panic. */
int janet_thread_send(JanetThread *thread, Janet msg, double timeout) {
/* Ensure mailbox is not closed. */
JanetMailbox *mailbox = thread->mailbox;
if (NULL == mailbox) return 2;
janet_mailbox_lock(mailbox);
if (mailbox->closed) {
janet_mailbox_ref_with_lock(mailbox, -1);
thread->mailbox = NULL;
return 2;
}
/* Back pressure */
if (mailbox_at_capacity(mailbox)) {
JanetWaiter wait;
janet_waiter_init(&wait, timeout);
if (wait.nowait) {
janet_mailbox_unlock(mailbox);
return 1;
}
/* Retry loop, as there can be multiple writers */
while (mailbox_at_capacity(mailbox)) {
if (janet_waiter_wait(&wait, mailbox)) {
janet_mailbox_unlock(mailbox);
janet_mailbox_wakeup(mailbox);
return 1;
}
}
}
/* Hack to capture all panics from marshalling. This works because
* we know janet_marshal won't mess with other essential global state. */
jmp_buf buf;
jmp_buf *old_buf = janet_vm_jmp_buf;
janet_vm_jmp_buf = &buf;
int32_t oldmcount = mailbox->messageCount;
int ret = 0;
if (setjmp(buf)) {
ret = 1;
mailbox->messageCount = oldmcount;
} else {
JanetBuffer *msgbuf = mailbox->messages + mailbox->messageNext;
msgbuf->count = 0;
/* Start panic zone */
janet_marshal(msgbuf, msg, thread->encode, JANET_MARSHAL_UNSAFE);
/* End panic zone */
mailbox->messageNext = (mailbox->messageNext + 1) % mailbox->messageCapacity;
mailbox->messageCount++;
}
/* Cleanup */
janet_vm_jmp_buf = old_buf;
janet_mailbox_unlock(mailbox);
/* Potentially wake up a blocked thread */
janet_mailbox_wakeup(mailbox);
return ret;
}
/* Returns 0 on successful message. Returns 1 if timedout */
int janet_thread_receive(Janet *msg_out, double timeout) {
JanetMailbox *mailbox = janet_vm_mailbox;
janet_mailbox_lock(mailbox);
/* For timeouts */
JanetWaiter wait;
janet_waiter_init(&wait, timeout);
for (;;) {
/* Check for messages waiting for us */
if (mailbox->messageCount > 0) {
/* Hack to capture all panics from marshalling. This works because
* we know janet_marshal won't mess with other essential global state. */
jmp_buf buf;
jmp_buf *old_buf = janet_vm_jmp_buf;
janet_vm_jmp_buf = &buf;
/* Handle errors */
if (setjmp(buf)) {
/* Cleanup jmp_buf, return error.
* Do not ignore bad messages as before. */
janet_vm_jmp_buf = old_buf;
*msg_out = *janet_vm_return_reg;
janet_mailbox_unlock(mailbox);
return 2;
} else {
JanetBuffer *msgbuf = mailbox->messages + mailbox->messageFirst;
mailbox->messageCount--;
mailbox->messageFirst = (mailbox->messageFirst + 1) % mailbox->messageCapacity;
/* Read from beginning of channel */
const uint8_t *nextItem = NULL;
Janet item = janet_unmarshal(
msgbuf->data, msgbuf->count,
JANET_MARSHAL_UNSAFE, janet_thread_get_decode(), &nextItem);
*msg_out = item;
/* Cleanup */
janet_vm_jmp_buf = old_buf;
janet_mailbox_unlock(mailbox);
/* Potentially wake up pending threads */
janet_mailbox_wakeup(mailbox);
return 0;
}
}
if (wait.nowait) {
janet_mailbox_unlock(mailbox);
return 1;
}
/* Wait for next message */
if (janet_waiter_wait(&wait, mailbox)) {
janet_mailbox_unlock(mailbox);
return 1;
}
}
}
static int janet_thread_getter(void *p, Janet key, Janet *out);
static Janet janet_thread_next(void *p, Janet key);
const JanetAbstractType janet_thread_type = {
"core/thread",
thread_gc,
thread_mark,
janet_thread_getter,
NULL, /* put */
NULL, /* marshal */
NULL, /* unmarshal */
NULL, /* tostring */
NULL, /* compare */
NULL, /* hash */
janet_thread_next,
JANET_ATEND_NEXT
};
static JanetThread *janet_make_thread(JanetMailbox *mailbox, JanetTable *encode) {
JanetThread *thread = janet_abstract(&janet_thread_type, sizeof(JanetThread));
janet_mailbox_ref(mailbox, 1);
thread->mailbox = mailbox;
thread->encode = encode;
return thread;
}
JanetThread *janet_getthread(const Janet *argv, int32_t n) {
return (JanetThread *) janet_getabstract(argv, n, &janet_thread_type);
}
/* Runs in new thread */
static int thread_worker(JanetMailboxPair *pair) {
JanetFiber *fiber = NULL;
Janet out;
/* Use the mailbox we were given */
janet_vm_mailbox = pair->newbox;
janet_mailbox_ref(pair->newbox, 1);
/* Init VM */
janet_init();
/* Get dictionaries for default encode/decode */
JanetTable *encode;
if (pair->flags & JANET_THREAD_HEAVYWEIGHT) {
encode = janet_get_core_table("make-image-dict");
} else {
encode = NULL;
janet_vm_thread_decode = janet_table(0);
janet_gcroot(janet_wrap_table(janet_vm_thread_decode));
}
/* Create parent thread */
JanetThread *parent = janet_make_thread(pair->original, encode);
Janet parentv = janet_wrap_abstract(parent);
/* Unmarshal the abstract registry */
if (pair->flags & JANET_THREAD_ABSTRACTS) {
Janet reg;
int status = janet_thread_receive(&reg, INFINITY);
if (status) goto error;
if (!janet_checktype(reg, JANET_TABLE)) goto error;
janet_gcunroot(janet_wrap_table(janet_vm_abstract_registry));
janet_vm_abstract_registry = janet_unwrap_table(reg);
janet_gcroot(janet_wrap_table(janet_vm_abstract_registry));
}
/* Unmarshal the normal registry */
if (pair->flags & JANET_THREAD_CFUNCTIONS) {
Janet reg;
int status = janet_thread_receive(&reg, INFINITY);
if (status) goto error;
if (!janet_checktype(reg, JANET_TABLE)) goto error;
janet_gcunroot(janet_wrap_table(janet_vm_registry));
janet_vm_registry = janet_unwrap_table(reg);
janet_gcroot(janet_wrap_table(janet_vm_registry));
}
/* Unmarshal the function */
Janet funcv;
int status = janet_thread_receive(&funcv, INFINITY);
if (status) goto error;
if (!janet_checktype(funcv, JANET_FUNCTION)) goto error;
JanetFunction *func = janet_unwrap_function(funcv);
/* Arity check */
if (func->def->min_arity > 1 || func->def->max_arity < 1) {
goto error;
}
/* Call function */
Janet argv[1] = { parentv };
fiber = janet_fiber(func, 64, 1, argv);
if (pair->flags & JANET_THREAD_HEAVYWEIGHT) {
fiber->env = janet_table(0);
fiber->env->proto = janet_core_env(NULL);
}
JanetSignal sig = janet_continue(fiber, janet_wrap_nil(), &out);
if (sig != JANET_SIGNAL_OK && sig < JANET_SIGNAL_USER0) {
janet_eprintf("in thread %v: ", janet_wrap_abstract(janet_make_thread(pair->newbox, encode)));
janet_stacktrace(fiber, out);
}
#ifdef JANET_EV
janet_loop();
#endif
/* Normal exit */
destroy_mailbox_pair(pair);
janet_deinit();
return 0;
/* Fail to set something up */
error:
destroy_mailbox_pair(pair);
janet_eprintf("\nthread failed to start\n");
janet_deinit();
return 1;
}
#ifdef JANET_WINDOWS
static DWORD WINAPI janet_create_thread_wrapper(LPVOID param) {
thread_worker((JanetMailboxPair *)param);
return 0;
}
static int janet_thread_start_child(JanetMailboxPair *pair) {
HANDLE handle = CreateThread(NULL, 0, janet_create_thread_wrapper, pair, 0, NULL);
int ret = NULL == handle;
/* Does not kill thread, simply detatches */
if (!ret) CloseHandle(handle);
return ret;
}
#else
static void *janet_pthread_wrapper(void *param) {
thread_worker((JanetMailboxPair *)param);
return NULL;
}
static int janet_thread_start_child(JanetMailboxPair *pair) {
pthread_t handle;
int error = pthread_create(&handle, NULL, janet_pthread_wrapper, pair);
if (error) {
return 1;
} else {
pthread_detach(handle);
return 0;
}
}
#endif
/*
* Setup/Teardown
*/
void janet_threads_init(void) {
if (NULL == janet_vm_mailbox) {
janet_vm_mailbox = janet_mailbox_create(1, 10);
}
janet_vm_thread_decode = NULL;
janet_vm_thread_current = NULL;
}
void janet_threads_deinit(void) {
janet_mailbox_lock(janet_vm_mailbox);
janet_vm_mailbox->closed = 1;
janet_mailbox_ref_with_lock(janet_vm_mailbox, -1);
janet_vm_mailbox = NULL;
janet_vm_thread_current = NULL;
janet_vm_thread_decode = NULL;
}
JanetThread *janet_thread_current(void) {
if (NULL == janet_vm_thread_current) {
janet_vm_thread_current = janet_make_thread(janet_vm_mailbox, janet_get_core_table("make-image-dict"));
janet_gcroot(janet_wrap_abstract(janet_vm_thread_current));
}
return janet_vm_thread_current;
}
/*
* Cfuns
*/
static Janet cfun_thread_current(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_abstract(janet_thread_current());
}
static Janet cfun_thread_new(int32_t argc, Janet *argv) {
janet_arity(argc, 1, 3);
/* Just type checking */
janet_getfunction(argv, 0);
int32_t cap = janet_optinteger(argv, argc, 1, 10);
if (cap < 1 || cap > UINT16_MAX) {
janet_panicf("bad slot #1, expected integer in range [1, 65535], got %d", cap);
}
uint64_t flags = argc >= 3 ? janet_getflags(argv, 2, janet_thread_flags) : JANET_THREAD_ABSTRACTS;
JanetTable *encode;
if (flags & JANET_THREAD_HEAVYWEIGHT) {
encode = janet_get_core_table("make-image-dict");
} else {
encode = NULL;
}
JanetMailboxPair *pair = make_mailbox_pair(janet_vm_mailbox, flags);
JanetThread *thread = janet_make_thread(pair->newbox, encode);
if (janet_thread_start_child(pair)) {
destroy_mailbox_pair(pair);
janet_panic("could not start thread");
}
if (flags & JANET_THREAD_ABSTRACTS) {
if (janet_thread_send(thread, janet_wrap_table(janet_vm_abstract_registry), INFINITY)) {
janet_panic("could not send abstract registry to thread");
}
}
if (flags & JANET_THREAD_CFUNCTIONS) {
if (janet_thread_send(thread, janet_wrap_table(janet_vm_registry), INFINITY)) {
janet_panic("could not send registry to thread");
}
}
/* If thread started, send the worker function. */
if (janet_thread_send(thread, argv[0], INFINITY)) {
janet_panicf("could not send worker function %v to thread", argv[0]);
}
return janet_wrap_abstract(thread);
}
static Janet cfun_thread_send(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 3);
JanetThread *thread = janet_getthread(argv, 0);
int status = janet_thread_send(thread, argv[1], janet_optnumber(argv, argc, 2, 1.0));
switch (status) {
default:
break;
case 1:
janet_panicf("failed to send message %v", argv[1]);
case 2:
janet_panic("thread mailbox is closed");
}
return argv[0];
}
static Janet cfun_thread_receive(int32_t argc, Janet *argv) {
janet_arity(argc, 0, 1);
double wait = janet_optnumber(argv, argc, 0, 1.0);
Janet out;
int status = janet_thread_receive(&out, wait);
switch (status) {
default:
break;
case 1:
janet_panicf("timeout after %f seconds", wait);
case 2:
janet_panicf("failed to receive message: %v", out);
}
return out;
}
static Janet cfun_thread_close(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
JanetThread *thread = janet_getthread(argv, 0);
janet_close_thread(thread);
return janet_wrap_nil();
}
static Janet cfun_thread_exit(int32_t argc, Janet *argv) {
(void) argv;
janet_arity(argc, 0, 1);
#if defined(JANET_WINDOWS)
int32_t flag = janet_optinteger(argv, argc, 0, 0);
ExitThread(flag);
#else
pthread_exit(NULL);
#endif
return janet_wrap_nil();
}
static const JanetMethod janet_thread_methods[] = {
{"send", cfun_thread_send},
{"close", cfun_thread_close},
{NULL, NULL}
};
static int janet_thread_getter(void *p, Janet key, Janet *out) {
(void) p;
if (!janet_checktype(key, JANET_KEYWORD)) return 0;
return janet_getmethod(janet_unwrap_keyword(key), janet_thread_methods, out);
}
static Janet janet_thread_next(void *p, Janet key) {
(void) p;
return janet_nextmethod(janet_thread_methods, key);
}
static const JanetReg threadlib_cfuns[] = {
{
"thread/current", cfun_thread_current,
JDOC("(thread/current)\n\n"
"Get the current running thread.")
},
{
"thread/new", cfun_thread_new,
JDOC("(thread/new func &opt capacity flags)\n\n"
"Start a new thread that will start immediately. "
"If capacity is provided, that is how many messages can be stored in the thread's mailbox before blocking senders. "
"The capacity must be between 1 and 65535 inclusive, and defaults to 10. "
"Can optionally provide flags to the new thread - supported flags are:\n\n"
"* :h - Start a heavyweight thread. This loads the core environment by default, so may use more memory initially. Messages may compress better, though.\n\n"
"* :a - Allow sending over registered abstract types to the new thread\n\n"
"* :c - Send over cfunction information to the new thread.\n\n"
"Returns a handle to the new thread.")
},
{
"thread/send", cfun_thread_send,
JDOC("(thread/send thread msgi &opt timeout)\n\n"
"Send a message to the thread. By default, the timeout is 1 second, but an optional timeout "
"in seconds can be provided. Use math/inf for no timeout. "
"Will throw an error if there is a problem sending the message.")
},
{
"thread/receive", cfun_thread_receive,
JDOC("(thread/receive &opt timeout)\n\n"
"Get a message sent to this thread. If timeout (in seconds) is provided, an error "
"will be thrown after the timeout has elapsed but "
"no messages are received. The default timeout is 1 second, and math/inf cam be passed to "
"turn off the timeout.")
},
{
"thread/close", cfun_thread_close,
JDOC("(thread/close thread)\n\n"
"Close a thread, unblocking it and ending communication with it. Note that closing "
"a thread is idempotent and does not cancel the thread's operation. Returns nil.")
},
{
"thread/exit", cfun_thread_exit,
JDOC("(thread/exit &opt code)\n\n"
"Exit from the current thread. If no more threads are running, ends the process, but otherwise does "
"not end the current process.")
},
{NULL, NULL, NULL}
};
/* Module entry point */
void janet_lib_thread(JanetTable *env) {
janet_core_cfuns(env, NULL, threadlib_cfuns);
janet_register_abstract_type(&janet_thread_type);
}
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -55,19 +55,35 @@ const Janet *janet_tuple_n(const Janet *values, int32_t n) {
/* C Functions */
static Janet cfun_tuple_brackets(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_tuple_brackets,
"(tuple/brackets & xs)",
"Creates a new bracketed tuple containing the elements xs.") {
const Janet *tup = janet_tuple_n(argv, argc);
janet_tuple_flag(tup) |= JANET_TUPLE_FLAG_BRACKETCTOR;
return janet_wrap_tuple(tup);
}
static Janet cfun_tuple_slice(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_tuple_slice,
"(tuple/slice arrtup [,start=0 [,end=(length arrtup)]])",
"Take a sub sequence of an array or tuple from index start "
"inclusive to index end exclusive. If start or end are not provided, "
"they default to 0 and the length of arrtup respectively. "
"'start' and 'end' can also be negative to indicate indexing "
"from the end of the input. Note that index -1 is synonymous with "
"index '(length arrtup)' to allow a full negative slice range. "
"Returns the new tuple.") {
JanetView view = janet_getindexed(argv, 0);
JanetRange range = janet_getslice(argc, argv);
return janet_wrap_tuple(janet_tuple_n(view.items + range.start, range.end - range.start));
}
static Janet cfun_tuple_type(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_tuple_type,
"(tuple/type tup)",
"Checks how the tuple was constructed. Will return the keyword "
":brackets if the tuple was parsed with brackets, and :parens "
"otherwise. The two types of tuples will behave the same most of "
"the time, but will print differently and be treated differently by "
"the compiler.") {
janet_fixarity(argc, 1);
const Janet *tup = janet_gettuple(argv, 0);
if (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) {
@ -77,7 +93,10 @@ static Janet cfun_tuple_type(int32_t argc, Janet *argv) {
}
}
static Janet cfun_tuple_sourcemap(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_tuple_sourcemap,
"(tuple/sourcemap tup)",
"Returns the sourcemap metadata attached to a tuple, "
" which is another tuple (line, column).") {
janet_fixarity(argc, 1);
const Janet *tup = janet_gettuple(argv, 0);
Janet contents[2];
@ -86,7 +105,10 @@ static Janet cfun_tuple_sourcemap(int32_t argc, Janet *argv) {
return janet_wrap_tuple(janet_tuple_n(contents, 2));
}
static Janet cfun_tuple_setmap(int32_t argc, Janet *argv) {
JANET_CORE_FN(cfun_tuple_setmap,
"(tuple/setmap tup line column)",
"Set the sourcemap metadata on a tuple. line and column indicate "
"should be integers.") {
janet_fixarity(argc, 3);
const Janet *tup = janet_gettuple(argv, 0);
janet_tuple_head(tup)->sm_line = janet_getinteger(argv, 1);
@ -94,48 +116,15 @@ static Janet cfun_tuple_setmap(int32_t argc, Janet *argv) {
return argv[0];
}
static const JanetReg tuple_cfuns[] = {
{
"tuple/brackets", cfun_tuple_brackets,
JDOC("(tuple/brackets & xs)\n\n"
"Creates a new bracketed tuple containing the elements xs.")
},
{
"tuple/slice", cfun_tuple_slice,
JDOC("(tuple/slice arrtup [,start=0 [,end=(length arrtup)]])\n\n"
"Take a sub sequence of an array or tuple from index start "
"inclusive to index end exclusive. If start or end are not provided, "
"they default to 0 and the length of arrtup respectively. "
"'start' and 'end' can also be negative to indicate indexing "
"from the end of the input. Note that index -1 is synonymous with "
"index '(length arrtup)' to allow a full negative slice range. "
"Returns the new tuple.")
},
{
"tuple/type", cfun_tuple_type,
JDOC("(tuple/type tup)\n\n"
"Checks how the tuple was constructed. Will return the keyword "
":brackets if the tuple was parsed with brackets, and :parens "
"otherwise. The two types of tuples will behave the same most of "
"the time, but will print differently and be treated differently by "
"the compiler.")
},
{
"tuple/sourcemap", cfun_tuple_sourcemap,
JDOC("(tuple/sourcemap tup)\n\n"
"Returns the sourcemap metadata attached to a tuple, "
" which is another tuple (line, column).")
},
{
"tuple/setmap", cfun_tuple_setmap,
JDOC("(tuple/setmap tup line column)\n\n"
"Set the sourcemap metadata on a tuple. line and column indicate "
"should be integers.")
},
{NULL, NULL, NULL}
};
/* Load the tuple module */
void janet_lib_tuple(JanetTable *env) {
janet_core_cfuns(env, NULL, tuple_cfuns);
JanetRegExt tuple_cfuns[] = {
JANET_CORE_REG("tuple/brackets", cfun_tuple_brackets),
JANET_CORE_REG("tuple/slice", cfun_tuple_slice),
JANET_CORE_REG("tuple/type", cfun_tuple_type),
JANET_CORE_REG("tuple/sourcemap", cfun_tuple_sourcemap),
JANET_CORE_REG("tuple/setmap", cfun_tuple_setmap),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, tuple_cfuns);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -362,105 +362,208 @@ const void *janet_strbinsearch(
return NULL;
}
/* Register a value in the global registry */
void janet_register(const char *name, JanetCFunction cfun) {
Janet key = janet_wrap_cfunction(cfun);
Janet value = janet_csymbolv(name);
janet_table_put(janet_vm_registry, key, value);
/* Add sourcemapping and documentation to a binding table */
static void janet_add_meta(JanetTable *table, const char *doc, const char *source_file, int32_t source_line) {
if (doc) {
janet_table_put(table, janet_ckeywordv("doc"), janet_cstringv(doc));
}
if (source_file && source_line) {
Janet triple[3];
triple[0] = janet_cstringv(source_file);
triple[1] = janet_wrap_integer(source_line);
triple[2] = janet_wrap_integer(1);
Janet value = janet_wrap_tuple(janet_tuple_n(triple, 3));
janet_table_put(table, janet_ckeywordv("source-map"), value);
}
}
/* Add a def to an environment */
void janet_def(JanetTable *env, const char *name, Janet val, const char *doc) {
void janet_def_sm(JanetTable *env, const char *name, Janet val, const char *doc, const char *source_file, int32_t source_line) {
JanetTable *subt = janet_table(2);
janet_table_put(subt, janet_ckeywordv("value"), val);
if (doc)
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(doc));
janet_add_meta(subt, doc, source_file, source_line);
janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt));
}
void janet_def(JanetTable *env, const char *name, Janet value, const char *doc) {
janet_def_sm(env, name, value, doc, NULL, 0);
}
/* Add a var to the environment */
void janet_var(JanetTable *env, const char *name, Janet val, const char *doc) {
void janet_var_sm(JanetTable *env, const char *name, Janet val, const char *doc, const char *source_file, int32_t source_line) {
JanetArray *array = janet_array(1);
JanetTable *subt = janet_table(2);
janet_array_push(array, val);
janet_table_put(subt, janet_ckeywordv("ref"), janet_wrap_array(array));
if (doc)
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(doc));
janet_add_meta(subt, doc, source_file, source_line);
janet_table_put(env, janet_csymbolv(name), janet_wrap_table(subt));
}
/* Load many cfunctions at once */
static void _janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns, int defprefix) {
uint8_t *longname_buffer = NULL;
size_t prefixlen = 0;
size_t bufsize = 0;
if (NULL != regprefix) {
prefixlen = strlen(regprefix);
bufsize = prefixlen + 256;
longname_buffer = janet_malloc(bufsize);
if (NULL == longname_buffer) {
JANET_OUT_OF_MEMORY;
}
safe_memcpy(longname_buffer, regprefix, prefixlen);
longname_buffer[prefixlen] = '/';
prefixlen++;
}
while (cfuns->name) {
Janet name;
if (NULL != regprefix) {
int32_t nmlen = 0;
while (cfuns->name[nmlen]) nmlen++;
int32_t totallen = (int32_t) prefixlen + nmlen;
if ((size_t) totallen > bufsize) {
bufsize = (size_t)(totallen) + 128;
longname_buffer = janet_realloc(longname_buffer, bufsize);
if (NULL == longname_buffer) {
JANET_OUT_OF_MEMORY;
}
}
safe_memcpy(longname_buffer + prefixlen, cfuns->name, nmlen);
name = janet_wrap_symbol(janet_symbol(longname_buffer, totallen));
} else {
name = janet_csymbolv(cfuns->name);
}
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (defprefix) {
JanetTable *subt = janet_table(2);
janet_table_put(subt, janet_ckeywordv("value"), fun);
if (cfuns->documentation)
janet_table_put(subt, janet_ckeywordv("doc"), janet_cstringv(cfuns->documentation));
janet_table_put(env, name, janet_wrap_table(subt));
} else {
janet_def(env, cfuns->name, fun, cfuns->documentation);
}
janet_table_put(janet_vm_registry, fun, name);
cfuns++;
}
janet_free(longname_buffer);
void janet_var(JanetTable *env, const char *name, Janet val, const char *doc) {
janet_var_sm(env, name, val, doc, NULL, 0);
}
void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
_janet_cfuns_prefix(env, regprefix, cfuns, 1);
/* Registry functions */
/* Put the registry in sorted order. */
static void janet_registry_sort(void) {
for (size_t i = 1; i < janet_vm.registry_count; i++) {
JanetCFunRegistry reg = janet_vm.registry[i];
size_t j;
for (j = i; j > 0; j--) {
if ((void *)(janet_vm.registry[j - 1].cfun) < (void *)(reg.cfun)) break;
janet_vm.registry[j] = janet_vm.registry[j - 1];
}
janet_vm.registry[j] = reg;
}
janet_vm.registry_dirty = 0;
}
void janet_registry_put(
JanetCFunction key,
const char *name,
const char *name_prefix,
const char *source_file,
int32_t source_line) {
if (janet_vm.registry_count == janet_vm.registry_cap) {
size_t newcap = (janet_vm.registry_count + 1) * 2;
/* Size it nicely with core by default */
if (newcap < 512) {
newcap = 512;
}
void *newmem = janet_realloc(janet_vm.registry, newcap * sizeof(JanetCFunRegistry));
if (NULL == newmem) {
JANET_OUT_OF_MEMORY;
}
janet_vm.registry = newmem;
janet_vm.registry_cap = newcap;
}
JanetCFunRegistry value = {
key,
name,
name_prefix,
source_file,
source_line
};
janet_vm.registry[janet_vm.registry_count++] = value;
janet_vm.registry_dirty = 1;
}
JanetCFunRegistry *janet_registry_get(JanetCFunction key) {
if (janet_vm.registry_dirty) {
janet_registry_sort();
}
for (size_t i = 0; i < janet_vm.registry_count; i++) {
if (janet_vm.registry[i].cfun == key) {
return janet_vm.registry + i;
}
}
JanetCFunRegistry *lo = janet_vm.registry;
JanetCFunRegistry *hi = lo + janet_vm.registry_count;
while (lo < hi) {
JanetCFunRegistry *mid = lo + (hi - lo) / 2;
if (mid->cfun == key) {
return mid;
}
if ((void *)(mid->cfun) > (void *)(key)) {
hi = mid;
} else {
lo = mid + 1;
}
}
return NULL;
}
typedef struct {
char *buf;
size_t plen;
} NameBuf;
static void namebuf_init(NameBuf *namebuf, const char *prefix) {
size_t plen = strlen(prefix);
namebuf->plen = plen;
namebuf->buf = janet_malloc(namebuf->plen + 256);
if (NULL == namebuf->buf) {
JANET_OUT_OF_MEMORY;
}
memcpy(namebuf->buf, prefix, plen);
namebuf->buf[plen] = '/';
}
static void namebuf_deinit(NameBuf *namebuf) {
janet_free(namebuf->buf);
}
static char *namebuf_name(NameBuf *namebuf, const char *suffix) {
size_t slen = strlen(suffix);
namebuf->buf = janet_realloc(namebuf->buf, namebuf->plen + 2 + slen);
if (NULL == namebuf->buf) {
JANET_OUT_OF_MEMORY;
}
memcpy(namebuf->buf + namebuf->plen + 1, suffix, slen);
namebuf->buf[namebuf->plen + 1 + slen] = '\0';
return (char *)(namebuf->buf);
}
void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
_janet_cfuns_prefix(env, regprefix, cfuns, 0);
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def(env, cfuns->name, fun, cfuns->documentation);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0);
cfuns++;
}
}
void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def_sm(env, cfuns->name, fun, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);
cfuns++;
}
}
void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
NameBuf nb;
if (env) namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, NULL, 0);
cfuns++;
}
if (env) namebuf_deinit(&nb);
}
void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
NameBuf nb;
if (env) namebuf_init(&nb, regprefix);
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
if (env) janet_def_sm(env, namebuf_name(&nb, cfuns->name), fun, cfuns->documentation, cfuns->source_file, cfuns->source_line);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);
cfuns++;
}
if (env) namebuf_deinit(&nb);
}
/* Register a value in the global registry */
void janet_register(const char *name, JanetCFunction cfun) {
janet_registry_put(cfun, name, NULL, NULL, 0);
}
/* Abstract type introspection */
void janet_register_abstract_type(const JanetAbstractType *at) {
Janet sym = janet_csymbolv(at->name);
Janet check = janet_table_get(janet_vm_abstract_registry, sym);
Janet check = janet_table_get(janet_vm.abstract_registry, sym);
if (!janet_checktype(check, JANET_NIL) && at != janet_unwrap_pointer(check)) {
janet_panicf("cannot register abstract type %s, "
"a type with the same name exists", at->name);
}
janet_table_put(janet_vm_abstract_registry, sym, janet_wrap_pointer((void *) at));
janet_table_put(janet_vm.abstract_registry, sym, janet_wrap_pointer((void *) at));
}
const JanetAbstractType *janet_get_abstract_type(Janet key) {
Janet wrapped = janet_table_get(janet_vm_abstract_registry, key);
Janet wrapped = janet_table_get(janet_vm.abstract_registry, key);
if (janet_checktype(wrapped, JANET_NIL)) {
return NULL;
}
@ -468,46 +571,82 @@ const JanetAbstractType *janet_get_abstract_type(Janet key) {
}
#ifndef JANET_BOOTSTRAP
void janet_core_def(JanetTable *env, const char *name, Janet x, const void *p) {
void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p, const void *sf, int32_t sl) {
(void) sf;
(void) sl;
(void) p;
Janet key = janet_csymbolv(name);
janet_table_put(env, key, x);
if (janet_checktype(x, JANET_CFUNCTION)) {
janet_table_put(janet_vm_registry, x, key);
janet_registry_put(janet_unwrap_cfunction(x), name, NULL, NULL, 0);
}
}
void janet_core_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns) {
void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns) {
(void) regprefix;
while (cfuns->name) {
Janet fun = janet_wrap_cfunction(cfuns->cfun);
janet_core_def(env, cfuns->name, fun, cfuns->documentation);
janet_table_put(env, janet_csymbolv(cfuns->name), fun);
janet_registry_put(cfuns->cfun, cfuns->name, regprefix, cfuns->source_file, cfuns->source_line);
cfuns++;
}
}
#endif
/* Resolve a symbol in the environment */
JanetBindingType janet_resolve(JanetTable *env, const uint8_t *sym, Janet *out) {
JanetBinding janet_resolve_ext(JanetTable *env, const uint8_t *sym) {
Janet ref;
JanetTable *entry_table;
Janet entry = janet_table_get(env, janet_wrap_symbol(sym));
JanetBinding binding = {
JANET_BINDING_NONE,
janet_wrap_nil(),
JANET_BINDING_DEP_NONE
};
/* Check environment for entry */
if (!janet_checktype(entry, JANET_TABLE))
return JANET_BINDING_NONE;
return binding;
entry_table = janet_unwrap_table(entry);
/* deprecation check */
Janet deprecate = janet_table_get(entry_table, janet_ckeywordv("deprecated"));
if (janet_checktype(deprecate, JANET_KEYWORD)) {
JanetKeyword depkw = janet_unwrap_keyword(deprecate);
if (!janet_cstrcmp(depkw, "relaxed")) {
binding.deprecation = JANET_BINDING_DEP_RELAXED;
} else if (!janet_cstrcmp(depkw, "normal")) {
binding.deprecation = JANET_BINDING_DEP_NORMAL;
} else if (!janet_cstrcmp(depkw, "strict")) {
binding.deprecation = JANET_BINDING_DEP_STRICT;
}
} else if (!janet_checktype(deprecate, JANET_NIL)) {
binding.deprecation = JANET_BINDING_DEP_NORMAL;
}
if (!janet_checktype(
janet_table_get(entry_table, janet_ckeywordv("macro")),
JANET_NIL)) {
*out = janet_table_get(entry_table, janet_ckeywordv("value"));
return JANET_BINDING_MACRO;
binding.value = janet_table_get(entry_table, janet_ckeywordv("value"));
binding.type = JANET_BINDING_MACRO;
return binding;
}
ref = janet_table_get(entry_table, janet_ckeywordv("ref"));
if (janet_checktype(ref, JANET_ARRAY)) {
*out = ref;
return JANET_BINDING_VAR;
binding.value = ref;
binding.type = JANET_BINDING_VAR;
return binding;
}
*out = janet_table_get(entry_table, janet_ckeywordv("value"));
return JANET_BINDING_DEF;
binding.value = janet_table_get(entry_table, janet_ckeywordv("value"));
binding.type = JANET_BINDING_DEF;
return binding;
}
JanetBindingType janet_resolve(JanetTable *env, const uint8_t *sym, Janet *out) {
JanetBinding binding = janet_resolve_ext(env, sym);
*out = binding.value;
return binding.type;
}
/* Resolve a symbol in the core environment. */
@ -728,7 +867,7 @@ void *(janet_malloc)(size_t size) {
}
void (janet_free)(void *ptr) {
return janet_free(ptr);
janet_free(ptr);
}
void *(janet_calloc)(size_t nmemb, size_t size) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -26,6 +26,7 @@
#ifndef JANET_AMALG
#include "features.h"
#include <janet.h>
#include "state.h"
#endif
#include <stdio.h>
@ -48,20 +49,16 @@
} while (0)
#endif
#define JANET_MARSHAL_DECREF 0x40000
#define janet_assert(c, m) do { \
if (!(c)) JANET_EXIT((m)); \
} while (0)
/* Omit docstrings in some builds */
#ifndef JANET_BOOTSTRAP
#define JDOC(x) NULL
#define JANET_NO_BOOTSTRAP
#else
#define JDOC(x) x
#endif
/* Utils */
#define janet_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1))
int janet_valid_utf8(const uint8_t *str, int32_t len);
int janet_is_symbol_char(uint8_t c);
extern const char janet_base64[65];
int32_t janet_array_calchash(const Janet *array, int32_t len);
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len);
@ -87,14 +84,29 @@ void janet_buffer_format(
Janet *argv);
Janet janet_next_impl(Janet ds, Janet key, int is_interpreter);
/* Registry functions */
void janet_registry_put(
JanetCFunction key,
const char *name,
const char *name_prefix,
const char *source_file,
int32_t source_line);
JanetCFunRegistry *janet_registry_get(JanetCFunction key);
/* Inside the janet core, defining globals is different
* at bootstrap time and normal runtime */
#ifdef JANET_BOOTSTRAP
#define janet_core_def janet_def
#define janet_core_cfuns janet_cfuns
#define JANET_CORE_REG JANET_REG
#define JANET_CORE_FN JANET_FN
#define JANET_CORE_DEF JANET_DEF
#define janet_core_def_sm janet_def_sm
#define janet_core_cfuns_ext janet_cfuns_ext
#else
void janet_core_def(JanetTable *env, const char *name, Janet x, const void *p);
void janet_core_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns);
#define JANET_CORE_REG JANET_REG_S
#define JANET_CORE_FN JANET_FN_S
#define JANET_CORE_DEF(ENV, NAME, X, DOC) janet_core_def_sm(ENV, NAME, X, DOC, NULL, 0)
void janet_core_def_sm(JanetTable *env, const char *name, Janet x, const void *p, const void *sf, int32_t sl);
void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
#endif
/* Clock gettime */
@ -136,9 +148,6 @@ void janet_lib_typed_array(JanetTable *env);
#ifdef JANET_INT_TYPES
void janet_lib_inttypes(JanetTable *env);
#endif
#ifdef JANET_THREADS
void janet_lib_thread(JanetTable *env);
#endif
#ifdef JANET_NET
void janet_lib_net(JanetTable *env);
extern const JanetAbstractType janet_address_type;

View File

@ -31,31 +31,28 @@
#include <math.h>
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_top = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_base = NULL;
static void push_traversal_node(void *lhs, void *rhs, int32_t index2) {
JanetTraversalNode node;
node.self = (JanetGCObject *) lhs;
node.other = (JanetGCObject *) rhs;
node.index = 0;
node.index2 = index2;
if (janet_vm_traversal + 1 >= janet_vm_traversal_top) {
size_t oldsize = janet_vm_traversal - janet_vm_traversal_base;
int is_new = janet_vm.traversal_base == NULL;
if (is_new || (janet_vm.traversal + 1 >= janet_vm.traversal_top)) {
size_t oldsize = is_new ? 0 : (janet_vm.traversal - janet_vm.traversal_base);
size_t newsize = 2 * oldsize + 1;
if (newsize < 128) {
newsize = 128;
}
JanetTraversalNode *tn = janet_realloc(janet_vm_traversal_base, newsize * sizeof(JanetTraversalNode));
JanetTraversalNode *tn = janet_realloc(janet_vm.traversal_base, newsize * sizeof(JanetTraversalNode));
if (tn == NULL) {
JANET_OUT_OF_MEMORY;
}
janet_vm_traversal_base = tn;
janet_vm_traversal_top = janet_vm_traversal_base + newsize;
janet_vm_traversal = janet_vm_traversal_base + oldsize;
janet_vm.traversal_base = tn;
janet_vm.traversal_top = janet_vm.traversal_base + newsize;
janet_vm.traversal = janet_vm.traversal_base + oldsize;
}
*(++janet_vm_traversal) = node;
*(++janet_vm.traversal) = node;
}
/*
@ -67,8 +64,8 @@ static void push_traversal_node(void *lhs, void *rhs, int32_t index2) {
* 3 - early stop - lhs > rhs
*/
static int traversal_next(Janet *x, Janet *y) {
JanetTraversalNode *t = janet_vm_traversal;
while (t && t > janet_vm_traversal_base) {
JanetTraversalNode *t = janet_vm.traversal;
while (t && t > janet_vm.traversal_base) {
JanetGCObject *self = t->self;
JanetTupleHead *tself = (JanetTupleHead *)self;
JanetStructHead *sself = (JanetStructHead *)self;
@ -81,7 +78,7 @@ static int traversal_next(Janet *x, Janet *y) {
int32_t index = t->index++;
*x = tself->data[index];
*y = tother->data[index];
janet_vm_traversal = t;
janet_vm.traversal = t;
return 0;
}
if (t->index2 && tself->length != tother->length) {
@ -94,14 +91,14 @@ static int traversal_next(Janet *x, Janet *y) {
int32_t index = t->index++;
*x = sself->data[index].value;
*y = sother->data[index].value;
janet_vm_traversal = t;
janet_vm.traversal = t;
return 0;
}
for (int32_t i = t->index; i < sself->capacity; i++) {
t->index2 = 1;
*x = sself->data[t->index].key;
*y = sother->data[t->index].key;
janet_vm_traversal = t;
janet_vm.traversal = t;
return 0;
}
/* Traverse prototype */
@ -112,13 +109,13 @@ static int traversal_next(Janet *x, Janet *y) {
if (oproto && sproto) {
*x = janet_wrap_struct(sproto);
*y = janet_wrap_struct(oproto);
janet_vm_traversal = t - 1;
janet_vm.traversal = t - 1;
return 0;
}
}
t--;
}
janet_vm_traversal = t;
janet_vm.traversal = t;
return 2;
}
@ -207,17 +204,17 @@ Janet janet_next_impl(Janet ds, Janet key, int is_interpreter) {
status == JANET_STATUS_USER4) {
return janet_wrap_nil();
}
janet_vm_fiber->child = child;
janet_vm.fiber->child = child;
JanetSignal sig = janet_continue(child, janet_wrap_nil(), &retreg);
if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) {
if (is_interpreter) {
janet_signalv(sig, retreg);
} else {
janet_vm_fiber->child = NULL;
janet_vm.fiber->child = NULL;
janet_panicv(retreg);
}
}
janet_vm_fiber->child = NULL;
janet_vm.fiber->child = NULL;
if (sig == JANET_SIGNAL_OK ||
sig == JANET_SIGNAL_ERROR ||
sig == JANET_SIGNAL_USER0 ||
@ -250,7 +247,7 @@ static int janet_compare_abstract(JanetAbstract xx, JanetAbstract yy) {
}
int janet_equals(Janet x, Janet y) {
janet_vm_traversal = janet_vm_traversal_base;
janet_vm.traversal = janet_vm.traversal_base;
do {
if (janet_type(x) != janet_type(y)) return 0;
switch (janet_type(x)) {
@ -360,7 +357,7 @@ int32_t janet_hash(Janet x) {
* If y is less, returns 1. All types are comparable
* and should have strict ordering, excepts NaNs. */
int janet_compare(Janet x, Janet y) {
janet_vm_traversal = janet_vm_traversal_base;
janet_vm.traversal = janet_vm.traversal_base;
int status;
do {
JanetType tx = janet_type(x);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -32,17 +32,6 @@
#include <math.h>
/* VM state */
JANET_THREAD_LOCAL JanetTable *janet_vm_top_dyns;
JANET_THREAD_LOCAL JanetTable *janet_vm_core_env;
JANET_THREAD_LOCAL JanetTable *janet_vm_registry;
JANET_THREAD_LOCAL JanetTable *janet_vm_abstract_registry;
JANET_THREAD_LOCAL int janet_vm_stackn = 0;
JANET_THREAD_LOCAL JanetFiber *janet_vm_fiber = NULL;
JANET_THREAD_LOCAL JanetFiber *janet_vm_root_fiber = NULL;
JANET_THREAD_LOCAL Janet *janet_vm_return_reg = NULL;
JANET_THREAD_LOCAL jmp_buf *janet_vm_jmp_buf = NULL;
/* Virtual registers
*
* One instruction word
@ -91,18 +80,18 @@ JANET_THREAD_LOCAL jmp_buf *janet_vm_jmp_buf = NULL;
func = janet_stack_frame(stack)->func; \
} while (0)
#define vm_return(sig, val) do { \
janet_vm_return_reg[0] = (val); \
janet_vm.return_reg[0] = (val); \
vm_commit(); \
return (sig); \
} while (0)
#define vm_return_no_restore(sig, val) do { \
janet_vm_return_reg[0] = (val); \
janet_vm.return_reg[0] = (val); \
return (sig); \
} while (0)
/* Next instruction variations */
#define maybe_collect() do {\
if (janet_vm_next_collection >= janet_vm_gc_interval) janet_collect(); } while (0)
if (janet_vm.next_collection >= janet_vm.gc_interval) janet_collect(); } while (0)
#define vm_checkgc_next() maybe_collect(); vm_next()
#define vm_pcnext() pc++; vm_next()
#define vm_checkgc_pcnext() maybe_collect(); vm_pcnext()
@ -122,6 +111,17 @@ JANET_THREAD_LOCAL jmp_buf *janet_vm_jmp_buf = NULL;
janet_panicf("expected %T, got %v", (TS), (X)); \
} \
} while (0)
#ifdef JANET_NO_INTERPRETER_INTERRUPT
#define vm_maybe_auto_suspend(COND)
#else
#define vm_maybe_auto_suspend(COND) do { \
if ((COND) && janet_vm.auto_suspend) { \
janet_vm.auto_suspend = 0; \
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
} \
} while (0)
#endif
/* Templates for certain patterns in opcodes */
#define vm_binop_immediate(op)\
@ -591,7 +591,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
JanetSignal sig = (fiber->gc.flags & JANET_FIBER_STATUS_MASK) >> JANET_FIBER_STATUS_OFFSET;
fiber->gc.flags &= ~JANET_FIBER_STATUS_MASK;
fiber->flags &= ~(JANET_FIBER_RESUME_SIGNAL | JANET_FIBER_FLAG_MASK);
janet_vm_return_reg[0] = in;
janet_vm.return_reg[0] = in;
return sig;
}
@ -757,11 +757,13 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
VM_OP(JOP_JUMP)
pc += DS;
vm_maybe_auto_suspend(DS < 0);
vm_next();
VM_OP(JOP_JUMP_IF)
if (janet_truthy(stack[A])) {
pc += ES;
vm_maybe_auto_suspend(ES < 0);
} else {
pc++;
}
@ -772,12 +774,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES < 0);
}
vm_next();
VM_OP(JOP_JUMP_IF_NIL)
if (janet_checktype(stack[A], JANET_NIL)) {
pc += ES;
vm_maybe_auto_suspend(ES < 0);
} else {
pc++;
}
@ -788,6 +792,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
pc++;
} else {
pc += ES;
vm_maybe_auto_suspend(ES < 0);
}
vm_next();
@ -961,6 +966,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
vm_checkgc_pcnext();
VM_OP(JOP_CALL) {
vm_maybe_auto_suspend(1);
Janet callee = stack[E];
if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow");
@ -1000,6 +1006,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
}
VM_OP(JOP_TAILCALL) {
vm_maybe_auto_suspend(1);
Janet callee = stack[D];
if (fiber->stacktop > fiber->maxstack) {
vm_throw("stack overflow");
@ -1046,6 +1053,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) {
VM_OP(JOP_RESUME) {
Janet retreg;
vm_maybe_auto_suspend(1);
vm_assert_type(stack[B], JANET_FIBER);
JanetFiber *child = janet_unwrap_fiber(stack[B]);
if (janet_check_can_resume(child, &retreg)) {
@ -1279,9 +1287,9 @@ JanetSignal janet_step(JanetFiber *fiber, Janet in, Janet *out) {
Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
/* Check entry conditions */
if (!janet_vm_fiber)
if (!janet_vm.fiber)
janet_panic("janet_call failed because there is no current fiber");
if (janet_vm_stackn >= JANET_RECURSION_GUARD)
if (janet_vm.stackn >= JANET_RECURSION_GUARD)
janet_panic("C stack recursed too deeply");
/* Tracing */
@ -1290,8 +1298,8 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
}
/* Push frame */
janet_fiber_pushn(janet_vm_fiber, argv, argc);
if (janet_fiber_funcframe(janet_vm_fiber, fun)) {
janet_fiber_pushn(janet_vm.fiber, argv, argc);
if (janet_fiber_funcframe(janet_vm.fiber, fun)) {
int32_t min = fun->def->min_arity;
int32_t max = fun->def->max_arity;
Janet funv = janet_wrap_function(fun);
@ -1301,31 +1309,31 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
janet_panicf("arity mismatch in %v, expected at least %d, got %d", funv, min, argc);
janet_panicf("arity mismatch in %v, expected at most %d, got %d", funv, max, argc);
}
janet_fiber_frame(janet_vm_fiber)->flags |= JANET_STACKFRAME_ENTRANCE;
janet_fiber_frame(janet_vm.fiber)->flags |= JANET_STACKFRAME_ENTRANCE;
/* Set up */
int32_t oldn = janet_vm_stackn++;
int32_t oldn = janet_vm.stackn++;
int handle = janet_gclock();
/* Run vm */
janet_vm_fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
JanetSignal signal = run_vm(janet_vm_fiber, janet_wrap_nil());
janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP;
JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil());
/* Teardown */
janet_vm_stackn = oldn;
janet_vm.stackn = oldn;
janet_gcunlock(handle);
if (signal != JANET_SIGNAL_OK) {
janet_panicv(*janet_vm_return_reg);
janet_panicv(*janet_vm.return_reg);
}
return *janet_vm_return_reg;
return *janet_vm.return_reg;
}
static JanetSignal janet_check_can_resume(JanetFiber *fiber, Janet *out) {
/* Check conditions */
JanetFiberStatus old_status = janet_fiber_status(fiber);
if (janet_vm_stackn >= JANET_RECURSION_GUARD) {
if (janet_vm.stackn >= JANET_RECURSION_GUARD) {
janet_fiber_set_status(fiber, JANET_STATUS_ERROR);
*out = janet_cstringv("C stack recursed too deeply");
return JANET_SIGNAL_ERROR;
@ -1343,21 +1351,21 @@ static JanetSignal janet_check_can_resume(JanetFiber *fiber, Janet *out) {
}
void janet_try_init(JanetTryState *state) {
state->stackn = janet_vm_stackn++;
state->gc_handle = janet_vm_gc_suspend;
state->vm_fiber = janet_vm_fiber;
state->vm_jmp_buf = janet_vm_jmp_buf;
state->vm_return_reg = janet_vm_return_reg;
janet_vm_return_reg = &(state->payload);
janet_vm_jmp_buf = &(state->buf);
state->stackn = janet_vm.stackn++;
state->gc_handle = janet_vm.gc_suspend;
state->vm_fiber = janet_vm.fiber;
state->vm_jmp_buf = janet_vm.signal_buf;
state->vm_return_reg = janet_vm.return_reg;
janet_vm.return_reg = &(state->payload);
janet_vm.signal_buf = &(state->buf);
}
void janet_restore(JanetTryState *state) {
janet_vm_stackn = state->stackn;
janet_vm_gc_suspend = state->gc_handle;
janet_vm_fiber = state->vm_fiber;
janet_vm_jmp_buf = state->vm_jmp_buf;
janet_vm_return_reg = state->vm_return_reg;
janet_vm.stackn = state->stackn;
janet_vm.gc_suspend = state->gc_handle;
janet_vm.fiber = state->vm_fiber;
janet_vm.signal_buf = state->vm_jmp_buf;
janet_vm.return_reg = state->vm_return_reg;
}
static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) {
@ -1373,13 +1381,13 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
/* Continue child fiber if it exists */
if (fiber->child) {
if (janet_vm_root_fiber == NULL) janet_vm_root_fiber = fiber;
if (janet_vm.root_fiber == NULL) janet_vm.root_fiber = fiber;
JanetFiber *child = fiber->child;
uint32_t instr = (janet_stack_frame(fiber->data + fiber->frame)->pc)[0];
janet_vm_stackn++;
janet_vm.stackn++;
JanetSignal sig = janet_continue(child, in, &in);
janet_vm_stackn--;
if (janet_vm_root_fiber == fiber) janet_vm_root_fiber = NULL;
janet_vm.stackn--;
if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL;
if (sig != JANET_SIGNAL_OK && !(child->flags & (1 << sig))) {
*out = in;
janet_fiber_set_status(fiber, sig);
@ -1425,14 +1433,14 @@ static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *o
JanetSignal sig = janet_try(&tstate);
if (!sig) {
/* Normal setup */
if (janet_vm_root_fiber == NULL) janet_vm_root_fiber = fiber;
janet_vm_fiber = fiber;
if (janet_vm.root_fiber == NULL) janet_vm.root_fiber = fiber;
janet_vm.fiber = fiber;
janet_fiber_set_status(fiber, JANET_STATUS_ALIVE);
sig = run_vm(fiber, in);
}
/* Restore */
if (janet_vm_root_fiber == fiber) janet_vm_root_fiber = NULL;
if (janet_vm.root_fiber == fiber) janet_vm.root_fiber = NULL;
janet_fiber_set_status(fiber, sig);
janet_restore(&tstate);
fiber->last_value = tstate.payload;
@ -1485,7 +1493,9 @@ JanetSignal janet_pcall(
Janet janet_mcall(const char *name, int32_t argc, Janet *argv) {
/* At least 1 argument */
if (argc < 1) janet_panicf("method :%s expected at least 1 argument");
if (argc < 1) {
janet_panicf("method :%s expected at least 1 argument", name);
}
/* Find method */
Janet method = janet_method_lookup(argv[0], name);
if (janet_checktype(method, JANET_NIL)) {
@ -1497,42 +1507,58 @@ Janet janet_mcall(const char *name, int32_t argc, Janet *argv) {
/* Setup VM */
int janet_init(void) {
/* Garbage collection */
janet_vm_blocks = NULL;
janet_vm_next_collection = 0;
janet_vm_gc_interval = 0x400000;
janet_vm_block_count = 0;
janet_vm.blocks = NULL;
janet_vm.next_collection = 0;
janet_vm.gc_interval = 0x400000;
janet_vm.block_count = 0;
janet_symcache_init();
/* Initialize gc roots */
janet_vm_roots = NULL;
janet_vm_root_count = 0;
janet_vm_root_capacity = 0;
janet_vm.roots = NULL;
janet_vm.root_count = 0;
janet_vm.root_capacity = 0;
/* Scratch memory */
janet_scratch_mem = NULL;
janet_scratch_len = 0;
janet_scratch_cap = 0;
janet_vm.user = NULL;
janet_vm.scratch_mem = NULL;
janet_vm.scratch_len = 0;
janet_vm.scratch_cap = 0;
/* Initialize registry */
janet_vm_registry = janet_table(0);
janet_vm_abstract_registry = janet_table(0);
janet_gcroot(janet_wrap_table(janet_vm_registry));
janet_gcroot(janet_wrap_table(janet_vm_abstract_registry));
janet_vm.registry = NULL;
janet_vm.registry_cap = 0;
janet_vm.registry_count = 0;
janet_vm.registry_dirty = 0;
/* Intialize abstract registry */
janet_vm.abstract_registry = janet_table(0);
janet_gcroot(janet_wrap_table(janet_vm.abstract_registry));
/* Traversal */
janet_vm_traversal = NULL;
janet_vm_traversal_base = NULL;
janet_vm_traversal_top = NULL;
janet_vm.traversal = NULL;
janet_vm.traversal_base = NULL;
janet_vm.traversal_top = NULL;
/* Core env */
janet_vm_core_env = NULL;
janet_vm.core_env = NULL;
/* Auto suspension */
janet_vm.auto_suspend = 0;
/* Dynamic bindings */
janet_vm_top_dyns = NULL;
janet_vm.top_dyns = NULL;
/* Seed RNG */
janet_rng_seed(janet_default_rng(), 0);
/* Fibers */
janet_vm_fiber = NULL;
janet_vm_root_fiber = NULL;
janet_vm_stackn = 0;
#ifdef JANET_THREADS
janet_threads_init();
#endif
janet_vm.fiber = NULL;
janet_vm.root_fiber = NULL;
janet_vm.stackn = 0;
#ifdef JANET_EV
janet_ev_init();
#endif
@ -1546,20 +1572,19 @@ int janet_init(void) {
void janet_deinit(void) {
janet_clear_memory();
janet_symcache_deinit();
janet_free(janet_vm_roots);
janet_vm_roots = NULL;
janet_vm_root_count = 0;
janet_vm_root_capacity = 0;
janet_vm_registry = NULL;
janet_vm_abstract_registry = NULL;
janet_vm_core_env = NULL;
janet_vm_top_dyns = NULL;
janet_free(janet_vm_traversal_base);
janet_vm_fiber = NULL;
janet_vm_root_fiber = NULL;
#ifdef JANET_THREADS
janet_threads_deinit();
#endif
janet_free(janet_vm.roots);
janet_vm.roots = NULL;
janet_vm.root_count = 0;
janet_vm.root_capacity = 0;
janet_vm.abstract_registry = NULL;
janet_vm.core_env = NULL;
janet_vm.top_dyns = NULL;
janet_vm.user = NULL;
janet_free(janet_vm.traversal_base);
janet_vm.fiber = NULL;
janet_vm.root_fiber = NULL;
janet_free(janet_vm.registry);
janet_vm.registry = NULL;
#ifdef JANET_EV
janet_ev_deinit();
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -163,7 +163,7 @@ Janet(janet_wrap_number)(double x) {
void *janet_memalloc_empty(int32_t count) {
int32_t i;
void *mem = janet_malloc((size_t) count * sizeof(JanetKV));
janet_vm_next_collection += (size_t) count * sizeof(JanetKV);
janet_vm.next_collection += (size_t) count * sizeof(JanetKV);
if (NULL == mem) {
JANET_OUT_OF_MEMORY;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -145,16 +145,17 @@ extern "C" {
#endif
/* Define how global janet state is declared */
/* Also enable the thread library only if not single-threaded */
#ifdef JANET_SINGLE_THREADED
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#elif defined(__GNUC__)
#define JANET_THREAD_LOCAL __thread
#define JANET_THREADS
#elif defined(_MSC_BUILD)
#define JANET_THREAD_LOCAL __declspec(thread)
#define JANET_THREADS
#else
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#endif
/* Enable or disable dynamic module loading. Enabled by default. */
@ -172,11 +173,6 @@ extern "C" {
#define JANET_PEG
#endif
/* Enable or disable the typedarray module */
#ifndef JANET_NO_TYPED_ARRAY
#define JANET_TYPED_ARRAY
#endif
/* Enable or disable event loop */
#if !defined(JANET_NO_EV) && !defined(__EMSCRIPTEN__)
#define JANET_EV
@ -192,6 +188,21 @@ extern "C" {
#define JANET_INT_TYPES
#endif
/* Enable or disable epoll on Linux */
#if defined(JANET_LINUX) && !defined(JANET_EV_NO_EPOLL)
#define JANET_EV_EPOLL
#endif
/* Enable or disable kqueue on BSD */
#if defined(JANET_BSD) && !defined(JANET_EV_NO_KQUEUE)
#define JANET_EV_KQUEUE
#endif
/* Enable or disable kqueue on Apple */
#if defined(JANET_APPLE) && !defined(JANET_EV_NO_KQUEUE)
#define JANET_EV_KQUEUE
#endif
/* How to export symbols */
#ifndef JANET_API
#ifdef JANET_WINDOWS
@ -299,9 +310,10 @@ typedef struct {
/***** START SECTION TYPES *****/
#ifdef JANET_WINDOWS
// Must be defined before including stdlib.h
/* Must be defined before including stdlib.h */
#define _CRT_RAND_S
#endif
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
@ -310,6 +322,25 @@ typedef struct {
#include <stddef.h>
#include <stdio.h>
/* Some extra includes if EV is enabled */
#ifdef JANET_EV
#ifdef JANET_WINDOWS
typedef struct JanetDudCriticalSection {
/* Avoid including windows.h here - instead, create a structure of the same size */
/* Needs to be same size as crtical section see WinNT.h for CRITCIAL_SECTION definition */
void *debug_info;
long lock_count;
long recursion_count;
void *owning_thread;
void *lock_semaphore;
unsigned long spin_count;
} JanetOSMutex;
#else
#include <pthread.h>
typedef pthread_mutex_t JanetOSMutex;
#endif
#endif
#ifdef JANET_BSD
int _setjmp(jmp_buf);
JANET_NO_RETURN void _longjmp(jmp_buf, int);
@ -348,6 +379,7 @@ typedef enum {
} JanetSignal;
#define JANET_SIGNAL_EVENT JANET_SIGNAL_USER9
#define JANET_SIGNAL_INTERRUPT JANET_SIGNAL_USER8
/* Fiber statuses - mostly corresponds to signals. */
typedef enum {
@ -369,6 +401,9 @@ typedef enum {
JANET_STATUS_ALIVE
} JanetFiberStatus;
/* For encapsulating all thread-local Janet state (except natives) */
typedef struct JanetVM JanetVM;
/* Use type punning for GC objects */
typedef struct JanetGCObject JanetGCObject;
@ -392,6 +427,7 @@ typedef struct JanetKV JanetKV;
typedef struct JanetStackFrame JanetStackFrame;
typedef struct JanetAbstractType JanetAbstractType;
typedef struct JanetReg JanetReg;
typedef struct JanetRegExt JanetRegExt;
typedef struct JanetMethod JanetMethod;
typedef struct JanetSourceMapping JanetSourceMapping;
typedef struct JanetView JanetView;
@ -827,7 +863,10 @@ JANET_API JanetAbstract janet_checkabstract(Janet x, const JanetAbstractType *at
* list of blocks, which is naive but works. */
struct JanetGCObject {
int32_t flags;
JanetGCObject *next;
union {
JanetGCObject *next;
int32_t refcount; /* For threaded abstract types */
} data;
};
/* A lightweight green thread in janet. Does not correspond to
@ -1083,6 +1122,14 @@ struct JanetReg {
const char *documentation;
};
struct JanetRegExt {
const char *name;
JanetCFunction cfun;
const char *documentation;
const char *source_file;
int32_t source_line;
};
struct JanetMethod {
const char *name;
JanetCFunction cfun;
@ -1268,10 +1315,36 @@ extern enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT];
#ifdef JANET_EV
extern JANET_API const JanetAbstractType janet_stream_type;
extern JANET_API const JanetAbstractType janet_channel_type;
/* Run the event loop */
JANET_API void janet_loop(void);
/* Run the event loop, but allow for user scheduled interrupts triggered
* by janet_loop1_interrupt being called in library code, a signal handler, or
* another thread.
*
* Example:
*
* while (!janet_loop_done()) {
* // One turn of the event loop
* JanetFiber *interrupted_fiber = janet_loop1();
* // interrupted_fiber may be NULL
* // do some work here periodically...
* if (NULL != interrupted_fiber) {
* if (cancel_interrupted_fiber) {
* janet_cancel(interrupted_fiber, janet_cstringv("fiber was interrupted for [reason]"));
* } else {
* janet_schedule(interrupted_fiber, janet_wrap_nil());
* }
* }
* }
*
*/
JANET_API int janet_loop_done(void);
JANET_API JanetFiber *janet_loop1(void);
JANET_API void janet_loop1_interrupt(JanetVM *vm);
/* Wrapper around streams */
JANET_API JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods);
JANET_API void janet_stream_close(JanetStream *stream);
@ -1299,7 +1372,20 @@ JANET_API void janet_addtimeout(double sec);
JANET_API void janet_ev_inc_refcount(void);
JANET_API void janet_ev_dec_refcount(void);
/* Get last error from a an IO operation */
/* Thread aware abstract types and helpers */
JANET_API void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size);
JANET_API void *janet_abstract_end_threaded(void *x);
JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size);
JANET_API int32_t janet_abstract_incref(void *abst);
JANET_API int32_t janet_abstract_decref(void *abst);
/* Expose some OS sync primitives to make portable abstract types easier to implement */
JANET_API void janet_os_mutex_init(JanetOSMutex *mutex);
JANET_API void janet_os_mutex_deinit(JanetOSMutex *mutex);
JANET_API void janet_os_mutex_lock(JanetOSMutex *mutex);
JANET_API void janet_os_mutex_unlock(JanetOSMutex *mutex);
/* Get last error from an IO operation */
JANET_API Janet janet_ev_lasterr(void);
/* Async service for calling a function or syscall in a background thread. This is not
@ -1313,6 +1399,7 @@ typedef struct {
int tag;
int argi;
void *argp;
Janet argj;
JanetFiber *fiber;
} JanetEVGenericMessage;
@ -1335,13 +1422,20 @@ typedef struct {
/* Function pointer that is run in the thread pool */
typedef JanetEVGenericMessage(*JanetThreadedSubroutine)(JanetEVGenericMessage arguments);
/* Handler that is run in the main thread with the result of the JanetAsyncSubroutine */
/* Handler for events posted to the event loop */
typedef void (*JanetCallback)(JanetEVGenericMessage return_value);
/* Handler that is run in the main thread with the result of the JanetAsyncSubroutine (same as JanetCallback) */
typedef void (*JanetThreadedCallback)(JanetEVGenericMessage return_value);
/* API calls for quickly offloading some work in C to a new thread or thread pool. */
JANET_API void janet_ev_threaded_call(JanetThreadedSubroutine fp, JanetEVGenericMessage arguments, JanetThreadedCallback cb);
JANET_NO_RETURN JANET_API void janet_ev_threaded_await(JanetThreadedSubroutine fp, int tag, int argi, void *argp);
/* Post callback + userdata to an event loop. Takes the vm parameter to allow posting from other
* threads or signal handlers. Use NULL to post to the current thread. */
JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGenericMessage msg);
/* Callback used by janet_ev_threaded_await */
JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value);
@ -1410,16 +1504,26 @@ struct JanetCompileResult {
enum JanetCompileStatus status;
};
JANET_API JanetCompileResult janet_compile(Janet source, JanetTable *env, JanetString where);
JANET_API JanetCompileResult janet_compile_lint(
Janet source,
JanetTable *env,
JanetString where,
JanetArray *lints);
/* Get the default environment for janet */
JANET_API JanetTable *janet_core_env(JanetTable *replacements);
JANET_API JanetTable *janet_core_lookup_table(JanetTable *replacements);
/* Execute strings */
JANET_API int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out);
JANET_API int janet_dostring(JanetTable *env, const char *str, const char *sourcePath, Janet *out);
/* Run the entrypoint of a wrapped program */
JANET_API int janet_loop_fiber(JanetFiber *fiber);
/* Number scanning */
JANET_API int janet_scan_number(const uint8_t *str, int32_t len, double *out);
JANET_API int janet_scan_number_base(const uint8_t *str, int32_t len, int32_t base, double *out);
JANET_API int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out);
JANET_API int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out);
@ -1436,6 +1540,7 @@ JANET_API JanetRNG *janet_default_rng(void);
JANET_API void janet_rng_seed(JanetRNG *rng, uint32_t seed);
JANET_API void janet_rng_longseed(JanetRNG *rng, const uint8_t *bytes, int32_t len);
JANET_API uint32_t janet_rng_u32(JanetRNG *rng);
JANET_API double janet_rng_double(JanetRNG *rng);
/* Array functions */
JANET_API JanetArray *janet_array(int32_t capacity);
@ -1529,6 +1634,7 @@ JANET_API const JanetKV *janet_struct_find(JanetStruct st, Janet key);
/* Table functions */
JANET_API JanetTable *janet_table(int32_t capacity);
JANET_API JanetTable *janet_table_init(JanetTable *table, int32_t capacity);
JANET_API JanetTable *janet_table_init_raw(JanetTable *table, int32_t capacity);
JANET_API void janet_table_deinit(JanetTable *table);
JANET_API Janet janet_table_get(JanetTable *t, Janet key);
JANET_API Janet janet_table_get_ex(JanetTable *t, Janet key, JanetTable **which);
@ -1644,6 +1750,12 @@ JANET_API int32_t janet_sorted_keys(const JanetKV *dict, int32_t cap, int32_t *i
/* VM functions */
JANET_API int janet_init(void);
JANET_API void janet_deinit(void);
JANET_API JanetVM *janet_vm_alloc(void);
JANET_API JanetVM *janet_local_vm(void);
JANET_API void janet_vm_free(JanetVM *vm);
JANET_API void janet_vm_save(JanetVM *into);
JANET_API void janet_vm_load(JanetVM *from);
JANET_API void janet_interpreter_interrupt(JanetVM *vm);
JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out);
JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig);
JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f);
@ -1668,12 +1780,24 @@ typedef enum {
JANET_BINDING_VAR,
JANET_BINDING_MACRO
} JanetBindingType;
typedef struct {
JanetBindingType type;
Janet value;
enum {
JANET_BINDING_DEP_NONE,
JANET_BINDING_DEP_RELAXED,
JANET_BINDING_DEP_NORMAL,
JANET_BINDING_DEP_STRICT,
} deprecation;
} JanetBinding;
JANET_API void janet_def(JanetTable *env, const char *name, Janet val, const char *documentation);
JANET_API void janet_var(JanetTable *env, const char *name, Janet val, const char *documentation);
JANET_API void janet_cfuns(JanetTable *env, const char *regprefix, const JanetReg *cfuns);
JANET_API void janet_cfuns_prefix(JanetTable *env, const char *regprefix, const JanetReg *cfuns);
JANET_API JanetBindingType janet_resolve(JanetTable *env, JanetSymbol sym, Janet *out);
JANET_API void janet_register(const char *name, JanetCFunction cfun);
JANET_API JanetBinding janet_resolve_ext(JanetTable *env, JanetSymbol sym);
/* Get values from the core environment. */
JANET_API Janet janet_resolve_core(const char *name);
@ -1683,6 +1807,70 @@ JANET_API Janet janet_resolve_core(const char *name);
/* Shorthand for janet C function declarations */
#define JANET_CFUN(name) Janet name (int32_t argc, Janet *argv)
/* Declare a C function with documentation and source mapping */
#define JANET_REG_END {NULL, NULL, NULL, NULL, 0}
/* no docstrings or sourcemaps */
#define JANET_REG_(JNAME, CNAME) {JNAME, CNAME, NULL, NULL, 0}
#define JANET_FN_(CNAME, USAGE, DOCSTRING) \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_(ENV, JNAME, VAL, DOC) \
janet_def(ENV, JNAME, VAL, NULL)
/* sourcemaps only */
#define JANET_REG_S(JNAME, CNAME) {JNAME, CNAME, NULL, __FILE__, CNAME##_sourceline_}
#define JANET_FN_S(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_S(ENV, JNAME, VAL, DOC) \
janet_def_sm(ENV, JNAME, VAL, NULL, __FILE__, __LINE__)
/* docstring only */
#define JANET_REG_D(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, NULL, 0}
#define JANET_FN_D(CNAME, USAGE, DOCSTRING) \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_D(ENV, JNAME, VAL, DOC) \
janet_def(ENV, JNAME, VAL, DOC)
/* sourcemaps and docstrings */
#define JANET_REG_SD(JNAME, CNAME) {JNAME, CNAME, CNAME##_docstring_, __FILE__, CNAME##_sourceline_}
#define JANET_FN_SD(CNAME, USAGE, DOCSTRING) \
static int32_t CNAME##_sourceline_ = __LINE__; \
static const char CNAME##_docstring_[] = USAGE "\n\n" DOCSTRING; \
Janet CNAME (int32_t argc, Janet *argv)
#define JANET_DEF_SD(ENV, JNAME, VAL, DOC) \
janet_def_sm(ENV, JNAME, VAL, DOC, __FILE__, __LINE__)
/* Choose defaults for source mapping and docstring based on config defs */
#if defined(JANET_NO_SOURCEMAPS) && defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_
#define JANET_FN JANET_FN_
#define JANET_DEF JANET_DEF_
#elif defined(JANET_NO_SOURCEMAPS) && !defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_D
#define JANET_FN JANET_FN_D
#define JANET_DEF JANET_DEF_D
#elif !defined(JANET_NO_SOURCEMAPS) && defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_S
#define JANET_FN JANET_FN_S
#define JANET_DEF JANET_DEF_S
#elif !defined(JANET_NO_SOURCEMAPS) && !defined(JANET_NO_DOCSTRINGS)
#define JANET_REG JANET_REG_SD
#define JANET_FN JANET_FN_SD
#define JANET_DEF JANET_DEF_SD
#endif
/* Define things with source mapping information */
JANET_API void janet_cfuns_ext(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
JANET_API void janet_cfuns_ext_prefix(JanetTable *env, const char *regprefix, const JanetRegExt *cfuns);
JANET_API void janet_def_sm(JanetTable *env, const char *name, Janet val, const char *documentation, const char *source_file, int32_t source_line);
JANET_API void janet_var_sm(JanetTable *env, const char *name, Janet val, const char *documentation, const char *source_file, int32_t source_line);
/* Legacy definition of C functions */
JANET_API void janet_register(const char *name, JanetCFunction cfun);
/* Allow setting entry name for static libraries */
#ifdef __cplusplus
#define JANET_MODULE_PREFIX extern "C"
@ -1812,6 +2000,7 @@ JANET_API uint8_t janet_unmarshal_byte(JanetMarshalContext *ctx);
JANET_API void janet_unmarshal_bytes(JanetMarshalContext *ctx, uint8_t *dest, size_t len);
JANET_API Janet janet_unmarshal_janet(JanetMarshalContext *ctx);
JANET_API JanetAbstract janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size);
JANET_API void janet_unmarshal_abstract_reuse(JanetMarshalContext *ctx, void *p);
JANET_API void janet_register_abstract_type(const JanetAbstractType *at);
JANET_API const JanetAbstractType *janet_get_abstract_type(Janet key);
@ -1852,7 +2041,8 @@ typedef enum {
RULE_READINT, /* [(signedness << 4) | (endianess << 5) | bytewidth, tag] */
RULE_LINE, /* [tag] */
RULE_COLUMN, /* [tag] */
RULE_UNREF /* [rule, tag] */
RULE_UNREF, /* [rule, tag] */
RULE_CAPTURE_NUM /* [rule, tag] */
} JanetPegOpcod;
typedef struct {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@ -1021,7 +1021,6 @@ int main(int argc, char **argv) {
janet_init_hash_key(hash_key);
#endif
/* Set up VM */
janet_init();
@ -1048,18 +1047,8 @@ int main(int argc, char **argv) {
JanetFiber *fiber = janet_fiber(janet_unwrap_function(mainfun), 64, 1, mainargs);
fiber->env = env;
#ifdef JANET_EV
janet_gcroot(janet_wrap_fiber(fiber));
janet_schedule(fiber, janet_wrap_nil());
janet_loop();
status = janet_fiber_status(fiber);
#else
Janet out;
status = janet_continue(fiber, janet_wrap_nil(), &out);
if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) {
janet_stacktrace(fiber, out);
}
#endif
/* Run the fiber in an event loop */
status = janet_loop_fiber(fiber);
/* Deinitialize vm */
janet_deinit();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Calvin Rose
* Copyright (c) 2021 Calvin Rose
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@ -1,10 +0,0 @@
/build
/modpath
.cache
.manifests
json.*
jhydro.*
circlet.*
argparse.*
sqlite3.*
path.*

View File

@ -1,26 +0,0 @@
(declare-project
:name "testmod")
(declare-native
:name "testmod"
:source @["testmod.c"])
(declare-native
:name "testmod2"
:source @["testmod2.c"])
(declare-native
:name "testmod3"
:source @["testmod3.cpp"])
(declare-native
:name "test-mod-4"
:source @["testmod4.c"])
(declare-native
:name "testmod5"
:source @["testmod5.cc"])
(declare-executable
:name "testexec"
:entry "testexec.janet")

View File

@ -1,3 +0,0 @@
(import /build/testmod :as testmod)
(if (not= 5 (testmod/get5)) (error "testmod/get5 failed"))

View File

@ -1,9 +0,0 @@
(use /build/testmod)
(use /build/testmod2)
(use /build/testmod3)
(use /build/test-mod-4)
(use /build/testmod5)
(defn main [&]
(print "Hello from executable!")
(print (+ (get5) (get6) (get7) (get8) (get9))))

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2020 Calvin Rose and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* A very simple native module */
#include <janet.h>
static Janet cfun_get_six(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_number(6.0);
}
static const JanetReg array_cfuns[] = {
{"get6", cfun_get_six, NULL},
{NULL, NULL, NULL}
};
JANET_MODULE_ENTRY(JanetTable *env) {
janet_cfuns(env, NULL, array_cfuns);
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2020 Calvin Rose and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* A very simple native module */
#include <janet.h>
#include <iostream>
static Janet cfun_get_seven(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
std::cout << "Hello!" << std::endl;
return janet_wrap_number(7.0);
}
static const JanetReg array_cfuns[] = {
{"get7", cfun_get_seven, NULL},
{NULL, NULL, NULL}
};
JANET_MODULE_ENTRY(JanetTable *env) {
janet_cfuns(env, NULL, array_cfuns);
}

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2020 Calvin Rose and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* A very simple native module */
#include <janet.h>
static Janet cfun_get_eight(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
return janet_wrap_number(8.0);
}
static const JanetReg array_cfuns[] = {
{"get8", cfun_get_eight, NULL},
{NULL, NULL, NULL}
};
JANET_MODULE_ENTRY(JanetTable *env) {
janet_cfuns(env, NULL, array_cfuns);
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2020 Calvin Rose and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* A very simple native module */
#include <janet.h>
#include <iostream>
static Janet cfun_get_nine(int32_t argc, Janet *argv) {
(void) argv;
janet_fixarity(argc, 0);
std::cout << "Hello!" << std::endl;
return janet_wrap_number(9.0);
}
static const JanetReg array_cfuns[] = {
{"get9", cfun_get_nine, NULL},
{NULL, NULL, NULL}
};
JANET_MODULE_ENTRY(JanetTable *env) {
janet_cfuns(env, NULL, array_cfuns);
}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose
# Copyright (c) 2021 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -202,6 +202,7 @@
#🐙🐙🐙🐙
(defn foo [Θa Θb Θc] 0)
(def 🦊 :fox)
(def 🐮 :cow)
(assert (= (string "🐼" 🦊 🐮) "🐼foxcow") "emojis 🙉 :)")

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose
# Copyright (c) 2021 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@ -1,4 +1,4 @@
#' Copyright (c) 2020 Calvin Rose
# Copyright (c) 2021 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose
# Copyright (c) 2021 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose
# Copyright (c) 2021 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose & contributors
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -51,7 +51,10 @@
(assert (deep= (take 0 [1 2 3 4 5]) []) "take 3")
(assert (deep= (take 10 [1 2 3]) [1 2 3]) "take 4")
(assert (deep= (take -1 [:a :b :c]) []) "take 5")
(assert-error :invalid-type (take 3 {}) "take 6")
(assert (deep= (take 3 (generate [x :in [1 2 3 4 5]] x)) @[1 2 3]) "take from fiber")
# NB: repeatedly resuming a fiber created with `generate` includes a `nil` as
# the final element. Thus a generate of 2 elements will create an array of 3.
(assert (= (length (take 4 (generate [x :in [1 2]] x))) 2) "take from short fiber")
# take-until
@ -61,6 +64,8 @@
(assert (deep= (take-until pos? @[-1 -2 3]) [-1 -2]) "take-until 4")
(assert (deep= (take-until pos? @[-1 1 -2]) [-1]) "take-until 5")
(assert (deep= (take-until |(= $ 115) "books") "book") "take-until 6")
(assert (deep= (take-until |(= $ 115) (generate [x :in "books"] x))
@[98 111 111 107]) "take-until from fiber")
# take-while
@ -69,6 +74,8 @@
(assert (deep= (take-while neg? @[-1 -2 -3]) [-1 -2 -3]) "take-while 3")
(assert (deep= (take-while neg? @[-1 -2 3]) [-1 -2]) "take-while 4")
(assert (deep= (take-while neg? @[-1 1 -2]) [-1]) "take-while 5")
(assert (deep= (take-while neg? (generate [x :in @[-1 1 -2]] x))
@[-1]) "take-while from fiber")
# drop

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose & contributors
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose & contributors
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -311,4 +311,6 @@
(tracev (def my-unique-var-name true))
(assert my-unique-var-name "tracev upscopes")
(assert (pos? (length (gensym))) "gensym not empty, regression #753")
(end-suite)

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020 Calvin Rose & contributors
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -344,4 +344,8 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(assert (deep= @[] (peg/match '(* "test" (any 1)) @"test")) "peg empty pattern 5")
(assert (deep= @[] (peg/match '(* "test" (any 1)) (buffer "test"))) "peg empty pattern 6")
# number pattern
(assert (deep= @[111] (peg/match '(number :d+) "111")) "simple number capture 1")
(assert (deep= @[255] (peg/match '(number :w+) "0xff")) "simple number capture 2")
(end-suite)

View File

@ -47,6 +47,11 @@
(assert-no-error "pipe stdin to process 2" (os/proc-wait p))
(assert (= "hello!" (string/trim x)) "round trip pipeline in process"))
(let [p (os/spawn [janet "-e" `(do (ev/sleep 30) (os/exit 24)`] :p)]
(os/proc-kill p)
(def retval (os/proc-wait p))
(assert (not= retval 24) "Process was *not* terminated by parent"))
# Parallel subprocesses
(defn calc-1
@ -100,6 +105,17 @@
(file/close outfile)
(os/rm "unique.txt"))
# Ensure that the stream created by os/open works
(assert-no-error "File writing 4.1"
(def outstream (os/open "unique.txt" :wct))
(defer (:close outstream)
(:write outstream "123\n")
(:write outstream "456\n"))
# Cast to string to enable comparison
(assert (= "123\n456\n" (string (slurp "unique.txt"))) "File writing 4.2")
(os/rm "unique.txt"))
# ev/gather
(assert (deep= @[1 2 3] (ev/gather 1 2 3)) "ev/gather 1")
@ -135,6 +151,38 @@
(:close s))
# Test localname and peername
(repeat 20
(defn check-matching-names [stream]
(def [my-ip my-port] (net/localname stream))
(def [remote-ip remote-port] (net/peername stream))
(def msg (string my-ip " " my-port " " remote-ip " " remote-port))
(def buf @"")
(ev/gather
(net/write stream msg)
(net/read stream 1024 buf))
(def comparison (string/split " " buf))
(assert (and (= my-ip (get comparison 2))
(= (string my-port) (get comparison 3))
(= remote-ip (get comparison 0))
(= (string remote-port) (get comparison 1)))
"localname should match peername"))
# Test on both server and client
(defn names-handler
[stream]
(defer (:close stream)
(check-matching-names stream)))
(with [s (net/server "127.0.0.1" "8000" names-handler)]
(defn test-names []
(with [conn (net/connect "127.0.0.1" "8000")]
(check-matching-names conn)))
(test-names)
(test-names))
(gccollect))
# Create pipe
(var pipe-counter 0)
@ -163,4 +211,46 @@
(assert (os/execute [janet "-e" `(+ 1 2 3)`] :xp) "os/execute self")
# Test some channel
(def c1 (ev/chan))
(def c2 (ev/chan))
(def arr @[])
(ev/spawn
(while (def x (ev/take c1))
(array/push arr x))
(ev/chan-close c2))
(for i 0 1000
(ev/give c1 i))
(ev/chan-close c1)
(ev/take c2)
(assert (= (slice arr) (slice (range 1000))) "ev/chan-close 1")
(def c1 (ev/chan))
(def c2 (ev/chan))
(def arr @[])
(ev/spawn
(while (def x (ev/take c1))
(array/push arr x))
(ev/sleep 0.1)
(ev/chan-close c2))
(for i 0 100
(ev/give c1 i))
(ev/chan-close c1)
(ev/select c2)
(assert (= (slice arr) (slice (range 100))) "ev/chan-close 2")
(def c1 (ev/chan))
(def c2 (ev/chan))
(def arr @[])
(ev/spawn
(while (def x (ev/take c1))
(array/push arr x))
(ev/chan-close c2))
(for i 0 100
(ev/give c1 i))
(ev/chan-close c1)
(ev/rselect c2)
(assert (= (slice arr) (slice (range 100))) "ev/chan-close 3")
(end-suite)

View File

@ -1,4 +1,4 @@
#- Copyright (c) 2020 Calvin Rose & contributors
# Copyright (c) 2021 Calvin Rose & contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
@ -161,10 +161,14 @@
([err] :caught))))
"regression #638"))
# Struct prototypes
(def x (struct/with-proto {1 2 3 4} 5 6))
(def y (-> x marshal unmarshal))
(def z {1 2 3 4})
(assert (= 2 (get x 1)) "struct get proto value 1")
(assert (= 4 (get x 3)) "struct get proto value 2")
(assert (= 6 (get x 5)) "struct get proto value 3")
(assert (= x y) "struct proto marshal equality 1")
(assert (= (getproto x) (getproto y)) "struct proto marshal equality 2")
(assert (= 0 (cmp x y)) "struct proto comparison 1")
@ -179,4 +183,13 @@
(assert (deep-not= x z) "struct proto deep= 2")
(assert (deep-not= y z) "struct proto deep= 3")
# Issue #751
(def t {:side false})
(assert (nil? (get-in t [:side :note])) "get-in with false value")
(assert (= (get-in t [:side :note] "dflt") "dflt")
"get-in with false value and default")
(assert (= (math/gcd 462 1071) 21) "math/gcd 1")
(assert (= (math/lcm 462 1071) 23562) "math/lcm 1")
(end-suite)

Some files were not shown because too many files have changed in this diff Show More