From a0ae3b2ef5c2dc481e55380d7ad7fff39f4066ae Mon Sep 17 00:00:00 2001 From: Zaaktin Lahm Date: Sat, 24 Aug 2024 15:03:50 +0100 Subject: [PATCH] quote, qquote, unquote --- docs/manual.html | 179 ++++++++++++++++++++++++++------------------- docs/manual.org | 25 +++++-- src/core.nim | 79 ++++++++++++++++++-- src/defs.nim | 4 +- src/parser.nim | 14 ++-- src/primitives.nim | 111 ++++++++++++++++++++-------- 6 files changed, 286 insertions(+), 126 deletions(-) diff --git a/docs/manual.html b/docs/manual.html index 0199471..8cbe28b 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -3,7 +3,7 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Stargaze user manual @@ -200,35 +200,36 @@

Stargaze user manual

Table of Contents

@@ -239,12 +240,12 @@

Table of Contents

-
-

1. Lexical syntax

+
+

1. Lexical syntax

-
-

1.1. Comment

+
+

1.1. Comment

Comment in Stargaze starts with ;. @@ -252,8 +253,8 @@

1.1. Comment

-
-

1.2. Booleans

+
+

1.2. Booleans

Only the strings #t and #f are boolean values; they evaluate to true and false respectively. @@ -261,8 +262,8 @@

1.2. Booleans

-
-

1.3. Strings

+
+

1.3. Strings

There's only one kind of syntax for string literals: "{string-piece}*", where string-piece is one of: @@ -279,8 +280,8 @@

1.3. Strings

-
-

1.4. Characters

+
+

1.4. Characters

Two kinds of syntax for character literals: @@ -318,18 +319,37 @@

1.4. Characters

-
-

1.5. Vector

+
+

1.5. Vector

Vector literals can be constructed by surrounding values with #{ and }.

+ +
+

1.6. Quotes

+
+

+Four kinds of quote exists in Stargaze: +

+ +
    +
  • quote / ' , which turns whatever it prefixed into "the thing itself"; e.g. (if #t 3 4) evaluates to 3 but '(if #t 3 4) evaluates to (if #t 3 4) (that is, a list containing if, #t, 3 and 4.)
  • +
  • qquote / ` , which works like normal quote, but any of the sub-expression within can be "unquoted" and thus get evaluated like normal.
  • +
  • unquote / , , which evaluates whatever it prefixed within a quasiquote-ed context. e.g. `(if (leq 3 4) 3 4) evaluates to (if (leq 3 4) 3 4) but `(if ,(leq 3 4) 3 4) evaluates to (if #t 3 4).
  • +
+ +

+These quotes are equivalent to their s-expr counterparts at all time, e.g. ''a would be equivalent to (quote (quote a)) which would evaluates to (quote a) (which also means that (list? ''a) would evaluate to #t.) +

+
+
-
-

2. Built-in primitives

+
+

2. Built-in primitives

  • (def NAME BODY): Bind the value BODY to the name NAME in the current environment.
  • @@ -341,11 +361,12 @@

    2. Built-in primitives
  • (quote ...): Quote its argument as symbolic values
  • (set! NAME VALUE): Assign VALUE to the name NAME.
  • (begin EXP1 ...): Executes EXP1 and the rest in the order they appear in the argument list. Returns the evaluated value of the last argument..
  • +
  • (equal EXP1 EXP2): Check if EXP1 and EXP2 has the same value.
-
-

2.1. Module-related

+
+

2.1. Module-related

  • (include STR): Include a file.
  • @@ -376,8 +397,8 @@

    2.1. Module-related

-
-

2.2. Miscellaneous

+
+

2.2. Miscellaneous

  • (atom? EXP): Check if EXP is an atom, i.e. a symbol, an integer, a boolean, a string, a character, or the empty list.
  • @@ -385,8 +406,8 @@

    2.2. Miscellaneous

-
-

2.3. Integer

+
+

2.3. Integer

  • (int? EXP): Check if EXP is an integer.
  • @@ -400,8 +421,8 @@

    2.3. Integer

-
-

2.4. Floating-point

+
+

2.4. Floating-point

  • (float? EXP): Check if EXP is an integer.
  • @@ -428,19 +449,23 @@

    2.4. Floating-point

-
-

2.5. Pair

+
+

2.5. Pair

  • (cons EXP1 EXP2): Return the pair of EXP1 and EXP2
  • (car EXP1): Return the first component of the pair EXP1.
  • (cdr EXP1): Return the second component of the pair EXP2.
  • +
  • (set-car! EXP NEWCAR): Set the first component of EXP to NEWCAR.
  • +
  • (set-cdr! EXP NEWCDR): Set the second component of EXP to NEWCAR.
  • +
  • (w/car EXP NEWCAR): Equivalent to (cons NEWCAR (cdr EXP)).
  • +
  • (w/cdr EXP NEWCDR): Equivalent to (cons (car EXP) NEWCDR).
-
-

2.6. Character

+
+

2.6. Character

  • (chr INT): Convert INT into the corresponding character.
  • @@ -450,8 +475,8 @@

    2.6. Character

-
-

2.7. String

+
+

2.7. String

  • (strref STR I): Retrieve the I-th (starting from 0) character of the string STR.
  • @@ -463,19 +488,19 @@

    2.7. String

-
-

2.8. Boolean

+
+

2.8. Boolean

    -
  • (and EXP1 ...):
  • -
  • (or EXP1 ...):
  • -
  • (not EXP):
  • +
  • (and EXP1 ...): Returns #f if one of the EXP evalueates to #f, or else returns the last argument.
  • +
  • (or EXP1 ...): Returns the first value that is not #f (if any); or else, returns #f.
  • +
  • (not EXP): If EXP evaluates to #f then return #t; returns #f otherwise.
-
-

2.9. Symbol

+
+

2.9. Symbol

  • (symstr SYM): Convert a symbol to a string.
  • @@ -484,8 +509,8 @@

    2.9. Symbol

-
-

2.10. List

+
+

2.10. List

  • (list EXP1 ...): Combine its arguments into a list.
  • @@ -494,8 +519,8 @@

    2.10. List

-
-

2.11. Vector

+
+

2.11. Vector

  • (vec? EXP): Check if EXP is a vector.
  • @@ -505,12 +530,14 @@

    2.11. Vector

  • (vecref VEC INT): Return the INT-th element from VEC.
  • (mkvec INT): Create a vector of size INT.
  • (vecset! VEC INT VALUE): Set the INT-th element of VEC to value VALUE.
  • +
  • (vec++ EXP1 ...):
  • +
  • (veclen VECTOR):
-
-

2.12. File input/output

+
+

2.12. File input/output

  • stdin, stdout, stderr: Standard input, standard output and standard error.
  • @@ -528,8 +555,8 @@

    2.12. File input/outpu

-
-

2.13. Iteration

+
+

2.13. Iteration

Iteration is important (at least for now) since we don't have tail-call optimization. @@ -542,34 +569,32 @@

2.13. Iteration

-
-

3. Extended primitives

+
+

3. Extended primitives

A few functions that should be able to be defined using the language itself is defined as primitives for performance, even if there aren't much performance to begin with…

-
-

3.1. List libraries

+
+

3.1. List libraries

  • (length LIST): Return the length of LIST.
  • (append EXP ...) / (list++ EXP ...): Combines EXP and the rest into one list.
  • (reverse LIST): Return a reversed version of LIST.
  • -
  • map:
  • -
  • filter:
  • -
  • member:
  • -
  • assoc:
  • -
  • set-car!:
  • -
  • set-cdr!:
  • +
  • (map F LIST1 ...): Returns a new list whose members are the results of applying F on LIST1 and the rest, e.g. (map add (list 3 4) (list 5 6) (list 7 8)) is equivalent to (list (add 3 5 7) (add 4 6 8)).
  • +
  • (filter F LIST): Returns a new list consisting of all the members of LIST that satisfies F. F should be a function that takes 1 argument and returns a boolean.
  • +
  • (member EXP LIST): Check if EXP is in the list LIST. If it is, returns the part of LIST starting from EXP; if not, return the false value.
  • +
  • (assoc EXP LIST): Check if EXP is in the assoc list LIST. An assoc list in LISP-like languages is a kind of list that consists of key-value pairs. If there is a key-value pair that uses EXP as the key, this primitive will return that key-value pair; or else, it will return the false value.
-

Created: 2024-08-22 Thu 15:25

+

Created: 2024-08-24 Sat 15:03

Validate

diff --git a/docs/manual.org b/docs/manual.org index 0ff01ab..d20d041 100644 --- a/docs/manual.org +++ b/docs/manual.org @@ -50,6 +50,16 @@ As for the first kind, Scheme R4RS only explicitly declares a =#\space= and a =# Vector literals can be constructed by surrounding values with =#{= and =}=. +** Quotes + +Four kinds of quote exists in Stargaze: + ++ =quote= / ='= , which turns whatever it prefixed into "the thing itself"; e.g. =(if #t 3 4)= evaluates to =3= but ='(if #t 3 4)= evaluates to =(if #t 3 4)= (that is, a list containing =if=, =#t=, =3= and =4=.) ++ =qquote= / =`= , which works like normal quote, but any of the sub-expression within can be "unquoted" and thus get evaluated like normal. ++ =unquote= / =,= , which evaluates whatever it prefixed within a =quasiquote=-ed context. e.g. =`(if (leq 3 4) 3 4)= evaluates to =(if (leq 3 4) 3 4)= but =`(if ,(leq 3 4) 3 4)= evaluates to =(if #t 3 4)=. + +These quotes are equivalent to their s-expr counterparts at all time, e.g. =''a= would be equivalent to =(quote (quote a))= which would evaluates to =(quote a)= (which also means that =(list? ''a)= would evaluate to =#t=.) + * Built-in primitives + =(def NAME BODY)=: Bind the value =BODY= to the name =NAME= in the current environment. @@ -61,6 +71,7 @@ Vector literals can be constructed by surrounding values with =#{= and =}=. + =(quote ...)=: Quote its argument as symbolic values + =(set! NAME VALUE)=: Assign =VALUE= to the name =NAME=. + =(begin EXP1 ...)=: Executes =EXP1= and the rest in the order they appear in the argument list. Returns the evaluated value of the last argument.. ++ =(equal EXP1 EXP2)=: Check if =EXP1= and =EXP2= has the same value. ** Module-related @@ -136,9 +147,9 @@ in the imported module; =NEW_NAME= would be the effective name in the *importing ** Boolean -+ =(and EXP1 ...)=: -+ =(or EXP1 ...)=: -+ =(not EXP)=: ++ =(and EXP1 ...)=: Returns =#f= if one of the =EXP= evalueates to =#f=, or else returns the last argument. ++ =(or EXP1 ...)=: Returns the first value that is not =#f= (if any); or else, returns =#f=. ++ =(not EXP)=: If =EXP= evaluates to =#f= then return =#t=; returns =#f= otherwise. ** Symbol @@ -189,10 +200,10 @@ A few functions that should be able to be defined using the language itself is d + =(length LIST)=: Return the length of =LIST=. + =(append EXP ...)= / =(list++ EXP ...)=: Combines =EXP= and the rest into one list. + =(reverse LIST)=: Return a reversed version of =LIST=. -+ =map=: -+ =filter=: -+ =member=: -+ =assoc=: ++ =(map F LIST1 ...)=: Returns a new list whose members are the results of applying =F= on =LIST1= and the rest, e.g. =(map add (list 3 4) (list 5 6) (list 7 8))= is equivalent to =(list (add 3 5 7) (add 4 6 8))=. ++ =(filter F LIST)=: Returns *a new list* consisting of all the members of =LIST= that satisfies =F=. =F= should be a function that takes 1 argument and returns a boolean. ++ =(member EXP LIST)=: Check if =EXP= is in the list =LIST=. If it is, returns the part of =LIST= starting from =EXP=; if not, return the false value. ++ =(assoc EXP LIST)=: Check if =EXP= is in the assoc list =LIST=. An assoc list in LISP-like languages is a kind of list that consists of key-value pairs. If there is a key-value pair that uses =EXP= as the key, this primitive will return that key-value pair; or else, it will return the false value. ** COMMENT Bitwise operations diff --git a/src/core.nim b/src/core.nim index 9926460..b17139b 100644 --- a/src/core.nim +++ b/src/core.nim @@ -42,6 +42,10 @@ proc quoteAsValue*(x: Node): Value = proc applyClosure*(x: Value, arglist: seq[Value], argtail: Value, e: Env): Value proc applyPrimitive*(x: Value, arglist: seq[Value], e: Env, call: Node): Value proc applySpecialForm*(x: Value, arglist: seq[Node], argtail: Node, e: Env, call: Node): Value +proc quoteF (x: seq[Node], tail: Node, e: Env, call: Node): Value +proc qquoteF (x: seq[Node], tail: Node, e: Env, call: Node): Value +proc unquoteF (x: seq[Node], tail: Node, e: Env, call: Node): Value +proc fnF (x: seq[Node], tail: Node, e: Env, call: Node): Value proc evalSingle*(x: Node, e: Env): Value = if x == nil: return nil case x.nType: @@ -67,11 +71,15 @@ proc evalSingle*(x: Node, e: Env): Value = of N_LIST: if x.lVal.len <= 0: x.errorWithReason("Invalid syntax for call") + var el: seq[Node] = x.lVal[1..= 1: + let head = n.lVal[0] + if head.isWordNodeOf("unquote"): + if n.lVal.len != 2 or n.tail != nil: head.invalidFormErrorWithReason("unquote") + let arg = n.lVal[1] + return arg.evalSingle(e) + elif head.isWordNodeOf("quote") or head.isWordNodeOf("qquote"): + return n.quoteAsValue() + else: + let listval = n.lVal.mapIt(it.evalQuasiQuoteContext(e)) + let tailval = n.tail.evalQuasiQuoteContext(e) + var r = tailval + var i = listval.len - 1 + while i >= 0: + r = mkPairValue(listval[i], r) + i -= 1 + return r + else: + if n.tail != nil: + # this is where cases like "( . 3)" reach. + n.invalidFormErrorWithReason("qquote") + else: + # this is where the empty list reach. + return nil + of N_VECTOR: + return mkVectorValue(n.vVal.mapIt(it.evalQuasiQuoteContext(e))) + else: + return n.quoteAsValue() +proc qquoteF (x: seq[Node], tail: Node, e: Env, call: Node): Value = + if tail != nil: call.invalidFormErrorWithReason("qquote") + if x.len != 1: call.invalidFormErrorWithReason("qquote", "1 argument") + return x[0].evalQuasiQuoteContext(e) + + +proc isWordNodeOf(n: Node, x: string): bool = + n.nType == N_WORD and n.wVal == x proc applyClosure*(x: Value, arglist: seq[Value], argtail: Value, e: Env): Value = assert x.vType == V_CLOSURE diff --git a/src/defs.nim b/src/defs.nim index 82243b6..8436e1a 100644 --- a/src/defs.nim +++ b/src/defs.nim @@ -108,7 +108,6 @@ proc `$`*(x: Node): string = "#{" & x.vVal.mapIt($it).join(" ") & "}" of N_EOF: "#eof" - proc mkWordNode*(wVal: string): Node = Node(line: -1, col: -1, filename: "", nType: N_WORD, wVal: wVal) proc mkIntegerNode*(iVal: int): Node = Node(line: -1, col: -1, filename: "", nType: N_INTEGER, iVal: iVal) @@ -130,6 +129,9 @@ proc withMetadata*(n: var Node, line: int, col: int, filename: string): Node = n.col = col n.filename = filename return n + +proc isWordNodeOf*(n: Node, w: string): bool = + n.nType == N_WORD and n.wVal == w type # the reason why seq is not suitable here is that in the most direct style diff --git a/src/parser.nim b/src/parser.nim index 3260014..ae6a65b 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -123,14 +123,15 @@ proc parseSingleNode*(x: var Filelike): Node = x.nextChar var res = mkListNode(lVal, tail) return res.withMetadata(line, col, fn) - #[ elif firstChar == '\'': x.nextChar - var res = mkWordNode("'") + var preres = x.parseSingleNode() + var res = mkListNode(@[mkWordNode("quote"), preres], nil) return res.withMetadata(line, col, fn) elif firstChar == '`': x.nextChar - var res = mkWordNode("`") + var preres = x.parseSingleNode() + var res = mkListNode(@[mkWordNode("qquote"), preres], nil) return res.withMetadata(line, col, fn) elif firstChar == ',': x.nextChar @@ -138,9 +139,12 @@ proc parseSingleNode*(x: var Filelike): Node = if not x.textEnded and x.nextCharExistsAndIs('@'): x.nextChar resword = ",@" - var res = mkWordNode(resword) + var preres = x.parseSingleNode() + var res = mkListNode(@[mkWordNode(if resword == ",": + "unquote" + else: + "unquotex"), preres], nil) return res.withMetadata(line, col, fn) - ]# elif firstChar.isDigit: var s = "" s.add(firstChar) diff --git a/src/primitives.nim b/src/primitives.nim index 43acfd2..8ada244 100644 --- a/src/primitives.nim +++ b/src/primitives.nim @@ -22,13 +22,6 @@ proc typeErrorWithReason(n: Node, req: ValueType, i: int, t: ValueType): void = proc typeErrorWithReason(n: Node, req: seq[ValueType], i: int, t: ValueType): void = n.errorWithReason("type error: " & req.mapIt($it).join(" or ") & " required but " & $t & " found at argument no. " & $(i+1)) -proc invalidFormErrorWithReason(n: Node, name: string, requirement: string = ""): void = - let tailstr = if requirement == "": - "'." - else: - "'; " & requirement & " required." - n.errorWithReason("Invalid form for '" & name & tailstr) - proc ensureArgOfType(n: Node, v: Value, i: int, t: ValueType): void = if v.vType != t: n.typeErrorWithReason(t, i, v.vType) @@ -38,19 +31,6 @@ proc ensureArgOfType(n: Node, v: Value, i: int, t: seq[ValueType]): void = proc verdictValue(x: bool): Value = if x: GlobalTrueValue else: GlobalFalseValue -# (fn ARGLIST BODY) -rootEnv.registerValue( - "fn", - mkSpecialFormValue( - proc (x: seq[Node], tail: Node, e: Env, call: Node): Value = - if tail != nil: tail.invalidFormErrorWithReason("fn") - if x.len < 2: call.invalidFormErrorWithReason("fn") - let r = mkClosureBase(x[0], x[1..