This commit is contained in:
parent
98f5f773a7
commit
531dd9bf4e
3
Makefile
3
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
|
||||
|
|
119
_docs/core.md
Normal file
119
_docs/core.md
Normal file
|
@ -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.
|
||||
|
22
_docs/index.md
Normal file
22
_docs/index.md
Normal file
|
@ -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.
|
|
@ -12,23 +12,10 @@
|
|||
</header>
|
||||
|
||||
<main>
|
||||
<h3>Playground</h3>
|
||||
|
||||
<div class="terminal" id="terminal"></div>
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"xterm": "https://unpkg.com/@xterm/xterm/lib/xterm.js",
|
||||
"wasm_exec": "./wasm_exec.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/main.js" type="module"></script>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>By Leon Mika. Terminal control using <a href="https://xtermjs.org">xterm.js</a></p>
|
||||
<p>By Leon Mika.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
39
_site/playground/index.html
Normal file
39
_site/playground/index.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="go-import" content="ucl.lmika.dev git https://lmika.dev/lmika/ucl">
|
||||
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@xterm/xterm/css/xterm.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>UCL</h1>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/core/">Core</a>
|
||||
<a href="/playground/">Playground</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h3>Playground</h3>
|
||||
|
||||
<div class="terminal" id="terminal"></div>
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"xterm": "https://unpkg.com/@xterm/xterm/lib/xterm.js",
|
||||
"wasm_exec": "/wasm_exec.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/main.js" type="module"></script>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>By Leon Mika. Terminal control using <a href="https://xtermjs.org">xterm.js</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -2,4 +2,4 @@
|
|||
border: solid 4px black;
|
||||
border-radius: 5px;
|
||||
scrollbar-color: white black;
|
||||
}
|
||||
}
|
||||
|
|
25
cmd/gendocs/frame.tmpl
Normal file
25
cmd/gendocs/frame.tmpl
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>UCL</h1>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/core/">Core</a>
|
||||
<a href="/playground/">Playground</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{.Body}}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>By Leon Mika. Terminal control using <a href="https://xtermjs.org">xterm.js</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
61
cmd/gendocs/main.go
Normal file
61
cmd/gendocs/main.go
Normal file
|
@ -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())
|
||||
}
|
3
go.mod
3
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
|
||||
)
|
||||
|
|
6
go.sum
6
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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue