v0.3.3: is lib word, UTF–8 string support
This commit is contained in:
@@ -10,7 +10,7 @@ Clyx aims for maximum portability and can be implemented in any modern runtime e
|
|||||||
|
|
||||||
The core spec of Clyx is mostly stable but the standard library is still a work-in-progress.
|
The core spec of Clyx is mostly stable but the standard library is still a work-in-progress.
|
||||||
|
|
||||||
The most recent Clyx version is **0.3.2**. The version is bumped every time the language core words (aka primitives), the standard library or the REPL get updated. The primitive set is expected to be indefinitely frozen once the version number reaches 1.0.
|
The most recent Clyx version is **0.3.3**. The version is bumped every time the language core words (aka primitives), the standard library or the REPL get updated. The primitive set is expected to be indefinitely frozen once the version number reaches 1.0.
|
||||||
|
|
||||||
## Language features
|
## Language features
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -284,7 +284,13 @@ func (c *Clyx) cmdDefw() {
|
|||||||
if isQForm { val = c.popCallerToken() }
|
if isQForm { val = c.popCallerToken() }
|
||||||
}
|
}
|
||||||
if val == nil { val = c.pop() }
|
if val == nil { val = c.pop() }
|
||||||
name := c.pop().(string)
|
nameVal := c.pop()
|
||||||
|
var name string
|
||||||
|
if s, ok := nameVal.(string); ok {
|
||||||
|
name = normToken(s).(string)
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%v", nameVal)
|
||||||
|
}
|
||||||
c.words[name] = val
|
c.words[name] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Clyx:
|
|||||||
self._words = {
|
self._words = {
|
||||||
'dup': lambda: self._stack.append(self._stack[-1]), 'drop': lambda: self._stack.pop(), 'swap': lambda: self._stack.extend([self._stack.pop(), self._stack.pop()]), 'i': lambda: self._eval(self._stack.pop()),
|
'dup': lambda: self._stack.append(self._stack[-1]), 'drop': lambda: self._stack.pop(), 'swap': lambda: self._stack.extend([self._stack.pop(), self._stack.pop()]), 'i': lambda: self._eval(self._stack.pop()),
|
||||||
'cond': lambda: (lambda c, t, f: self._exec_qform(t if c else f))(self._stack.pop(), self._stack.pop(), self._stack.pop()), 'cons': lambda: (lambda q: self._stack.append([self._stack.pop()] + q))(self._stack.pop()),
|
'cond': lambda: (lambda c, t, f: self._exec_qform(t if c else f))(self._stack.pop(), self._stack.pop(), self._stack.pop()), 'cons': lambda: (lambda q: self._stack.append([self._stack.pop()] + q))(self._stack.pop()),
|
||||||
'uncons': lambda: (lambda q: (lambda l: self._stack.extend([l, 0] if not l else [l[1:], l[0], 1]))(list(map(ord, q)) if isinstance(q, str) else q))(self._stack.pop()), 'qpop': lambda: (lambda q: self._stack.extend([q[:-1], q[-1]]))(self._stack.pop()),
|
'uncons': lambda: (lambda q: (lambda l: self._stack.extend([l, 0] if not l else [l[1:], l[0], 1]))(list(q.encode('utf-8')) if isinstance(q, str) else q))(self._stack.pop()), 'qpop': lambda: (lambda q: self._stack.extend([q[:-1], q[-1]]))(self._stack.pop()),
|
||||||
'qlen': lambda: (lambda v: self._stack.append(len(v) if isinstance(v, list) else 0))(self._stack.pop()), 'qget': lambda: (lambda i,v: self._stack.append(v[i] if isinstance(v, list) else None))(self._stack.pop(), self._stack.pop()),
|
'qlen': lambda: (lambda v: self._stack.append(len(v) if isinstance(v, list) else 0))(self._stack.pop()), 'qget': lambda: (lambda i,v: self._stack.append(v[i] if isinstance(v, list) else None))(self._stack.pop(), self._stack.pop()),
|
||||||
'qset': self._cmd_qset, 'slen': lambda: (lambda v: self._stack.append(len(v) if isinstance(v, str) else 0))(self._stack.pop()),
|
'qset': self._cmd_qset, 'slen': lambda: (lambda v: self._stack.append(len(v) if isinstance(v, str) else 0))(self._stack.pop()),
|
||||||
'+': lambda: self._stack.append(self._stack.pop() + self._stack.pop()), '*': lambda: self._stack.append(self._stack.pop() * self._stack.pop()), '-': lambda: (lambda y: self._stack.append(self._stack.pop() - y))(self._stack.pop()), '/': lambda: (lambda y: self._stack.append(self._stack.pop() / y))(self._stack.pop()),
|
'+': lambda: self._stack.append(self._stack.pop() + self._stack.pop()), '*': lambda: self._stack.append(self._stack.pop() * self._stack.pop()), '-': lambda: (lambda y: self._stack.append(self._stack.pop() - y))(self._stack.pop()), '/': lambda: (lambda y: self._stack.append(self._stack.pop() / y))(self._stack.pop()),
|
||||||
@@ -69,7 +69,7 @@ class Clyx:
|
|||||||
'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())),
|
||||||
'q2s': lambda: self._stack.append("".join(chr(int(x)) for x in self._stack.pop())),
|
'q2s': lambda: self._stack.append(bytes(int(x) for x in self._stack.pop()).decode('utf-8', errors='replace')),
|
||||||
'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()))),
|
'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()))),
|
||||||
@@ -104,7 +104,12 @@ class Clyx:
|
|||||||
def _exec_loop(self, c, b):
|
def _exec_loop(self, c, b):
|
||||||
while (self._exec_qform(c), self._stack.pop())[1]: self._exec_qform(b)
|
while (self._exec_qform(c), self._stack.pop())[1]: self._exec_qform(b)
|
||||||
_pop_caller_token = lambda self: (lambda t: (t.pop(0) if t[0] != '[' else self._parse_qform((t.pop(0), t)[1])) if t else None)(self._token_streams[-1] if self._token_streams else self._tokens)
|
_pop_caller_token = lambda self: (lambda t: (t.pop(0) if t[0] != '[' else self._parse_qform((t.pop(0), t)[1])) if t else None)(self._token_streams[-1] if self._token_streams else self._tokens)
|
||||||
def _cmd_defw(self): t = self._token_streams[-1] if self._token_streams else self._tokens; val = self._pop_caller_token() if (t and (t[0] == '[' or isinstance(t[0], list))) else self._stack.pop(); self._words[self._stack.pop()] = val
|
def _cmd_defw(self):
|
||||||
|
t = self._token_streams[-1] if self._token_streams else self._tokens
|
||||||
|
val = self._pop_caller_token() if (t and (t[0] == '[' or isinstance(t[0], list))) else self._stack.pop()
|
||||||
|
name = self._stack.pop()
|
||||||
|
if isinstance(name, str): name = self._norm_token(name)
|
||||||
|
self._words[name] = val
|
||||||
|
|
||||||
def _tokenize(self, code):
|
def _tokenize(self, code):
|
||||||
tokens = []
|
tokens = []
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "clyx"
|
name = "clyx"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
description = "Clyx programming language interpreter"
|
description = "Clyx programming language interpreter"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.7"
|
||||||
|
|||||||
+6
-1
@@ -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 Q-forms (special forms to represent data similar to Python or Tcl 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 Q-forms (special forms to represent data similar to Python or Tcl 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 **48 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 **49 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.
|
||||||
|
|
||||||
@@ -570,3 +570,8 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
|
|||||||
- **Syntax**: `_start [actions]`
|
- **Syntax**: `_start [actions]`
|
||||||
- **Stack Effect**: `( -- )` (parses actions Q-form from the execution stream).
|
- **Stack Effect**: `( -- )` (parses actions Q-form from the execution stream).
|
||||||
- **Description**: Defines the entry point of the program by setting the `_main` word to execute `[actions]`.
|
- **Description**: Defines the entry point of the program by setting the `_main` word to execute `[actions]`.
|
||||||
|
|
||||||
|
#### `is`
|
||||||
|
- **Syntax**: `is name [actions]`
|
||||||
|
- **Stack Effect**: `( -- )` (parses name and actions Q-form from the execution stream).
|
||||||
|
- **Description**: A synonym/helper for `::` used to define inline words. Defined as `[ next defw ]`.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Clyx standard library (in addition to [ :: readf src ])
|
# Clyx standard library (in addition to [ :: readf src ])
|
||||||
# Created by Luxferre in 2026, released into the public domain
|
# Created by Luxferre in 2026, released into the public domain
|
||||||
|
|
||||||
|
:: is [ next defw ]
|
||||||
:: source [ next src ]
|
:: source [ next src ]
|
||||||
:: then [ ]
|
:: then [ ]
|
||||||
:: _start [ next _main swap defw ]
|
:: _start [ next _main swap defw ]
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ if [
|
|||||||
] [
|
] [
|
||||||
" q2s, streq, lower, and upper FAIL" puts cr
|
" q2s, streq, lower, and upper FAIL" puts cr
|
||||||
]
|
]
|
||||||
|
"ñ" s2q [ 195 177 ] streq
|
||||||
|
[ 195 177 ] q2s "ñ" streq and
|
||||||
|
"ñ" slen 1 = and
|
||||||
|
if [
|
||||||
|
" Unicode and s2q/q2s PASS" puts cr
|
||||||
|
] [
|
||||||
|
" Unicode and s2q/q2s FAIL" puts cr
|
||||||
|
]
|
||||||
|
|
||||||
# 8. Q-form Operations
|
# 8. Q-form Operations
|
||||||
"Testing Q-form operations..." puts cr
|
"Testing Q-form operations..." puts cr
|
||||||
@@ -216,6 +224,11 @@ if [
|
|||||||
my_inline 100 = if [ " :: (inline) PASS" puts cr ] [ " :: (inline) FAIL" puts cr ] # Test inline ::
|
my_inline 100 = if [ " :: (inline) PASS" puts cr ] [ " :: (inline) FAIL" puts cr ] # Test inline ::
|
||||||
5 then 5 = if [ " then PASS" puts cr ] [ " then FAIL" puts cr ] # Test then
|
5 then 5 = if [ " then PASS" puts cr ] [ " then FAIL" puts cr ] # Test then
|
||||||
":: source_test_word [ 99 ]" "test_source.clx" writef drop source test_source.clx source_test_word 99 = if [ "test_source.clx" delf " source PASS" puts cr ] [ "test_source.clx" delf " source FAIL" puts cr ] # Test source
|
":: source_test_word [ 99 ]" "test_source.clx" writef drop source test_source.clx source_test_word 99 = if [ "test_source.clx" delf " source PASS" puts cr ] [ "test_source.clx" delf " source FAIL" puts cr ] # Test source
|
||||||
|
is my_is_word [ 200 ]
|
||||||
|
my_is_word 200 =
|
||||||
|
is "my_is_string_word" [ 300 ]
|
||||||
|
my_is_string_word 300 = and
|
||||||
|
if [ " is PASS" puts cr ] [ " is FAIL" puts cr ] # Test is
|
||||||
" _start PASS" puts cr # Test _start (executed via entry point)
|
" _start PASS" puts cr # Test _start (executed via entry point)
|
||||||
|
|
||||||
# 11. Host Language Evaluation: hseval
|
# 11. Host Language Evaluation: hseval
|
||||||
|
|||||||
Reference in New Issue
Block a user