Skip to content

Commit 65a8066

Browse files
andreasrongegithub-actions[bot]claude
authored
feat(lisp): triage tier 6 core candidates (#942)
* feat(lisp): triage tier 6 core candidates Add small Clojure core compatibility aliases for Tier 6 audit items, including arbitrary-precision arithmetic aliases, array-map, hash-set, nthrest/nthnext, ==, and parse-boolean. Mark the remaining low-value/JVM-specific candidates as not relevant and regenerate the function reference and Clojure core audit docs. Verified with: - mix test test/ptc_runner/lisp/parser_test.exs test/ptc_runner/lisp/runtime_arithmetic_test.exs test/ptc_runner/lisp/runtime/special_values_test.exs test/ptc_runner/lisp/integration/collection_ops_test.exs --max-failures 5 - mix test test/ptc_runner/lisp/registry_test.exs test/readme_test.exs --max-failures 5 - mix test --max-failures 10 * fix(lisp): address core candidate review Normalize nthrest/nthnext through seqable conversion, align var-reader symbol parsing with normal symbols, and return a structured arity error for zero-arity -'. Verified with targeted Lisp parser/collection/arithmetic suites, related arity/bitwise regressions, and full mix test before rebase. * fix(lisp): address PR #942 review suggestions - DIV-18 doc: add parse-boolean alongside parse-long/parse-double - array-map: produce correct error message (was saying hash-map) - ==: document non-numeric acceptance divergence from Clojure (DIV-10) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cd08d26 commit 65a8066

17 files changed

Lines changed: 516 additions & 148 deletions

docs/clojure-conformance-gaps.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ No `:pre`/`:post` condition maps in `defn`. Without exception handling, assertio
398398

399399
**Rationale:** Matches Clojure. Ambiguous which `%` refers to which scope.
400400

401-
### DIV-18: `parse-long`/`parse-double` return `nil` for non-string input
401+
### DIV-18: `parse-long`/`parse-double`/`parse-boolean` return `nil` for non-string input
402402

403403
| Field | Value |
404404
|-------|-------|
@@ -408,10 +408,14 @@ No `:pre`/`:post` condition maps in `defn`. Without exception handling, assertio
408408

409409
```clojure
410410
;; Clojure 1.11+
411-
(parse-long 42) ;=> IllegalArgumentException
411+
(parse-long 42) ;=> IllegalArgumentException
412+
(parse-double 42) ;=> IllegalArgumentException
413+
(parse-boolean 42) ;=> IllegalArgumentException
412414

413415
;; PTC-Lisp
414-
(parse-long 42) ;=> nil
416+
(parse-long 42) ;=> nil
417+
(parse-double 42) ;=> nil
418+
(parse-boolean 42) ;=> nil
415419
```
416420

417421
**Rationale:** No exception handling (DIV-10). Returning `nil` is safer for LLM-generated code.

docs/clojure-core-audit.md

Lines changed: 47 additions & 47 deletions
Large diffs are not rendered by default.

docs/function-reference.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
> **Warning:** This file is auto-generated by `mix ptc.gen_docs` from `priv/functions.exs`.
55
> Manual edits will be overwritten. Edit `priv/functions.exs` instead.
66
7-
292 functions and special forms.
7+
308 functions and special forms.
88

99
See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelines](ptc-lisp-design-guidelines.md) | [Clojure Core](clojure-core-audit.md) | [Clojure String](clojure-string-audit.md) | [Clojure Set](clojure-set-audit.md) | [Java Math](java-math-audit.md)
1010

@@ -15,11 +15,12 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
1515
- [Threading Macros](#threading-macros) (7)
1616
- [Control Flow](#control-flow) (4)
1717
- [Iteration](#iteration) (2)
18-
- [Core](#core) (167)
18+
- [Core](#core) (177)
19+
- [Predicate Builders](#predicate-builders) (4)
1920
- [Functional Tools](#functional-tools) (3)
2021
- [Agent Control](#agent-control) (5)
21-
- [String Functions](#string-functions) (24)
22-
- [Set Operations](#set-operations) (9)
22+
- [String Functions](#string-functions) (25)
23+
- [Set Operations](#set-operations) (10)
2324
- [Regex Functions](#regex-functions) (6)
2425
- [Math Functions](#math-functions) (24)
2526
- [Interop](#interop) (18)
@@ -98,16 +99,21 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
9899
| Function | Signature | Description |
99100
|----------|-----------|-------------|
100101
| `*` | `(* x y ...)` | Multiplication |
102+
| `*'` | `(*' x y ...)` | Arbitrary precision multiplication alias |
101103
| `+` | `(+ x y ...)` | Addition |
104+
| `+'` | `(+' x y ...)` | Arbitrary precision addition alias |
102105
| `-` | `(- x y ...)` | Subtraction |
106+
| `-'` | `(-' x y ...)` | Arbitrary precision subtraction alias |
103107
| `/` | `(/ x y)` | Division (always returns float) |
104108
| `<` | `(< x y)` | Less than |
105109
| `<=` | `(<= x y)` | Less or equal |
106110
| `=` | `(= x y)` | Equality |
111+
| `==` | `(== x y)` | Type-independent numeric equality |
107112
| `>` | `(> x y)` | Greater than |
108113
| `>=` | `(>= x y)` | Greater or equal |
109114
| `NaN?` | `(NaN? ...)` | |
110115
| `apply` | `(apply f coll)` | Applies function `f` to the argument sequence `coll` |
116+
| `array-map` | `(array-map & kvs)` | Create map from alternating key-value pairs |
111117
| `assoc` | `(assoc m key val)` | Add/update key |
112118
| `assoc-in` | `(assoc-in m path val)` | Add/update nested |
113119
| `associative?` | `(associative? ...)` | |
@@ -129,6 +135,7 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
129135
| `count` | `(count coll)` | Number of items |
130136
| `counted?` | `(counted? ...)` | |
131137
| `dec` | `(dec x)` | Subtract 1 |
138+
| `dec'` | `(dec' x)` | Arbitrary precision decrement alias |
132139
| `decimal?` | `(decimal? ...)` | |
133140
| `dedupe` | `(dedupe coll)` | Remove consecutive duplicates |
134141
| `dissoc` | `(dissoc m key)` | Remove key |
@@ -164,6 +171,7 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
164171
| `identity` | `(identity x)` | Returns argument unchanged |
165172
| `ifn?` | `(ifn? ...)` | |
166173
| `inc` | `(inc x)` | Add 1 |
174+
| `inc'` | `(inc' x)` | Arbitrary precision increment alias |
167175
| `indexed?` | `(indexed? ...)` | |
168176
| `infinite?` | `(infinite? ...)` | |
169177
| `int?` | `(int? ...)` | |
@@ -205,6 +213,8 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
205213
| `not-every?` | `(not-every? :key coll)` | True if not all have truthy `:key` |
206214
| `not=` | `(not= x y)` | Inequality |
207215
| `nth` | `(nth coll idx)` | Item at index or nil |
216+
| `nthnext` | `(nthnext coll n)` | Drop n items and return seq or nil |
217+
| `nthrest` | `(nthrest coll n)` | Drop n items |
208218
| `number?` | `(number? ...)` | |
209219
| `odd?` | `(odd? ...)` | |
210220
| `partial` | `(partial f arg1 ...)` | Returns a function with some arguments pre-filled |
@@ -270,6 +280,10 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
270280
;; => %{}
271281
(hash-map :a 1 :b 2)
272282
;; => {:a 1, :b 2}
283+
(array-map)
284+
;; => %{}
285+
(array-map :a 1 :b 2)
286+
;; => {:a 1, :b 2}
273287
```
274288

275289

@@ -310,6 +324,7 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
310324
| `last-index-of` | `(last-index-of s value), (last-index-of s value from-index)` | Index of last occurrence up to position |
311325
| `lower-case` | `(lower-case ...)` | |
312326
| `name` | `(name x)` | Returns name string of keyword or string |
327+
| `parse-boolean` | `(parse-boolean s)` | Parse string to boolean |
313328
| `parse-double` | `(parse-double ...)` | |
314329
| `parse-int` | `(parse-int ...)` | |
315330
| `parse-long` | `(parse-long ...)` | |
@@ -324,6 +339,12 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
324339
| `upcase` * | `(upcase ...)` | |
325340
| `upper-case` | `(upper-case ...)` | |
326341

342+
```clojure
343+
(parse-boolean "true")
344+
;; => true
345+
(parse-boolean "false")
346+
;; => false
347+
```
327348

328349

329350
## Set Operations
@@ -333,13 +354,18 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
333354
| `contains?` | `(contains? coll key)` | True if key/element exists (maps, sets, lists) |
334355
| `difference` | `(clojure.set/difference & sets)` | Returns the difference of one or more sets |
335356
| `disj` | `(disj set x ...)` | Remove elements from set |
357+
| `hash-set` | `(hash-set & items)` | Create set from arguments |
336358
| `intersection` | `(clojure.set/intersection & sets)` | Returns the intersection of one or more sets |
337359
| `set` | `(set coll)` | Convert collection to set |
338360
| `set?` | `(set? x)` | Returns true if x is a set |
339361
| `union` | `(clojure.set/union & sets)` | Returns the union of zero or more sets |
340362
| `vec` | `(vec coll)` | Convert collection to vector |
341363
| `vector` | `(vector & args)` | Create vector from arguments |
342364

365+
```clojure
366+
(hash-set 1 1 2)
367+
;; => #{1 2}
368+
```
343369

344370

345371
## Regex Functions
@@ -427,4 +453,3 @@ See also: [PTC-Lisp Specification](ptc-lisp-specification.md) | [Design Guidelin
427453
| `mcp/json` | `(mcp/json result)` | Extract typed JSON from an MCP tool-result envelope. Returns structuredContent if set, else parses content[0].text. |
428454
| `mcp/text` | `(mcp/text result)` | Extract content[0].text from an MCP tool-result envelope; nil for any non-conforming input. |
429455

430-

lib/ptc_runner/lisp/env.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ defmodule PtcRunner.Lisp.Env do
184184
{:nnext, {:normal, &Runtime.nnext/1}},
185185
{:take, {:normal, &Runtime.take/2}},
186186
{:drop, {:normal, &Runtime.drop/2}},
187+
{:nthrest, {:normal, &Runtime.nthrest/2}},
188+
{:nthnext, {:normal, &Runtime.nthnext/2}},
187189
{:"take-while", {:normal, &Runtime.take_while/2}},
188190
{:"drop-while", {:normal, &Runtime.drop_while/2}},
189191
{:"take-last", {:normal, &Runtime.take_last/2}},
@@ -292,6 +294,11 @@ defmodule PtcRunner.Lisp.Env do
292294
{:quot, {:normal, &Runtime.Math.quot/2}},
293295
{:inc, {:normal, &Runtime.inc/1}},
294296
{:dec, {:normal, &Runtime.dec/1}},
297+
{:"+'", {:collect, &Runtime.Math.add/1}},
298+
{:"-'", {:collect, &Runtime.Math.subtract/1}},
299+
{:"*'", {:collect, &Runtime.Math.multiply/1}},
300+
{:"inc'", {:normal, &Runtime.inc/1}},
301+
{:"dec'", {:normal, &Runtime.dec/1}},
295302
{:abs, {:normal, &Runtime.abs/1}},
296303
{:max, {:variadic_nonempty, :max, &Runtime.Math.max/2}},
297304
{:min, {:variadic_nonempty, :min, &Runtime.Math.min/2}},
@@ -324,6 +331,7 @@ defmodule PtcRunner.Lisp.Env do
324331
# Comparison — normal (binary)
325332
# ============================================================
326333
{:=, {:normal, &Runtime.Math.eq/2}},
334+
{:==, {:normal, &Runtime.Math.eq/2}},
327335
{:"not=", {:normal, &Runtime.Math.not_eq/2}},
328336
{:>, {:normal, &Runtime.gt/2}},
329337
{:<, {:normal, &Runtime.lt/2}},
@@ -373,6 +381,8 @@ defmodule PtcRunner.Lisp.Env do
373381
# reach for Clojure's `list`. Returning a vector keeps semantics uniform.
374382
{:list, {:collect, &Function.identity/1}},
375383
{:"hash-map", {:collect, &Runtime.hash_map/1}},
384+
{:"array-map", {:collect, &Runtime.array_map/1}},
385+
{:"hash-set", {:collect, &Runtime.hash_set/1}},
376386
{:map?, {:normal, &Runtime.map?/1}},
377387
{:coll?, {:normal, &Runtime.coll?/1}},
378388
{:sequential?, {:normal, &Runtime.sequential?/1}},
@@ -418,6 +428,7 @@ defmodule PtcRunner.Lisp.Env do
418428
{:"parse-long", {:normal, &Runtime.parse_long/1}},
419429
{:"parse-int", {:normal, &Runtime.parse_long/1}},
420430
{:"parse-double", {:normal, &Runtime.parse_double/1}},
431+
{:"parse-boolean", {:normal, &Runtime.parse_boolean/1}},
421432

422433
# ============================================================
423434
# JSON builtins (Plans/json-support.md §4)

lib/ptc_runner/lisp/eval/apply.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ defmodule PtcRunner.Lisp.Eval.Apply do
1717
alias PtcRunner.Lisp.Eval.Context, as: EvalContext
1818
alias PtcRunner.Lisp.Eval.Helpers
1919
alias PtcRunner.Lisp.Eval.Patterns
20+
alias PtcRunner.Lisp.ExecutionError
2021
alias PtcRunner.Lisp.Format
2122
alias PtcRunner.Lisp.Runtime.Math
2223
alias PtcRunner.SubAgent.Namespace.TypeVocabulary
@@ -263,6 +264,10 @@ defmodule PtcRunner.Lisp.Eval.Apply do
263264
push_side_effect_stash()
264265
result = fun.(converted_args)
265266
{:ok, result, pop_side_effects(eval_ctx)}
267+
rescue
268+
e in ExecutionError ->
269+
pop_side_effects(eval_ctx)
270+
{:error, execution_error_tuple(e)}
266271
end
267272

268273
# Multi-arity builtins: select function based on argument count
@@ -678,6 +683,12 @@ defmodule PtcRunner.Lisp.Eval.Apply do
678683
end
679684
end
680685

686+
defp execution_error_tuple(%ExecutionError{reason: reason, message: message, data: nil}),
687+
do: {reason, message}
688+
689+
defp execution_error_tuple(%ExecutionError{reason: reason, message: message, data: data}),
690+
do: {reason, message, data}
691+
681692
# ============================================================
682693
# Closure Execution Helpers
683694
# ============================================================

lib/ptc_runner/lisp/parser.ex

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ defmodule PtcRunner.Lisp.Parser do
3232
# Character classes for lookahead
3333
# Note: . is allowed for Clojure-style namespaces like clojure.string/join
3434
# Note: & is allowed for rest pattern destructuring [a & rest]
35-
symbol_rest = [?a..?z, ?A..?Z, ?0..?9, ?+, ?-, ?*, ?/, ?<, ?>, ?=, ??, ?!, ?_, ?%, ?., ?&]
35+
# Note: ' is allowed after the first character for Clojure core names like inc'
36+
symbol_rest = [?a..?z, ?A..?Z, ?0..?9, ?+, ?-, ?*, ?/, ?<, ?>, ?=, ??, ?!, ?_, ?%, ?., ?&, ?']
3637

3738
nil_literal =
3839
string("nil")
@@ -188,10 +189,8 @@ defmodule PtcRunner.Lisp.Parser do
188189
defcombinatorp(
189190
:var_reader,
190191
ignore(string("#'"))
191-
|> ascii_string([?a..?z, ?A..?Z, ?+, ?-, ?*, ?/, ?<, ?>, ?=, ??, ?!, ?_], 1)
192-
|> optional(
193-
ascii_string([?a..?z, ?A..?Z, ?0..?9, ?+, ?-, ?*, ?/, ?<, ?>, ?=, ??, ?!, ?_], min: 1)
194-
)
192+
|> ascii_string(symbol_first, 1)
193+
|> optional(ascii_string(symbol_rest, min: 1))
195194
|> reduce({ParserHelpers, :build_var, []})
196195
)
197196

lib/ptc_runner/lisp/runtime.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ defmodule PtcRunner.Lisp.Runtime do
7070
defdelegate nnext(coll), to: Collection
7171
defdelegate take(n, coll), to: Collection
7272
defdelegate drop(n, coll), to: Collection
73+
defdelegate nthrest(coll, n), to: Collection
74+
defdelegate nthnext(coll, n), to: Collection
7375
defdelegate take_while(pred, coll), to: Collection
7476
defdelegate drop_while(pred, coll), to: Collection
7577
defdelegate distinct(coll), to: Collection
@@ -125,6 +127,7 @@ defmodule PtcRunner.Lisp.Runtime do
125127
defdelegate range(start, end_val), to: Collection
126128
defdelegate range(start, end_val, step), to: Collection
127129
defdelegate combinations(coll, n), to: Collection
130+
defdelegate hash_set(args), to: Collection
128131

129132
# Tree Traversal
130133
defdelegate walk(inner, outer, form), to: Collection
@@ -163,6 +166,7 @@ defmodule PtcRunner.Lisp.Runtime do
163166
defdelegate reduce_kv(f, init, m), to: MapOps
164167
defdelegate zipmap(keys, vals), to: MapOps
165168
defdelegate hash_map(args), to: MapOps
169+
defdelegate array_map(args), to: MapOps
166170

167171
# ============================================================
168172
# Arithmetic
@@ -265,6 +269,7 @@ defmodule PtcRunner.Lisp.Runtime do
265269

266270
defdelegate parse_long(s), to: RuntimeString
267271
defdelegate parse_double(s), to: RuntimeString
272+
defdelegate parse_boolean(s), to: RuntimeString
268273

269274
# ============================================================
270275
# Regex Operations

lib/ptc_runner/lisp/runtime/collection.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ defmodule PtcRunner.Lisp.Runtime.Collection do
241241
coll |> Enum.drop(n) |> Enum.map(fn {k, v} -> [k, v] end)
242242
end
243243

244+
def nthrest(coll, n), do: Enum.drop(Normalize.to_seq(coll), max(n, 0))
245+
def nthnext(coll, n), do: coll |> nthrest(n) |> seq()
246+
244247
# ============================================================
245248
# Distinct
246249
# ============================================================
@@ -253,6 +256,8 @@ defmodule PtcRunner.Lisp.Runtime.Collection do
253256
# Construction
254257
# ============================================================
255258

259+
def hash_set(args) when is_list(args), do: MapSet.new(args)
260+
256261
def concat2(a, b) do
257262
a = a || []
258263
b = b || []

lib/ptc_runner/lisp/runtime/map_ops.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ defmodule PtcRunner.Lisp.Runtime.MapOps do
2323
iex> PtcRunner.Lisp.Runtime.MapOps.hash_map([:a, 1, :b, 2])
2424
%{a: 1, b: 2}
2525
"""
26+
def array_map(args) when is_list(args) do
27+
if rem(length(args), 2) != 0 do
28+
raise ExecutionError,
29+
message: "array-map requires an even number of arguments, got #{length(args)}"
30+
end
31+
32+
args
33+
|> Enum.chunk_every(2)
34+
|> Enum.into(%{}, fn [k, v] -> {k, v} end)
35+
end
36+
2637
def hash_map(args) when is_list(args) do
2738
if rem(length(args), 2) != 0 do
2839
raise ExecutionError,

lib/ptc_runner/lisp/runtime/math.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ defmodule PtcRunner.Lisp.Runtime.Math do
2929

3030
def add(x, y), do: add([x, y])
3131

32+
def subtract([]), do: arity_error("-'")
33+
3234
def subtract([x]) do
3335
case x do
3436
:nan -> :nan

0 commit comments

Comments
 (0)