11 KiB
11 KiB
Lyng Language Reference for AI Agents (Current Compiler State)
Purpose: dense, implementation-first reference for generating valid Lyng code.
Primary sources used: lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,Token,Compiler,Script,TypeDecl}.kt, lynglib/stdlib/lyng/root.lyng, tests in lynglib/src/commonTest and lynglib/src/jvmTest.
1. Ground Rules
- Resolution is compile-time-first. Avoid runtime name/member lookup assumptions.
lyng.stdlibis auto-seeded for normal scripts (default import manager).- Use explicit casts when receiver type is unknown (
Object/Obj). - Prefer modern null-safe operators (
?.,?:/??,?=,as?,!!). - Do not rely on fallback opcodes or dynamic member fallback semantics.
2. Lexical Syntax
- Comments:
// line,/* block */. - Strings:
"..."(supports escapes). Multiline string content is normalized by indentation logic. - Numbers:
Int(123,1_000),Real(1.2,1e3), hex (0xFF). - Char:
'a', escaped chars supported. - Labels:
- statement label:
loop@ for (...) { ... } - label reference:
break@loop,continue@loop,return@fnLabel
- statement label:
- Keywords/tokens include (contextual in many places):
- declarations:
fun/fn,val,var,class,object,interface,enum,type,init - modifiers:
private,protected,static,abstract,closed,override,extern,open - flow:
if,else,when,for,while,do,try,catch,finally,throw,return,break,continue
- declarations:
3. Literals and Core Expressions
- Scalars:
null,true,false,void. - List literal:
[a, b, c], spreads with....- Spread positions: beginning, middle, end are all valid:
[...a],[0, ...a, 4],[head, ...mid, tail]. - Spread source must be a
Listat runtime (non-list spread raises an error).
- Spread positions: beginning, middle, end are all valid:
- Map literal:
{ key: value, x:, ...otherMap }.x:means shorthandx: x.- Map spread source must be a
Map.
- Range literals:
- inclusive:
a..b - exclusive end:
a..<b - open-ended forms are supported (
a..,..b,..). - optional step:
a..b step 2
- inclusive:
- Lambda literal:
- with params:
{ x, y -> x + y } - implicit
it:{ it + 1 }
- with params:
- Ternary conditional is supported:
cond ? thenExpr : elseExpr.
3.1 Splats in Calls and Lambdas
- Declaration-side variadic parameters use ellipsis suffix:
- functions:
fun f(head, tail...) { ... } - lambdas:
{ x, rest... -> ... }
- functions:
- Call-side splats use
...exprand are expanded by argument kind:- positional splat:
f(...[1,2,3]) - named splat:
f(...{ a: 1, b: 2 })(map-style)
- positional splat:
- Runtime acceptance for splats:
- positional splat accepts
Listand generalIterable(iterable is converted to list first). - named splat accepts
Mapwith string keys only.
- positional splat accepts
- Ordering/validation rules (enforced):
- positional argument cannot follow named arguments (except trailing-block parsing case).
- positional splat cannot follow named arguments.
- duplicate named arguments are errors (including duplicates introduced via named splat).
- unknown named parameters are errors.
- variadic parameter itself cannot be passed as a named argument (
fun g(args..., tail)theng(args: ...)is invalid).
- Trailing block + named arguments:
- if the last callable parameter is already provided by name in parentheses, adding a trailing block is invalid.
4. Operators (implemented)
- Assignment:
=,+=,-=,*=,/=,%=,?=. - Logical:
||,&&, unary!. - Bitwise:
|,^,&,~, shifts<<,>>. - Equality/comparison:
==,!=,===,!==,<,<=,>,>=,<=>,=~,!~. - Type/containment:
is,!is,in,!in,as,as?. - Null-safe family:
- member access:
?. - safe index:
?[i] - safe invoke:
?(...) - safe block invoke:
?{ ... } - elvis:
?:and??.
- member access:
- Increment/decrement: prefix and postfix
++,--.
5. Declarations
- Variables:
valimmutable,varmutable.- top-level/local
valmust be initialized. - class
valmay be late-initialized, but must be assigned in class body/init before class parse ends. - destructuring declaration:
val [a, b, rest...] = expr. - destructuring declaration details:
- allowed in
valandvardeclarations. - supports nested patterns:
val [a, [b, c...], d] = rhs. - supports at most one splat (
...) per pattern level. - RHS must be a
List. - without splat: RHS must have at least as many elements as pattern arity.
- with splat: head/tail elements are bound directly, splat receives a
List.
- allowed in
- Functions:
funandfnare equivalent.- full body:
fun f(x) { ... } - shorthand:
fun f(x) = expr. - generics:
fun f<T>(x: T): T. - extension functions:
fun Type.name(...) { ... }. - delegated callable:
fun f(...) by delegate.
- Type aliases:
type Name = TypeExpr- generic:
type Box<T> = List<T> - aliases are expanded structurally.
- Classes/objects/enums/interfaces:
interfaceis parsed as abstract class synonym.objectsupports named singleton and anonymous object expression forms.- enums support lifted entries:
enum E* { A, B }. - multiple inheritance is supported; override is enforced when overriding base members.
- Properties/accessors in class body:
- accessor form supports
get/set, includingprivate set/protected set.
- accessor form supports
6. Control Flow
ifis expression-like.when(value) { ... }supported.- branch conditions support equality,
in,!in,is,!is, andnullablepredicate. when { ... }(subject-less) is currently not implemented.
- branch conditions support equality,
- Loops:
for,while,do ... while.- loop
elseblocks are supported. break valuecan return a loop result.
- loop
- Exceptions:
try/catch/finally,throw.
6.1 Destructuring Assignment (implemented)
- Reassignment form is supported (not only declaration):
[x, y] = [y, x]
- Semantics match destructuring declaration:
- nested patterns allowed.
- at most one splat per pattern level.
- RHS must be a
List. - too few RHS elements raises runtime error.
- Targets in pattern are variables parsed from identifier patterns.
7. Type System (current behavior)
- Non-null by default (
T), nullable withT?. as(checked cast),as?(safe cast returningnull),!!non-null assertion.- Type expressions support:
- unions
A | B - intersections
A & B - function types
(A, B)->Rand receiver formReceiver.(A)->R - variadics in function type via ellipsis (
T...)
- unions
- Generics:
- type params on classes/functions/type aliases
- bounds via
:with union/intersection expressions - declaration-site variance via
in/out
- Generic function/class/type syntax examples:
- function:
fun choose<T>(a: T, b: T): T = a - class:
class Box<T>(val value: T) - alias:
type PairList<T> = List<List<T>>
- function:
- Untyped params default to
Object(x) orObject?(x?shorthand). - Untyped
var xstarts asUnset; first assignment fixes type tracking in compiler.
7.1 Generics Runtime Model and Bounds (AI-critical)
- Lyng generic type information is operational in script execution contexts; do not assume JVM-style full erasure.
- Generic call type arguments can be:
- explicit at call site (
f<Int>(1)style), - inferred from runtime values/declared arg types,
- defaulted from type parameter defaults (or
Anyfallback).
- explicit at call site (
- At function execution, generic type parameters are runtime-bound as constants in scope:
- simple non-null class-like types are bound as
ObjClass, - complex/nullable/union/intersection forms are bound as
ObjTypeExpr.
- simple non-null class-like types are bound as
- Practical implication for generated code:
- inside generic code, treat type params as usable type objects in
is/in/type-expression logic (not as purely compile-time placeholders). - example pattern:
if (value is T) { ... }.
- inside generic code, treat type params as usable type objects in
- Bound syntax (implemented):
- intersection bound:
fun f<T: A & B>(x: T) { ... } - union bound:
fun g<T: A | B>(x: T) { ... }
- intersection bound:
- Bound checks happen at two points:
- compile-time call checking for resolvable generic calls,
- runtime re-check while binding type params for actual invocation.
- Bound satisfaction is currently class-hierarchy based for class-resolvable parts (including union/intersection combination rules).
- Keep expectations realistic:
- extern-generic runtime ABI for full instance-level generic metadata is still proposal-level (
proposals/extern_generic_runtime_abi.md), so avoid assuming fully materialized generic-instance metadata everywhere.
- extern-generic runtime ABI for full instance-level generic metadata is still proposal-level (
7.2 Differences vs Java / Kotlin / Scala
- Java:
- Java generics are erased at runtime (except reflection metadata and raw
Classtokens). - Lyng generic params in script execution are runtime-bound type objects, so generated code can reason about
Tdirectly.
- Java generics are erased at runtime (except reflection metadata and raw
- Kotlin:
- Kotlin on JVM is mostly erased; full runtime type access usually needs
inline reified. - Lyng generic function execution binds
Twithout requiring an inline/reified escape hatch.
- Kotlin on JVM is mostly erased; full runtime type access usually needs
- Scala:
- Scala has richer static typing but still runs on JVM erasure model unless carrying explicit runtime evidence (
TypeTag, etc.). - Lyng exposes runtime-bound type expressions/classes directly in generic execution scope.
- Scala has richer static typing but still runs on JVM erasure model unless carrying explicit runtime evidence (
- AI generation rule:
- do not port JVM-language assumptions like “
Tunavailable at runtime unless reified/tagged”. - in Lyng, prefer direct type-expression-driven branching when useful, but avoid assuming extern object generic args are always introspectable today.
- do not port JVM-language assumptions like “
8. OOP, Members, and Dispatch
- Multiple inheritance with C3-style linearization behavior is implemented in class machinery.
- Disambiguation helpers are supported:
- qualified this:
this@Base.member() - cast view:
(obj as Base).member()
- qualified this:
- On unknown receiver types, compiler allows only Object-safe members:
toString,toInspectString,let,also,apply,run
- Other members require known receiver type or explicit cast.
9. Delegation (by)
- Works for
val,var, andfun. - Expected delegate hooks in practice:
getValue(thisRef, name)setValue(thisRef, name, newValue)invoke(thisRef, name, args...)for delegated callables- optional
bind(name, access, thisRef)
@Transientis recognized for declarations/params and affects serialization/equality behavior.
10. Modules and Imports
packageandimport module.nameare supported.- Import form is module-only (no aliasing/selective import syntax in parser).
- Default module ecosystem includes:
- auto-seeded:
lyng.stdlib - available by import:
lyng.observable,lyng.buffer,lyng.serialization,lyng.time - extra module (when installed):
lyng.io.fs,lyng.io.process
- auto-seeded:
11. Current Limitations / Avoid
- No subject-less
when { ... }yet. - No regex literal tokenization (
/.../); useRegex("...")or"...".re. - Do not generate runtime name fallback patterns from legacy docs.