q-form renaming effort

This commit is contained in:
Luxferre
2026-07-04 16:56:08 +03:00
parent 33a94f0084
commit 46ff217e14
6 changed files with 96 additions and 97 deletions
+4 -5
View File
@@ -10,11 +10,10 @@ The core spec of Clyx is mostly stable but the standard library is still a work-
## Language features ## Language features
- Single, unbounded data stack that can hold numbers, strings and quotations (lists) - Single, unbounded data stack that can hold numbers, strings and lists (here called Q-forms)
- Forth-like parsing, RPN (postfix notation) and word definition mechanics - Forth-like parsing, RPN (postfix notation) and word definition mechanics
- Tcl-like quotation (list) bracket syntax - Tcl-like bracket syntax and Janet-like list operations on Q-forms
- Janet-like quotation (list) operations - Homoiconicity: Q-forms and strings can be manipulated as data and executed dynamically
- Homoiconicity: quotations and strings can be manipulated as data and executed dynamically
- Character I/O (implementation-dependent) - Character I/O (implementation-dependent)
- File I/O (may not support all features on some targets) - File I/O (may not support all features on some targets)
- TCP socket I/O (may not be supported on some targets) - TCP socket I/O (may not be supported on some targets)
@@ -175,7 +174,7 @@ Additionally, this name can be viewed as an acronym hinting at the order some bi
More of a high-level one (because you don't have any memory control), but in fact it can be both. More of a high-level one (because you don't have any memory control), but in fact it can be both.
Clyx combines the conceptual simplicity of Forth (and concatenative/RPN programming in general) with list definition syntax, list manipulation and metaprogramming capabilities of Janet and Tcl, making it much more practical for everyday tasks. At the same time, you can build abstraction levels as high as you need to, making it a nice choice for creating custom DSLs (domain-specific languages), especially with the `next` word that pushes the next logical token onto the stack, allowing to change token processing order. Clyx combines the conceptual simplicity of Forth (and concatenative/RPN programming in general) with list definition syntax, manipulation and metaprogramming capabilities of Janet and Tcl, making it much more practical for everyday tasks. At the same time, you can build abstraction levels as high as you need to, making it a nice choice for creating custom DSLs (domain-specific languages), especially with the `next` word that pushes the next logical token onto the stack, allowing to change token processing order.
### Why not just pick up a Forth instead? ### Why not just pick up a Forth instead?
+28 -28
View File
@@ -182,19 +182,19 @@ func normToken(t string) any {
return t return t
} }
func parseQuot(tokens *[]any) ([]any, error) { func parseQForm(tokens *[]any) ([]any, error) {
var quot []any var qform []any
for { for {
if len(*tokens) == 0 { return nil, fmt.Errorf("Syntax error: unbalanced brackets") } if len(*tokens) == 0 { return nil, fmt.Errorf("Syntax error: unbalanced brackets") }
t := (*tokens)[0] t := (*tokens)[0]
*tokens = (*tokens)[1:] *tokens = (*tokens)[1:]
if s, ok := t.(string); ok && s == "]" { return quot, nil } if s, ok := t.(string); ok && s == "]" { return qform, nil }
if s, ok := t.(string); ok && s == "[" { if s, ok := t.(string); ok && s == "[" {
sub, err := parseQuot(tokens) sub, err := parseQForm(tokens)
if err != nil { return nil, err } if err != nil { return nil, err }
quot = append(quot, sub) qform = append(qform, sub)
} else { } else {
if s, ok := t.(string); ok { quot = append(quot, normToken(s)) } else { quot = append(quot, t) } if s, ok := t.(string); ok { qform = append(qform, normToken(s)) } else { qform = append(qform, t) }
} }
} }
} }
@@ -225,17 +225,17 @@ func (c *Clyx) reprStack() string {
return "[" + strings.Join(parts, ", ") + "]" return "[" + strings.Join(parts, ", ") + "]"
} }
func (c *Clyx) execQuot(q []any) { func (c *Clyx) execQForm(q []any) {
c.tokenStreams = append(c.tokenStreams, c.tokens) c.tokenStreams = append(c.tokenStreams, c.tokens)
c.tokens = make([]any, len(q)) c.tokens = make([]any, len(q))
copy(c.tokens, q) copy(c.tokens, q)
for len(c.tokens) > 0 { for len(c.tokens) > 0 {
t := c.tokens[0] t := c.tokens[0]
c.tokens = c.tokens[1:] c.tokens = c.tokens[1:]
if listVal, ok := t.([]any); ok { if qformVal, ok := t.([]any); ok {
c.stack = append(c.stack, listVal) c.stack = append(c.stack, qformVal)
} else if s, ok := t.(string); ok && s == "[" { } else if s, ok := t.(string); ok && s == "[" {
qParsed, err := parseQuot(&c.tokens) qParsed, err := parseQForm(&c.tokens)
if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); break } if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); break }
c.stack = append(c.stack, qParsed) c.stack = append(c.stack, qParsed)
} else { } else {
@@ -247,7 +247,7 @@ func (c *Clyx) execQuot(q []any) {
if isWord { if isWord {
switch val := v.(type) { switch val := v.(type) {
case func(): val() case func(): val()
case []any: c.execQuot(val) case []any: c.execQForm(val)
default: c.stack = append(c.stack, val) default: c.stack = append(c.stack, val)
} }
} else { } else {
@@ -266,7 +266,7 @@ func (c *Clyx) popCallerToken() any {
t := (*stream)[0] t := (*stream)[0]
*stream = (*stream)[1:] *stream = (*stream)[1:]
if s, ok := t.(string); ok && s == "[" { if s, ok := t.(string); ok && s == "[" {
qParsed, err := parseQuot(stream) qParsed, err := parseQForm(stream)
if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); return nil } if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err); return nil }
return qParsed return qParsed
} }
@@ -279,9 +279,9 @@ func (c *Clyx) cmdDefw() {
var val any var val any
if len(*stream) > 0 { if len(*stream) > 0 {
t0 := (*stream)[0] t0 := (*stream)[0]
isQuot := false isQForm := false
if s, ok := t0.(string); ok && s == "[" { isQuot = true } else if _, ok := t0.([]any); ok { isQuot = true } if s, ok := t0.(string); ok && s == "[" { isQForm = true } else if _, ok := t0.([]any); ok { isQForm = true }
if isQuot { val = c.popCallerToken() } if isQForm { val = c.popCallerToken() }
} }
if val == nil { val = c.pop() } if val == nil { val = c.pop() }
name := c.pop().(string) name := c.pop().(string)
@@ -299,7 +299,7 @@ func (c *Clyx) cmdType() {
} }
func (c *Clyx) Run(code string) { func (c *Clyx) Run(code string) {
c.execQuot(toAnySlice(tokenize(code))) c.execQForm(toAnySlice(tokenize(code)))
} }
func (c *Clyx) Eval(q any) { func (c *Clyx) Eval(q any) {
@@ -307,11 +307,11 @@ func (c *Clyx) Eval(q any) {
if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", r) } if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", r) }
}() }()
if s, ok := q.(string); ok { if s, ok := q.(string); ok {
c.execQuot(toAnySlice(tokenize(s))) c.execQForm(toAnySlice(tokenize(s)))
} else if slice, ok := q.([]any); ok { } else if slice, ok := q.([]any); ok {
c.execQuot(slice) c.execQForm(slice)
} else { } else {
panic("eval expects string/quotation") panic("eval expects string or Q-form")
} }
} }
@@ -345,7 +345,7 @@ func NewClyx(libfname string, libCode string) *Clyx {
cVal, tVal, fVal := c.pop(), c.pop(), c.pop() cVal, tVal, fVal := c.pop(), c.pop(), c.pop()
runVal := fVal runVal := fVal
if isTruthy(cVal) { runVal = tVal } if isTruthy(cVal) { runVal = tVal }
if listVal, ok := runVal.([]any); ok { c.execQuot(listVal) } else { c.execQuot([]any{runVal}) } if qformVal, ok := runVal.([]any); ok { c.execQForm(qformVal) } else { c.execQForm([]any{runVal}) }
} }
c.words["cons"] = func() { q, x := c.pop().([]any), c.pop(); c.stack = append(c.stack, append([]any{x}, q...)) } c.words["cons"] = func() { q, x := c.pop().([]any), c.pop(); c.stack = append(c.stack, append([]any{x}, q...)) }
c.words["uncons"] = func() { c.words["uncons"] = func() {
@@ -420,16 +420,16 @@ func NewClyx(libfname string, libCode string) *Clyx {
c.words["write"] = func() { c.words["write"] = func() {
data, fd := c.pop(), c.pop() data, fd := c.pop(), c.pop()
var toWrite string var toWrite string
if listVal, ok := data.([]any); ok { if qformVal, ok := data.([]any); ok {
var sb strings.Builder var sb strings.Builder
for _, val := range listVal { for _, val := range qformVal {
sb.WriteByte(byte(toInt(val))) sb.WriteByte(byte(toInt(val)))
} }
toWrite = sb.String() toWrite = sb.String()
} else if strVal, ok := data.(string); ok { } else if strVal, ok := data.(string); ok {
toWrite = strVal toWrite = strVal
} else { } else {
panic("write expects string or list") panic("write expects string or Q-form")
} }
n, err := fd.(ClyxFile).Write(toWrite) n, err := fd.(ClyxFile).Write(toWrite)
if err != nil { if err != nil {
@@ -450,11 +450,11 @@ func NewClyx(libfname string, libCode string) *Clyx {
if err != nil { if err != nil {
panic(err) panic(err)
} }
var list []any var qform []any
for i := 0; i < len(res); i++ { for i := 0; i < len(res); i++ {
list = append(list, int64(res[i])) qform = append(qform, int64(res[i]))
} }
c.stack = append(c.stack, list) c.stack = append(c.stack, qform)
} }
} }
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() } }
@@ -464,8 +464,8 @@ func NewClyx(libfname string, libCode string) *Clyx {
c.words["deld"] = func() { if err := os.Remove(c.pop().(string)); err != nil { panic(err) } } 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["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[".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["dip"] = func() { q, y := c.pop().([]any), c.pop(); c.execQForm(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["loop"] = func() { body, cond := c.pop().([]any), c.pop().([]any); for { c.execQForm(cond); if !isTruthy(c.pop()) { break }; c.execQForm(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() { c.words["l2s"] = func() {
+13 -13
View File
@@ -51,7 +51,7 @@ class Clyx:
self._stack, self._tokens, self._token_streams = [], [], [] self._stack, self._tokens, self._token_streams = [], [], []
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_quot(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(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()),
'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()),
@@ -65,7 +65,7 @@ class Clyx:
'read': self._cmd_read, '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_qform(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())),
@@ -102,8 +102,8 @@ class Clyx:
self._stack.append(res) 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_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_quot((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(); self._words[self._stack.pop()] = val
def _tokenize(self, code): def _tokenize(self, code):
@@ -116,13 +116,13 @@ class Clyx:
_next_token = lambda self: self._tokens.pop(0) if self._tokens else None _next_token = lambda self: self._tokens.pop(0) if self._tokens else None
def _parse_quot(self, tokens=None): def _parse_qform(self, tokens=None):
tokens, quot = self._tokens if tokens is None else tokens, [] tokens, qform = self._tokens if tokens is None else tokens, []
try: try:
while True: while True:
t = tokens.pop(0) t = tokens.pop(0)
if t == ']': return quot if t == ']': return qform
quot.append(self._parse_quot(tokens) if t == '[' else self._norm_token(t)) qform.append(self._parse_qform(tokens) if t == '[' else self._norm_token(t))
except IndexError: raise SyntaxError('Syntax error: unbalanced brackets') except IndexError: raise SyntaxError('Syntax error: unbalanced brackets')
def _norm_token(self, t): def _norm_token(self, t):
@@ -130,25 +130,25 @@ class Clyx:
except: return t[1:-1].replace('\\\\', '\x01').replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r').replace('\\s', ' ').replace('\\b', '\b').replace('\\f', '\f').replace('\\"', '"').replace('\x01', '\\') if (t.startswith('"') and t.endswith('"')) else t except: return t[1:-1].replace('\\\\', '\x01').replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r').replace('\\s', ' ').replace('\\b', '\b').replace('\\f', '\f').replace('\\"', '"').replace('\x01', '\\') if (t.startswith('"') and t.endswith('"')) else t
_flush = lambda self, fd: getattr(fd, 'flush', lambda: None)() _flush = lambda self, fd: getattr(fd, 'flush', lambda: None)()
def _exec_quot(self, q): def _exec_qform(self, q):
self._token_streams.append(self._tokens); self._tokens = list(q) self._token_streams.append(self._tokens); self._tokens = list(q)
while self._tokens: while self._tokens:
t = self._next_token() t = self._next_token()
if isinstance(t, list): self._stack.append(t) if isinstance(t, list): self._stack.append(t)
elif t == '[': self._stack.append(self._parse_quot(self._tokens)) elif t == '[': self._stack.append(self._parse_qform(self._tokens))
else: else:
t = self._norm_token(t) if isinstance(t, str) else t t = self._norm_token(t) if isinstance(t, str) else t
v = self._words.get(t) if isinstance(t, str) else None v = self._words.get(t) if isinstance(t, str) else None
if callable(v): v() if callable(v): v()
elif isinstance(v, list): self._exec_quot(v) elif isinstance(v, list): self._exec_qform(v)
elif v is not None: self._stack.append(v) elif v is not None: self._stack.append(v)
else: self._stack.append(t) else: self._stack.append(t)
self._tokens = self._token_streams.pop() self._tokens = self._token_streams.pop()
def _eval(self, q): def _eval(self, q):
try: self._exec_quot(self._tokenize(q) if isinstance(q, str) else q) try: self._exec_qform(self._tokenize(q) if isinstance(q, str) else q)
except Exception as e: sys.stderr.write(f'Error: {e}\n') except Exception as e: sys.stderr.write(f'Error: {e}\n')
run = lambda self, code: self._exec_quot(self._tokenize(code)) run = lambda self, code: self._exec_qform(self._tokenize(code))
def clyx(f): def clyx(f):
try: try:
+44 -44
View File
@@ -1,6 +1,6 @@
# Clyx Reference Manual # Clyx Reference Manual
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 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 **42 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.
@@ -25,8 +25,8 @@ Stack effects are described in the format:
``` ```
Where: Where:
- The rightmost element is the top of the stack. - The rightmost element is the top of the stack.
- `x`, `y`, `z` represent values (numbers, strings, or quotations). - `x`, `y`, `z` represent values (numbers, strings, or Q-forms).
- `[q]` represents a quotation (a block of code enclosed in brackets, e.g., `[ 1 + ]`). - `[q]` represents a Q-form (a block of code or data enclosed in brackets, e.g., `[ 1 + ]`).
- `s` represents a string. - `s` represents a string.
- `c` represents a boolean flag (`1` for true, `0` for false). - `c` represents a boolean flag (`1` for true, `0` for false).
@@ -37,7 +37,7 @@ Where:
Clyx supports three basic data types: Clyx supports three basic data types:
1. **Numbers**: Integers (e.g., `42`) and Floating-point numbers (e.g., `3.14`). 1. **Numbers**: Integers (e.g., `42`) and Floating-point numbers (e.g., `3.14`).
2. **Strings**: Sequences of characters enclosed in double quotes. Escaping double quotes (`\"`), backslashes (`\\`), and whitespace characters (`\n` for newline, `\t` for tab, `\r` for carriage return, `\s` for space, `\b` for backspace, `\f` for form feed) is supported inside strings using a backslash (e.g., `"hello \"world\""`, `"a\\b"`, or `"hello\nworld"`). 2. **Strings**: Sequences of characters enclosed in double quotes. Escaping double quotes (`\"`), backslashes (`\\`), and whitespace characters (`\n` for newline, `\t` for tab, `\r` for carriage return, `\s` for space, `\b` for backspace, `\f` for form feed) is supported inside strings using a backslash (e.g., `"hello \"world\""`, `"a\\b"`, or `"hello\nworld"`).
3. **Quotations**: Ordered lists of values or code blocks enclosed in square brackets (e.g., `[ 1 2 + ]`). 3. **Q-forms** (short for _quotation forms_): Ordered lists of values or code blocks enclosed in square brackets (e.g., `[ 1 2 + ]`).
### Word Name Collision Rule ### Word Name Collision Rule
In Clyx, the evaluation engine executes any string token that matches a defined word name. This means that double-quoted string literals that match a registered word name (such as `"dup"`, `"+"`, or `"."`) **will be executed as that word** when evaluated, rather than being pushed as a literal string. In Clyx, the evaluation engine executes any string token that matches a defined word name. This means that double-quoted string literals that match a registered word name (such as `"dup"`, `"+"`, or `"."`) **will be executed as that word** when evaluated, rather than being pushed as a literal string.
@@ -75,31 +75,31 @@ Any line fragment starting with `#` to the end of the physical source line is re
--- ---
## 2. Quotation Operations (Lists) ## 2. Q-form Operations
### `cons` (CONStruct) ### `cons` (CONStruct)
- **Stack Effect**: `( x [q] -- [x q...] )` - **Stack Effect**: `( x [q] -- [x q...] )`
- **Description**: Prepends the element `x` to the beginning of the quotation/list `[q]`. - **Description**: Prepends the element `x` to the beginning of the Q-form `[q]`.
### `uncons` (UNCONStruct) ### `uncons` (UNCONStruct)
- **Stack Effect**: `( [q] -- [q_rest] x 1 )` or `( [] -- [] 0 )` - **Stack Effect**: `( [q] -- [q_rest] x 1 )` or `( [] -- [] 0 )`
- **Description**: Deconstructs a quotation or string. If it is non-empty, it pushes the remaining list/string, the first element/character code, and a success flag `1`. If it is empty, it pushes the empty list/string and a failure flag `0`. - **Description**: Deconstructs a Q-form or string. If it is non-empty, it pushes the remaining Q-form/string, the first element/character code, and a success flag `1`. If it is empty, it pushes the empty Q-form/string and a failure flag `0`.
### `qpop` (Quotation Pop) ### `qpop` (Q-form Pop)
- **Stack Effect**: `( [q] -- [q_rest] x )` - **Stack Effect**: `( [q] -- [q_rest] x )`
- **Description**: Pops the last element `x` from the quotation/list `[q]`, returning the remaining list and the popped element. - **Description**: Pops the last element `x` from the Q-form `[q]`, returning the remaining Q-form and the popped element.
### `qlen` (Quotation Length) ### `qlen` (Q-form Length)
- **Stack Effect**: `( [q] -- len )` - **Stack Effect**: `( [q] -- len )`
- **Description**: Pushes the length of the list `[q]`. Pushes `0` if the operand is not a list. - **Description**: Pushes the length of the Q-form `[q]`. Pushes `0` if the operand is not a Q-form.
### `qget` (Quotation Get) ### `qget` (Q-form Get)
- **Stack Effect**: `( [q] idx -- x )` - **Stack Effect**: `( [q] idx -- x )`
- **Description**: Pushes the element at index `idx` from the list `[q]`. Pushes `None` if the operand is not a list. - **Description**: Pushes the element at index `idx` from the Q-form `[q]`. Pushes `None` if the operand is not a Q-form.
### `qset` (Quotation Set) ### `qset` (Q-form Set)
- **Stack Effect**: `( x [q] idx -- [q_new] )` - **Stack Effect**: `( x [q] idx -- [q_new] )`
- **Description**: Sets the element at index `idx` in the list `[q]` to the value `x`, returning the modified list. Pushes `None` if the operand is not a list. - **Description**: Sets the element at index `idx` in the Q-form `[q]` to the value `x`, returning the modified Q-form. Pushes `None` if the operand is not a Q-form.
--- ---
@@ -117,13 +117,13 @@ 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) ### `s2l` (String to Q-form)
- **Stack Effect**: `( val -- [q] )` - **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. - **Description**: Converts the value `val` (if it is a string) to a Q-form of its byte values/integers. If `val` is already a Q-form, it does nothing.
### `l2s` (List to String) ### `l2s` (Q-form to String)
- **Stack Effect**: `( [q] -- s )` - **Stack Effect**: `( [q] -- s )`
- **Description**: Converts the list of integers `[q]` (character/byte codes) into its string representation `s`. Reverts the operation of `s2l`. - **Description**: Converts the Q-form of integers `[q]` (character/byte codes) into its string representation `s`. Reverts the operation of `s2l`.
--- ---
@@ -195,15 +195,15 @@ Any line fragment starting with `#` to the end of the physical source line is re
### `i` (Interpret) ### `i` (Interpret)
- **Stack Effect**: `( [q] -- )` - **Stack Effect**: `( [q] -- )`
- **Description**: "De-quotes" and immediately executes the quotation `[q]`. - **Description**: "De-quotes" and immediately executes the Q-form `[q]`.
### `cond` ### `cond`
- **Stack Effect**: `( f t c -- )` - **Stack Effect**: `( f t c -- )`
- **Description**: Pops the condition `c`, the true branch quotation `t`, and the false branch quotation `f`. If `c` is non-zero, executes `t`; otherwise executes `f`. - **Description**: Pops the condition `c`, the true branch Q-form `t`, and the false branch Q-form `f`. If `c` is non-zero, executes `t`; otherwise executes `f`.
### `loop` ### `loop`
- **Stack Effect**: `( [cond] [body] -- )` - **Stack Effect**: `( [cond] [body] -- )`
- **Description**: Repeatedly executes the condition quotation `[cond]`. If the top element of the stack after `[cond]` is non-zero, executes `[body]` and loops again; otherwise terminates. - **Description**: Repeatedly executes the condition Q-form `[cond]`. If the top element of the stack after `[cond]` is non-zero, executes `[body]` and loops again; otherwise terminates.
### `dip` (Stack-dip) ### `dip` (Stack-dip)
- **Stack Effect**: `( y [q] -- y )` - **Stack Effect**: `( y [q] -- y )`
@@ -215,7 +215,7 @@ Any line fragment starting with `#` to the end of the physical source line is re
### `defw` (Define Word) ### `defw` (Define Word)
- **Stack Effect**: `( name [q] -- )` or `( name x -- )` - **Stack Effect**: `( name [q] -- )` or `( name x -- )`
- **Description**: Defines a new word associated with `name`. If the next token in the interpreter stream is `[`, parses it as a quotation and defines `name` to execute that quotation. Otherwise, defines `name` to represent the top stack value `x`. - **Description**: Defines a new word associated with `name`. If the next token in the interpreter stream is `[`, parses it as a Q-form and defines `name` to execute that Q-form. Otherwise, defines `name` to represent the top stack value `x`.
### `next` (Next Token) ### `next` (Next Token)
- **Stack Effect**: `( -- token )` - **Stack Effect**: `( -- token )`
@@ -223,7 +223,7 @@ Any line fragment starting with `#` to the end of the physical source line is re
### `type` ### `type`
- **Stack Effect**: `( x -- x type_code )` - **Stack Effect**: `( x -- x type_code )`
- **Description**: Inspects the top element of the stack `x` (without popping it) and pushes its type code: `0` if it's a number, `1` if it's a string, or `2` if it's a list (quotation). - **Description**: Inspects the top element of the stack `x` (without popping it) and pushes its type code: `0` if it's a number, `1` if it's a string, or `2` if it's a Q-form.
--- ---
@@ -277,7 +277,7 @@ Any line fragment starting with `#` to the end of the physical source line is re
### `listd` (List Directory) ### `listd` (List Directory)
- **Stack Effect**: `( path -- [contents] )` - **Stack Effect**: `( path -- [contents] )`
- **Description**: Lists the contents of the directory at `path`. Pushes a quotation containing a list of strings representing the names of the files and directories inside the directory. - **Description**: Lists the contents of the directory at `path`. Pushes a Q-form of strings representing the names of the files and directories inside the directory.
**N.B.**: this primitive may not be supported on all targets. **N.B.**: this primitive may not be supported on all targets.
@@ -303,9 +303,9 @@ Any line fragment starting with `#` to the end of the physical source line is re
### `hsargs` (Host Arguments) ### `hsargs` (Host Arguments)
- **Stack Effect**: `( -- [args] )` - **Stack Effect**: `( -- [args] )`
- **Description**: Pushes a list/quotation of all passed command line arguments onto the stack. - **Description**: Pushes a Q-form of all passed command line arguments onto the stack.
**N.B.**: on the platforms that don't support CLI arguments, this primitive will always push an empty quotation. **N.B.**: on the platforms that don't support CLI arguments, this primitive will always push an empty Q-form.
### `hsexit` (Host Exit) ### `hsexit` (Host Exit)
- **Stack Effect**: `( code -- )` - **Stack Effect**: `( code -- )`
@@ -333,11 +333,11 @@ These words are defined in Clyx itself during the interpreter's bootstrap phase:
### `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 list of bytes. - **Description**: Reads the entire contents of the file `filename` and pushes it as a Q-form of byte values.
### `src` (Source) ### `src` (Source)
- **Stack Effect**: `( filename -- )` - **Stack Effect**: `( filename -- )`
- **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`. - **Description**: Reads the file `filename` using `readf`, converts the Q-form of bytes to a string using `l2s`, and runs the resulting Clyx code in real-time using `i`.
--- ---
@@ -367,12 +367,12 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
#### `if` #### `if`
- **Syntax**: `if [true_actions] [false_actions]` - **Syntax**: `if [true_actions] [false_actions]`
- **Stack Effect**: `( c -- )` (pops condition from stack, parses branch quotations from the execution stream). - **Stack Effect**: `( c -- )` (pops condition from stack, parses branch Q-forms from the execution stream).
- **Description**: Executes `true_actions` if `c` is non-zero, otherwise executes `false_actions`. - **Description**: Executes `true_actions` if `c` is non-zero, otherwise executes `false_actions`.
#### `while` #### `while`
- **Syntax**: `while [actions]` - **Syntax**: `while [actions]`
- **Stack Effect**: `( -- )` (parses body quotation from the execution stream). - **Stack Effect**: `( -- )` (parses body Q-form from the execution stream).
- **Description**: Loops repeatedly. The body `actions` is executed, and must leave the next condition value on the stack. If the condition is non-zero, the loop repeats; otherwise it terminates. - **Description**: Loops repeatedly. The body `actions` is executed, and must leave the next condition value on the stack. If the condition is non-zero, the loop repeats; otherwise it terminates.
### 10.3 Mathematics & Arithmetic ### 10.3 Mathematics & Arithmetic
@@ -435,7 +435,7 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
#### `puts` (Put String) #### `puts` (Put String)
- **Stack Effect**: `( s -- )` or `( [q] -- )` - **Stack Effect**: `( s -- )` or `( [q] -- )`
- **Description**: Outputs a string or list of character codes to standard output character-by-character. - **Description**: Outputs a string or Q-form of character codes to standard output character-by-character.
#### `readln` (Read Line) #### `readln` (Read Line)
- **Stack Effect**: `( -- s )` - **Stack Effect**: `( -- s )`
@@ -461,9 +461,9 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
- **Stack Effect**: `( x -- x c )` - **Stack Effect**: `( x -- x c )`
- **Description**: Pushes `1` if the top stack element `x` is a string; otherwise pushes `0`. Does not pop `x`. - **Description**: Pushes `1` if the top stack element `x` is a string; otherwise pushes `0`. Does not pop `x`.
#### `isquot` (Is Quotation) #### `isq` (Is Q-form)
- **Stack Effect**: `( x -- x c )` - **Stack Effect**: `( x -- x c )`
- **Description**: Pushes `1` if the top stack element `x` is a list (quotation); otherwise pushes `0`. Does not pop `x`. - **Description**: Pushes `1` if the top stack element `x` is a Q-form; otherwise pushes `0`. Does not pop `x`.
#### `=` (Equal) #### `=` (Equal)
- **Stack Effect**: `( a b -- c )` - **Stack Effect**: `( a b -- c )`
@@ -490,13 +490,13 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
- **Description**: Pops `b`, pops `a`, and pushes `1` if `a` is less than or equal to `b`; otherwise pushes `0`. - **Description**: Pops `b`, pops `a`, and pushes `1` if `a` is less than or equal to `b`; otherwise pushes `0`.
#### `vlen` (Value Length) #### `vlen` (Value Length)
- **Stack Effect**: `( val -- len )` - **Stack Effect**: `( val -- len )`
- **Description**: Pushes the length of `val`. If `val` is a list, it returns the number of elements; if it is a string, it returns the number of characters. - **Description**: Pushes the length of `val`. If `val` is a Q-form, it returns the number of elements; if it is a string, it returns the number of characters.
### 10.7 String Operations ### 10.7 String Operations
#### `streq` (String Equal) #### `streq` (String Equal)
- **Stack Effect**: `( s1 s2 -- c )` - **Stack Effect**: `( s1 s2 -- c )`
- **Description**: Pops `s2`, pops `s1`, and pushes `1` if the two strings `s1` and `s2` (or their character list equivalents) are equal; otherwise pushes `0`. - **Description**: Pops `s2`, pops `s1`, and pushes `1` if the two strings `s1` and `s2` (or their character Q-form equivalents) are equal; otherwise pushes `0`.
#### `lower` (To Lowercase) #### `lower` (To Lowercase)
- **Stack Effect**: `( s -- s_lower )` - **Stack Effect**: `( s -- s_lower )`
@@ -506,23 +506,23 @@ These words are defined in the standard library file `lib.clx` and loaded dynami
- **Stack Effect**: `( s -- s_upper )` - **Stack Effect**: `( s -- s_upper )`
- **Description**: Pops the string `s` and pushes its uppercase equivalent `s_upper` onto the stack. - **Description**: Pops the string `s` and pushes its uppercase equivalent `s_upper` onto the stack.
#### `s+` (String / List Concatenation) #### `s+` (String / Q-form Concatenation)
- **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 Q-forms of character codes) and pushes the result as a Q-form of character codes representing the concatenated string.
### 10.8 List Operations ### 10.8 Q-form Operations
#### `qpush` (Quotation Push) #### `qpush` (Q-form Push)
- **Stack Effect**: `( [q] x -- [q... x] )` - **Stack Effect**: `( [q] x -- [q... x] )`
- **Description**: Appends the element `x` to the end of the quotation/list `[q]`. - **Description**: Appends the element `x` to the end of the Q-form `[q]`.
#### `rev` #### `rev`
- **Stack Effect**: `( [q] -- [q_rev] )` - **Stack Effect**: `( [q] -- [q_rev] )`
- **Description**: Reverses the order of elements in the list `[q]`. - **Description**: Reverses the order of elements in the Q-form `[q]`.
#### `lcat` (List Concatenation) #### `lcat` (Q-form Concatenation)
- **Stack Effect**: `( [q1] [q2] -- [q1_q2] )` - **Stack Effect**: `( [q1] [q2] -- [q1_q2] )`
- **Description**: Concatenates two lists `[q1]` and `[q2]`. - **Description**: Concatenates two Q-forms `[q1]` and `[q2]`.
### 10.9 Bitwise Operations ### 10.9 Bitwise Operations
+1 -1
View File
@@ -33,7 +33,7 @@
:: <= [ > not ] :: <= [ > not ]
:: isnum [ type 0= ] :: isnum [ type 0= ]
:: isstr [ type 1 = ] :: isstr [ type 1 = ]
:: isquot [ type 2 = ] :: isq [ 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 ] :: asc [ s2l 0 qget ]
+6 -6
View File
@@ -99,10 +99,10 @@ readln puts cr # Test readln
] [ ] [
drop drop drop " isstr FAIL" puts cr drop drop drop " isstr FAIL" puts cr
] ]
[ 1 2 ] isquot [ 42 isquot not ] dip and [ "hello" isquot not ] dip and if [ [ 1 2 ] isq [ 42 isq not ] dip and [ "hello" isq not ] dip and if [
drop drop drop " isquot PASS" puts cr drop drop drop " isq PASS" puts cr
] [ ] [
drop drop drop " isquot FAIL" puts cr drop drop drop " isq FAIL" puts cr
] ]
"Testing binary predicates..." puts cr "Testing binary predicates..." puts cr
5 3 > 5 3 >
@@ -171,8 +171,8 @@ if [
" l2s, streq, lower, and upper FAIL" puts cr " l2s, streq, lower, and upper FAIL" puts cr
] ]
# 8. List Operations # 8. Q-form Operations
"Testing list operations..." puts cr "Testing Q-form operations..." puts cr
5 [ 6 ] cons uncons 1 = [ 5 = ] dip and [ [ 6 ] streq ] dip and if [ " cons/uncons PASS" puts cr ] [ " cons/uncons FAIL" puts cr ] # Test cons/uncons 5 [ 6 ] cons uncons 1 = [ 5 = ] dip and [ [ 6 ] streq ] dip and if [ " cons/uncons PASS" puts cr ] [ " cons/uncons FAIL" puts cr ] # Test cons/uncons
5 [ 6 ] qpush [ 6 5 ] streq if [ " qpush PASS" puts cr ] [ " qpush FAIL" puts cr ] # Test qpush 5 [ 6 ] qpush [ 6 5 ] streq if [ " qpush PASS" puts cr ] [ " qpush FAIL" puts cr ] # Test qpush
@@ -222,7 +222,7 @@ my_inline 100 = if [ " :: (inline) PASS" puts cr ] [ " :: (inline) FAIL" puts
# 12. Host Arguments & Host Exit # 12. Host Arguments & Host Exit
"Testing host arguments and exit..." puts cr "Testing host arguments and exit..." puts cr
hsargs isquot [ hsargs qlen 0 > ] and if [ hsargs isq [ hsargs qlen 0 > ] and if [
" hsargs PASS" puts cr " hsargs PASS" puts cr
] [ ] [
" hsargs FAIL" puts cr " hsargs FAIL" puts cr