mirror of https://github.com/rwxrob/bonzai
Working commit to preserve refactoring
parent
a50179bb1d
commit
51d622e1c1
@ -1,66 +0,0 @@
|
|||||||
// Copyright 2022 Robert S. Muhlestein.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package Z_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
Z "github.com/rwxrob/bonzai"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleArgsFrom() {
|
|
||||||
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french`))
|
|
||||||
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french `))
|
|
||||||
// Output:
|
|
||||||
// ["greet" "hi" "french"]
|
|
||||||
// ["greet" "hi" "french" ""]
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleArgsOrIn_read_Nil() {
|
|
||||||
|
|
||||||
orig := os.Stdin
|
|
||||||
defer func() { os.Stdin = orig }()
|
|
||||||
os.Stdin, _ = os.Open(`testdata/in`)
|
|
||||||
|
|
||||||
fmt.Println(Z.ArgsOrIn(nil))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// some thing
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleArgsOrIn_read_Zero_Args() {
|
|
||||||
|
|
||||||
orig := os.Stdin
|
|
||||||
defer func() { os.Stdin = orig }()
|
|
||||||
os.Stdin, _ = os.Open(`testdata/in`)
|
|
||||||
|
|
||||||
fmt.Println(Z.ArgsOrIn([]string{}))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// some thing
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleArgsOrIn_args_Joined() {
|
|
||||||
|
|
||||||
fmt.Println(Z.ArgsOrIn([]string{"some", "thing"}))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// some thing
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEsc() {
|
|
||||||
fmt.Println(Z.Esc("|&;()<>![]"))
|
|
||||||
fmt.Printf("%q", Z.Esc(" \n\r"))
|
|
||||||
// Output:
|
|
||||||
// \|\&\;\(\)\<\>\!\[\]
|
|
||||||
// "\\ \\\n\\\r"
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEscAll() {
|
|
||||||
list := []string{"so!me", "<here>", "other&"}
|
|
||||||
fmt.Println(Z.EscAll(list))
|
|
||||||
// Output:
|
|
||||||
// [so\!me \<here\> other\&]
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
|
||||||
github.com/rwxrob/bonzai v0.0.39 h1:EJ1aVVVwnm4mdPKJEeCLI3wVttgbD8ulggXKZiwEO4w=
|
|
||||||
github.com/rwxrob/bonzai v0.0.39/go.mod h1:PyKG44H68o3DZ2Xh6ouViGW9IAbYZVw0OYqLMdCM1RI=
|
|
||||||
github.com/rwxrob/config v0.3.1 h1:YwnPEuEFDZz7gD5ZJwYkYEF/oSjvM9ryXV6oK+aDvJc=
|
|
||||||
github.com/rwxrob/config v0.3.1/go.mod h1:aqV/tWGH+Tz2ciADZDDYoodAniZ2iQTm8WZNM0wRyCA=
|
|
||||||
github.com/rwxrob/fn v0.3.0 h1:R4kcZhInEc9Fn3lsWbn3O6ZOoZ/D43Y1l3SS5Nxm1wc=
|
|
||||||
github.com/rwxrob/fn v0.3.0/go.mod h1:omPqOqEB+dDna09z5pi5YFxq4IZqDvv3wFPUCES5LvY=
|
|
||||||
github.com/rwxrob/fs v0.4.3 h1:ntu9TZnk7NHd1Yen+p4+xruBmkQMugKtFU0OLfAMa+M=
|
|
||||||
github.com/rwxrob/fs v0.4.3/go.mod h1:vO8AeluD7rnrO7zC54745xTEBFgHPUpHL0hbp1NnsVo=
|
|
||||||
github.com/rwxrob/json v0.4.1 h1:b4ToZe4mrQO8rRL/kRFglzZszyZZnGv6JRHj6jrI3f4=
|
|
||||||
github.com/rwxrob/json v0.4.1/go.mod h1:DU3TQKCWY4bK7sQ0wu80cRmTs96b6M//OYvT7Eg2mJA=
|
|
||||||
github.com/rwxrob/structs v0.5.0 h1:pjLsfyYHS+gB1CtzRj3H39wRYL4lI5pTpFf8kl91guw=
|
|
||||||
github.com/rwxrob/structs v0.5.0/go.mod h1:2gIte2ZI9zUok6q6F3v3l41ZXo7Zg5Kf1GUTP2+pXyQ=
|
|
||||||
github.com/rwxrob/term v0.1.5 h1:jKvNgEYlsT6sLRqIuR9ITcNNh4woGkWxcAe71pPJReQ=
|
|
||||||
github.com/rwxrob/term v0.1.5/go.mod h1:IVE7hG+VQlM4R+kS4pY6uhfMHoG0QECrZF7d7bKcdsk=
|
|
||||||
github.com/rwxrob/to v0.2.1 h1:ZYBNEa8LJT5VDQUHm2wSwiRf61xSqU87UqbYvitV3eY=
|
|
||||||
github.com/rwxrob/to v0.2.1/go.mod h1:8qdgCWkh50Avs8sRpV6/P7lAQgVf3KLRSKMZahV/W48=
|
|
||||||
github.com/rwxrob/y2j v0.3.1 h1:qOCU7J6g0Q/7KlLAabCMLx6/wG1/NelG6QTOVpESAQg=
|
|
||||||
github.com/rwxrob/y2j v0.3.1/go.mod h1:/3eS+LPnOF1F2VfoqZr3Upkr8q4ByziAi3eB6FIgzoE=
|
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|
||||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
|
|
||||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,52 +0,0 @@
|
|||||||
package help
|
|
||||||
|
|
||||||
/*
|
|
||||||
var Help = &Z.Cmd{
|
|
||||||
Name: `help`,
|
|
||||||
Aliases: []string{"h"},
|
|
||||||
Call: func(x *Z.Cmd, args ...string) error {
|
|
||||||
|
|
||||||
var buf string
|
|
||||||
|
|
||||||
buf += util.Emph("**NAME**", 0, -1) + "\n " + x.Title() + "\n\n"
|
|
||||||
buf += util.Emph("**SYNOPSIS**", 0, -1) + "\n " + x.Name + " " + x.Usage + "\n\n"
|
|
||||||
|
|
||||||
if len(x.Commands.M) > 0 {
|
|
||||||
buf += util.Emph("**COMMANDS**", 0, -1) + "\n" + x.Titles(7, 20) + "\n\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(x.Description) > 0 {
|
|
||||||
buf +=
|
|
||||||
util.Emph("**DESCRIPTION**", 0, -1) + "\n" +
|
|
||||||
util.Emph(x.Description, 7, 65) + "\n\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Source != "" || x.Issues != "" || x.Site != "" {
|
|
||||||
|
|
||||||
buf += util.Emph("**LINKS**", 0, -1) + "\n"
|
|
||||||
|
|
||||||
if x.Site != "" {
|
|
||||||
buf += " Site: " + x.Site + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Source != "" {
|
|
||||||
buf += " Source: " + x.Source + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Issues != "" {
|
|
||||||
buf += " Issues: " + x.Issues + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
buf += "\n"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Copyright != "" {
|
|
||||||
buf += util.Emph("**LEGAL**", 0, -1) + "\n" + util.Indent(x.Legal(), 7) + "\n\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
*/
|
|
@ -1,6 +0,0 @@
|
|||||||
package z
|
|
||||||
|
|
||||||
// keep only compound expressions here
|
|
||||||
|
|
||||||
var WS = I{' ', '\n', '\t', '\r'}
|
|
||||||
var Digit = R{0, 9}
|
|
@ -1,182 +0,0 @@
|
|||||||
// Copyright 2022 Robert S. Muhlestein.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package z (often imported as "is") defines the Bonzai Parsing Expression
|
|
||||||
Grammar Notation (BPEGN) (aka "Bonzai Scanner Expect" language)
|
|
||||||
implemented entirely using Go types (mostly slices and structs). BPEGN
|
|
||||||
can be 100% transpiled to and from the Parsing Expression Grammer
|
|
||||||
Notation (PEGN). Code in one grammar and use the bonzai command to
|
|
||||||
easily generate the other.
|
|
||||||
|
|
||||||
Nodes and Expressions
|
|
||||||
|
|
||||||
Nodes (z.Nd) indicate something to be captured as a part of the resulting
|
|
||||||
abstract syntax tree. They are functionally equivalent to parenthesis in
|
|
||||||
regular expressions but with the obvious advantage of capturing a rooted
|
|
||||||
node tree instead of an array. Expressions (z.X) indicate a sequence to
|
|
||||||
be scanned but not captured unless the expression itself is within
|
|
||||||
a node (z.Nd).
|
|
||||||
|
|
||||||
Tokens
|
|
||||||
|
|
||||||
The BPEGN token strings are contained in the "tk" package can be used as
|
|
||||||
is. See the "tk" package for details.
|
|
||||||
|
|
||||||
Parameterized Struct and Set Slice Expressions
|
|
||||||
|
|
||||||
BPEGN uses Go structs and slices to represent scannable expressions with
|
|
||||||
obvious similarities to regular expressions. These expressions have
|
|
||||||
one-to-parity with PEGN expressions. Slices represent sets of
|
|
||||||
possibilities. Structs provide parameters for more complex expressions
|
|
||||||
and are are guaranteed never to change allowing them to be dependably
|
|
||||||
used in assignment without struct field names using Go's inline
|
|
||||||
composable syntax. Some editors may need configuring to allow this since
|
|
||||||
in general practice this can create subtle (but substantial) foot-guns
|
|
||||||
for maintainers.
|
|
||||||
|
|
||||||
Look-Ahead and Advancing Expressions
|
|
||||||
|
|
||||||
"Advancing" expressions will advance the scan to the end of the
|
|
||||||
expression match. "Look-ahead" expressions simply check for a match but
|
|
||||||
do not advance the scan. Developers should take careful note of the
|
|
||||||
difference in the documentation.
|
|
||||||
|
|
||||||
Composites
|
|
||||||
|
|
||||||
Composites are compound expressions composed of others. They represent
|
|
||||||
the tokens and classes from PEGN and other grammars and are designed to
|
|
||||||
simplify grammar development at a higher level. Pull requests are
|
|
||||||
welcome for missing, commonly used composite candidates.
|
|
||||||
|
|
||||||
Hook Functions
|
|
||||||
|
|
||||||
Hook functions are not strictly an expression type and are declared in
|
|
||||||
the scan package itself (to avoid a cyclical import dependency since it
|
|
||||||
is passed a scan.R). A Hook is passed the *scan.R and can return an error.
|
|
||||||
See scan.Hook for more information.
|
|
||||||
*/
|
|
||||||
package z
|
|
||||||
|
|
||||||
// ------------------------------- core -------------------------------
|
|
||||||
|
|
||||||
// P ("parse") is a named sequence of expressions that will be parsed
|
|
||||||
// and captured as a new Node and added to the scan.R.Nodes field
|
|
||||||
// effectively turning the scan.R into a parser as well. The first
|
|
||||||
// string must always be the name which can be any valid Go string. If
|
|
||||||
// any expression fails to match the scan fails. Otherwise, a new
|
|
||||||
// scan.Node is added under the current node and the scan proceeds.
|
|
||||||
// Nodes must either contain other nodes or no nodes at all. If the
|
|
||||||
// first item in the sequence after the name is not also a node (z.P)
|
|
||||||
// then the node is marked as "edge" (or "leaf") and any nodes detected
|
|
||||||
// further in the sequence will cause the scan to fail with a syntax
|
|
||||||
// error.
|
|
||||||
type P []any
|
|
||||||
|
|
||||||
// X ("expression") is a sequence of expressions. If any are not the
|
|
||||||
// scan fails. (Equal to (?foo) in regular expressions.)
|
|
||||||
type X []any
|
|
||||||
|
|
||||||
// ------------------------------- sets -------------------------------
|
|
||||||
|
|
||||||
// Y ("yes") is a set of positive lookahead expressions. If any are
|
|
||||||
// seen at the current cursor position the scan will proceed without
|
|
||||||
// consuming them (unlike z.O and z.I). If none are found the scan
|
|
||||||
// fails. (Equal to ampersand (&) in
|
|
||||||
// PEGN.)
|
|
||||||
type Y []any
|
|
||||||
|
|
||||||
// N ("not") is a set of negative lookahead expressions. If any are seen
|
|
||||||
// at the current cursor position the scan will fail and the scan is
|
|
||||||
// never advanced. This is useful when everything from one expression is
|
|
||||||
// wanted except for a few negative exceptions. (Equal to exclamation
|
|
||||||
// point (!) in PEGN.)
|
|
||||||
type N []any
|
|
||||||
|
|
||||||
// I ("in","include") is a set of advancing expressions. If any
|
|
||||||
// expression in the slice is found the scan advances to the end of that
|
|
||||||
// expression and continues. If none of the expressions is found the
|
|
||||||
// scan fails. Evaluation of expressions is always left to right
|
|
||||||
// allowing parser developers to prioritize common expressions at the
|
|
||||||
// beginning of the slice.
|
|
||||||
type I []any
|
|
||||||
|
|
||||||
// O is a set of optional advancing expressions. If any expression is
|
|
||||||
// found the scan is advanced (unlike is.It, which does not advance).
|
|
||||||
type O []any
|
|
||||||
|
|
||||||
// T ("to") is a set of advancing expressions that mark an exclusive
|
|
||||||
// boundary at which the scan should stop returning a cursor just before
|
|
||||||
// the boundary.
|
|
||||||
type T []any
|
|
||||||
|
|
||||||
// Ti ("to inclusive") is an inclusive version of z.T returning
|
|
||||||
// a cursor that points to the last rune of the boundary itself.
|
|
||||||
type Ti []any
|
|
||||||
|
|
||||||
// --------------------------- parameterized --------------------------
|
|
||||||
|
|
||||||
// MM ("minmax") is a parameterized advancing expression that matches an
|
|
||||||
// inclusive minimum and maximum count of the given expression (This).
|
|
||||||
type MM struct {
|
|
||||||
Min int
|
|
||||||
Max int
|
|
||||||
This any
|
|
||||||
}
|
|
||||||
|
|
||||||
// M ("min") is a parameterized advancing expression that matches an
|
|
||||||
// inclusive minimum number of the given expression item (This). Use
|
|
||||||
// within is.It to disable advancement.
|
|
||||||
type M struct {
|
|
||||||
Min int
|
|
||||||
This any
|
|
||||||
}
|
|
||||||
|
|
||||||
// M1 is shorthand for z.M{1,This}.
|
|
||||||
type M1 struct{ This any }
|
|
||||||
|
|
||||||
// C is a parameterized advancing expression that matches an exact count
|
|
||||||
// of the given expression (This). Use within is.It to disable
|
|
||||||
// advancement.
|
|
||||||
type C struct {
|
|
||||||
N int
|
|
||||||
This any
|
|
||||||
}
|
|
||||||
|
|
||||||
// C2 is shorthand for z.C{2,This}.
|
|
||||||
type C2 struct{ This any }
|
|
||||||
|
|
||||||
// C3 is shorthand for z.C{3,This}.
|
|
||||||
type C3 struct{ This any }
|
|
||||||
|
|
||||||
// C4 is shorthand for z.C{4,This}.
|
|
||||||
type C4 struct{ This any }
|
|
||||||
|
|
||||||
// C5 is shorthand for z.C{5,This}.
|
|
||||||
type C5 struct{ This any }
|
|
||||||
|
|
||||||
// C6 is shorthand for z.C{6,This}.
|
|
||||||
type C6 struct{ This any }
|
|
||||||
|
|
||||||
// C7 is shorthand for z.C{7,This}.
|
|
||||||
type C7 struct{ This any }
|
|
||||||
|
|
||||||
// C8 is shorthand for z.C{8,This}.
|
|
||||||
type C8 struct{ This any }
|
|
||||||
|
|
||||||
// C9 is shorthand for z.C{9,This}.
|
|
||||||
type C9 struct{ This any }
|
|
||||||
|
|
||||||
// A is short for z.C{tk.ANY}. (Mnemonic: "any", "asterisk")
|
|
||||||
type A struct {
|
|
||||||
N int
|
|
||||||
}
|
|
||||||
|
|
||||||
// R ("range") is a parameterized advancing expression that matches
|
|
||||||
// a single Unicode code point (rune, int32) from an inclusive
|
|
||||||
// consecutive set from First to Last (First,Last). (Mnemonic: "range",
|
|
||||||
// "rune")
|
|
||||||
type R struct {
|
|
||||||
First rune
|
|
||||||
Last rune
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
# Bonzai Mark (Simple Subset of CommonMark)
|
|
||||||
|
|
||||||
<https://github.com/pegn/spec>
|
|
@ -1,383 +0,0 @@
|
|||||||
package mark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/rwxrob/bonzai/term/esc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenItalic opens italic emphasis. Default: ANSI italic.
|
|
||||||
var OpenItalic = esc.Italic
|
|
||||||
|
|
||||||
// CloseItalic closes italic emphasis. Default: ANSI reset.
|
|
||||||
var CloseItalic = esc.Reset
|
|
||||||
|
|
||||||
// OpenBold opens bold emphasis. Default: ANSI bold.
|
|
||||||
var OpenBold = esc.Bold
|
|
||||||
|
|
||||||
// CloseBold closes bold emphasis. Default: ANSI reset.
|
|
||||||
var CloseBold = esc.Reset
|
|
||||||
|
|
||||||
// OpenBoldItalic opens bold italic emphasis. Default: ANSI bold italic.
|
|
||||||
var OpenBoldItalic = esc.BoldItalic
|
|
||||||
|
|
||||||
// CloseBoldItalic closes bold italic emphasis. Default: ANSI reset.
|
|
||||||
var CloseBoldItalic = esc.Reset
|
|
||||||
|
|
||||||
// OpenUnderline open underline emphasis. Default: ANSI underline.
|
|
||||||
var OpenUnderline = esc.Underline
|
|
||||||
|
|
||||||
// CloseUnderline closes underline emphasis. Default: ANSI reset.
|
|
||||||
var CloseUnderline = esc.Reset
|
|
||||||
|
|
||||||
/*
|
|
||||||
func init() {
|
|
||||||
if !term.IsTerminal() {
|
|
||||||
reset = ""
|
|
||||||
italic = ""
|
|
||||||
bold = ""
|
|
||||||
bolditalic = ""
|
|
||||||
underline = ""
|
|
||||||
return
|
|
||||||
}
|
|
||||||
emphFromLess()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func emphFromLess() {
|
|
||||||
var x string
|
|
||||||
x = os.Getenv("LESS_TERMCAP_us")
|
|
||||||
if x != "" {
|
|
||||||
OpenItalic = x
|
|
||||||
}
|
|
||||||
x = os.Getenv("LESS_TERMCAP_md")
|
|
||||||
if x != "" {
|
|
||||||
OpenBold = x
|
|
||||||
}
|
|
||||||
x = os.Getenv("LESS_TERMCAP_mb")
|
|
||||||
if x != "" {
|
|
||||||
OpenBoldItalic = x
|
|
||||||
}
|
|
||||||
x = os.Getenv("LESS_TERMCAP_us")
|
|
||||||
if x != "" {
|
|
||||||
OpenUnderline = x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatWrapped takes a command documentation format string (an
|
|
||||||
// extremely limited version of Markdown that is also Godoc friendly)
|
|
||||||
// and transforms it as follows:
|
|
||||||
//
|
|
||||||
// * Initial and trailing blank lines are removed.
|
|
||||||
//
|
|
||||||
// * Indentation is removed - the number of spaces preceeding the first
|
|
||||||
// word of the first line are ignored in every line (including raw
|
|
||||||
// text blocks).
|
|
||||||
//
|
|
||||||
// * Raw text ignored - any line beginning with four or more spaces
|
|
||||||
// (after convenience indentation is removed) will be kept as it is
|
|
||||||
// exactly (code examples, etc.) but should never exceed 80 characters
|
|
||||||
// (including the spaces).
|
|
||||||
//
|
|
||||||
// * Blocks are unwrapped - any non-blank (without three or less initial
|
|
||||||
// spaces) will be trimmed line following a line will be joined to the
|
|
||||||
// preceding line recursively (unless hard break).
|
|
||||||
//
|
|
||||||
// * Hard breaks kept - like Markdown any line that ends with two or
|
|
||||||
// more spaces will automatically force a line return.
|
|
||||||
//
|
|
||||||
// * URL links argument names and anything else within angle brackets
|
|
||||||
// (<url>), will trigger underline in both text blocks
|
|
||||||
// and usage sections.
|
|
||||||
//
|
|
||||||
// * Italic, Bold, and BoldItalic inline emphasis using one, two, or
|
|
||||||
// three stars respectively will be observed and cannot be intermixed
|
|
||||||
// or intra-word. Each opener must be preceded by a UNICODE space (or
|
|
||||||
// nothing) and followed by a non-space rune. Each closer must be
|
|
||||||
// preceded by a non-space rune and followed by a UNICODE space (or
|
|
||||||
// nothing).
|
|
||||||
//
|
|
||||||
// For historic reasons the following environment variables will be
|
|
||||||
// observed if found (and also provide color support for the less pager
|
|
||||||
// utility):
|
|
||||||
//
|
|
||||||
// * Italic LESS_TERMCAP_so
|
|
||||||
// * Bold LESS_TERMCAP_md
|
|
||||||
// * BoldItalic LESS_TERMCAP_mb
|
|
||||||
// * Underline LESS_TERMCAP_us
|
|
||||||
//
|
|
||||||
func FormatWrapped(input string, indent, width int) (output string) {
|
|
||||||
|
|
||||||
// this scanner could be waaaay more lexy
|
|
||||||
// but suits the need and clear to read
|
|
||||||
|
|
||||||
var strip int
|
|
||||||
var blockbuf string
|
|
||||||
|
|
||||||
// standard state machine approach
|
|
||||||
inblock := false
|
|
||||||
inraw := false
|
|
||||||
inhard := false
|
|
||||||
gotindent := false
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
txt := scanner.Text()
|
|
||||||
trimmed := strings.TrimSpace(txt)
|
|
||||||
|
|
||||||
// ignore blank lines
|
|
||||||
if !(inraw || inblock) && len(trimmed) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// infer the indent to strip for every line
|
|
||||||
if !gotindent && len(trimmed) > 0 {
|
|
||||||
for i, v := range txt {
|
|
||||||
if v != ' ' {
|
|
||||||
strip = i
|
|
||||||
gotindent = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip convenience indent
|
|
||||||
if len(txt) >= strip {
|
|
||||||
txt = txt[strip:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw block start
|
|
||||||
if !inblock && !inraw && len(txt) > 4 && txt[0:4] == " " && len(trimmed) > 0 {
|
|
||||||
inraw = true
|
|
||||||
output += "\n\n" + txt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// in raw block
|
|
||||||
if inraw && len(txt) > 4 {
|
|
||||||
output += "\n" + txt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw block end
|
|
||||||
if inraw && len(trimmed) == 0 {
|
|
||||||
inraw = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// another block line, join it
|
|
||||||
if inblock && len(trimmed) > 0 {
|
|
||||||
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
|
|
||||||
inhard = true
|
|
||||||
}
|
|
||||||
space := " "
|
|
||||||
if inhard {
|
|
||||||
space = "\n"
|
|
||||||
}
|
|
||||||
blockbuf += space + trimmed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// beginning of a new block
|
|
||||||
if !inblock && len(trimmed) > 0 {
|
|
||||||
inhard = false
|
|
||||||
inblock = true
|
|
||||||
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
|
|
||||||
inhard = true
|
|
||||||
}
|
|
||||||
blockbuf = trimmed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// end block
|
|
||||||
if inblock && len(trimmed) == 0 {
|
|
||||||
inblock = false
|
|
||||||
output += "\n\n" + Format(Wrap(blockbuf, width-strip-4))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush last block
|
|
||||||
if inblock {
|
|
||||||
output += "\n\n" + Format(Wrap(blockbuf, width-strip-4))
|
|
||||||
}
|
|
||||||
output = Indent(strings.TrimSpace(output), indent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format replaces minimal Markdown-like syntax with *Italic*,
|
|
||||||
// **Bold**, ***BoldItalic***, and <bracketed>
|
|
||||||
func Format(buf string) string {
|
|
||||||
// TODO add functional parser, for fun
|
|
||||||
log.Println("would Format")
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indent indents each line the set number of spaces.
|
|
||||||
func Indent(buf string, spaces int) string {
|
|
||||||
nbuf := ""
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(buf))
|
|
||||||
scanner.Scan()
|
|
||||||
for n := 0; n < spaces; n++ {
|
|
||||||
nbuf += " "
|
|
||||||
}
|
|
||||||
nbuf += scanner.Text()
|
|
||||||
for scanner.Scan() {
|
|
||||||
nbuf += "\n"
|
|
||||||
for n := 0; n < spaces; n++ {
|
|
||||||
nbuf += " "
|
|
||||||
}
|
|
||||||
nbuf += scanner.Text()
|
|
||||||
}
|
|
||||||
return nbuf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapped is the same as Format but without any emphasis.
|
|
||||||
func Wrapped(input string, indent, width int) (output string) {
|
|
||||||
|
|
||||||
// this scanner could be waaaay more lexy
|
|
||||||
// but suits the need and clear to read
|
|
||||||
|
|
||||||
var strip int
|
|
||||||
var blockbuf string
|
|
||||||
|
|
||||||
// standard state machine approach
|
|
||||||
inblock := false
|
|
||||||
inraw := false
|
|
||||||
inhard := false
|
|
||||||
gotindent := false
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
txt := scanner.Text()
|
|
||||||
trimmed := strings.TrimSpace(txt)
|
|
||||||
|
|
||||||
// ignore blank lines
|
|
||||||
if !(inraw || inblock) && len(trimmed) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// infer the indent to strip for every line
|
|
||||||
if !gotindent && len(trimmed) > 0 {
|
|
||||||
for i, v := range txt {
|
|
||||||
if v != ' ' {
|
|
||||||
strip = i
|
|
||||||
gotindent = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip convenience indent
|
|
||||||
if len(txt) >= strip {
|
|
||||||
txt = txt[strip:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw block start
|
|
||||||
if !inblock && !inraw && len(txt) > 4 && txt[0:4] == " " && len(trimmed) > 0 {
|
|
||||||
inraw = true
|
|
||||||
output += "\n\n" + txt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// in raw block
|
|
||||||
if inraw && len(txt) > 4 {
|
|
||||||
output += "\n" + txt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw block end
|
|
||||||
if inraw && len(trimmed) == 0 {
|
|
||||||
inraw = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// another block line, join it
|
|
||||||
if inblock && len(trimmed) > 0 {
|
|
||||||
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
|
|
||||||
inhard = true
|
|
||||||
}
|
|
||||||
space := " "
|
|
||||||
if inhard {
|
|
||||||
space = "\n"
|
|
||||||
}
|
|
||||||
blockbuf += space + trimmed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// beginning of a new block
|
|
||||||
if !inblock && len(trimmed) > 0 {
|
|
||||||
inhard = false
|
|
||||||
inblock = true
|
|
||||||
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
|
|
||||||
inhard = true
|
|
||||||
}
|
|
||||||
blockbuf = trimmed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// end block
|
|
||||||
if inblock && len(trimmed) == 0 {
|
|
||||||
inblock = false
|
|
||||||
output += "\n\n" + Wrap(blockbuf, width-strip-4)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush last block
|
|
||||||
if inblock {
|
|
||||||
output += "\n\n" + Wrap(blockbuf, width-strip-4)
|
|
||||||
}
|
|
||||||
output = Indent(strings.TrimSpace(output), indent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// peekWord returns the runes up to the next space.
|
|
||||||
func peekWord(buf []rune, start int) []rune {
|
|
||||||
word := []rune{}
|
|
||||||
for _, r := range buf[start:] {
|
|
||||||
if unicode.IsSpace(r) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
word = append(word, r)
|
|
||||||
}
|
|
||||||
return word
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap wraps the string to the given width using spaces to separate
|
|
||||||
// words. If passed a negative width will effectively join all words in
|
|
||||||
// the buffer into a single line with no wrapping.
|
|
||||||
func Wrap(buf string, width int) string {
|
|
||||||
if width == 0 {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
nbuf := ""
|
|
||||||
curwidth := 0
|
|
||||||
for i, r := range []rune(buf) {
|
|
||||||
// hard breaks always as is
|
|
||||||
if r == '\n' {
|
|
||||||
nbuf += "\n"
|
|
||||||
curwidth = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if unicode.IsSpace(r) {
|
|
||||||
// FIXME: don't peek every word, only after passed width
|
|
||||||
// change the space to a '\n' in the buffer slice directly
|
|
||||||
next := peekWord([]rune(buf), i+1)
|
|
||||||
if width > 0 && (curwidth+len(next)+1) > width {
|
|
||||||
nbuf += "\n"
|
|
||||||
curwidth = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nbuf += string(r)
|
|
||||||
curwidth++
|
|
||||||
}
|
|
||||||
return nbuf
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
package mark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormat(t *testing.T) {
|
|
||||||
want := []string{
|
|
||||||
OpenItalic + "Italic" + CloseItalic,
|
|
||||||
OpenBold + "Bold" + CloseBold,
|
|
||||||
OpenBoldItalic + "BoldItalic" + CloseBoldItalic,
|
|
||||||
"<" + OpenUnderline + "bracketed" + CloseUnderline + ">",
|
|
||||||
}
|
|
||||||
args := []string{"*Italic*", "**Bold**", "***BoldItalic***", "<bracketed>"}
|
|
||||||
for i, arg := range args {
|
|
||||||
t.Logf("testing: %v\n", arg)
|
|
||||||
got := Format(arg)
|
|
||||||
if got != want[i] {
|
|
||||||
t.Errorf("\nwant: %q\ngot: %q\n", want[i], got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWrapped(t *testing.T) {
|
|
||||||
text := `
|
|
||||||
Something *easy* to write here that can be indented however you like
|
|
||||||
and wrapped and have each line indented and with <code>:
|
|
||||||
|
|
||||||
This will not be messed with.
|
|
||||||
Nor this.
|
|
||||||
|
|
||||||
So it's a lot like a **simple** version of Markdown that only supports
|
|
||||||
what is likely going to be used in stuff similar to man pages.
|
|
||||||
|
|
||||||
Let's try a hard
|
|
||||||
return.`
|
|
||||||
|
|
||||||
want := " Something " + OpenItalic + "easy" + CloseItalic + " to write here that can be indented however\n you like and wrapped and have each line indented and with\n <" + OpenUnderline + "code" + CloseUnderline + ">:\n \n This will not be messed with.\n Nor this.\n \n So it's a lot like a " + OpenBold + "simple" + CloseBold + " version of Markdown that only\n supports what is likely going to be used in stuff similar to\n man pages.\n \n Let's try a hard\n return."
|
|
||||||
|
|
||||||
got := FormatWrapped(text, 5, 70)
|
|
||||||
t.Log("\n" + got)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("\nwant:\n%q\ngot:\n%q\n", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapped(t *testing.T) {
|
|
||||||
text := `
|
|
||||||
Something *easy* to write here that can be indented however you like
|
|
||||||
and wrapped and have each line indented and with <code>:
|
|
||||||
|
|
||||||
This will not be messed with.
|
|
||||||
Nor this.
|
|
||||||
|
|
||||||
So it's a lot like a **simple** version of Markdown that only supports
|
|
||||||
what is likely going to be used in stuff similar to man pages.
|
|
||||||
|
|
||||||
Let's try a hard
|
|
||||||
return.`
|
|
||||||
|
|
||||||
want := " Something *easy* to write here that can be indented however\n you like and wrapped and have each line indented and with\n <code>:\n \n This will not be messed with.\n Nor this.\n \n So it's a lot like a **simple** version of Markdown that only\n supports what is likely going to be used in stuff similar to\n man pages.\n \n Let's try a hard\n return."
|
|
||||||
|
|
||||||
got := Wrapped(text, 5, 70)
|
|
||||||
t.Log("\n" + got)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("\nwant:\n%q\ngot:\n%q\n", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeekWord(t *testing.T) {
|
|
||||||
var buf []rune
|
|
||||||
var word string
|
|
||||||
buf = []rune(`some thing`)
|
|
||||||
word = string(peekWord(buf, 0))
|
|
||||||
t.Logf("%q", word)
|
|
||||||
if word != "some" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
word = string(peekWord(buf, 5))
|
|
||||||
t.Logf("%q", word)
|
|
||||||
if word != "thing" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
word = string(peekWord(buf, 4))
|
|
||||||
t.Logf("%q", word)
|
|
||||||
if word != "" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrap(t *testing.T) {
|
|
||||||
buf := "Here's a string that's not long."
|
|
||||||
want := "Here's a\nstring\nthat's not\nlong."
|
|
||||||
got := Wrap(buf, 10)
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("\nwant: %q\ngot: %q\n", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrap_none(t *testing.T) {
|
|
||||||
if Wrap("some thing", 0) != "some thing" {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package ast
|
|
@ -1,36 +0,0 @@
|
|||||||
# BonzaiMark (v1.0.0-alpha) mark.bonzai.dev
|
|
||||||
# Copyright 2022 Robert S. Muhlestein
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
# <-- "contains nodes"
|
|
||||||
# <- "does not contain nodes"
|
|
||||||
|
|
||||||
# Must be parsed in three passes:
|
|
||||||
#
|
|
||||||
# 1. Unindent every line
|
|
||||||
# 1. Chop into blocks
|
|
||||||
# 1. Parse each block
|
|
||||||
|
|
||||||
Indented <-- FirstLine Lines*
|
|
||||||
FirstLine <- Indentation (!EndLine .)*
|
|
||||||
_Indent <-- # number of spaces before first nongraphic
|
|
||||||
|
|
||||||
|
|
||||||
Block <-- Bulleted / Numbered / Paragraph / Verbatim
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EndBlock <- EndLine{2,} / EOD
|
|
||||||
Paragraph <-- (!EndBlock <Span>)+ EndBlock
|
|
||||||
Bulleted <-- (!EndLine '*' SP <Span*>)+
|
|
||||||
Numbered <-- (!EndLine '1.' SP <Span*>)+
|
|
||||||
Verbatim <-- (!EndLine SP{4} <ugraphic*> EndLine)+
|
|
||||||
Span <- BoldItalic / Bold / Italic / Bracketed / Plain
|
|
||||||
BoldItalic <-- ('***' !ws) (!'***' ugraphic)+ (!ws '***')
|
|
||||||
Bold <-- ('**' !ws) (!'**' ugraphic)+ (!ws '**')
|
|
||||||
Italic <-- ('*' !ws) (!'*' ugraphic)+ (!ws '*')
|
|
||||||
Bracketed <-- ('<' !ws) <(!'>' ugraphic)+> (!ws '>')
|
|
||||||
Plain <-- ugraphic+
|
|
||||||
EndLine <- LF / CRLF / CR
|
|
||||||
EOD <- # end of document
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
|||||||
package mark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rwxrob/scan"
|
|
||||||
z "github.com/rwxrob/scan/is"
|
|
||||||
"github.com/rwxrob/scan/pg"
|
|
||||||
"github.com/rwxrob/structs/tree"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Parse(in any) (*tree.E[string], error) {
|
|
||||||
|
|
||||||
names := []string{
|
|
||||||
`Grammar`, // 1
|
|
||||||
`Bulleted`, // 2
|
|
||||||
`Numbered`, // ...
|
|
||||||
`Verbatim`,
|
|
||||||
`Paragraph`,
|
|
||||||
`BoldItalic`,
|
|
||||||
`Bold`,
|
|
||||||
`Italic`,
|
|
||||||
`Bracketed`,
|
|
||||||
`Plain`,
|
|
||||||
}
|
|
||||||
s := scan.New(names)
|
|
||||||
s.X(Grammar)
|
|
||||||
s.Print()
|
|
||||||
s.Tree.Root.Print()
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Grammar(s *scan.R) bool { return s.X(z.P{1, z.X{Block, pg.EndLine}}) }
|
|
||||||
|
|
||||||
func Block(s *scan.R) bool {
|
|
||||||
return s.X(z.X{z.I{Bulleted, Numbered, Paragraph, Verbatim}, EndBlock})
|
|
||||||
}
|
|
||||||
|
|
||||||
func EndBlock(s *scan.R) bool { return s.X(z.I{z.M{2, pg.EndLine}, scan.EOD}) }
|
|
||||||
|
|
||||||
func Bulleted(s *scan.R) bool {
|
|
||||||
return s.X(z.P{2, z.M1{z.X{z.N{pg.EndLine}, '*', ' ', z.M0{Span}}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Numbered(s *scan.R) bool {
|
|
||||||
return s.X(z.P{3, z.M1{z.X{z.N{pg.EndLine}, "1.", ' ', z.M0{Span}}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Verbatim(s *scan.R) bool {
|
|
||||||
return s.X(z.P{4, z.M1{z.X{z.N{pg.EndLine}, z.C{4, ' '}, z.M1{pg.UGraphic}}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Paragraph(s *scan.R) bool {
|
|
||||||
return s.X(z.P{4, z.M1{z.X{z.N{z.I{z.C{2, pg.EndLine}, scan.EOD}}, Span}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Span(s *scan.R) bool { return s.X(z.I{BoldItalic, Bold, Italic, Bracketed, Plain}) }
|
|
||||||
|
|
||||||
func BoldItalic(s *scan.R) bool {
|
|
||||||
return s.X(z.P{6, z.X{"***", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "***"}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Bold(s *scan.R) bool {
|
|
||||||
return s.X(z.P{7, z.X{"**", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "**"}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Italic(s *scan.R) bool {
|
|
||||||
return s.X(z.P{8, z.X{"*", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "*"}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Bracketed(s *scan.R) bool {
|
|
||||||
//return s.X(z.P{9, z.X{'<', z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, '>'}})
|
|
||||||
return s.X('<', z.P{9, z.X{z.M1{z.X{z.N{'>'}, pg.UGraphic}}}}, '>')
|
|
||||||
}
|
|
||||||
|
|
||||||
func Plain(s *scan.R) bool { return s.X(z.P{10, z.M1{pg.UGraphic}}) }
|
|
@ -1,28 +0,0 @@
|
|||||||
package mark_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rwxrob/bonzai/mark"
|
|
||||||
"github.com/rwxrob/scan"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExamplePlain() {
|
|
||||||
s := scan.New("some thing here")
|
|
||||||
//s.TraceX()
|
|
||||||
s.X(mark.Plain)
|
|
||||||
s.Print()
|
|
||||||
s.Tree.Root.Print()
|
|
||||||
// Output:
|
|
||||||
// <EOD>
|
|
||||||
// {"T":1,"N":[{"T":10,"V":"some thing here"}]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleBracketed() {
|
|
||||||
s := scan.New("<thing>")
|
|
||||||
s.TraceX()
|
|
||||||
s.X(mark.Bracketed)
|
|
||||||
s.Print()
|
|
||||||
s.Tree.Root.Print()
|
|
||||||
// Output:
|
|
||||||
// <EOD>
|
|
||||||
// {"T":1,"N":[{"T":10,"V":"some thing here"}]}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package scan
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogPanic is used with defer to trap any panic and log it.
|
|
||||||
func (s *R) LogPanic() {
|
|
||||||
r := recover()
|
|
||||||
if r != nil {
|
|
||||||
log.Printf("%v at %v", r, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogPanic is used with defer to trap any panic and print it.
|
|
||||||
func (s *R) PrintPanic() {
|
|
||||||
r := recover()
|
|
||||||
if r != nil {
|
|
||||||
fmt.Printf("%v at %v", r, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------- PEGN support ---------------------------
|
|
||||||
// The following are directly correlated to supported PEGN expressions
|
|
||||||
// and are intended to be generated from PEGN grammars specifically
|
|
||||||
// (altough other languages generators could easily be adapted).
|
|
||||||
|
|
||||||
// Rune matches the exact rune specified or panics.
|
|
||||||
func (s *R) Rune(r rune) {
|
|
||||||
if s.Cur.Rune != r {
|
|
||||||
panic(fmt.Sprintf("expected %q", r))
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Str iterates over all of the runes in the string as if Rune were
|
|
||||||
// called on each.
|
|
||||||
func (s *R) Str(v string) {
|
|
||||||
for _, v := range []rune(v) {
|
|
||||||
if v != s.Cur.Rune {
|
|
||||||
panic(fmt.Sprintf("expected %q", v))
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package scan_test
|
|
||||||
|
|
||||||
import "github.com/rwxrob/scan"
|
|
||||||
|
|
||||||
func ExampleR_Any() {
|
|
||||||
s, _ := scan.New("so")
|
|
||||||
s.Print()
|
|
||||||
s.Any(2)
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// <EOD>
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleR_Rune() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
//s.Any(3)
|
|
||||||
s.Rune('s')
|
|
||||||
s.Rune('o')
|
|
||||||
s.Rune('m')
|
|
||||||
s.Print() // same as Any(3)
|
|
||||||
// Output:
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleR_Rune_fail() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
defer s.PrintPanic()
|
|
||||||
s.Rune('s')
|
|
||||||
s.Rune('o')
|
|
||||||
s.Rune('m')
|
|
||||||
s.Rune('e')
|
|
||||||
s.Rune('\t')
|
|
||||||
// Output:
|
|
||||||
// expected '\t' at U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleR_Str() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Str("som")
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleR_Str_fail() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
defer s.PrintPanic()
|
|
||||||
s.Str("some\t")
|
|
||||||
// Output:
|
|
||||||
// expected '\t' at U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
@ -1,549 +0,0 @@
|
|||||||
package scan
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// Parse creates a new Node reference from the string returned by Look.
|
|
||||||
func (s *R) Parse(to *Cur) *Node {
|
|
||||||
n := new(Node)
|
|
||||||
n.V = s.Look(to)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseSlice creates a new Node reference from the string returned by
|
|
||||||
// LookSlice.
|
|
||||||
func (s *R) ParseSlice(b *Cur, e *Cur) *Node {
|
|
||||||
n := new(Node)
|
|
||||||
n.V = s.LookSlice(b, e)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Expect method is primarily designed for ease of use quickly by
|
|
||||||
// allowing BPEGN expressions to be coded much faster than traditional
|
|
||||||
// parser functions. In fact, entire applications can be generated
|
|
||||||
// automatically from BPEGN, PEGN, ABNF, EBNF, or any other meta syntax
|
|
||||||
// language by adding additional scan.Hooks to do work besides just
|
|
||||||
// parsing what is scanned. Generate the BPEGN Go expressions to pass to
|
|
||||||
// Expect and code the hook callbacks. That's it. Coding in BPEGN comes
|
|
||||||
// naturally when putting together a parser quickly and without the
|
|
||||||
// cognitive overhead of switching between grammars. The BPEGN
|
|
||||||
// expressions passed to Expect are 100% Go. For full documentation of
|
|
||||||
// BPEGN expressions see the z ("is") package and the source of this
|
|
||||||
// Expect method.
|
|
||||||
//
|
|
||||||
// Warning: While it is nothing for most developers to be concerned
|
|
||||||
// with, the Expect method does do a fair amount of functional recursion
|
|
||||||
// for the sake of simplicity and to support BPEGN syntax. Those wishing
|
|
||||||
// to gain the maximum performance should consider using other scan.R
|
|
||||||
// methods instead in such cases. Developers are encouraged to do their
|
|
||||||
// own benchmarking and perhaps start with BPEGN until they can create
|
|
||||||
// more optimized parsers when and if necessary. Most will discover
|
|
||||||
// other more substantial bottlenecks. The Bonzai project places
|
|
||||||
// priority on speed and quality of developer delivery over run-time
|
|
||||||
// performance. Delivery time is far more costly than the minimal gains
|
|
||||||
// in run-time performance. "Premature optimization is the root of all
|
|
||||||
// evil," as they say.
|
|
||||||
func (s *R) Expect(expr any) (*Cur, error) {
|
|
||||||
s.Last = s.Cur
|
|
||||||
|
|
||||||
// please keep the most common expressions types at the top
|
|
||||||
|
|
||||||
switch v := expr.(type) {
|
|
||||||
|
|
||||||
case rune: // ------------------------------------------------------
|
|
||||||
if s.Cur.Rune != v {
|
|
||||||
err := s.ErrorExpected(v)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case tk.Token: // --------------------------------------------------
|
|
||||||
switch v {
|
|
||||||
case tk.ANY: // A, A1
|
|
||||||
s.Scan()
|
|
||||||
case tk.A2:
|
|
||||||
s.ScanN(2)
|
|
||||||
case tk.A3:
|
|
||||||
s.ScanN(3)
|
|
||||||
case tk.A4:
|
|
||||||
s.ScanN(4)
|
|
||||||
case tk.A5:
|
|
||||||
s.ScanN(5)
|
|
||||||
case tk.A6:
|
|
||||||
s.ScanN(6)
|
|
||||||
case tk.A7:
|
|
||||||
s.ScanN(7)
|
|
||||||
case tk.A8:
|
|
||||||
s.ScanN(8)
|
|
||||||
case tk.A9:
|
|
||||||
s.ScanN(9)
|
|
||||||
}
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case string: // ----------------------------------------------------
|
|
||||||
if v == "" {
|
|
||||||
return s.Mark(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid the temptation to look directly at bytes since it would
|
|
||||||
// allow none runes to be passed within "strings"
|
|
||||||
for _, v := range []rune(v) {
|
|
||||||
if v != s.Cur.Rune {
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case z.X: // -----------------------------------------------------
|
|
||||||
var err error
|
|
||||||
b := s.Mark()
|
|
||||||
m := s.Mark()
|
|
||||||
for _, i := range v {
|
|
||||||
m, err = s.Expect(i)
|
|
||||||
if err != nil {
|
|
||||||
s.Jump(b)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case z.O: // -------------------------------------------------------
|
|
||||||
for _, i := range v {
|
|
||||||
m, _ := s.Expect(i)
|
|
||||||
if m != nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("scanner: unsupported input type: %t", i)
|
|
||||||
}
|
|
||||||
s.BufLen = len(s.Buf)
|
|
||||||
if s.BufLen == 0 {
|
|
||||||
return fmt.Errorf("scanner: no input")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// Parse creates a new Node reference from the string returned by Look.
|
|
||||||
func (s *R) Parse(to *Cur) *Node {
|
|
||||||
n := new(Node)
|
|
||||||
n.V = s.Look(to)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseSlice creates a new Node reference from the string returned by
|
|
||||||
// LookSlice.
|
|
||||||
func (s *R) ParseSlice(b *Cur, e *Cur) *Node {
|
|
||||||
n := new(Node)
|
|
||||||
n.V = s.LookSlice(b, e)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look returns a string containing all the bytes from the current
|
|
||||||
// scanner cursor position ahead or behind to the passed cursor
|
|
||||||
// position. Neither the internal nor the passed cursor position is
|
|
||||||
// changed. Also see Peek and LookSlice.
|
|
||||||
func (s *R) Look(to *Cur) string {
|
|
||||||
if to.Byte < s.Cur.Byte {
|
|
||||||
return string(s.Buf[to.Byte:s.Cur.Next])
|
|
||||||
}
|
|
||||||
return string(s.Buf[s.Cur.Byte:to.Next])
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookSlice returns a string containing all the bytes from the first
|
|
||||||
// cursor up to the second cursor. Neither cursor position is changed.
|
|
||||||
func (s *R) LookSlice(beg *Cur, end *Cur) string {
|
|
||||||
return string(s.Buf[beg.Byte:end.Next])
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Expect method is primarily designed for ease of use quickly by
|
|
||||||
// allowing BPEGN expressions to be coded much faster than traditional
|
|
||||||
// parser functions. In fact, entire applications can be generated
|
|
||||||
// automatically from BPEGN, PEGN, ABNF, EBNF, or any other meta syntax
|
|
||||||
// language by adding additional scan.Hooks to do work besides just
|
|
||||||
// parsing what is scanned. Generate the BPEGN Go expressions to pass to
|
|
||||||
// Expect and code the hook callbacks. That's it. Coding in BPEGN comes
|
|
||||||
// naturally when putting together a parser quickly and without the
|
|
||||||
// cognitive overhead of switching between grammars. The BPEGN
|
|
||||||
// expressions passed to Expect are 100% Go. For full documentation of
|
|
||||||
// BPEGN expressions see the z ("is") package and the source of this
|
|
||||||
// Expect method.
|
|
||||||
//
|
|
||||||
// Warning: While it is nothing for most developers to be concerned
|
|
||||||
// with, the Expect method does do a fair amount of functional recursion
|
|
||||||
// for the sake of simplicity and to support BPEGN syntax. Those wishing
|
|
||||||
// to gain the maximum performance should consider using other scan.R
|
|
||||||
// methods instead in such cases. Developers are encouraged to do their
|
|
||||||
// own benchmarking and perhaps start with BPEGN until they can create
|
|
||||||
// more optimized parsers when and if necessary. Most will discover
|
|
||||||
// other more substantial bottlenecks. The Bonzai project places
|
|
||||||
// priority on speed and quality of developer delivery over run-time
|
|
||||||
// performance. Delivery time is far more costly than the minimal gains
|
|
||||||
// in run-time performance. "Premature optimization is the root of all
|
|
||||||
// evil," as they say.
|
|
||||||
func (s *R) Expect(expr any) (*Cur, error) {
|
|
||||||
s.Last = s.Cur
|
|
||||||
|
|
||||||
// please keep the most common expressions types at the top
|
|
||||||
|
|
||||||
switch v := expr.(type) {
|
|
||||||
|
|
||||||
case rune: // ------------------------------------------------------
|
|
||||||
if s.Cur.Rune != v {
|
|
||||||
err := s.ErrorExpected(v)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case tk.Token: // --------------------------------------------------
|
|
||||||
switch v {
|
|
||||||
case tk.ANY: // A, A1
|
|
||||||
s.Scan()
|
|
||||||
case tk.A2:
|
|
||||||
s.ScanN(2)
|
|
||||||
case tk.A3:
|
|
||||||
s.ScanN(3)
|
|
||||||
case tk.A4:
|
|
||||||
s.ScanN(4)
|
|
||||||
case tk.A5:
|
|
||||||
s.ScanN(5)
|
|
||||||
case tk.A6:
|
|
||||||
s.ScanN(6)
|
|
||||||
case tk.A7:
|
|
||||||
s.ScanN(7)
|
|
||||||
case tk.A8:
|
|
||||||
s.ScanN(8)
|
|
||||||
case tk.A9:
|
|
||||||
s.ScanN(9)
|
|
||||||
}
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case string: // ----------------------------------------------------
|
|
||||||
if v == "" {
|
|
||||||
return s.Mark(), nil
|
|
||||||
}
|
|
||||||
// avoid the temptation to look directly at bytes since it would
|
|
||||||
// allow none runes to be passed within "strings"
|
|
||||||
for _, v := range []rune(v) {
|
|
||||||
if v != s.Cur.Rune {
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case z.X: // -----------------------------------------------------
|
|
||||||
var err error
|
|
||||||
b := s.Mark()
|
|
||||||
m := s.Mark()
|
|
||||||
for _, i := range v {
|
|
||||||
m, err = s.Expect(i)
|
|
||||||
if err != nil {
|
|
||||||
s.Jump(b)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case z.O: // -------------------------------------------------------
|
|
||||||
for _, i := range v {
|
|
||||||
m, _ := s.Expect(i)
|
|
||||||
if m != nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case z.Ti: // -----------------------------------------------------
|
|
||||||
back := s.Mark()
|
|
||||||
for s.Cur.Rune != tk.EOD {
|
|
||||||
for _, i := range v {
|
|
||||||
m, _ := s.Expect(i)
|
|
||||||
if m != nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
s.Jump(back)
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
|
|
||||||
case z.T: // -----------------------------------------------------
|
|
||||||
m := s.Mark()
|
|
||||||
b4 := s.Mark()
|
|
||||||
for s.Cur.Rune != tk.EOD {
|
|
||||||
for _, i := range v {
|
|
||||||
b := s.Mark()
|
|
||||||
c, _ := s.Expect(i)
|
|
||||||
if c != nil {
|
|
||||||
s.Jump(b)
|
|
||||||
return b4, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b4 = s.Mark()
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
s.Jump(m)
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
|
|
||||||
case z.Y: // ----------------------------------------------------
|
|
||||||
var m *Cur
|
|
||||||
b := s.Mark()
|
|
||||||
for _, i := range v {
|
|
||||||
m, _ = s.Expect(i)
|
|
||||||
if m != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m == nil {
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
s.Jump(b)
|
|
||||||
return b, nil
|
|
||||||
|
|
||||||
case z.N: // ----------------------------------------------------
|
|
||||||
m := s.Mark()
|
|
||||||
for _, i := range v {
|
|
||||||
if c, _ := s.Expect(i); c != nil {
|
|
||||||
s.Jump(m)
|
|
||||||
err := s.ErrorExpected(v, i)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case z.I: // -----------------------------------------------------
|
|
||||||
var m *Cur
|
|
||||||
for _, i := range v {
|
|
||||||
var err error
|
|
||||||
last := s.Mark()
|
|
||||||
m, err = s.Expect(i)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s.Jump(last)
|
|
||||||
}
|
|
||||||
if m == nil {
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case z.MM: // ----------------------------------------------------
|
|
||||||
c := 0
|
|
||||||
last := s.Mark()
|
|
||||||
var err error
|
|
||||||
var m, end *Cur
|
|
||||||
for {
|
|
||||||
m, err = s.Expect(v.This)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
last = m
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
if c == 0 && v.Min == 0 {
|
|
||||||
if end == nil {
|
|
||||||
end = last
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !(v.Min <= c && c <= v.Max) {
|
|
||||||
s.Jump(last)
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
return end, nil
|
|
||||||
|
|
||||||
case z.M1: // ----------------------------------------------------
|
|
||||||
m := s.Mark()
|
|
||||||
c, err := s.Expect(z.M{1, v.This})
|
|
||||||
if err != nil {
|
|
||||||
s.Jump(m)
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
|
|
||||||
case z.M: // ----------------------------------------------------
|
|
||||||
c := 0
|
|
||||||
last := s.Mark()
|
|
||||||
var err error
|
|
||||||
var m *Cur
|
|
||||||
for {
|
|
||||||
m, err = s.Expect(v.This)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
last = m
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
if c < v.Min {
|
|
||||||
s.Jump(last)
|
|
||||||
return nil, s.ErrorExpected(v)
|
|
||||||
}
|
|
||||||
return last, nil
|
|
||||||
|
|
||||||
case z.A: // ----------------------------------------------------
|
|
||||||
for n := 0; n < v.N; n++ {
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
return s.Last, nil
|
|
||||||
// see rune for A2-9
|
|
||||||
|
|
||||||
case z.R: // ----------------------------------------------------
|
|
||||||
if !(v.First <= s.Cur.Rune && s.Cur.Rune <= v.Last) {
|
|
||||||
err := s.ErrorExpected(v)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
return s.Last, nil
|
|
||||||
|
|
||||||
case z.C: // ------------------------------------------------------
|
|
||||||
return s.expcount(v.N, v.This)
|
|
||||||
case z.C2:
|
|
||||||
return s.expcount(2, v.This)
|
|
||||||
case z.C3:
|
|
||||||
return s.expcount(3, v.This)
|
|
||||||
case z.C4:
|
|
||||||
return s.expcount(4, v.This)
|
|
||||||
case z.C5:
|
|
||||||
return s.expcount(5, v.This)
|
|
||||||
case z.C6:
|
|
||||||
return s.expcount(6, v.This)
|
|
||||||
case z.C7:
|
|
||||||
return s.expcount(7, v.This)
|
|
||||||
case z.C8:
|
|
||||||
return s.expcount(8, v.This)
|
|
||||||
case z.C9:
|
|
||||||
return s.expcount(9, v.This)
|
|
||||||
|
|
||||||
case Hook: // ------------------------------------------------------
|
|
||||||
if err := v(s); err != nil {
|
|
||||||
return nil, s.ErrorExpected(v, err)
|
|
||||||
}
|
|
||||||
return s.Cur, nil
|
|
||||||
|
|
||||||
case func(r *R) error:
|
|
||||||
if err := v(s); err != nil {
|
|
||||||
return nil, s.ErrorExpected(v, err)
|
|
||||||
}
|
|
||||||
return s.Cur, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unknown expression (%T)", expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handles all C* expressions
|
|
||||||
func (s *R) expcount(n int, expr any) (*Cur, error) {
|
|
||||||
b := s.Mark()
|
|
||||||
m := s.Mark()
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
m, _ := s.Expect(expr)
|
|
||||||
if m == nil {
|
|
||||||
s.Jump(b)
|
|
||||||
return nil, s.ErrorExpected(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorExpected returns a verbose, one-line error describing what was
|
|
||||||
// expected when it encountered whatever the scanner last scanned. All
|
|
||||||
// expression types are supported. See Expect.
|
|
||||||
func (s *R) ErrorExpected(this any, args ...any) error {
|
|
||||||
var msg string
|
|
||||||
but := fmt.Sprintf(` at %v`, s)
|
|
||||||
if s.Cur != nil && s.Cur.Rune == tk.EOD && s.Cur.Len == 0 {
|
|
||||||
runes := `runes`
|
|
||||||
if s.Cur.Pos.Rune == 1 {
|
|
||||||
runes = `rune`
|
|
||||||
}
|
|
||||||
but = fmt.Sprintf(`, exceeded data length (%v %v)`,
|
|
||||||
s.Cur.Pos.Rune, runes)
|
|
||||||
}
|
|
||||||
switch v := this.(type) {
|
|
||||||
case rune: // otherwise will use uint32
|
|
||||||
msg = fmt.Sprintf(`expected rune %q`, v)
|
|
||||||
case z.Y:
|
|
||||||
if len(v) > 1 {
|
|
||||||
msg = fmt.Sprintf(`expected one of %q`, v)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf(`expected %q`, v[0])
|
|
||||||
}
|
|
||||||
case z.N:
|
|
||||||
msg = fmt.Sprintf(`unexpected %q`, args[0])
|
|
||||||
case z.I:
|
|
||||||
str := `expected one of %q`
|
|
||||||
msg = fmt.Sprintf(str, v)
|
|
||||||
case z.X:
|
|
||||||
//str := `expected %q in sequence %q at %v beginning`
|
|
||||||
//msg = fmt.Sprintf(str, args[0], v, args[1])
|
|
||||||
str := `expected %q in sequence`
|
|
||||||
msg = fmt.Sprintf(str, v)
|
|
||||||
case z.O:
|
|
||||||
str := `expected an optional %v`
|
|
||||||
msg = fmt.Sprintf(str, v)
|
|
||||||
case z.M1:
|
|
||||||
str := `expected one or more %q`
|
|
||||||
msg = fmt.Sprintf(str, v.This)
|
|
||||||
case z.M:
|
|
||||||
str := `expected min %v of %q`
|
|
||||||
msg = fmt.Sprintf(str, v.Min, v.This)
|
|
||||||
case z.MM:
|
|
||||||
str := `expected min %v, max %v of %q`
|
|
||||||
msg = fmt.Sprintf(str, v.Min, v.Max, v.This)
|
|
||||||
case z.R:
|
|
||||||
str := `expected range [%v-%v]`
|
|
||||||
msg = fmt.Sprintf(str, string(v.First), string(v.Last))
|
|
||||||
case z.Ti:
|
|
||||||
str := `%q not found`
|
|
||||||
if len(v) > 1 {
|
|
||||||
str = `none of %q found`
|
|
||||||
}
|
|
||||||
msg = fmt.Sprintf(str, v)
|
|
||||||
case z.T:
|
|
||||||
msg = fmt.Sprintf(`none of %q found`, v)
|
|
||||||
case Hook:
|
|
||||||
msg = fmt.Sprintf("%v: %v", strings.ToLower(util.FuncName(v)), args[0])
|
|
||||||
case func(s *R) error:
|
|
||||||
msg = fmt.Sprintf("%v: %v", strings.ToLower(util.FuncName(v)), args[0])
|
|
||||||
default:
|
|
||||||
msg = fmt.Sprintf(`expected %T %q`, v, v)
|
|
||||||
}
|
|
||||||
return errors.New(msg + but)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------- new functions --------------------------
|
|
||||||
|
|
||||||
func (s *R) Any(n int) bool {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *R) Str(strs ...string) {
|
|
||||||
for _, it := range strs {
|
|
||||||
for _, r := range []rune(it) {
|
|
||||||
if r != s.Cur.Rune {
|
|
||||||
panic(fmt.Sprintf("expecting %q", r))
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *R) Opt(strs ...string) {
|
|
||||||
s.Snap()
|
|
||||||
OUT:
|
|
||||||
for _, it := range strs {
|
|
||||||
for _, r := range []rune(it) {
|
|
||||||
if r != s.Cur.Rune {
|
|
||||||
s.Back()
|
|
||||||
continue OUT
|
|
||||||
}
|
|
||||||
s.Scan()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,619 +0,0 @@
|
|||||||
package scan_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/rwxrob/bonzai/scan"
|
|
||||||
z "github.com/rwxrob/bonzai/scan/is"
|
|
||||||
"github.com/rwxrob/bonzai/scan/tk"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
func ExampleNew_string() {
|
|
||||||
s, err := scan.New("some thing")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
s.Print()
|
|
||||||
s.ScanN(3)
|
|
||||||
s.Print()
|
|
||||||
//Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNew_bytes() {
|
|
||||||
s, err := scan.New([]byte{'s', 'o', 'm'})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
s.Print()
|
|
||||||
s.ScanN(3)
|
|
||||||
s.Print()
|
|
||||||
//Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// <EOD>
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNew_reader() {
|
|
||||||
r := strings.NewReader("some thing")
|
|
||||||
s, err := scan.New(r)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
s.Print()
|
|
||||||
s.ScanN(3)
|
|
||||||
s.Print()
|
|
||||||
//Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleInit() {
|
|
||||||
s, err := scan.New("some thing")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
s.Init("other stuff entirely")
|
|
||||||
s.Print()
|
|
||||||
s.ScanN(3)
|
|
||||||
s.Print()
|
|
||||||
s.Scan()
|
|
||||||
s.Print()
|
|
||||||
//Output:
|
|
||||||
// U+006F 'o' 1,1-1 (1-1)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
// U+0072 'r' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleMark() {
|
|
||||||
s, err := scan.New("some thing")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
m := s.Mark()
|
|
||||||
fmt.Println(s.Cur != m)
|
|
||||||
// Output:
|
|
||||||
// true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleJump() {
|
|
||||||
s1, _ := scan.New("some thing")
|
|
||||||
s1.ScanN(5)
|
|
||||||
s1.Print() // t
|
|
||||||
|
|
||||||
s2, _ := scan.New("other thing")
|
|
||||||
s2.ScanN(6)
|
|
||||||
s2.Print() // t
|
|
||||||
s1.Jump(s2.Cur) // WRONG, must be same source buffer
|
|
||||||
s1.Print()
|
|
||||||
|
|
||||||
s3, _ := scan.New("some thing") // identical
|
|
||||||
s3.ScanN(6)
|
|
||||||
s3.Print() // h
|
|
||||||
s1.Jump(s3.Cur)
|
|
||||||
s1.Print()
|
|
||||||
s3.ScanN(2)
|
|
||||||
s1.Jump(s3.Cur)
|
|
||||||
s1.Print()
|
|
||||||
s3.Print()
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// U+0074 't' 1,6-6 (6-6)
|
|
||||||
// U+0074 't' 1,7-7 (7-7)
|
|
||||||
// U+0074 't' 1,7-7 (7-7)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNewLine() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Print()
|
|
||||||
s.NewLine()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 2,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func ExampleR_Expect_parse_Single_Success() {
|
|
||||||
const FOO = `FOO`
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
//c, _ := s.Expect(z.P{"some", ' ', z.I{'t', 'T'}})// FIXME
|
|
||||||
c, _ := s.Expect(z.P{FOO, "some", ' ', 't'})
|
|
||||||
c.Print() // same as "some t", points at 'h'
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0074 't' 1,6-6 (6-6)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
func ExampleR_Expect_parse_Success_One_Deep() {
|
|
||||||
const P = `PHRASE`
|
|
||||||
const S = `STRING`
|
|
||||||
const R = `RUNE`
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.P{P, z.P{S, "some"}, z.P{R, ' '}, z.P{R, 't'}})
|
|
||||||
c.Print() // same as "some t", points at 't'
|
|
||||||
//s.Print() // advances to 'h
|
|
||||||
for _, v := range s.Nodes {
|
|
||||||
fmt.Println(v)
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// U+0074 't' 1,6-6 (6-6)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
// not null
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleR_Expect_parse_Succes_Mixed() {
|
|
||||||
const P = `PHRASE`
|
|
||||||
const R = `RUNE`
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, e := s.Expect(z.P{P, "some", z.P{R, ' '}, "thing"})
|
|
||||||
fmt.Println(e)
|
|
||||||
c.Print() // same as "some t", points at 'h'
|
|
||||||
s.Print()
|
|
||||||
//s.CurNode.Print()
|
|
||||||
//fmt.Println(s.CurNode == s.Nodes[0])
|
|
||||||
// Output:
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
// {"T":"FOO","V":"some t"}
|
|
||||||
// true
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func ExampleErrorExpected() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
fmt.Println(s.ErrorExpected("foo"))
|
|
||||||
fmt.Println(s.ErrorExpected('f'))
|
|
||||||
fmt.Println(s.ErrorExpected([]byte{'f', 'o', 'o'}))
|
|
||||||
// Output:
|
|
||||||
// expected string "foo" at U+0073 's' 1,1-1 (1-1)
|
|
||||||
// expected rune 'f' at U+0073 's' 1,1-1 (1-1)
|
|
||||||
// expected []uint8 "foo" at U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_string() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect("som")
|
|
||||||
c.Print() // same as s.ScanN(2), last is 'm'
|
|
||||||
s.Print() // point to next scan 'e'
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_compound_Expr_String() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.X{"some ", "thin"})
|
|
||||||
c.Print()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
// U+0067 'g' 1,10-10 (10-10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_compound_Expr_Rune() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.X{"some", ' ', "thin"})
|
|
||||||
c.Print()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
// U+0067 'g' 1,10-10 (10-10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_it_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.Y{"some"})
|
|
||||||
c.Print() // even though true, not moved
|
|
||||||
s.Print() // scanner also not moved
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_it_Success_Middle() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.X{"some", z.Y{' '}})
|
|
||||||
c.Print() // advanced up to (but not including) ' '
|
|
||||||
s.Print() // scanner also not moved
|
|
||||||
// Output:
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_it_Fail() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
_, err := s.Expect(z.X{"some", z.Y{"thing"}})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // but scanner did get "some" so advanced
|
|
||||||
// Output:
|
|
||||||
// expected "thing" at U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_it_Fail_X() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
_, err := s.Expect(z.X{"some", z.Y{"thing"}})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // but scanner did get "some" so advanced
|
|
||||||
// Output:
|
|
||||||
// expected "thing" at U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_not_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.N{"foo"})
|
|
||||||
c.Print() // not advanced, but also not <nil>
|
|
||||||
s.Print() // not advanced at all
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_not_Fail() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
_, err := s.Expect(z.N{"some"})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // not advanced at all
|
|
||||||
// Output:
|
|
||||||
// unexpected "some" at U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_not_X_Fail() {
|
|
||||||
s, _ := scan.New("some thing wonderful")
|
|
||||||
_, err := s.Expect(z.X{z.N{'s'}, 'o'})
|
|
||||||
fmt.Println(err) // sees the s so fails
|
|
||||||
s.Print() // not advanced
|
|
||||||
// Output:
|
|
||||||
// unexpected 's' at U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_not_X_Success() {
|
|
||||||
s, _ := scan.New("some thing wonderful")
|
|
||||||
c, _ := s.Expect(z.X{z.N{`n`}, z.I{`som`}})
|
|
||||||
c.Print() // pointing to last in match 'm'
|
|
||||||
s.Print() // advanced to next after match 'e'
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_to_Success_Mid() {
|
|
||||||
s, _ := scan.New("some thing wonderful")
|
|
||||||
c, _ := s.Expect(z.T{"wo"})
|
|
||||||
c.Print() // "wo" not inc, same as "some thing ", so ' '
|
|
||||||
s.Print() // advances to 'w'
|
|
||||||
// Output:
|
|
||||||
// U+0020 ' ' 1,11-11 (11-11)
|
|
||||||
// U+0077 'w' 1,12-12 (12-12)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_avoid_Not_with_In() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Snap()
|
|
||||||
c, _ := s.Expect(z.I{z.N{'s'}, z.R{'a', 'z'}})
|
|
||||||
c.Print() // unexpected success
|
|
||||||
s.Print() // advanced to 'o'
|
|
||||||
s.Back()
|
|
||||||
// use z.X instead
|
|
||||||
_, err := s.Expect(z.X{z.N{'s'}, z.R{'a', 'z'}})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // not advanced
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// unexpected 's' at U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func ExampleExpect_seq_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.P{"some", ' ', "thin"})
|
|
||||||
c.Print() // same as "some thin", points at 'n'
|
|
||||||
s.Print() // advanced to 'g'
|
|
||||||
// Output:
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
// U+0067 'g' 1,10-10 (10-10)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func ExampleExpect_seq_Fail() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
_, err := s.Expect(z.X{"some", "thin"})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // not advanced at all
|
|
||||||
// Output:
|
|
||||||
// expected rune 't' at U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_seq_Not_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.X{"some", ` `, z.N{`T`}, "thin"})
|
|
||||||
c.Print() // same as "some thin"
|
|
||||||
s.Print() // advanced to next after ('g')
|
|
||||||
// Output:
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
// U+0067 'g' 1,10-10 (10-10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_seq_Not_Fail() {
|
|
||||||
s, _ := scan.New("some Thing")
|
|
||||||
_, err := s.Expect(z.X{"some", ' ', z.N{`T`}, "ignored"})
|
|
||||||
fmt.Println(err)
|
|
||||||
s.Print() // not advanced at all
|
|
||||||
// Output:
|
|
||||||
// unexpected "T" at U+0054 'T' 1,6-6 (6-6)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_token_ANY() {
|
|
||||||
s, _ := scan.New("some thing wonderful")
|
|
||||||
c, _ := s.Expect(tk.ANY)
|
|
||||||
c.Print() // same as `s` or s.Scan()
|
|
||||||
s.Print() // advances
|
|
||||||
c, _ = s.Expect(tk.A)
|
|
||||||
c.Print() // same as `o` or s.Scan()
|
|
||||||
s.Print() // advances
|
|
||||||
c, _ = s.Expect(tk.A1)
|
|
||||||
// we'll skip tk.A2 - tk.A8
|
|
||||||
s.Print()
|
|
||||||
c, _ = s.Expect(tk.A9)
|
|
||||||
s.Print() // should advance 9 to pos 13
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
// U+006F 'o' 1,13-13 (13-13)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_any_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.A{5})
|
|
||||||
c.Print() // same as "some "
|
|
||||||
s.Print() // advanced to next after ('t')
|
|
||||||
// Output:
|
|
||||||
// U+0074 't' 1,6-6 (6-6)
|
|
||||||
// U+0068 'h' 1,7-7 (7-7)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_o_Optional_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
//c, _ := s.Expect(z.O{"thing", "some"})
|
|
||||||
c, _ := s.Expect("some")
|
|
||||||
c.Print() // same as "some", points to 'e'
|
|
||||||
s.Print() // advances to space ' '
|
|
||||||
// Output:
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_minimum_One() {
|
|
||||||
s, _ := scan.New("sommme thing")
|
|
||||||
start := s.Mark()
|
|
||||||
s.ScanN(2)
|
|
||||||
c, _ := s.Expect(z.M1{'m'}) // goggles up all three
|
|
||||||
c.Print()
|
|
||||||
s.Print()
|
|
||||||
s.Jump(start)
|
|
||||||
c, _ = s.Expect(z.M1{'s'}) // yep, just one
|
|
||||||
c.Print()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,5-5 (5-5)
|
|
||||||
// U+0065 'e' 1,6-6 (6-6)
|
|
||||||
// U+0073 's' 1,1-1 (1-1)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_minimum() {
|
|
||||||
s, _ := scan.New("sssoommme thing")
|
|
||||||
c, _ := s.Expect(z.M{2, 's'})
|
|
||||||
c.Print() // needs 2, but will consume all three to last 's'
|
|
||||||
s.Print() // advances to next after ('o')
|
|
||||||
// Output:
|
|
||||||
// U+0073 's' 1,3-3 (3-3)
|
|
||||||
// U+006F 'o' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_mMx() {
|
|
||||||
s, _ := scan.New("sommme thing")
|
|
||||||
s.Snap()
|
|
||||||
s.ScanN(2)
|
|
||||||
s.Print()
|
|
||||||
s.Expect(z.MM{1, 3, 'm'}) // goggles up all three
|
|
||||||
s.Print()
|
|
||||||
s.Back()
|
|
||||||
s.Expect(z.MM{1, 3, 's'}) // yep, at least one
|
|
||||||
s.Print()
|
|
||||||
_, err := s.Expect(z.MM{1, 3, 'X'}) // nope
|
|
||||||
fmt.Println(err)
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,6-6 (6-6)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// expected min 1, max 3 of 'X' at U+006F 'o' 1,2-2 (2-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_c() {
|
|
||||||
s, _ := scan.New("sommme thing")
|
|
||||||
s.Snap()
|
|
||||||
s.ScanN(2)
|
|
||||||
s.Print()
|
|
||||||
s.Expect(z.C{3, 'm'}) // goggles up all three
|
|
||||||
s.Print()
|
|
||||||
s.Back()
|
|
||||||
s.Expect(z.C{1, 's'}) // yes, but silly since 's' is easier
|
|
||||||
s.Print()
|
|
||||||
_, err := s.Expect(z.C{3, 'X'}) // nope
|
|
||||||
fmt.Println(err)
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,6-6 (6-6)
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// expected rune 'X' at U+006F 'o' 1,2-2 (2-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_rng() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Scan()
|
|
||||||
c1, _ := s.Expect(z.R{'l', 'p'})
|
|
||||||
c1.Print()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FailHook(s *scan.R) error { return fmt.Errorf("imma fail") }
|
|
||||||
|
|
||||||
func ExampleExpect_hook() {
|
|
||||||
|
|
||||||
// plain function signature
|
|
||||||
WouldSave := scan.Hook(func(s *scan.R) error {
|
|
||||||
fmt.Println("would save")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// as scan.Hook
|
|
||||||
WouldScan := scan.Hook(func(s *scan.R) error {
|
|
||||||
s.Scan()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// FailHook defined outside of Example function (see source)
|
|
||||||
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Scan()
|
|
||||||
s.Expect(WouldSave)
|
|
||||||
s.Print() // hook didn't advance
|
|
||||||
s.Expect(WouldScan)
|
|
||||||
s.Print() // hook advanced scan by one
|
|
||||||
_, e := s.Expect(FailHook)
|
|
||||||
fmt.Println(e)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// would save
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// failhook: imma fail at U+006D 'm' 1,3-3 (3-3)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_to_Success() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.T{'e'})
|
|
||||||
c.Print() // same as "som", points to 'm'
|
|
||||||
s.Print() // scanned next after ('e')
|
|
||||||
// Output:
|
|
||||||
// U+006D 'm' 1,3-3 (3-3)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleExpect_to_Inclusive() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
c, _ := s.Expect(z.Ti{'e'})
|
|
||||||
c.Print() // same as "some", points to 'e'
|
|
||||||
s.Print() // scanned next after (' ')
|
|
||||||
// Output:
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleSnap() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.ScanN(3)
|
|
||||||
s.Snap()
|
|
||||||
s.Print()
|
|
||||||
s.ScanN(4)
|
|
||||||
s.Print()
|
|
||||||
s.Back()
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
// U+0069 'i' 1,8-8 (8-8)
|
|
||||||
// U+0065 'e' 1,4-4 (4-4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleScan() {
|
|
||||||
defer log.SetOutput(os.Stderr)
|
|
||||||
defer log.SetFlags(log.Flags())
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
log.SetFlags(0)
|
|
||||||
s, _ := scan.New(`s😈me thing`)
|
|
||||||
s.Scan()
|
|
||||||
s.Print()
|
|
||||||
s.Scan()
|
|
||||||
s.Print()
|
|
||||||
s.Log()
|
|
||||||
// Output:
|
|
||||||
// U+1F608 '😈' 1,2-2 (2-2)
|
|
||||||
// U+006D 'm' 1,3-6 (3-6)
|
|
||||||
// U+006D 'm' 1,3-6 (3-6)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleStr() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Str("some")
|
|
||||||
s.Print()
|
|
||||||
s.Str(" ", "th")
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
// U+0069 'i' 1,8-8 (8-8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleAny() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
s.Any(4)
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+0020 ' ' 1,5-5 (5-5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleOpt() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
defer s.PrintPanic()
|
|
||||||
s.Opt("S", "s")
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006F 'o' 1,2-2 (2-2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_all() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
defer s.PrintPanic()
|
|
||||||
s.Opt("S", "s")
|
|
||||||
s.Str("ome", " ", "thi")
|
|
||||||
s.Print()
|
|
||||||
// Output:
|
|
||||||
// U+006E 'n' 1,9-9 (9-9)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example() {
|
|
||||||
s, _ := scan.New("some thing")
|
|
||||||
defer s.PrintPanic()
|
|
||||||
s.Opt("S", "s")
|
|
||||||
s.Str("ome", " ", "thi")
|
|
||||||
s.Print()
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package scan
|
|
||||||
|
|
||||||
import "github.com/rwxrob/structs/tree"
|
|
||||||
|
|
||||||
// FIXME These currently do not create the needed Node structures
|
|
||||||
// properly
|
|
||||||
|
|
||||||
// Slice is like "string" but marks the beginning and ending in the
|
|
||||||
// buffer string data instead of containing the actual data.
|
|
||||||
type Slice struct {
|
|
||||||
Beg *Cur
|
|
||||||
End *Cur
|
|
||||||
}
|
|
||||||
|
|
||||||
// Beg begins a new Node containing a Slice at the current location and
|
|
||||||
// pushes onto Parsing.
|
|
||||||
func (s *R) Beg(t int) {
|
|
||||||
|
|
||||||
if s.CurNode == nil {
|
|
||||||
tr := tree.New[*Slice]([]string{})
|
|
||||||
s.CurNode = tr.Root
|
|
||||||
s.Trees = append(s.Trees, tr)
|
|
||||||
}
|
|
||||||
|
|
||||||
sl := &Slice{Beg: s.Mark()}
|
|
||||||
n := s.CurNode.Add(t, sl)
|
|
||||||
s.Parsing.Push(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// End ends a new Node at the current location and pops it off of
|
|
||||||
// Parsing placing adding it under the node in the current tree. End is
|
|
||||||
// automatically implied when the end of data is reached for every Beg
|
|
||||||
// that has not yet been closed.
|
|
||||||
func (s *R) End() {
|
|
||||||
if s.CurNode == nil {
|
|
||||||
s.Warn(`no current node open, possibly forgot Beg? at %v`, s.Cur)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n := s.Parsing.Pop()
|
|
||||||
n.V.End = s.Mark()
|
|
||||||
if n.P != nil {
|
|
||||||
s.CurNode = n.P
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,203 @@
|
|||||||
|
// Copyright 2022 Robert S. Muhlestein.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package Z_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
Z "github.com/rwxrob/bonzai/z"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleArgsFrom() {
|
||||||
|
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french`))
|
||||||
|
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french `))
|
||||||
|
// Output:
|
||||||
|
// ["greet" "hi" "french"]
|
||||||
|
// ["greet" "hi" "french" ""]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleArgsOrIn_read_Nil() {
|
||||||
|
|
||||||
|
orig := os.Stdin
|
||||||
|
defer func() { os.Stdin = orig }()
|
||||||
|
os.Stdin, _ = os.Open(`testdata/in`)
|
||||||
|
|
||||||
|
fmt.Println(Z.ArgsOrIn(nil))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// some thing
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleArgsOrIn_read_Zero_Args() {
|
||||||
|
|
||||||
|
orig := os.Stdin
|
||||||
|
defer func() { os.Stdin = orig }()
|
||||||
|
os.Stdin, _ = os.Open(`testdata/in`)
|
||||||
|
|
||||||
|
fmt.Println(Z.ArgsOrIn([]string{}))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// some thing
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleArgsOrIn_args_Joined() {
|
||||||
|
|
||||||
|
fmt.Println(Z.ArgsOrIn([]string{"some", "thing"}))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// some thing
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleEsc() {
|
||||||
|
fmt.Println(Z.Esc("|&;()<>![]"))
|
||||||
|
fmt.Printf("%q", Z.Esc(" \n\r"))
|
||||||
|
// Output:
|
||||||
|
// \|\&\;\(\)\<\>\!\[\]
|
||||||
|
// "\\ \\\n\\\r"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleEscAll() {
|
||||||
|
list := []string{"so!me", "<here>", "other&"}
|
||||||
|
fmt.Println(Z.EscAll(list))
|
||||||
|
// Output:
|
||||||
|
// [so\!me \<here\> other\&]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_optional_Param() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// (p1|p2)?
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_min_One_Param() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
MinParm: 1,
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// (p1|p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_min_3_Param() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
MinParm: 3,
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// (p1|p2){3,}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_commands() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Commands: []*Z.Cmd{
|
||||||
|
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
|
||||||
|
&Z.Cmd{Name: "bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// ((f|foo)|bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_commands_and_Params() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
Commands: []*Z.Cmd{
|
||||||
|
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
|
||||||
|
&Z.Cmd{Name: "bar"},
|
||||||
|
},
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// ((p1|p2)?|((f|foo)|bar))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_error_No_Call_or_Command() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// {ERROR: neither Call nor Commands defined}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleInferredUsage_error_Params_without_Call() {
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
Commands: []*Z.Cmd{
|
||||||
|
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
|
||||||
|
&Z.Cmd{Name: "bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
// Output:
|
||||||
|
// {ERROR: Params without Call: p1, p2}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func ExampleInferredUsage() {
|
||||||
|
|
||||||
|
// call method, params, aliases, commands, and hidden commands
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// [(p1|p2)|(h|help)]
|
||||||
|
|
||||||
|
// Call with optional params and also Commands
|
||||||
|
x := &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// no Call, Command required
|
||||||
|
x = &Z.Cmd{
|
||||||
|
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// no Call with unused params (ERROR)
|
||||||
|
x = &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// Call with optional Params, but no commands
|
||||||
|
x = &Z.Cmd{
|
||||||
|
Params: []string{"p1", "p2"},
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// Call with optional Commands, but no params
|
||||||
|
x = &Z.Cmd{
|
||||||
|
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
|
||||||
|
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
|
||||||
|
}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// no Call, Commands, or Params (ERROR)
|
||||||
|
x = &Z.Cmd{}
|
||||||
|
fmt.Println(Z.InferredUsage(x))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// [p1|p2|foo|bar]
|
||||||
|
// (foo|bar)
|
||||||
|
// {ERROR: Params without Call: p1, p2}
|
||||||
|
// [p1|p2]
|
||||||
|
// [foo|bar]
|
||||||
|
// {ERROR: neither Call nor Commands defined}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
Loading…
Reference in New Issue