514 lines
19 KiB
Go
514 lines
19 KiB
Go
// 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 (c *Clyx) HasWord(name string) bool {
|
|
_, ok := c.words[name]
|
|
return ok
|
|
}
|
|
|
|
|
|
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 := 0; i < len(s); i++ { l[i] = int64(s[i]) }
|
|
} 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()
|
|
var toWrite string
|
|
if listVal, ok := data.([]any); ok {
|
|
var sb strings.Builder
|
|
for _, val := range listVal {
|
|
sb.WriteByte(byte(toInt(val)))
|
|
}
|
|
toWrite = sb.String()
|
|
} else if strVal, ok := data.(string); ok {
|
|
toWrite = strVal
|
|
} else {
|
|
panic("write expects string or list")
|
|
}
|
|
n, err := fd.(ClyxFile).Write(toWrite)
|
|
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)
|
|
}
|
|
var list []any
|
|
for i := 0; i < len(res); i++ {
|
|
list = append(list, int64(res[i]))
|
|
}
|
|
c.stack = append(c.stack, list)
|
|
}
|
|
}
|
|
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["hsargs"] = func() { c.stack = append(c.stack, toAnySlice(os.Args)) }
|
|
c.words["hsexit"] = func() { os.Exit(int(toInt(c.pop()))) }
|
|
c.words["l2s"] = func() {
|
|
l := c.pop().([]any)
|
|
var sb strings.Builder
|
|
for _, val := range l {
|
|
sb.WriteByte(byte(toInt(val)))
|
|
}
|
|
c.stack = append(c.stack, sb.String())
|
|
}
|
|
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())))) }
|
|
|
|
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 l2s 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) }
|
|
}()
|
|
c := NewClyx("", libCode)
|
|
c.Run(string(content))
|
|
if c.HasWord("_main") {
|
|
c.Run("_main")
|
|
}
|
|
}
|