SkillAgentSearch skills...

Superficie

Surface syntax for Clojure to help exposition/onboarding.

Install / Use

/learn @replikativ/Superficie
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Superficie

CircleCI Clojars

Surface syntax for Clojure — a bidirectional renderer that translates Clojure S-expressions into familiar, readable syntax and back.

Why?

During my PhD in machine learning, I worked in Clojure while everyone around me used Python. I couldn't show my code to colleagues, supervisors, or domain experts without first explaining parentheses. In presentations, papers, and code reviews, the syntax was a wall — not because S-expressions are bad, but because you can't expect someone to parse them on the fly when they've never seen them before.

That initial unfamiliarity typically takes a few days to overcome. But a few days is infinity when you're in a meeting, reading a blog post, or reviewing code with someone outside your team.

Superficie exists to remove that barrier. You write Clojure as normal. When you need to show it to someone, you render it to a syntax they can already read.

Try the playground — paste any Clojure code and see it rendered live, or use the built-in SCI REPL to evaluate superficie syntax directly.

Quick Look

<table> <tr><th>Clojure</th><th>Superficie</th></tr> <tr> <td>
(defn greet [name]
  (str "Hello, " name "!"))
</td> <td>
defn greet [name]:
  str("Hello, " name "!")
end
</td> </tr> <tr> <td>
(defn quadratic [a b c x]
  (+ (* a x x) (* b x) c))
</td> <td>
defn quadratic [a b c x]:
  a * x * x + b * x + c
end
</td> </tr> <tr> <td>
(defn process-users [users]
  (->> users
       (filter :active)
       (map :name)
       (sort)
       (take 10)))
</td> <td>
defn process-users [users]:
  users |> filter(:active)
        |> map(:name)
        |> sort()
        |> take(10)
end
</td> </tr> <tr> <td>
(defprotocol Shape
  (area [this])
  (perimeter [this]))

(defrecord Circle [r]
  Shape
  (area [this] (* Math/PI r r))
  (perimeter [this] (* 2 Math/PI r)))
</td> <td>
defprotocol Shape:
  area [this]
  perimeter [this]
end

defrecord Circle [r]:
  Shape
  area [this]:
    Math/PI * r * r
  end
  perimeter [this]:
    2 * Math/PI * r
  end
end
</td> </tr> </table>

What It Is (and Isn't)

Superficie is a communication tool. It renders Clojure into syntax that Python/Julia/TypeScript developers can read immediately — for presentations, documentation, blog posts, and conversations with domain experts.

It is also a usable language. Superficie syntax can be parsed back to Clojure forms and evaluated directly — via the JVM, Babashka, or a browser SCI REPL. You can write .sup files, run a REPL, and interoperate with any Clojure ecosystem library.

It is not a separate ecosystem. There is no superficie runtime, no lock-in. Printing any Clojure source as superficie always works. Reading back has a small set of design constraints: superficie reserves certain keywords (if, when, let, match, …) as block syntax, and Clojure code that uses these as variable names or relies on read-time namespace resolution (::alias/key) cannot round-trip cleanly. Code written in superficie — respecting its block syntax — roundtrips completely.

Syntax at a Glance

Definitions

def pi: 3.14159
defonce conn: connect("localhost:5432")

defn factorial [n]:
  loop [i n acc 1]:
    if i <= 1 :
      acc
    else:
      recur(dec(i) acc * i)
    end
  end
end

Control Flow

if x > 0 :
  :positive
else:
  :non-positive
end

cond:
  neg?(x)  => :negative
  zero?(x) => :zero
  :else    => :positive
end

case method :
  :get  => fetch(path)
  :post => create(path body)
  =>      not-found()
end

Bindings

let [x 1 y 2]:
  x + y
end

for [x xs y ys :when x not= y]:
  [x y]
end

Threading (Pipes)

;; ->> becomes |>
users |> filter(:active) |> map(:name) |> sort() |> take(10)

;; -> becomes .>
config .> assoc(:port 8080) .> merge(defaults)

Java Interop

s.toUpperCase()
point.-x
new StringBuilder("hello")
Integer/parseInt(s)
Math/PI

Protocols and Records

defprotocol Shape:
  area [this]
  perimeter [this]
end

defrecord Circle [r]:
  Shape
  area [this]:
    Math/PI * r * r
  end
  perimeter [this]:
    2 * Math/PI * r
  end
end

defmulti area: :shape
defmethod area :circle [s]:
  Math/PI * :r(s) * :r(s)
end

Namespaces

ns myapp.core:
  require:
    [clojure.string :as str]
    [myapp.db :refer [query insert!]]
  import:
    [java.time Instant Duration]
end

Error Handling and Interop

try:
  Integer/parseInt(s)
catch [NumberFormatException e]:
  println(e.getMessage())
  nil
end

Macros and Syntax-Quote

Macros are first-class in superficie. The backtick syntax-quote applies to a block form, and ~/~@ unquote/unquote-splicing work inside it:

defmacro unless [pred & body]:
  `if not(~pred):
    do(~@body)
  end
end

This round-trips correctly with Clojure. The clj->sup converter preserves syntax-quote structure from existing macros rather than expanding it, so real-world macros render readably:

;; Clojure
(defmacro -> [x & forms]
  (loop [x x, forms forms]
    (if forms
      (let [form (first forms)
            threaded (if (seq? form)
                       `(~(first form) ~x ~@(next form))
                       (list form x))]
        (recur threaded (next forms)))
      x)))
;; Superficie
defmacro -> [x & forms]:
  loop [x x, forms forms]:
    if forms :
      let [form first(forms), threaded if seq?(form):
        `~first(form)(~x ~@next(form))
      else:
        list(form x)
      end]:
        recur(threaded next(forms))
      end
    else:
      x
    end
  end
end

User-defined macros are called with function syntax (unless(pred body)). Block syntax (unless pred: body end) is reserved for macros registered in the block registry — either built-in forms or library macros that explicitly declare their surface block kind via :superficie/role metadata.

Function Call Fallback

The renderer never fails. Any Clojure form that doesn't match a known block pattern or operator is rendered using function call syntax — f(a b c) — which is always valid superficie and always round-trips cleanly:

;; Clojure
(defmacro my-macro [x]
  (list 'if x :yes :no))
;; Superficie — list call renders as a regular function call
defmacro my-macro [x]:
  list('if x :yes :no)
end

Note: (...) in superficie is grouping for infix, not a raw S-expression form — (a + b) * c. To write a literal quoted list in superficie source, use '(...): '(if x :yes :no) reads back as (quote (if x :yes :no)).

How It Works

Pipeline

Superficie uses a hand-written LL parser with four stages inspired by Racket's shrubbery notation:

source text
  → tokenizer  (characters → flat token vector; throws on unterminated strings)
  → grouper    (tokens → shrubbery tree; NEVER throws — bracket errors become
                ShrubError nodes embedded in the valid surrounding tree)
  → enforest   (shrubbery → healed token stream; re-wraps partial bracket
                children with synthetic delimiters; drops stray closers)
  → reader     (token stream → Clojure forms; LL(1) recursive-descent with
                block dispatch by keyword and Pratt infix climbing)

The key design choice is the two-phase bracket / semantic split:

  • The grouper resolves bracket structure and reports all structural errors without aborting. f(x] produces (f x) with an attached ShrubError; the surrounding code is still parsed correctly.
  • The reader handles semantics: block keywords, operator precedence, and namespace resolution. Semantic errors throw ex-info with structured :line, :col, :source-context, and :hint data.

This is the same separation Rust's compiler and Racket's Rhombus use: construct the bracket/token tree first (where recovery is mechanical), then parse semantics against a structurally valid input.

Error messages are formatted with source context and underlines:

Error: Expected 'end' to close defn block (line 2, col 7)
2 |   x + 1
  |       ^

Error: Unterminated string — missing closing " (line 2, col 7)
2 |   str("hello x)
  |       ^

Error: Maximum nesting depth (150) exceeded (line 1, col 301)
1 | f(f(f(f(f(f(f( ...
  |                ^

The errors/format-error function turns any superficie ex-info into this format, and is also available to users building tooling on top of the parser.

Each block-dispatch entry maps a surface keyword ("defn", "if", "for", …) to a parse function that consumes the rest of the line and the indented body. Forms that don't match any block keyword are parsed as infix expressions or function calls via Pratt climbing.

Block Registration

On the JVM, superficie maintains a block registry: when you evaluate a form, the printer records which Clojure vars have surface block representations. This lets the renderer correctly handle project-specific def-like macros — if your project defines defcomponent, the renderer can be told it uses the defn block pattern.

The registry is populated by superficie.runtime/register-ns! and updated incrementally via the REPL.

Interleaving with Clojure Evaluation

The pipeline follows Clojure's incremental evaluation model: each top-level form is fully parsed and evaluated before the next form is read. This means:

  • Macros defined in form N are available when parsing form N+1
  • ns declarations take effect immediately, so subsequent forms resolve in the new namespace
  • The REPL and file runner share the same model — no dis
View on GitHub
GitHub Stars22
CategoryDevelopment
Updated1d ago
Forks0

Languages

Clojure

Security Score

90/100

Audited on Apr 4, 2026

No findings