Files
clyx/clyx-go/clyx.go
T

469 lines
18 KiB
Go
Raw Normal View History

2026-07-03 16:12:25 +03:00
// 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")
}
}
2026-07-04 08:52:56 +03:00
func (c *Clyx) HasWord(name string) bool {
_, ok := c.words[name]
return ok
}
2026-07-03 16:12:25 +03:00
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) } }
2026-07-04 08:52:56 +03:00
c.words["hsargs"] = func() { c.stack = append(c.stack, toAnySlice(os.Args)) }
c.words["hsexit"] = func() { os.Exit(int(toInt(c.pop()))) }
2026-07-03 16:12:25 +03:00
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) }
}()
2026-07-04 09:30:02 +03:00
c := NewClyx("", libCode)
c.Run(string(content))
if c.HasWord("_main") {
c.Run("_main")
}
2026-07-03 16:12:25 +03:00
}