2023-06-01 22:04:07 +00:00
|
|
|
# Copyright (c) 2023 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.
|
|
|
|
|
|
|
|
(import ./helper :prefix "" :exit true)
|
|
|
|
(start-suite)
|
|
|
|
|
|
|
|
# 7e46ead2f
|
|
|
|
(assert (not false) "false literal")
|
|
|
|
(assert true "true literal")
|
|
|
|
(assert (not nil) "nil literal")
|
|
|
|
|
|
|
|
(assert (= '(1 2 3) (quote (1 2 3)) (tuple 1 2 3)) "quote shorthand")
|
|
|
|
|
|
|
|
# String literals
|
|
|
|
# 45f8db0
|
|
|
|
(assert (= "abcd" "\x61\x62\x63\x64") "hex escapes")
|
|
|
|
(assert (= "\e" "\x1B") "escape character")
|
|
|
|
(assert (= "\x09" "\t") "tab character")
|
|
|
|
|
|
|
|
# Long strings
|
|
|
|
# 7e6342720
|
|
|
|
(assert (= "hello, world" `hello, world`) "simple long string")
|
|
|
|
(assert (= "hello, \"world\"" `hello, "world"`)
|
|
|
|
"long string with embedded quotes")
|
|
|
|
(assert (= "hello, \\\\\\ \"world\"" `hello, \\\ "world"`)
|
|
|
|
"long string with embedded quotes and backslashes")
|
|
|
|
|
|
|
|
#
|
|
|
|
# Longstring indentation
|
|
|
|
#
|
|
|
|
# 7aa4241
|
|
|
|
(defn reindent
|
|
|
|
"Reindent the contents of a longstring as the Janet parser would.
|
|
|
|
This include removing leading and trailing newlines."
|
|
|
|
[text indent]
|
|
|
|
|
|
|
|
# Detect minimum indent
|
|
|
|
(var rewrite true)
|
|
|
|
(each index (string/find-all "\n" text)
|
|
|
|
(for i (+ index 1) (+ index indent 1)
|
|
|
|
(case (get text i)
|
|
|
|
nil (break)
|
|
|
|
(chr "\n") (break)
|
|
|
|
(chr " ") nil
|
|
|
|
(set rewrite false))))
|
|
|
|
|
|
|
|
# Only re-indent if no dedented characters.
|
|
|
|
(def str
|
|
|
|
(if rewrite
|
|
|
|
(peg/replace-all ~(* "\n" (between 0 ,indent " ")) "\n" text)
|
|
|
|
text))
|
|
|
|
|
|
|
|
(def first-nl (= (chr "\n") (first str)))
|
|
|
|
(def last-nl (= (chr "\n") (last str)))
|
|
|
|
(string/slice str (if first-nl 1 0) (if last-nl -2)))
|
|
|
|
|
|
|
|
(defn reindent-reference
|
|
|
|
"Same as reindent but use parser functionality. Useful for
|
|
|
|
validating conformance."
|
|
|
|
[text indent]
|
|
|
|
(if (empty? text) (break text))
|
|
|
|
(def source-code
|
|
|
|
(string (string/repeat " " indent) "``````"
|
|
|
|
text
|
|
|
|
"``````"))
|
|
|
|
(parse source-code))
|
|
|
|
|
|
|
|
(var indent-counter 0)
|
|
|
|
(defn check-indent
|
|
|
|
[text indent]
|
|
|
|
(++ indent-counter)
|
|
|
|
(let [a (reindent text indent)
|
|
|
|
b (reindent-reference text indent)]
|
|
|
|
(assert (= a b)
|
|
|
|
(string "indent " indent-counter " (indent=" indent ")"))))
|
|
|
|
|
|
|
|
(check-indent "" 0)
|
|
|
|
(check-indent "\n" 0)
|
|
|
|
(check-indent "\n" 1)
|
|
|
|
(check-indent "\n\n" 0)
|
|
|
|
(check-indent "\n\n" 1)
|
|
|
|
(check-indent "\nHello, world!" 0)
|
|
|
|
(check-indent "\nHello, world!" 1)
|
|
|
|
(check-indent "Hello, world!" 0)
|
|
|
|
(check-indent "Hello, world!" 1)
|
|
|
|
(check-indent "\n Hello, world!" 4)
|
|
|
|
(check-indent "\n Hello, world!\n" 4)
|
|
|
|
(check-indent "\n Hello, world!\n " 4)
|
|
|
|
(check-indent "\n Hello, world!\n " 4)
|
|
|
|
(check-indent "\n Hello, world!\n dedented text\n " 4)
|
|
|
|
(check-indent "\n Hello, world!\n indented text\n " 4)
|
|
|
|
|
|
|
|
# Symbols with @ character
|
|
|
|
# d68eae9
|
|
|
|
(def @ 1)
|
|
|
|
(assert (= @ 1) "@ symbol")
|
|
|
|
(def @-- 2)
|
|
|
|
(assert (= @-- 2) "@-- symbol")
|
|
|
|
(def @hey 3)
|
|
|
|
(assert (= @hey 3) "@hey symbol")
|
|
|
|
|
|
|
|
# Parser clone
|
|
|
|
# 43520ac67
|
|
|
|
(def p (parser/new))
|
|
|
|
(assert (= 7 (parser/consume p "(1 2 3 ")) "parser 1")
|
|
|
|
(def p2 (parser/clone p))
|
|
|
|
(parser/consume p2 ") 1 ")
|
|
|
|
(parser/consume p ") 1 ")
|
|
|
|
(assert (deep= (parser/status p) (parser/status p2)) "parser 2")
|
|
|
|
(assert (deep= (parser/state p) (parser/state p2)) "parser 3")
|
|
|
|
|
|
|
|
# Parser errors
|
|
|
|
# 976dfc719
|
|
|
|
(defn parse-error [input]
|
|
|
|
(def p (parser/new))
|
|
|
|
(parser/consume p input)
|
|
|
|
(parser/error p))
|
|
|
|
|
|
|
|
# Invalid utf-8 sequences
|
|
|
|
(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
|
|
|
|
# 77b79e989
|
|
|
|
(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")
|
|
|
|
|
|
|
|
# Issue #861 - should be valgrind clean
|
|
|
|
# 39c6be7cb
|
|
|
|
(def step1 "(a b c d)\n")
|
|
|
|
(def step2 "(a b)\n")
|
|
|
|
(def p1 (parser/new))
|
|
|
|
(parser/state p1)
|
|
|
|
(parser/consume p1 step1)
|
|
|
|
(loop [v :iterate (parser/produce p1)])
|
|
|
|
(parser/state p1)
|
|
|
|
(def p2 (parser/clone p1))
|
|
|
|
(parser/state p2)
|
|
|
|
(parser/consume p2 step2)
|
|
|
|
(loop [v :iterate (parser/produce p2)])
|
|
|
|
(parser/state p2)
|
|
|
|
|
2023-06-03 16:59:19 +00:00
|
|
|
# parser delimiter errors
|
|
|
|
(defn test-error [delim fmt]
|
|
|
|
(def p (parser/new))
|
|
|
|
(parser/consume p delim)
|
|
|
|
(parser/eof p)
|
|
|
|
(def msg (string/format fmt delim))
|
|
|
|
(assert (= (parser/error p) msg) "delimiter error"))
|
|
|
|
(each c [ "(" "{" "[" "\"" "``" ]
|
|
|
|
(test-error c "unexpected end of source, %s opened at line 1, column 1"))
|
|
|
|
|
|
|
|
# parser/insert
|
|
|
|
(def p (parser/new))
|
|
|
|
(parser/consume p "(")
|
|
|
|
(parser/insert p "hello")
|
|
|
|
(parser/consume p ")")
|
|
|
|
(assert (= (parser/produce p) ["hello"]))
|
|
|
|
|
|
|
|
(def p (parser/new))
|
|
|
|
(parser/consume p `("hel`)
|
|
|
|
(parser/insert p `lo`)
|
|
|
|
(parser/consume p `")`)
|
|
|
|
(assert (= (parser/produce p) ["hello"]))
|
|
|
|
|
2023-06-01 22:04:07 +00:00
|
|
|
(end-suite)
|
|
|
|
|