Re: Lark - A surface syntax for Common Lisp
"David McClain (as dbm at refined-audiometrics dot com)" <[email protected]>
| Newsgroups | gmane.lisp.lispworks.general |
|---|---|
| Message-ID | <[email protected]> |
Wow! I really admire your tenacity.
But honestly, I cannot imagine anything more awful than trying to remember how to close off compound expressions. Was that {([ or {[( ?
I had to write Rust for a year, and faced that dilemma constantly. Why would I ever wish to have this same handicap in my Lisp?
> On Feb 14, 2026, at 12:39, Gerry Weaver (as gerryw at compvia dot com) <[email protected]> wrote:
>
> Hello All,
>
> I've been working on this thing for a while. I'm getting close to the
> point where I'm going to call it good for now. I'm mainly working on the
> stdlib stuff (ie; http server, http client, websockets, etc.). It isn't
> meant to replace Lisp. It is intended to be an ergonomic extension.
> There is a lark command that is similar to that provided by the go
> language go command and provides most of the same functionality. There
> is also a syntax file for Sublime Text. I'm very interested in any
> thoughts or suggestions y'all might have. If enough folks are
> interested, I will try to put it on github at some point.
>
> Here is a rough description of the syntax. It is still evolving a
> little, but mostly stable.
>
> Thanks,
>
> -G
>
>
>
> -- Lark is a readable surface syntax for Common Lisp.
> -- Everything here compiles to standard CL s-expressions.
>
>
> -- ═══════════════════════════════════════════════════════════
> -- COMMENTS
> -- ═══════════════════════════════════════════════════════════
>
> -- Single line comment (double dash)
>
>
> -- ═══════════════════════════════════════════════════════════
> -- VARIABLES
> -- ═══════════════════════════════════════════════════════════
>
> let x = 42 -- lexical binding (let)
> let* y = x + 1 -- sequential binding (let*)
> let name = "Lark" -- strings
> let pi = 3.14159 -- floats
> let ratio = 2/3 -- rationals
> let hex = 0xFF -- hex literals
> let nothing = nil -- nil
> let yes = true -- boolean true (t)
> let no = false -- boolean false (nil)
> let ch = #\A -- character literal
> let kw = :hello -- keyword symbol
>
> -- Assignment (mutation)
> x := 100 -- setf
>
> -- Destructuring multiple return values
> let (q, r) = floor(17, 5) -- multiple-value-bind
>
>
> -- ═══════════════════════════════════════════════════════════
> -- FUNCTIONS
> -- ═══════════════════════════════════════════════════════════
>
> -- Expression form (single expression body)
> fn square(x) = x * x
>
> -- Block form (multiple statements)
> fn greet(name)
> let msg = concat("Hello, ", name, "!")
> print(msg)
> msg
> end
>
> -- With docstring
> fn factorial(n)
> "Compute n! recursively."
> if n <= 1 then 1 else n * factorial(n - 1) end
> end
>
> -- Optional parameters
> fn connect(host, port = 8080) = print("%s:%d", host, port)
>
> -- Keyword parameters
> fn makeUser(name:, age: 0, role: "guest")
> hash(name: name, age: age, role: role)
> end
> -- Call with keywords: makeUser(name: "Gerry", age: 30)
>
> -- Rest parameters
> fn logAll(...args) = forEach(args, |a| print(a))
>
> -- CL lambda-list keywords also work
> fn withRest(a, b, &rest others) = list(a, b, others)
>
> -- Return values
> fn safeDivide(a, b)
> if b == 0 then return nil end
> a / b
> end
>
> -- Multiple return values
> fn divmod(a, b)
> return (floor(a, b)) -- returns quotient and remainder
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- CLOSURES (LAMBDAS)
> -- ═══════════════════════════════════════════════════════════
>
> -- Single expression
> let double = |x| x * 2
>
> -- Multi-statement
> let counter = |start| do
> let n = start
> n := n + 1
> n
> end
>
> -- No parameters
> let greetWorld = || print("Hello, World!")
>
> -- Function references (like CL #'name)
> let f = &square -- #'square
> map([1, 2, 3], &square) -- (mapcar #'square '(1 2 3))
>
>
> -- ═══════════════════════════════════════════════════════════
> -- DATA STRUCTURES
> -- ═══════════════════════════════════════════════════════════
>
> -- Lists
> let nums = [1, 2, 3, 4, 5]
> let empty = []
>
> -- Vectors
> let vec = #(10, 20, 30)
>
> -- Hash tables (dictionaries)
> let config = {host: "localhost", port: 8080, debug: true}
> let empty = {}
>
> -- Access
> config["host"] -- lark-ref (string key)
> nums[0] -- lark-ref (index)
>
> -- Dot access (method-style)
> config.host -- not valid for hashes, use []
> nums.length -- (length nums)
>
> -- Quoted symbols and lists
> let sym = 'hello -- (quote hello)
> let quoted = '(1, 2, 3) -- (quote (1 2 3))
>
>
> -- ═══════════════════════════════════════════════════════════
> -- OPERATORS
> -- ═══════════════════════════════════════════════════════════
>
> -- Arithmetic
> 1 + 2 -- addition
> 10 - 3 -- subtraction
> 4 * 5 -- multiplication
> 10 / 3 -- division (CL exact: 10/3)
> 17 mod 5 -- modulo
> 17 rem 5 -- remainder
> -x -- negation
>
> -- Comparison
> x == y -- equal
> x != y -- not equal
> x === y -- eq (identity)
> x !== y -- not eq
> x < y -- less than
> x > y -- greater than
> x <= y -- less or equal
> x >= y -- greater or equal
>
> -- Logical
> x and y -- short-circuit and
> x or y -- short-circuit or
> not x -- negation
>
> -- Pipeline (threading)
> "hello world" |> upper() |> split(" ")
> -- → (split (upper "hello world") " ")
>
> -- Data flows left to right:
> [1, 2, 3, 4, 5]
> |> filter(|x| x > 2)
> |> map(|x| x * 10)
> |> reduce(0, |acc, x| acc + x)
>
>
> -- ═══════════════════════════════════════════════════════════
> -- STRINGS
> -- ═══════════════════════════════════════════════════════════
>
> let s = "hello"
> let interp = "x is ${x} and 2+2 is ${2 + 2}" -- string interpolation
> let raw = "line1\nline2\ttab" -- escape sequences
>
> -- Triple-quoted strings (no escaping needed)
> let html = """
> <html>
> <body><h1>Hello</h1></body>
> </html>
> """
>
> let json = """
> {"host": "localhost", "port": 8080, "debug": true}
> """
>
> let regex = """\d+\.\d+""" -- backslashes are literal
>
> -- Triple-quoted with interpolation
> let page = """
> <html>
> <title>${title}</title>
> <body>${content}</body>
> </html>
> """
>
> -- String operations
> split("a,b,c", ",") -- ["a", "b", "c"]
> join(["a", "b"], "-") -- "a-b"
> trim(" hi ") -- "hi"
> upper("hello") -- "HELLO"
> lower("HELLO") -- "hello"
> replace("hello", "l", "r") -- "herlo" (first)
> replaceAll("hello", "l", "r") -- "herro" (all)
> startsWith?("hello", "he") -- true
> endsWith?("hello", "lo") -- true
> contains?("hello", "ell") -- true (works on strings, lists, hashes)
> indexOf("hello", "ll") -- 2
> substr("hello", 1, 3) -- "el"
> concat("a", "b", "c") -- "abc"
> repeat("ha", 3) -- "hahaha"
> lines("a\nb\nc") -- ["a", "b", "c"]
> words("hello world") -- ["hello", "world"]
> chars("abc") -- [#\a, #\b, #\c]
> padLeft("42", 5) -- " 42"
> padRight("hi", 5) -- "hi "
> len("hello") -- 5
>
>
> -- ═══════════════════════════════════════════════════════════
> -- COLLECTIONS
> -- ═══════════════════════════════════════════════════════════
>
> -- Functional operations
> map([1, 2, 3], |x| x * 2) -- [2, 4, 6]
> filter([1, 2, 3, 4], |x| x > 2) -- [3, 4]
> reject([1, 2, 3, 4], |x| x > 2) -- [1, 2]
> reduce([1, 2, 3], 0, |a, b| a + b) -- 6
> find([1, 2, 3], |x| x > 1) -- 2
> any?([1, 2, 3], |x| x > 2) -- true
> all?([1, 2, 3], |x| x > 0) -- true
> sort([3, 1, 2], |a, b| a < b) -- [1, 2, 3]
> forEach([1, 2, 3], |x| print(x)) -- side effects
>
> -- Container operations
> len([1, 2, 3]) -- 3
> size({a: 1, b: 2}) -- 2
> keys({a: 1, b: 2}) -- ["a", "b"]
> values({a: 1, b: 2}) -- [1, 2]
> has?({a: 1}, "a") -- true
> pairs({a: 1, b: 2}) -- [("a" . 1), ("b" . 2)]
> merge({a: 1}, {b: 2}) -- {a: 1, b: 2}
> contains?([1, 2, 3], 2) -- true
> flatten([[1, 2], [3, [4]]]) -- [1, 2, 3, 4]
> unique([1, 2, 2, 3]) -- [1, 2, 3]
>
> -- Mutation
> let lst = [1, 2, 3]
> push!(lst, 4) -- lst is now (4 1 2 3)
> pop!(lst) -- returns 4
> reverse!(lst) -- in-place reverse
> let h = {a: 1, b: 2}
> delete!(h, "a") -- remove key
>
> -- Slicing
> take([1, 2, 3, 4], 2) -- [1, 2]
> drop([1, 2, 3, 4], 2) -- [3, 4]
> zip([1, 2], ["a", "b"]) -- [(1 "a"), (2 "b")]
> range(5) -- [0, 1, 2, 3, 4]
> range(1, 5) -- [1, 2, 3, 4]
> range(0, 10, 2) -- [0, 2, 4, 6, 8]
> enumerate(["a", "b"]) -- [(0 . "a"), (1 . "b")]
>
>
> -- ═══════════════════════════════════════════════════════════
> -- CONTROL FLOW
> -- ═══════════════════════════════════════════════════════════
>
> -- If/elseif/else (expression — returns value)
> let label = if x > 100 then "big"
> elseif x > 10 then "medium"
> else "small"
> end
>
> -- If/then (statement)
> if ready then
> process()
> end
>
> -- Match (pattern matching)
> fn describe(val)
> match val with
> | 0 -> "zero"
> | 1 -> "one"
> | n when n < 0 -> "negative"
> | _ -> "other"
> end
> end
>
> -- Type matching
> fn handle(msg)
> match msg with
> | _: String -> print("string: %s", msg)
> | _: Integer -> print("integer: %d", msg)
> | _ -> print("unknown")
> end
> end
>
> -- Named bindings in match
> match result with
> | x when x > 0 -> print("positive: %d", x)
> | x -> print("non-positive: %d", x)
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- LOOPS
> -- ═══════════════════════════════════════════════════════════
>
> -- For-in (iteration)
> for item in [1, 2, 3] do
> print(item)
> end
>
> -- For-range (numeric)
> for i = 0, 10 do
> print(i)
> end
>
> -- For-range with step
> for i = 0, 100, 5 do
> print(i)
> end
>
> -- While loop
> let n = 10
> while n > 0 do
> print(n)
> n := n - 1
> end
>
> -- Loop (CL loop pass-through)
> loop
> for i from 1 to 10
> when i mod 2 == 0
> collect i
> end
>
> -- For-collect (loop comprehension)
> for x in range(10) when x > 5 collect x * 2
>
> -- For-sum
> for x in [1, 2, 3, 4] sum x * x
>
> -- List comprehension (bracket syntax)
> [x * 2 for x in range(10)]
> [x * 2 for x in range(10) if x > 5]
>
> -- Comprehension with destructuring
> [concat(k, "=", v) for (k, v) in pairs(config)]
>
>
> -- ═══════════════════════════════════════════════════════════
> -- ERROR HANDLING
> -- ═══════════════════════════════════════════════════════════
>
> -- Try/on/ensure (→ handler-case + unwind-protect)
> try
> riskyOperation()
> on Error e do
> print("caught: %s", e)
> on TypeError e do
> print("type error: %s", e)
> ensure
> cleanup()
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- LOCAL FUNCTIONS
> -- ═══════════════════════════════════════════════════════════
>
> -- Where block (→ labels, mutually recursive)
> where
> fn isEven(n) = n == 0 or isOdd(n - 1)
> fn isOdd(n) = n != 0 and isEven(n - 1)
> in
> isEven(42)
> end
>
> -- Flet block (→ flet, non-recursive)
> flet
> fn double(x) = x * 2
> fn triple(x) = x * 3
> in
> double(5) + triple(5)
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- CLASSES AND METHODS
> -- ═══════════════════════════════════════════════════════════
>
> -- Class definition (→ defclass)
> class Point(x: 0, y: 0)
>
> -- Inheritance
> class Point3D(z: 0) extends Point
>
> -- Struct definition (→ defstruct: faster, typed, no CLOS overhead)
> struct PacketHeader(version: 0, length: 0, type: :tcp)
>
> -- Struct with inheritance (single only)
> struct ExtHeader(extra: nil) extends PacketHeader
>
> -- Struct constructor: makeStructName(slot: value, ...)
> -- let pkt = makePacketHeader(version: 1, type: :udp)
>
> -- Struct predicate: structName?(x)
> -- packetHeader?(pkt) → true
>
> -- Slot access works the same as class: pkt.version, pkt.type
>
> -- Methods (typed params → defmethod)
> fn describe(p: Point) = format(nil, "(%d, %d)", p.x, p.y)
>
> -- Generic declaration
> generic area(shape)
>
> -- Method qualifiers via annotations
> @before
> fn validate(p: Point)
> print("about to use point")
> end
>
> @after
> fn logUsage(p: Point)
> print("point was used")
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- MACROS
> -- ═══════════════════════════════════════════════════════════
>
> -- Macro definition
> macro unless(test, &body body)
> `(if (not ,test) (progn ,@body))
> end
>
> -- Backtick templates work for quasiquoting
> macro myIf(test, consequent, alternate)
> `(cond (,test ,consequent) (t ,alternate))
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- PACKAGES
> -- ═══════════════════════════════════════════════════════════
>
> package MyLib
> use: [:cl]
> export: [:myFunc, :myVar]
> end
>
>
> -- ═══════════════════════════════════════════════════════════
> -- ANNOTATIONS
> -- ═══════════════════════════════════════════════════════════
>
> -- @inline — declare function inline
> @inline
> fn fastAdd(a, b) = a + b
>
> -- @const — compile-time constant
> @const let maxSize = 1024
>
> -- @type, @optimize, @ignore — CL declarations
> -- Used inside function bodies for type hints
> -- @type fixnum x
> -- @optimize (speed 3) (safety 0)
> -- Or use s-expression escape for complex declarations:
> @(declaim (optimize (speed 3) (safety 0)))
>
> -- @when/@unless — conditional compilation (feature flags)
> @when(:sbcl)
> fn sbclOnly() = print("SBCL specific")
>
>
> -- ═══════════════════════════════════════════════════════════
> -- TYPE DEFINITIONS
> -- ═══════════════════════════════════════════════════════════
>
> -- Type aliases (→ deftype)
> type Byte = @(unsigned-byte 8)
> type Index = @(integer 0 #.most-positive-fixnum)
>
>
> -- ═══════════════════════════════════════════════════════════
> -- PRINTING AND FORMAT
> -- ═══════════════════════════════════════════════════════════
>
> -- print: adds newline
> print("hello") -- simple
> print("x = %d", x) -- printf-style: %s %d %f %x %o %b %e %g
> print(x, y, z) -- multiple values (space-separated)
>
> -- printn: no newline
> printn("waiting...")
>
> -- format: CL format with printf syntax
> let s = format(nil, "%.2f", 3.14159) -- returns string
> format(t, "output: %s\n", result) -- prints to stdout
>
>
> -- ═══════════════════════════════════════════════════════════
> -- INTEROP WITH COMMON LISP
> -- ═══════════════════════════════════════════════════════════
>
> -- S-expression escape: @(raw CL code)
> @(defvar *global* 42)
> @(declaim (optimize (speed 3)))
>
> -- Backtick quasiquote
> let form = `(+ 1 2 3)
>
> -- All CL functions available directly
> let result = mapcar(&car, '((1, 2), (3, 4)))
>
> -- Name translation: camelCase → kebab-case
> -- hashTable → hash-table
> -- processOrders → process-orders
> -- empty? → emptyp (? → p)
> -- reverse! → nreverse (! → f)
> -- HTMLParser → html-parser
>
>
> -- ═══════════════════════════════════════════════════════════
> -- FFI (FOREIGN FUNCTION INTERFACE)
> -- ═══════════════════════════════════════════════════════════
>
> -- C library bindings (requires CFFI)
> -- clib "libm"
> -- fn sqrt(x: double) -> double
> -- fn pow(base: double, exp: double) -> double
> -- end
>
> -- C struct definitions
> -- cstruct Timeval
> -- sec: long
> -- usec: long
> -- end
>
> -- FFI types: int, uint, int8-64, uint8-64, float, double,
> -- ptr, cstr, void, bool, size, ssize
>
> -- FFI operations:
> -- alloc(type), cfree(ptr), nullPtr(), nullPtr?(p)
> -- sizeOf(type), memRef(ptr, type), memSet(ptr, type, val)
> -- cStr(s), fromCStr(ptr), slot(ptr, type, name)
> -- ptrInc(ptr, offset), ptrAddress(ptr), makePtr(addr)
>
>
> -- ═══════════════════════════════════════════════════════════
> -- SCRIPTING / EMBEDDING API
> -- ═══════════════════════════════════════════════════════════
>
> -- Create an isolated scripting environment
> -- let env = larkEnv("my-scripts")
>
> -- Execute Lark code in the environment
> -- larkRun(env, "let x = 42")
> -- larkRunFile(env, "scripts/handlers.lk")
>
> -- Exchange data between host and scripts
> -- let val = larkGet(env, "x")
> -- larkSet(env, "config", myConfig)
>
> -- Expose host functions to scripts
> -- larkExpose(env, "notify", |msg| sendSlack(msg))
>
> -- Call script functions from host
> -- larkCall(env, "onEvent", eventData)
>
> -- Hot reload
> -- larkReload(env)
>
> -- Quick one-shot (no environment)
> -- larkEval("2 + 2")
> -- larkEvalFile("script.lk")
>
> -- Cleanup
> -- larkDestroy(env)
>
>
>
> _______________________________________________
> Lisp Hug - the mailing list for LispWorks users
> [email protected]
> http://www.lispworks.com/support/lisp-hug.html
_______________________________________________
Lisp Hug - the mailing list for LispWorks users
[email protected]
http://www.lispworks.com/support/lisp-hug.html