Re: Lark - A surface syntax for Common Lisp
"Stefan Edwards (as saedwards dot ecc at gmail dot com)" <[email protected]>
| Newsgroups | gmane.lisp.lispworks.general |
|---|---|
| Message-ID | <CAEMRNFN03Ax0q3BYe_JOipDJEVDBd4gNX41ZvxWcfDFWtU6b0g@mail.gmail.com> |
I always enjoy these alternative syntax projects; Sweet Expressions and SRFI-49 especially. Are you using this for any large projects yourself or just experimenting with the syntax broadly? I would be curious to follow along and potentially help if this ended up on GitHub or Codeberg for sure. ==== Q. How many Prolog programmers does it take to change a lightbulb? A. No. On Sat, Feb 14, 2026 at 14:40 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 >