Added Go implementation

This commit is contained in:
Luxferre
2026-07-03 16:12:25 +03:00
parent 7f236d97b3
commit eabb972834
7 changed files with 553 additions and 10 deletions
+41 -1
View File
@@ -50,6 +50,31 @@ sh dist-python.sh /path/to/mounted/volume/app/clyx
```
This will install Clyx as a T-DeckARD applet.
### Reference Go implementation installation
To install the Go-based Clyx implementation directly from Git, run:
```sh
go install codeberg.org/luxferre/clyx@latest
```
For maximum performance, you can pass optimization flags to build a stripped binary without debug symbols, bounds checks, or inlining:
```sh
go install -a -trimpath -gcflags=all="-l -B -e" -ldflags "-s -w" codeberg.org/luxferre/clyx@latest
```
This will install the executable as `clyx` into your `$GOPATH/bin` directory. To upgrade to the latest version at any time, simply run the same command again.
Alternatively, to compile and build it locally, navigate to the `clyx-go` directory and run:
```sh
cd clyx-go
make
```
This will build the `clyx` executable.
## Usage
Note: this section describes how to run the interpreter itself. For the language manual, refer to [Clyx Reference Manual](clyx_manual.md).
@@ -71,6 +96,20 @@ clyx.repl()
```
To run a Clyx source file directly, issue `clyx.clyx('path/to/file.clx')` instead.
### Running the Go implementation
To run the REPL, execute the compiled `clyx` binary without arguments:
```sh
./clyx
```
To run a program directly, pass the path to the `.clx` file as an argument:
```sh
./clyx path/to/program.clx
```
Caveats:
1. The entire operation is rather slow (which is expected for an interpreted runtime on top of CircuitPython on this class of machines) and may sometimes overflow the allocated (native) Python stack. As such, it's not recommended to `src` files from the REPL if you can run them directly instead.
@@ -79,6 +118,7 @@ Caveats:
## Implementations
- [Reference implementation in Python](clyx-python/clyx/clyx.py) (compatible with MicroPython and T-DeckARD on CircuitPython)
- [Reference implementation in Go](clyx-go/clyx.go)
## FAQ
@@ -108,7 +148,7 @@ Apart from that, your implementation needs to be able to load `lib.clx` (standar
### Doesn't `hseval` primitive make Clyx programs non-portable across different implementations?
Yes, it does, because it works differently across different host languages (and Clyx implementations are allowed to not support it at all), so it must only be used as a last resort when any other attempts to interact with the outside world are unsuccessful. Additionally, using `hseval` introduces extra security risks. Nevertheless, this primitive had been added in order to enable some host-specific interoperability that would otherwise be impossible or very cumbersome to implement, such as browser-based deployments.
Yes, it does, because it works differently across different host languages (and Clyx implementations like `clyx-go` are allowed to not support it at all), so it must only be used as a last resort when any other attempts to interact with the outside world are unsuccessful. Additionally, using `hseval` introduces extra security risks. Nevertheless, this primitive had been added in order to enable some host-specific interoperability that would otherwise be impossible or very cumbersome to implement, such as browser-based deployments.
In other words, `hseval` is sometimes very useful in very specific cases, but please avoid using it unless you have a really good reason to use it.
+12
View File
@@ -0,0 +1,12 @@
.PHONY: all build clean test
all: build
build:
go build -a -trimpath -gcflags=all="-l -B -e" -ldflags "-s -w" -o clyx ../main.go
clean:
rm -f clyx
test: build
./clyx ../test.clx
+456
View File
@@ -0,0 +1,456 @@
// Clyx programming language interpreter, Go implementation
// Created by Luxferre in 2026, released into the public domain
package clyx
import (
"bufio"
"fmt"
"io"
"math"
"math/rand"
"net"
"os"
"strconv"
"strings"
"unicode/utf8"
)
var stdinReader = bufio.NewReader(os.Stdin)
type ClyxFile interface {
Read() (string, error)
Write(string) (int, error)
Close() error
Accept() (ClyxFile, error)
}
type StdinFile struct{}
func (f *StdinFile) Read() (string, error) { line, err := stdinReader.ReadString('\n'); return strings.TrimRight(line, "\r\n"), err }
func (f *StdinFile) Write(string) (int, error) { return 0, nil }
func (f *StdinFile) Close() error { return nil }
func (f *StdinFile) Accept() (ClyxFile, error) { return nil, fmt.Errorf("not server") }
type RealFile struct {
f *os.File
m string
}
func (f *RealFile) Read() (string, error) {
if f.m == "r" { d, err := io.ReadAll(f.f); return strings.TrimRight(string(d), "\r\n"), err }
return "", fmt.Errorf("not reading")
}
func (f *RealFile) Write(s string) (int, error) { return f.f.WriteString(s) }
func (f *RealFile) Close() error { return f.f.Close() }
func (f *RealFile) Accept() (ClyxFile, error) { return nil, fmt.Errorf("not server") }
type ServerSocket struct{ l net.Listener }
func (s *ServerSocket) Read() (string, error) { return "", fmt.Errorf("no read") }
func (s *ServerSocket) Write(string) (int, error) { return 0, fmt.Errorf("no write") }
func (s *ServerSocket) Close() error { return s.l.Close() }
func (s *ServerSocket) Accept() (ClyxFile, error) {
c, err := s.l.Accept()
if err != nil { return nil, err }
return &ClientSocket{conn: c, r: bufio.NewReader(c)}, nil
}
type ClientSocket struct {
conn net.Conn
r *bufio.Reader
}
func (c *ClientSocket) Read() (string, error) { line, err := c.r.ReadString('\n'); return strings.TrimRight(line, "\r\n"), err }
func (c *ClientSocket) Write(s string) (int, error) { return c.conn.Write([]byte(s)) }
func (c *ClientSocket) Close() error { return c.conn.Close() }
func (c *ClientSocket) Accept() (ClyxFile, error) { return nil, fmt.Errorf("not server") }
func toFloat(v any) float64 {
if ix, ok := v.(int64); ok { return float64(ix) }
if fx, ok := v.(float64); ok { return fx }
return 0
}
func toInt(v any) int64 {
if ix, ok := v.(int64); ok { return ix }
if fx, ok := v.(float64); ok { return int64(fx) }
return 0
}
func isFloat(v any) bool { _, ok := v.(float64); return ok }
func formatFloat(v float64) string { if v == math.Floor(v) { return fmt.Sprintf("%.1f", v) }; return fmt.Sprintf("%g", v) }
func repr(v any) string {
switch val := v.(type) {
case int64: return fmt.Sprintf("%d", val)
case float64: return formatFloat(val)
case string:
escaped := strconv.Quote(val)
if !strings.Contains(val, "'") { return "'" + escaped[1:len(escaped)-1] + "'" }
return escaped
case []any:
var parts []string
for _, item := range val { parts = append(parts, repr(item)) }
return "[" + strings.Join(parts, ", ") + "]"
case nil: return "None"
default: return "<object>"
}
}
func isTruthy(v any) bool {
switch val := v.(type) {
case int64: return val != 0
case float64: return val != 0.0
case bool: return val
case string: return val != ""
case []any: return len(val) > 0
default: return val != nil
}
}
func opDivmod(x, y any) (any, any) {
if ix, okx := x.(int64); okx {
if iy, oky := y.(int64); oky {
if iy == 0 { panic("division by zero") }
q, r := ix/iy, ix%iy
if (r < 0 && iy > 0) || (r > 0 && iy < 0) { q--; r += iy }
return q, r
}
}
fx, fy := toFloat(x), toFloat(y)
if fy == 0 { panic("division by zero") }
q := math.Floor(fx / fy)
return q, fx - q*fy
}
func opShl(x, f any) any {
xv, fv := toInt(x), toInt(f)
if fv >= 0 { return xv << fv }
return xv >> (-fv)
}
func opPow(x, y any) any {
if ix, okx := x.(int64); okx {
if iy, oky := y.(int64); oky && iy >= 0 {
res := int64(1)
for base, exp := ix, iy; exp > 0; exp >>= 1 {
if exp&1 == 1 { res *= base }
base *= base
}
return res
}
}
return math.Pow(toFloat(x), toFloat(y))
}
func tokenize(code string) []string {
var tokens []string
code = strings.ReplaceAll(strings.ReplaceAll(code, "\\\\", "\x01"), "\\\"", "\x02")
for _, l := range strings.Split(code, "\n") {
for i, p := range strings.Split(l, "\"") {
if i%2 != 0 {
p = strings.ReplaceAll(strings.ReplaceAll(p, "\x02", "\""), "\x01", "\\")
tokens = append(tokens, "\""+p+"\"")
} else {
commentIdx := strings.Index(p, "#")
if commentIdx != -1 { p = p[:commentIdx] }
p = strings.ReplaceAll(strings.ReplaceAll(p, "[", " [ "), "]", " ] ")
tokens = append(tokens, strings.Fields(p)...)
if commentIdx != -1 { break }
}
}
}
return tokens
}
func normToken(t string) any {
if strings.Contains(t, ".") && t != "." {
if f, err := strconv.ParseFloat(strings.TrimSpace(t), 64); err == nil { return f }
}
if i, err := strconv.ParseInt(strings.TrimSpace(t), 10, 64); err == nil { return i }
if len(t) >= 2 && t[0] == '"' && t[len(t)-1] == '"' {
s := t[1 : len(t)-1]
s = strings.ReplaceAll(s, "\\\\", "\x01")
s = strings.ReplaceAll(s, "\\n", "\n")
s = strings.ReplaceAll(s, "\\t", "\t")
s = strings.ReplaceAll(s, "\\r", "\r")
s = strings.ReplaceAll(s, "\\s", " ")
s = strings.ReplaceAll(s, "\\b", "\b")
s = strings.ReplaceAll(s, "\\f", "\f")
s = strings.ReplaceAll(s, "\\\"", "\"")
return strings.ReplaceAll(s, "\x01", "\\")
}
return t
}
func parseQuot(tokens *[]any) ([]any, error) {
var quot []any
for {
if len(*tokens) == 0 { return nil, fmt.Errorf("Syntax error: unbalanced brackets") }
t := (*tokens)[0]
*tokens = (*tokens)[1:]
if s, ok := t.(string); ok && s == "]" { return quot, nil }
if s, ok := t.(string); ok && s == "[" {
sub, err := parseQuot(tokens)
if err != nil { return nil, err }
quot = append(quot, sub)
} else {
if s, ok := t.(string); ok { quot = append(quot, normToken(s)) } else { quot = append(quot, t) }
}
}
}
func toAnySlice(strs []string) []any {
res := make([]any, len(strs))
for i, s := range strs { res[i] = s }
return res
}
type Clyx struct {
stack []any
tokens []any
tokenStreams [][]any
words map[string]any
}
func (c *Clyx) pop() any {
if len(c.stack) == 0 { panic("stack underflow") }
val := c.stack[len(c.stack)-1]
c.stack = c.stack[:len(c.stack)-1]
return val
}
func (c *Clyx) reprStack() string {
var parts []string
for _, item := range c.stack { parts = append(parts, repr(item)) }
return "[" + strings.Join(parts, ", ") + "]"
}
func (c *Clyx) execQuot(q []any) {
c.tokenStreams = append(c.tokenStreams, c.tokens)
c.tokens = make([]any, len(q))
copy(c.tokens, q)
for len(c.tokens) > 0 {
t := c.tokens[0]
c.tokens = c.tokens[1:]
if listVal, ok := t.([]any); ok {
c.stack = append(c.stack, listVal)
} else if s, ok := t.(string); ok && s == "[" {
qParsed, err := parseQuot(&c.tokens)
if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); break }
c.stack = append(c.stack, qParsed)
} else {
normT := t
if s, ok := t.(string); ok { normT = normToken(s) }
var v any
var isWord bool
if sNorm, ok := normT.(string); ok { v, isWord = c.words[sNorm] }
if isWord {
switch val := v.(type) {
case func(): val()
case []any: c.execQuot(val)
default: c.stack = append(c.stack, val)
}
} else {
c.stack = append(c.stack, normT)
}
}
}
c.tokens = c.tokenStreams[len(c.tokenStreams)-1]
c.tokenStreams = c.tokenStreams[:len(c.tokenStreams)-1]
}
func (c *Clyx) popCallerToken() any {
var stream *[]any
if len(c.tokenStreams) > 0 { stream = &c.tokenStreams[len(c.tokenStreams)-1] } else { stream = &c.tokens }
if len(*stream) == 0 { return nil }
t := (*stream)[0]
*stream = (*stream)[1:]
if s, ok := t.(string); ok && s == "[" {
qParsed, err := parseQuot(stream)
if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); return nil }
return qParsed
}
return t
}
func (c *Clyx) cmdDefw() {
var stream *[]any
if len(c.tokenStreams) > 0 { stream = &c.tokenStreams[len(c.tokenStreams)-1] } else { stream = &c.tokens }
var val any
if len(*stream) > 0 {
t0 := (*stream)[0]
isQuot := false
if s, ok := t0.(string); ok && s == "[" { isQuot = true } else if _, ok := t0.([]any); ok { isQuot = true }
if isQuot { val = c.popCallerToken() }
}
if val == nil { val = c.pop() }
name := c.pop().(string)
c.words[name] = val
}
func (c *Clyx) cmdType() {
val := c.stack[len(c.stack)-1]
var t int64 = 0
switch val.(type) {
case []any: t = 2
case string: t = 1
}
c.stack = append(c.stack, t)
}
func (c *Clyx) Run(code string) {
c.execQuot(toAnySlice(tokenize(code)))
}
func (c *Clyx) Eval(q any) {
defer func() {
if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", r) }
}()
if s, ok := q.(string); ok {
c.execQuot(toAnySlice(tokenize(s)))
} else if slice, ok := q.([]any); ok {
c.execQuot(slice)
} else {
panic("eval expects string/quotation")
}
}
func printVal(v any) {
switch val := v.(type) {
case int64: fmt.Printf("%d\n", val)
case float64: fmt.Println(formatFloat(val))
case string: fmt.Println(val)
case []any: fmt.Println(repr(val))
case nil: fmt.Println("None")
default: fmt.Printf("%v\n", val)
}
}
func NewClyx(libfname string, libCode string) *Clyx {
c := &Clyx{
words: make(map[string]any),
}
c.words["dup"] = func() { c.stack = append(c.stack, c.stack[len(c.stack)-1]) }
c.words["drop"] = func() { c.pop() }
c.words["swap"] = func() { v1, v2 := c.pop(), c.pop(); c.stack = append(c.stack, v1, v2) }
c.words["i"] = func() { c.Eval(c.pop()) }
c.words["cond"] = func() {
cVal, tVal, fVal := c.pop(), c.pop(), c.pop()
runVal := fVal
if isTruthy(cVal) { runVal = tVal }
if listVal, ok := runVal.([]any); ok { c.execQuot(listVal) } else { c.execQuot([]any{runVal}) }
}
c.words["cons"] = func() { q, x := c.pop().([]any), c.pop(); c.stack = append(c.stack, append([]any{x}, q...)) }
c.words["uncons"] = func() {
qVal := c.pop()
var l []any
if s, ok := qVal.(string); ok {
l = make([]any, len(s))
for i, r := range s { l[i] = int64(r) }
} else {
l = qVal.([]any)
}
if len(l) == 0 { c.stack = append(c.stack, l, int64(0)) } else { c.stack = append(c.stack, l[1:], l[0], int64(1)) }
}
c.words["qpop"] = func() { q := c.pop().([]any); c.stack = append(c.stack, q[:len(q)-1], q[len(q)-1]) }
c.words["qlen"] = func() { if l, ok := c.pop().([]any); ok { c.stack = append(c.stack, int64(len(l))) } else { c.stack = append(c.stack, int64(0)) } }
c.words["qget"] = func() { idx, v := toInt(c.pop()), c.pop(); if l, ok := v.([]any); ok && idx >= 0 && idx < int64(len(l)) { c.stack = append(c.stack, l[idx]) } else { c.stack = append(c.stack, nil) } }
c.words["qset"] = func() { idx, v, x := toInt(c.pop()), c.pop(), c.pop(); if l, ok := v.([]any); ok && idx >= 0 && idx < int64(len(l)) { l[idx] = x; c.stack = append(c.stack, l) } else { c.stack = append(c.stack, nil) } }
c.words["slen"] = func() { if s, ok := c.pop().(string); ok { c.stack = append(c.stack, int64(utf8.RuneCountInString(s))) } else { c.stack = append(c.stack, int64(0)) } }
c.words["+"] = func() {
y, x := c.pop(), c.pop()
if yL, okY := y.([]any); okY { if xL, okX := x.([]any); okX { c.stack = append(c.stack, append(yL, xL...)); return } }
if yS, okY := y.(string); okY { if xS, okX := x.(string); okX { c.stack = append(c.stack, yS+xS); return } }
if isFloat(y) || isFloat(x) { c.stack = append(c.stack, toFloat(y)+toFloat(x)) } else { c.stack = append(c.stack, toInt(y)+toInt(x)) }
}
c.words["*"] = func() {
y, x := c.pop(), c.pop()
if yS, okY := y.(string); okY { if xI, okX := x.(int64); okX { if xI < 0 { xI = 0 }; c.stack = append(c.stack, strings.Repeat(yS, int(xI))); return } }
if xS, okX := x.(string); okX { if yI, okY := y.(int64); okY { if yI < 0 { yI = 0 }; c.stack = append(c.stack, strings.Repeat(xS, int(yI))); return } }
if yL, okY := y.([]any); okY { if xI, okX := x.(int64); okX { if xI < 0 { xI = 0 }; var res []any; for i := int64(0); i < xI; i++ { res = append(res, yL...) }; c.stack = append(c.stack, res); return } }
if xL, okX := x.([]any); okX { if yI, okY := y.(int64); okY { if yI < 0 { yI = 0 }; var res []any; for i := int64(0); i < yI; i++ { res = append(res, xL...) }; c.stack = append(c.stack, res); return } }
if isFloat(y) || isFloat(x) { c.stack = append(c.stack, toFloat(y)*toFloat(x)) } else { c.stack = append(c.stack, toInt(y)*toInt(x)) }
}
c.words["-"] = func() { y, x := c.pop(), c.pop(); if isFloat(y) || isFloat(x) { c.stack = append(c.stack, toFloat(x)-toFloat(y)) } else { c.stack = append(c.stack, toInt(x)-toInt(y)) } }
c.words["/"] = func() { y, x := c.pop(), c.pop(); c.stack = append(c.stack, toFloat(x)/toFloat(y)) }
c.words["divmod"] = func() { y, x := c.pop(), c.pop(); q, r := opDivmod(x, y); c.stack = append(c.stack, q, r) }
c.words["shl"] = func() { f, x := c.pop(), c.pop(); c.stack = append(c.stack, opShl(x, f)) }
c.words["^"] = func() { y, x := c.pop(), c.pop(); c.stack = append(c.stack, opPow(x, y)) }
c.words["0="] = func() { v := c.pop(); if v == int64(0) || v == 0.0 { c.stack = append(c.stack, int64(1)) } else { c.stack = append(c.stack, int64(0)) } }
c.words["0<"] = func() { v := c.pop(); isNeg := false; if ix, ok := v.(int64); ok { isNeg = ix < 0 } else if fx, ok := v.(float64); ok { isNeg = fx < 0 }; if isNeg { c.stack = append(c.stack, int64(1)) } else { c.stack = append(c.stack, int64(0)) } }
c.words["nand"] = func() { y, x := c.pop(), c.pop(); c.stack = append(c.stack, ^(toInt(x) & toInt(y))) }
c.words["defw"] = func() { c.cmdDefw() }
c.words["type"] = func() { c.cmdType() }
c.words["next"] = func() { c.stack = append(c.stack, c.popCallerToken()) }
c.words["."] = func() { printVal(c.pop()) }
c.words["emit"] = func() { fmt.Printf("%c", rune(toInt(c.pop()))) }
c.words["fopen"] = func() {
mode, fileVal := c.pop().(string), c.pop()
isStdin := false
if ix, ok := fileVal.(int64); ok && ix == 0 { isStdin = true } else if sx, ok := fileVal.(string); ok && sx == "0" { isStdin = true }
if isStdin {
c.stack = append(c.stack, &StdinFile{})
} else {
var file *os.File
var err error
if mode == "r" { file, err = os.Open(fileVal.(string)) } else if mode == "w" { file, err = os.Create(fileVal.(string)) } else if mode == "a" { file, err = os.OpenFile(fileVal.(string), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) }
if err != nil { panic(err) }
c.stack = append(c.stack, &RealFile{f: file, m: mode})
}
}
c.words["nopen"] = func() {
portStr, host := fmt.Sprintf("%v", c.pop()), c.pop().(string)
if host == "0.0.0.0" {
l, err := net.Listen("tcp", host+":"+portStr)
if err != nil { panic(err) }
c.stack = append(c.stack, &ServerSocket{l: l})
} else {
conn, err := net.Dial("tcp", host+":"+portStr)
if err != nil { panic(err) }
c.stack = append(c.stack, &ClientSocket{conn: conn, r: bufio.NewReader(conn)})
}
}
c.words["write"] = func() { data, fd := c.pop(), c.pop(); n, err := fd.(ClyxFile).Write(data.(string)); if err != nil { panic(err) }; c.stack = append(c.stack, int64(n)) }
c.words["read"] = func() { fd := c.pop().(ClyxFile); if srv, ok := fd.(*ServerSocket); ok { client, err := srv.Accept(); if err != nil { panic(err) }; c.stack = append(c.stack, client) } else { res, err := fd.Read(); if err != nil { panic(err) }; c.stack = append(c.stack, res) } }
c.words["close"] = func() { if fd, ok := c.pop().(ClyxFile); ok { fd.Close() } }
c.words["delf"] = func() { if err := os.Remove(c.pop().(string)); err != nil { panic(err) } }
c.words["maked"] = func() { if err := os.Mkdir(c.pop().(string), 0755); err != nil { panic(err) } }
c.words["listd"] = func() { entries, err := os.ReadDir(c.pop().(string)); if err != nil { panic(err) }; var names []any; for _, entry := range entries { names = append(names, entry.Name()) }; c.stack = append(c.stack, names) }
c.words["deld"] = func() { if err := os.Remove(c.pop().(string)); err != nil { panic(err) } }
c.words["rand"] = func() { limit := toInt(c.pop()); if limit <= 0 { c.stack = append(c.stack, int64(0)) } else { c.stack = append(c.stack, rand.Int63n(limit)) } }
c.words[".s"] = func() { printVal(c.reprStack()) }
c.words["dip"] = func() { q, y := c.pop().([]any), c.pop(); c.execQuot(q); c.stack = append(c.stack, y) }
c.words["loop"] = func() { body, cond := c.pop().([]any), c.pop().([]any); for { c.execQuot(cond); if !isTruthy(c.pop()) { break }; c.execQuot(body) } }
c.words["hseval"] = func() { fmt.Fprintln(os.Stderr, "Error: unsupported word"); os.Exit(1) }
c.words["chr"] = func() { c.stack = append(c.stack, string(rune(toInt(c.pop())))) }
c.words["asc"] = func() { s := c.pop().(string); r, _ := utf8.DecodeRuneInString(s); c.stack = append(c.stack, int64(r)) }
regMath := func(name string, f func(float64) float64) {
c.words[name] = func() { c.stack = append(c.stack, f(toFloat(c.pop()))) }
}
regMath("sin", math.Sin)
regMath("cos", math.Cos)
regMath("atan", math.Atan)
regMath("exp", math.Exp)
regMath("log", math.Log)
c.Run(`"::" [ next defw ] defw :: readf [ "r" fopen dup read swap close ] :: src [ readf i ]`)
if libfname == "" {
c.Run(libCode)
} else {
c.Run(fmt.Sprintf(`"%s" src`, libfname))
}
return c
}
func RunFile(filePath string, libCode string) {
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error executing %s: %v\n", filePath, err)
os.Exit(1)
}
defer func() {
if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Error executing %s: %v\n", filePath, r); os.Exit(1) }
}()
NewClyx("", libCode).Run(string(content))
}
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Clyx programming language interpreter, reference core implementation
# Clyx programming language interpreter, reference Python implementation
# Created by Luxferre in 2026, released into the public domain
import os, sys, math
+3
View File
@@ -0,0 +1,3 @@
module codeberg.org/luxferre/clyx
go 1.26.4
+32
View File
@@ -0,0 +1,32 @@
// Clyx programming language interpreter, Go implementation wrapper
// Created by Luxferre in 2026, released into the public domain
package main
import (
_ "embed"
"fmt"
"os"
clyx "codeberg.org/luxferre/clyx/clyx-go"
)
//go:embed lib.clx
var defaultLibCode string
//go:embed clyx_repl.clx
var defaultReplCode string
func main() {
if len(os.Args) > 1 {
clyx.RunFile(os.Args[1], defaultLibCode)
} else {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", r)
os.Exit(1)
}
}()
clyx.NewClyx("", defaultLibCode).Run(defaultReplCode)
}
}
+8 -8
View File
@@ -206,12 +206,12 @@ if [
my_inline 100 = if [ " :: (inline) PASS" puts cr ] [ " :: (inline) FAIL" puts cr ] # Test inline ::
# 11. Host Language Evaluation: hseval
"Testing host language evaluation..." puts cr
"[1, 2] * 2" hseval [ 1 2 1 2 ] streq
"1 + 2" hseval 3 = and
if [
" hseval PASS" puts cr
] [
" hseval FAIL" puts cr
]
# "Testing host language evaluation..." puts cr
# "[1, 2] * 2" hseval [ 1 2 1 2 ] streq
# "1 + 2" hseval 3 = and
# if [
# " hseval PASS" puts cr
# ] [
# " hseval FAIL" puts cr
# ]