Skip to content

Instantly share code, notes, and snippets.

@moea
Last active March 17, 2025 00:46
Show Gist options
  • Save moea/cce2b742d3b2920cda4874fbe9b6934c to your computer and use it in GitHub Desktop.
Save moea/cce2b742d3b2920cda4874fbe9b6934c to your computer and use it in GitHub Desktop.
(ns assistant.reader
(:import [java.io PushbackReader StringReader]))
(defprotocol Reader
(read-char [this])
(peek-char [this])
(unread-char [this ch])
(eof? [this]))
(extend-type PushbackReader
Reader
(read-char [rdr]
(let [ch (.read rdr)]
(when-not (neg? ch)
(char ch))))
(peek-char [rdr]
(let [ch (.read rdr)]
(when-not (neg? ch)
(.unread rdr ch)
(char ch))))
(unread-char [rdr ch] (.unread rdr (int ch)))
(eof? [rdr] (nil? (peek-char rdr))))
;; Skip whitespace
(defn skip-whitespace [reader]
(loop []
(let [ch (peek-char reader)]
(when (and ch (Character/isWhitespace ch))
(read-char reader)
(recur)))))
;; Parsing functions
(declare parse)
(defn parse-int [reader first-digit]
(let [sb (StringBuilder.)]
(.append sb first-digit)
(loop []
(let [ch (peek-char reader)]
(if (and ch (Character/isDigit ch))
(do
(.append sb (read-char reader))
(recur))
(Integer/parseInt (.toString sb)))))))
(defn parse-string [reader]
(let [sb (StringBuilder.)]
(loop []
(let [ch (read-char reader)]
(cond
(nil? ch) (throw (ex-info "Unexpected EOF in string" {}))
(= ch \\) (do (.append sb (read-char reader)) (recur))
(= ch \" ) (.toString sb)
:else (do (.append sb ch)
(recur)))))))
(defn parse-symbol [reader first-char]
(let [sb (StringBuilder.)]
(.append sb first-char)
(loop []
(let [ch (peek-char reader)]
(if (and ch (not (Character/isWhitespace ch)) (not (#{\( \) \[ \] \"} ch)))
(do (.append sb (read-char reader))
(recur))
(symbol (.toString sb)))))))
(defn parse-vector [reader]
(loop [elems []]
(skip-whitespace reader)
(let [ch (peek-char reader)]
(cond
(nil? ch) (throw (ex-info "Unexpected EOF in vector" {}))
(= ch \]) (do (read-char reader) (vec elems))
:else (recur (conj elems (parse reader)))))))
(defn parse-list [reader]
(loop [elems []]
(skip-whitespace reader)
(let [ch (peek-char reader)]
(cond
(nil? ch) (throw (ex-info "Unexpected EOF in list" {}))
(= ch \)) (do (read-char reader) (apply list elems))
:else (recur (conj elems (parse reader)))))))
(defn parse [reader]
(skip-whitespace reader)
(let [ch (read-char reader)]
(cond
(nil? ch) nil
(Character/isDigit ch) (parse-int reader ch)
(= ch \" ) (parse-string reader)
(= ch \[) (parse-vector reader)
(= ch \() (parse-list reader)
:else (parse-symbol reader ch))))
(defn read-sexp [^PushbackReader rdr]
(parse rdr))
(comment
(with-open [rdr (PushbackReader. (StringReader. "(hello [\"hello\" 123 [456]])"))]
(read-sexp rdr)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment