mirror of
https://github.com/janet-lang/janet
synced 2024-12-23 15:00:27 +00:00
Merge branch 'master' of https://github.com/bakpakin/janet
This commit is contained in:
commit
02673dd791
32
Makefile
32
Makefile
@ -149,18 +149,6 @@ valtest: $(JANET_TARGET) $(TEST_PROGRAMS)
|
||||
for f in build/*.out; do $(VALGRIND_COMMAND) "$$f" || exit; done
|
||||
for f in test/*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done
|
||||
|
||||
###################
|
||||
##### Natives #####
|
||||
###################
|
||||
|
||||
natives: $(JANET_TARGET)
|
||||
$(MAKE) -C natives/json
|
||||
$(MAKE) -j 8 -C natives/sqlite3
|
||||
|
||||
clean-natives:
|
||||
$(MAKE) -C natives/json clean
|
||||
$(MAKE) -C natives/sqlite3 clean
|
||||
|
||||
########################
|
||||
##### Distribution #####
|
||||
########################
|
||||
@ -168,9 +156,19 @@ clean-natives:
|
||||
dist: build/janet-dist.tar.gz
|
||||
|
||||
build/janet-%.tar.gz: $(JANET_TARGET) src/include/janet/janet.h \
|
||||
janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) README.md
|
||||
janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) \
|
||||
build/doc.html README.md $(wildcard doc/*)
|
||||
tar -czvf $@ $^
|
||||
|
||||
#########################
|
||||
##### Documentation #####
|
||||
#########################
|
||||
|
||||
docs: build/doc.html
|
||||
|
||||
build/doc.html: $(JANET_TARGET) doc/gendoc.janet
|
||||
$(JANET_TARGET) doc/gendoc.janet > build/doc.html
|
||||
|
||||
#################
|
||||
##### Other #####
|
||||
#################
|
||||
@ -189,16 +187,12 @@ install: $(JANET_TARGET)
|
||||
mandb
|
||||
$(LDCONFIG)
|
||||
|
||||
install-libs: natives
|
||||
mkdir -p $(JANET_PATH)
|
||||
cp -r lib $(JANET_PATH)
|
||||
cp natives/*/*.so $(JANET_PATH)
|
||||
|
||||
uninstall:
|
||||
-rm $(BINDIR)/../$(JANET_TARGET)
|
||||
-rm $(LIBDIR)/../$(JANET_LIBRARY)
|
||||
-rm -rf $(INCLUDEDIR)
|
||||
$(LDCONFIG)
|
||||
|
||||
.PHONY: clean install repl debug valgrind test valtest emscripten dist install uninstall \
|
||||
.PHONY: clean install repl debug valgrind test \
|
||||
valtest emscripten dist uninstall docs \
|
||||
$(TEST_PROGRAM_PHONIES) $(TEST_PROGRAM_VALPHONIES)
|
||||
|
24
README.md
24
README.md
@ -1,9 +1,9 @@
|
||||
# janet
|
||||
|
||||
[![Build Status](https://travis-ci.org/bakpakin/janet.svg?branch=master)](https://travis-ci.org/bakpakin/janet)
|
||||
[![Appveyor Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/bakpakin/janet)
|
||||
|
||||
Janet is a functional and imperative programming language and bytecode interpreter. It is a
|
||||
<img src="https://raw.githubusercontent.com/honix/janet/master/assets/janet-w200.png" alt="Janet logo" width=200 align="left">
|
||||
|
||||
**Janet** is a functional and imperative programming language and bytecode interpreter. It is a
|
||||
modern lisp, but lists are replaced
|
||||
by other data structures with better utility and performance (arrays, tables, structs, tuples).
|
||||
The language also bridging bridging to native code written in C, meta-programming with macros, and bytecode assembly.
|
||||
@ -13,12 +13,15 @@ to run script files. This client program is separate from the core runtime, so
|
||||
janet could be embedded into other programs. Try janet in your browser at
|
||||
[https://janet-lang.org](https://janet-lang.org).
|
||||
|
||||
#
|
||||
|
||||
Implemented in mostly standard C99, janet runs on Windows, Linux and macOS.
|
||||
The few features that are not standard C (dynamic library loading, compiler specific optimizations),
|
||||
are fairly straight forward. Janet can be easily ported to new platforms.
|
||||
|
||||
For syntax highlighting, there is some preliminary vim syntax highlighting in [janet.vim](https://github.com/bakpakin/janet.vim).
|
||||
Generic lisp syntax highlighting should, however, provide good results.
|
||||
Generic lisp syntax highlighting should, however, provide good results. There is also a janet.tmLanguage file
|
||||
that should provide good syntax highlighting for many editors.
|
||||
|
||||
## Use Cases
|
||||
|
||||
@ -48,11 +51,16 @@ Janet makes a good system scripting language, or a language to embed in other pr
|
||||
|
||||
## Documentation
|
||||
|
||||
API documentation and design documents can be found in the
|
||||
[wiki](https://github.com/bakpakin/janet/wiki). There is an introduction
|
||||
section in the wiki that contains a good overview of the language.
|
||||
Documentation can be found in the doc directory of
|
||||
the repository. There is an introduction
|
||||
section contains a good overview of the language.
|
||||
|
||||
For individual bindings, use the `(doc symbol-name)` macro to get API
|
||||
API documentation for all bindings can also be generated
|
||||
with `make docs`, which will create `build/doc.html`, which
|
||||
can be viewed with any web browser. This
|
||||
includes all forms in the core library except special forms.
|
||||
|
||||
For individual bindings from within the REPL, use the `(doc symbol-name)` macro to get API
|
||||
documentation for the core library. For example,
|
||||
```
|
||||
(doc doc)
|
||||
|
BIN
assets/janet-big.png
Normal file
BIN
assets/janet-big.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
assets/janet-w200.png
Normal file
BIN
assets/janet-w200.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
@ -43,13 +43,13 @@ mkdir build\mainclient
|
||||
|
||||
@rem Build the sources
|
||||
for %%f in (src\core\*.c) do (
|
||||
@%JANET_COMPILE% /Fobuild\core\%%~nf.obj %%f
|
||||
@%JANET_COMPILE% /Fobuild\core\%%~nf.obj %%f
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
)
|
||||
|
||||
@rem Build the main client
|
||||
for %%f in (src\mainclient\*.c) do (
|
||||
@%JANET_COMPILE% /Fobuild\mainclient\%%~nf.obj %%f
|
||||
@%JANET_COMPILE% /Fobuild\mainclient\%%~nf.obj %%f
|
||||
@if errorlevel 1 goto :BUILDFAIL
|
||||
)
|
||||
|
||||
@ -70,7 +70,7 @@ exit /b 1
|
||||
@rem Show help
|
||||
:HELP
|
||||
@echo.
|
||||
@echo Usage: build_windows [subcommand=clean,help,test]
|
||||
@echo Usage: build_windows [subcommand=clean,help,test,dist]
|
||||
@echo.
|
||||
@echo Script to build janet on windows. Must be run from the Visual Studio
|
||||
@echo command prompt.
|
||||
@ -85,20 +85,22 @@ exit /b 0
|
||||
@rem Run tests
|
||||
:TEST
|
||||
for %%f in (test/suite*.janet) do (
|
||||
janet.exe test\%%f
|
||||
@if errorlevel 1 goto :TESTFAIL
|
||||
janet.exe test\%%f
|
||||
@if errorlevel 1 goto :TESTFAIL
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
@rem Build a dist directory
|
||||
:DIST
|
||||
mkdir dist
|
||||
janet.exe doc\gendoc.janet > dist\doc.html
|
||||
copy janet.exe dist\janet.exe
|
||||
copy LICENSE dist\LICENSE
|
||||
copy README.md dist\README.md
|
||||
copy janet.lib dist\janet.lib
|
||||
copy janet.exp dist\janet.exp
|
||||
copy src\include\janet\janet.h dist\janet.h
|
||||
xcopy /s doc dist\doc
|
||||
exit /b 0
|
||||
|
||||
:TESTFAIL
|
||||
|
6
doc/Home.md
Normal file
6
doc/Home.md
Normal file
@ -0,0 +1,6 @@
|
||||
Janet is a dynamic, lightweight programming language with strong functional
|
||||
capabilities as well as support for imperative programming. It to be used
|
||||
for short lived scripts as well as for building real programs. It can also
|
||||
be extended with native code (C modules) for better performance and interfacing with
|
||||
existing software. Janet takes ideas from Lua, Scheme, Racket, Clojure, Smalltalk, Erlang, Arc, and
|
||||
a whole bunch of other dynamic languages.
|
746
doc/Introduction.md
Normal file
746
doc/Introduction.md
Normal file
@ -0,0 +1,746 @@
|
||||
# Hello, world!
|
||||
|
||||
Following tradition, a simple Janet program will print "Hello, world!".
|
||||
|
||||
```
|
||||
(print "Hello, world!")
|
||||
```
|
||||
|
||||
Put the following code in a file named `hello.janet`, and run `./janet hello.janet`.
|
||||
The words "Hello, world!" should be printed to the console, and then the program
|
||||
should immediately exit. You now have a working janet program!
|
||||
|
||||
Alternatively, run the program `./janet` without any arguments to enter a REPL,
|
||||
or read eval print loop. This is a mode where Janet functions like a calculator,
|
||||
reading some input from the user, evaluating it, and printing out the result, all
|
||||
in an infinite loop. This is a useful mode for exploring or prototyping in Janet.
|
||||
|
||||
This hello world program is about the simplest program one can write, and consists of only
|
||||
a few pieces of syntax. This first element is the `print` symbol. This is a function
|
||||
that simply prints its arguments to the console. The second argument is the
|
||||
string literal "Hello, world!", which is the one and only argument to the
|
||||
print function. Lastly, the print symbol and the string literal are wrapped
|
||||
in parentheses, forming a tuple. In Janet, parentheses and brackets are interchangeable,
|
||||
brackets are used mostly when the resulting tuple is not a function call. The tuple
|
||||
above indicates that the function `print` is to be called with one argument, `"Hello, world"`.
|
||||
|
||||
Like all lisps, all operations in Janet are in prefix notation; the name of the
|
||||
operator is the first value in the tuple, and the arguments passed to it are
|
||||
in the rest of the tuple.
|
||||
|
||||
# A bit more - Arithmetic
|
||||
|
||||
Any programming language will have some way to do arithmetic. Janet is no exception,
|
||||
and supports the basic arithmetic operators
|
||||
|
||||
```
|
||||
# Prints 13
|
||||
# (1 + (2*2) + (10/5) + 3 + 4 + (5 - 6))
|
||||
(print (+ 1 (* 2 2) (/ 10 5) 3 4 (- 5 6)))
|
||||
```
|
||||
|
||||
Just like the print function, all arithmetic operators are entered in
|
||||
prefix notation. Janet also supports the remainder operator, or `%`, which returns
|
||||
the remainder of division. For example, `(% 10 3)` is 1, and `(% 10.5 3)` is
|
||||
1.5. The lines that begin with `#` are comments.
|
||||
|
||||
Janet actually has two "flavors" of numbers; integers and real numbers. Integers are any
|
||||
integer value between -2,147,483,648 and 2,147,483,647 (32 bit signed integer).
|
||||
Reals are real numbers, and are represented by IEEE-754 double precision floating point
|
||||
numbers. That means that they can represent any number an integer can represent, as well
|
||||
fractions to very high precision.
|
||||
|
||||
Although real numbers can represent any value an integer can, try to distinguish between
|
||||
real numbers and integers in your program. If you are using a number to index into a structure,
|
||||
you probably want integers. Otherwise, you may want to use reals (this is only a rule of thumb).
|
||||
|
||||
Arithmetic operator will convert integers to real numbers if needed, but real numbers
|
||||
will not be converted to integers, as not all real numbers can be safely converted to integers.
|
||||
|
||||
## Numeric literals
|
||||
|
||||
Numeric literals can be written in many ways. Numbers can be written in base 10, with
|
||||
underscores used to separate digits into groups. A decimal point can be used for floating
|
||||
point numbers. Numbers can also be written in other bases by prefixing the number with the desired
|
||||
base and the character 'r'. For example, 16 can be written as `16`, `1_6`, `16r10`, `4r100`, or `0x10`. The
|
||||
`0x` prefix can be used for hexadecimal as it is so common. The radix must be themselves written in base 10, and
|
||||
can be any integer from 2 to 36. For any radix above 10, use the letters as digits (not case sensitive).
|
||||
|
||||
Numbers can also be in scientific notation such as `3e10`. A custom radix can be used as well
|
||||
as for scientific notation numbers, (the exponent will share the radix). For numbers in scientific
|
||||
notation with a radix besides 10, use the `&` symbol to indicate the exponent rather then `e`.
|
||||
|
||||
## Arithmetic Functions
|
||||
|
||||
Besides the 5 main arithmetic functions, janet also supports a number of math functions
|
||||
taken from the C library `<math.h>`, as well as bitwise operators that behave like they
|
||||
do in C or Java. Functions like `math/sin`, `math/cos`, `math/log`, and `math/exp` will
|
||||
behave as expected to a C programmer. They all take either 1 or 2 numeric arguments and
|
||||
return a real number (never an integer!) Bitwise functions are all prefixed with b.
|
||||
Thet are `bnot`, `bor`, `bxor`, `band`, `blshift`, `brshift`, and `brushift`. Bitwise
|
||||
functions only work on integers.
|
||||
|
||||
# Strings, Keywords and Symbols
|
||||
|
||||
Janet supports several varieties of types that can be used as labels for things in
|
||||
your program. The most useful type for this purpose is the keyword type. A keyword
|
||||
begins with a semicolon, and then contains 0 or more alphanumeric or a few other common
|
||||
characters. For example, `:hello`, `:my-name`, `::`, and `:ABC123_-*&^%$` are all keywords.
|
||||
Keywords are actually just special cases of symbols, which are similar but don't start with
|
||||
a semicolon. The difference between symbols and keywords is that keywords evaluate to themselves, while
|
||||
symbols evaluate to whatever they are bound to. To have a symbol evaluate to itself, it must be
|
||||
quoted.
|
||||
|
||||
```lisp
|
||||
# Evaluates to :monday
|
||||
:monday
|
||||
|
||||
# Will throw a compile error as monday is not defined
|
||||
monday
|
||||
|
||||
# Quote it - evaluates to the symbol monday
|
||||
'monday
|
||||
|
||||
# Or first define monday
|
||||
(def monday "It is monday")
|
||||
|
||||
# Now the evaluation should work - monday evaluates to "It is monday"
|
||||
monday
|
||||
```
|
||||
|
||||
The most common thing to do with a keyword is to check it for equality or use it as a key into
|
||||
a table or struct. Note that symbols, keywords and strings are all immutable. Besides making your
|
||||
code easier to reason about, it allows for many optimizations involving these types.
|
||||
|
||||
```lisp
|
||||
# Evaluates to true
|
||||
(= :hello :hello)
|
||||
|
||||
# Evaluates to false, everything in janet is case sensitive
|
||||
(= :hello :HeLlO)
|
||||
|
||||
# Look up into a table - evaluates to 25
|
||||
(get {
|
||||
:name "John"
|
||||
:age 25
|
||||
:occupation "plumber"
|
||||
} :age)
|
||||
```
|
||||
|
||||
Strings can be used similarly to keywords, but there primary usage is for defining either text
|
||||
or arbitrary sequences of bytes. Strings (and symbols) in janet are what is sometimes known as
|
||||
"8-bit clean"; they can hold any number of bytes, and are completely unaware of things like character
|
||||
encodings. This is completely compatible with ASCII and UTF-8, two of the most common character
|
||||
encodings. By being encoding agnostic, janet strings can be very simple, fast, and useful for
|
||||
for other uses besides holding text.
|
||||
|
||||
Literal text can be entered inside quotes, as we have seen above.
|
||||
|
||||
```
|
||||
"Hello, this is a string."
|
||||
|
||||
# We can also add escape characters for newlines, double quotes, backslash, tabs, etc.
|
||||
"Hello\nThis is on line two\n\tThis is indented\n"
|
||||
|
||||
# For long strings where you don't want to type a lot of escape characters,
|
||||
# you can use 1 or more backticks (`\``) to delimit a string.
|
||||
# To close this string, simply repeat the opening sequence of backticks
|
||||
``
|
||||
This is a string.
|
||||
Line 2
|
||||
Indented
|
||||
"We can just type quotes here", and backslashes \ no problem.
|
||||
``
|
||||
```
|
||||
|
||||
# Functions
|
||||
|
||||
Janet is a functional language - that means that one of the basic building blocks of your
|
||||
program will be defining functions (the other is using data structures). Because janet
|
||||
is a Lisp, functions are values just like numbers or strings - they can be passed around and
|
||||
created as needed.
|
||||
|
||||
Functions can be defined with the `defn` macro, like so:
|
||||
|
||||
```lisp
|
||||
(defn triangle-area
|
||||
"Calculates the area of a triangle."
|
||||
[base height]
|
||||
(print "calculating area of a triangle...")
|
||||
(* base height 0.5))
|
||||
```
|
||||
|
||||
A function defined with `defn` consists of a name, a number of optional flags for def, and
|
||||
finally a function body. The example above is named triangle-area and takes two parameters named base and height. The body of the function will print a message and then evaluate to the area of the triangle.
|
||||
|
||||
Once a function like the above one is defined, the programmer can use the `triangle-area`
|
||||
function just like any other, say `print` or `+`.
|
||||
|
||||
```lisp
|
||||
# Prints "calculating area of a triangle..." and then "25"
|
||||
(print (triangle-area 5 10))
|
||||
```
|
||||
|
||||
Note that when nesting function calls in other function calls like above (a call to triangle-area is
|
||||
nested inside a call to print), the inner function calls are evaluated first. Also, arguments to
|
||||
a function call are evaluated in order, from first argument to last argument).
|
||||
|
||||
Because functions are first-class values like numbers or strings, they can be passed
|
||||
as arguments to other functions as well.
|
||||
|
||||
```
|
||||
(print triangle-area)
|
||||
```
|
||||
|
||||
This prints the location in memory of the function triangle area.
|
||||
|
||||
Functions don't need to have names. The `fn` keyword can be used to introduce function
|
||||
literals without binding them to a symbol.
|
||||
|
||||
```
|
||||
# Evaluates to 40
|
||||
((fn [x y] (+ x x y)) 10 20)
|
||||
# Also evaluates to 40
|
||||
((fn [x y &] (+ x x y)) 10 20)
|
||||
|
||||
# Will throw an error about the wrong arity
|
||||
((fn [x] x) 1 2)
|
||||
# Will not throw an error about the wrong arity
|
||||
((fn [x &] x) 1 2)
|
||||
```
|
||||
|
||||
The first expression creates an anonymous function that adds twice
|
||||
the first argument to the second, and then calls that function with arguments 10 and 20.
|
||||
This will return (10 + 10 + 20) = 40.
|
||||
|
||||
There is a common macro `defn` that can be used for creating functions and immediately binding
|
||||
them to a name. `defn` works as expected at both the top level and inside another form. There is also
|
||||
the corresponding
|
||||
|
||||
Note that putting an ampersand at the end of the argument list inhibits strict arity checking.
|
||||
This means that such a function will accept fewer or more arguments than specified.
|
||||
|
||||
```lisp
|
||||
(defn myfun [x y]
|
||||
(+ x x y))
|
||||
|
||||
# You can think of defn as a shorthand for def and fn together
|
||||
(def myfun-same (fn [x y]
|
||||
(+ x x Y)))
|
||||
|
||||
(myfun 3 4) # -> 10
|
||||
```
|
||||
|
||||
Janet has many macros provided for you (and you can write your own).
|
||||
Macros are just functions that take your source code
|
||||
and transform it into some other source code, usually automating some repetitive pattern for you.
|
||||
|
||||
# Defs and Vars
|
||||
|
||||
Values can be bound to symbols for later use using the keyword `def`. Using undefined
|
||||
symbols will raise an error.
|
||||
|
||||
```
|
||||
(def a 100)
|
||||
(def b (+ 1 a))
|
||||
(def c (+ b b))
|
||||
(def d (- c 100))
|
||||
```
|
||||
|
||||
Bindings created with def have lexical scoping. Also, bindings created with def are immutable; they
|
||||
cannot be changed after definition. For mutable bindings, like variables in other programming
|
||||
languages, use the `var` keyword. The assignment special form `set` can then be used to update
|
||||
a var.
|
||||
|
||||
```
|
||||
(var myvar 1)
|
||||
(print myvar)
|
||||
(set myvar 10)
|
||||
(print myvar)
|
||||
```
|
||||
|
||||
In the global scope, you can use the `:private` option on a def or var to prevent it from
|
||||
being exported to code that imports your current module. You can also add documentation to
|
||||
a function by passing a string the def or var command.
|
||||
|
||||
```lisp
|
||||
(def mydef :private "This will have priavte scope. My doc here." 123)
|
||||
(var myvar "docstring here" 321)
|
||||
```
|
||||
|
||||
## Scopes
|
||||
|
||||
Defs and vars (collectively known as bindings) live inside what is called a scope. A scope is
|
||||
simply where the bindings are valid. If a binding is referenced outside of its scope, the compiler
|
||||
will throw an error. Scopes are useful for organizing your bindings and my extension your programs.
|
||||
There are two main ways to create a scope in Janet.
|
||||
|
||||
The first is to use the `do` special form. `do` executes a series of statements in a scope
|
||||
and evaluates to the last statement. Bindings create inside the form do not escape outside
|
||||
of its scope.
|
||||
|
||||
```lisp
|
||||
(def a :outera)
|
||||
|
||||
(do
|
||||
(def a 1)
|
||||
(def b 2)
|
||||
(def c 3)
|
||||
(+ a b c)) # -> 6
|
||||
|
||||
a # -> :outera
|
||||
b # -> compile error: "unknown symbol \"b\""
|
||||
c # -> compile error: "unknown symbol \"c\""
|
||||
```
|
||||
|
||||
Any attempt to reference the bindings from the do form after it has finished
|
||||
executing will fail. Also notice who defining `a` inside the do form did not
|
||||
overwrite the original definition of `a` for the global scope.
|
||||
|
||||
The second way to create a scope is to create a closure.
|
||||
The `fn` special form also introduces a scope just like
|
||||
the `do` special form.
|
||||
|
||||
There is another built in macro, `let`, that does multiple defs at once, and then introduces a scope.
|
||||
`let` is a wrapper around a combination of defs and dos, and is the most "functional" way of
|
||||
creating bindings.
|
||||
|
||||
```lisp
|
||||
(let [a 1
|
||||
b 2
|
||||
c 3]
|
||||
(+ a b c)) # -> 6
|
||||
```
|
||||
|
||||
The above is equivalent to the example using `do` and `def`.
|
||||
This is the preferable form in most cases,
|
||||
but using do with multiple defs is fine as well.
|
||||
|
||||
# Data Structures
|
||||
|
||||
Once you have a handle on functions and the primitive value types, you may be wondering how
|
||||
to work with collections of things. Janet has a small number of core data structure types
|
||||
that are very versatile. Tables, Structs, Arrays, Tuples, Strings, and Buffers, are the 6 main
|
||||
built in data structure types. These data structures can be arranged in a useful table describing
|
||||
there relationship to each other.
|
||||
|
||||
| | Mutable | Immutable |
|
||||
| ---------- | ------- | --------------- |
|
||||
| Indexed | Array | Tuple |
|
||||
| Dictionary | Table | Struct |
|
||||
| Byteseq | Buffer | String (Symbol) |
|
||||
|
||||
Indexed types are linear lists of elements than can be accessed in constant time with an integer index.
|
||||
Indexed types are backed by a single chunk of memory for fast access, and are indexed from 0 as in C.
|
||||
Dictionary types associate keys with values. The difference between dictionaries and indexed types
|
||||
is that dictionaries are not limited to integer keys. They are backed by a hashtable and also offer
|
||||
constant time lookup (and insertion for the mutable case).
|
||||
Finally, the 'byteseq' abstraction is any type that contains a sequence of bytes. A byteseq associates
|
||||
integer keys (the indices) with integer values between 0 and 255 (the byte values). In this way,
|
||||
they behave much like Arrays and Tuples. However, one cannot put non integer values into a byteseq.
|
||||
|
||||
```lisp
|
||||
(def mytuple (tuple 1 2 3))
|
||||
|
||||
(def myarray @(1 2 3))
|
||||
(def myarray (array 1 2 3))
|
||||
|
||||
(def mystruct {
|
||||
:key "value"
|
||||
:key2 "another"
|
||||
1 2
|
||||
4 3})
|
||||
|
||||
(def another-struct
|
||||
(struct :a 1 :b 2))
|
||||
|
||||
(def my-table @{
|
||||
:a :b
|
||||
:c :d
|
||||
:A :qwerty})
|
||||
(def another-table
|
||||
(table 1 2 3 4))
|
||||
|
||||
(def my-buffer @"thisismutable")
|
||||
(def my-buffer2 @```
|
||||
This is also mutable ":)"
|
||||
```)
|
||||
```
|
||||
|
||||
To read the values in a data structure, use the get function. The first parameter is the data structure
|
||||
itself, and the second parameter is the key.
|
||||
|
||||
```lisp
|
||||
(get @{:a 1} :a) # -> 1
|
||||
(get {:a 1} :a) # -> 1
|
||||
(get @[:a :b :c] 2) # -> :c
|
||||
(get (tuple "a" "b" "c") 1) # -> "b"
|
||||
(get @"hello, world" 1) # -> 101
|
||||
(get "hello, world" 0) # -> 104
|
||||
```
|
||||
|
||||
### Destructuring
|
||||
|
||||
In many cases, however, you do not need the `get` function at all. Janet supports destructuring, which
|
||||
means both the `def` and `var` special forms can extract values from inside structures themselves.
|
||||
|
||||
```lisp
|
||||
# Before, we might do
|
||||
(def my-array @[:mary :had :a :little :lamb])
|
||||
(def lamb (get my-array 4))
|
||||
(print lamb) # Prints :lamb
|
||||
|
||||
# Now, with destructuring,
|
||||
(def [_ _ _ _ lamb] my-array)
|
||||
(print lamb) # Again, prints :lamb
|
||||
|
||||
# Destructuring works with tables as well
|
||||
(def person @{:name "Bob Dylan" :age 77}
|
||||
(def
|
||||
{:name person-name
|
||||
:age person-age} person)
|
||||
```
|
||||
To update a mutable data structure, use the `put` function. It takes 3 arguments, the data structure,
|
||||
the key, and the value, and returns the data structure. The allowed types keys and values
|
||||
depend on what data structure is passed in.
|
||||
|
||||
```lisp
|
||||
(put @[] 100 :a)
|
||||
(put @{} :key "value")
|
||||
(put @"" 100 92)
|
||||
```
|
||||
|
||||
Note that for Arrays and Buffers, putting an index that is outside the length of the data structure
|
||||
will extend the data structure and fill it with nils in the case of the Array,
|
||||
or 0s in the case of the Buffer.
|
||||
|
||||
The last generic function for all data structures is the `length` function. This returns the number of
|
||||
values in a data structure (the number of keys in a dictionary type).
|
||||
|
||||
# Flow Control
|
||||
|
||||
Janet has only two built in primitives to change flow while inside a function. The first is the
|
||||
`if` special form, which behaves as expected in most functional languages. It takes two or three parameters:
|
||||
a condition, an expression to evaluate to if the condition is true (not nil or false),
|
||||
and an optional condition to evaluate to when the condition is nil or false. If the optional parameter
|
||||
is omitted, the if form evaluates to nil.
|
||||
|
||||
```lisp
|
||||
(if (> 4 3)
|
||||
"4 is greater than 3"
|
||||
"4 is not greater then three") # Evaluates to the first statement
|
||||
|
||||
(if true
|
||||
(print "Hey")) # Will print
|
||||
|
||||
(if false
|
||||
(print "Oy!")) # Will not print
|
||||
```
|
||||
|
||||
The second primitive control flow construct is the while loop. The while behaves much the same
|
||||
as in many other programming languages, including C, Java, and Python. The while loop takes
|
||||
two or more parameters: the first is a condition (like in the `if` statement), that is checked before
|
||||
every iteration of the loop. If it is nil or false, the while loop ends and evaluates to nil. Otherwise,
|
||||
the rest of the parameters will be evaluated sequentially and then the program will return to the beginning
|
||||
of the loop.
|
||||
|
||||
```
|
||||
# Loop from 100 down to 1 and print each time
|
||||
(var i 100)
|
||||
(while (pos? i)
|
||||
(print "the number is " i)
|
||||
(-- i))
|
||||
|
||||
# Print ... until a random number in range [0, 1) is >= 0.9
|
||||
# (math/random evaluates to a value between 0 and 1)
|
||||
(while (> 0.9 (math/random))
|
||||
(print "..."))
|
||||
```
|
||||
|
||||
Besides these special forms, Janet has many macros for both conditional testing and looping
|
||||
that are much better for the majority of cases. For conditional testing, the `cond`, `switch`, and
|
||||
`when` macros can be used to great effect. `cond` can be used for making an if-else chain, where using
|
||||
just raw if forms would result in many parentheses. `case` For looping, the `loop`, `seq`, and `generate`
|
||||
implement janet's form of list comprehension, as in Python or Clojure.
|
||||
|
||||
# The Core Library
|
||||
|
||||
Janet has a built in core library of over 300 functions and macros at the time of writing.
|
||||
While some of these functions may be refactored into separate modules, it is useful to get to know
|
||||
the core to avoid rewriting provided functions.
|
||||
|
||||
For any given function, use the `doc` macro to view the documentation for it in the repl.
|
||||
|
||||
```lisp
|
||||
(doc defn) -> Prints the documentation for "defn"
|
||||
```
|
||||
To see a list of all global functions in the repl, type the command
|
||||
|
||||
```lisp
|
||||
(table/getproto *env*)
|
||||
# Or
|
||||
(all-symbols)
|
||||
```
|
||||
Which will print out every built-in global binding
|
||||
(it will not show your global bindings). To print all
|
||||
of your global bindings, just use \*env\*, which is a var
|
||||
that is bound to the current environment.
|
||||
|
||||
The convention of surrounding a symbol in stars is taken from lisp
|
||||
and Clojure, and indicates a global dynamic variable rather than a normal
|
||||
definition. To get the static environment at the time of compilation, use the
|
||||
`_env` symbol.
|
||||
|
||||
# Prototypes
|
||||
|
||||
To support basic generic programming, Janet tables support a prototype
|
||||
table. A prototype table contains default values for a table if certain keys
|
||||
are not found in the original table. This allows many similar tables to share
|
||||
contents without duplicating memory.
|
||||
|
||||
```lisp
|
||||
# One of many Object Oriented schemes that can
|
||||
# be implented in janet.
|
||||
(def proto1 @{:type :custom1
|
||||
:behave (fn [self x] (print "behaving " x))})
|
||||
(def proto2 @{:type :custom2
|
||||
:behave (fn [self x] (print "behaving 2 " x))})
|
||||
|
||||
(def thing1 (table/setproto @{} proto1))
|
||||
(def thing2 (table/setproto @{} proto2))
|
||||
|
||||
(print thing1:type) # prints :custom1
|
||||
(print thing2:type) # prints :custom2
|
||||
|
||||
(thing1:behave thing1 :a) # prints "behaving :a"
|
||||
(thing2:behave thing2 :b) # prints "behaving 2 :b"
|
||||
```
|
||||
|
||||
Looking up in a table with a prototype can be summed up with the following algorithm.
|
||||
|
||||
1. `(get my-table my-key)` is called.
|
||||
2. my-table is checked for the key if my-key. If there is a value for the key, it is returned.
|
||||
3. if there is a prototype table for my-table, set `my-table = my-table's prototype` and got to 2.
|
||||
4. Return nil as the key was not found.
|
||||
|
||||
Janet will check up to about a 1000 prototypes recursively by default before giving up and returning nil. This
|
||||
is to prevent an infinite loop. This value can be changed by adjusting the `JANET_RECURSION_GUARD` value
|
||||
in janet.h.
|
||||
|
||||
Note that Janet prototypes are not as expressive as metatables in Lua and many other languages.
|
||||
This is by design, as adding Lua or Python like capabilities would not be technically difficult.
|
||||
Users should prefer plain data and functions that operate on them rather than mutable objects
|
||||
with methods.
|
||||
|
||||
# Fibers
|
||||
|
||||
Janet has support for single-core asynchronous programming via coroutines, or fibers.
|
||||
Fibers allow a process to stop and resume execution later, essentially enabling
|
||||
multiple returns from a function. This allows many patterns such a schedules, generators,
|
||||
iterators, live debugging, and robust error handling. Janet's error handling is actually built on
|
||||
top of fibers (when an error is thrown, the parent fiber will handle the error).
|
||||
|
||||
A temporary return from a fiber is called a yield, and can be invoked with the `yield` function.
|
||||
To resume a fiber that has been yielded, use the `resume` function. When resume is called on a fiber,
|
||||
it will only return when that fiber either returns, yields, throws an error, or otherwise emits
|
||||
a signal.
|
||||
|
||||
Different from traditional coroutines, Janet's fibers implement a signaling mechanism, which
|
||||
is used to differentiate different kinds of returns. When a fiber yields or throws an error,
|
||||
control is returned to the calling fiber. The parent fiber must then check what kind of state the
|
||||
fiber is in to differentiate errors from return values from user defined signals.
|
||||
|
||||
To create a fiber, user the `fiber/new` function. The fiber constructor take one or two arguments.
|
||||
the first, necessary argument is the function that the fiber will execute. This function must accept
|
||||
an arity of zero. The next optional argument is a collection of flags checking what kinds of
|
||||
signals to trap and return via `resume`. This is useful so
|
||||
the programmer does not need to handle all different kinds of signals from a fiber. Any un-trapped signals
|
||||
are simply propagated to the next fiber.
|
||||
|
||||
```lisp
|
||||
(def f (fiber/new (fn []
|
||||
(yield 1)
|
||||
(yield 2)
|
||||
(yield 3)
|
||||
(yield 4)
|
||||
5)))
|
||||
|
||||
# Get the status of the fiber (:alive, :dead, :debug, :new, :pending, or :user0-:user9)
|
||||
(print (fiber/status f)) # -> :new
|
||||
|
||||
(print (resume f)) # -> prints 1
|
||||
(print (resume f)) # -> prints 2
|
||||
(print (resume f)) # -> prints 3
|
||||
(print (resume f)) # -> prints 4
|
||||
(print (fiber/status f)) # -> print :pending
|
||||
(print (resume f)) # -> prints 5
|
||||
(print (fiber/status f)) # -> print :dead
|
||||
(print (resume f)) # -> throws an error because the fiber is dead
|
||||
```
|
||||
|
||||
## Using Fibers to Capture Errors
|
||||
|
||||
Besides being used as coroutines, fibers can be used to implement error handling (exceptions).
|
||||
|
||||
```lisp
|
||||
(defn my-function-that-errors [x]
|
||||
(print "start function with " x)
|
||||
(error "oops!")
|
||||
(print "never gets here"))
|
||||
|
||||
# Use the :e flag to only trap errors.
|
||||
(def f (fiber/new my-function-that-errors :e))
|
||||
(def result (resume f))
|
||||
(if (= (fiber/status f) :error)
|
||||
(print "result contains the error")
|
||||
(print "result contains the good result"))
|
||||
```
|
||||
|
||||
# Macros
|
||||
|
||||
Janet supports macros like most lisps. A macro is like a function, but transforms
|
||||
the code itself rather than data. They let you extend the syntax of the language itself.
|
||||
|
||||
You have seen some macros already. The `let`, `loop`, and `defn` forms are macros. When the compiler
|
||||
sees a macro, it evaluates the macro and then compiles the result. We say the macro has been
|
||||
*expanded* after the compiler evaluates it. A simple version of the `defn` macro can
|
||||
be thought of as transforming code of the form
|
||||
|
||||
```lisp
|
||||
(defn1 myfun [x] body)
|
||||
```
|
||||
into
|
||||
```lisp
|
||||
(def myfun (fn myfun [x] body))
|
||||
```
|
||||
|
||||
We could write such a macro like so:
|
||||
|
||||
```lisp
|
||||
(defmacro defn1 [name args body]
|
||||
(tuple 'def name (tuple 'fn name args body)))
|
||||
```
|
||||
|
||||
There are a couple of issues with this macro, but it will work for simple functions
|
||||
quite well.
|
||||
|
||||
The first issue is that our defn2 macro can't define functions with multiple expressions
|
||||
in the body. We can make the macro variadic, just like a function. Here is a second version
|
||||
of this macro.
|
||||
|
||||
```lisp
|
||||
(defmacro defn2 [name args & body]
|
||||
(tuple 'def name (apply tuple 'fn name args body)))
|
||||
```
|
||||
|
||||
Great! Now we can define functions with multiple elements in the body. We can still improve this
|
||||
macro even more though. First, we can add a docstring to it. If someone is using the function later,
|
||||
they can use `(doc defn3)` to get a description of the function. Next, we can rewrite the macro
|
||||
using janet's builtin quasiquoting facilities.
|
||||
|
||||
```lisp
|
||||
(defmacro defn3
|
||||
"Defines a new function."
|
||||
[name args & body]
|
||||
`(def ,name (fn ,name ,args ,;body)))
|
||||
```
|
||||
|
||||
This is functionally identical to our previous version `defn2`, but written in such
|
||||
a way that the macro output is more clear. The leading backtick is shorthand for the
|
||||
`(quasiquote x)` special form, which is like `(quote x)` except we can unquote
|
||||
expressions inside it. The comma in front of `name` and `args` is an unquote, which
|
||||
allows us to put a value in the quasiquote. Without the unquote, the symbol \'name\'
|
||||
would be put in the returned tuple. Without the unquote, every function we defined
|
||||
would be called \'name\'!.
|
||||
|
||||
Similar to name, we must also unquote body. However, a normal unquote doesn't work.
|
||||
See what happens if we use a normal unquote for body as well.
|
||||
|
||||
```lisp
|
||||
(def name 'myfunction)
|
||||
(def args '[x y z])
|
||||
(defn body '[(print x) (print y) (print z)])
|
||||
|
||||
`(def ,name (fn ,name ,args ,body))
|
||||
# -> (def myfunction (fn myfunction (x y z) ((print x) (print y) (print z))))
|
||||
```
|
||||
|
||||
There is an extra set of parentheses around the body of our function! We don't
|
||||
want to put the body *inside* the form `(fn args ...)`, we want to *splice* it
|
||||
into the form. Luckily, janet has the `(splice x)` special form for this purpose,
|
||||
and a shorthand for it, the ; character.
|
||||
When combined with the unquote special, we get the desired output.
|
||||
|
||||
```lisp
|
||||
`(def ,name (fn ,name ,args ,;body))
|
||||
# -> (def myfunction (fn myfunction (x y z) (print x) (print y) (print z)))
|
||||
```
|
||||
|
||||
## Hygiene
|
||||
|
||||
Sometime when we write macros, we must generate symbols for local bindings. Ignoring that
|
||||
it could be written as a function, consider
|
||||
the following macro
|
||||
|
||||
```lisp
|
||||
(defmacro max1
|
||||
"Get the max of two values."
|
||||
[x y]
|
||||
`(if (> ,x ,y) ,x ,y))
|
||||
```
|
||||
|
||||
This almost works, but will evaluate both x and y twice. This is because both show up
|
||||
in the macro twice. For example, `(max1 (do (print 1) 1) (do (print 2) 2))` will
|
||||
print both 1 and 2 twice, which is surprising to a user of this macro.
|
||||
|
||||
We can do better:
|
||||
|
||||
```lisp
|
||||
(defmacro max2
|
||||
"Get the max of two values."
|
||||
[x y]
|
||||
`(let [x ,x
|
||||
y ,y]
|
||||
(if (> x y) x y)))
|
||||
```
|
||||
|
||||
Now we have no double evaluation problem! But we now have an even more subtle problem.
|
||||
What happens in the following code?
|
||||
|
||||
```lisp
|
||||
(def x 10)
|
||||
(max2 8 (+ x 4))
|
||||
```
|
||||
|
||||
We want the max to be 14, but this will actually evaluate to 12! This can be understood
|
||||
if we expand the macro. You can expand macro once in janet using the `(macex1 x)` function.
|
||||
(To expand macros until there are no macros left to expand, use `(macex x)`. Be careful,
|
||||
janet has many macros, so the full expansion may be almost unreadable).
|
||||
|
||||
```lisp
|
||||
(macex1 '(max2 8 (+ x 4)))
|
||||
# -> (let (x 8 y (+ x 4)) (if (> x y) x y))
|
||||
```
|
||||
|
||||
After expansion, y wrongly refers to the x inside the macro (which is bound to 8) rather than the x defined
|
||||
to be 10. The problem is the reuse of the symbol x inside the macro, which overshadowed the original
|
||||
binding.
|
||||
|
||||
Janet provides a general solution to this problem in terms of the `(gensym)` function, which returns
|
||||
a symbol which is guarenteed to be unique and not collide with any symbols defined previously. We can define
|
||||
our macro once more for a fully correct macro.
|
||||
|
||||
```lisp
|
||||
(defmacro max3
|
||||
"Get the max of two values."
|
||||
[x y]
|
||||
(def $x (gensym))
|
||||
(def $y (gensym))
|
||||
`(let [,$x ,x
|
||||
,$y ,y]
|
||||
(if (> ,$x ,$y) ,$x ,$y)))
|
||||
```
|
||||
|
||||
As you can see, macros are very powerful but also are prone to subtle bugs. You must remember that
|
||||
at their core, macros are just functions that output code, and the code that they return must
|
||||
work in many contexts!
|
245
doc/Parser.md
Normal file
245
doc/Parser.md
Normal file
@ -0,0 +1,245 @@
|
||||
# The Parser
|
||||
|
||||
A Janet program begins life as a text file, just a sequence of byte like
|
||||
any other on your system. Janet source files should be UTF-8 or ASCII
|
||||
encoded. Before Janet can compile or run your program, it must transform
|
||||
your source code into a data structure. Janet is a lisp, which means it is
|
||||
homoiconic - code is data, so all of the facilities in the language for
|
||||
manipulating arrays, tuples, strings, and tables can be used for manipulating
|
||||
your source code as well.
|
||||
|
||||
But before janet code is represented as a data structure, it must be read, or parsed,
|
||||
by the janet parser. Called the reader in many other lisps, the parser is a machine
|
||||
that takes in plain text and outputs data structures which can be used by both
|
||||
the compiler and macros. In janet, it is a parser rather than a reader because
|
||||
there is no code execution at read time. This is safer and simpler, and also
|
||||
lets janet syntax serve as a robust data interchange format. While a parser
|
||||
is not extensible, in janet the philosophy is to extend the language via macros
|
||||
rather than reader macros.
|
||||
|
||||
## Nil, True and False
|
||||
|
||||
Nil, true and false are all literals than can be entered as such
|
||||
in the parser.
|
||||
|
||||
```
|
||||
nil
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Symbols
|
||||
|
||||
Janet symbols are represented a sequence of alphanumeric characters
|
||||
not starting with a digit. They can also contain the characters
|
||||
\!, @, $, \%, \^, \&, \*, -, \_, +, =, \|, \~, :, \<, \>, ., \?, \\, /, as
|
||||
well as any Unicode codepoint not in the ascii range.
|
||||
|
||||
By convention, most symbols should be all lower case and use dashes to connect words
|
||||
(sometimes called kebab case).
|
||||
|
||||
Symbols that come from another module often contain a forward slash that separates
|
||||
the name of the module from the name of the definition in the module
|
||||
|
||||
```
|
||||
symbol
|
||||
kebab-case-symbol
|
||||
snake_case_symbol
|
||||
my-module/my-fuction
|
||||
*****
|
||||
!%$^*__--__._+++===~-crazy-symbol
|
||||
*global-var*
|
||||
你好
|
||||
```
|
||||
|
||||
## Keywords
|
||||
|
||||
Janet keywords are really just symbols that begin with the character :. However, they
|
||||
are used differently and treated by the compiler as a constant rather than a name for
|
||||
something. Keywords are used mostly for keys in tables and structs, or pieces of syntax
|
||||
in macros.
|
||||
|
||||
```
|
||||
:keyword
|
||||
:range
|
||||
:0x0x0x0
|
||||
:a-keyword
|
||||
::
|
||||
:
|
||||
```
|
||||
|
||||
## Numbers
|
||||
|
||||
Janet numbers are represented by either 32 bit integers or
|
||||
IEEE-754 floating point numbers. The syntax is similar to that of many other languages
|
||||
as well. Numbers can be written in base 10, with
|
||||
underscores used to separate digits into groups. A decimal point can be used for floating
|
||||
point numbers. Numbers can also be written in other bases by prefixing the number with the desired
|
||||
base and the character 'r'. For example, 16 can be written as `16`, `1_6`, `16r10`, `4r100`, or `0x10`. The
|
||||
`0x` prefix can be used for hexadecimal as it is so common. The radix must be themselves written in base 10, and
|
||||
can be any integer from 2 to 36. For any radix above 10, use the letters as digits (not case sensitive).
|
||||
|
||||
```
|
||||
0
|
||||
12
|
||||
-65912
|
||||
4.98
|
||||
1.3e18
|
||||
1.3E18
|
||||
18r123C
|
||||
11raaa&a
|
||||
1_000_000
|
||||
0xbeef
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
Strings in janet are surrounded by double quotes. Strings are 8bit clean, meaning
|
||||
meaning they can contain any arbitrary sequence of bytes, including embedded
|
||||
0s. To insert a double quote into a string itself, escape
|
||||
the double quote with a backslash. For unprintable characters, you can either use
|
||||
one of a few common escapes, use the `\xHH` escape to escape a single byte in
|
||||
hexidecimal. The supported escapes are:
|
||||
|
||||
- \\xHH Escape a single arbitrary byte in hexidecimal.
|
||||
- \\n Newline (ASCII 10)
|
||||
- \\t Tab character (ASCII 9)
|
||||
- \\r Carriage Return (ASCII 13)
|
||||
- \\0 Null (ASCII 0)
|
||||
- \\z Null (ASCII 0)
|
||||
- \\f Form Feed (ASCII 12)
|
||||
- \\e Escape (ASCII 27)
|
||||
- \\" Double Quote (ASCII 34)
|
||||
- \\\\ Backslash (ASCII 92)
|
||||
|
||||
Strings can also contain literal newline characters that will be ignore.
|
||||
This lets one define a multiline string that does not contain newline characters.
|
||||
|
||||
An alternative way of representing strings in janet is the long string, or the backquote
|
||||
delimited string. A string can also be define to start with a certain number of
|
||||
backquotes, and will end the same number of backquotes. Long strings
|
||||
do not contain escape sequences; all bytes will be parsed literally until
|
||||
ending delimiter is found. This is useful
|
||||
for definining multiline strings with literal newline characters, unprintable
|
||||
characters, or strings that would otherwise require many escape sequences.
|
||||
|
||||
```
|
||||
"This is a string."
|
||||
"This\nis\na\nstring."
|
||||
"This
|
||||
is
|
||||
a
|
||||
string."
|
||||
``
|
||||
This
|
||||
is
|
||||
a
|
||||
string
|
||||
``
|
||||
```
|
||||
|
||||
## Buffers
|
||||
|
||||
Buffers are similar strings except they are mutable data structures. Strings in janet
|
||||
cannot be mutated after created, where a buffer can be changed after creation.
|
||||
The syntax for a buffer is the same as that for a string or long string, but
|
||||
the buffer must be prefixed with the '@' character.
|
||||
|
||||
```
|
||||
@""
|
||||
@"Buffer."
|
||||
@``Another buffer``
|
||||
```
|
||||
|
||||
## Tuples
|
||||
|
||||
Tuples are a sequence of white space separated values surrounded by either parentheses
|
||||
or brackets. The parser considers any of the characters ASCII 32, \\0, \\f, \\n, \\r or \\t
|
||||
to be whitespace.
|
||||
|
||||
```
|
||||
(do 1 2 3)
|
||||
[do 1 2 3]
|
||||
```
|
||||
|
||||
## Arrays
|
||||
|
||||
Arrays are the same as tuples, but have a leading @ to indicate mutability.
|
||||
|
||||
```
|
||||
@(:one :two :three)
|
||||
@[:one :two :three]
|
||||
```
|
||||
|
||||
## Structs
|
||||
|
||||
Structs are represented by a sequence of whitespace delimited key value pairs
|
||||
surrounded by curly braces. The sequence is defined as key1, value1, key2, value2, etc.
|
||||
There must be an even number of items between curly braces or the parser will
|
||||
signal a parse error. Any value can be a key or value. Using nil as a key or
|
||||
value, however, will drop that pair from the parsed struct.
|
||||
|
||||
```
|
||||
{}
|
||||
{:key1 "value1" :key2 :value2 :key3 3}
|
||||
{(1 2 3) (4 5 6)}
|
||||
{@[] @[]}
|
||||
{1 2 3 4 5 6}
|
||||
```
|
||||
## Tables
|
||||
|
||||
Table have the same syntax as structs, except they have the @ prefix to indicate
|
||||
that they are mutable.
|
||||
|
||||
```
|
||||
@{}
|
||||
@{:key1 "value1" :key2 :value2 :key3 3}
|
||||
@{(1 2 3) (4 5 6)}
|
||||
@{@[] @[]}
|
||||
@{1 2 3 4 5 6}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments begin with a \# character and continue until the end of the line.
|
||||
There are no multiline comments. For ricm multiline comments, use a
|
||||
string literal.
|
||||
|
||||
## Shorthands
|
||||
|
||||
Often called reader macros in other lisps, Janet provides several shorthand
|
||||
notations for some forms.
|
||||
|
||||
### 'x
|
||||
|
||||
Shorthand for `(quote x)`
|
||||
|
||||
### ;x
|
||||
|
||||
Shorthand for `(splice x)`
|
||||
|
||||
### ~x
|
||||
|
||||
Shorthand for `(quasiquote x)`
|
||||
|
||||
### ,x
|
||||
|
||||
Shorthand for `(unquote x)`
|
||||
|
||||
These shorthand notations can be combined in any order, allowing
|
||||
forms like `''x` (`(quote (quote x))`), or `,;x` (`(unquote (splice x))`).
|
||||
|
||||
## API
|
||||
|
||||
The parser contains the following functions which exposes
|
||||
the parser state machine as a janet abstract object.
|
||||
|
||||
- `parser/byte`
|
||||
- `parser/consume`
|
||||
- `parser/error`
|
||||
- `parser/flush`
|
||||
- `parser/new`
|
||||
- `parser/produce`
|
||||
- `parser/state`
|
||||
- `parser/status`
|
||||
- `parser/where`
|
31
doc/SQLite.md
Normal file
31
doc/SQLite.md
Normal file
@ -0,0 +1,31 @@
|
||||
# SQLite bindings
|
||||
|
||||
There are some sqlite3 bindings in the directory natives/sqlite3 bundled with
|
||||
the janet source code. They serve mostly as a
|
||||
proof of concept external c library. To use, first compile the module with Make.
|
||||
|
||||
```sh
|
||||
make natives
|
||||
```
|
||||
|
||||
Next, enter the repl and create a database and a table.
|
||||
|
||||
```
|
||||
janet:1:> (import natives/sqlite3 :as sql)
|
||||
nil
|
||||
janet:2:> (def db (sql/open "test.db"))
|
||||
<sqlite3.connection 0x5561A138C470>
|
||||
janet:3:> (sql/eval db `CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);`)
|
||||
@[]
|
||||
janet:4:> (sql/eval db `INSERT INTO customers VALUES(:id, :name);` {:name "John" :id 12345})
|
||||
@[]
|
||||
janet:5:> (sql/eval db `SELECT * FROM customers;`)
|
||||
@[{"id" 12345 "name" "John"}]
|
||||
```
|
||||
|
||||
Finally, close the database connection when done with it.
|
||||
|
||||
```
|
||||
janet:6:> (sql/close db)
|
||||
nil
|
||||
```
|
238
doc/The-Janet-Abstract-Machine-Bytecode.md
Normal file
238
doc/The-Janet-Abstract-Machine-Bytecode.md
Normal file
@ -0,0 +1,238 @@
|
||||
The Janet language is implemented on top of an abstract machine (AM). The compiler
|
||||
converts Janet data structures to this bytecode, which can then be efficiently executed
|
||||
from inside a C program. To understand the janet bytecode, it is useful to understand
|
||||
the abstractions used inside the Janet AM, as well as the C types used to implement these
|
||||
features.
|
||||
|
||||
## The Stack = The Fiber
|
||||
|
||||
A Janet Fiber is the type used to represent multiple concurrent processes
|
||||
in janet. It is basically a wrapper around the idea of a stack. The stack is
|
||||
divided into a number of stack frames (`JanetStackFrame *` in C), each of which
|
||||
contains information such as the function that created the stack frame,
|
||||
the program counter for the stack frame, a pointer to the previous frame,
|
||||
and the size of the frame. Each stack frame also is paired with a number
|
||||
registers.
|
||||
|
||||
```
|
||||
X: Slot
|
||||
|
||||
X
|
||||
X - Stack Top, for next function call.
|
||||
-----
|
||||
Frame next
|
||||
-----
|
||||
X
|
||||
X
|
||||
X
|
||||
X
|
||||
X
|
||||
X
|
||||
X - Stack 0
|
||||
-----
|
||||
Frame 0
|
||||
-----
|
||||
X
|
||||
X
|
||||
X - Stack -1
|
||||
-----
|
||||
Frame -1
|
||||
-----
|
||||
X
|
||||
X
|
||||
X
|
||||
X
|
||||
X - Stack -2
|
||||
-----
|
||||
Frame -2
|
||||
-----
|
||||
...
|
||||
...
|
||||
...
|
||||
-----
|
||||
Bottom of stack
|
||||
```
|
||||
|
||||
Fibers also have an incomplete stack frame for the next function call on top
|
||||
of their stacks. Making a function call involves pushing arguments to this
|
||||
temporary stack, and then invoking either the CALL or TCALL instructions.
|
||||
Arguments for the next function call are pushed via the PUSH, PUSH2, PUSH3, and
|
||||
PUSHA instructions. The stack of a fiber will grow as large as needed, although by
|
||||
default janet will limit the maximum size of a fiber's stack.
|
||||
The maximum stack size can be modified on a per fiber basis.
|
||||
|
||||
The slots in the stack are exposed as virtual registers to instructions. They
|
||||
can hold any Janet value.
|
||||
|
||||
## Closures
|
||||
|
||||
All functions in janet are closures; they combine some bytecode instructions
|
||||
with 0 or more environments. In the C source, a closure (hereby the same as
|
||||
a function) is represented by the type `JanetFunction *`. The bytecode instruction
|
||||
part of the function is represented by `JanetFuncDef *`, and a function environment
|
||||
is represented with `JanetFuncEnv *`.
|
||||
|
||||
The function definition part of a function (the 'bytecode' part, `JanetFuncDef *`),
|
||||
we also store various metadata about the function which is useful for debugging,
|
||||
as well as constants referenced by the function.
|
||||
|
||||
## C Functions
|
||||
|
||||
Janet uses C functions to bridge to native code. A C function
|
||||
(`JanetCFunction *` in C) is a C function pointer that can be called like
|
||||
a normal janet closure. From the perspective of the bytecode instruction set, there is no difference
|
||||
in invoking a C function and invoking a normal janet function.
|
||||
|
||||
## Bytecode Format
|
||||
|
||||
Janet bytecode presents an interface to a virtual machine with a large number
|
||||
of identical registers that can hold any Janet value (`Janet *` in C). Most instructions
|
||||
have a destination register, and 1 or 2 source register. Registers are simply
|
||||
named with positive integers.
|
||||
|
||||
Each instruction is a 32 bit integer, meaning that the instruction set is a constant
|
||||
width RISC instruction set like MIPS. The opcode of each instruction is the least significant
|
||||
byte of the instruction. The highest bit of
|
||||
this leading byte is reserved for debugging purpose, so there are 128 possible opcodes encodable
|
||||
with this scheme. Not all of these possible opcode are defined, and will trap the interpreter
|
||||
and emit a debug signal. Note that this mean an unknown opcode is still valid bytecode, it will
|
||||
just put the interpreter into a debug state when executed.
|
||||
|
||||
```
|
||||
X - Payload bits
|
||||
O - Opcode bits
|
||||
|
||||
4 3 2 1
|
||||
+----+----+----+----+
|
||||
| XX | XX | XX | OO |
|
||||
+----+----+----+----+
|
||||
```
|
||||
|
||||
8 bits for the opcode leaves 24 bits for the payload, which may or may not be utilized.
|
||||
There are a few instruction variants that divide these payload bits.
|
||||
|
||||
* 0 arg - Used for noops, returning nil, or other instructions that take no
|
||||
arguments. The payload is essentially ignored.
|
||||
* 1 arg - All payload bits correspond to a single value, usually a signed or unsigned integer.
|
||||
Used for instructions of 1 argument, like returning a value, yielding a value to the parent fiber,
|
||||
or doing a (relative) jump.
|
||||
* 2 arg - Payload is split into byte 2 and bytes 3 and 4.
|
||||
The first argument is the 8 bit value from byte 2, and the second argument is the 16 bit value
|
||||
from bytes 3 and 4 (`instruction >> 16`). Used for instructions of two arguments, like move, normal
|
||||
function calls, conditionals, etc.
|
||||
* 3 arg - Bytes 2, 3, and 4 each correspond to an 8 bit argument.
|
||||
Used for arithmetic operations, emitting a signal, etc.
|
||||
|
||||
These instruction variants can be further refined based on the semantics of the arguments.
|
||||
Some instructions may treat an argument as a slot index, while other instructions
|
||||
will treat the argument as a signed integer literal, and index for a constant, an index
|
||||
for an environment, or an unsigned integer.
|
||||
|
||||
## Instruction Reference
|
||||
|
||||
A listing of all opcode values can be found in src/include/janet/janetopcodes.h. The janet assembly
|
||||
short names can be found src/assembler/asm.c. In this document, we will refer to the instructions
|
||||
by their short names as presented to the assembler rather than their numerical values.
|
||||
|
||||
Each instruction is also listed with a signature, which are the arguments the instruction
|
||||
expects. There are a handful of instruction signatures, which combine the arity and type
|
||||
of the instruction. The assembler does not
|
||||
do any typechecking per closure, but does prevent jumping to invalid instructions and
|
||||
failure to return or error.
|
||||
|
||||
### Notation
|
||||
|
||||
* The $ prefix indicates that a instruction parameter is acting as a virtual register (slot).
|
||||
If a parameter does not have the $ suffix in the description, it is acting as some kind
|
||||
of literal (usually an unsigned integer for indexes, and a signed integer for literal integers).
|
||||
|
||||
* Some operators in the description have the suffix 'i' or 'r'. These indicate
|
||||
that these operators correspond to integers or real numbers only, respectively. All
|
||||
bitwise operators and bit shifts only work with integers.
|
||||
|
||||
* The `>>>` indicates unsigned right shift, as in Java. Because all integers in janet are
|
||||
signed, we differentiate the two kinds of right bit shift.
|
||||
|
||||
* The 'im' suffix in the instruction name is short for immediate. The 'i' suffix is short for integer,
|
||||
and the 'r' suffix is short for real.
|
||||
|
||||
### Reference Table
|
||||
|
||||
| Instruction | Signature | Description |
|
||||
| ----------- | --------------------------- | --------------------------------- |
|
||||
| `add` | `(add dest lhs rhs)` | $dest = $lhs + $rhs |
|
||||
| `addi` | `(addi dest lhs rhs)` | $dest = $lhs +i $rhs |
|
||||
| `addim` | `(addim dest lhs im)` | $dest = $lhs +i im |
|
||||
| `addr` | `(addr dest lhs rhs)` | $dest = $lhs +r $rhs |
|
||||
| `band` | `(band dest lhs rhs)` | $dest = $lhs & $rhs |
|
||||
| `bnot` | `(bnot dest operand)` | $dest = ~$operand |
|
||||
| `bor` | `(bor dest lhs rhs)` | $dest = $lhs | $rhs |
|
||||
| `bxor` | `(bxor dest lhs rhs)` | $dest = $lhs ^ $rhs |
|
||||
| `call` | `(call dest callee)` | $dest = call($callee, args) |
|
||||
| `clo` | `(clo dest index)` | $dest = closure(defs[$index]) |
|
||||
| `cmp` | `(cmp dest lhs rhs)` | $dest = janet\_compare($lhs, $rhs) |
|
||||
| `div` | `(div dest lhs rhs)` | $dest = $lhs / $rhs |
|
||||
| `divi` | `(divi dest lhs rhs)` | $dest = $lhs /i $rhs |
|
||||
| `divim` | `(divim dest lhs im)` | $dest = $lhs /i im |
|
||||
| `divr` | `(divr dest lhs rhs)` | $dest = $lhs /r $rhs |
|
||||
| `eq` | `(eq dest lhs rhs)` | $dest = $lhs == $rhs |
|
||||
| `eqi` | `(eqi dest lhs rhs)` | $dest = $lhs ==i $rhs |
|
||||
| `eqim` | `(eqim dest lhs im)` | $dest = $lhs ==i im |
|
||||
| `eqr` | `(eqr dest lhs rhs)` | $dest = $lhs ==r $rhs |
|
||||
| `err` | `(err message)` | Throw error $message. |
|
||||
| `get` | `(get dest ds key)` | $dest = $ds[$key] |
|
||||
| `geti` | `(geti dest ds index)` | $dest = $ds[index] |
|
||||
| `gt` | `(gt dest lhs rhs)` | $dest = $lhs > $rhs |
|
||||
| `gti` | `(gti dest lhs rhs)` | $dest = $lhs \>i $rhs |
|
||||
| `gtim` | `(gtim dest lhs im)` | $dest = $lhs \>i im |
|
||||
| `gtr` | `(gtr dest lhs rhs)` | $dest = $lhs \>r $rhs |
|
||||
| `gter` | `(gter dest lhs rhs)` | $dest = $lhs >=r $rhs |
|
||||
| `jmp` | `(jmp label)` | pc = label, pc += offset |
|
||||
| `jmpif` | `(jmpif cond label)` | if $cond pc = label else pc++ |
|
||||
| `jmpno` | `(jmpno cond label)` | if $cond pc++ else pc = label |
|
||||
| `ldc` | `(ldc dest index)` | $dest = constants[index] |
|
||||
| `ldf` | `(ldf dest)` | $dest = false |
|
||||
| `ldi` | `(ldi dest integer)` | $dest = integer |
|
||||
| `ldn` | `(ldn dest)` | $dest = nil |
|
||||
| `lds` | `(lds dest)` | $dest = current closure (self) |
|
||||
| `ldt` | `(ldt dest)` | $dest = true |
|
||||
| `ldu` | `(ldu dest env index)` | $dest = envs[env][index] |
|
||||
| `len` | `(len dest ds)` | $dest = length(ds) |
|
||||
| `lt` | `(lt dest lhs rhs)` | $dest = $lhs < $rhs |
|
||||
| `lti` | `(lti dest lhs rhs)` | $dest = $lhs \<i $rhs |
|
||||
| `ltim` | `(ltim dest lhs im)` | $dest = $lhs \<i im |
|
||||
| `ltr` | `(ltr dest lhs rhs)` | $dest = $lhs \<r $rhs |
|
||||
| `mkarr` | `(mkarr dest)` | $dest = call(array, args) |
|
||||
| `mkbuf` | `(mkbuf dest)` | $dest = call(buffer, args) |
|
||||
| `mktab` | `(mktab dest)` | $dest = call(table, args) |
|
||||
| `mkstr` | `(mkstr dest)` | $dest = call(string, args) |
|
||||
| `mkstu` | `(mkstu dest)` | $dest = call(struct, args) |
|
||||
| `mktup` | `(mktup dest)` | $dest = call(tuple, args) |
|
||||
| `movf` | `(movf src dest)` | $dest = $src |
|
||||
| `movn` | `(movn dest src)` | $dest = $src |
|
||||
| `mul` | `(mul dest lhs rhs)` | $dest = $lhs * $rhs |
|
||||
| `muli` | `(muli dest lhs rhs)` | $dest = $lhs \*i $rhs |
|
||||
| `mulim` | `(mulim dest lhs im)` | $dest = $lhs \*i im |
|
||||
| `mulr` | `(mulr dest lhs rhs)` | $dest = $lhs \*r $rhs |
|
||||
| `noop` | `(noop)` | Does nothing. |
|
||||
| `push` | `(push val)` | Push $val on arg |
|
||||
| `push2` | `(push2 val1 val3)` | Push $val1, $val2 on args |
|
||||
| `push3` | `(push3 val1 val2 val3)` | Push $val1, $val2, $val3, on args |
|
||||
| `pusha` | `(pusha array)` | Push values in $array on args |
|
||||
| `put` | `(put ds key val)` | $ds[$key] = $val |
|
||||
| `puti` | `(puti ds index val)` | $ds[index] = $val |
|
||||
| `res` | `(res dest fiber val)` | $dest = resume $fiber with $val |
|
||||
| `ret` | `(ret val)` | Return $val |
|
||||
| `retn` | `(retn)` | Return nil |
|
||||
| `setu` | `(setu env index val)` | envs[env][index] = $val |
|
||||
| `sig` | `(sig dest value sigtype)` | $dest = emit $value as sigtype |
|
||||
| `sl` | `(sl dest lhs rhs)` | $dest = $lhs << $rhs |
|
||||
| `slim` | `(slim dest lhs shamt)` | $dest = $lhs << shamt |
|
||||
| `sr` | `(sr dest lhs rhs)` | $dest = $lhs >> $rhs |
|
||||
| `srim` | `(srim dest lhs shamt)` | $dest = $lhs >> shamt |
|
||||
| `sru` | `(sru dest lhs rhs)` | $dest = $lhs >>> $rhs |
|
||||
| `sruim` | `(sruim dest lhs shamt)` | $dest = $lhs >>> shamt |
|
||||
| `sub` | `(sub dest lhs rhs)` | $dest = $lhs - $rhs |
|
||||
| `tcall` | `(tcall callee)` | Return call($callee, args) |
|
||||
| `tchck` | `(tcheck slot types)` | Assert $slot does matches types |
|
||||
|
97
doc/gendoc.janet
Normal file
97
doc/gendoc.janet
Normal file
@ -0,0 +1,97 @@
|
||||
# Generate documentation
|
||||
|
||||
(def- prelude
|
||||
```
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Janet Language Documentation</title>
|
||||
<meta name="description" content="API Documentation for the janet programming language.">
|
||||
<style>
|
||||
.docstring {
|
||||
font-family: monospace;
|
||||
}
|
||||
.binding-type {
|
||||
color: blue;
|
||||
}
|
||||
.source-map {
|
||||
color: steelblue;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
```)
|
||||
|
||||
(def- postlude
|
||||
```
|
||||
</html>
|
||||
```)
|
||||
|
||||
(def- escapes
|
||||
{10 "<br>"
|
||||
09 " "
|
||||
38 "&"
|
||||
60 "<"
|
||||
62 ">"
|
||||
34 """
|
||||
39 "'"
|
||||
47 "/"})
|
||||
|
||||
(defn- trim-lead
|
||||
"Trim leading newlines"
|
||||
[str]
|
||||
(var i 0)
|
||||
(while (= 10 str.i) (++ i))
|
||||
(string/slice str i))
|
||||
|
||||
(defn- html-escape
|
||||
"Escape special characters for HTML encoding."
|
||||
[str]
|
||||
(def buf @"")
|
||||
(loop [byte :in str]
|
||||
(if-let [rep escapes.byte]
|
||||
(buffer/push-string buf rep)
|
||||
(buffer/push-byte buf byte)))
|
||||
buf)
|
||||
|
||||
(defn- make-title
|
||||
"Generate title"
|
||||
[]
|
||||
(string "<h1>Janet Core API</h1>"
|
||||
"<p>Version " janet/version "-" janet/build "</p>"
|
||||
"<p>Generated "
|
||||
(string/number (os/time) :f 0 20)
|
||||
" seconds after epoch</p>"
|
||||
"<hr>"))
|
||||
|
||||
(defn- emit-item
|
||||
"Generate documentation for one entry."
|
||||
[key env-entry]
|
||||
(let [{:macro macro
|
||||
:value val
|
||||
:ref ref
|
||||
:source-map sm
|
||||
:doc docstring} env-entry
|
||||
binding-type (cond
|
||||
macro :macro
|
||||
ref (string :var " (" (type ref.0) ")")
|
||||
(type val))
|
||||
source-ref (if-let [[path start end] sm]
|
||||
(string "<span class=\"source-map\">" path " (" start ":" end ")</span>")
|
||||
"")]
|
||||
(string "<h2 class=\"binding\">" (html-escape key) "</h2>\n"
|
||||
"<span class=\"binding-type\">" binding-type "</span>\n"
|
||||
"<p class=\"docstring\">" (trim-lead (html-escape docstring)) "</p>\n"
|
||||
source-ref)))
|
||||
|
||||
# Generate parts and print them to stdout
|
||||
(def parts (seq [[k entry]
|
||||
:in (sort (pairs (table/getproto _env)))
|
||||
:when (and entry:doc (not entry:private))]
|
||||
(emit-item k entry)))
|
||||
(print
|
||||
prelude
|
||||
(make-title)
|
||||
;(interpose "<hr>\n" parts)
|
||||
postlude)
|
@ -18,12 +18,12 @@
|
||||
(if ,loaded
|
||||
,state
|
||||
(do
|
||||
(:= ,loaded true)
|
||||
(:= ,state (do ;forms)))))))
|
||||
(set ,loaded true)
|
||||
(set ,state (do ;forms)))))))
|
||||
|
||||
# Use tuples instead of structs to save memory
|
||||
(def HEAD :private 0)
|
||||
(def TAIL :private 1)
|
||||
(def- HEAD 0)
|
||||
(def- TAIL 1)
|
||||
|
||||
(defn empty-seq
|
||||
"The empty sequence."
|
@ -16,7 +16,7 @@
|
||||
(def cell-set (frequencies state))
|
||||
(def neighbor-set (frequencies (mapcat neighbors state)))
|
||||
(seq [coord :keys neighbor-set
|
||||
:let [count neighbor-set@coord]
|
||||
:let [count neighbor-set.coord]
|
||||
:when (or (= count 3) (and (get cell-set coord) (= count 2)))]
|
||||
coord))
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"Draw cells in the game of life from (x1, y1) to (x2, y2)"
|
||||
[state x1 y1 x2 y2]
|
||||
(def cellset @{})
|
||||
(each cell state (:= cellset@cell true))
|
||||
(each cell state (set cellset.cell true))
|
||||
(loop [x :range [x1 (+ 1 x2)]
|
||||
:after (print)
|
||||
y :range [y1 (+ 1 y2)]]
|
||||
@ -40,4 +40,4 @@
|
||||
(for i 0 20
|
||||
(print "generation " i)
|
||||
(draw *state* -7 -7 7 7)
|
||||
(:= *state* (tick *state*)))
|
||||
(set *state* (tick *state*)))
|
||||
|
@ -9,6 +9,6 @@
|
||||
(def len (length list))
|
||||
(for j 0 len
|
||||
(def trial (get list j))
|
||||
(if (zero? (% i trial)) (:= isprime? false)))
|
||||
(if (zero? (% i trial)) (set isprime? false)))
|
||||
(if isprime? (array/push list i)))
|
||||
list)
|
||||
|
335
grammar/janet.tmLanguage
Normal file
335
grammar/janet.tmLanguage
Normal file
@ -0,0 +1,335 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>fileTypes</key>
|
||||
<array>
|
||||
<string>janet</string>
|
||||
</array>
|
||||
<key>foldingStartMarker</key>
|
||||
<string>\{</string>
|
||||
<key>foldingStopMarker</key>
|
||||
<string>\}</string>
|
||||
<key>foldingStartMarker</key>
|
||||
<string>\[</string>
|
||||
<key>foldingStopMarker</key>
|
||||
<string>\]</string>
|
||||
<key>foldingStartMarker</key>
|
||||
<string>\(</string>
|
||||
<key>foldingStopMarker</key>
|
||||
<string>\)</string>
|
||||
<key>keyEquivalent</key>
|
||||
<string>^~L</string>
|
||||
<key>name</key>
|
||||
<string>Janet</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#all</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>repository</key>
|
||||
<dict>
|
||||
<key>all</key>
|
||||
<dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#comment</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#parens</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#brackets</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#braces</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#readermac</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#string</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#longstring</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#literal</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#corelib</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#r-number</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#dec-number</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#hex-number</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#keysym</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#symbol</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>comment</key>
|
||||
<dict>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.comment.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>(#).*$</string>
|
||||
<key>name</key>
|
||||
<string>comment.line.janet</string>
|
||||
</dict>
|
||||
<key>braces</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(@?{)</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.braces.begin.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(})</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.braces.end.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#all</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>brackets</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(@?\[)</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.brackets.begin.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(\])</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.brackets.end.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#all</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>parens</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(@?\()</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.parens.begin.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(\))</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.parens.end.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#all</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>readermac</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>[\'\~\;\,]</string>
|
||||
<key>name</key>
|
||||
<string>punctuation.other.janet</string>
|
||||
</dict>
|
||||
<!-- string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*]) token match here (?![\.:\w_\-=!@\$%^&?|\\/<>*])</string -->
|
||||
<key>literal</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])(true|false|nil)(?![\.:\w_\-=!@\$%^&?|\\/<>*])</string>
|
||||
<key>name</key>
|
||||
<string>constant.language.janet</string>
|
||||
</dict>
|
||||
<key>corelib</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])(def|do|fn|if|quasiquote|quote|set|splice|unquote|var|while|%|%=|\*|\*=|\*doc\-width\*|\*env\*|\+|\+\+|\+=|\-|\-\-|\-=|\->|\->>|/|/=|<|<=|=|==|>|>=|_env|abstract\?|all|all\-symbols|allsyms|and|apply|array|array/concat|array/ensure|array/insert|array/new|array/peek|array/pop|array/push|array/slice|array\?|asm|band|blshift|bnot|boolean\?|bor|brshift|brushift|buffer|buffer/clear|buffer/new|buffer/popn|buffer/push\-byte|buffer/push\-integer|buffer/push\-string|buffer/slice|buffer\?|bxor|bytes\?|callable\?|case|cfunction\?|comment|comp|compile|complement|cond|coro|count|debug|debug/arg\-stack|debug/break|debug/fbreak|debug/lineage|debug/stack|debug/unbreak|debug/unfbreak|dec|deep\-not=|deep=|def\-|default|defglobal|defmacro|defmacro\-|defn|defn\-|describe|dictionary\?|disasm|distinct|doc|doc\*|doc\-format|drop\-until|drop\-while|each|empty\?|env\-lookup|error|eval|eval\-string|even\?|every\?|extreme|false\?|fiber/current|fiber/maxstack|fiber/new|fiber/setmaxstack|fiber/status|fiber\?|file/close|file/flush|file/open|file/popen|file/read|file/seek|file/write|filter|find|find\-index|first|flatten|flatten\-into|for|frequencies|function\?|gccollect|gcinterval|gcsetinterval|generate|gensym|get|getline|hash|idempotent\?|identity|if\-let|if\-not|import|import\*|inc|indexed\?|int|integer\?|interleave|interpose|invert|janet/build|janet/version|juxt|juxt\*|keep|keys|keyword\?|kvs|last|length|let|loop|macex|macex1|make\-env|map|mapcat|marshal|match|match\-1|math/acos|math/asin|math/atan|math/ceil|math/cos|math/e|math/exp|math/floor|math/inf|math/log|math/log10|math/pi|math/pow|math/random|math/seedrandom|math/sin|math/sqrt|math/tan|max|max\-order|merge|merge\-into|min|min\-order|module/find|module/native\-paths|module/paths|native|neg\?|next|nil\?|not|not=|not==|number\?|odd\?|one\?|or|order<|order<=|order>|order>=|os/clock|os/cwd|os/execute|os/exit|os/getenv|os/setenv|os/shell|os/sleep|os/time|os/which|pairs|parser/byte|parser/consume|parser/error|parser/flush|parser/new|parser/produce|parser/state|parser/status|parser/where|partial|pos\?|print|process/args|product|put|range|real|real\?|reduce|repl|require|resume|reverse|run\-context|scan\-integer|scan\-number|scan\-real|sentinel|seq|some|sort|sorted|status\-pp|stderr|stdin|stdout|string|string/ascii\-lower|string/ascii\-upper|string/bytes|string/check\-set|string/find|string/find\-all|string/from\-bytes|string/join|string/number|string/pretty|string/repeat|string/replace|string/replace\-all|string/reverse|string/slice|string/split|string\?|struct|struct\?|sum|symbol|symbol\?|table|table/getproto|table/new|table/rawget|table/setproto|table/to\-struct|table\?|take\-until|take\-while|true\?|tuple|tuple/append|tuple/prepend|tuple/slice|tuple\?|type|unless|unmarshal|update|values|varglobal|when|when\-let|with\-idemp|yield|zero\?|zipcoll)(?![\.:\w_\-=!@\$%^&?|\\/<>*])</string>
|
||||
<key>name</key>
|
||||
<string>keyword.control.janet</string>
|
||||
</dict>
|
||||
<key>keysym</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*]):[\.:\w_\-=!@\$%^&?|\\/<>*]*</string>
|
||||
<key>name</key>
|
||||
<string>constant.keyword.janet</string>
|
||||
</dict>
|
||||
<key>symbol</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])[\.a-zA-Z_\-=!@\$%^&?|\\/<>*][\.:\w_\-=!@\$%^&?|\\/<>*]*</string>
|
||||
<key>name</key>
|
||||
<string>variable.other.janet</string>
|
||||
</dict>
|
||||
<key>hex-number</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])[-+]?0x([_\da-fA-F]+|[_\da-fA-F]+\.[_\da-fA-F]*|\.[_\da-fA-F]+)(&[+-]?[\da-fA-F]+)?(?![\.:\w_\-=!@\$%^&?|\\/<>*])</string>
|
||||
<key>name</key>
|
||||
<string>constant.numeric.hex.janet</string>
|
||||
</dict>
|
||||
<key>dec-number</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])[-+]?([_\d]+|[_\d]+\.[_\d]*|\.[_\d]+)([eE&][+-]?[\d]+)?(?![\.:\w_\-=!@\$%^&?|\\/<>*])</string>
|
||||
<key>name</key>
|
||||
<string>constant.numeric.decimal.janet</string>
|
||||
</dict>
|
||||
<key>r-number</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(?<![\.:\w_\-=!@\$%^&?|\\/<>*])[-+]?\d\d?r([_\w]+|[_\w]+\.[_\w]*|\.[_\w]+)(&[+-]?[\w]+)?(?![\.:\w_\-=!@\$%^&?|\\/<>*])</string>
|
||||
<key>name</key>
|
||||
<string>constant.numeric.decimal.janet</string>
|
||||
</dict>
|
||||
<key>string</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(@?")</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>(")</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.double.janet</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>(\\[ne0zft"\\']|\\x[0-9a-fA-F][0-9a-fA-f])</string>
|
||||
<key>name</key>
|
||||
<string>constant.character.escape.janet</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>longstring</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>(@?)(`+)</string>
|
||||
<key>beginCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.janet</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.begin.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>end</key>
|
||||
<string>\2</string>
|
||||
<key>endCaptures</key>
|
||||
<dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>punctuation.definition.string.end.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>string.quoted.triple.janet</string>
|
||||
</dict>
|
||||
<key>nomatch</key>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\S+</string>
|
||||
<key>name</key>
|
||||
<string>invalid.illegal.janet</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>scopeName</key>
|
||||
<string>source.janet</string>
|
||||
<key>uuid</key>
|
||||
<string>3743190f-20c4-44d0-8640-6611a983296b</string>
|
||||
</dict>
|
||||
</plist>
|
30
grammar/tmcorelib.janet
Normal file
30
grammar/tmcorelib.janet
Normal file
@ -0,0 +1,30 @@
|
||||
# Helper to generate core library mappings for janet
|
||||
|
||||
(def allsyms (all-symbols))
|
||||
|
||||
(def- escapes
|
||||
{(get "|" 0) `\|`
|
||||
(get "-" 0) `\-`
|
||||
(get "+" 0) `\+`
|
||||
(get "*" 0) `\*`
|
||||
(get "^" 0) `\^`
|
||||
(get "$" 0) `\$`
|
||||
(get "?" 0) `\?`
|
||||
38 "&"
|
||||
60 "<"
|
||||
62 ">"
|
||||
34 """
|
||||
39 "'"
|
||||
47 "/"})
|
||||
|
||||
(defn- escape
|
||||
"Escape special characters for HTML and regex encoding."
|
||||
[str]
|
||||
(def buf @"")
|
||||
(loop [byte :in str]
|
||||
(if-let [rep escapes.byte]
|
||||
(buffer/push-string buf rep)
|
||||
(buffer/push-byte buf byte)))
|
||||
buf)
|
||||
|
||||
(print (string/join (map escape allsyms) "|"))
|
@ -588,7 +588,7 @@ static int json_encode(JanetArgs args) {
|
||||
|
||||
static const JanetReg cfuns[] = {
|
||||
{"encode", json_encode,
|
||||
"(json/encode x)\n\n"
|
||||
"(json/encode x [,tab [,newline]])\n\n"
|
||||
"Encodes a janet value in JSON (utf-8)."
|
||||
},
|
||||
{"decode", json_decode,
|
||||
|
@ -710,8 +710,8 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int
|
||||
if (!janet_checktype(tup[1], JANET_INTEGER)) {
|
||||
janet_asm_error(&a, "expected integer");
|
||||
}
|
||||
mapping.line = janet_unwrap_integer(tup[0]);
|
||||
mapping.column = janet_unwrap_integer(tup[1]);
|
||||
mapping.start = janet_unwrap_integer(tup[0]);
|
||||
mapping.end = janet_unwrap_integer(tup[1]);
|
||||
def->sourcemap[i] = mapping;
|
||||
}
|
||||
}
|
||||
@ -876,8 +876,8 @@ Janet janet_disasm(JanetFuncDef *def) {
|
||||
for (i = 0; i < def->bytecode_length; i++) {
|
||||
Janet *t = janet_tuple_begin(2);
|
||||
JanetSourceMapping mapping = def->sourcemap[i];
|
||||
t[0] = janet_wrap_integer(mapping.line);
|
||||
t[1] = janet_wrap_integer(mapping.column);
|
||||
t[0] = janet_wrap_integer(mapping.start);
|
||||
t[1] = janet_wrap_integer(mapping.end);
|
||||
sourcemap->data[i] = janet_wrap_tuple(janet_tuple_end(t));
|
||||
}
|
||||
sourcemap->count = def->bytecode_length;
|
||||
|
@ -125,10 +125,10 @@ int32_t janet_verify(JanetFuncDef *def) {
|
||||
for (i = 0; i < def->bytecode_length; i++) {
|
||||
uint32_t instr = def->bytecode[i];
|
||||
/* Check for invalid instructions */
|
||||
if ((instr & 0xFF) >= JOP_INSTRUCTION_COUNT) {
|
||||
if ((instr & 0x7F) >= JOP_INSTRUCTION_COUNT) {
|
||||
return 3;
|
||||
}
|
||||
enum JanetInstructionType type = janet_instructions[instr & 0xFF];
|
||||
enum JanetInstructionType type = janet_instructions[instr & 0x7F];
|
||||
switch (type) {
|
||||
case JINT_0:
|
||||
continue;
|
||||
|
@ -462,9 +462,9 @@ static int macroexpand1(
|
||||
if (janet_tuple_length(form) == 0)
|
||||
return 0;
|
||||
/* Source map - only set when we get a tuple */
|
||||
if (janet_tuple_sm_line(form) > 0) {
|
||||
c->current_mapping.line = janet_tuple_sm_line(form);
|
||||
c->current_mapping.column = janet_tuple_sm_col(form);
|
||||
if (janet_tuple_sm_start(form) >= 0) {
|
||||
c->current_mapping.start = janet_tuple_sm_start(form);
|
||||
c->current_mapping.end = janet_tuple_sm_end(form);
|
||||
}
|
||||
if (!janet_checktype(form[0], JANET_SYMBOL))
|
||||
return 0;
|
||||
@ -575,13 +575,13 @@ JanetSlot janetc_value(JanetFopts opts, Janet x) {
|
||||
|
||||
if (c->result.status == JANET_COMPILE_ERROR)
|
||||
return janetc_cslot(janet_wrap_nil());
|
||||
c->current_mapping = last_mapping;
|
||||
if (opts.flags & JANET_FOPTS_TAIL)
|
||||
ret = janetc_return(opts.compiler, ret);
|
||||
if (opts.flags & JANET_FOPTS_HINT) {
|
||||
janetc_copy(opts.compiler, opts.hint, ret);
|
||||
ret = opts.hint;
|
||||
}
|
||||
c->current_mapping = last_mapping;
|
||||
opts.compiler->recursion_guard++;
|
||||
return ret;
|
||||
}
|
||||
@ -648,15 +648,15 @@ static void janetc_init(JanetCompiler *c, JanetTable *env, const uint8_t *where)
|
||||
c->recursion_guard = JANET_RECURSION_GUARD;
|
||||
c->env = env;
|
||||
c->source = where;
|
||||
c->current_mapping.line = 0;
|
||||
c->current_mapping.column = 0;
|
||||
c->current_mapping.start = -1;
|
||||
c->current_mapping.end = -1;
|
||||
/* Init result */
|
||||
c->result.error = NULL;
|
||||
c->result.status = JANET_COMPILE_OK;
|
||||
c->result.funcdef = NULL;
|
||||
c->result.macrofiber = NULL;
|
||||
c->result.error_mapping.line = 0;
|
||||
c->result.error_mapping.column = 0;
|
||||
c->result.error_mapping.start = -1;
|
||||
c->result.error_mapping.end = -1;
|
||||
}
|
||||
|
||||
/* Deinitialize a compiler struct */
|
||||
@ -717,8 +717,8 @@ static int cfun(JanetArgs args) {
|
||||
} else {
|
||||
t = janet_table(4);
|
||||
janet_table_put(t, janet_csymbolv(":error"), janet_wrap_string(res.error));
|
||||
janet_table_put(t, janet_csymbolv(":line"), janet_wrap_integer(res.error_mapping.line));
|
||||
janet_table_put(t, janet_csymbolv(":column"), janet_wrap_integer(res.error_mapping.column));
|
||||
janet_table_put(t, janet_csymbolv(":start"), janet_wrap_integer(res.error_mapping.start));
|
||||
janet_table_put(t, janet_csymbolv(":end"), janet_wrap_integer(res.error_mapping.end));
|
||||
if (res.macrofiber) {
|
||||
janet_table_put(t, janet_csymbolv(":fiber"), janet_wrap_fiber(res.macrofiber));
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
i
|
||||
(do
|
||||
(if (= t :string)
|
||||
(:= docstr ith)
|
||||
(set docstr ith)
|
||||
(array/push modifiers ith))
|
||||
(if (< i len) (recur (+ i 1)))))))
|
||||
(def start (fstart 0))
|
||||
@ -37,7 +37,7 @@
|
||||
(while (< index arglen)
|
||||
(buffer/push-string buf " ")
|
||||
(string/pretty args.index 4 buf)
|
||||
(:= index (+ index 1)))
|
||||
(set index (+ index 1)))
|
||||
(array/push modifiers (string buf ")\n\n" docstr))
|
||||
# Build return value
|
||||
~(def ,name ,;modifiers (fn ,name ,;(tuple/slice more start)))))
|
||||
@ -84,25 +84,24 @@
|
||||
(defn neg? "Check if x is less than 0." [x] (< x 0))
|
||||
(defn one? "Check if x is equal to 1." [x] (== x 1))
|
||||
(defn integer? "Check if x is an integer." [x] (= (type x) :integer))
|
||||
(defn real? [x] "Check if x is a real number." (= (type x) :real))
|
||||
(defn real? "Check if x is a real number." [x] (= (type x) :real))
|
||||
(defn number? "Check if x is a number." [x]
|
||||
(def t (type x))
|
||||
(if (= t :integer) true (= t :real)))
|
||||
(defn fiber? "Check if x is a fiber." [x] (= (type x) :fiber))
|
||||
(defn string? "Check if x is a string." [x] (= (type x) :string))
|
||||
(defn symbol? "Check if x is a symbol." [x] (= (type x) :symbol))
|
||||
(defn keyword? "Check if x is a keyword style symbol."
|
||||
[x]
|
||||
(defn keyword? "Check if x is a keyword style symbol." [x]
|
||||
(if (not= (type x) :symbol) nil (= 58 x.0)))
|
||||
(defn buffer? "Check if x is a buffer." [x] (= (type x) :buffer))
|
||||
(defn function? "Check if x is a function (not a cfunction)."
|
||||
[x] (= (type x) :function))
|
||||
(defn function? "Check if x is a function (not a cfunction)." [x]
|
||||
(= (type x) :function))
|
||||
(defn cfunction? "Check if x a cfunction." [x] (= (type x) :cfunction))
|
||||
(defn table? [x] "Check if x a table." (= (type x) :table ))
|
||||
(defn struct? [x] "Check if x a struct." (= (type x) :struct))
|
||||
(defn array? [x] "Check if x is an array." (= (type x) :array))
|
||||
(defn tuple? [x] "Check if x is a tuple." (= (type x) :tuple))
|
||||
(defn boolean? [x] "Check if x is a boolean." (= (type x) :boolean))
|
||||
(defn table? "Check if x a table." [x] (= (type x) :table ))
|
||||
(defn struct? "Check if x a struct." [x] (= (type x) :struct))
|
||||
(defn array? "Check if x is an array." [x] (= (type x) :array))
|
||||
(defn tuple? "Check if x is a tuple." [x] (= (type x) :tuple))
|
||||
(defn boolean? "Check if x is a boolean." [x] (= (type x) :boolean))
|
||||
(defn bytes? "Check if x is a string, symbol, or buffer." [x]
|
||||
(def t (type x))
|
||||
(if (= t :string) true (if (= t :symbol) true (= t :buffer))))
|
||||
@ -132,9 +131,9 @@
|
||||
|
||||
(defmacro with-idemp
|
||||
"Return janet code body that has been prepended
|
||||
with a binding of form to atom. If form is a non-
|
||||
idempotent form (a function call, etc.), make sure the resulting
|
||||
code will only call evaluate once, even if body contains multiple
|
||||
with a binding of form to atom. If form is a non-idempotent
|
||||
form (a function call, etc.), make sure the resulting
|
||||
code will only evaluate once, even if body contains multiple
|
||||
copies of binding. In body, use binding instead of form."
|
||||
[binding form & body]
|
||||
(def $result (gensym))
|
||||
@ -147,22 +146,16 @@
|
||||
,$result
|
||||
(tuple 'do (tuple 'def ,binding ,$form) ,$result))))
|
||||
|
||||
# C style macros and functions for imperative sugar
|
||||
# C style macros and functions for imperative sugar. No bitwise though.
|
||||
(defn inc "Returns x + 1." [x] (+ x 1))
|
||||
(defn dec "Returns x - 1." [x] (- x 1))
|
||||
(defmacro ++ "Increments the var x by 1." [x] ~(:= ,x (,+ ,x ,1)))
|
||||
(defmacro -- "Decrements the var x by 1." [x] ~(:= ,x (,- ,x ,1)))
|
||||
(defmacro += "Increments the var x by n." [x n] ~(:= ,x (,+ ,x ,n)))
|
||||
(defmacro -= "Decrements the vat x by n." [x n] ~(:= ,x (,- ,x ,n)))
|
||||
(defmacro *= "Shorthand for (:= x (* x n))." [x n] ~(:= ,x (,* ,x ,n)))
|
||||
(defmacro /= "Shorthand for (:= x (/ x n))." [x n] ~(:= ,x (,/ ,x ,n)))
|
||||
(defmacro %= "Shorthand for (:= x (% x n))." [x n] ~(:= ,x (,% ,x ,n)))
|
||||
(defmacro &= "Shorthand for (:= x (& x n))." [x n] ~(:= ,x (,& ,x ,n)))
|
||||
(defmacro |= "Shorthand for (:= x (| x n))." [x n] ~(:= ,x (,| ,x ,n)))
|
||||
(defmacro ^= "Shorthand for (:= x (^ x n))." [x n] ~(:= ,x (,^ ,x ,n)))
|
||||
(defmacro >>= "Shorthand for (:= x (>> x n))." [x n] ~(:= ,x (,>> ,x ,n)))
|
||||
(defmacro <<= "Shorthand for (:= x (<< x n))." [x n] ~(:= ,x (,<< ,x ,n)))
|
||||
(defmacro >>>= "Shorthand for (:= x (>>> x n))." [x n] ~(:= ,x (,>>> ,x ,n)))
|
||||
(defmacro ++ "Increments the var x by 1." [x] ~(set ,x (,+ ,x ,1)))
|
||||
(defmacro -- "Decrements the var x by 1." [x] ~(set ,x (,- ,x ,1)))
|
||||
(defmacro += "Increments the var x by n." [x n] ~(set ,x (,+ ,x ,n)))
|
||||
(defmacro -= "Decrements the var x by n." [x n] ~(set ,x (,- ,x ,n)))
|
||||
(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)))
|
||||
(defmacro %= "Shorthand for (set x (% x n))." [x n] ~(set ,x (,% ,x ,n)))
|
||||
|
||||
(defmacro default
|
||||
"Define a default value for an optional argument.
|
||||
@ -249,7 +242,7 @@
|
||||
(var i len)
|
||||
(while (> i 0)
|
||||
(-- i)
|
||||
(:= ret (if (= ret true)
|
||||
(set ret (if (= ret true)
|
||||
forms.i
|
||||
(tuple 'if forms.i ret))))
|
||||
ret)
|
||||
@ -264,7 +257,7 @@
|
||||
(while (> i 0)
|
||||
(-- i)
|
||||
(def fi forms.i)
|
||||
(:= ret (if (idempotent? fi)
|
||||
(set ret (if (idempotent? fi)
|
||||
(tuple 'if fi fi ret)
|
||||
(do
|
||||
(def $fi (gensym))
|
||||
@ -332,13 +325,13 @@
|
||||
(tuple 'var $iter 0)
|
||||
(tuple 'while
|
||||
(tuple/slice spreds)
|
||||
(tuple := $iter (tuple + 1 $iter))
|
||||
(tuple 'set $iter (tuple + 1 $iter))
|
||||
sub)))
|
||||
(error (string "unexpected loop predicate: " bindings)))
|
||||
(case verb
|
||||
:iterate (do
|
||||
(def $iter (gensym))
|
||||
(def preds @['and (tuple ':= $iter object)])
|
||||
(def preds @['and (tuple 'set $iter object)])
|
||||
(def subloop (doone (+ i 3) preds))
|
||||
(tuple 'do
|
||||
(tuple 'var $iter nil)
|
||||
@ -358,7 +351,7 @@
|
||||
(tuple 'while (tuple/slice preds)
|
||||
(tuple 'def bindings $iter)
|
||||
subloop
|
||||
(tuple ':= $iter (tuple + $iter inc)))))
|
||||
(tuple 'set $iter (tuple + $iter inc)))))
|
||||
:keys (do
|
||||
(def $dict (gensym))
|
||||
(def $iter (gensym))
|
||||
@ -370,7 +363,7 @@
|
||||
(tuple 'while (tuple/slice preds)
|
||||
(tuple 'def bindings $iter)
|
||||
subloop
|
||||
(tuple ':= $iter (tuple next $dict $iter)))))
|
||||
(tuple 'set $iter (tuple next $dict $iter)))))
|
||||
:in (do
|
||||
(def $len (gensym))
|
||||
(def $i (gensym))
|
||||
@ -384,7 +377,7 @@
|
||||
(tuple 'while (tuple/slice preds 0)
|
||||
(tuple 'def bindings (tuple get $indexed $i))
|
||||
subloop
|
||||
(tuple ':= $i (tuple + 1 $i)))))
|
||||
(tuple 'set $i (tuple + 1 $i)))))
|
||||
:generate (do
|
||||
(def $fiber (gensym))
|
||||
(def $yieldval (gensym))
|
||||
@ -401,7 +394,7 @@
|
||||
(tuple 'while (tuple/slice preds 0)
|
||||
(tuple 'def bindings $yieldval)
|
||||
subloop
|
||||
(tuple := $yieldval (tuple resume $fiber)))))
|
||||
(tuple 'set $yieldval (tuple resume $fiber)))))
|
||||
(error (string "unexpected loop verb: " verb)))))))
|
||||
(doone 0 nil))
|
||||
|
||||
@ -434,21 +427,23 @@
|
||||
(tuple fiber/new (tuple 'fn '[&] ;body)))
|
||||
|
||||
(defn sum
|
||||
"Returns the sum of xs. If xs is empty, returns 0."
|
||||
[xs]
|
||||
(var accum 0)
|
||||
(loop [x :in xs] (+= accum x))
|
||||
accum)
|
||||
|
||||
(defn product
|
||||
"Returns the product of xs. If xs is empty, returns 1."
|
||||
[xs]
|
||||
(var accum 1)
|
||||
(loop [x :in xs] (*= accum x))
|
||||
accum)
|
||||
|
||||
(defmacro if-let
|
||||
"Takes the first one or two forms in a vector and if both are true binds
|
||||
all the forms with let and evaluates the first expression else
|
||||
evaluates the second"
|
||||
"Make mutliple bindings, anf if all are truthy,
|
||||
evaluate the tru form. If any are false or nil, evaluate
|
||||
the fal form. Bindings have the same syntax as the let macro."
|
||||
[bindings tru fal &]
|
||||
(def len (length bindings))
|
||||
(if (zero? len) (error "expected at least 1 binding"))
|
||||
@ -477,8 +472,7 @@
|
||||
(aux 0))
|
||||
|
||||
(defmacro when-let
|
||||
"Takes the first one or two forms in vector and if true binds
|
||||
all the forms with let and evaluates the body"
|
||||
"Same as (if-let bindings (do ;body))."
|
||||
[bindings & body]
|
||||
~(if-let ,bindings (do ,;body)))
|
||||
|
||||
@ -516,13 +510,26 @@
|
||||
(var ret args.0)
|
||||
(loop [i :range [0 len]]
|
||||
(def v args.i)
|
||||
(if (order v ret) (:= ret v)))
|
||||
(if (order v ret) (set ret v)))
|
||||
ret))
|
||||
|
||||
(defn max [& args] (extreme > args))
|
||||
(defn min [& args] (extreme < args))
|
||||
(defn max-order [& args] (extreme order> args))
|
||||
(defn min-order [& args] (extreme order< args))
|
||||
(defn max
|
||||
"Returns the numeric maximum of the arguments."
|
||||
[& args] (extreme > args))
|
||||
|
||||
(defn min
|
||||
"Returns the numeric minimum of the arguments."
|
||||
[& args] (extreme < args))
|
||||
|
||||
(defn max-order
|
||||
"Returns the maximum of the arguments according to a total
|
||||
order over all values."
|
||||
[& args] (extreme order> args))
|
||||
|
||||
(defn min-order
|
||||
"Returns the minimum of the arguments according to a total
|
||||
order over all values."
|
||||
[& args] (extreme order< args))
|
||||
|
||||
(defn first
|
||||
"Get the first element from an indexed data structure."
|
||||
@ -541,7 +548,7 @@
|
||||
###
|
||||
|
||||
(def sort
|
||||
"Sort an array in-place. Uses quicksort and is not a stable sort."
|
||||
"(sort xs [, by])\n\nSort an array in-place. Uses quicksort and is not a stable sort."
|
||||
(do
|
||||
|
||||
(defn partition
|
||||
@ -552,11 +559,11 @@
|
||||
(def aj a.j)
|
||||
(when (by aj pivot)
|
||||
(def ai a.i)
|
||||
(:= a.i aj)
|
||||
(:= a.j ai)
|
||||
(set a.i aj)
|
||||
(set a.j ai)
|
||||
(++ i)))
|
||||
(:= a.hi a.i)
|
||||
(:= a.i pivot)
|
||||
(set a.hi a.i)
|
||||
(set a.i pivot)
|
||||
i)
|
||||
|
||||
(defn sort-help
|
||||
@ -567,7 +574,7 @@
|
||||
(sort-help a (+ piv 1) hi by))
|
||||
a)
|
||||
|
||||
(fn [a by &]
|
||||
(fn sort [a by &]
|
||||
(sort-help a 0 (- (length a) 1) (or by order<)))))
|
||||
|
||||
(defn sorted
|
||||
@ -581,7 +588,7 @@
|
||||
[f init ind]
|
||||
(var res init)
|
||||
(loop [x :in ind]
|
||||
(:= res (f res x)))
|
||||
(set res (f res x)))
|
||||
res)
|
||||
|
||||
(defn map
|
||||
@ -593,18 +600,18 @@
|
||||
(var limit (length inds.0))
|
||||
(loop [i :range [0 ninds]]
|
||||
(def l (length inds.i))
|
||||
(if (< l limit) (:= limit l)))
|
||||
(if (< l limit) (set limit l)))
|
||||
(def [i1 i2 i3 i4] inds)
|
||||
(def res (array/new limit))
|
||||
(case ninds
|
||||
1 (loop [i :range [0 limit]] (:= res.i (f i1.i)))
|
||||
2 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i)))
|
||||
3 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i i3.i)))
|
||||
4 (loop [i :range [0 limit]] (:= res.i (f i1.i i2.i i3.i i4.i)))
|
||||
1 (loop [i :range [0 limit]] (set res.i (f i1.i)))
|
||||
2 (loop [i :range [0 limit]] (set res.i (f i1.i i2.i)))
|
||||
3 (loop [i :range [0 limit]] (set res.i (f i1.i i2.i i3.i)))
|
||||
4 (loop [i :range [0 limit]] (set res.i (f i1.i i2.i i3.i i4.i)))
|
||||
(loop [i :range [0 limit]]
|
||||
(def args (array/new ninds))
|
||||
(loop [j :range [0 ninds]] (:= args.j inds.j.i))
|
||||
(:= res.i (f ;args))))
|
||||
(loop [j :range [0 ninds]] (set args.j inds.j.i))
|
||||
(set res.i (f ;args))))
|
||||
res)
|
||||
|
||||
(defn mapcat
|
||||
@ -677,7 +684,7 @@
|
||||
(var going true)
|
||||
(while (if (< i len) going)
|
||||
(def item ind.i)
|
||||
(if (pred item) (:= going false) (++ i)))
|
||||
(if (pred item) (set going false) (++ i)))
|
||||
(if going nil i))
|
||||
|
||||
(defn find
|
||||
@ -770,7 +777,7 @@
|
||||
[ind]
|
||||
(var res true)
|
||||
(loop [x :in ind :while res]
|
||||
(if x nil (:= res x)))
|
||||
(if x nil (set res x)))
|
||||
res)
|
||||
|
||||
(defn reverse
|
||||
@ -795,8 +802,8 @@ value, one key will be ignored."
|
||||
ret)
|
||||
|
||||
(defn zipcoll
|
||||
"Creates an table or tuple from two arrays/tuples. If a third argument of
|
||||
:struct is given result is struct else is table. Returns a new table."
|
||||
"Creates an table or tuple from two arrays/tuples.
|
||||
Returns a new table."
|
||||
[keys vals]
|
||||
(def res @{})
|
||||
(def lk (length keys))
|
||||
@ -811,7 +818,7 @@ value, one key will be ignored."
|
||||
The key then, is associated to the function's return value"
|
||||
[coll a-key a-function & args]
|
||||
(def old-value coll.a-key)
|
||||
(:= coll.a-key (a-function old-value ;args)))
|
||||
(set coll.a-key (a-function old-value ;args)))
|
||||
|
||||
(defn merge-into
|
||||
"Merges multiple tables/structs into a table. If a key appears in more than one
|
||||
@ -820,7 +827,7 @@ value, one key will be ignored."
|
||||
[tab & colls]
|
||||
(loop [c :in colls
|
||||
key :keys c]
|
||||
(:= tab.key c.key))
|
||||
(set tab.key c.key))
|
||||
tab)
|
||||
|
||||
(defn merge
|
||||
@ -831,7 +838,7 @@ value, one key will be ignored."
|
||||
(def container @{})
|
||||
(loop [c :in colls
|
||||
key :keys c]
|
||||
(:= container.key c.key))
|
||||
(set container.key c.key))
|
||||
container)
|
||||
|
||||
(defn keys
|
||||
@ -841,7 +848,7 @@ value, one key will be ignored."
|
||||
(var k (next x nil))
|
||||
(while (not= nil k)
|
||||
(array/push arr k)
|
||||
(:= k (next x k)))
|
||||
(set k (next x k)))
|
||||
arr)
|
||||
|
||||
(defn values
|
||||
@ -851,7 +858,7 @@ value, one key will be ignored."
|
||||
(var k (next x nil))
|
||||
(while (not= nil k)
|
||||
(array/push arr x.k)
|
||||
(:= k (next x k)))
|
||||
(set k (next x k)))
|
||||
arr)
|
||||
|
||||
(defn pairs
|
||||
@ -861,7 +868,7 @@ value, one key will be ignored."
|
||||
(var k (next x nil))
|
||||
(while (not= nil k)
|
||||
(array/push arr (tuple k x.k))
|
||||
(:= k (next x k)))
|
||||
(set k (next x k)))
|
||||
arr)
|
||||
|
||||
(defn frequencies
|
||||
@ -871,7 +878,7 @@ value, one key will be ignored."
|
||||
(loop
|
||||
[x :in ind]
|
||||
(def n freqs.x)
|
||||
(:= freqs.x (if n (+ 1 n) 1)))
|
||||
(set freqs.x (if n (+ 1 n) 1)))
|
||||
freqs)
|
||||
|
||||
(defn interleave
|
||||
@ -892,7 +899,7 @@ value, one key will be ignored."
|
||||
[xs]
|
||||
(def ret @[])
|
||||
(def seen @{})
|
||||
(loop [x :in xs] (if seen.x nil (do (:= seen.x true) (array/push ret x))))
|
||||
(loop [x :in xs] (if seen.x nil (do (set seen.x true) (array/push ret x))))
|
||||
ret)
|
||||
|
||||
(defn flatten-into
|
||||
@ -925,7 +932,7 @@ value, one key will be ignored."
|
||||
[sep ind]
|
||||
(def len (length ind))
|
||||
(def ret (array/new (- (* 2 len) 1)))
|
||||
(if (> len 0) (:= ret.0 ind.0))
|
||||
(if (> len 0) (set ret.0 ind.0))
|
||||
(var i 1)
|
||||
(while (< i len)
|
||||
(array/push ret sep ind.i)
|
||||
@ -977,7 +984,7 @@ value, one key will be ignored."
|
||||
$dict expr
|
||||
~(if (dictionary? ,$dict)
|
||||
,((fn aux []
|
||||
(:= key (next pattern key))
|
||||
(set key (next pattern key))
|
||||
(if (= key nil)
|
||||
(onmatch)
|
||||
(match-1 (get pattern key) (tuple get $dict key) aux seen))))
|
||||
@ -1035,7 +1042,7 @@ value, one key will be ignored."
|
||||
(def oldcur current)
|
||||
(def spacer
|
||||
(if (<= maxcol (+ current (length word) 1))
|
||||
(do (:= current 0) "\n ")
|
||||
(do (set current 0) "\n ")
|
||||
(do (++ current) " ")))
|
||||
(+= current (length word))
|
||||
(if (> oldcur 0)
|
||||
@ -1052,7 +1059,7 @@ value, one key will be ignored."
|
||||
(if (> (length word) 0) (pushword))
|
||||
(when (= b 10)
|
||||
(buffer/push-string buf "\n ")
|
||||
(:= current 0)))))
|
||||
(set current 0)))))
|
||||
|
||||
# Last word
|
||||
(pushword)
|
||||
@ -1066,8 +1073,18 @@ value, one key will be ignored."
|
||||
(if (not x)
|
||||
(print "symbol " sym " not found.")
|
||||
(do
|
||||
(def bind-type
|
||||
(string " "
|
||||
(cond
|
||||
x:ref (string :var " (" (type (get x:ref 0)) ")")
|
||||
x:macro :macro
|
||||
(type x:value))
|
||||
"\n"))
|
||||
(def d x:doc)
|
||||
(print "\n\n" (if d (doc-format d) "no documentation found.") "\n\n"))))
|
||||
(print "\n\n"
|
||||
(if d bind-type "")
|
||||
(if d (doc-format d) "no documentation found.")
|
||||
"\n\n"))))
|
||||
|
||||
(defmacro doc
|
||||
"Shows documentation for the given symbol."
|
||||
@ -1089,7 +1106,7 @@ value, one key will be ignored."
|
||||
(var key (next t nil))
|
||||
(while (not= nil key)
|
||||
(put newt (macex1 key) (on-value t.key))
|
||||
(:= key (next t key)))
|
||||
(set key (next t key)))
|
||||
newt)
|
||||
|
||||
(defn expand-bindings [x]
|
||||
@ -1137,7 +1154,7 @@ value, one key will be ignored."
|
||||
(tuple t.0 (qq t.1)))
|
||||
|
||||
(def specs
|
||||
{':= expanddef
|
||||
{'set expanddef
|
||||
'def expanddef
|
||||
'do expandall
|
||||
'fn expandfn
|
||||
@ -1167,19 +1184,24 @@ value, one key will be ignored."
|
||||
x))
|
||||
ret)
|
||||
|
||||
(defn all [pred xs]
|
||||
(defn all
|
||||
"Returns true if all xs are truthy, otherwise the first false or nil value."
|
||||
[pred xs]
|
||||
(var ret true)
|
||||
(loop [x :in xs :while ret] (:= ret (pred x)))
|
||||
(loop [x :in xs :while ret] (set ret (pred x)))
|
||||
ret)
|
||||
|
||||
(defn some [pred xs]
|
||||
(defn some
|
||||
"Returns false if all xs are false or nil, otherwise returns the first true value."
|
||||
[pred xs]
|
||||
(var ret nil)
|
||||
(loop [x :in xs :while (not ret)] (if-let [y (pred x)] (:= ret y)))
|
||||
(loop [x :in xs :while (not ret)] (if-let [y (pred x)] (set ret y)))
|
||||
ret)
|
||||
|
||||
(defn deep-not= [x y]
|
||||
(defn deep-not=
|
||||
"Like not=, but mutable types (arrays, tables, buffers) are considered
|
||||
equal if they have identical structure. Much slower than not=."
|
||||
[x y]
|
||||
(def tx (type x))
|
||||
(or
|
||||
(not= tx (type y))
|
||||
@ -1191,9 +1213,10 @@ value, one key will be ignored."
|
||||
:buffer (not= (string x) (string y))
|
||||
(not= x y))))
|
||||
|
||||
(defn deep= [x y]
|
||||
(defn deep=
|
||||
"Like =, but mutable types (arrays, tables, buffers) are considered
|
||||
equal if they have identical structure. Much slower than =."
|
||||
[x y]
|
||||
(not (deep-not= x y)))
|
||||
|
||||
(defn macex
|
||||
@ -1205,8 +1228,8 @@ value, one key will be ignored."
|
||||
(while (deep-not= current previous)
|
||||
(if (> (++ counter) 200)
|
||||
(error "macro expansion too nested"))
|
||||
(:= previous current)
|
||||
(:= current (macex1 current)))
|
||||
(set previous current)
|
||||
(set current (macex1 current)))
|
||||
current)
|
||||
|
||||
###
|
||||
@ -1216,6 +1239,9 @@ value, one key will be ignored."
|
||||
###
|
||||
|
||||
(defn make-env
|
||||
"Create a new environment table. The new environment
|
||||
will inherit bindings from the parent environment, but new
|
||||
bindings will not pollute the parent environment."
|
||||
[parent &]
|
||||
(def parent (if parent parent _env))
|
||||
(def newenv (table/setproto @{} parent))
|
||||
@ -1251,12 +1277,12 @@ value, one key will be ignored."
|
||||
(if (= (type res) :function)
|
||||
(res)
|
||||
(do
|
||||
(:= good false)
|
||||
(def {:error err :line errl :column errc :fiber errf} res)
|
||||
(set good false)
|
||||
(def {:error err :start start :end end :fiber errf} res)
|
||||
(onstatus
|
||||
:compile
|
||||
(if (< 0 errl)
|
||||
(string err "\n in a form at line " errl ", column " errc)
|
||||
(if (<= 0 start)
|
||||
(string err "\n at (" start ":" end ")")
|
||||
err)
|
||||
errf
|
||||
where))))
|
||||
@ -1266,7 +1292,7 @@ value, one key will be ignored."
|
||||
(if going (onstatus (fiber/status f) res f where))))
|
||||
|
||||
(def oldenv *env*)
|
||||
(:= *env* env)
|
||||
(set *env* env)
|
||||
|
||||
# Run loop
|
||||
(def buf @"")
|
||||
@ -1274,22 +1300,21 @@ value, one key will be ignored."
|
||||
(buffer/clear buf)
|
||||
(chunks buf p)
|
||||
(var pindex 0)
|
||||
(var pstatus nil)
|
||||
(def len (length buf))
|
||||
(if (= len 0) (:= going false))
|
||||
(if (= len 0) (set going false))
|
||||
(while (> len pindex)
|
||||
(+= pindex (parser/consume p buf pindex))
|
||||
(case (parser/status p)
|
||||
:full (eval1 (parser/produce p))
|
||||
:error (do
|
||||
(def (line col) (parser/where p))
|
||||
(onstatus :parse
|
||||
(string (parser/error p)
|
||||
" on line " line
|
||||
", column " col)
|
||||
nil
|
||||
where)))))
|
||||
(while (= (set pstatus (parser/status p)) :full)
|
||||
(eval1 (parser/produce p)))
|
||||
(when (= pstatus :error)
|
||||
(onstatus :parse
|
||||
(string (parser/error p)
|
||||
" around byte " (parser/where p))
|
||||
nil
|
||||
where))))
|
||||
|
||||
(:= *env* oldenv)
|
||||
(set *env* oldenv)
|
||||
|
||||
env)
|
||||
|
||||
@ -1309,7 +1334,7 @@ value, one key will be ignored."
|
||||
"\n")
|
||||
(when f
|
||||
(loop
|
||||
[nf :in (reverse (fiber/lineage f))
|
||||
[nf :in (reverse (debug/lineage f))
|
||||
:before (file/write stderr " (fiber)\n")
|
||||
{:function func
|
||||
:tail tail
|
||||
@ -1317,8 +1342,8 @@ value, one key will be ignored."
|
||||
:c c
|
||||
:name name
|
||||
:source source
|
||||
:line source-line
|
||||
:column source-col} :in (fiber/stack nf)]
|
||||
:source-start start
|
||||
:source-end end} :in (debug/stack nf)]
|
||||
(file/write stderr " in")
|
||||
(when c (file/write stderr " cfunction"))
|
||||
(if name
|
||||
@ -1327,14 +1352,15 @@ value, one key will be ignored."
|
||||
(if source
|
||||
(do
|
||||
(file/write stderr " [" source "]")
|
||||
(if source-line
|
||||
(if start
|
||||
(file/write
|
||||
stderr
|
||||
" on line "
|
||||
(string source-line)
|
||||
", column "
|
||||
(string source-col)))))
|
||||
(if (and (not source-line) pc)
|
||||
" at ("
|
||||
(string start)
|
||||
":"
|
||||
(string end)
|
||||
")"))))
|
||||
(if (and (not start) pc)
|
||||
(file/write stderr " (pc=" (string pc) ")"))
|
||||
(when tail (file/write stderr " (tailcall)"))
|
||||
(file/write stderr "\n"))))
|
||||
@ -1346,7 +1372,7 @@ value, one key will be ignored."
|
||||
(var state (string str))
|
||||
(defn chunks [buf _]
|
||||
(def ret state)
|
||||
(:= state nil)
|
||||
(set state nil)
|
||||
(when ret
|
||||
(buffer/push-string buf str)
|
||||
(buffer/push-string buf "\n")))
|
||||
@ -1354,7 +1380,7 @@ value, one key will be ignored."
|
||||
(run-context *env* chunks
|
||||
(fn [sig x f source]
|
||||
(if (= sig :dead)
|
||||
(:= returnval x)
|
||||
(set returnval x)
|
||||
(status-pp sig x f source)))
|
||||
"eval")
|
||||
returnval)
|
||||
@ -1410,7 +1436,8 @@ value, one key will be ignored."
|
||||
path))
|
||||
|
||||
(def require
|
||||
"Require a module with the given name. Will search all of the paths in
|
||||
"(require module)\n\n
|
||||
Require a module with the given name. Will search all of the paths in
|
||||
module/paths, then the path as a raw file path. Returns the new environment
|
||||
returned from compiling and running the file."
|
||||
(do
|
||||
@ -1446,8 +1473,8 @@ value, one key will be ignored."
|
||||
check
|
||||
(do
|
||||
(def newenv (make-env))
|
||||
(:= cache.path newenv)
|
||||
(:= loading.path true)
|
||||
(set cache.path newenv)
|
||||
(set loading.path true)
|
||||
(def f (find-mod path))
|
||||
(if f
|
||||
(do
|
||||
@ -1466,10 +1493,13 @@ value, one key will be ignored."
|
||||
(if (not n)
|
||||
(error (string "could not open file for module " path)))
|
||||
((native n) newenv)))
|
||||
(:= loading.path false)
|
||||
(set loading.path false)
|
||||
newenv)))))
|
||||
|
||||
(defn import*
|
||||
"Import a module into a given environment table. This is the
|
||||
functional form of (import ...) that expects and explicit environment
|
||||
table."
|
||||
[env path & args]
|
||||
(def targs (table ;args))
|
||||
(def {:as as
|
||||
@ -1483,7 +1513,7 @@ value, one key will be ignored."
|
||||
(when (not v:private)
|
||||
(def newv (table/setproto @{:private true} v))
|
||||
(put env (symbol prefix k) newv))
|
||||
(:= k (next newenv k))))
|
||||
(set k (next newenv k))))
|
||||
|
||||
(defmacro import
|
||||
"Import a module. First requires the module, and then merges its
|
||||
@ -1507,6 +1537,7 @@ value, one key will be ignored."
|
||||
(def newenv (make-env))
|
||||
(default chunks (fn [buf _] (file/read stdin :line buf)))
|
||||
(default onsignal (fn [sig x f source]
|
||||
(put newenv '_fiber @{:value f})
|
||||
(case sig
|
||||
:dead (do
|
||||
(put newenv '_ @{:value x})
|
||||
@ -1519,9 +1550,9 @@ value, one key will be ignored."
|
||||
[env &]
|
||||
(default env *env*)
|
||||
(def envs @[])
|
||||
(do (var e env) (while e (array/push envs e) (:= e (table/getproto e))))
|
||||
(do (var e env) (while e (array/push envs e) (set e (table/getproto e))))
|
||||
(def symbol-set @{})
|
||||
(loop [envi :in envs
|
||||
k :keys envi]
|
||||
(:= symbol-set.k true))
|
||||
(set symbol-set.k true))
|
||||
(sort (keys symbol-set)))
|
||||
|
@ -324,7 +324,7 @@ static const JanetReg cfuns[] = {
|
||||
"(buffer & xs)\n\n"
|
||||
"Creates a new buffer by concatenating values together. Values are "
|
||||
"converted to bytes via describe if they are not byte sequences. Returns "
|
||||
"the new symbol."
|
||||
"the new buffer."
|
||||
},
|
||||
{"abstract?", janet_core_is_abstract,
|
||||
"(abstract? x)\n\n"
|
||||
@ -384,7 +384,7 @@ static const JanetReg cfuns[] = {
|
||||
{"gcsetinterval", janet_core_gcsetinterval,
|
||||
"(gcsetinterval interval)\n\n"
|
||||
"Set an integer number of bytes to allocate before running garbage collection. "
|
||||
"Low values interval will be slower but use less memory. "
|
||||
"Low valuesi for interval will be slower but use less memory. "
|
||||
"High values will be faster but use more memory."
|
||||
},
|
||||
{"gcinterval", janet_core_gcinterval,
|
||||
@ -406,9 +406,9 @@ static const JanetReg cfuns[] = {
|
||||
"\t:string\n"
|
||||
"\t:buffer\n"
|
||||
"\t:symbol\n"
|
||||
"\t:abstract\n"
|
||||
"\t:function\n"
|
||||
"\t:cfunction"
|
||||
"\t:cfunction\n\n"
|
||||
"or another symbol for an abstract type."
|
||||
},
|
||||
{"next", janet_core_next,
|
||||
"(next dict key)\n\n"
|
||||
@ -711,25 +711,25 @@ JanetTable *janet_core_env(void) {
|
||||
"Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns "
|
||||
"the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining "
|
||||
"values. Division by two integers uses truncating division.");
|
||||
templatize_varop(env, JANET_FUN_BAND, "&", -1, -1, JOP_BAND,
|
||||
"(& & xs)\n\n"
|
||||
templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND,
|
||||
"(band & xs)\n\n"
|
||||
"Returns the bitwise and of all values in xs. Each x in xs must be an integer.");
|
||||
templatize_varop(env, JANET_FUN_BOR, "|", 0, 0, JOP_BOR,
|
||||
"(| & xs)\n\n"
|
||||
templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR,
|
||||
"(bor & xs)\n\n"
|
||||
"Returns the bitwise or of all values in xs. Each x in xs must be an integer.");
|
||||
templatize_varop(env, JANET_FUN_BXOR, "^", 0, 0, JOP_BXOR,
|
||||
"(^ & xs)\n\n"
|
||||
templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR,
|
||||
"(bxor & xs)\n\n"
|
||||
"Returns the bitwise xor of all values in xs. Each in xs must be an integer.");
|
||||
templatize_varop(env, JANET_FUN_LSHIFT, "<<", 1, 1, JOP_SHIFT_LEFT,
|
||||
"(<< x & shifts)\n\n"
|
||||
templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT,
|
||||
"(blshift x & shifts)\n\n"
|
||||
"Returns the value of x bit shifted left by the sum of all values in shifts. x "
|
||||
"and each element in shift must be an integer.");
|
||||
templatize_varop(env, JANET_FUN_RSHIFT, ">>", 1, 1, JOP_SHIFT_RIGHT,
|
||||
"(>> x & shifts)\n\n"
|
||||
templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT,
|
||||
"(brshift x & shifts)\n\n"
|
||||
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
|
||||
"and each element in shift must be an integer.");
|
||||
templatize_varop(env, JANET_FUN_RSHIFTU, ">>>", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED,
|
||||
"(>> x & shifts)\n\n"
|
||||
templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED,
|
||||
"(brushift x & shifts)\n\n"
|
||||
"Returns the value of x bit shifted right by the sum of all values in shifts. x "
|
||||
"and each element in shift must be an integer. The sign of x is not preserved, so "
|
||||
"for positive shifts the return value will always be positive.");
|
||||
@ -801,6 +801,7 @@ JanetTable *janet_core_env(void) {
|
||||
janet_lib_os(args);
|
||||
janet_lib_parse(args);
|
||||
janet_lib_compile(args);
|
||||
janet_lib_debug(args);
|
||||
janet_lib_string(args);
|
||||
janet_lib_marsh(args);
|
||||
#ifdef JANET_ASSEMBLER
|
||||
|
313
src/core/debug.c
Normal file
313
src/core/debug.c
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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.
|
||||
*/
|
||||
|
||||
#include <janet/janet.h>
|
||||
#include "gc.h"
|
||||
#include "state.h"
|
||||
|
||||
/* Implements functionality to build a debugger from within janet.
|
||||
* The repl should also be able to serve as pretty featured debugger
|
||||
* out of the box. */
|
||||
|
||||
/* Add a break point to a function */
|
||||
int janet_debug_break(JanetFuncDef *def, int32_t pc) {
|
||||
if (pc >= def->bytecode_length || pc < 0)
|
||||
return 1;
|
||||
def->bytecode[pc] |= 0x80;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove a break point from a function */
|
||||
int janet_debug_unbreak(JanetFuncDef *def, int32_t pc) {
|
||||
if (pc >= def->bytecode_length || pc < 0)
|
||||
return 1;
|
||||
def->bytecode[pc] &= ~((uint32_t)0x80);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a location for a breakpoint given a source file an
|
||||
* location.
|
||||
*/
|
||||
int janet_debug_find(
|
||||
JanetFuncDef **def_out, int32_t *pc_out,
|
||||
const uint8_t *source, int32_t offset) {
|
||||
/* Scan the heap for right func def */
|
||||
JanetGCMemoryHeader *current = janet_vm_blocks;
|
||||
/* Keep track of the best source mapping we have seen so far */
|
||||
int32_t besti = -1;
|
||||
int32_t best_range = INT32_MAX;
|
||||
JanetFuncDef *best_def = NULL;
|
||||
while (NULL != current) {
|
||||
if ((current->flags & JANET_MEM_TYPEBITS) == JANET_MEMORY_FUNCDEF) {
|
||||
JanetFuncDef *def = (JanetFuncDef *)(current + 1);
|
||||
if (def->sourcemap &&
|
||||
def->source &&
|
||||
!janet_string_compare(source, def->source)) {
|
||||
/* Correct source file, check mappings. The chosen
|
||||
* pc index is the first match with the smallest range. */
|
||||
int32_t i;
|
||||
for (i = 0; i < def->bytecode_length; i++) {
|
||||
int32_t start = def->sourcemap[i].start;
|
||||
int32_t end = def->sourcemap[i].end;
|
||||
if (end - start < best_range &&
|
||||
start <= offset &&
|
||||
end >= offset) {
|
||||
best_range = end - start;
|
||||
besti = i;
|
||||
best_def = def;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
if (best_def) {
|
||||
*def_out = best_def;
|
||||
*pc_out = besti;
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CFuns
|
||||
*/
|
||||
|
||||
/* Helper to find funcdef and bytecode offset to insert or remove breakpoints.
|
||||
* Takes a source file name and byte offset. */
|
||||
static int helper_find(JanetArgs args, JanetFuncDef **def, int32_t *bytecode_offset) {
|
||||
const uint8_t *source;
|
||||
int32_t source_offset;
|
||||
JANET_FIXARITY(args, 2);
|
||||
JANET_ARG_STRING(source, args, 0);
|
||||
JANET_ARG_INTEGER(source_offset, args, 1);
|
||||
if (janet_debug_find(
|
||||
def, bytecode_offset, source, source_offset)) {
|
||||
JANET_THROW(args, "could not find breakpoint");
|
||||
}
|
||||
JANET_RETURN_NIL(args);
|
||||
}
|
||||
|
||||
/* Helper to find funcdef and bytecode offset to insert or remove breakpoints.
|
||||
* Takes a function and byte offset*/
|
||||
static int helper_find_fun(JanetArgs args, JanetFuncDef **def, int32_t *bytecode_offset) {
|
||||
JanetFunction *func;
|
||||
int32_t offset = 0;
|
||||
JANET_MINARITY(args, 1);
|
||||
JANET_MAXARITY(args, 2);
|
||||
JANET_ARG_FUNCTION(func, args, 0);
|
||||
if (args.n == 2) {
|
||||
JANET_ARG_INTEGER(offset, args, 1);
|
||||
}
|
||||
*def = func->def;
|
||||
*bytecode_offset = offset;
|
||||
JANET_RETURN_NIL(args);
|
||||
}
|
||||
|
||||
static int cfun_break(JanetArgs args) {
|
||||
JanetFuncDef *def;
|
||||
int32_t offset;
|
||||
int status = helper_find(args, &def, &offset);
|
||||
if (status == 0) janet_debug_break(def, offset);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int cfun_unbreak(JanetArgs args) {
|
||||
JanetFuncDef *def;
|
||||
int32_t offset;
|
||||
int status = helper_find(args, &def, &offset);
|
||||
if (status == 0) janet_debug_unbreak(def, offset);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int cfun_fbreak(JanetArgs args) {
|
||||
JanetFuncDef *def;
|
||||
int32_t offset;
|
||||
int status = helper_find_fun(args, &def, &offset);
|
||||
if (status == 0) {
|
||||
if (janet_debug_break(def, offset)) {
|
||||
JANET_THROW(args, "could not find breakpoint");
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int cfun_unfbreak(JanetArgs args) {
|
||||
JanetFuncDef *def;
|
||||
int32_t offset;
|
||||
int status = helper_find_fun(args, &def, &offset);
|
||||
if (status == 0) {
|
||||
if (janet_debug_unbreak(def, offset)) {
|
||||
JANET_THROW(args, "could not find breakpoint");
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int cfun_lineage(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JanetArray *array;
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
array = janet_array(0);
|
||||
while (fiber) {
|
||||
janet_array_push(array, janet_wrap_fiber(fiber));
|
||||
fiber = fiber->child;
|
||||
}
|
||||
JANET_RETURN_ARRAY(args, array);
|
||||
}
|
||||
|
||||
/* Extract info from one stack frame */
|
||||
static Janet doframe(JanetStackFrame *frame) {
|
||||
int32_t off;
|
||||
JanetTable *t = janet_table(3);
|
||||
JanetFuncDef *def = NULL;
|
||||
if (frame->func) {
|
||||
janet_table_put(t, janet_csymbolv(":function"), janet_wrap_function(frame->func));
|
||||
def = frame->func->def;
|
||||
if (def->name) {
|
||||
janet_table_put(t, janet_csymbolv(":name"), janet_wrap_string(def->name));
|
||||
}
|
||||
} 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_csymbolv(":name"), name);
|
||||
}
|
||||
}
|
||||
janet_table_put(t, janet_csymbolv(":c"), janet_wrap_true());
|
||||
}
|
||||
if (frame->flags & JANET_STACKFRAME_TAILCALL) {
|
||||
janet_table_put(t, janet_csymbolv(":tail"), janet_wrap_true());
|
||||
}
|
||||
if (frame->func && frame->pc) {
|
||||
Janet *stack = (Janet *)frame + JANET_FRAME_SIZE;
|
||||
JanetArray *slots;
|
||||
off = (int32_t) (frame->pc - def->bytecode);
|
||||
janet_table_put(t, janet_csymbolv(":pc"), janet_wrap_integer(off));
|
||||
if (def->sourcemap) {
|
||||
JanetSourceMapping mapping = def->sourcemap[off];
|
||||
janet_table_put(t, janet_csymbolv(":source-start"), janet_wrap_integer(mapping.start));
|
||||
janet_table_put(t, janet_csymbolv(":source-end"), janet_wrap_integer(mapping.end));
|
||||
}
|
||||
if (def->source) {
|
||||
janet_table_put(t, janet_csymbolv(":source"), janet_wrap_string(def->source));
|
||||
}
|
||||
/* Add stack arguments */
|
||||
slots = janet_array(def->slotcount);
|
||||
memcpy(slots->data, stack, sizeof(Janet) * def->slotcount);
|
||||
slots->count = def->slotcount;
|
||||
janet_table_put(t, janet_csymbolv(":slots"), janet_wrap_array(slots));
|
||||
}
|
||||
return janet_wrap_table(t);
|
||||
}
|
||||
|
||||
static int cfun_stack(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JanetArray *array;
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
array = janet_array(0);
|
||||
{
|
||||
int32_t i = fiber->frame;
|
||||
JanetStackFrame *frame;
|
||||
while (i > 0) {
|
||||
frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE);
|
||||
janet_array_push(array, doframe(frame));
|
||||
i = frame->prevframe;
|
||||
}
|
||||
}
|
||||
JANET_RETURN_ARRAY(args, array);
|
||||
}
|
||||
|
||||
static int cfun_argstack(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JanetArray *array;
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
array = janet_array(fiber->stacktop - fiber->stackstart);
|
||||
memcpy(array->data, fiber->data + fiber->stackstart, array->capacity * sizeof(Janet));
|
||||
array->count = array->capacity;
|
||||
JANET_RETURN_ARRAY(args, array);
|
||||
}
|
||||
|
||||
static const JanetReg cfuns[] = {
|
||||
{"debug/break", cfun_break,
|
||||
"(debug/break source byte-offset)\n\n"
|
||||
"Sets a breakpoint with source a key at a given byte offset. An offset "
|
||||
"of 0 is the first byte in a file. Will throw an error if the breakpoint location "
|
||||
"cannot be found. For example\n\n"
|
||||
"\t(debug/break \"core.janet\" 1000)\n\n"
|
||||
"wil set a breakpoint at the 1000th byte of the file core.janet."},
|
||||
{"debug/unbreak", cfun_unbreak,
|
||||
"(debug/unbreak source byte-offset)\n\n"
|
||||
"Remove a breakpoint with a source key at a given byte offset. An offset "
|
||||
"of 0 is the first byte in a file. Will throw an error if the breakpoint "
|
||||
"cannot be found."},
|
||||
{"debug/fbreak", cfun_fbreak,
|
||||
"(debug/fbreak fun [,pc=0])\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_unfbreak,
|
||||
"(debug/unfbreak fun [,pc=0])\n\n"
|
||||
"Unset a breakpoint set with debug/fbreak."},
|
||||
{"debug/arg-stack", cfun_argstack,
|
||||
"(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_stack,
|
||||
"(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"
|
||||
"\t:c - true if the stack frame is a c function invocation\n"
|
||||
"\t:column - the current source column of the stack frame\n"
|
||||
"\t:function - the function that the stack frame represents\n"
|
||||
"\t:line - the current source line of the stack frame\n"
|
||||
"\t:name - the human friendly name of the function\n"
|
||||
"\t:pc - integer indicating the location of the program counter\n"
|
||||
"\t:source - string with filename or other identifier for the source code\n"
|
||||
"\t:slots - array of all values in each slot\n"
|
||||
"\t:tail - boolean indicating a tail call"
|
||||
},
|
||||
{"debug/lineage", cfun_lineage,
|
||||
"(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."
|
||||
},
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
/* Module entry point */
|
||||
int janet_lib_debug(JanetArgs args) {
|
||||
JanetTable *env = janet_env(args);
|
||||
janet_cfuns(env, NULL, cfuns);
|
||||
return 0;
|
||||
}
|
114
src/core/fiber.c
114
src/core/fiber.c
@ -349,88 +349,11 @@ static int cfun_new(JanetArgs args) {
|
||||
|
||||
static int cfun_status(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
const char *status = "";
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
uint32_t s = (fiber->flags & JANET_FIBER_STATUS_MASK) >>
|
||||
JANET_FIBER_STATUS_OFFSET;
|
||||
switch (s) {
|
||||
case JANET_STATUS_DEAD: status = ":dead"; break;
|
||||
case JANET_STATUS_ERROR: status = ":error"; break;
|
||||
case JANET_STATUS_DEBUG: status = ":debug"; break;
|
||||
case JANET_STATUS_PENDING: status = ":pending"; break;
|
||||
case JANET_STATUS_USER0: status = ":user0"; break;
|
||||
case JANET_STATUS_USER1: status = ":user1"; break;
|
||||
case JANET_STATUS_USER2: status = ":user2"; break;
|
||||
case JANET_STATUS_USER3: status = ":user3"; break;
|
||||
case JANET_STATUS_USER4: status = ":user4"; break;
|
||||
case JANET_STATUS_USER5: status = ":user5"; break;
|
||||
case JANET_STATUS_USER6: status = ":user6"; break;
|
||||
case JANET_STATUS_USER7: status = ":user7"; break;
|
||||
case JANET_STATUS_USER8: status = ":user8"; break;
|
||||
case JANET_STATUS_USER9: status = ":user9"; break;
|
||||
case JANET_STATUS_NEW: status = ":new"; break;
|
||||
default:
|
||||
case JANET_STATUS_ALIVE: status = ":alive"; break;
|
||||
}
|
||||
JANET_RETURN_CSYMBOL(args, status);
|
||||
}
|
||||
|
||||
/* Extract info from one stack frame */
|
||||
static Janet doframe(JanetStackFrame *frame) {
|
||||
int32_t off;
|
||||
JanetTable *t = janet_table(3);
|
||||
JanetFuncDef *def = NULL;
|
||||
if (frame->func) {
|
||||
janet_table_put(t, janet_csymbolv(":function"), janet_wrap_function(frame->func));
|
||||
def = frame->func->def;
|
||||
if (def->name) {
|
||||
janet_table_put(t, janet_csymbolv(":name"), janet_wrap_string(def->name));
|
||||
}
|
||||
} 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_csymbolv(":name"), name);
|
||||
}
|
||||
}
|
||||
janet_table_put(t, janet_csymbolv(":c"), janet_wrap_true());
|
||||
}
|
||||
if (frame->flags & JANET_STACKFRAME_TAILCALL) {
|
||||
janet_table_put(t, janet_csymbolv(":tail"), janet_wrap_true());
|
||||
}
|
||||
if (frame->func && frame->pc) {
|
||||
off = (int32_t) (frame->pc - def->bytecode);
|
||||
janet_table_put(t, janet_csymbolv(":pc"), janet_wrap_integer(off));
|
||||
if (def->sourcemap) {
|
||||
JanetSourceMapping mapping = def->sourcemap[off];
|
||||
janet_table_put(t, janet_csymbolv(":line"), janet_wrap_integer(mapping.line));
|
||||
janet_table_put(t, janet_csymbolv(":column"), janet_wrap_integer(mapping.column));
|
||||
}
|
||||
if (def->source) {
|
||||
janet_table_put(t, janet_csymbolv(":source"), janet_wrap_string(def->source));
|
||||
}
|
||||
}
|
||||
return janet_wrap_table(t);
|
||||
}
|
||||
|
||||
static int cfun_stack(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JanetArray *array;
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
array = janet_array(0);
|
||||
{
|
||||
int32_t i = fiber->frame;
|
||||
JanetStackFrame *frame;
|
||||
while (i > 0) {
|
||||
frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE);
|
||||
janet_array_push(array, doframe(frame));
|
||||
i = frame->prevframe;
|
||||
}
|
||||
}
|
||||
JANET_RETURN_ARRAY(args, array);
|
||||
JANET_RETURN_CSYMBOL(args, janet_status_names[s]);
|
||||
}
|
||||
|
||||
static int cfun_current(JanetArgs args) {
|
||||
@ -438,19 +361,6 @@ static int cfun_current(JanetArgs args) {
|
||||
JANET_RETURN_FIBER(args, janet_vm_fiber);
|
||||
}
|
||||
|
||||
static int cfun_lineage(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JanetArray *array;
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_ARG_FIBER(fiber, args, 0);
|
||||
array = janet_array(0);
|
||||
while (fiber) {
|
||||
janet_array_push(array, janet_wrap_fiber(fiber));
|
||||
fiber = fiber->child;
|
||||
}
|
||||
JANET_RETURN_ARRAY(args, array);
|
||||
}
|
||||
|
||||
static int cfun_maxstack(JanetArgs args) {
|
||||
JanetFiber *fiber;
|
||||
JANET_FIXARITY(args, 1);
|
||||
@ -500,32 +410,10 @@ static const JanetReg cfuns[] = {
|
||||
"\t:alive - the fiber is currently running and cannot be resumed\n"
|
||||
"\t:new - the fiber has just been created and not yet run"
|
||||
},
|
||||
{"fiber/stack", cfun_stack,
|
||||
"(fiber/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"
|
||||
"\t:c - true if the stack frame is a c function invokation\n"
|
||||
"\t:column - the current source column of the stack frame\n"
|
||||
"\t:function - the function that the stack frame represents\n"
|
||||
"\t:line - the current source line of the stack frame\n"
|
||||
"\t:name - the human friendly name of the function\n"
|
||||
"\t:pc - integer indicating the location of the program counter\n"
|
||||
"\t:source - string with filename or other identifier for the source code\n"
|
||||
"\t:tail - boolean indicating a tail call"
|
||||
},
|
||||
{"fiber/current", cfun_current,
|
||||
"(fiber/current)\n\n"
|
||||
"Returns the currently running fiber."
|
||||
},
|
||||
{"fiber/lineage", cfun_lineage,
|
||||
"(fiber/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."
|
||||
},
|
||||
{"fiber/maxstack", cfun_maxstack,
|
||||
"(fiber/maxstack fib)\n\n"
|
||||
"Gets the maximum stack size in janet values allowed for a fiber. While memory for "
|
||||
|
@ -47,7 +47,7 @@ struct IOFile {
|
||||
static int janet_io_gc(void *p, size_t len);
|
||||
|
||||
JanetAbstractType janet_io_filetype = {
|
||||
":core.file",
|
||||
":core/file",
|
||||
janet_io_gc,
|
||||
NULL
|
||||
};
|
||||
|
@ -234,8 +234,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
|
||||
if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCEMAP) {
|
||||
for (int32_t i = 0; i < def->bytecode_length; i++) {
|
||||
JanetSourceMapping map = def->sourcemap[i];
|
||||
pushint(st, map.line);
|
||||
pushint(st, map.column);
|
||||
pushint(st, map.start);
|
||||
pushint(st, map.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -740,8 +740,8 @@ static const uint8_t *unmarshal_one_def(
|
||||
JANET_OUT_OF_MEMORY;
|
||||
}
|
||||
for (int32_t i = 0; i < bytecode_length; i++) {
|
||||
def->sourcemap[i].line = readint(st, &data);
|
||||
def->sourcemap[i].column = readint(st, &data);
|
||||
def->sourcemap[i].start = readint(st, &data);
|
||||
def->sourcemap[i].end = readint(st, &data);
|
||||
}
|
||||
} else {
|
||||
def->sourcemap = NULL;
|
||||
|
@ -102,8 +102,7 @@ struct JanetParseState {
|
||||
int32_t counter;
|
||||
int32_t argn;
|
||||
int flags;
|
||||
size_t start_line;
|
||||
size_t start_col;
|
||||
size_t start;
|
||||
Consumer consumer;
|
||||
};
|
||||
|
||||
@ -147,8 +146,7 @@ static void pushstate(JanetParser *p, Consumer consumer, int flags) {
|
||||
s.argn = 0;
|
||||
s.flags = flags;
|
||||
s.consumer = consumer;
|
||||
s.start_line = p->line;
|
||||
s.start_col = p->col;
|
||||
s.start = p->offset;
|
||||
_pushstate(p, s);
|
||||
}
|
||||
|
||||
@ -159,8 +157,8 @@ static void popstate(JanetParser *p, Janet val) {
|
||||
if (newtop->flags & PFLAG_CONTAINER) {
|
||||
/* Source mapping info */
|
||||
if (janet_checktype(val, JANET_TUPLE)) {
|
||||
janet_tuple_sm_line(janet_unwrap_tuple(val)) = (int32_t) top.start_line;
|
||||
janet_tuple_sm_col(janet_unwrap_tuple(val)) = (int32_t) top.start_col;
|
||||
janet_tuple_sm_start(janet_unwrap_tuple(val)) = (int32_t) top.start;
|
||||
janet_tuple_sm_end(janet_unwrap_tuple(val)) = (int32_t) p->offset;
|
||||
}
|
||||
newtop->argn++;
|
||||
push_arg(p, val);
|
||||
@ -176,8 +174,8 @@ static void popstate(JanetParser *p, Janet val) {
|
||||
t[0] = janet_csymbolv(which);
|
||||
t[1] = val;
|
||||
/* Quote source mapping info */
|
||||
janet_tuple_sm_line(t) = (int32_t) newtop->start_line;
|
||||
janet_tuple_sm_col(t) = (int32_t) newtop->start_col;
|
||||
janet_tuple_sm_start(t) = (int32_t) newtop->start;
|
||||
janet_tuple_sm_end(t) = (int32_t) p->offset;
|
||||
val = janet_wrap_tuple(janet_tuple_end(t));
|
||||
} else {
|
||||
return;
|
||||
@ -522,12 +520,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) {
|
||||
int janet_parser_consume(JanetParser *parser, uint8_t c) {
|
||||
int consumed = 0;
|
||||
if (parser->error) return 0;
|
||||
if (c == '\n') {
|
||||
parser->line++;
|
||||
parser->col = 0;
|
||||
} else if (c != '\r') {
|
||||
parser->col++;
|
||||
}
|
||||
parser->offset++;
|
||||
while (!consumed && !parser->error) {
|
||||
JanetParseState *state = parser->states + parser->statecount - 1;
|
||||
consumed = state->consumer(parser, state, c);
|
||||
@ -584,9 +577,8 @@ void janet_parser_init(JanetParser *parser) {
|
||||
parser->statecount = 0;
|
||||
parser->statecap = 0;
|
||||
parser->error = NULL;
|
||||
parser->line = 1;
|
||||
parser->col = 0;
|
||||
parser->lookback = -1;
|
||||
parser->offset = 0;
|
||||
|
||||
pushstate(parser, root, PFLAG_CONTAINER);
|
||||
}
|
||||
@ -617,7 +609,7 @@ static int parsergc(void *p, size_t size) {
|
||||
}
|
||||
|
||||
static JanetAbstractType janet_parse_parsertype = {
|
||||
":core.parser",
|
||||
":core/parser",
|
||||
parsergc,
|
||||
parsermark
|
||||
};
|
||||
@ -742,10 +734,7 @@ static int cfun_where(JanetArgs args) {
|
||||
JANET_FIXARITY(args, 1);
|
||||
JANET_CHECKABSTRACT(args, 0, &janet_parse_parsertype);
|
||||
p = (JanetParser *) janet_unwrap_abstract(args.v[0]);
|
||||
Janet *tup = janet_tuple_begin(2);
|
||||
tup[0] = janet_wrap_integer((int32_t)p->line);
|
||||
tup[1] = janet_wrap_integer((int32_t)p->col);
|
||||
JANET_RETURN_TUPLE(args, janet_tuple_end(tup));
|
||||
JANET_RETURN_INTEGER(args, p->offset);
|
||||
}
|
||||
|
||||
static int cfun_state(JanetArgs args) {
|
||||
|
@ -67,7 +67,7 @@ void janet_stacktrace(JanetFiber *fiber, const char *errtype, Janet err) {
|
||||
int32_t off = (int32_t) (frame->pc - def->bytecode);
|
||||
if (def->sourcemap) {
|
||||
JanetSourceMapping mapping = def->sourcemap[off];
|
||||
fprintf(stderr, " on line %d, column %d", mapping.line, mapping.column);
|
||||
fprintf(stderr, " at (%d:%d)", mapping.start, mapping.end);
|
||||
} else {
|
||||
fprintf(stderr, " pc=%d", off);
|
||||
}
|
||||
@ -75,6 +75,8 @@ void janet_stacktrace(JanetFiber *fiber, const char *errtype, Janet err) {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
janet_v_free(fibers);
|
||||
}
|
||||
|
||||
/* Run a string */
|
||||
|
@ -172,6 +172,15 @@ static int destructure(JanetCompiler *c,
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a source map for definitions. */
|
||||
static const Janet *janetc_make_sourcemap(JanetCompiler *c) {
|
||||
Janet *tup = janet_tuple_begin(3);
|
||||
tup[0] = janet_wrap_string(c->source);
|
||||
tup[1] = janet_wrap_integer(c->current_mapping.start);
|
||||
tup[2] = janet_wrap_integer(c->current_mapping.end);
|
||||
return janet_tuple_end(tup);
|
||||
}
|
||||
|
||||
static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) {
|
||||
/*JanetFopts subopts = janetc_fopts_default(opts.compiler);*/
|
||||
/*JanetSlot ret, dest;*/
|
||||
@ -252,6 +261,8 @@ static int varleaf(
|
||||
JanetArray *ref = janet_array(1);
|
||||
janet_array_push(ref, janet_wrap_nil());
|
||||
janet_table_put(reftab, janet_csymbolv(":ref"), janet_wrap_array(ref));
|
||||
janet_table_put(reftab, janet_csymbolv(":source-map"),
|
||||
janet_wrap_tuple(janetc_make_sourcemap(c)));
|
||||
janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(reftab));
|
||||
refslot = janetc_cslot(janet_wrap_array(ref));
|
||||
janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0);
|
||||
@ -278,6 +289,8 @@ static int defleaf(
|
||||
JanetTable *attr) {
|
||||
if (c->scope->flags & JANET_SCOPE_TOP) {
|
||||
JanetTable *tab = janet_table(2);
|
||||
janet_table_put(tab, janet_csymbolv(":source-map"),
|
||||
janet_wrap_tuple(janetc_make_sourcemap(c)));
|
||||
tab->proto = attr;
|
||||
JanetSlot valsym = janetc_cslot(janet_csymbolv(":value"));
|
||||
JanetSlot tabslot = janetc_cslot(janet_wrap_table(tab));
|
||||
@ -648,16 +661,15 @@ error2:
|
||||
|
||||
/* Keep in lexicographic order */
|
||||
static const JanetSpecial janetc_specials[] = {
|
||||
{":=", janetc_varset},
|
||||
{"def", janetc_def},
|
||||
{"do", janetc_do},
|
||||
{"fn", janetc_fn},
|
||||
{"if", janetc_if},
|
||||
{"quasiquote", janetc_quasiquote},
|
||||
{"quote", janetc_quote},
|
||||
{"set", janetc_varset},
|
||||
{"splice", janetc_splice},
|
||||
{"unquote", janetc_unquote},
|
||||
{"unquote", janetc_unquote},
|
||||
{"var", janetc_var},
|
||||
{"while", janetc_while}
|
||||
};
|
||||
|
@ -32,8 +32,8 @@ Janet *janet_tuple_begin(int32_t length) {
|
||||
char *data = janet_gcalloc(JANET_MEMORY_TUPLE, 4 * sizeof(int32_t) + length * sizeof(Janet));
|
||||
Janet *tuple = (Janet *)(data + (4 * sizeof(int32_t)));
|
||||
janet_tuple_length(tuple) = length;
|
||||
janet_tuple_sm_line(tuple) = 0;
|
||||
janet_tuple_sm_col(tuple) = 0;
|
||||
janet_tuple_sm_start(tuple) = -1;
|
||||
janet_tuple_sm_end(tuple) = -1;
|
||||
return tuple;
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,42 @@ const char *const janet_type_names[16] = {
|
||||
":abstract"
|
||||
};
|
||||
|
||||
const char *const janet_signal_names[14] = {
|
||||
":ok",
|
||||
":error",
|
||||
":debug",
|
||||
":yield",
|
||||
":user0",
|
||||
":user1",
|
||||
":user2",
|
||||
":user3",
|
||||
":user4",
|
||||
":user5",
|
||||
":user6",
|
||||
":user7",
|
||||
":user8",
|
||||
":user9"
|
||||
};
|
||||
|
||||
const char *const janet_status_names[16] = {
|
||||
":dead",
|
||||
":error",
|
||||
":debug",
|
||||
":pending",
|
||||
":user0",
|
||||
":user1",
|
||||
":user2",
|
||||
":user3",
|
||||
":user4",
|
||||
":user5",
|
||||
":user6",
|
||||
":user7",
|
||||
":user8",
|
||||
":user9",
|
||||
":new",
|
||||
":alive"
|
||||
};
|
||||
|
||||
/* Calculate hash for string */
|
||||
|
||||
int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
|
||||
|
@ -57,5 +57,6 @@ int janet_lib_parse(JanetArgs args);
|
||||
int janet_lib_asm(JanetArgs args);
|
||||
#endif
|
||||
int janet_lib_compile(JanetArgs args);
|
||||
int janet_lib_debug(JanetArgs args);
|
||||
|
||||
#endif
|
||||
|
@ -54,6 +54,8 @@ JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out) {
|
||||
/* Expected types on type error */
|
||||
uint16_t expected_types;
|
||||
|
||||
uint8_t first_opcode;
|
||||
|
||||
/* Signal to return when done */
|
||||
JanetSignal signal = JANET_SIGNAL_OK;
|
||||
|
||||
@ -92,16 +94,24 @@ JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out) {
|
||||
* instruction. */
|
||||
retreg = in;
|
||||
goto vm_resume_child;
|
||||
} else if (startstatus != JANET_STATUS_NEW) {
|
||||
} else if (startstatus != JANET_STATUS_NEW &&
|
||||
((*pc & 0xFF) == JOP_SIGNAL)) {
|
||||
/* Only should be hit if child is waiting on a SIGNAL instruction */
|
||||
/* If waiting for response to signal, use input and increment pc */
|
||||
stack[oparg(1, 0xFF)] = in;
|
||||
pc++;
|
||||
}
|
||||
|
||||
/* The first opcode to execute. If the first opcode has
|
||||
* the breakpoint bit set and we were in the debug state, skip
|
||||
* that first breakpoint. */
|
||||
first_opcode = (startstatus == JANET_STATUS_DEBUG)
|
||||
? (*pc & 0x7F)
|
||||
: (*pc & 0xFF);
|
||||
|
||||
/* Use computed gotos for GCC and clang, otherwise use switch */
|
||||
#ifdef __GNUC__
|
||||
#define VM_START() {vm_next();
|
||||
#ifdef ____GNUC__
|
||||
#define VM_START() { goto *op_lookup[first_opcode];
|
||||
#define VM_END() }
|
||||
#define VM_OP(op) label_##op :
|
||||
#define VM_DEFAULT() label_unknown_op:
|
||||
@ -193,11 +203,11 @@ static void *op_lookup[255] = {
|
||||
&&label_unknown_op
|
||||
};
|
||||
#else
|
||||
#define VM_START() for(;;){switch(*pc & 0xFF){
|
||||
#define VM_START() uint8_t opcode = first_opcode; for (;;) {switch(opcode) {
|
||||
#define VM_END() }}
|
||||
#define VM_OP(op) case op :
|
||||
#define VM_DEFAULT() default:
|
||||
#define vm_next() continue
|
||||
#define vm_next() opcode = *pc & 0xFF; continue
|
||||
#endif
|
||||
|
||||
#define vm_checkgc_next() janet_maybe_collect(); vm_next()
|
||||
@ -279,6 +289,7 @@ static void *op_lookup[255] = {
|
||||
VM_START();
|
||||
|
||||
VM_DEFAULT();
|
||||
signal = JANET_SIGNAL_DEBUG;
|
||||
retreg = janet_wrap_nil();
|
||||
goto vm_exit;
|
||||
|
||||
@ -535,7 +546,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_GREATER_THAN_INTEGER)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_integer(stack[oparg(2, 0xFF)]) >
|
||||
@ -543,7 +553,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_GREATER_THAN_IMMEDIATE)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_integer(stack[oparg(2, 0xFF)]) > ((*(int32_t *)pc) >> 24)
|
||||
@ -551,7 +560,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_GREATER_THAN_REAL)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_real(stack[oparg(2, 0xFF)]) >
|
||||
@ -559,7 +567,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_GREATER_THAN_EQUAL_REAL)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_real(stack[oparg(2, 0xFF)]) >=
|
||||
@ -575,7 +582,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_EQUALS_INTEGER)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_integer(stack[oparg(2, 0xFF)]) ==
|
||||
@ -584,7 +590,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_EQUALS_REAL)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_real(stack[oparg(2, 0xFF)]) ==
|
||||
@ -593,7 +598,6 @@ static void *op_lookup[255] = {
|
||||
pc++;
|
||||
vm_next();
|
||||
|
||||
/* Candidate */
|
||||
VM_OP(JOP_EQUALS_IMMEDIATE)
|
||||
stack[oparg(1, 0xFF)] = janet_wrap_boolean(
|
||||
janet_unwrap_integer(stack[oparg(2, 0xFF)]) == ((*(int32_t *)pc) >> 24)
|
||||
|
@ -29,7 +29,7 @@ extern "C" {
|
||||
|
||||
/***** START SECTION CONFIG *****/
|
||||
|
||||
#define JANET_VERSION "0.1.0"
|
||||
#define JANET_VERSION "0.2.0"
|
||||
|
||||
#ifndef JANET_BUILD
|
||||
#define JANET_BUILD "local"
|
||||
@ -204,6 +204,8 @@ extern "C" {
|
||||
|
||||
/* Names of all of the types */
|
||||
extern const char *const janet_type_names[16];
|
||||
extern const char *const janet_signal_names[14];
|
||||
extern const char *const janet_status_names[16];
|
||||
|
||||
/* Fiber signals */
|
||||
typedef enum {
|
||||
@ -667,8 +669,8 @@ struct JanetKV {
|
||||
|
||||
/* Source mapping structure for a bytecode instruction */
|
||||
struct JanetSourceMapping {
|
||||
int32_t line;
|
||||
int32_t column;
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
};
|
||||
|
||||
/* A function definition. Contains information needed to instantiate closures. */
|
||||
@ -731,8 +733,7 @@ struct JanetParser {
|
||||
size_t statecap;
|
||||
size_t bufcount;
|
||||
size_t bufcap;
|
||||
size_t line;
|
||||
size_t col;
|
||||
size_t offset;
|
||||
int lookback;
|
||||
};
|
||||
|
||||
@ -937,6 +938,13 @@ JANET_API Janet janet_scan_number(const uint8_t *src, int32_t len);
|
||||
JANET_API int32_t janet_scan_integer(const uint8_t *str, int32_t len, int *err);
|
||||
JANET_API double janet_scan_real(const uint8_t *str, int32_t len, int *err);
|
||||
|
||||
/* Debugging */
|
||||
JANET_API int janet_debug_break(JanetFuncDef *def, int32_t pc);
|
||||
JANET_API int janet_debug_unbreak(JanetFuncDef *def, int32_t pc);
|
||||
JANET_API int janet_debug_find(
|
||||
JanetFuncDef **def_out, int32_t *pc_out,
|
||||
const uint8_t *source, int32_t offset);
|
||||
|
||||
/* Array functions */
|
||||
JANET_API JanetArray *janet_array(int32_t capacity);
|
||||
JANET_API JanetArray *janet_array_n(const Janet *elements, int32_t n);
|
||||
@ -967,8 +975,8 @@ JANET_API int janet_buffer_push_u64(JanetBuffer *buffer, uint64_t x);
|
||||
#define janet_tuple_raw(t) ((int32_t *)(t) - 4)
|
||||
#define janet_tuple_length(t) (janet_tuple_raw(t)[0])
|
||||
#define janet_tuple_hash(t) ((janet_tuple_raw(t)[1]))
|
||||
#define janet_tuple_sm_line(t) ((janet_tuple_raw(t)[2]))
|
||||
#define janet_tuple_sm_col(t) ((janet_tuple_raw(t)[3]))
|
||||
#define janet_tuple_sm_start(t) ((janet_tuple_raw(t)[2]))
|
||||
#define janet_tuple_sm_end(t) ((janet_tuple_raw(t)[3]))
|
||||
JANET_API Janet *janet_tuple_begin(int32_t length);
|
||||
JANET_API const Janet *janet_tuple_end(Janet *tuple);
|
||||
JANET_API const Janet *janet_tuple_n(const Janet *values, int32_t n);
|
||||
|
@ -24,12 +24,12 @@
|
||||
(os/exit 0)
|
||||
1)
|
||||
"v" (fn [&] (print janet/version "-" janet/build) (os/exit 0) 1)
|
||||
"s" (fn [&] (:= *raw-stdin* true) (:= *should-repl* true) 1)
|
||||
"r" (fn [&] (:= *should-repl* true) 1)
|
||||
"p" (fn [&] (:= *exit-on-error* false) 1)
|
||||
"-" (fn [&] (:= *handleopts* 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)
|
||||
"-" (fn [&] (set *handleopts* false) 1)
|
||||
"e" (fn [i &]
|
||||
(:= *no-file* false)
|
||||
(set *no-file* false)
|
||||
(eval (get process/args (+ i 1)))
|
||||
2)})
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
(if (and *handleopts* (= "-" (string/slice arg 0 1)))
|
||||
(+= i (dohandler (string/slice arg 1 2) i))
|
||||
(do
|
||||
(:= *no-file* false)
|
||||
(set *no-file* false)
|
||||
(import* _env arg :prefix "" :exit *exit-on-error*)
|
||||
(++ i))))
|
||||
|
||||
@ -55,6 +55,6 @@
|
||||
(do
|
||||
(print (string "Janet " janet/version "-" janet/build " Copyright (C) 2017-2018 Calvin Rose"))
|
||||
(repl (fn [buf p]
|
||||
(def [line] (parser/where p))
|
||||
(def prompt (string "janet:" line ":" (parser/state p) "> "))
|
||||
(def offset (parser/where p))
|
||||
(def prompt (string "janet:" offset ":" (parser/state p) "> "))
|
||||
(getline prompt buf)))))))
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
(fiber/new (fn webrepl []
|
||||
(repl (fn get-line [buf p]
|
||||
(def [line] (parser/where p))
|
||||
(def prompt (string "janet:" line ":" (parser/state p) "> "))
|
||||
(def offset (parser/where p))
|
||||
(def prompt (string "janet:" offset ":" (parser/state p) "> "))
|
||||
(repl-yield prompt buf)
|
||||
(yield)
|
||||
buf))))
|
||||
|
@ -13,7 +13,7 @@
|
||||
x)
|
||||
|
||||
(defn start-suite [x]
|
||||
(:= suite-num x)
|
||||
(set suite-num x)
|
||||
(print "\nRunning test suite " x " tests...\n"))
|
||||
|
||||
(defn end-suite []
|
||||
|
@ -24,8 +24,8 @@
|
||||
(assert (= 10 (+ 1 2 3 4)) "addition")
|
||||
(assert (= -8 (- 1 2 3 4)) "subtraction")
|
||||
(assert (= 24 (* 1 2 3 4)) "multiplication")
|
||||
(assert (= 4 (<< 1 2)) "left shift")
|
||||
(assert (= 1 (>> 4 2)) "right shift")
|
||||
(assert (= 4 (blshift 1 2)) "left shift")
|
||||
(assert (= 1 (brshift 4 2)) "right shift")
|
||||
(assert (< 1 2 3 4 5 6) "less than integers")
|
||||
(assert (< 1.0 2.0 3.0 4.0 5.0 6.0) "less than reals")
|
||||
(assert (> 6 5 4 3 2 1) "greater than integers")
|
||||
@ -60,10 +60,10 @@
|
||||
(assert (not false) "false literal")
|
||||
(assert true "true literal")
|
||||
(assert (not nil) "nil literal")
|
||||
(assert (= 7 (| 3 4)) "bit or")
|
||||
(assert (= 0 (& 3 4)) "bit and")
|
||||
(assert (= 0xFF (^ 0x0F 0xF0)) "bit xor")
|
||||
(assert (= 0xF0 (^ 0xFF 0x0F)) "bit xor 2")
|
||||
(assert (= 7 (bor 3 4)) "bit or")
|
||||
(assert (= 0 (band 3 4)) "bit and")
|
||||
(assert (= 0xFF (bxor 0x0F 0xF0)) "bit xor")
|
||||
(assert (= 0xF0 (bxor 0xFF 0x0F)) "bit xor 2")
|
||||
|
||||
# Set global variables to prevent some possible compiler optimizations that defeat point of the test
|
||||
(var zero 0)
|
||||
@ -80,7 +80,7 @@
|
||||
|
||||
# Mcarthy's 91 function
|
||||
(var f91 nil)
|
||||
(:= f91 (fn [n] (if (> n 100) (- n 10) (f91 (f91 (+ n 11))))))
|
||||
(set f91 (fn [n] (if (> n 100) (- n 10) (f91 (f91 (+ n 11))))))
|
||||
(assert (= 91 (f91 10)) "f91(10) = 91")
|
||||
(assert (= 91 (f91 11)) "f91(11) = 91")
|
||||
(assert (= 91 (f91 20)) "f91(20) = 91")
|
||||
@ -92,7 +92,7 @@
|
||||
(assert (= 94 (f91 104)) "f91(104) = 94")
|
||||
|
||||
# Fibonacci
|
||||
(def fib (do (var fib nil) (:= fib (fn [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))))
|
||||
(def fib (do (var fib nil) (set fib (fn [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))))
|
||||
(def fib2 (fn fib2 [n] (if (< n 2) n (+ (fib2 (- n 1)) (fib2 (- n 2))))))
|
||||
|
||||
(assert (= (fib 0) (fib2 0) 0) "fib(0)")
|
||||
@ -127,15 +127,15 @@
|
||||
(var accum 1)
|
||||
(var count 0)
|
||||
(while (< count 16)
|
||||
(:= accum (<< accum 1))
|
||||
(:= count (+ 1 count)))
|
||||
(set accum (blshift accum 1))
|
||||
(set count (+ 1 count)))
|
||||
(assert (= accum 65536) "loop in closure")))
|
||||
|
||||
(var accum 1)
|
||||
(var count 0)
|
||||
(while (< count 16)
|
||||
(:= accum (<< accum 1))
|
||||
(:= count (+ 1 count)))
|
||||
(set accum (blshift accum 1))
|
||||
(set count (+ 1 count)))
|
||||
(assert (= accum 65536) "loop globally")
|
||||
|
||||
(assert (= (struct 1 2 3 4 5 6 7 8) (struct 7 8 5 6 3 4 1 2)) "struct order does not matter 1")
|
||||
@ -228,18 +228,18 @@
|
||||
(def xi (get xs i))
|
||||
(def yj (get ys j))
|
||||
(if (< xi yj)
|
||||
(do (array/push ret xi) (:= i (+ i 1)))
|
||||
(do (array/push ret yj) (:= j (+ j 1)))))
|
||||
(do (array/push ret xi) (set i (+ i 1)))
|
||||
(do (array/push ret yj) (set j (+ j 1)))))
|
||||
# Push rest of xs
|
||||
(while (< i xlen)
|
||||
(def xi (get xs i))
|
||||
(array/push ret xi)
|
||||
(:= i (+ i 1)))
|
||||
(set i (+ i 1)))
|
||||
# Push rest of ys
|
||||
(while (< j ylen)
|
||||
(def yj (get ys j))
|
||||
(array/push ret yj)
|
||||
(:= j (+ j 1)))
|
||||
(set j (+ j 1)))
|
||||
ret)
|
||||
|
||||
(assert (apply <= (merge @[1 3 5] @[2 4 6])) "merge sort merge 1")
|
||||
@ -255,7 +255,7 @@
|
||||
(var count 0)
|
||||
(while (< count 128)
|
||||
(put syms (gensym) true)
|
||||
(:= count (+ 1 count)))
|
||||
(set count (+ 1 count)))
|
||||
(assert (= (length syms) 128) "many symbols")))
|
||||
|
||||
# Let
|
||||
|
@ -33,7 +33,7 @@
|
||||
|
||||
(defn myfun [x]
|
||||
(var a 10)
|
||||
(:= a (do
|
||||
(set a (do
|
||||
(def y x)
|
||||
(if x 8 9))))
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
(var good true)
|
||||
(loop [i :range [0 n]]
|
||||
(if (not (f))
|
||||
(:= good false)))
|
||||
(set good false)))
|
||||
(assert good e))
|
||||
|
||||
(assert-many (fn [] (>= 1 (math/random) 0)) 200 "(random) between 0 and 1")
|
||||
|
Loading…
Reference in New Issue
Block a user