2.0 syntax
This commit is contained in:
parent
d02322ce73
commit
d5b91b7fd2
3 changed files with 56 additions and 124 deletions
65
README.md
65
README.md
|
@ -9,8 +9,7 @@ code).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
**TL;DR:** `[object slot-name]` is like `object.slotName` in C++, and
|
**TL;DR:** `[object (method args)]` is like `object.method(args)` in C++.
|
||||||
`[object (.method args)]` is like `object.method(args)` in C++.
|
|
||||||
|
|
||||||
First, to enable `objective-lisp`'s syntax, just load the system:
|
First, to enable `objective-lisp`'s syntax, just load the system:
|
||||||
``` common-lisp
|
``` common-lisp
|
||||||
|
@ -18,66 +17,44 @@ First, to enable `objective-lisp`'s syntax, just load the system:
|
||||||
```
|
```
|
||||||
|
|
||||||
`objective-lisp`'s syntax takes the form of a special S-expression, contained
|
`objective-lisp`'s syntax takes the form of a special S-expression, contained
|
||||||
in square brackets rather than parentheses. Each form within treats the previous
|
in square brackets rather than parentheses. Each expression within acts upon the
|
||||||
form as its subject, like a chain of `.` (dot) operators in C-like languages.
|
result of the previous one, like a chain of `.` (dot) operators in C-like
|
||||||
|
languages.
|
||||||
```common-lisp
|
```common-lisp
|
||||||
[foo bar baz quux]
|
[foo (bar) (baz) (quux)]
|
||||||
;; C++: foo.bar.baz.quux
|
;; C++: foo.bar().baz().quux()
|
||||||
```
|
```
|
||||||
|
|
||||||
To access a slot, just put the slot name after the object:
|
To call a method, just write it after the object:
|
||||||
``` common-lisp
|
``` common-lisp
|
||||||
[object slot-name]
|
[object (method args...)]
|
||||||
;; => (slot-value object 'slot-name)
|
;; => (method object args...)
|
||||||
|
|
||||||
(setf [object slot-name] value)
|
|
||||||
;; => (setf (slot-value object 'slot-name) value)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`slot-name` doesn't have to be an unquoted symbol:
|
Slot accessors, and other methods that don't take additional arguments, can be
|
||||||
|
written without enclosing parentheses:
|
||||||
``` common-lisp
|
``` common-lisp
|
||||||
(defun choose-slot (x)
|
[object get-something]
|
||||||
(if (<= x 0) 'pos-slot 'neg-slot))
|
;; => (get-something object)
|
||||||
(setf [object (choose-slot -1)] value)
|
|
||||||
|
|
||||||
(defvar slot-var 'slot-name)
|
|
||||||
(setf [object `,slot-var] value)
|
|
||||||
```
|
|
||||||
|
|
||||||
To call a method, write an S-expression as you normally would, but put a `.`
|
|
||||||
(dot) before its name:
|
|
||||||
``` common-lisp
|
|
||||||
[object (.method arg-1 arg-2 ... arg-N)]
|
|
||||||
;; => (method object arg-1 arg-2 ... arg-N)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Under the hood, this just passes `object` as the first argument to `method`, so
|
Under the hood, this just passes `object` as the first argument to `method`, so
|
||||||
you can do stuff like this (I won't kinkshame you, but your coworkers might):
|
you can do stuff like this (I won't kinkshame you, but your coworkers might):
|
||||||
```common-lisp
|
```common-lisp
|
||||||
[object slot-name (.setf value)]
|
[object (slot-value 'slot-name) (setf value)]
|
||||||
;; => [(slot-value object 'slot-name) (.setf value)]
|
;; => [(slot-value object 'slot-name) (setf value)]
|
||||||
;; => (setf (slot-value object 'slot-name) value)
|
;; => (setf (slot-value object 'slot-name) value)
|
||||||
```
|
```
|
||||||
|
|
||||||
And, of course, you can chain multiple slot accesses and method calls together:
|
To access slots directly, use the `:slot` keyword:
|
||||||
``` common-lisp
|
``` common-lisp
|
||||||
[object child (.method-of-child) (.method-of-return-value) slot-of-return-value]
|
[object :slot slot-name]
|
||||||
;; => [(slot-value object 'child) (.method-of-child) ...]
|
;; => (slot-value object 'slot-name)
|
||||||
;; => [(method-of-child (slot-value object 'child)) (.method-of-return-value) ...]
|
|
||||||
;; => [(method-of-return-value (method-of-child ...)) slot-of-return-value]
|
|
||||||
;; => (slot-value (method-of-return-value ...) 'slot-of-return-value)
|
|
||||||
```
|
|
||||||
|
|
||||||
**NEW:** You can also call slot accessors without wrapping them in a cons. (This
|
|
||||||
works for any method that takes no additional arguments, but that might cause
|
|
||||||
code readability issues.)
|
|
||||||
``` common-lisp
|
|
||||||
[object .slot-accessor]
|
|
||||||
;; => (slot-accessor object)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
`objective-lisp` is public domain. You can do whatever you want with it. I don't
|
`objective-lisp` is public domain (CC0). You can do whatever you want with it. I
|
||||||
really care about credit, it's just a silly little thing I wrote in a few hours.
|
don't really care about credit, it's just a silly little thing I wrote in a few
|
||||||
|
hours. (And then rewrote just now because the syntax sucked.)
|
||||||
|
|
||||||
But if you find it useful, *please* let me know. I'd love to hear about it.
|
But if you find it useful, *please* let me know. I'd love to hear about it.
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
(defsystem "objective-lisp"
|
(defsystem "objective-lisp"
|
||||||
:description "Syntactic sugar for object-oriented Lisp."
|
:description "Syntactic sugar for object-oriented Lisp."
|
||||||
:version "1.0"
|
:version "2.0"
|
||||||
:author "~keith"
|
:author "~keith"
|
||||||
:homepage "https://bytes.keithhacks.cyou/keith/objective-lisp"
|
:homepage "https://bytes.keithhacks.cyou/keith/objective-lisp"
|
||||||
:license "Public Domain"
|
:license "Public Domain/CC0"
|
||||||
:components ((:file "objective-lisp")))
|
:components ((:file "objective-lisp")))
|
||||||
|
|
|
@ -3,94 +3,49 @@
|
||||||
|
|
||||||
(defpackage objective-lisp
|
(defpackage objective-lisp
|
||||||
(:use common-lisp)
|
(:use common-lisp)
|
||||||
(:export +open-construct-char+ +close-construct-char+ +method-char+
|
(:export +open-char+ +close-char+
|
||||||
read-construct-contents
|
read-construct-item
|
||||||
destructure-construct
|
read-construct
|
||||||
read-method-name
|
|
||||||
read-open-construct
|
|
||||||
read-unexpected))
|
read-unexpected))
|
||||||
|
|
||||||
(in-package objective-lisp)
|
(in-package objective-lisp)
|
||||||
|
|
||||||
(defparameter +open-construct-char+ #\[)
|
(defconstant +open-char+ #\[)
|
||||||
(defparameter +close-construct-char+ #\])
|
(defconstant +close-char+ #\])
|
||||||
(defparameter +method-char+ #\.)
|
|
||||||
|
|
||||||
;; DONE
|
(defun read-construct-item (sexpr stream)
|
||||||
;; (setf (slot-value object 'slot) value)
|
"Recursively read and rewrite an objective-lisp construct."
|
||||||
;; => (setf [object slot] value)
|
(if (char= (peek-char t stream t nil t) +close-char+)
|
||||||
|
; We've hit the end, return the sexpr we built
|
||||||
;; DONE
|
(progn (read-char stream t nil t)
|
||||||
;; (setf (slot-value (slot-value object 'slot-a) 'slot-b) value)
|
sexpr)
|
||||||
;; => (setf [object slot-a slot-b] value)
|
(let ((item (read stream t nil t)))
|
||||||
|
|
||||||
;; TODO
|
|
||||||
;; (object-method object args...)
|
|
||||||
;; => [object (.method args...)]
|
|
||||||
|
|
||||||
(defun read-construct-contents (stream end-char)
|
|
||||||
"Read the contents of an objective-lisp construct."
|
|
||||||
(loop until (char= (peek-char t stream t nil t) end-char)
|
|
||||||
collect (read stream t nil t)
|
|
||||||
; Skip over end-char
|
|
||||||
finally (read-char stream t nil t)))
|
|
||||||
|
|
||||||
(defun quote-if-symbol (x)
|
|
||||||
"Quote x if it's a symbol, return it verbatim otherwise."
|
|
||||||
(if (symbolp x) `(quote ,x) x))
|
|
||||||
|
|
||||||
(defun method-call-p (x)
|
|
||||||
"Check if x is a sexpr of the form (.method args...)"
|
|
||||||
(and (consp x) (consp (car x)) (eq (caar x) :method-call)))
|
|
||||||
|
|
||||||
(defun consless-method-call-p (x)
|
|
||||||
"Check if x is of the form .method (no arguments)"
|
|
||||||
(and (consp x) (eq (car x) :method-call)))
|
|
||||||
|
|
||||||
(defun destructure-construct (construct)
|
|
||||||
"Recursively destructure an objective-lisp construct."
|
|
||||||
(cond
|
(cond
|
||||||
; Nothing left to destructure, return car
|
; Method call [object (method args...)]
|
||||||
((not (cdr construct))
|
((consp item)
|
||||||
(car construct))
|
(read-construct-item `(,(car item) ,sexpr ,@(cdr item))
|
||||||
; Method call [object (.method args...)]
|
stream))
|
||||||
; => [object ((:method-call . method) args...)]
|
; Slot access [object :slot slot-name]
|
||||||
((method-call-p (cadr construct))
|
((eq item :slot)
|
||||||
(let ((method-call (cadr construct)))
|
(read-construct-item `(slot-value ,sexpr ',(read stream t nil t))
|
||||||
(destructure-construct
|
stream))
|
||||||
(cons `(,(cdar method-call) ,(car construct) ,@(cdr method-call))
|
; Consless method call [object method]
|
||||||
(cddr construct)))
|
((symbolp item)
|
||||||
))
|
(read-construct-item `(,item ,sexpr)
|
||||||
; Consless method call [object .method]
|
stream))
|
||||||
; => [object (:method-call . method)]
|
; Something else
|
||||||
((consless-method-call-p (cadr construct))
|
(t (error "Unexpected item ~S" item))
|
||||||
(destructure-construct
|
))))
|
||||||
(cons `(,(cdadr construct) ,(car construct))
|
|
||||||
(cddr construct)))
|
|
||||||
)
|
|
||||||
; Slot access [object slot]
|
|
||||||
(t
|
|
||||||
(destructure-construct
|
|
||||||
(cons `(slot-value ,(car construct) ,(quote-if-symbol (cadr construct)))
|
|
||||||
(cddr construct)))
|
|
||||||
)))
|
|
||||||
|
|
||||||
(defun read-method-name (stream char)
|
(defun read-construct (stream char)
|
||||||
"Read an objective-lisp method name."
|
|
||||||
(declare (ignore char))
|
|
||||||
(cons :method-call (read stream t nil t)))
|
|
||||||
|
|
||||||
(defun read-open-construct (stream char)
|
|
||||||
"Read an objective-lisp construct."
|
"Read an objective-lisp construct."
|
||||||
(declare (ignore char))
|
(declare (ignore char))
|
||||||
(let ((*readtable* (copy-readtable)))
|
(read-construct-item (read stream t nil t) ; 1st sexpr is the object, don't rewrite it
|
||||||
(set-macro-character +method-char+ 'read-method-name
|
stream))
|
||||||
t) ; non-terminating so (12.34) doesn't become (12 (:method-call . 34))
|
|
||||||
(destructure-construct (read-construct-contents stream +close-construct-char+))))
|
|
||||||
|
|
||||||
(defun read-unexpected (stream char)
|
(defun read-unexpected (stream char)
|
||||||
(declare (ignore stream))
|
(declare (ignore stream))
|
||||||
(error "Unexpected character ~S" char))
|
(error "Unexpected character ~S" char))
|
||||||
|
|
||||||
(set-macro-character +open-construct-char+ 'read-open-construct)
|
(set-macro-character +open-char+ 'read-construct)
|
||||||
(set-macro-character +close-construct-char+ 'read-unexpected)
|
(set-macro-character +close-char+ 'read-unexpected)
|
||||||
|
|
Loading…
Reference in a new issue