mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-31 15:43:01 +00:00 
			
		
		
		
	Merge branch 'master' into struct-proto
This commit is contained in:
		| @@ -9,4 +9,3 @@ tasks: | ||||
|     gmake | ||||
|     gmake test | ||||
|     sudo gmake install | ||||
|     gmake test-install | ||||
|   | ||||
| @@ -19,5 +19,3 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     sudo ninja install | ||||
|     sudo jpm --verbose install circlet | ||||
|     sudo jpm --verbose install spork | ||||
|   | ||||
| @@ -29,5 +29,4 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     doas ninja install | ||||
|     doas jpm --verbose install circlet | ||||
|  | ||||
|   | ||||
							
								
								
									
										35
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal 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
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										25
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -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" | ||||
							
								
								
									
										67
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -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`. | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										64
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								Makefile
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | ||||
| [](https://gitter.im/janet-language/community) | ||||
|   | ||||
| [](https://ci.appveyor.com/project/bakpakin/janet/branch/master) | ||||
| [](https://travis-ci.org/janet-lang/janet) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml?) | ||||
| [](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"? | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										22
									
								
								examples/threaded-channels.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								examples/threaded-channels.janet
									
									
									
									
									
										Normal 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!")) | ||||
| @@ -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
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								janet.1
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										298
									
								
								jpm.1
									
									
									
									
									
								
							
							
						
						
									
										298
									
								
								jpm.1
									
									
									
									
									
								
							| @@ -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> | ||||
							
								
								
									
										30
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								meson.build
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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> */ | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										190
									
								
								src/core/array.c
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								src/core/array.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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? */ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										202
									
								
								src/core/debug.c
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								src/core/debug.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										1670
									
								
								src/core/ev.c
									
									
									
									
									
								
							
							
						
						
									
										1670
									
								
								src/core/ev.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										203
									
								
								src/core/fiber.c
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								src/core/fiber.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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;\ | ||||
|   | ||||
							
								
								
									
										202
									
								
								src/core/gc.c
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								src/core/gc.c
									
									
									
									
									
								
							| @@ -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; | ||||
|             } | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
							
								
								
									
										325
									
								
								src/core/io.c
									
									
									
									
									
								
							
							
						
						
									
										325
									
								
								src/core/io.c
									
									
									
									
									
								
							| @@ -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."); | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										146
									
								
								src/core/marsh.c
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/core/marsh.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
							
								
								
									
										450
									
								
								src/core/math.c
									
									
									
									
									
								
							
							
						
						
									
										450
									
								
								src/core/math.c
									
									
									
									
									
								
							| @@ -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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										367
									
								
								src/core/net.c
									
									
									
									
									
								
							
							
						
						
									
										367
									
								
								src/core/net.c
									
									
									
									
									
								
							| @@ -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) { | ||||
|   | ||||
							
								
								
									
										652
									
								
								src/core/os.c
									
									
									
									
									
								
							
							
						
						
									
										652
									
								
								src/core/os.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
							
								
								
									
										211
									
								
								src/core/parse.c
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								src/core/parse.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
							
								
								
									
										136
									
								
								src/core/peg.c
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								src/core/peg.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
							
								
								
									
										202
									
								
								src/core/state.h
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								src/core/state.h
									
									
									
									
									
								
							| @@ -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); | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										120
									
								
								src/core/table.c
									
									
									
									
									
								
							
							
						
						
									
										120
									
								
								src/core/table.c
									
									
									
									
									
								
							| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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(®, 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(®, 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 | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
							
								
								
									
										301
									
								
								src/core/util.c
									
									
									
									
									
								
							
							
						
						
									
										301
									
								
								src/core/util.c
									
									
									
									
									
								
							| @@ -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) { | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										197
									
								
								src/core/vm.c
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								src/core/vm.c
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										10
									
								
								test/install/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								test/install/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +0,0 @@ | ||||
| /build | ||||
| /modpath | ||||
| .cache | ||||
| .manifests | ||||
| json.* | ||||
| jhydro.* | ||||
| circlet.* | ||||
| argparse.* | ||||
| sqlite3.* | ||||
| path.* | ||||
| @@ -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") | ||||
| @@ -1,3 +0,0 @@ | ||||
| (import /build/testmod :as testmod) | ||||
|  | ||||
| (if (not= 5 (testmod/get5)) (error "testmod/get5 failed")) | ||||
| @@ -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)))) | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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 🙉 :)") | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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
		Reference in New Issue
	
	Block a user
	 Calvin Rose
					Calvin Rose