diff --git a/.builds/epoll.yml b/.builds/linux.yml
similarity index 100%
rename from .builds/epoll.yml
rename to .builds/linux.yml
diff --git a/.builds/meson.yml b/.builds/meson.yml
deleted file mode 100644
index a3a6324f..00000000
--- a/.builds/meson.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-image: openbsd/latest
-sources:
-- https://git.sr.ht/~bakpakin/janet
-packages:
-- meson
-tasks:
-- build: |
- cd janet
- meson setup build --buildtype=release
- cd build
- ninja
- ninja test
- doas ninja install
- doas jpm --verbose install circlet
diff --git a/.builds/meson2.yml b/.builds/meson2.yml
deleted file mode 100644
index f9015ca5..00000000
--- a/.builds/meson2.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-image: openbsd/latest
-sources:
-- https://git.sr.ht/~bakpakin/janet
-packages:
-- meson
-tasks:
-- build: |
- cd janet
- meson setup build --buildtype=release
- cd build
- meson configure -Dprf=true
- ninja
- ninja test
- doas ninja install
- doas jpm --verbose install circlet
diff --git a/.builds/meson_min.yml b/.builds/meson_min.yml
deleted file mode 100644
index e0fa4adb..00000000
--- a/.builds/meson_min.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-image: openbsd/latest
-sources:
-- https://git.sr.ht/~bakpakin/janet
-packages:
-- meson
-tasks:
-- build: |
- cd janet
- meson setup build --buildtype=release
- cd build
- meson configure -Dsingle_threaded=true
- meson configure -Dnanbox=false
- meson configure -Ddynamic_modules=false
- meson configure -Ddocstrings=false
- meson configure -Dnet=false
- meson configure -Dsourcemaps=false
- meson configure -Dpeg=false
- meson configure -Dassembler=false
- meson configure -Dint_types=false
- meson configure -Dtyped_array=false
- meson configure -Dreduced_os=true
- ninja # will not pass tests but should build
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml
index 27ad30a1..f2fc3733 100644
--- a/.builds/openbsd.yml
+++ b/.builds/openbsd.yml
@@ -3,10 +3,31 @@ sources:
- https://git.sr.ht/~bakpakin/janet
packages:
- gmake
+- meson
tasks:
-- build: |
+- gmake: |
cd janet
gmake
gmake test
doas gmake install
gmake test-install
+- meson_min: |
+ cd janet
+ meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dtyped_array=false -Dreduced_os=true
+ cd build_meson_min
+ ninja
+- meson_prf: |
+ cd janet
+ meson setup build_meson_prf --buildtype=release -Dprf=true
+ cd build_meson_prf
+ ninja
+ ninja test
+- meson_default: |
+ cd janet
+ meson setup build_meson_default --buildtype=release
+ cd build_meson_default
+ ninja
+ ninja test
+ doas ninja install
+ doas jpm --verbose install circlet
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1e0db63..9c33e830 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,8 +2,24 @@
All notable changes to this project will be documented in this file.
## Unreleased - ???
+- Deprecate `file/popen` in favor of `os/spawn`.
+- Add `:all` keyword to `ev/read` and `net/read` to make them more like `file/read`. However, we
+ do not provide any `:line` option as that requires buffering.
+- Change repl behavior to make Ctrl-C raise SIGINT on posix. The old behavior for Ctrl-C,
+ to clear the current line buffer, has been moved to Ctrl-Q.
+- Importing modules that start with `/` is now the only way to import from project root.
+ Before, this would import from / on disk.
+- Change hash function for numbers.
+- Improve error handling of `dofile`.
+
+## 1.13.1 - 2020-12-13
+- Pretty printing a table with a prototype will look for `:_name` instead of `:name`
+ in the prototype table to tag the output.
+- `match` macro implementation changed to be tail recursive.
+- Adds a :preload loader which allows one to manually put things into `module/cache`.
+- Add `buffer/push` function.
- Backtick delimited strings and buffers are now reindented based on the column of the
- opening delimiter. WHitespace in columns to the left of the starting column is ignored unless
+ opening delimiter. Whitespace in columns to the left of the starting column is ignored unless
there are non-space/non-newline characters in that region, in which case the old behavior is preserved.
- Argument to `(error)` combinator in PEGs is now optional.
- Add `(line)` and `(column)` combinators to PEGs to capture source line and column.
@@ -12,16 +28,17 @@ All notable changes to this project will be documented in this file.
- During installation and release, merge janetconf.h into janet.h for easier install.
- Add `upscope` special form.
- `os/execute` and `os/spawn` can take streams for redirecting IO.
-- Add `;parser` and `:read` parameters to `run-context`.
+- Add `:parser` and `:read` parameters to `run-context`.
- Add `os/open` if ev is enabled.
- Add `os/pipe` if ev is enabled.
- Add `janet_thread_current(void)` to C API
- Add integer parsing forms to pegs. This makes parsing many binary protocols easier.
- Lots of updates to networking code - now can use epoll (or poll) on linux and IOCP on windows.
- Add `ev/` module. This exposes a fiber scheduler, queues, timeouts, and other functionality to users
- for single threaded cooperative scheduling and asynchornous IO.
+ for single threaded cooperative scheduling and asynchronous IO.
- Add `net/accept-loop` and `net/listen`. These functions break down `net/server` into it's essential parts
- and are more flexible. They also allow furter improvements to these utility functions.
+ and are more flexible. They also allow further improvements to these utility functions.
+- Various small bug fixes.
## 1.12.2 - 2020-09-20
- Add janet\_try and janet\_restore to C API.
diff --git a/Makefile b/Makefile
index b863b659..9b6ee905 100644
--- a/Makefile
+++ b/Makefile
@@ -157,7 +157,7 @@ build/janet.c: build/janet_boot src/boot/boot.janet
##### Amalgamation #####
########################
-SONAME=libjanet.so.1.12
+SONAME=libjanet.so.1.13
build/shell.c: src/mainclient/shell.c
cp $< $@
diff --git a/README.md b/README.md
index 3af472a5..735c2733 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,6 @@
[![Build Status](https://travis-ci.org/janet-lang/janet.svg?branch=master)](https://travis-ci.org/janet-lang/janet)
[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml?)
[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml?)
-[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/meson.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/meson.yml?)
-[![builds.sr.ht status](https://builds.sr.ht/~bakpakin/janet/commits/meson_min.yml.svg)](https://builds.sr.ht/~bakpakin/janet/commits/meson_min.yml?)
diff --git a/janet.1 b/janet.1
index 61c65b64..e25989e2 100644
--- a/janet.1
+++ b/janet.1
@@ -64,6 +64,10 @@ Move cursor to the beginning of input line.
.BR Ctrl\-B
Move cursor one character to the left.
+.TP 16
+.BR Ctrl\-D
+If on a newline, indicate end of stream and exit the repl.
+
.TP 16
.BR Ctrl\-E
Move cursor to the end of input line.
@@ -100,6 +104,10 @@ Delete one word before the cursor.
.BR Ctrl\-G
Show documentation for the current symbol under the cursor.
+.TP 16
+.BR Ctrl\-Q
+Clear the current command, including already typed lines.
+
.TP 16
.BR Alt\-B/Alt\-F
Move cursor backwards and forwards one word.
diff --git a/meson.build b/meson.build
index 8745bf02..1a6c679d 100644
--- a/meson.build
+++ b/meson.build
@@ -20,7 +20,7 @@
project('janet', 'c',
default_options : ['c_std=c99', 'b_lundef=false', 'default_library=both'],
- version : '1.13.0')
+ version : '1.13.1')
# Global settings
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
diff --git a/src/boot/boot.janet b/src/boot/boot.janet
index 3da90a8d..d8a564ec 100644
--- a/src/boot/boot.janet
+++ b/src/boot/boot.janet
@@ -180,8 +180,8 @@
(defmacro cond
`Evaluates conditions sequentially until the first true condition
is found, and then executes the corresponding body. If there are an
- odd number of forms, the last expression is executed if no forms
- are matched. If there are no matches, return nil.`
+ odd number of forms, and no forms are matched, the last expression
+ is executed. If there are no matches, return nil.`
[& pairs]
(defn aux [i]
(def restlen (- (length pairs) i))
@@ -494,13 +494,13 @@
(error (string "unexpected loop verb " verb)))))
(defmacro forv
- `Do a c style for loop for side effects. The iteration variable i
- can be mutated in the loop, unlike normal for. Returns nil.`
+ ``Do a C-style for-loop for side effects. The iteration variable `i`
+ can be mutated in the loop, unlike normal `for`. Returns nil.``
[i start stop & body]
(for-var-template i start stop 1 < + body))
(defmacro for
- "Do a c style for loop for side effects. Returns nil."
+ "Do a C-style for-loop for side effects. Returns nil."
[i start stop & body]
(for-template i start stop 1 < + body))
@@ -562,7 +562,7 @@
two-element tuple with a start and (exclusive) end value, and an optional
(positive!) step size.
- * :down-to -- same :as down, but the range is inclusive [start, end].
+ * :down-to -- same as :down, but the range is inclusive [start, end].
* :keys -- iterate over the keys in a data structure.
@@ -692,7 +692,7 @@
;(tuple/slice functions 4 -1)))))
(defn identity
- "A function that returns its first argument."
+ "A function that returns its argument."
[x]
x)
@@ -731,11 +731,11 @@
## Polymorphic comparisons
(defn compare
- `Polymorphic compare. Returns -1, 0, 1 for x < y, x = y, x > y respectively.
+ ``Polymorphic compare. Returns -1, 0, 1 for x < y, x = y, x > y respectively.
Differs from the primitive comparators in that it first checks to
- see whether either x or y implement a 'compare' method which can
- compare x and y. If so it uses that compare method. If not, it
- delegates to the primitive comparators.`
+ see whether either x or y implement a `compare` method which can
+ compare x and y. If so, it uses that method. If not, it
+ delegates to the primitive comparators.``
[x y]
(or
(when-let [f (get x :compare)] (f x y))
@@ -753,27 +753,27 @@
r)
(defn compare=
- "Equivalent of '=' but using compare function instead of primitive comparator"
+ ``Equivalent of `=` but using polymorphic `compare` instead of primitive comparator.``
[& xs]
(compare-reduce = xs))
(defn compare<
- "Equivalent of '<' but using compare function instead of primitive comparator"
+ ``Equivalent of `<` but using polymorphic `compare` instead of primitive comparator.``
[& xs]
(compare-reduce < xs))
(defn compare<=
- "Equivalent of '<=' but using compare function instead of primitive comparator"
+ ``Equivalent of `<=` but using polymorphic `compare` instead of primitive comparator.``
[& xs]
(compare-reduce <= xs))
(defn compare>
- "Equivalent of '>' but using compare function instead of primitive comparator"
+ ``Equivalent of `>` but using polymorphic `compare` instead of primitive comparator.``
[& xs]
(compare-reduce > xs))
(defn compare>=
- "Equivalent of '>=' but using compare function instead of primitive comparator"
+ ``Equivalent of `>=` but using polymorphic `compare` instead of primitive comparator.``
[& xs]
(compare-reduce >= xs))
@@ -790,36 +790,50 @@
###
###
-(defn- sort-part
- [a lo hi by]
- (def pivot (in a hi))
- (var i lo)
- (forv j lo hi
- (def aj (in a j))
- (when (by aj pivot)
- (def ai (in a i))
- (set (a i) aj)
- (set (a j) ai)
- (++ i)))
- (set (a hi) (in a i))
- (set (a i) pivot)
- i)
+(defn- median-of-three [a b c]
+ (if (not= (> a b) (> a c))
+ a
+ (if (not= (> b a) (> b c)) b c)))
-(defn- sort-help
- [a lo hi by]
- (when (> hi lo)
- (def piv (sort-part a lo hi by))
- (sort-help a lo (- piv 1) by)
- (sort-help a (+ piv 1) hi by))
+(defn- insertion-sort [a lo hi by]
+ (for i (+ lo 1) (+ hi 1)
+ (def temp (in a i))
+ (var j (- i 1))
+ (while (and (>= j lo) (by temp (in a j)))
+ (set (a (+ j 1)) (in a j))
+ (-- j))
+
+ (set (a (+ j 1)) temp))
a)
(defn sort
"Sort an array in-place. Uses quick-sort and is not a stable sort."
[a &opt by]
- (sort-help a 0 (- (length a) 1) (or by <)))
+ (default by <)
+ (def stack @[[0 (- (length a) 1)]])
+ (while (not (empty? stack))
+ (def [lo hi] (array/pop stack))
+ (when (< lo hi)
+ (when (< (- hi lo) 32) (insertion-sort a lo hi by) (break))
+ (def pivot (median-of-three (in a hi) (in a lo) (in a (math/floor (/ (+ lo hi) 2)))))
+ (var left lo)
+ (var right hi)
+ (while true
+ (while (by (in a left) pivot) (++ left))
+ (while (by pivot (in a right)) (-- right))
+ (when (<= left right)
+ (def tmp (in a left))
+ (set (a left) (in a right))
+ (set (a right) tmp)
+ (++ left)
+ (-- right))
+ (if (>= left right) (break)))
+ (array/push stack [lo right])
+ (array/push stack [left hi])))
+ a)
-(undef sort-part)
-(undef sort-help)
+(undef median-of-three)
+(undef insertion-sort)
(defn sort-by
`Returns a new sorted array that compares elements by invoking
@@ -945,8 +959,10 @@
counter)
(defn keep
- `Given a predicate, take only elements from an array or tuple for
- which (pred element) is truthy. Returns a new array of truthy predicate results.`
+ ``Given a predicate `pred`, return a new array containing the truthy results
+ of applying `pred` to each element in the indexed collection `ind`. This is
+ different from `filter` which returns an array of the original elements where
+ the predicate is truthy.``
[pred ind]
(def res @[])
(each item ind
@@ -1557,109 +1573,173 @@
###
###
-(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 evaluate once, even if body contains multiple
- copies of binding. In body, use binding instead of form.`
- [binding form & body]
- (def $result (gensym))
- (def $form (gensym))
- ~(do
- (def ,$form ,form)
- (def ,binding (if (idempotent? ,$form) ,$form (gensym)))
- (def ,$result (do ,;body))
- (if (= ,$form ,binding)
- ,$result
- (tuple 'do (tuple 'def ,binding ,$form) ,$result))))
-
-
-# Sentinel value for mismatches
-(def- sentinel ~',(gensym))
-
-(defn- match-1
- [pattern expr onmatch seen]
- (cond
-
- (= '_ pattern)
- (onmatch)
-
- (symbol? pattern)
- (if (in seen pattern)
- ~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)
- (do
- (put seen pattern true)
- ~(do (def ,pattern ,expr) ,(onmatch))))
-
- (and (tuple? pattern) (= :parens (tuple/type pattern)))
- (if (= (get pattern 0) '@)
- # Unification with external values
- ~(if (= ,(get pattern 1) ,expr) ,(onmatch) ,sentinel)
- (match-1
- (in pattern 0) expr
- (fn []
- ~(if (and ,;(tuple/slice pattern 1)) ,(onmatch) ,sentinel)) seen))
-
- (indexed? pattern)
- (do
- (def len (length pattern))
- (var i -1)
- (with-idemp
- $arr expr
- ~(if (,indexed? ,$arr)
- (if (< (,length ,$arr) ,len)
- ,sentinel
- ,((fn aux []
- (++ i)
- (if (= i len)
- (onmatch)
- (match-1 (in pattern i) (tuple in $arr i) aux seen)))))
- ,sentinel)))
-
- (dictionary? pattern)
- (do
- (var key nil)
- (with-idemp
- $dict expr
- ~(if (,dictionary? ,$dict)
- ,((fn aux []
- (set key (next pattern key))
- (def $val (gensym))
- (if (= key nil)
- (onmatch)
- ~(do (def ,$val (,get ,$dict ,key))
- ,(match-1 [(in pattern key) [not= nil $val]] $val aux seen)))))
- ,sentinel)))
-
- :else ~(if (= ,pattern ,expr) ,(onmatch) ,sentinel)))
-
(defmacro match
- `Pattern matching. Match an expression x against
- any number of cases. Each case is a pattern to match against, followed
- by an expression to evaluate to if that case is matched. A pattern that is
- a symbol will match anything, binding x's value to that symbol. An array
- will match only if all of it's elements match the corresponding elements in
- x. A table or struct will match if all values match with the corresponding
- values in x. A tuple pattern will match if it's first element matches, and the following
- elements are treated as predicates and are true. The last special case is
- the '_ symbol, which is a wildcard that will match any value without creating a binding.
- Any other value pattern will only match if it is equal to x.`
- [x & cases]
- (with-idemp $x x
- (def len (length cases))
- (def len-1 (dec len))
- ((fn aux [i]
- (cond
- (= i len-1) (in cases i)
- (< i len-1) (with-syms [$res]
- ~(if (= ,sentinel (def ,$res ,(match-1 (in cases i) $x (fn [] (in cases (inc i))) @{})))
- ,(aux (+ 2 i))
- ,$res)))) 0)))
+ ```
+ Pattern matching. Match an expression `x` against any number of cases.
+ Each case is a pattern to match against, followed by an expression to
+ evaluate to if that case is matched. Legal patterns are:
-(undef sentinel)
-(undef match-1)
-(undef with-idemp)
+ * symbol -- a pattern that is a symbol will match anything, binding `x`'s
+ value to that symbol.
+
+ * array -- an array will match only if all of its elements match the
+ corresponding elements in `x`.
+
+ * table or struct -- a table or struct will match if all values match with
+ the corresponding values in `x`.
+
+ * tuple -- a tuple pattern will match if its first element matches, and the
+ following elements are treated as predicates and are true.
+
+ * `_` symbol -- the last special case is the `_` symbol, which is a wildcard
+ that will match any value without creating a binding.
+
+ Any other value pattern will only match if it is equal to `x`.
+ ```
+ [x & cases]
+
+ # Partition body into sections.
+ (def oddlen (odd? (length cases)))
+ (def else (if oddlen (last cases)))
+ (def patterns (partition 2 (if oddlen (slice cases 0 -2) cases)))
+
+ # Keep an array for accumulating the compilation output
+ (def x-sym (if (idempotent? x) x (gensym)))
+ (def accum @[])
+ (if (not= x x-sym) (array/push accum ['def x-sym x]))
+
+ # Table of gensyms
+ (def symbols @{[nil nil] x-sym})
+ (def length-symbols @{})
+
+ (defn emit [x] (array/push accum x))
+ (defn emit-branch [condition result] (array/push accum :branch condition result))
+
+ (defn get-sym
+ [parent-sym key]
+ (def symbol-key [parent-sym key])
+ (or (get symbols symbol-key)
+ (let [s (gensym)]
+ (put symbols symbol-key s)
+ (emit ['def s [get parent-sym key]])
+ s)))
+
+ (defn get-length-sym
+ [parent-sym]
+ (or (get length-symbols parent-sym)
+ (let [s (gensym)]
+ (put length-symbols parent-sym s)
+ (emit ['def s ['if [indexed? parent-sym] [length parent-sym]]])
+ s)))
+
+ (defn visit-pattern-1
+ [b2g parent-sym key pattern]
+ (if (= pattern '_) (break))
+ (def s (get-sym parent-sym key))
+ (def t (type pattern))
+ (def isarr (or (= t :array) (and (= t :tuple) (= (tuple/type pattern) :brackets))))
+ (cond
+
+ # match local binding
+ (= t :symbol)
+ (if-let [x (in b2g pattern)]
+ (array/push x s)
+ (put b2g pattern @[s]))
+
+ # match data structure template
+ (or isarr (= t :struct) (= t :table))
+ (do
+ (when isarr (get-length-sym s))
+ (eachp [i sub-pattern] pattern
+ (visit-pattern-1 b2g s i sub-pattern)))
+
+ # match global unification
+ (and (= t :tuple) (= 2 (length pattern)) (= '@ (pattern 0)))
+ (break)
+
+ # match predicated binding
+ (and (= t :tuple) (>= (length pattern) 2))
+ (do
+ (visit-pattern-1 b2g parent-sym key (pattern 0)))))
+
+ (defn visit-pattern-2
+ [anda gun preds parent-sym key pattern]
+ (if (= pattern '_) (break))
+ (def s (get-sym parent-sym key))
+ (def t (type pattern))
+ (def isarr (or (= t :array) (and (= t :tuple) (= (tuple/type pattern) :brackets))))
+ (when isarr
+ (array/push anda (get-length-sym s))
+ (array/push anda [<= (length pattern) (get-length-sym s)]))
+ (cond
+
+ # match data structure template
+ (or isarr (= t :struct) (= t :table))
+ (eachp [i sub-pattern] pattern
+ (when (not isarr)
+ (array/push anda [not= nil (get-sym s i)]))
+ (visit-pattern-2 anda gun preds s i sub-pattern))
+
+ # match local binding
+ (= t :symbol) (break)
+
+ # match global unification
+ (and (= t :tuple) (= 2 (length pattern)) (= '@ (pattern 0)))
+ (if-let [x (in gun (pattern 1))]
+ (array/push x s)
+ (put gun (pattern 1) @[s]))
+
+ # match predicated binding
+ (and (= t :tuple) (>= (length pattern) 2))
+ (do
+ (array/push preds ;(slice pattern 1))
+ (visit-pattern-2 anda gun preds parent-sym key (pattern 0)))
+
+ # match literal
+ (array/push anda ['= s pattern])))
+
+ # Compile the patterns
+ (each [pattern expression] patterns
+ (def b2g @{})
+ (def gun @{})
+ (def preds @[])
+ (visit-pattern-1 b2g nil nil pattern)
+ (def anda @['and])
+ (visit-pattern-2 anda gun preds nil nil pattern)
+ # Local unification
+ (def unify @[])
+ (each syms b2g
+ (when (< 1 (length syms))
+ (array/push unify [= ;syms])))
+ # Global unification
+ (eachp [binding syms] gun
+ (array/push unify [= binding ;syms]))
+ (sort unify)
+ (array/concat anda unify)
+ # Final binding
+ (def defs (seq [[k v] :in (sort (pairs b2g))] ['def k (first v)]))
+ # Predicates
+ (unless (empty? preds)
+ (def pred-join ~(do ,;defs (and ,;preds)))
+ (array/push anda pred-join))
+ (emit-branch (tuple/slice anda) ['do ;defs expression]))
+
+ # Expand branches
+ (def stack @[else])
+ (each el (reverse accum)
+ (if (= :branch el)
+ (let [condition (array/pop stack)
+ truthy (array/pop stack)
+ if-form ~(if ,condition ,truthy
+ ,(case (length stack)
+ 0 nil
+ 1 (stack 0)
+ ~(do ,;(reverse stack))))]
+ (array/remove stack 0 (length stack))
+ (array/push stack if-form))
+ (array/push stack el)))
+
+ ~(do ,;(reverse stack)))
###
###
@@ -2061,7 +2141,8 @@
'quasiquote expandqq
'var expanddef
'while expandall
- 'break expandall})
+ 'break expandall
+ 'upscope expandall})
(defn dotup [t]
(def h (in t 0))
@@ -2359,9 +2440,6 @@
(default where "")
(default guard :ydt)
- # Are we done yet?
- (var going true)
-
# Evaluate 1 source form in a protected manner
(defn eval1 [source]
(def source (if expand (expand source) source))
@@ -2385,7 +2463,7 @@
(fiber/setenv f env)
(while (fiber/can-resume? f)
(def res (resume f resumeval))
- (when good (when going (set resumeval (onstatus f res))))))
+ (when good (set resumeval (onstatus f res)))))
# Reader version
(when read
@@ -2410,11 +2488,11 @@
# Loop
(def buf @"")
- (while going
+ (var parser-not-done true)
+ (while parser-not-done
(if (env :exit) (break))
(buffer/clear buf)
- (if (= (chunks buf p)
- :cancel)
+ (if (= (chunks buf p) :cancel)
(do
# A :cancel chunk represents a cancelled form in the REPL, so reset.
(:flush p)
@@ -2425,19 +2503,23 @@
(def len (length buf))
(when (= len 0)
(:eof p)
- (set going false))
+ (set parser-not-done false))
(while (> len pindex)
(+= pindex (p-consume p buf pindex))
(while (p-has-more p)
- (eval1 (p-produce p)))
+ (eval1 (p-produce p))
+ (if (env :exit) (break)))
(when (= (p-status p) :error)
- (parse-err p where))))))
+ (parse-err p where)
+ (if (env :exit) (break)))))))
# Check final parser state
- (while (p-has-more p)
- (eval1 (p-produce p)))
- (when (= (p-status p) :error)
- (parse-err p where))
+ (unless (env :exit)
+ (while (p-has-more p)
+ (eval1 (p-produce p))
+ (if (env :exit) (break)))
+ (when (= (p-status p) :error)
+ (parse-err p where)))
(in env :exit-value env))
@@ -2497,16 +2579,16 @@
(error (parser/error p))
(error "no value")))))
-(def make-image-dict
- `A table used in combination with marshal to marshal code (images), such that
- (make-image x) is the same as (marshal x make-image-dict).`
- @{})
-
(def load-image-dict
`A table used in combination with unmarshal to unmarshal byte sequences created
by make-image, such that (load-image bytes) is the same as (unmarshal bytes load-image-dict).`
@{})
+(def make-image-dict
+ `A table used in combination with marshal to marshal code (images), such that
+ (make-image x) is the same as (marshal x make-image-dict).`
+ @{})
+
(defmacro comptime
"Evals x at compile time and returns the result. Similar to a top level unquote."
[x]
@@ -2536,8 +2618,9 @@
[image]
(unmarshal image load-image-dict))
-(defn- check-. [x] (if (string/has-prefix? "." x) x))
-(defn- not-check-. [x] (unless (string/has-prefix? "." x) x))
+(defn- check-relative [x] (if (string/has-prefix? "." x) x))
+(defn- check-is-dep [x] (unless (or (string/has-prefix? "/" x) (string/has-prefix? "." x)) x))
+(defn- check-project-relative [x] (if (string/has-prefix? "/" x) x))
(def module/paths
```
@@ -2557,6 +2640,10 @@
(setdyn :syspath (boot/opts "JANET_PATH"))
(setdyn :headerpath (boot/opts "JANET_HEADERPATH"))
+(def module/cache
+ "Table mapping loaded module identifiers to their environments."
+ @{})
+
(defn module/add-paths
```
Add paths to module/paths for a given loader such that
@@ -2569,18 +2656,19 @@
(defn- find-prefix
[pre]
(or (find-index |(and (string? ($ 0)) (string/has-prefix? pre ($ 0))) module/paths) 0))
- (def all-index (find-prefix ":all:"))
- (array/insert module/paths all-index [(string ":all:" ext) loader not-check-.])
+ (def all-index (find-prefix ".:all:"))
+ (array/insert module/paths all-index [(string ".:all:" ext) loader check-project-relative])
(def sys-index (find-prefix ":sys:"))
- (array/insert module/paths sys-index [(string ":sys:/:all:" ext) loader not-check-.])
+ (array/insert module/paths sys-index [(string ":sys:/:all:" ext) loader check-is-dep])
(def curall-index (find-prefix ":cur:/:all:"))
- (array/insert module/paths curall-index [(string ":cur:/:all:" ext) loader check-.])
+ (array/insert module/paths curall-index [(string ":cur:/:all:" ext) loader check-relative])
module/paths)
(module/add-paths ":native:" :native)
(module/add-paths "/init.janet" :source)
(module/add-paths ".janet" :source)
(module/add-paths ".jimage" :image)
+(array/insert module/paths 0 [(fn is-cached [path] (if (in module/cache path) path)) :preload])
# Version of fexists that works even with a reduced OS
(defn fexists
@@ -2632,12 +2720,9 @@
(undef fexists)
(undef mod-filter)
-(undef check-.)
-(undef not-check-.)
-
-(def module/cache
- "Table mapping loaded module identifiers to their environments."
- @{})
+(undef check-relative)
+(undef check-project-relative)
+(undef check-is-dep)
(def module/loading
`Table mapping currently loading modules to true. Used to prevent
@@ -2665,15 +2750,25 @@
(def spath (string path))
(put env :current-file (or src (if-not path-is-file spath)))
(put env :source (or src (if-not path-is-file spath path)))
+ (var exit-error nil)
+ (var exit-fiber nil)
(defn chunks [buf _] (file/read f 2048 buf))
(defn bp [&opt x y]
- (def ret (bad-parse x y))
- (if exit (os/exit 1))
- ret)
+ (when exit
+ (bad-parse x y)
+ (os/exit 1))
+ (put env :exit true)
+ (def [line col] (:where x))
+ (def pe (string (:error x) " in " y " around line " line ", column " col))
+ (set exit-error pe))
(defn bc [&opt x y z]
- (def ret (bad-compile x y z))
- (if exit (os/exit 1))
- ret)
+ (when exit
+ (bad-compile x y z)
+ (os/exit 1))
+ (put env :exit true)
+ (def ce (string x " while compiling " z))
+ (set exit-error ce)
+ (set exit-fiber y))
(unless f
(error (string "could not find file " path)))
(def nenv
@@ -2683,27 +2778,40 @@
:on-compile-error bc
:on-status (fn [f x]
(when (not= (fiber/status f) :dead)
- (debug/stacktrace f x)
- (if exit (os/exit 1) (eflush))))
+ (when exit
+ (debug/stacktrace f x)
+ (eflush)
+ (os/exit 1))
+ (put env :exit true)
+ (set exit-error x)
+ (set exit-fiber f)))
:evaluator evaluator
:expander expander
:read read
:parser parser
:source (or src (if path-is-file "" spath))}))
(if-not path-is-file (file/close f))
+ (when exit-error
+ (if exit-fiber
+ (propagate exit-error exit-fiber)
+ (error exit-error)))
nenv)
(def module/loaders
`A table of loading method names to loading functions.
This table lets require and import load many different kinds
of files as modules.`
- @{:native (fn [path &] (native path (make-env)))
- :source (fn [path args]
+ @{:native (fn native-loader [path &] (native path (make-env)))
+ :source (fn source-loader [path args]
(put module/loading path true)
- (def newenv (dofile path ;args))
- (put module/loading path nil)
- newenv)
- :image (fn [path &] (load-image (slurp path)))})
+ (defer (put module/loading path nil)
+ (dofile path ;args)))
+ :preload (fn preload-loader [path & args]
+ (when-let [m (in module/cache path)]
+ (if (function? m)
+ (set (module/cache path) (m path ;args))
+ m)))
+ :image (fn image-loader [path &] (load-image (slurp path)))})
(defn require-1
[path args kargs]
@@ -3019,7 +3127,16 @@
(defmacro ev/spawn
"Run some code in a new fiber. This is shorthand for (ev/call (fn [] ;body))."
[& body]
- ~(,ev/call (fn [] ,;body))))
+ ~(,ev/call (fn [] ,;body)))
+
+ (defmacro ev/with-deadline
+ `Run a body of code with a deadline, such that if the code does not complete before
+ the deadline is up, it will be canceled.`
+ [deadline & body]
+ (with-syms [f]
+ ~(let [,f (coro ,;body)]
+ (,ev/deadline ,deadline nil ,f)
+ (,resume ,f)))))
(compwhen (dyn 'net/listen)
(defn net/server
@@ -3214,8 +3331,7 @@
(put load-dict 'boot/args nil)
(each [k v] (pairs load-dict)
(if (number? v) (put load-dict k nil)))
- (merge-into load-image-dict load-dict)
- (merge-into make-image-dict (invert load-dict)))
+ (merge-into load-image-dict load-dict))
###
###
diff --git a/src/conf/janetconf.h b/src/conf/janetconf.h
index b8875f7b..ef4efb88 100644
--- a/src/conf/janetconf.h
+++ b/src/conf/janetconf.h
@@ -5,9 +5,9 @@
#define JANET_VERSION_MAJOR 1
#define JANET_VERSION_MINOR 13
-#define JANET_VERSION_PATCH 0
+#define JANET_VERSION_PATCH 2
#define JANET_VERSION_EXTRA "-dev"
-#define JANET_VERSION "1.13.0-dev"
+#define JANET_VERSION "1.13.2-dev"
/* #define JANET_BUILD "local" */
diff --git a/src/core/buffer.c b/src/core/buffer.c
index 6e040c00..ae490ddb 100644
--- a/src/core/buffer.c
+++ b/src/core/buffer.c
@@ -31,12 +31,11 @@
/* Initialize a buffer */
JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) {
uint8_t *data = NULL;
- if (capacity > 0) {
- janet_gcpressure(capacity);
- data = malloc(sizeof(uint8_t) * (size_t) capacity);
- if (NULL == data) {
- JANET_OUT_OF_MEMORY;
- }
+ if (capacity < 4) capacity = 4;
+ janet_gcpressure(capacity);
+ data = malloc(sizeof(uint8_t) * (size_t) capacity);
+ if (NULL == data) {
+ JANET_OUT_OF_MEMORY;
}
buffer->count = 0;
buffer->capacity = capacity;
@@ -200,19 +199,14 @@ static Janet cfun_buffer_fill(int32_t argc, Janet *argv) {
static Janet cfun_buffer_trim(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
- if (buffer->count) {
- if (buffer->count < buffer->capacity) {
- uint8_t *newData = realloc(buffer->data, buffer->count);
- if (NULL == newData) {
- JANET_OUT_OF_MEMORY;
- }
- buffer->data = newData;
- buffer->capacity = buffer->count;
+ if (buffer->count < buffer->capacity) {
+ int32_t newcap = buffer->count > 4 ? buffer->count : 4;
+ uint8_t *newData = realloc(buffer->data, newcap);
+ if (NULL == newData) {
+ JANET_OUT_OF_MEMORY;
}
- } else {
- buffer->capacity = 0;
- free(buffer->data);
- buffer->data = NULL;
+ buffer->data = newData;
+ buffer->capacity = newcap;
}
return argv[0];
}
@@ -256,6 +250,26 @@ static Janet cfun_buffer_chars(int32_t argc, Janet *argv) {
return argv[0];
}
+static Janet cfun_buffer_push(int32_t argc, Janet *argv) {
+ int32_t i;
+ janet_arity(argc, 1, -1);
+ JanetBuffer *buffer = janet_getbuffer(argv, 0);
+ for (i = 1; i < argc; i++) {
+ if (janet_checktype(argv[i], JANET_NUMBER)) {
+ janet_buffer_push_u8(buffer, (uint8_t)(janet_getinteger(argv, i) & 0xFF));
+ } else {
+ JanetByteView view = janet_getbytes(argv, i);
+ if (view.bytes == buffer->data) {
+ janet_buffer_ensure(buffer, buffer->count + view.len, 2);
+ view.bytes = buffer->data;
+ }
+ janet_buffer_push_bytes(buffer, view.bytes, view.len);
+ }
+ }
+ return argv[0];
+}
+
+
static Janet cfun_buffer_clear(int32_t argc, Janet *argv) {
janet_fixarity(argc, 1);
JanetBuffer *buffer = janet_getbuffer(argv, 0);
@@ -407,22 +421,32 @@ static const JanetReg buffer_cfuns[] = {
},
{
"buffer/push-byte", cfun_buffer_u8,
- JDOC("(buffer/push-byte buffer x)\n\n"
- "Append a byte to a buffer. Will expand the buffer as necessary. "
+ JDOC("(buffer/push-byte buffer & xs)\n\n"
+ "Append bytes to a buffer. Will expand the buffer as necessary. "
"Returns the modified buffer. Will throw an error if the buffer overflows.")
},
{
"buffer/push-word", cfun_buffer_word,
- JDOC("(buffer/push-word buffer x)\n\n"
- "Append a machine word to a buffer. The 4 bytes of the integer are appended "
- "in twos complement, little endian order, unsigned. Returns the modified buffer. Will "
+ JDOC("(buffer/push-word buffer & xs)\n\n"
+ "Append machine words to a buffer. The 4 bytes of the integer are appended "
+ "in twos complement, little endian order, unsigned for all x. Returns the modified buffer. Will "
"throw an error if the buffer overflows.")
},
{
"buffer/push-string", cfun_buffer_chars,
- JDOC("(buffer/push-string buffer str)\n\n"
- "Push a string onto the end of a buffer. Non string values will be converted "
- "to strings before being pushed. Returns the modified buffer. "
+ JDOC("(buffer/push-string buffer & xs)\n\n"
+ "Push byte sequences onto the end of a buffer. "
+ "Will accept any of strings, keywords, symbols, and buffers. "
+ "Returns the modified buffer. "
+ "Will throw an error if the buffer overflows.")
+ },
+ {
+ "buffer/push", cfun_buffer_push,
+ JDOC("(buffer/push buffer & xs)\n\n"
+ "Push both individual bytes and byte sequences to a buffer. For each x in xs, "
+ "push the byte if x is an integer, otherwise push the bytesequence to the buffer. "
+ "Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. "
+ "Returns the modified buffer. "
"Will throw an error if the buffer overflows.")
},
{
diff --git a/src/core/corelib.c b/src/core/corelib.c
index f1c6c0d2..5e35bc18 100644
--- a/src/core/corelib.c
+++ b/src/core/corelib.c
@@ -548,35 +548,35 @@ static const JanetReg corelib_cfuns[] = {
{
"describe", janet_core_describe,
JDOC("(describe x)\n\n"
- "Returns a string that is a human readable description of a value x.")
+ "Returns a string that is a human-readable description of a value x.")
},
{
"string", janet_core_string,
- JDOC("(string & parts)\n\n"
- "Creates a string by concatenating values together. Values are "
- "converted to bytes via describe if they are not byte sequences. "
+ JDOC("(string & xs)\n\n"
+ "Creates a string by concatenating the elements of `xs` together. If an "
+ "element is not a byte sequence, it is converted to bytes via `describe`. "
"Returns the new string.")
},
{
"symbol", janet_core_symbol,
JDOC("(symbol & xs)\n\n"
- "Creates a symbol by concatenating values together. Values are "
- "converted to bytes via describe if they are not byte sequences. Returns "
- "the new symbol.")
+ "Creates a symbol by concatenating the elements of `xs` together. If an "
+ "element is not a byte sequence, it is converted to bytes via `describe`. "
+ "Returns the new symbol.")
},
{
"keyword", janet_core_keyword,
JDOC("(keyword & xs)\n\n"
- "Creates a keyword by concatenating values together. Values are "
- "converted to bytes via describe if they are not byte sequences. Returns "
- "the new keyword.")
+ "Creates a keyword by concatenating the elements of `xs` together. If an "
+ "element is not a byte sequence, it is converted to bytes via `describe`. "
+ "Returns the new keyword.")
},
{
"buffer", janet_core_buffer,
JDOC("(buffer & xs)\n\n"
- "Creates a new buffer by concatenating values together. Values are "
- "converted to bytes via describe if they are not byte sequences. Returns "
- "the new buffer.")
+ "Creates a buffer by concatenating the elements of `xs` together. If an "
+ "element is not a byte sequence, it is converted to bytes via `describe`. "
+ "Returns the new buffer.")
},
{
"abstract?", janet_core_is_abstract,
@@ -1250,6 +1250,21 @@ JanetTable *janet_core_env(JanetTable *replacements) {
JanetTable *env = janet_unwrap_table(marsh_out);
janet_vm_core_env = env;
+ /* Invert image dict manually here. We can't do this in boot.janet as it
+ * breaks deterministic builds */
+ Janet lidv, midv;
+ lidv = midv = janet_wrap_nil();
+ janet_resolve(env, janet_csymbol("load-image-dict"), &lidv);
+ janet_resolve(env, janet_csymbol("make-image-dict"), &midv);
+ JanetTable *lid = janet_unwrap_table(lidv);
+ JanetTable *mid = janet_unwrap_table(midv);
+ for (int32_t i = 0; i < lid->capacity; i++) {
+ const JanetKV *kv = lid->data + i;
+ if (!janet_checktype(kv->key, JANET_NIL)) {
+ janet_table_put(mid, kv->value, kv->key);
+ }
+ }
+
return env;
}
diff --git a/src/core/ev.c b/src/core/ev.c
index 94736642..d6fe845a 100644
--- a/src/core/ev.c
+++ b/src/core/ev.c
@@ -132,6 +132,7 @@ typedef struct JanetTimeout JanetTimeout;
struct JanetTimeout {
JanetTimestamp when;
JanetFiber *fiber;
+ JanetFiber *curr_fiber;
uint32_t sched_id;
int is_error;
};
@@ -378,12 +379,56 @@ static int janet_stream_getter(void *p, Janet key, Janet *out) {
return 0;
}
+static void janet_stream_marshal(void *p, JanetMarshalContext *ctx) {
+ JanetStream *s = p;
+ if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) {
+ janet_panic("can only marshal stream with unsafe flag");
+ }
+ janet_marshal_abstract(ctx, p);
+ janet_marshal_int(ctx, (int32_t) s->flags);
+ janet_marshal_int64(ctx, (intptr_t) s->methods);
+#ifdef JANET_WINDOWS
+ /* TODO - ref counting to avoid situation where a handle is closed or GCed
+ * while in transit, and it's value gets reused. DuplicateHandle does not work
+ * for network sockets, and in general for winsock it is better to nipt duplicate
+ * unless there is a need to. */
+ janet_marshal_int64(ctx, (int64_t)(s->handle));
+#else
+ /* Marshal after dup becuse it is easier than maintaining our own ref counting. */
+ int duph = dup(s->handle);
+ if (duph < 0) janet_panicf("failed to duplicate stream handle: %V", janet_ev_lasterr());
+ janet_marshal_int(ctx, (int32_t)(duph));
+#endif
+}
+
+static void *janet_stream_unmarshal(JanetMarshalContext *ctx) {
+ if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) {
+ janet_panic("can only unmarshal stream with unsafe flag");
+ }
+ JanetStream *p = janet_unmarshal_abstract(ctx, sizeof(JanetStream));
+ /* Can't share listening state and such across threads */
+ p->_mask = 0;
+ p->state = NULL;
+ p->flags = (uint32_t) janet_unmarshal_int(ctx);
+ p->methods = (void *) janet_unmarshal_int64(ctx);
+#ifdef JANET_WINDOWS
+ p->handle = (JanetHandle) janet_unmarshal_int64(ctx);
+#else
+ p->handle = (JanetHandle) janet_unmarshal_int(ctx);
+#endif
+ return p;
+}
+
+
const JanetAbstractType janet_stream_type = {
"core/stream",
janet_stream_gc,
janet_stream_mark,
janet_stream_getter,
- JANET_ATEND_GET
+ NULL,
+ janet_stream_marshal,
+ janet_stream_unmarshal,
+ JANET_ATEND_UNMARSHAL
};
/* Register a fiber to resume with value */
@@ -435,6 +480,9 @@ void janet_ev_mark(void) {
/* Pending timeouts */
for (size_t i = 0; i < janet_vm_tq_count; i++) {
janet_mark(janet_wrap_fiber(janet_vm_tq[i].fiber));
+ if (janet_vm_tq[i].curr_fiber != NULL) {
+ janet_mark(janet_wrap_fiber(janet_vm_tq[i].curr_fiber));
+ }
}
/* Pending listeners */
@@ -488,6 +536,7 @@ void janet_addtimeout(double sec) {
JanetTimeout to;
to.when = ts_delta(ts_now(), sec);
to.fiber = fiber;
+ to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 1;
add_timeout(to);
@@ -776,11 +825,27 @@ void janet_loop1(void) {
JanetTimestamp now = ts_now();
while (peek_timeout(&to) && to.when <= now) {
pop_timeout(0);
- if (to.fiber->sched_id == to.sched_id) {
- if (to.is_error) {
- janet_cancel(to.fiber, janet_cstringv("timeout"));
- } else {
- janet_schedule(to.fiber, janet_wrap_nil());
+ if (to.curr_fiber != NULL) {
+ /* This is a deadline (for a fiber, not a function call) */
+ JanetFiberStatus s = janet_fiber_status(to.curr_fiber);
+ int isFinished = s == (JANET_STATUS_DEAD ||
+ s == JANET_STATUS_ERROR ||
+ s == JANET_STATUS_USER0 ||
+ s == JANET_STATUS_USER1 ||
+ s == JANET_STATUS_USER2 ||
+ s == JANET_STATUS_USER3 ||
+ s == JANET_STATUS_USER4);
+ if (!isFinished) {
+ janet_cancel(to.fiber, janet_cstringv("deadline expired"));
+ }
+ } else {
+ /* This is a timeout (for a function call, not a whole fiber) */
+ if (to.fiber->sched_id == to.sched_id) {
+ if (to.is_error) {
+ janet_cancel(to.fiber, janet_cstringv("timeout"));
+ } else {
+ janet_schedule(to.fiber, janet_wrap_nil());
+ }
}
}
}
@@ -798,7 +863,7 @@ void janet_loop1(void) {
memset(&to, 0, sizeof(to));
int has_timeout;
/* Drop timeouts that are no longer needed */
- while ((has_timeout = peek_timeout(&to)) && to.fiber->sched_id != to.sched_id) {
+ while ((has_timeout = peek_timeout(&to)) && (to.curr_fiber == NULL) && to.fiber->sched_id != to.sched_id) {
pop_timeout(0);
}
/* Run polling implementation only if pending timeouts or pending events */
@@ -1319,7 +1384,7 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
janet_buffer_push_bytes(state->buf, state->chunk_buf, s->bytes);
state->bytes_left -= s->bytes;
- if (state->bytes_left <= 0 || !state->is_chunk || s->bytes == 0) {
+ if (state->bytes_left == 0 || !state->is_chunk || s->bytes == 0) {
Janet resume_val;
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
@@ -1382,12 +1447,11 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
}
return JANET_ASYNC_STATUS_DONE;
}
- case JANET_ASYNC_EVENT_READ:
- /* Read in bytes */
- {
+ case JANET_ASYNC_EVENT_READ: {
JanetBuffer *buffer = state->buf;
int32_t bytes_left = state->bytes_left;
- janet_buffer_extra(buffer, bytes_left);
+ int32_t read_limit = bytes_left < 0 ? 4096 : bytes_left;
+ janet_buffer_extra(buffer, read_limit);
ssize_t nread;
#ifdef JANET_NET
char saddr[256];
@@ -1396,14 +1460,14 @@ JanetAsyncStatus ev_machine_read(JanetListenerState *s, JanetAsyncEvent event) {
do {
#ifdef JANET_NET
if (state->mode == JANET_ASYNC_READMODE_RECVFROM) {
- nread = recvfrom(s->stream->handle, buffer->data + buffer->count, bytes_left, state->flags,
+ nread = recvfrom(s->stream->handle, buffer->data + buffer->count, read_limit, state->flags,
(struct sockaddr *)&saddr, &socklen);
} else if (state->mode == JANET_ASYNC_READMODE_RECV) {
- nread = recv(s->stream->handle, buffer->data + buffer->count, bytes_left, state->flags);
+ nread = recv(s->stream->handle, buffer->data + buffer->count, read_limit, state->flags);
} else
#endif
{
- nread = read(s->stream->handle, buffer->data + buffer->count, bytes_left);
+ nread = read(s->stream->handle, buffer->data + buffer->count, read_limit);
}
} while (nread == -1 && errno == EINTR);
@@ -1784,6 +1848,7 @@ JANET_NO_RETURN void janet_sleep_await(double sec) {
to.fiber = janet_vm_root_fiber;
to.is_error = 0;
to.sched_id = to.fiber->sched_id;
+ to.curr_fiber = NULL;
add_timeout(to);
janet_await();
}
@@ -1794,6 +1859,21 @@ static Janet cfun_ev_sleep(int32_t argc, Janet *argv) {
janet_sleep_await(sec);
}
+static Janet cfun_ev_deadline(int32_t argc, Janet *argv) {
+ janet_arity(argc, 1, 3);
+ double sec = janet_getnumber(argv, 0);
+ JanetFiber *tocancel = janet_optfiber(argv, argc, 1, janet_vm_root_fiber);
+ JanetFiber *tocheck = janet_optfiber(argv, argc, 2, janet_vm_fiber);
+ JanetTimeout to;
+ to.when = ts_delta(ts_now(), sec);
+ to.fiber = tocancel;
+ to.curr_fiber = tocheck;
+ to.is_error = 0;
+ to.sched_id = to.fiber->sched_id;
+ add_timeout(to);
+ return janet_wrap_fiber(tocancel);
+}
+
static Janet cfun_ev_cancel(int32_t argc, Janet *argv) {
janet_fixarity(argc, 2);
JanetFiber *fiber = janet_getfiber(argv, 0);
@@ -1813,11 +1893,16 @@ Janet janet_cfun_stream_read(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE);
- int32_t n = janet_getnat(argv, 1);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, 10);
double to = janet_optnumber(argv, argc, 3, INFINITY);
- if (to != INFINITY) janet_addtimeout(to);
- janet_ev_read(stream, buffer, n);
+ if (janet_keyeq(argv[1], "all")) {
+ if (to != INFINITY) janet_addtimeout(to);
+ janet_ev_readchunk(stream, buffer, -1);
+ } else {
+ int32_t n = janet_getnat(argv, 1);
+ if (to != INFINITY) janet_addtimeout(to);
+ janet_ev_read(stream, buffer, n);
+ }
janet_await();
}
@@ -1867,6 +1952,14 @@ static const JanetReg ev_cfuns[] = {
JDOC("(ev/sleep sec)\n\n"
"Suspend the current fiber for sec seconds without blocking the event loop.")
},
+ {
+ "ev/deadline", cfun_ev_deadline,
+ JDOC("(ev/deadline sec &opt tocancel tocheck)\n\n"
+ "Set a deadline for a fiber `tocheck`. If `tocheck` is not finished after `sec` seconds, "
+ "`tocancel` will be canceled as with `ev/cancel`. "
+ "If `tocancel` and `tocheck` are not given, they default to `(fiber/root)` and "
+ "`(fiber/current)` respectively. Returns `tocancel`.")
+ },
{
"ev/chan", cfun_channel_new,
JDOC("(ev/chan &opt capacity)\n\n"
@@ -1924,7 +2017,8 @@ static const JanetReg ev_cfuns[] = {
{
"ev/read", janet_cfun_stream_read,
JDOC("(ev/read stream n &opt buffer timeout)\n\n"
- "Read up to n bytes into a buffer asynchronously from a stream. "
+ "Read up to n bytes into a buffer asynchronously from a stream. `n` can also be the keyword "
+ "`:all` to read into the buffer until end of stream. "
"Optionally provide a buffer to write into "
"as well as a timeout in seconds after which to cancel the operation and raise an error. "
"Returns the buffer if the read was successful or nil if end-of-stream reached. Will raise an "
diff --git a/src/core/fiber.c b/src/core/fiber.c
index f08aa047..546c89c5 100644
--- a/src/core/fiber.c
+++ b/src/core/fiber.c
@@ -420,8 +420,7 @@ void janet_fiber_cframe(JanetFiber *fiber, JanetCFunction cfun) {
newframe->flags = 0;
}
-/* Pop a stack frame from the fiber. Returns the new stack frame, or
- * NULL if there are no more frames */
+/* Pop a stack frame from the fiber. */
void janet_fiber_popframe(JanetFiber *fiber) {
JanetStackFrame *frame = janet_fiber_frame(fiber);
if (fiber->frame == 0) return;
diff --git a/src/core/gc.c b/src/core/gc.c
index 33435d9f..7788f48b 100644
--- a/src/core/gc.c
+++ b/src/core/gc.c
@@ -226,11 +226,14 @@ static void janet_mark_function(JanetFunction *func) {
if (janet_gc_reachable(func))
return;
janet_gc_mark(func);
- numenvs = func->def->environments_length;
- for (i = 0; i < numenvs; ++i) {
- janet_mark_funcenv(func->envs[i]);
+ if (NULL != func->def) {
+ /* this should always be true, except if function is only partially constructed */
+ numenvs = func->def->environments_length;
+ for (i = 0; i < numenvs; ++i) {
+ janet_mark_funcenv(func->envs[i]);
+ }
+ janet_mark_funcdef(func->def);
}
- janet_mark_funcdef(func->def);
}
static void janet_mark_fiber(JanetFiber *fiber) {
diff --git a/src/core/io.c b/src/core/io.c
index 47376290..8bdb3202 100644
--- a/src/core/io.c
+++ b/src/core/io.c
@@ -777,7 +777,7 @@ static const JanetReg io_cfuns[] = {
#ifndef JANET_NO_PROCESSES
{
"file/popen", cfun_io_popen,
- JDOC("(file/popen command &opt mode)\n\n"
+ JDOC("(file/popen command &opt mode) (DEPRECATED for os/spawn)\n\n"
"Open a file that is backed by a process. The file must be opened in either "
"the :r (read) or the :w (write) mode. In :r mode, the stdout of the "
"process can be read from the file. In :w mode, the stdin of the process "
diff --git a/src/core/marsh.c b/src/core/marsh.c
index 96767a53..e80932bc 100644
--- a/src/core/marsh.c
+++ b/src/core/marsh.c
@@ -286,7 +286,7 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) {
#define JANET_FIBER_FLAG_HASCHILD (1 << 29)
#define JANET_FIBER_FLAG_HASENV (1 << 30)
-#define JANET_STACKFRAME_HASENV (1 << 31)
+#define JANET_STACKFRAME_HASENV (INT32_MIN)
/* Marshal a fiber */
static void marshal_one_fiber(MarshalState *st, JanetFiber *fiber, int flags) {
@@ -542,9 +542,10 @@ static void marshal_one(MarshalState *st, Janet x, int flags) {
case JANET_FUNCTION: {
pushbyte(st, LB_FUNCTION);
JanetFunction *func = janet_unwrap_function(x);
- marshal_one_def(st, func->def, flags);
- /* Mark seen after reading def, but before envs */
+ /* Mark seen before reading def */
MARK_SEEN();
+ pushint(st, func->def->environments_length);
+ marshal_one_def(st, func->def, flags);
for (int32_t i = 0; i < func->def->environments_length; i++)
marshal_one_env(st, func->envs[i], flags + 1);
return;
@@ -1228,12 +1229,20 @@ static const uint8_t *unmarshal_one(
case LB_FUNCTION: {
JanetFunction *func;
JanetFuncDef *def;
- data = unmarshal_one_def(st, data + 1, &def, flags + 1);
+ data++;
+ int32_t len = readnat(st, &data);
+ if (len > 255) {
+ janet_panicf("invalid function");
+ }
func = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) +
- def->environments_length * sizeof(JanetFuncEnv));
- func->def = def;
+ len * sizeof(JanetFuncEnv));
*out = janet_wrap_function(func);
janet_v_push(st->lookup, *out);
+ data = unmarshal_one_def(st, data, &def, flags + 1);
+ if (def->environments_length != len) {
+ janet_panicf("invalid function");
+ }
+ func->def = def;
for (int32_t i = 0; i < def->environments_length; i++) {
data = unmarshal_one_env(st, data, &(func->envs[i]), flags + 1);
}
diff --git a/src/core/net.c b/src/core/net.c
index f0b3984e..ed8a6e17 100644
--- a/src/core/net.c
+++ b/src/core/net.c
@@ -509,11 +509,16 @@ static Janet cfun_stream_read(int32_t argc, Janet *argv) {
janet_arity(argc, 2, 4);
JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type);
janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET);
- int32_t n = janet_getnat(argv, 1);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, 10);
double to = janet_optnumber(argv, argc, 3, INFINITY);
- if (to != INFINITY) janet_addtimeout(to);
- janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL);
+ if (janet_keyeq(argv[1], "all")) {
+ if (to != INFINITY) janet_addtimeout(to);
+ janet_ev_recvchunk(stream, buffer, -1, MSG_NOSIGNAL);
+ } else {
+ int32_t n = janet_getnat(argv, 1);
+ if (to != INFINITY) janet_addtimeout(to);
+ janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL);
+ }
janet_await();
}
@@ -643,6 +648,7 @@ static const JanetReg net_cfuns[] = {
"net/read", cfun_stream_read,
JDOC("(net/read stream nbytes &opt buf timeout)\n\n"
"Read up to n bytes from a stream, suspending the current fiber until the bytes are available. "
+ "`n` can also be the keyword `:all` to read into the buffer until end of stream. "
"If less than n bytes are available (and more than 0), will push those bytes and return early. "
"Takes an optional timeout in seconds, after which will return nil. "
"Returns a buffer with up to n more bytes in it, or raises an error if the read failed.")
diff --git a/src/core/os.c b/src/core/os.c
index 64539727..1f996732 100644
--- a/src/core/os.c
+++ b/src/core/os.c
@@ -2126,25 +2126,25 @@ static const JanetReg os_cfuns[] = {
"mode should be a file mode as passed to os/chmod, but only if the create flag is given. "
"The default mode is 8r666. "
"Allowed flags are as follows:\n\n"
- "\t:r - open this file for reading\n"
- "\t:w - open this file for writing\n"
- "\t:c - create a new file (O_CREATE)\n"
- "\t:e - fail if the file exists (O_EXCL)\n"
- "\t:t - shorten an existing file to length 0 (O_TRUNC)\n\n"
- "Posix only flags:\n"
- "\t:a - append to a file (O_APPEND)\n"
- "\t:x - O_SYNC\n"
- "\t:C - O_NOCTTY\n\n"
- "Windows only flags:\n"
- "\t:R - share reads (FILE_SHARE_READ)\n"
- "\t:W - share writes (FILE_SHARE_WRITE)\n"
- "\t:D - share deletes (FILE_SHARE_DELETE)\n"
- "\t:H - FILE_ATTRIBUTE_HIDDEN\n"
- "\t:O - FILE_ATTRIBUTE_READONLY\n"
- "\t:F - FILE_ATTRIBUTE_OFFLINE\n"
- "\t:T - FILE_ATTRIBUTE_TEMPORARY\n"
- "\t:d - FILE_FLAG_DELETE_ON_CLOSE\n"
- "\t:b - FILE_FLAG_NO_BUFFERING\n")
+ " * :r - open this file for reading\n"
+ " * :w - open this file for writing\n"
+ " * :c - create a new file (O_CREATE)\n"
+ " * :e - fail if the file exists (O_EXCL)\n"
+ " * :t - shorten an existing file to length 0 (O_TRUNC)\n\n"
+ "Posix only flags:\n\n"
+ " * :a - append to a file (O_APPEND)\n"
+ " * :x - O_SYNC\n"
+ " * :C - O_NOCTTY\n\n"
+ "Windows only flags:\n\n"
+ " * :R - share reads (FILE_SHARE_READ)\n"
+ " * :W - share writes (FILE_SHARE_WRITE)\n"
+ " * :D - share deletes (FILE_SHARE_DELETE)\n"
+ " * :H - FILE_ATTRIBUTE_HIDDEN\n"
+ " * :O - FILE_ATTRIBUTE_READONLY\n"
+ " * :F - FILE_ATTRIBUTE_OFFLINE\n"
+ " * :T - FILE_ATTRIBUTE_TEMPORARY\n"
+ " * :d - FILE_FLAG_DELETE_ON_CLOSE\n"
+ " * :b - FILE_FLAG_NO_BUFFERING\n")
},
{
"os/pipe", os_pipe,
diff --git a/src/core/parse.c b/src/core/parse.c
index 16b67c17..e59ca132 100644
--- a/src/core/parse.c
+++ b/src/core/parse.c
@@ -985,8 +985,20 @@ static Janet cfun_parse_flush(int32_t argc, Janet *argv) {
}
static Janet cfun_parse_where(int32_t argc, Janet *argv) {
- janet_fixarity(argc, 1);
+ janet_arity(argc, 1, 3);
JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type);
+ if (argc > 1) {
+ int32_t line = janet_getinteger(argv, 1);
+ if (line < 1)
+ janet_panicf("invalid line number %d", line);
+ p->line = (size_t) line;
+ }
+ if (argc > 2) {
+ int32_t column = janet_getinteger(argv, 2);
+ if (column < 0)
+ janet_panicf("invalid column number %d", column);
+ p->column = (size_t) column;
+ }
Janet *tup = janet_tuple_begin(2);
tup[0] = janet_wrap_integer(p->line);
tup[1] = janet_wrap_integer(p->column);
@@ -1247,8 +1259,10 @@ static const JanetReg parse_cfuns[] = {
},
{
"parser/where", cfun_parse_where,
- JDOC("(parser/where parser)\n\n"
- "Returns the current line number and column of the parser's internal state.")
+ JDOC("(parser/where parser &opt line col)\n\n"
+ "Returns the current line number and column of the parser's internal state. If line is "
+ "provided, the current line number of the parser is first set to that value. If column is "
+ "also provided, the current column number of the parser is also first set to that value.")
},
{
"parser/eof", cfun_parse_eof,
diff --git a/src/core/pp.c b/src/core/pp.c
index 1fcc1e18..65a8d755 100644
--- a/src/core/pp.c
+++ b/src/core/pp.c
@@ -565,7 +565,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) {
JanetTable *t = janet_unwrap_table(x);
JanetTable *proto = t->proto;
if (NULL != proto) {
- Janet name = janet_table_get(proto, janet_ckeywordv("name"));
+ Janet name = janet_table_get(proto, janet_ckeywordv("_name"));
const uint8_t *n;
int32_t len;
if (janet_bytes_view(name, &n, &len)) {
diff --git a/src/core/util.c b/src/core/util.c
index e2257759..3886ced1 100644
--- a/src/core/util.c
+++ b/src/core/util.c
@@ -227,19 +227,21 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) {
/* Computes hash of an array of values */
int32_t janet_array_calchash(const Janet *array, int32_t len) {
const Janet *end = array + len;
- uint32_t hash = 5381;
- while (array < end)
- hash = (hash << 5) + hash + janet_hash(*array++);
+ uint32_t hash = 0;
+ while (array < end) {
+ uint32_t elem = janet_hash(*array++);
+ hash ^= elem + 0x9e3779b9 + (hash << 6) + (hash >> 2);
+ }
return (int32_t) hash;
}
/* Computes hash of an array of values */
int32_t janet_kv_calchash(const JanetKV *kvs, int32_t len) {
const JanetKV *end = kvs + len;
- uint32_t hash = 5381;
+ uint32_t hash = 0;
while (kvs < end) {
- hash = (hash << 5) + hash + janet_hash(kvs->key);
- hash = (hash << 5) + hash + janet_hash(kvs->value);
+ hash ^= janet_hash(kvs->key) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
+ hash ^= janet_hash(kvs->value) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
kvs++;
}
return (int32_t) hash;
diff --git a/src/core/value.c b/src/core/value.c
index 1be7966c..ae418d5e 100644
--- a/src/core/value.c
+++ b/src/core/value.c
@@ -28,6 +28,8 @@
#include
#endif
+#include
+
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_top = NULL;
JANET_THREAD_LOCAL JanetTraversalNode *janet_vm_traversal_base = NULL;
@@ -261,6 +263,21 @@ int32_t janet_hash(Janet x) {
case JANET_STRUCT:
hash = janet_struct_hash(janet_unwrap_struct(x));
break;
+ case JANET_NUMBER: {
+ double num = janet_unwrap_number(x);
+ if (isnan(num) || isinf(num) || num == 0) {
+ hash = 0;
+ } else {
+ hash = (int32_t)num;
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = ((hash >> 16) ^ hash) * 0x45d9f3b;
+ hash = (hash >> 16) ^ hash;
+
+ uint32_t lo = (uint32_t)(janet_u64(x) & 0xFFFFFFFF);
+ hash ^= lo + 0x9e3779b9 + (hash << 6) + (hash >> 2);
+ }
+ break;
+ }
case JANET_ABSTRACT: {
JanetAbstract xx = janet_unwrap_abstract(x);
const JanetAbstractType *at = janet_abstract_type(xx);
diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c
index 47de7788..174a324f 100644
--- a/src/mainclient/shell.c
+++ b/src/mainclient/shell.c
@@ -126,28 +126,21 @@ https://github.com/antirez/linenoise/blob/master/linenoise.c
#define JANET_LINE_MAX 1024
#define JANET_MATCH_MAX 256
#define JANET_HISTORY_MAX 100
-static int gbl_israwmode = 0;
-static const char *gbl_prompt = "> ";
-static int gbl_plen = 2;
-static char gbl_buf[JANET_LINE_MAX];
-static int gbl_len = 0;
-static int gbl_pos = 0;
-static int gbl_cols = 80;
-static char *gbl_history[JANET_HISTORY_MAX];
-static int gbl_history_count = 0;
-static int gbl_historyi = 0;
-static int gbl_sigint_flag = 0;
-static struct termios gbl_termios_start;
-static JanetByteView gbl_matches[JANET_MATCH_MAX];
-static int gbl_match_count = 0;
-static int gbl_lines_below = 0;
-
-/* Put a lock around this global state so we don't screw up
- * the terminal in a multithreaded situation */
-#ifndef JANET_SINGLE_THREADED
-#include
-static pthread_mutex_t gbl_lock = PTHREAD_MUTEX_INITIALIZER;
-#endif
+static JANET_THREAD_LOCAL int gbl_israwmode = 0;
+static JANET_THREAD_LOCAL const char *gbl_prompt = "> ";
+static JANET_THREAD_LOCAL int gbl_plen = 2;
+static JANET_THREAD_LOCAL char gbl_buf[JANET_LINE_MAX];
+static JANET_THREAD_LOCAL int gbl_len = 0;
+static JANET_THREAD_LOCAL int gbl_pos = 0;
+static JANET_THREAD_LOCAL int gbl_cols = 80;
+static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX];
+static JANET_THREAD_LOCAL int gbl_history_count = 0;
+static JANET_THREAD_LOCAL int gbl_historyi = 0;
+static JANET_THREAD_LOCAL int gbl_sigint_flag = 0;
+static JANET_THREAD_LOCAL struct termios gbl_termios_start;
+static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
+static JANET_THREAD_LOCAL int gbl_match_count = 0;
+static JANET_THREAD_LOCAL int gbl_lines_below = 0;
/* Unsupported terminal list from linenoise */
static const char *badterms[] = {
@@ -169,9 +162,6 @@ static char *sdup(const char *s) {
/* Ansi terminal raw mode */
static int rawmode(void) {
struct termios t;
-#ifndef JANET_SINGLE_THREADED
- pthread_mutex_lock(&gbl_lock);
-#endif
if (!isatty(STDIN_FILENO)) goto fatal;
if (tcgetattr(STDIN_FILENO, &gbl_termios_start) == -1) goto fatal;
t = gbl_termios_start;
@@ -185,9 +175,6 @@ static int rawmode(void) {
return 0;
fatal:
errno = ENOTTY;
-#ifndef JANET_SINGLE_THREADED
- pthread_mutex_unlock(&gbl_lock);
-#endif
return -1;
}
@@ -195,9 +182,6 @@ fatal:
static void norawmode(void) {
if (gbl_israwmode && tcsetattr(STDIN_FILENO, TCSADRAIN, &gbl_termios_start) != -1)
gbl_israwmode = 0;
-#ifndef JANET_SINGLE_THREADED
- pthread_mutex_unlock(&gbl_lock);
-#endif
}
static int curpos(void) {
@@ -774,6 +758,10 @@ static int line() {
kleft();
break;
case 3: /* ctrl-c */
+ clearlines();
+ gbl_sigint_flag = 1;
+ return -1;
+ case 17: /* ctrl-q */
gbl_cancel_current_repl_form = 1;
clearlines();
return -1;
diff --git a/test/helper.janet b/test/helper.janet
index 3e697b8a..0d97265b 100644
--- a/test/helper.janet
+++ b/test/helper.janet
@@ -13,7 +13,7 @@
(when x (++ num-tests-passed))
(def str (string e))
(def truncated
- (if (> (length e) 40) (string (string/slice e 0 35) "...") (string e)))
+ (if (> (length e) 40) (string (string/slice e 0 35) "...") (describe e)))
(if x
(eprintf "\e[32m✔\e[0m %s: %v" truncated x)
(eprintf "\n\e[31m✘\e[0m %s: %v" truncated x))
diff --git a/test/install/test/test1.janet b/test/install/test/test1.janet
index 57fde0e1..d6c9c0f5 100644
--- a/test/install/test/test1.janet
+++ b/test/install/test/test1.janet
@@ -1,3 +1,3 @@
-(import build/testmod :as testmod)
+(import /build/testmod :as testmod)
(if (not= 5 (testmod/get5)) (error "testmod/get5 failed"))
diff --git a/test/install/testexec.janet b/test/install/testexec.janet
index 4f4c5020..36c91498 100644
--- a/test/install/testexec.janet
+++ b/test/install/testexec.janet
@@ -1,7 +1,7 @@
-(use build/testmod)
-(use build/testmod2)
-(use build/testmod3)
-(use build/test-mod-4)
+(use /build/testmod)
+(use /build/testmod2)
+(use /build/testmod3)
+(use /build/test-mod-4)
(defn main [&]
(print "Hello from executable!")
diff --git a/test/suite0006.janet b/test/suite0006.janet
index 679a6425..42f2367d 100644
--- a/test/suite0006.janet
+++ b/test/suite0006.janet
@@ -128,6 +128,18 @@
(assert (not= nil (parse-error @"\xc3\x28")) "reject invalid utf-8 symbol")
(assert (not= nil (parse-error @":\xc3\x28")) "reject invalid utf-8 keyword")
+# Parser line and column numbers
+(defn parser-location [input &opt location]
+ (def p (parser/new))
+ (parser/consume p input)
+ (if location
+ (parser/where p ;location)
+ (parser/where p)))
+
+(assert (= [1 7] (parser-location @"(+ 1 2)")) "parser location 1")
+(assert (= [5 7] (parser-location @"(+ 1 2)" [5])) "parser location 2")
+(assert (= [10 10] (parser-location @"(+ 1 2)" [10 10])) "parser location 3")
+
# String check-set
(assert (string/check-set "abc" "a") "string/check-set 1")
(assert (not (string/check-set "abc" "z")) "string/check-set 2")
diff --git a/test/suite0008.janet b/test/suite0008.janet
index 92e40e44..a10508bf 100644
--- a/test/suite0008.janet
+++ b/test/suite0008.janet
@@ -125,6 +125,7 @@
(assert (= :yes (match {:a 1} {:a _} :yes :no)) "match wildcard 5")
(assert (= false (match {:a 1 :b 2 :c 3} {:a a :b _ :c _ :d _} :no {:a _ :b _ :c _} false :no)) "match wildcard 6")
(assert (= nil (match {:a 1 :b 2 :c 3} {:a a :b _ :c _ :d _} :no {:a _ :b _ :c _} nil :no)) "match wildcard 7")
+(assert (= "t" (match [true nil] [true _] "t")) "match wildcard 8")
# Regression #301
(def b (buffer/new-filled 128 0x78))
@@ -221,20 +222,6 @@
neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
\0\0\0\0\0*\xFE\x01\04\x02\0\0'\x03\0\r\0\r\0\r\0\r" load-image-dict))
-# No segfault, valgrind clean.
-
-(def x @"\xCC\xCD.nd\x80\0\r\x1C\xCDg!\0\x07\xCC\xCD\r\x1Ce\x10\0\r;\xCDb\x04\xFF9\xFF\x80\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04uu\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\0\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04}\x04\x04\x04\x04\x04\x04\x04\x04#\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\0\x01\0\0\x03\x04\x04\x04\xE2\x03\x04\x04\x04\x04\x04\x04\x04\x04\x04\x14\x1A\x04\x04\x04\x04\x04\x18\x04\x04!\x04\xE2\x03\x04\x04\x04\x04\x04\x04$\x04\x04\x04\x04\x04\x04\x04\x04\x04\x80\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04A\0\0\0\x03\0\0!\xBF\xFF")
-(assert-error "bad fiber status" (unmarshal x load-image-dict))
-(gccollect)
-(marshal x make-image-dict)
-
-(def b @"\xCC\xCD\0\x03\0\x08\x04\rm\xCD\x7F\xFF\xFF\xFF\x02\0\x02\xD7\xCD\0\x98\0\0\x05\x01\x01\x01\x01\x08\xCE\x01f\xCE../tools/afl/generate_unmarshal_testcases.janet\xCE\x012,\x01\0\0&\x03\0\06\x02\x03\x03)\x03\x01\0*\x04\0\00\x03\x04\0>\x03\0\0\x03\x03\0\0*\x05\0\x11\0\x11\0\x05\0\x05\0\x05\0\x05\0\x05\xC9\xDA\x04\xC9\xC9\xC9")
-(unmarshal b load-image-dict)
-(gccollect)
-
-(def v (unmarshal
- @"\xD7\xCD0\xD4000000\0\x03\x01\xCE\00\0\x01\0\0000\x03\0\0\0000000000\xCC0\0000"
- load-image-dict))
(gccollect)
# in vs get regression
@@ -349,4 +336,12 @@ neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02
(check-replacer "aba" "ZZZZZZ" "ababababababa")
(check-replacer "aba" "" "ababababababa")
+# Peg bug
+(assert (deep= @[] (peg/match '(any 1) @"")) "peg empty pattern 1")
+(assert (deep= @[] (peg/match '(any 1) (buffer))) "peg empty pattern 2")
+(assert (deep= @[] (peg/match '(any 1) "")) "peg empty pattern 3")
+(assert (deep= @[] (peg/match '(any 1) (string))) "peg empty pattern 4")
+(assert (deep= @[] (peg/match '(* "test" (any 1)) @"test")) "peg empty pattern 5")
+(assert (deep= @[] (peg/match '(* "test" (any 1)) (buffer "test"))) "peg empty pattern 6")
+
(end-suite)