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