binary safety

This commit is contained in:
Luxferre
2026-07-04 12:59:58 +03:00
parent c1c66f9152
commit 147a05d8e0
5 changed files with 102 additions and 23 deletions
+50 -5
View File
@@ -351,7 +351,7 @@ func NewClyx(libfname string, libCode string) *Clyx {
var l []any var l []any
if s, ok := qVal.(string); ok { if s, ok := qVal.(string); ok {
l = make([]any, len(s)) l = make([]any, len(s))
for i, r := range s { l[i] = int64(r) } for i := 0; i < len(s); i++ { l[i] = int64(s[i]) }
} else { } else {
l = qVal.([]any) l = qVal.([]any)
} }
@@ -415,8 +415,46 @@ func NewClyx(libfname string, libCode string) *Clyx {
c.stack = append(c.stack, &ClientSocket{conn: conn, r: bufio.NewReader(conn)}) 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["write"] = func() {
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) } } 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["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["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["maked"] = func() { if err := os.Mkdir(c.pop().(string), 0755); err != nil { panic(err) } }
@@ -428,9 +466,16 @@ func NewClyx(libfname string, libCode string) *Clyx {
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["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["hsargs"] = func() { c.stack = append(c.stack, toAnySlice(os.Args)) }
c.words["hsexit"] = func() { os.Exit(int(toInt(c.pop()))) } 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["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["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) { regMath := func(name string, f func(float64) float64) {
c.words[name] = func() { c.stack = append(c.stack, f(toFloat(c.pop()))) } c.words[name] = func() { c.stack = append(c.stack, f(toFloat(c.pop()))) }
@@ -441,7 +486,7 @@ func NewClyx(libfname string, libCode string) *Clyx {
regMath("exp", math.Exp) regMath("exp", math.Exp)
regMath("log", math.Log) regMath("log", math.Log)
c.Run(`"::" [ next defw ] defw :: readf [ "r" fopen dup read swap close ] :: src [ readf i ]`) c.Run(`"::" [ next defw ] defw :: readf [ "r" fopen dup read swap close ] :: src [ readf l2s i ]`)
if libfname == "" { if libfname == "" {
c.Run(libCode) c.Run(libCode)
+38 -10
View File
@@ -19,13 +19,18 @@ replpath = _fpath(f'{scriptdir}/clyx_repl.clx', f'{scriptdir}/../../clyx_repl.cl
class Sock: class Sock:
def __init__(self, s): self.s = s def __init__(self, s): self.s = s
def write(self, d): return self.s.write(d) if hasattr(self.s, 'write') else self.send(d.encode('utf-8') if isinstance(d, str) else d) def write(self, d):
def send(self, d): return self.s.send(d) if hasattr(self.s, 'send') else getattr(self.s, 'write', lambda *a: 0)(d) data = d.encode('utf-8') if isinstance(d, str) else d
return self.s.write(data) if hasattr(self.s, 'write') else self.send(data)
def send(self, d):
data = d.encode('utf-8') if isinstance(d, str) else d
return self.s.send(data) if hasattr(self.s, 'send') else getattr(self.s, 'write', lambda *a: 0)(data)
def recv(self, n): def recv(self, n):
if hasattr(self.s, 'recv'): return self.s.recv(n)
b = bytearray(n); return bytes(b[:self.s.recv_into(b)]) if hasattr(self.s, 'recv_into') else getattr(self.s, 'read', lambda *a: b'')(n) b = bytearray(n); return bytes(b[:self.s.recv_into(b)]) if hasattr(self.s, 'recv_into') else getattr(self.s, 'read', lambda *a: b'')(n)
def readline(self): def readline(self):
if hasattr(self.s, 'readline'): return self.s.readline() if hasattr(self.s, 'readline'):
r = self.s.readline()
return r.decode('utf-8') if isinstance(r, bytes) else r
res = b'' res = b''
while not res.endswith(b'\n'): while not res.endswith(b'\n'):
c = self.recv(1) c = self.recv(1)
@@ -54,24 +59,47 @@ class Clyx:
'divmod': lambda: (lambda y: (lambda x: self._stack.extend(divmod(x, y)))(self._stack.pop()))(self._stack.pop()), 'shl': lambda: (lambda f, x: self._stack.append(x << f if f >= 0 else x >> -f))(self._stack.pop(), self._stack.pop()), '^': lambda: (lambda y: self._stack.append(self._stack.pop() ** y))(self._stack.pop()), 'divmod': lambda: (lambda y: (lambda x: self._stack.extend(divmod(x, y)))(self._stack.pop()))(self._stack.pop()), 'shl': lambda: (lambda f, x: self._stack.append(x << f if f >= 0 else x >> -f))(self._stack.pop(), self._stack.pop()), '^': lambda: (lambda y: self._stack.append(self._stack.pop() ** y))(self._stack.pop()),
'0=': lambda: self._stack.append(1 if self._stack.pop() == 0 else 0), '0<': lambda: self._stack.append(1 if self._stack.pop() < 0 else 0), 'nand': lambda: self._stack.append(~(self._stack.pop() & self._stack.pop())), 'defw': self._cmd_defw, 'type': lambda: self._stack.append(2 if isinstance(self._stack[-1], list) else (1 if isinstance(self._stack[-1], str) else 0)), '0=': lambda: self._stack.append(1 if self._stack.pop() == 0 else 0), '0<': lambda: self._stack.append(1 if self._stack.pop() < 0 else 0), 'nand': lambda: self._stack.append(~(self._stack.pop() & self._stack.pop())), 'defw': self._cmd_defw, 'type': lambda: self._stack.append(2 if isinstance(self._stack[-1], list) else (1 if isinstance(self._stack[-1], str) else 0)),
'next': lambda: self._stack.append(self._pop_caller_token()), '.': lambda: self._out_num(self._stack.pop()), 'emit': lambda: self._out_char(self._stack.pop()), 'next': lambda: self._stack.append(self._pop_caller_token()), '.': lambda: self._out_num(self._stack.pop()), 'emit': lambda: self._out_char(self._stack.pop()),
'fopen': lambda: (lambda m, f: self._stack.append(type('FD0', (), {'readline': lambda s: self._in_str() + '\n', 'read': lambda s: self._in_str(), 'fileno': lambda s: 0})() if f in (0, '0') else open(f, m)))(self._stack.pop(), self._stack.pop()), 'fopen': lambda: (lambda m, f: self._stack.append(type('FD0', (), {'readline': lambda s: self._in_str() + '\n', 'read': lambda s: self._in_str(), 'fileno': lambda s: 0})() if f in (0, '0') else open(f, m if 'b' in m else m + 'b')))(self._stack.pop(), self._stack.pop()),
'nopen': lambda: (lambda p, h: (lambda s: (lambda addr: (s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if hasattr(s, 'setsockopt') else None, s.bind(addr) if h == '0.0.0.0' else s.connect(addr), s.listen(1) if h == '0.0.0.0' else None, self._stack.append(s if h == '0.0.0.0' else Sock(getattr(s, 'makefile', lambda *a: s)('rw'))))[3])(socket.getaddrinfo(h, int(p))[0][-1] if hasattr(socket, 'getaddrinfo') else (h, int(p))))(socket.socket()))(self._stack.pop(), self._stack.pop()), 'nopen': lambda: (lambda p, h: (lambda s: (lambda addr: (s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if hasattr(s, 'setsockopt') else None, s.bind(addr) if h == '0.0.0.0' else s.connect(addr), s.listen(1) if h == '0.0.0.0' else None, self._stack.append(s if h == '0.0.0.0' else Sock(getattr(s, 'makefile', lambda *a: s)('rwb'))))[3])(socket.getaddrinfo(h, int(p))[0][-1] if hasattr(socket, 'getaddrinfo') else (h, int(p))))(socket.socket()))(self._stack.pop(), self._stack.pop()),
'write': lambda: (lambda d, fd: (self._stack.append(fd.write(d)), self._flush(fd)))(self._stack.pop(), self._stack.pop()), 'write': self._cmd_write,
'read': lambda: (lambda fd: self._stack.append((lambda c: Sock(getattr(c, 'makefile', lambda *a: c)('rw')))(fd.accept()[0]) if hasattr(fd, 'accept') else (lambda r: r.rstrip(b'\r\n' if isinstance(r, bytes) else '\r\n'))(fd.readline() if (fd is sys.stdin or fd.__class__.__name__ in ('FD0', 'Sock')) else fd.read())))(self._stack.pop()), 'read': self._cmd_read,
'close': lambda: (lambda fd: fd.close() if not (fd in (sys.stdin, sys.stdout, sys.stderr) or fd.__class__.__name__ == 'FD0') else None)(self._stack.pop()), 'close': lambda: (lambda fd: fd.close() if not (fd in (sys.stdin, sys.stdout, sys.stderr) or fd.__class__.__name__ == 'FD0') else None)(self._stack.pop()),
'delf': lambda: os.remove(self._stack.pop()), 'maked': lambda: os.mkdir(self._stack.pop()), 'listd': lambda: self._stack.append(os.listdir(self._stack.pop())), 'deld': lambda: os.rmdir(self._stack.pop()), 'delf': lambda: os.remove(self._stack.pop()), 'maked': lambda: os.mkdir(self._stack.pop()), 'listd': lambda: self._stack.append(os.listdir(self._stack.pop())), 'deld': lambda: os.rmdir(self._stack.pop()),
'rand': lambda: self._stack.append(randrange(int(self._stack.pop()))), '.s': lambda: self._out_num(repr(self._stack)), 'dip': lambda: (lambda q, y: (self._exec_quot(q), self._stack.append(y)))(self._stack.pop(), self._stack.pop()), 'rand': lambda: self._stack.append(randrange(int(self._stack.pop()))), '.s': lambda: self._out_num(repr(self._stack)), 'dip': lambda: (lambda q, y: (self._exec_quot(q), self._stack.append(y)))(self._stack.pop(), self._stack.pop()),
'loop': lambda: (lambda body, cond: self._exec_loop(cond, body))(self._stack.pop(), self._stack.pop()), 'loop': lambda: (lambda body, cond: self._exec_loop(cond, body))(self._stack.pop(), self._stack.pop()),
'hsargs': lambda: self._stack.append(list(sys.argv)), 'hsargs': lambda: self._stack.append(list(sys.argv)),
'hsexit': lambda: sys.exit(int(self._stack.pop())), 'hsexit': lambda: sys.exit(int(self._stack.pop())),
'l2s': lambda: self._stack.append("".join(chr(int(x)) for x in self._stack.pop())),
'hseval': lambda: self._stack.append(eval(self._stack.pop())), 'hseval': lambda: self._stack.append(eval(self._stack.pop())),
'chr': lambda: self._stack.append(chr(int(self._stack.pop()))), 'asc': lambda: self._stack.append(ord(self._stack.pop())), 'chr': lambda: self._stack.append(chr(int(self._stack.pop()))),
'sin': lambda: self._stack.append(math.sin(float(self._stack.pop()))), 'cos': lambda: self._stack.append(math.cos(float(self._stack.pop()))), 'atan': lambda: self._stack.append(math.atan(float(self._stack.pop()))), 'sin': lambda: self._stack.append(math.sin(float(self._stack.pop()))), 'cos': lambda: self._stack.append(math.cos(float(self._stack.pop()))), 'atan': lambda: self._stack.append(math.atan(float(self._stack.pop()))),
'exp': lambda: self._stack.append(math.exp(float(self._stack.pop()))), 'log': lambda: self._stack.append(math.log(float(self._stack.pop()))) 'exp': lambda: self._stack.append(math.exp(float(self._stack.pop()))), 'log': lambda: self._stack.append(math.log(float(self._stack.pop())))
} }
self.run('"::" [ next defw ] defw :: readf [ "r" fopen dup read swap close ] :: src [ readf i ]') self.run('"::" [ next defw ] defw :: readf [ "r" fopen dup read swap close ] :: src [ readf l2s i ]')
self.run(f'"{libpath}" src' if libfname is None else (libfname + ' src')) self.run(f'"{libpath}" src' if libfname is None else (libfname + ' src'))
def _cmd_write(self):
d, fd = self._stack.pop(), self._stack.pop()
data = bytes(int(x) for x in d) if isinstance(d, list) else (d.encode('utf-8') if isinstance(d, str) else d)
if hasattr(fd, 'write'):
n = fd.write(data)
self._flush(fd)
self._stack.append(n)
elif hasattr(fd, 'send'):
n = fd.send(data)
self._stack.append(n)
else:
self._stack.append(0)
def _cmd_read(self):
fd = self._stack.pop()
if hasattr(fd, 'accept'):
c, addr = fd.accept()
self._stack.append(Sock(getattr(c, 'makefile', lambda *a: c)('rwb')))
else:
r = fd.readline() if (fd is sys.stdin or fd.__class__.__name__ in ('FD0', 'Sock')) else fd.read()
r = r.rstrip(b'\r\n' if isinstance(r, bytes) else '\r\n')
res = list(r) if isinstance(r, (bytes, bytearray)) else [ord(c) for c in r]
self._stack.append(res)
def _cmd_qset(self): i, v, x = self._stack.pop(), self._stack.pop(), self._stack.pop(); (v.pop(int(i)), v.insert(int(i), x)) if isinstance(v, list) else None; self._stack.append(v if isinstance(v, list) else None) def _cmd_qset(self): i, v, x = self._stack.pop(), self._stack.pop(), self._stack.pop(); (v.pop(int(i)), v.insert(int(i), x)) if isinstance(v, list) else None; self._stack.append(v if isinstance(v, list) else None)
def _exec_loop(self, c, b): def _exec_loop(self, c, b):
while (self._exec_quot(c), self._stack.pop())[1]: self._exec_quot(b) while (self._exec_quot(c), self._stack.pop())[1]: self._exec_quot(b)
+12 -8
View File
@@ -2,7 +2,7 @@
Clyx is a concatenative, stack-oriented programming language designed for simplicity, minimalism, and metaprogramming capabilities. It uses postfix notation (aka Reverse Polish Notation, RPN), where operations are performed on a global stack. Clyx features homoiconicity: code is structured as quotations (lists), which can be manipulated as data and executed dynamically. Clyx is a concatenative, stack-oriented programming language designed for simplicity, minimalism, and metaprogramming capabilities. It uses postfix notation (aka Reverse Polish Notation, RPN), where operations are performed on a global stack. Clyx features homoiconicity: code is structured as quotations (lists), which can be manipulated as data and executed dynamically.
This document describes the stack effects and behavior of the **50 primitive core words**, **3 bootstrapped core words**, and **41 standard library words** present in the reference implementation. This document describes the stack effects and behavior of the **50 primitive core words**, **3 bootstrapped core words**, and **42 standard library words** present in the reference implementation.
The Clyx runtime itself allows the programmer to redefine any already defined word, including the core ones and the ones from the standard library. Unlike e.g. Forth, where words are resolved at compile-time, Clyx words are resolved at runtime, so such redefinitions may affect all previously established behavior. That's why, as a rule, any **predefined** Clyx word, both in the language core and the standard library, must not exceed 6 characters. This is done to encourage programmers to define their own human-readable words (7+ characters long) without any risk of overwriting existing word definitions. The Clyx runtime itself allows the programmer to redefine any already defined word, including the core ones and the ones from the standard library. Unlike e.g. Forth, where words are resolved at compile-time, Clyx words are resolved at runtime, so such redefinitions may affect all previously established behavior. That's why, as a rule, any **predefined** Clyx word, both in the language core and the standard library, must not exceed 6 characters. This is done to encourage programmers to define their own human-readable words (7+ characters long) without any risk of overwriting existing word definitions.
@@ -117,6 +117,14 @@ Any line fragment starting with `#` to the end of the physical source line is re
- **Stack Effect**: `( s -- code )` - **Stack Effect**: `( s -- code )`
- **Description**: Converts the first character of the string `s` to its integer ASCII character code. - **Description**: Converts the first character of the string `s` to its integer ASCII character code.
### `s2l` (String to List)
- **Stack Effect**: `( val -- [q] )`
- **Description**: Converts the value `val` (if it is a string) to a list of its byte values/integers. If `val` is already a list, it does nothing.
### `l2s` (List to String)
- **Stack Effect**: `( [q] -- s )`
- **Description**: Converts the list of integers `[q]` (character/byte codes) into its string representation `s`. Reverts the operation of `s2l`.
--- ---
## 4. Arithmetic & Mathematics ## 4. Arithmetic & Mathematics
@@ -322,12 +330,12 @@ These words are defined in Clyx itself during the interpreter's bootstrap phase:
- **Description**: Metaprogramming helper used to define inline words. Defined as `[ next defw ]`. - **Description**: Metaprogramming helper used to define inline words. Defined as `[ next defw ]`.
### `readf` (Read File) ### `readf` (Read File)
- **Stack Effect**: `( filename -- content )` - **Stack Effect**: `( filename -- [content] )`
- **Description**: Reads the entire contents of the file `filename` and pushes it as a string. - **Description**: Reads the entire contents of the file `filename` and pushes it as a list of bytes.
### `src` (Source) ### `src` (Source)
- **Stack Effect**: `( filename -- )` - **Stack Effect**: `( filename -- )`
- **Description**: Reads the file `filename` using `readf` and runs the resulting string of Clyx code in real-time using `i`. - **Description**: Reads the file `filename` using `readf`, converts the list of bytes to a string using `l2s`, and runs the resulting Clyx code in real-time using `i`.
--- ---
@@ -500,10 +508,6 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
- **Stack Effect**: `( s1 s2 -- [s1_s2] )` - **Stack Effect**: `( s1 s2 -- [s1_s2] )`
- **Description**: Concatenates `s1` and `s2` (which can be strings or lists of character codes) and pushes the result as a list of character codes representing the concatenated string. - **Description**: Concatenates `s1` and `s2` (which can be strings or lists of character codes) and pushes the result as a list of character codes representing the concatenated string.
#### `s2l` (String to List)
- **Stack Effect**: `( val -- [q] )`
- **Description**: Converts the value `val` (if it is a string) to a list of its ASCII character codes. If `val` is already a list, it does nothing.
### 10.8 List Operations ### 10.8 List Operations
#### `qpush` (Quotation Push) #### `qpush` (Quotation Push)
+1
View File
@@ -36,6 +36,7 @@
:: isquot [ type 2 = ] :: isquot [ type 2 = ]
:: vlen [ type 2 = if [ qlen ] [ slen ] ] :: vlen [ type 2 = if [ qlen ] [ slen ] ]
:: s2l [ type 2 = if [] [ dup slen 0 = if [ drop [] ] [ uncons drop swap cons ] ] ] :: s2l [ type 2 = if [] [ dup slen 0 = if [ drop [] ] [ uncons drop swap cons ] ] ]
:: asc [ s2l 0 qget ]
:: lcat [ [ rev ] dip swap uncons while [ [ swap ] dip swap cons swap uncons ] drop ] :: lcat [ [ rev ] dip swap uncons while [ [ swap ] dip swap cons swap uncons ] drop ]
:: streq [ dup vlen [ swap dup vlen ] dip = if [ 1 while [ uncons if [ [ swap uncons drop ] dip = if [ dup qlen 0 = if [ drop drop 1 0 ] [ swap 1 ] ] [ drop drop 0 0 ] ] [ drop drop 1 0 ] ] ] [ drop drop 0 ] ] :: streq [ dup vlen [ swap dup vlen ] dip = if [ 1 while [ uncons if [ [ swap uncons drop ] dip = if [ dup qlen 0 = if [ drop drop 1 0 ] [ swap 1 ] ] [ drop drop 0 0 ] ] [ drop drop 1 0 ] ] ] [ drop drop 0 ] ]
:: lower [ "" swap uncons while [ dup 65 >= [ dup 90 <= ] dip and if [ 32 + ] [] [ swap ] dip chr s+ swap uncons ] drop ] :: lower [ "" swap uncons while [ dup 65 >= [ dup 90 <= ] dip and if [ 32 + ] [] [ swap ] dip chr s+ swap uncons ] drop ]
+1
View File
@@ -160,6 +160,7 @@ if [
"aBc" upper "ABC" streq and "aBc" upper "ABC" streq and
"abc" "def" s+ "abcdef" streq and "abc" "def" s+ "abcdef" streq and
"abc" s2l [ 97 98 99 ] streq and "abc" s2l [ 97 98 99 ] streq and
"abc" s2l l2s "abc" streq and
if [ if [
" streq, lower, and upper PASS" puts cr " streq, lower, and upper PASS" puts cr
] [ ] [