Started building out the site.
All checks were successful
Build / build (push) Successful in 2m7s

This commit is contained in:
Leon Mika 2025-01-18 16:02:35 +11:00
parent 98f5f773a7
commit 531dd9bf4e
14 changed files with 298 additions and 28 deletions

View file

@ -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
View 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
View 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.

View file

@ -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>

View 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>

View file

@ -2,4 +2,4 @@
border: solid 4px black;
border-radius: 5px;
scrollbar-color: white black;
}
}

25
cmd/gendocs/frame.tmpl Normal file
View 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
View 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
View file

@ -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
View file

@ -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=

View file

@ -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
}

View file

@ -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

View file

@ -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))

View file

@ -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