Lark - A surface syntax for Common Lisp

"Gerry Weaver (as gerryw at compvia dot com)" <[email protected]>
Newsgroups gmane.lisp.lispworks.general
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.