You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Create the StreamData generators module for property-based testing of PTC-Lisp. This module generates valid Raw AST nodes that can be serialized by the Formatter and parsed back, enabling roundtrip and invariant property tests.
Context
Architecture reference: docs/ptc-lisp-property-testing-plan.md Section 3 Dependencies: Issue #128 (closed) - StreamData dependency and Formatter module ✅ Related issues: None
Current State
StreamData dependency is installed in mix.exs
Formatter module exists at lib/ptc_runner/lisp/formatter.ex
Test support directory exists with existing utilities: llm_benchmark.ex, llm_client.ex, ptc_lisp_benchmark.ex
No lisp_generators.ex exists yet
The AST types are defined in lib/ptc_runner/lisp/ast.ex:
Primitive generators should be bounded (integers -1_000_000..1_000_000, floats with similar bounds)
Float generator must filter out NaN values using filter(fn f -> not (f != f) end)
String generators should use alphanumeric for simplicity, with separate gen_string_with_escapes for roundtrip testing
Depth control is critical - use depth parameter to prevent infinite recursion
Scope tracking for let and fn generators to produce well-scoped programs
Division / is excluded from arithmetic to avoid divide-by-zero
Note on String.to_atom/1:
The plan specifies this is safe in generators because gen_identifier produces bounded strings from a fixed character set, preventing atom table exhaustion.
Test Plan
Unit tests (create test/support/lisp_generators_test.exs):
defmodulePtcRunner.TestSupport.LispGeneratorsTestdouseExUnit.Case,async: trueuseExUnitPropertiesaliasPtcRunner.TestSupport.LispGenerators,as: GenaliasPtcRunner.Lisp.{Formatter,Parser}describe"primitive generators"doproperty"gen_integer produces integers"docheckalln<-Gen.gen_integer()doassertis_integer(n)assertn>=-1_000_000andn<=1_000_000endendproperty"gen_float produces valid floats (no NaN)"docheckallf<-Gen.gen_float()doassertis_float(f)# NaN check: f != f is true only for NaNrefutef!=fendendproperty"gen_string produces valid string AST"docheckallstr<-Gen.gen_string()doassert{:string,s}=strassertis_binary(s)endendproperty"gen_keyword produces valid keyword AST"docheckallkw<-Gen.gen_keyword()doassert{:keyword,k}=kwassertis_atom(k)endendenddescribe"expression generator"doproperty"gen_expr with depth 0 produces leaf expressions"docheckallexpr<-Gen.gen_expr(0)do# Should be a leaf: literal, symbol, or ctx accessrefutematch?({:list,_},expr)refutematch?({:vector,_},expr)refutematch?({:map,_},expr)endendproperty"gen_expr produces formattable AST"docheckallexpr<-Gen.gen_expr(2)dosource=Formatter.format(expr)assertis_binary(source)assertString.length(source)>0endendenddescribe"special forms"doproperty"gen_if produces valid if structure"docheckallif_expr<-Gen.gen_if(1,[])doassert{:list,[{:symbol,:if},_cond,_then,_else]}=if_exprendendproperty"gen_let produces valid let structure"docheckalllet_expr<-Gen.gen_let(1,[])doassert{:list,[{:symbol,:let},{:vector,_bindings},_body]}=let_exprendendendend
E2E test (in same file or separate):
describe"roundtrip integration"doproperty"generated AST roundtrips through format -> parse"docheckallexpr<-Gen.gen_expr(2)dosource=Formatter.format(expr)assert{:ok,_parsed}=Parser.parse(source)endendend
Summary
Create the StreamData generators module for property-based testing of PTC-Lisp. This module generates valid Raw AST nodes that can be serialized by the Formatter and parsed back, enabling roundtrip and invariant property tests.
Context
Architecture reference:
docs/ptc-lisp-property-testing-plan.mdSection 3Dependencies: Issue #128 (closed) - StreamData dependency and Formatter module ✅
Related issues: None
Current State
lib/ptc_runner/lisp/formatter.exllm_benchmark.ex,llm_client.ex,ptc_lisp_benchmark.exlisp_generators.exexists yetThe AST types are defined in
lib/ptc_runner/lisp/ast.ex:nil,boolean(),number(),{:string, String.t()},{:keyword, atom()}{:vector, [t()]},{:map, [{t(), t()}]}{:symbol, atom()},{:ns_symbol, :ctx | :memory, atom()}{:list, [t()]}Acceptance Criteria
test/support/lisp_generators.exwithPtcRunner.TestSupport.LispGeneratorsmodulegen_nil,gen_boolean,gen_integer,gen_float,gen_string,gen_string_with_escapes,gen_keyword,gen_identifiergen_builtin_symbol,gen_variable_from_scope,gen_ctx_accessgen_vector,gen_map(with depth control)gen_leaf_expr,gen_expr(main entry point with depth control)gen_if,gen_let,gen_fn,gen_arithmetic_call,gen_comparison,gen_and,gen_or,gen_where,gen_tool_callmix compile --warnings-as-errorspassesImplementation Hints
Files to create:
test/support/lisp_generators.ex- Main generators moduleFiles to modify:
Patterns to follow:
test/support/llm_benchmark.ex(module structure, documentation)use ExUnitPropertiesas shown in the property testing plan Section 3StreamDatafunctions:constant/1,map/2,bind/2,frequency/1,member_of/1,filter/2,tuple/1,list_of/2,one_of/1Key implementation details from Section 3:
filter(fn f -> not (f != f) end)gen_string_with_escapesfor roundtrip testingdepthparameter to prevent infinite recursionletandfngenerators to produce well-scoped programs/is excluded from arithmetic to avoid divide-by-zeroNote on String.to_atom/1:
The plan specifies this is safe in generators because
gen_identifierproduces bounded strings from a fixed character set, preventing atom table exhaustion.Test Plan
Unit tests (create
test/support/lisp_generators_test.exs):E2E test (in same file or separate):
Out of Scope
when,cond, threading macros (->,->>), predicate combinators (all-of,any-of,none-of)memory/key) - excluded per plan Section 7, point 2Documentation Updates
None - this is test infrastructure only.