From 9b6614fc45ae88712bcf272c70a1b7ba86f17432 Mon Sep 17 00:00:00 2001 From: ~keith Date: Tue, 22 Feb 2022 04:50:53 +0000 Subject: [PATCH] Rewrite as standard macro --- README.md | 27 +++++++++++++-------- objective-lisp.asd | 2 +- objective-lisp.lisp | 59 +++++++++++++++++---------------------------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 6fb8ec9..d2cddfd 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ Syntactic sugar for object-oriented Lisp. `objective-lisp` provides a simple, concise, and (slightly) more conventional -syntax for accessing the slots and methods of objects. It defines a reader -macro for the `[` and `]` characters (although you can change these in the -code). +syntax for accessing the slots and methods of objects. It defines a macro named +`O!`, and a reader macro for `#[...]` (although you can change these characters +in the code). ## Usage -**TL;DR:** `[object (method args)]` is like `object.method(args)` in C++. +**TL;DR:** `#[object (method args)]` is like `object.method(args)` in C++. First, to enable `objective-lisp`'s syntax, just load the system: ``` common-lisp @@ -21,37 +21,44 @@ in square brackets rather than parentheses. Each expression within acts upon the result of the previous one, like a chain of `.` (dot) operators in C-like languages. ```common-lisp -[foo (bar) (baz) (quux)] +#[foo (bar) (baz) (quux)] ;; C++: foo.bar().baz().quux() ``` To call a method, just write it after the object: ``` common-lisp -[object (method args...)] +#[object (method args...)] ;; => (method object args...) ``` 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): ```common-lisp -[object (slot-value 'slot-name) (setf value)] -;; => [(slot-value object 'slot-name) (setf value)] +#[object (slot-value 'slot-name) (setf value)] +;; => #[(slot-value object 'slot-name) (setf value)] ;; => (setf (slot-value object 'slot-name) value) ``` Slot accessors, and other methods that don't take additional arguments, can be written without enclosing parentheses: ``` common-lisp -[object get-something] +#[object get-something] ;; => (get-something object) ``` To access slots directly, use the `:slot` keyword: ``` common-lisp -[object :slot slot-name] +#[object :slot slot-name] ;; => (slot-value object 'slot-name) ``` +You can also just use the `O!` macro directly: + +``` common-lisp +(O! object :slot foo (do-something args...)) +;; => (do-something (slot-value object 'foo) args...) +``` + ## License `objective-lisp` is public domain (CC0). You can do whatever you want with it. I don't really care about credit, it's just a silly little thing I wrote in a few diff --git a/objective-lisp.asd b/objective-lisp.asd index 573d57e..de4e78e 100644 --- a/objective-lisp.asd +++ b/objective-lisp.asd @@ -3,7 +3,7 @@ (defsystem "objective-lisp" :description "Syntactic sugar for object-oriented Lisp." - :version "2.0" + :version "3.0" :author "~keith" :homepage "https://bytes.keithhacks.cyou/keith/objective-lisp" :license "Public Domain/CC0" diff --git a/objective-lisp.lisp b/objective-lisp.lisp index 6557c9a..b6d285d 100644 --- a/objective-lisp.lisp +++ b/objective-lisp.lisp @@ -3,49 +3,34 @@ (defpackage objective-lisp (:use common-lisp) - (:export +open-char+ +close-char+ - read-construct-item - read-construct - read-unexpected)) + (:export O!)) (in-package objective-lisp) (defconstant +open-char+ #\[) (defconstant +close-char+ #\]) -(defun read-construct-item (sexpr stream) - "Recursively read and rewrite an objective-lisp construct." - (if (char= (peek-char t stream t nil t) +close-char+) - ; We've hit the end, return the sexpr we built - (progn (read-char stream t nil t) - sexpr) - (let ((item (read stream t nil t))) - (cond - ; Method call [object (method args...)] - ((consp item) - (read-construct-item `(,(car item) ,sexpr ,@(cdr item)) - stream)) - ; Slot access [object :slot slot-name] - ((eq item :slot) - (read-construct-item `(slot-value ,sexpr ',(read stream t nil t)) - stream)) - ; Consless method call [object method] - ((symbolp item) - (read-construct-item `(,item ,sexpr) - stream)) - ; Something else - (t (error "Unexpected item ~S" item)) - )))) +(defmacro O! (root &rest forms) + (let ((expr root)) + (loop for entry on forms + for form = (car entry) + do (setf expr + (cond ((consp form) `(,(car form) ,expr ,@(cdr form))) + ((eq form :slot) (prog1 + `(slot-value ,expr ',(cadr entry)) + (unless (cdr entry) + (error "Unexpected end of list (expected slot name)")) + (rplacd entry (cddr entry)))) + ((symbolp form) `(,form ,expr)) + (t (error "Unexpected form ~S" form)) + ))) + expr)) -(defun read-construct (stream char) +(defun read-construct (stream char arg) "Read an objective-lisp construct." - (declare (ignore char)) - (read-construct-item (read stream t nil t) ; 1st sexpr is the object, don't rewrite it - stream)) + (declare (ignore char arg)) + (macroexpand-1 (cons 'O! (read-delimited-list +close-char+ stream)))) -(defun read-unexpected (stream char) - (declare (ignore stream)) - (error "Unexpected character ~S" char)) - -(set-macro-character +open-char+ 'read-construct) -(set-macro-character +close-char+ 'read-unexpected) +;(set-macro-character +open-char+ 'read-construct) +(set-dispatch-macro-character #\# +open-char+ #'read-construct) +(set-macro-character +close-char+ (get-macro-character #\)))