From 531dd9bf4e0df77b37bb0a4d554f348109c60ea7 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sat, 18 Jan 2025 16:02:35 +1100 Subject: [PATCH] Started building out the site. --- Makefile | 3 + _docs/core.md | 119 ++++++++++++++++++++++++++++++++++++ _docs/index.md | 22 +++++++ _site/index.html | 15 +---- _site/playground/index.html | 39 ++++++++++++ _site/style.css | 2 +- cmd/gendocs/frame.tmpl | 25 ++++++++ cmd/gendocs/main.go | 61 ++++++++++++++++++ go.mod | 3 + go.sum | 6 ++ repl/evaldisplay.go | 4 ++ ucl/builtins.go | 11 ---- ucl/inst.go | 1 - ucl/userbuiltin_test.go | 15 ++++- 14 files changed, 298 insertions(+), 28 deletions(-) create mode 100644 _docs/core.md create mode 100644 _docs/index.md create mode 100644 _site/playground/index.html create mode 100644 cmd/gendocs/frame.tmpl create mode 100644 cmd/gendocs/main.go diff --git a/Makefile b/Makefile index 626870d..0bb4177 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,10 @@ test: site: clean mkdir build mkdir build/site + mkdir build/site/core cp -r _site/* build/site/. + go run ./cmd/gendocs/main.go ./_docs/index.md > build/site/index.html + go run ./cmd/gendocs/main.go ./_docs/core.md > build/site/core/index.html GOOS=js GOARCH=wasm go build -o build/site/playwasm.wasm ./cmd/playwasm/. site-deploy: site diff --git a/_docs/core.md b/_docs/core.md new file mode 100644 index 0000000..27b4ed4 --- /dev/null +++ b/_docs/core.md @@ -0,0 +1,119 @@ +--- +--- + +# Core Functions + +### call + +``` +call BLOCK [ARGS...] +``` + +Invokes block, passing in the arguments. This is as if block was invoked as a command. This may not always be necessary +unless these are need to call BLOCK with exactly 0 arguments. + +### echo + +``` +echo [ARGS...] +``` + +Displays the string representation of ARGS to stdout followed by a new line. + +### foreach + +``` +foreach SEQ BLOCK +``` + +Iterates BLOCK over every element of the sequence. + +The values pass to BLOCK will depend on the type of SEQ. If SEQ is a list, BLOCK receives the element value. +If SEQ is a hash, BLOCK receives both the key and value of each element. + +BLOCK can call `break` and `continue` which will exit out of the loop, or jump to the start of the next iteration +respectively. + +The return value of `foreach` will be the result of the last iteration, unless `break` is called with a value. + +``` +foreach [1 2 3] { |e| + echo "Element = $e" +} + +foreach [a:"one" b:"two"] { |k v| + echo "Key $k = $v" +} +``` + +### keys + +``` +keys HASH +``` + +Returns the keys of the passed in hash as a list. The order of keys are non-deterministic. +If HASH is not a hash, then nil will be returned. + +### len + +``` +len SEQ +``` + +Returns the length of SEQ. If SEQ is a list or hash, SEQ will be the number of +elements. If SEQ is a string, SEQ will be the string's length. All other values will +return a length of 0. + +### map + +``` +map SEQ BLOCK +``` + +Returns a new list of elements mapped from SEQ according to the result of BLOCK. SEQ can be any listable data +structure, however the result will always be a concrete list. + +``` +map [1 2 3] { |x| str $x | len } +``` + +### proc + +``` +proc [NAME] BLOCK +``` + +Defines a new function optionally with the given name. When called without NAME, this will define a new +lambda which can be invoked using `call`. + +When NAME is set, this function defining a function a name will always declare it at the top-level scope. + +### reduce + +``` +reduce SEQ [INIT] BLOCK +``` + +Returns the result of reducing the elements of SEQ with the passed in block. + +BLOCK will receive at least two argument, with the current value of the accumulator always being the last argument. +If SEQ is a list, the arguments will be _|element accumulator|_, and if SEQ is a hash, the arguments will be +_|key value accumulator|_. + +The block result will be set as the value of the accumulator for the next iteration. Once all elements are process +the accumulator value will be returned as the result of `reduce`. + +If INIT is not set, and SEQ is a list, the accumulator will be set to the first value and BLOCK will be called +from the second element, if any. If SEQ is a hash, then the accumulator will be set to nil. + +### set + +``` +set NAME VALUE +``` + +Sets the value of variable NAME to VALUE. Any variable with NAME will be checked +within the scope first, including any parent scopes, before a new variable is defined. +Any new variables will only be defined with the current scope. + diff --git a/_docs/index.md b/_docs/index.md new file mode 100644 index 0000000..f535869 --- /dev/null +++ b/_docs/index.md @@ -0,0 +1,22 @@ +# Universal Command Language + +Universal Command Language, or UCL, is a scripting language designed for use with REPLs or batch jobs. +It's heavily inspired by the likes of TCL and Bash, and it's purpose is to be usable as an interactive command +language while at the same time being useful enough as a scripting language for simple automation tasks. + +The flavour of a particular UCL instance will depend heavily on the host environment. Most likely additional commands +will be defined, and some commands may be removed. But this page describes the features of a "typical" UCL instance. + +## Example + +``` +proc greet { |someone| + echo "Hello, $someone" +} + +greet "world" +``` + +## Status + +This is very much still in development and is subject to change. \ No newline at end of file diff --git a/_site/index.html b/_site/index.html index 15ea466..6508f95 100644 --- a/_site/index.html +++ b/_site/index.html @@ -12,23 +12,10 @@
-

Playground

- -
- - -
\ No newline at end of file diff --git a/_site/playground/index.html b/_site/playground/index.html new file mode 100644 index 0000000..fcba6ea --- /dev/null +++ b/_site/playground/index.html @@ -0,0 +1,39 @@ + + + + + + + + + +
+

UCL

+ +
+ +
+

Playground

+ +
+ + + +
+ + + + \ No newline at end of file diff --git a/_site/style.css b/_site/style.css index 34bb4fa..6622960 100644 --- a/_site/style.css +++ b/_site/style.css @@ -2,4 +2,4 @@ border: solid 4px black; border-radius: 5px; scrollbar-color: white black; -} \ No newline at end of file +} diff --git a/cmd/gendocs/frame.tmpl b/cmd/gendocs/frame.tmpl new file mode 100644 index 0000000..2c5e142 --- /dev/null +++ b/cmd/gendocs/frame.tmpl @@ -0,0 +1,25 @@ + + + + + + + +
+

UCL

+ +
+ +
+ {{.Body}} +
+ + + + \ No newline at end of file diff --git a/cmd/gendocs/main.go b/cmd/gendocs/main.go new file mode 100644 index 0000000..e53b1e7 --- /dev/null +++ b/cmd/gendocs/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "bytes" + "embed" + "flag" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "go.abhg.dev/goldmark/frontmatter" + "html/template" + "log" + "os" +) + +//go:embed frame.tmpl +var frameTmpl embed.FS + +func main() { + flag.Parse() + + if flag.NArg() != 1 { + log.Fatalln("usage: gendocs [markdown]") + } + + md := goldmark.New(goldmark.WithExtensions(&frontmatter.Extender{})) + + mdData, err := os.ReadFile(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + + ctx := parser.NewContext() + + var buf bytes.Buffer + if err := md.Convert(mdData, &buf, parser.WithContext(ctx)); err != nil { + log.Fatal(err) + } + + var frontMatter struct { + Title string `yaml:"title"` + } + if fm := frontmatter.Get(ctx); fm != nil { + if err := fm.Decode(&frontMatter); err != nil { + log.Fatal(err) + } + } + + frameTmpls, err := template.ParseFS(frameTmpl, "*.tmpl") + if err != nil { + log.Fatal(err) + } + var res bytes.Buffer + if err := frameTmpls.ExecuteTemplate(&res, "frame.tmpl", map[string]interface{}{ + "Title": frontMatter.Title, + "Body": template.HTML(buf.Bytes()), + }); err != nil { + log.Fatal(err) + } + + os.Stdout.Write(res.Bytes()) +} diff --git a/go.mod b/go.mod index 93767db..99a6577 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,11 @@ require ( ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/yuin/goldmark v1.7.8 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect golang.org/x/sys v0.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b63096c..f9cdee7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= @@ -20,6 +22,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/repl/evaldisplay.go b/repl/evaldisplay.go index f09254c..6e8977b 100644 --- a/repl/evaldisplay.go +++ b/repl/evaldisplay.go @@ -2,6 +2,7 @@ package repl import ( "context" + "errors" "fmt" "io" "os" @@ -15,6 +16,9 @@ type NoResults struct{} func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error { res, err := r.inst.Eval(ctx, expr) if err != nil { + if errors.Is(err, ucl.ErrNotConvertable) { + return nil + } return err } diff --git a/ucl/builtins.go b/ucl/builtins.go index 3b3fbd0..734663e 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -196,17 +196,6 @@ func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) { return newVal, nil } -func toUpperBuiltin(ctx context.Context, args invocationArgs) (Object, error) { - if err := args.expectArgn(1); err != nil { - return nil, err - } - sarg, err := args.stringArg(0) - if err != nil { - return nil, err - } - return StringObject(strings.ToUpper(sarg)), nil -} - func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err diff --git a/ucl/inst.go b/ucl/inst.go index 92d58cd..6332256 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -57,7 +57,6 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("echo", invokableFunc(echoBuiltin)) rootEC.addCmd("set", invokableFunc(setBuiltin)) - rootEC.addCmd("toUpper", invokableFunc(toUpperBuiltin)) rootEC.addCmd("len", invokableFunc(lenBuiltin)) rootEC.addCmd("keys", invokableFunc(keysBuiltin)) rootEC.addCmd("index", invokableFunc(indexBuiltin)) diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index db15689..149e988 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -433,6 +433,13 @@ func TestCallArgs_CanBind(t *testing.T) { t.Run("can bind invokable", func(t *testing.T) { inst := ucl.New() + inst.SetBuiltin("toUpper", func(ctx context.Context, args ucl.CallArgs) (any, error) { + var s string + if err := args.Bind(&s); err != nil { + return nil, err + } + return strings.ToUpper(s), nil + }) inst.SetBuiltin("wrap", func(ctx context.Context, args ucl.CallArgs) (any, error) { var inv ucl.Invokable @@ -458,7 +465,13 @@ func TestCallArgs_CanBind(t *testing.T) { t.Run("can carry invokable outside of context", func(t *testing.T) { inst := ucl.New() var inv ucl.Invokable - + inst.SetBuiltin("toUpper", func(ctx context.Context, args ucl.CallArgs) (any, error) { + var s string + if err := args.Bind(&s); err != nil { + return nil, err + } + return strings.ToUpper(s), nil + }) inst.SetBuiltin("wrap", func(ctx context.Context, args ucl.CallArgs) (any, error) { if err := args.Bind(&inv); err != nil { return nil, err