Compare commits
5 Commits
f0fc7ddd84
...
d9a26dd467
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a26dd467 | |||
| 813ebebddd | |||
| 2d721101dd | |||
| d6e6d68b18 | |||
| 83825a9272 |
17
CHANGELOG.md
17
CHANGELOG.md
@ -2,6 +2,23 @@
|
|||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- Language: Named arguments and named splats
|
||||||
|
- New call-site syntax for named arguments using colon: `name: value`.
|
||||||
|
- Positional arguments must come before named; positionals after a named argument inside parentheses are rejected.
|
||||||
|
- Trailing-lambda interaction: if the last parameter is already assigned by name (or via a named splat), a trailing `{ ... }` block is illegal.
|
||||||
|
- Named splats: `...` can now expand a Map into named arguments.
|
||||||
|
- Only string keys are allowed; non-string keys raise a clear error.
|
||||||
|
- Duplicate assignment across named args and named splats is an error.
|
||||||
|
- Ellipsis (variadic) parameters remain positional-only and cannot be named.
|
||||||
|
- Rationale: `=` is assignment and an expression in Lyng; `:` at call sites avoids ambiguity. Declarations keep `name: Type`; call-site casts continue to use `as` / `as?`.
|
||||||
|
- Documentation updated: proposals and declaring-arguments sections now cover named args/splats and error cases.
|
||||||
|
- Tests added covering success cases and errors for named args/splats and trailing-lambda interactions.
|
||||||
|
|
||||||
|
- Tooling: Highlighters and TextMate bundle updated for named args
|
||||||
|
- Website/editor highlighter (lyngweb + site) works with `name: value` and `...Map("k" => v)`; added JS tests covering punctuation/operator spans for `:` and `...`.
|
||||||
|
- TextMate grammar updated to recognize named call arguments: `name: value` after `(` or `,` with `name` highlighted as `variable.parameter.named.lyng` and `:` as punctuation; excludes `::`.
|
||||||
|
- TextMate bundle version bumped to 0.0.3; README updated with details and guidance.
|
||||||
|
|
||||||
- Multiple Inheritance (MI) completed and enabled by default:
|
- Multiple Inheritance (MI) completed and enabled by default:
|
||||||
- Active C3 Method Resolution Order (MRO) for deterministic, monotonic lookup across complex hierarchies and diamonds.
|
- Active C3 Method Resolution Order (MRO) for deterministic, monotonic lookup across complex hierarchies and diamonds.
|
||||||
- Qualified dispatch:
|
- Qualified dispatch:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ lambdas and class declarations.
|
|||||||
|
|
||||||
## Regular
|
## Regular
|
||||||
|
|
||||||
## default values
|
## Default values
|
||||||
|
|
||||||
Default parameters should not be mixed with mandatory ones:
|
Default parameters should not be mixed with mandatory ones:
|
||||||
|
|
||||||
@ -96,5 +96,52 @@ There could be any number of splats at any positions. You can splat any other [I
|
|||||||
>>> [start,1,2,3,end]
|
>>> [start,1,2,3,end]
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## Named arguments in calls
|
||||||
|
|
||||||
|
Lyng supports named arguments at call sites using colon syntax `name: value`:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
|
||||||
|
|
||||||
|
assertEquals(["foo", "b", "bazz"], test(b: "b"))
|
||||||
|
assertEquals(["a", "bar", "c"], test("a", c: "c"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Named arguments must follow positional arguments. After the first named argument, no positional arguments may appear inside the parentheses.
|
||||||
|
- The only exception is the syntactic trailing block after the call: `f(args) { ... }`. This block is outside the parentheses and is handled specially (see below).
|
||||||
|
- A named argument cannot reassign a parameter already set positionally.
|
||||||
|
- If the last parameter has already been assigned by a named argument (or named splat), a trailing block is not allowed and results in an error.
|
||||||
|
|
||||||
|
Why `:` and not `=` at call sites? In Lyng, `=` is an expression (assignment), so we use `:` to avoid ambiguity. Declarations continue to use `:` for types, while call sites use `as` / `as?` for type operations.
|
||||||
|
|
||||||
|
## Named splats (map splats)
|
||||||
|
|
||||||
|
Splat (`...`) of a Map provides named arguments to the call. Only string keys are allowed:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
|
||||||
|
val r = test("A?", ...Map("d" => "D!", "b" => "B!"))
|
||||||
|
assertEquals(["A?","B!","c","D!"], r)
|
||||||
|
```
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- Map splat keys must be strings; otherwise, a clean error is thrown.
|
||||||
|
- Named splats cannot duplicate parameters already assigned (by positional or named arguments).
|
||||||
|
- Named splats must follow all positional arguments and positional splats.
|
||||||
|
- Ellipsis parameters (variadic) remain positional-only and cannot be assigned by name.
|
||||||
|
|
||||||
|
## Trailing-lambda rule interaction
|
||||||
|
|
||||||
|
If a call is immediately followed by a block `{ ... }`, it is treated as an extra last argument and bound to the last parameter. However, if the last parameter is already assigned by a named argument or a named splat, using a trailing block is an error:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun f(x, onDone) { onDone(x) }
|
||||||
|
f(x: 1) { 42 } // ERROR
|
||||||
|
f(1) { 42 } // OK
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
[tutorial]: tutorial.md
|
[tutorial]: tutorial.md
|
||||||
|
|||||||
82
docs/proposals/map_literal.md
Normal file
82
docs/proposals/map_literal.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
Note for AI. please ignore this file until this remark is removed.
|
||||||
|
|
||||||
|
# Map literals proposal
|
||||||
|
|
||||||
|
Note to the AI: please ignore this file until this note is removed.
|
||||||
|
|
||||||
|
Implement javascript-like literals for maps. It uses the similar syntax already adopted for named arguments in function calls so it should be organic and familiar, as the purpose is similar.
|
||||||
|
|
||||||
|
String literal keys
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
val m = { a: "foo", b: "bar" }
|
||||||
|
assertEqual(m.a, "foo")
|
||||||
|
assertEqual(m.b, "bar")
|
||||||
|
```
|
||||||
|
|
||||||
|
The map literal is a map with keys that are string literals only. This is important. In a relatively rare case when keys are calculated, or extravagant but still possible case when keys are of different types, literal could be combined with "=>":
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
val k1 = "bar"
|
||||||
|
val m = { "foo": 123 } + k1 => "buzz"
|
||||||
|
// this is same as Map("foo" => 123) + Map("bar" => k2) but can be optimized by compiler
|
||||||
|
assertEqual(m["foo"], 123)
|
||||||
|
assertEqual(m["bar"], "buzz")
|
||||||
|
```
|
||||||
|
|
||||||
|
The lambda syntax is different, it can start with the `map_lteral_start` above, it should produce compile time error, so we can add map literals of this sort.
|
||||||
|
|
||||||
|
Also, we will allow splats in map literals:
|
||||||
|
|
||||||
|
```
|
||||||
|
val m = { foo: "bar", ...{bar: "buzz"} }
|
||||||
|
assertEquals("bar",m["foo"])
|
||||||
|
assertEquals("buzz", m["bar"])
|
||||||
|
```
|
||||||
|
|
||||||
|
When the literal argument and splats are used together, they must be evaluated left-to-right with allowed overwriting
|
||||||
|
between named elements and splats, allowing any combination and multiple splats:
|
||||||
|
|
||||||
|
```
|
||||||
|
val m = { foo: "bar", ...{bar: "buzz"}, ...{foo: "foobar"}, bar: "bar" }
|
||||||
|
assertEquals("foobar",m["foo"])
|
||||||
|
assertEquals("bar", m["bar"])
|
||||||
|
```
|
||||||
|
|
||||||
|
Still we disallow duplicating _string literals_:
|
||||||
|
|
||||||
|
```
|
||||||
|
// this is an compile-time exception:
|
||||||
|
{ foo: 1, bar: 2, foo: 3 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Special syntax allows to insert key-value pair from the variable which name should be the key, and content is value:
|
||||||
|
|
||||||
|
```
|
||||||
|
val foo = "bar"
|
||||||
|
val bar = "buzz"
|
||||||
|
assertEquals( {foo: "bar", bar: "buzz"}, { *foo, *bar } )
|
||||||
|
```
|
||||||
|
|
||||||
|
Question to the AI: maybe better syntax than asterisk for that case?
|
||||||
|
|
||||||
|
So, summarizing, overwriting/duplication rules are:
|
||||||
|
|
||||||
|
- string literals can't duplicate
|
||||||
|
- splats add or update content, effectively overwrite preceding content,
|
||||||
|
- string literals overwrite content received from preceding splats (as no duplication string literal keys allowed)
|
||||||
|
- the priority and order is left-to-right, rightmost wins.
|
||||||
|
- var inclusion is treated as form of the literal
|
||||||
|
|
||||||
|
This approach resolves the ambiguity from lambda syntax, as
|
||||||
|
|
||||||
|
```ebnf
|
||||||
|
ws = zero or more whitespace characters including newline
|
||||||
|
map_literal start = "{", ws, (s1 | s2 | s3)
|
||||||
|
s1 = string_literal, ws, ":", ws, expression
|
||||||
|
s2 = "...", string_literal
|
||||||
|
s3 = "*", string_literal
|
||||||
|
```
|
||||||
|
|
||||||
|
as we can see, `map_literal_start` is not a valid lambda beginning so it is not create ambiguity.
|
||||||
|
|
||||||
67
docs/proposals/named_args.md
Normal file
67
docs/proposals/named_args.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Named arguments proposal
|
||||||
|
|
||||||
|
Extend function/method calls to allow setting arguments by name using colon syntax at call sites. This is especially convenient with many parameters and default values.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
|
||||||
|
|
||||||
|
assertEquals(test(b: "b"), ["foo", "b", "bazz"])
|
||||||
|
assertEquals(test("a", c: "c"), ["a", "bar", "c"])
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Named arguments are optional. If named arguments are present, their order is not important.
|
||||||
|
- Named arguments must follow positional arguments; positional arguments cannot follow named ones (the only exception is the syntactic trailing block outside parentheses, see below).
|
||||||
|
- A named argument cannot reassign a parameter already set positionally.
|
||||||
|
- If the last parameter is already assigned by a named argument (or named splat), the trailing-lambda rule must NOT apply: a following `{ ... }` after the call is an error.
|
||||||
|
|
||||||
|
Rationale for using `:` instead of `=` in calls: in Lyng, assignment `=` is an expression; using `:` avoids ambiguity and keeps declarations (`name: Type`) distinct from call sites, where casting uses `as` / `as?`.
|
||||||
|
|
||||||
|
Migration note: earlier drafts/examples used `name = value`. The final syntax is `name: value` at call sites.
|
||||||
|
|
||||||
|
## Extended call argument splats: named splats
|
||||||
|
|
||||||
|
With named arguments, splats (`...`) are extended to support maps as named splats. When a splat evaluates to a Map, its entries provide name→value assignments:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
|
||||||
|
|
||||||
|
assertEquals(test("A?", ...Map("d" => "D!", "b" => "B!")), ["A?", "B!", "c", "D!"])
|
||||||
|
```
|
||||||
|
|
||||||
|
Constraints for named splats:
|
||||||
|
|
||||||
|
- Only string keys are allowed in map splats; otherwise, a clean error is thrown.
|
||||||
|
- Named splats cannot reassign parameters already set (positionally or by earlier named arguments/splats).
|
||||||
|
- Named splats follow the same ordering as named arguments: they must appear after all positional arguments and positional splats.
|
||||||
|
|
||||||
|
## Trailing-lambda interaction
|
||||||
|
|
||||||
|
Lyng supports a syntactic trailing block after a call: `f(args) { ... }`. With named args/splats, if the last parameter is already assigned by name, the trailing block must not apply and the call is an error:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun f(x, onDone) { onDone(x) }
|
||||||
|
f(x: 1) { 42 } // ERROR: last parameter already assigned by name
|
||||||
|
f(1) { 42 } // OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors (non-exhaustive)
|
||||||
|
|
||||||
|
- Positional argument after any named argument inside parentheses: error.
|
||||||
|
- Positional splat after any named argument: error.
|
||||||
|
- Duplicate named assignment (directly or via map splats): error.
|
||||||
|
- Unknown parameter name in a named argument/splat: error.
|
||||||
|
- Map splat with non-string keys: error.
|
||||||
|
- Attempt to target the ellipsis parameter by name: error.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Declarations continue to use `:` for types, while call sites use `:` for named arguments and `as` / `as?` for type casts/checks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2
docs/samples/sum.lyng
Normal file → Executable file
2
docs/samples/sum.lyng
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
|
#!/bin/env lyng
|
||||||
/*
|
/*
|
||||||
Calculate the limit of Sum( f(n) )
|
Calculate the limit of Sum( f(n) )
|
||||||
until it reaches asymptotic limit 0.00001% change
|
until it reaches asymptotic limit 0.00001% change
|
||||||
|
|
||||||
return null or found limit
|
return null or found limit
|
||||||
*/
|
*/
|
||||||
fun findSumLimit(f) {
|
fun findSumLimit(f) {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ Files
|
|||||||
- Operators including ranges (`..`, `..<`, `...`), null-safe (`?.`, `?[`, `?(`, `?{`, `?:`, `??`), arrows (`->`, `=>`, `::`), match operators (`=~`, `!~`), bitwise, arithmetic, etc.
|
- Operators including ranges (`..`, `..<`, `...`), null-safe (`?.`, `?[`, `?(`, `?{`, `?:`, `??`), arrows (`->`, `=>`, `::`), match operators (`=~`, `!~`), bitwise, arithmetic, etc.
|
||||||
- Shuttle operator `<=>`
|
- Shuttle operator `<=>`
|
||||||
- Division operator `/` (note: Lyng has no regex literal syntax; `/` is always division)
|
- Division operator `/` (note: Lyng has no regex literal syntax; `/` is always division)
|
||||||
|
- Named arguments at call sites `name: value` (the `name` part is highlighted as `variable.parameter.named.lyng` and the `:` as punctuation). The rule is anchored to `(` or `,` and excludes `::` to avoid conflicts.
|
||||||
|
|
||||||
Install in IntelliJ IDEA (and other JetBrains IDEs)
|
Install in IntelliJ IDEA (and other JetBrains IDEs)
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
@ -56,6 +57,7 @@ Notes and limitations
|
|||||||
---------------------
|
---------------------
|
||||||
- Type highlighting is heuristic (Capitalized identifiers). The IntelliJ plugin will use language semantics and avoid false positives.
|
- Type highlighting is heuristic (Capitalized identifiers). The IntelliJ plugin will use language semantics and avoid false positives.
|
||||||
- If your language adds or changes tokens, please update patterns in `lyng.tmLanguage.json`. The Kotlin sources in `lynglib/src/commonMain/kotlin/net/sergeych/lyng/highlight/` are a good reference for token kinds.
|
- If your language adds or changes tokens, please update patterns in `lyng.tmLanguage.json`. The Kotlin sources in `lynglib/src/commonMain/kotlin/net/sergeych/lyng/highlight/` are a good reference for token kinds.
|
||||||
|
- Labels `name:` at statement level remain supported and are kept distinct from named call arguments by context. The grammar prefers named-argument matching when a `name:` appears right after `(` or `,`.
|
||||||
|
|
||||||
Lyng specifics
|
Lyng specifics
|
||||||
--------------
|
--------------
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "lyng-textmate",
|
"name": "lyng-textmate",
|
||||||
"displayName": "Lyng",
|
"displayName": "Lyng",
|
||||||
"description": "TextMate grammar for the Lyng language (for JetBrains IDEs via TextMate Bundles and VS Code).",
|
"description": "TextMate grammar for the Lyng language (for JetBrains IDEs via TextMate Bundles and VS Code).",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"publisher": "lyng",
|
"publisher": "lyng",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": { "vscode": "^1.0.0" },
|
"engines": { "vscode": "^1.0.0" },
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
{ "include": "#keywords" },
|
{ "include": "#keywords" },
|
||||||
{ "include": "#constants" },
|
{ "include": "#constants" },
|
||||||
{ "include": "#types" },
|
{ "include": "#types" },
|
||||||
|
{ "include": "#namedArgs" },
|
||||||
{ "include": "#annotations" },
|
{ "include": "#annotations" },
|
||||||
{ "include": "#labels" },
|
{ "include": "#labels" },
|
||||||
{ "include": "#directives" },
|
{ "include": "#directives" },
|
||||||
@ -41,6 +42,18 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"annotations": { "patterns": [ { "name": "entity.name.label.at.lyng", "match": "@[\\p{L}_][\\p{L}\\p{N}_]*:" }, { "name": "storage.modifier.annotation.lyng", "match": "@[\\p{L}_][\\p{L}\\p{N}_]*" } ] },
|
"annotations": { "patterns": [ { "name": "entity.name.label.at.lyng", "match": "@[\\p{L}_][\\p{L}\\p{N}_]*:" }, { "name": "storage.modifier.annotation.lyng", "match": "@[\\p{L}_][\\p{L}\\p{N}_]*" } ] },
|
||||||
|
"namedArgs": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.argument.named.lyng",
|
||||||
|
"match": "(?:(?<=\\()|(?<=,))\\s*([\\p{L}_][\\p{L}\\p{N}_]*)\\s*(:)(?!:)",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "variable.parameter.named.lyng" },
|
||||||
|
"2": { "name": "punctuation.separator.colon.lyng" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"labels": { "patterns": [ { "name": "entity.name.label.lyng", "match": "[\\p{L}_][\\p{L}\\p{N}_]*:" } ] },
|
"labels": { "patterns": [ { "name": "entity.name.label.lyng", "match": "[\\p{L}_][\\p{L}\\p{N}_]*:" } ] },
|
||||||
"directives": { "patterns": [ { "name": "meta.directive.lyng", "match": "^\\s*#[_A-Za-z][_A-Za-z0-9]*" } ] },
|
"directives": { "patterns": [ { "name": "meta.directive.lyng", "match": "^\\s*#[_A-Za-z][_A-Za-z0-9]*" } ] },
|
||||||
"declarations": { "patterns": [ { "name": "meta.function.declaration.lyng", "match": "\\b(?:fun|fn)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.function.lyng" } } }, { "name": "meta.type.declaration.lyng", "match": "\\b(?:class|enum)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.type.lyng" } } }, { "name": "meta.variable.declaration.lyng", "match": "\\b(?:val|var)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "variable.other.declaration.lyng" } } } ] },
|
"declarations": { "patterns": [ { "name": "meta.function.declaration.lyng", "match": "\\b(?:fun|fn)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.function.lyng" } } }, { "name": "meta.type.declaration.lyng", "match": "\\b(?:class|enum)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.type.lyng" } } }, { "name": "meta.variable.declaration.lyng", "match": "\\b(?:val|var)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "variable.other.declaration.lyng" } } } ] },
|
||||||
|
|||||||
@ -31,6 +31,15 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
// Suppress Beta warning for expect/actual classes across all targets in this module
|
||||||
|
targets.configureEach {
|
||||||
|
compilations.configureEach {
|
||||||
|
compilerOptions.configure {
|
||||||
|
freeCompilerArgs.add("-Xexpect-actual-classes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jvm {
|
jvm {
|
||||||
binaries {
|
binaries {
|
||||||
executable {
|
executable {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.0.0-SNAPSHOT"
|
version = "1.0.1-SNAPSHOT"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
@ -72,6 +72,15 @@ kotlin {
|
|||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Suppress Beta warning for expect/actual classes across all targets
|
||||||
|
targets.configureEach {
|
||||||
|
compilations.configureEach {
|
||||||
|
compilerOptions.configure {
|
||||||
|
freeCompilerArgs.add("-Xexpect-actual-classes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
@ -141,6 +150,40 @@ tasks.withType<org.gradle.api.tasks.testing.Test> {
|
|||||||
showStandardStreams = true
|
showStandardStreams = true
|
||||||
}
|
}
|
||||||
maxParallelForks = 1
|
maxParallelForks = 1
|
||||||
|
|
||||||
|
// Benchmarks toggle: disabled by default, enable when optimizing locally.
|
||||||
|
// Enable via any of the following:
|
||||||
|
// - Gradle property: ./gradlew :lynglib:jvmTest -Pbenchmarks=true
|
||||||
|
// - JVM system prop: ./gradlew :lynglib:jvmTest -Dbenchmarks=true
|
||||||
|
// - Environment var: BENCHMARKS=true ./gradlew :lynglib:jvmTest
|
||||||
|
val benchmarksEnabled: Boolean = run {
|
||||||
|
val p = (project.findProperty("benchmarks") as String?)?.toBooleanStrictOrNull()
|
||||||
|
val s = System.getProperty("benchmarks")?.lowercase()?.let { it == "true" || it == "1" || it == "yes" }
|
||||||
|
val e = System.getenv("BENCHMARKS")?.lowercase()?.let { it == "true" || it == "1" || it == "yes" }
|
||||||
|
p ?: s ?: e ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the flag visible inside tests if they want to branch on it
|
||||||
|
systemProperty("LYNG_BENCHMARKS", benchmarksEnabled.toString())
|
||||||
|
|
||||||
|
if (!benchmarksEnabled) {
|
||||||
|
// Exclude all JVM tests whose class name ends with or contains BenchmarkTest
|
||||||
|
// This keeps CI fast and avoids noisy timing logs by default.
|
||||||
|
filter {
|
||||||
|
excludeTestsMatching("*BenchmarkTest")
|
||||||
|
// Also guard against alternative naming
|
||||||
|
excludeTestsMatching("*Bench*Test")
|
||||||
|
// Exclude A/B performance tests unless explicitly enabled
|
||||||
|
excludeTestsMatching("*ABTest")
|
||||||
|
// Exclude stress/perf soak tests
|
||||||
|
excludeTestsMatching("*Stress*Test")
|
||||||
|
// Exclude allocation profiling tests by default
|
||||||
|
excludeTestsMatching("*AllocationProfileTest")
|
||||||
|
}
|
||||||
|
logger.lifecycle("[tests] Benchmarks are DISABLED. To enable: -Pbenchmarks=true or -Dbenchmarks=true or set BENCHMARKS=true")
|
||||||
|
} else {
|
||||||
|
logger.lifecycle("[tests] Benchmarks are ENABLED: *BenchmarkTest will run")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//mavenPublishing {
|
//mavenPublishing {
|
||||||
|
|||||||
@ -57,72 +57,137 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
recordType = ObjRecord.Type.Argument)
|
recordType = ObjRecord.Type.Argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
// will be used with last lambda arg fix
|
// Prepare positional args and parameter count, handle tail-block binding
|
||||||
val callArgs: List<Obj>
|
val callArgs: List<Obj>
|
||||||
val paramsSize: Int
|
val paramsSize: Int
|
||||||
|
|
||||||
if (arguments.tailBlockMode) {
|
if (arguments.tailBlockMode) {
|
||||||
|
// If last parameter is already assigned by a named argument, it's an error
|
||||||
|
val lastParam = params.last()
|
||||||
|
if (arguments.named.containsKey(lastParam.name))
|
||||||
|
scope.raiseIllegalArgument("trailing block cannot be used when the last parameter is already assigned by a named argument")
|
||||||
paramsSize = params.size - 1
|
paramsSize = params.size - 1
|
||||||
assign(params.last(), arguments.list.last())
|
assign(lastParam, arguments.list.last())
|
||||||
callArgs = arguments.list.dropLast(1)
|
callArgs = arguments.list.dropLast(1)
|
||||||
} else {
|
} else {
|
||||||
paramsSize = params.size
|
paramsSize = params.size
|
||||||
callArgs = arguments.list
|
callArgs = arguments.list
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processHead(index: Int): Int {
|
// Compute which parameter indexes are inevitably covered by positional arguments
|
||||||
|
// based on the number of supplied positionals, defaults and ellipsis placement.
|
||||||
|
val coveredByPositional = BooleanArray(paramsSize)
|
||||||
|
run {
|
||||||
|
// Count required (non-default, non-ellipsis) params in head and in tail
|
||||||
|
var headRequired = 0
|
||||||
|
var tailRequired = 0
|
||||||
|
val ellipsisIdx = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
|
if (ellipsisIdx >= 0) {
|
||||||
|
for (i in 0 until ellipsisIdx) if (!params[i].isEllipsis && params[i].defaultValue == null) headRequired++
|
||||||
|
for (i in paramsSize - 1 downTo ellipsisIdx + 1) if (params[i].defaultValue == null) tailRequired++
|
||||||
|
} else {
|
||||||
|
for (i in 0 until paramsSize) if (params[i].defaultValue == null) headRequired++
|
||||||
|
}
|
||||||
|
val P = callArgs.size
|
||||||
|
if (ellipsisIdx < 0) {
|
||||||
|
// No ellipsis: all positionals go to head until exhausted
|
||||||
|
val k = minOf(P, paramsSize)
|
||||||
|
for (i in 0 until k) coveredByPositional[i] = true
|
||||||
|
} else {
|
||||||
|
// With ellipsis: head takes min(P, headRequired) first
|
||||||
|
val headTake = minOf(P, headRequired)
|
||||||
|
for (i in 0 until headTake) coveredByPositional[i] = true
|
||||||
|
val remaining = P - headTake
|
||||||
|
// tail takes min(remaining, tailRequired) from the end
|
||||||
|
val tailTake = minOf(remaining, tailRequired)
|
||||||
|
var j = paramsSize - 1
|
||||||
|
var taken = 0
|
||||||
|
while (j > ellipsisIdx && taken < tailTake) {
|
||||||
|
coveredByPositional[j] = true
|
||||||
|
j--
|
||||||
|
taken++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare arrays for named assignments
|
||||||
|
val assignedByName = BooleanArray(paramsSize)
|
||||||
|
val namedValues = arrayOfNulls<Obj>(paramsSize)
|
||||||
|
if (arguments.named.isNotEmpty()) {
|
||||||
|
for ((k, v) in arguments.named) {
|
||||||
|
val idx = params.subList(0, paramsSize).indexOfFirst { it.name == k }
|
||||||
|
if (idx < 0) scope.raiseIllegalArgument("unknown parameter '$k'")
|
||||||
|
if (params[idx].isEllipsis) scope.raiseIllegalArgument("ellipsis (variadic) parameter cannot be assigned by name: '$k'")
|
||||||
|
if (coveredByPositional[idx]) scope.raiseIllegalArgument("argument '$k' is already set by positional argument")
|
||||||
|
if (assignedByName[idx]) scope.raiseIllegalArgument("argument '$k' is already set")
|
||||||
|
assignedByName[idx] = true
|
||||||
|
namedValues[idx] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: assign head part, consuming from headPos; stop at ellipsis
|
||||||
|
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
|
||||||
var i = index
|
var i = index
|
||||||
while (i != paramsSize) {
|
var hp = headPos
|
||||||
|
while (i < paramsSize) {
|
||||||
val a = params[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
if (assignedByName[i]) {
|
||||||
i < callArgs.size -> callArgs[i]
|
assign(a, namedValues[i]!!)
|
||||||
a.defaultValue != null -> a.defaultValue.execute(scope)
|
} else {
|
||||||
else -> {
|
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||||
// println("callArgs: ${callArgs.joinToString()}")
|
else a.defaultValue?.execute(scope)
|
||||||
// println("tailBlockMode: ${arguments.tailBlockMode}")
|
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
scope.raiseIllegalArgument("too few arguments for the call")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return i
|
return i to hp
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processTail(index: Int): Int {
|
// Helper: assign tail part from the end, consuming from tailPos; stop before ellipsis index
|
||||||
|
// Do not consume elements below headPosBound to avoid overlap with head consumption
|
||||||
|
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
|
||||||
var i = paramsSize - 1
|
var i = paramsSize - 1
|
||||||
var j = callArgs.size - 1
|
var tp = tailStart
|
||||||
while (i > index) {
|
while (i > startExclusive) {
|
||||||
val a = params[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
if (i < assignedByName.size && assignedByName[i]) {
|
||||||
j >= index -> {
|
assign(a, namedValues[i]!!)
|
||||||
callArgs[j--]
|
} else {
|
||||||
}
|
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||||
|
else a.defaultValue?.execute(scope)
|
||||||
a.defaultValue != null -> a.defaultValue.execute(scope)
|
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
else -> scope.raiseIllegalArgument("too few arguments for the call")
|
|
||||||
}
|
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
|
}
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
return j
|
return tp
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
|
||||||
val a = params[index]
|
val a = params[index]
|
||||||
val l = if (index > toFromIndex) ObjList()
|
val from = headPos
|
||||||
else ObjList(callArgs.subList(index, toFromIndex + 1).toMutableList())
|
val to = tailPos
|
||||||
|
val l = if (from > to) ObjList()
|
||||||
|
else ObjList(callArgs.subList(from, to + 1).toMutableList())
|
||||||
assign(a, l)
|
assign(a, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
val leftIndex = processHead(0)
|
// Locate ellipsis index within considered parameters
|
||||||
if (leftIndex < paramsSize) {
|
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
val end = processTail(leftIndex)
|
|
||||||
processEllipsis(leftIndex, end)
|
if (ellipsisIndex >= 0) {
|
||||||
|
// Assign head first to know how many positionals are consumed from the start
|
||||||
|
val (afterHead, headConsumedTo) = processHead(0, 0)
|
||||||
|
// Then assign tail consuming from the end down to headConsumedTo boundary
|
||||||
|
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
|
||||||
|
// Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
|
||||||
|
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
|
||||||
} else {
|
} else {
|
||||||
if (leftIndex < callArgs.size)
|
// No ellipsis: assign head only; any leftover positionals → error
|
||||||
|
val (_, headConsumedTo) = processHead(0, 0)
|
||||||
|
if (headConsumedTo != callArgs.size)
|
||||||
scope.raiseIllegalArgument("too many arguments for the call")
|
scope.raiseIllegalArgument("too many arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,24 +17,27 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.obj.ObjIterable
|
|
||||||
import net.sergeych.lyng.obj.ObjList
|
|
||||||
|
|
||||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
data class ParsedArgument(
|
||||||
|
val value: Statement,
|
||||||
|
val pos: Pos,
|
||||||
|
val isSplat: Boolean = false,
|
||||||
|
val name: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
||||||
// Small-arity fast path (no splats) to reduce allocations
|
// Detect if we can use the fast path: no splats and no named args
|
||||||
if (PerfFlags.ARG_BUILDER) {
|
if (PerfFlags.ARG_BUILDER) {
|
||||||
val limit = if (PerfFlags.ARG_SMALL_ARITY_12) 12 else 8
|
val limit = if (PerfFlags.ARG_SMALL_ARITY_12) 12 else 8
|
||||||
var hasSplat = false
|
var hasSplatOrNamed = false
|
||||||
var count = 0
|
var count = 0
|
||||||
for (pa in this) {
|
for (pa in this) {
|
||||||
if (pa.isSplat) { hasSplat = true; break }
|
if (pa.isSplat || pa.name != null) { hasSplatOrNamed = true; break }
|
||||||
count++
|
count++
|
||||||
if (count > limit) break
|
if (count > limit) break
|
||||||
}
|
}
|
||||||
if (!hasSplat && count == this.size) {
|
if (!hasSplatOrNamed && count == this.size) {
|
||||||
val quick = when (count) {
|
val quick = when (count) {
|
||||||
0 -> Arguments.EMPTY
|
0 -> Arguments.EMPTY
|
||||||
1 -> Arguments(listOf(this.elementAt(0).value.execute(scope)), tailBlockMode)
|
1 -> Arguments(listOf(this.elementAt(0).value.execute(scope)), tailBlockMode)
|
||||||
@ -153,90 +156,86 @@ import net.sergeych.lyng.obj.ObjList
|
|||||||
if (quick != null) return quick
|
if (quick != null) return quick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Single-splat fast path: if there is exactly one splat argument that evaluates to ObjList,
|
|
||||||
// avoid builder and copies by returning its list directly.
|
|
||||||
if (PerfFlags.ARG_BUILDER) {
|
|
||||||
if (this.size == 1) {
|
|
||||||
val only = this.first()
|
|
||||||
if (only.isSplat) {
|
|
||||||
val v = only.value.execute(scope)
|
|
||||||
if (v is ObjList) {
|
|
||||||
return Arguments(v.list, tailBlockMode)
|
|
||||||
} else if (v.isInstanceOf(ObjIterable)) {
|
|
||||||
// Convert iterable to list once and return directly
|
|
||||||
val i = (v.invokeInstanceMethod(scope, "toList") as ObjList).list
|
|
||||||
return Arguments(i, tailBlockMode)
|
|
||||||
} else {
|
|
||||||
scope.raiseClassCastError("expected list of objects for splat argument")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// General path with builder or simple list fallback
|
// General path: build positional list and named map, enforcing ordering rules
|
||||||
if (PerfFlags.ARG_BUILDER) {
|
val positional: MutableList<Obj> = mutableListOf()
|
||||||
val b = ArgBuilderProvider.acquire()
|
var named: MutableMap<String, Obj>? = null
|
||||||
try {
|
var namedSeen = false
|
||||||
b.reset(this.size)
|
for ((idx, x) in this.withIndex()) {
|
||||||
for (x in this) {
|
if (x.name != null) {
|
||||||
|
// Named argument
|
||||||
|
if (named == null) named = linkedMapOf()
|
||||||
|
if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set")
|
||||||
|
val v = x.value.execute(scope)
|
||||||
|
named[x.name] = v
|
||||||
|
namedSeen = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
val value = x.value.execute(scope)
|
val value = x.value.execute(scope)
|
||||||
if (x.isSplat) {
|
if (x.isSplat) {
|
||||||
when {
|
when {
|
||||||
|
// IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats
|
||||||
|
// are treated as named splats, not as positional iteration over entries
|
||||||
|
value is ObjMap -> {
|
||||||
|
if (named == null) named = linkedMapOf()
|
||||||
|
for ((k, v) in value.map) {
|
||||||
|
if (k !is ObjString) scope.raiseIllegalArgument("named splat expects a Map with string keys")
|
||||||
|
val key = k.value
|
||||||
|
if (named.containsKey(key)) scope.raiseIllegalArgument("argument '$key' is already set")
|
||||||
|
named[key] = v
|
||||||
|
}
|
||||||
|
namedSeen = true
|
||||||
|
}
|
||||||
value is ObjList -> {
|
value is ObjList -> {
|
||||||
b.addAll(value.list)
|
if (namedSeen) {
|
||||||
|
// allow only if this is the very last positional which will be the trailing block; but
|
||||||
|
// splat can never be a trailing block, so it's always illegal here
|
||||||
|
scope.raiseIllegalArgument("positional splat cannot follow named arguments")
|
||||||
|
}
|
||||||
|
positional.addAll(value.list)
|
||||||
}
|
}
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
value.isInstanceOf(ObjIterable) -> {
|
||||||
|
if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments")
|
||||||
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
b.addAll(i)
|
positional.addAll(i)
|
||||||
}
|
}
|
||||||
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.add(value)
|
if (namedSeen) {
|
||||||
|
// Allow exactly one positional after named only when it is the very last argument overall
|
||||||
|
// and tailBlockMode is true (syntactic trailing block). Otherwise, forbid it.
|
||||||
|
val isLast = idx == this.size - 1
|
||||||
|
if (!(isLast && tailBlockMode))
|
||||||
|
scope.raiseIllegalArgument("positional argument cannot follow named arguments")
|
||||||
|
}
|
||||||
|
positional.add(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.build(tailBlockMode)
|
val namedFinal = named ?: emptyMap()
|
||||||
} finally {
|
return Arguments(positional, tailBlockMode, namedFinal)
|
||||||
b.release()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val list: MutableList<Obj> = mutableListOf()
|
|
||||||
for (x in this) {
|
|
||||||
val value = x.value.execute(scope)
|
|
||||||
if (x.isSplat) {
|
|
||||||
when {
|
|
||||||
value is ObjList -> list.addAll(value.list)
|
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
|
||||||
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
|
||||||
list.addAll(i)
|
|
||||||
}
|
|
||||||
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
list.add(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Arguments(list, tailBlockMode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) : List<Obj> by list {
|
data class Arguments(
|
||||||
|
val list: List<Obj>,
|
||||||
|
val tailBlockMode: Boolean = false,
|
||||||
|
val named: Map<String, Obj> = emptyMap(),
|
||||||
|
) : List<Obj> by list {
|
||||||
|
|
||||||
constructor(vararg values: Obj) : this(values.toList())
|
constructor(vararg values: Obj) : this(values.toList())
|
||||||
|
|
||||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||||
val v = list.first()
|
|
||||||
// Tiny micro-alloc win: avoid byValueCopy for immutable singletons
|
// Tiny micro-alloc win: avoid byValueCopy for immutable singletons
|
||||||
return when (v) {
|
return when (val v = list.first()) {
|
||||||
net.sergeych.lyng.obj.ObjNull,
|
ObjNull,
|
||||||
net.sergeych.lyng.obj.ObjTrue,
|
ObjTrue,
|
||||||
net.sergeych.lyng.obj.ObjFalse,
|
ObjFalse,
|
||||||
// Immutable scalars: safe to return directly
|
// Immutable scalars: safe to return directly
|
||||||
is net.sergeych.lyng.obj.ObjInt,
|
is ObjInt,
|
||||||
is net.sergeych.lyng.obj.ObjReal,
|
is ObjReal,
|
||||||
is net.sergeych.lyng.obj.ObjChar,
|
is ObjChar,
|
||||||
is net.sergeych.lyng.obj.ObjString -> v
|
is ObjString -> v
|
||||||
else -> v.byValueCopy()
|
else -> v.byValueCopy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,8 +35,8 @@ class Compiler(
|
|||||||
|
|
||||||
// Stack of parameter-to-slot plans for current function being parsed (by declaration index)
|
// Stack of parameter-to-slot plans for current function being parsed (by declaration index)
|
||||||
private val paramSlotPlanStack = mutableListOf<Map<String, Int>>()
|
private val paramSlotPlanStack = mutableListOf<Map<String, Int>>()
|
||||||
private val currentParamSlotPlan: Map<String, Int>?
|
// private val currentParamSlotPlan: Map<String, Int>?
|
||||||
get() = paramSlotPlanStack.lastOrNull()
|
// get() = paramSlotPlanStack.lastOrNull()
|
||||||
|
|
||||||
// Track identifiers known to be locals/parameters in the current function for fast local emission
|
// Track identifiers known to be locals/parameters in the current function for fast local emission
|
||||||
private val localNamesStack = mutableListOf<MutableSet<String>>()
|
private val localNamesStack = mutableListOf<MutableSet<String>>()
|
||||||
@ -50,7 +50,11 @@ class Compiler(
|
|||||||
|
|
||||||
private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
|
private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
|
||||||
localNamesStack.add(names.toMutableSet())
|
localNamesStack.add(names.toMutableSet())
|
||||||
return try { block() } finally { localNamesStack.removeLast() }
|
return try {
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
localNamesStack.removeLast()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun declareLocalName(name: String) {
|
private fun declareLocalName(name: String) {
|
||||||
@ -86,6 +90,7 @@ class Compiler(
|
|||||||
if (t.startsWith("*")) t.removePrefix("*").trimStart() else line
|
if (t.startsWith("*")) t.removePrefix("*").trimStart() else line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> raw
|
else -> raw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,6 +163,7 @@ class Compiler(
|
|||||||
// A standalone newline not immediately following a comment resets doc buffer
|
// A standalone newline not immediately following a comment resets doc buffer
|
||||||
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
cc.next()
|
cc.next()
|
||||||
@ -191,12 +197,15 @@ class Compiler(
|
|||||||
val start = Pos(pos.source, pos.line, col)
|
val start = Pos(pos.source, pos.line, col)
|
||||||
val end = Pos(pos.source, pos.line, col + p.length)
|
val end = Pos(pos.source, pos.line, col + p.length)
|
||||||
col += p.length + 1 // account for following '.' between segments
|
col += p.length + 1 // account for following '.' between segments
|
||||||
net.sergeych.lyng.miniast.MiniImport.Segment(p, net.sergeych.lyng.miniast.MiniRange(start, end))
|
MiniImport.Segment(
|
||||||
|
p,
|
||||||
|
MiniRange(start, end)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val lastEnd = segs.last().range.end
|
val lastEnd = segs.last().range.end
|
||||||
miniSink?.onImport(
|
miniSink?.onImport(
|
||||||
net.sergeych.lyng.miniast.MiniImport(
|
MiniImport(
|
||||||
net.sergeych.lyng.miniast.MiniRange(pos, lastEnd),
|
MiniRange(pos, lastEnd),
|
||||||
segs
|
segs
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -241,7 +250,10 @@ class Compiler(
|
|||||||
Script(start, statements)
|
Script(start, statements)
|
||||||
}.also {
|
}.also {
|
||||||
// Best-effort script end notification (use current position)
|
// Best-effort script end notification (use current position)
|
||||||
miniSink?.onScriptEnd(cc.currentPos(), net.sergeych.lyng.miniast.MiniScript(MiniRange(start, cc.currentPos())))
|
miniSink?.onScriptEnd(
|
||||||
|
cc.currentPos(),
|
||||||
|
MiniScript(MiniRange(start, cc.currentPos()))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +339,6 @@ class Compiler(
|
|||||||
var lvalue: ObjRef? = parseExpressionLevel(level + 1) ?: return null
|
var lvalue: ObjRef? = parseExpressionLevel(level + 1) ?: return null
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
val opToken = cc.next()
|
val opToken = cc.next()
|
||||||
val op = byLevel[level][opToken.type]
|
val op = byLevel[level][opToken.type]
|
||||||
if (op == null) {
|
if (op == null) {
|
||||||
@ -552,11 +563,14 @@ class Compiler(
|
|||||||
|
|
||||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||||
operand = operand?.let { left ->
|
operand = operand?.let { left ->
|
||||||
cc.previous()
|
// Trailing block-argument function call: the leading '{' is already consumed,
|
||||||
|
// and the lambda must be parsed as a single argument BEFORE any following
|
||||||
|
// selectors like ".foo" are considered. Do NOT rewind here, otherwise
|
||||||
|
// the expression parser may capture ".foo" as part of the lambda expression.
|
||||||
parseFunctionCall(
|
parseFunctionCall(
|
||||||
left,
|
left,
|
||||||
blockArgument = true,
|
blockArgument = true,
|
||||||
t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
isOptional = t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
||||||
)
|
)
|
||||||
} ?: parseLambdaExpression()
|
} ?: parseLambdaExpression()
|
||||||
}
|
}
|
||||||
@ -778,7 +792,11 @@ class Compiler(
|
|||||||
val typeStart = cc.currentPos()
|
val typeStart = cc.currentPos()
|
||||||
var lastEnd = typeStart
|
var lastEnd = typeStart
|
||||||
while (true) {
|
while (true) {
|
||||||
val idTok = if (first) cc.requireToken(Token.Type.ID, "type name or type expression required") else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type")
|
val idTok =
|
||||||
|
if (first) cc.requireToken(Token.Type.ID, "type name or type expression required") else cc.requireToken(
|
||||||
|
Token.Type.ID,
|
||||||
|
"identifier expected after '.' in type"
|
||||||
|
)
|
||||||
first = false
|
first = false
|
||||||
segments += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
|
segments += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
|
||||||
lastEnd = cc.currentPos()
|
lastEnd = cc.currentPos()
|
||||||
@ -796,8 +814,11 @@ class Compiler(
|
|||||||
// Helper to build MiniTypeRef (base or generic)
|
// Helper to build MiniTypeRef (base or generic)
|
||||||
fun buildBaseRef(rangeEnd: Pos, args: List<MiniTypeRef>?, nullable: Boolean): MiniTypeRef {
|
fun buildBaseRef(rangeEnd: Pos, args: List<MiniTypeRef>?, nullable: Boolean): MiniTypeRef {
|
||||||
val base = MiniTypeName(MiniRange(typeStart, rangeEnd), segments.toList(), nullable = false)
|
val base = MiniTypeName(MiniRange(typeStart, rangeEnd), segments.toList(), nullable = false)
|
||||||
return if (args == null || args.isEmpty()) base.copy(range = MiniRange(typeStart, rangeEnd), nullable = nullable)
|
return if (args == null || args.isEmpty()) base.copy(
|
||||||
else net.sergeych.lyng.miniast.MiniGenericType(MiniRange(typeStart, rangeEnd), base, args, nullable)
|
range = MiniRange(typeStart, rangeEnd),
|
||||||
|
nullable = nullable
|
||||||
|
)
|
||||||
|
else MiniGenericType(MiniRange(typeStart, rangeEnd), base, args, nullable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional generic arguments: '<' Type (',' Type)* '>' — single-level only (no nested generics for now)
|
// Optional generic arguments: '<' Type (',' Type)* '>' — single-level only (no nested generics for now)
|
||||||
@ -811,12 +832,17 @@ class Compiler(
|
|||||||
var argFirst = true
|
var argFirst = true
|
||||||
val argStart = cc.currentPos()
|
val argStart = cc.currentPos()
|
||||||
while (true) {
|
while (true) {
|
||||||
val idTok = if (argFirst) cc.requireToken(Token.Type.ID, "type argument name expected") else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type argument")
|
val idTok = if (argFirst) cc.requireToken(
|
||||||
|
Token.Type.ID,
|
||||||
|
"type argument name expected"
|
||||||
|
) else cc.requireToken(Token.Type.ID, "identifier expected after '.' in type argument")
|
||||||
argFirst = false
|
argFirst = false
|
||||||
argSegs += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
|
argSegs += MiniTypeName.Segment(idTok.value, MiniRange(idTok.pos, idTok.pos))
|
||||||
val p = cc.savePos()
|
val p = cc.savePos()
|
||||||
val tt = cc.next()
|
val tt = cc.next()
|
||||||
if (tt.type == Token.Type.DOT) continue else { cc.restorePos(p); break }
|
if (tt.type == Token.Type.DOT) continue else {
|
||||||
|
cc.restorePos(p); break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val argNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
|
val argNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
|
||||||
val argEnd = cc.currentPos()
|
val argEnd = cc.currentPos()
|
||||||
@ -825,7 +851,9 @@ class Compiler(
|
|||||||
|
|
||||||
val sep = cc.next()
|
val sep = cc.next()
|
||||||
when (sep.type) {
|
when (sep.type) {
|
||||||
Token.Type.COMMA -> { /* continue */ }
|
Token.Type.COMMA -> { /* continue */
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.GT -> break
|
Token.Type.GT -> break
|
||||||
else -> sep.raiseSyntax("expected ',' or '>' in generic arguments")
|
else -> sep.raiseSyntax("expected ',' or '>' in generic arguments")
|
||||||
}
|
}
|
||||||
@ -853,6 +881,21 @@ class Compiler(
|
|||||||
private suspend fun parseArgs(): Pair<List<ParsedArgument>, Boolean> {
|
private suspend fun parseArgs(): Pair<List<ParsedArgument>, Boolean> {
|
||||||
|
|
||||||
val args = mutableListOf<ParsedArgument>()
|
val args = mutableListOf<ParsedArgument>()
|
||||||
|
suspend fun tryParseNamedArg(): ParsedArgument? {
|
||||||
|
val save = cc.savePos()
|
||||||
|
val t1 = cc.next()
|
||||||
|
if (t1.type == Token.Type.ID) {
|
||||||
|
val t2 = cc.next()
|
||||||
|
if (t2.type == Token.Type.COLON) {
|
||||||
|
// name: expr
|
||||||
|
val name = t1.value
|
||||||
|
val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'")
|
||||||
|
return ParsedArgument(rhs, t1.pos, isSplat = false, name = name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc.restorePos(save)
|
||||||
|
return null
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
when (t.type) {
|
when (t.type) {
|
||||||
@ -867,10 +910,14 @@ class Compiler(
|
|||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
|
val named = tryParseNamedArg()
|
||||||
|
if (named != null) {
|
||||||
|
args += named
|
||||||
|
} else {
|
||||||
parseExpression()?.let { args += ParsedArgument(it, t.pos) }
|
parseExpression()?.let { args += ParsedArgument(it, t.pos) }
|
||||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||||
if (cc.current().type == Token.Type.COLON)
|
// In call-site arguments, ':' is reserved for named args. Do not parse type declarations here.
|
||||||
parseTypeDeclaration()
|
}
|
||||||
// Here should be a valid termination:
|
// Here should be a valid termination:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -901,6 +948,20 @@ class Compiler(
|
|||||||
*/
|
*/
|
||||||
private suspend fun parseArgsNoTailBlock(): List<ParsedArgument> {
|
private suspend fun parseArgsNoTailBlock(): List<ParsedArgument> {
|
||||||
val args = mutableListOf<ParsedArgument>()
|
val args = mutableListOf<ParsedArgument>()
|
||||||
|
suspend fun tryParseNamedArg(): ParsedArgument? {
|
||||||
|
val save = cc.savePos()
|
||||||
|
val t1 = cc.next()
|
||||||
|
if (t1.type == Token.Type.ID) {
|
||||||
|
val t2 = cc.next()
|
||||||
|
if (t2.type == Token.Type.COLON) {
|
||||||
|
val name = t1.value
|
||||||
|
val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'")
|
||||||
|
return ParsedArgument(rhs, t1.pos, isSplat = false, name = name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc.restorePos(save)
|
||||||
|
return null
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
when (t.type) {
|
when (t.type) {
|
||||||
@ -915,10 +976,14 @@ class Compiler(
|
|||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
|
val named = tryParseNamedArg()
|
||||||
|
if (named != null) {
|
||||||
|
args += named
|
||||||
|
} else {
|
||||||
parseExpression()?.let { args += ParsedArgument(it, t.pos) }
|
parseExpression()?.let { args += ParsedArgument(it, t.pos) }
|
||||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||||
if (cc.current().type == Token.Type.COLON)
|
// Do not parse type declarations in call args
|
||||||
parseTypeDeclaration()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (t.type != Token.Type.RPAREN)
|
} while (t.type != Token.Type.RPAREN)
|
||||||
@ -934,11 +999,14 @@ class Compiler(
|
|||||||
): ObjRef {
|
): ObjRef {
|
||||||
var detectedBlockArgument = blockArgument
|
var detectedBlockArgument = blockArgument
|
||||||
val args = if (blockArgument) {
|
val args = if (blockArgument) {
|
||||||
val blockArg = ParsedArgument(
|
// Leading '{' has already been consumed by the caller token branch.
|
||||||
parseExpression()
|
// Parse only the lambda expression as the last argument and DO NOT
|
||||||
?: throw ScriptError(cc.currentPos(), "lambda body expected"), cc.currentPos()
|
// allow any subsequent selectors (like ".last()") to be absorbed
|
||||||
)
|
// into the lambda body. This ensures expected order:
|
||||||
listOf(blockArg)
|
// foo { ... }.bar() == (foo { ... }).bar()
|
||||||
|
val callableAccessor = parseLambdaExpression()
|
||||||
|
val argStmt = statement { callableAccessor.get(this).value }
|
||||||
|
listOf(ParsedArgument(argStmt, cc.currentPos()))
|
||||||
} else {
|
} else {
|
||||||
val r = parseArgs()
|
val r = parseArgs()
|
||||||
detectedBlockArgument = r.second
|
detectedBlockArgument = r.second
|
||||||
@ -1058,6 +1126,7 @@ class Compiler(
|
|||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseVarDeclaration(false, Visibility.Public)
|
parseVarDeclaration(false, Visibility.Public)
|
||||||
}
|
}
|
||||||
|
|
||||||
"var" -> {
|
"var" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
@ -1069,6 +1138,7 @@ class Compiler(
|
|||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"fn" -> {
|
"fn" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
@ -1085,11 +1155,24 @@ class Compiler(
|
|||||||
when (k.value) {
|
when (k.value) {
|
||||||
"val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic)
|
"val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic)
|
||||||
"var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic)
|
"var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic)
|
||||||
"fun" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic)
|
"fun" -> parseFunctionDeclaration(
|
||||||
"fn" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic)
|
visibility = Visibility.Private,
|
||||||
|
isOpen = false,
|
||||||
|
isExtern = false,
|
||||||
|
isStatic = isStatic
|
||||||
|
)
|
||||||
|
|
||||||
|
"fn" -> parseFunctionDeclaration(
|
||||||
|
visibility = Visibility.Private,
|
||||||
|
isOpen = false,
|
||||||
|
isExtern = false,
|
||||||
|
isStatic = isStatic
|
||||||
|
)
|
||||||
|
|
||||||
else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}")
|
else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"protected" -> {
|
"protected" -> {
|
||||||
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'")
|
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'")
|
||||||
var isStatic = false
|
var isStatic = false
|
||||||
@ -1100,11 +1183,24 @@ class Compiler(
|
|||||||
when (k.value) {
|
when (k.value) {
|
||||||
"val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic)
|
"val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic)
|
||||||
"var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic)
|
"var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic)
|
||||||
"fun" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic)
|
"fun" -> parseFunctionDeclaration(
|
||||||
"fn" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic)
|
visibility = Visibility.Protected,
|
||||||
|
isOpen = false,
|
||||||
|
isExtern = false,
|
||||||
|
isStatic = isStatic
|
||||||
|
)
|
||||||
|
|
||||||
|
"fn" -> parseFunctionDeclaration(
|
||||||
|
visibility = Visibility.Protected,
|
||||||
|
isOpen = false,
|
||||||
|
isExtern = false,
|
||||||
|
isStatic = isStatic
|
||||||
|
)
|
||||||
|
|
||||||
else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}")
|
else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"while" -> parseWhileStatement()
|
"while" -> parseWhileStatement()
|
||||||
"do" -> parseDoWhileStatement()
|
"do" -> parseDoWhileStatement()
|
||||||
"for" -> parseForStatement()
|
"for" -> parseForStatement()
|
||||||
@ -1116,11 +1212,13 @@ class Compiler(
|
|||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseClassDeclaration()
|
parseClassDeclaration()
|
||||||
}
|
}
|
||||||
|
|
||||||
"enum" -> {
|
"enum" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseEnumDeclaration()
|
parseEnumDeclaration()
|
||||||
}
|
}
|
||||||
|
|
||||||
"try" -> parseTryStatement()
|
"try" -> parseTryStatement()
|
||||||
"throw" -> parseThrowStatement(id.pos)
|
"throw" -> parseThrowStatement(id.pos)
|
||||||
"when" -> parseWhenStatement()
|
"when" -> parseWhenStatement()
|
||||||
@ -1130,9 +1228,10 @@ class Compiler(
|
|||||||
val isExtern = cc.skipId("extern")
|
val isExtern = cc.skipId("extern")
|
||||||
when {
|
when {
|
||||||
cc.matchQualifiers("fun", "private") -> {
|
cc.matchQualifiers("fun", "private") -> {
|
||||||
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc();
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc()
|
||||||
parseFunctionDeclaration(Visibility.Private, isExtern)
|
parseFunctionDeclaration(Visibility.Private, isExtern)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("fun", "private", "static") -> parseFunctionDeclaration(
|
cc.matchQualifiers("fun", "private", "static") -> parseFunctionDeclaration(
|
||||||
Visibility.Private,
|
Visibility.Private,
|
||||||
isExtern,
|
isExtern,
|
||||||
@ -1149,27 +1248,78 @@ class Compiler(
|
|||||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
||||||
|
|
||||||
cc.matchQualifiers("fun") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern) }
|
cc.matchQualifiers("fun") -> {
|
||||||
cc.matchQualifiers("fn") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern) }
|
pendingDeclStart = id.pos; pendingDeclDoc =
|
||||||
|
consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
||||||
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("val", "private", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
cc.matchQualifiers("fn") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc =
|
||||||
|
consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("val", "private", "static") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
false,
|
false,
|
||||||
Visibility.Private,
|
Visibility.Private,
|
||||||
isStatic = true
|
isStatic = true
|
||||||
) }
|
)
|
||||||
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("val", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Public, isStatic = true) }
|
cc.matchQualifiers("val", "static") -> {
|
||||||
cc.matchQualifiers("val", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Private) }
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
cc.matchQualifiers("var", "static") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Public, isStatic = true) }
|
false,
|
||||||
cc.matchQualifiers("var", "static", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
Visibility.Public,
|
||||||
|
isStatic = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("val", "private") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
|
false,
|
||||||
|
Visibility.Private
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("var", "static") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
|
true,
|
||||||
|
Visibility.Public,
|
||||||
|
isStatic = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("var", "static", "private") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
true,
|
true,
|
||||||
Visibility.Private,
|
Visibility.Private,
|
||||||
isStatic = true
|
isStatic = true
|
||||||
) }
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("var", "private") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
|
true,
|
||||||
|
Visibility.Private
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("val", "open") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
|
false,
|
||||||
|
Visibility.Private,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.matchQualifiers("var", "open") -> {
|
||||||
|
pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(
|
||||||
|
true,
|
||||||
|
Visibility.Private,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("var", "private") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Private) }
|
|
||||||
cc.matchQualifiers("val", "open") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(false, Visibility.Private, true) }
|
|
||||||
cc.matchQualifiers("var", "open") -> { pendingDeclStart = id.pos; pendingDeclDoc = consumePendingDoc(); parseVarDeclaration(true, Visibility.Private, true) }
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.next()
|
cc.next()
|
||||||
null
|
null
|
||||||
@ -1306,9 +1456,10 @@ class Compiler(
|
|||||||
errorObject.extraData,
|
errorObject.extraData,
|
||||||
errorObject.useStackTrace
|
errorObject.useStackTrace
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throwScope.raiseError("this is not an exception object: $errorObject")
|
else -> throwScope.raiseError("this is not an exception object: $errorObject")
|
||||||
}
|
}
|
||||||
throwScope.raiseError(errorObject as ObjException)
|
throwScope.raiseError(errorObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1473,6 +1624,7 @@ class Compiler(
|
|||||||
|
|
||||||
// Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )?
|
// Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )?
|
||||||
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
|
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
|
||||||
|
|
||||||
val baseSpecs = mutableListOf<BaseSpec>()
|
val baseSpecs = mutableListOf<BaseSpec>()
|
||||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||||
do {
|
do {
|
||||||
@ -1516,13 +1668,13 @@ class Compiler(
|
|||||||
val declRange = MiniRange(pendingDeclStart ?: nameToken.pos, cc.currentPos())
|
val declRange = MiniRange(pendingDeclStart ?: nameToken.pos, cc.currentPos())
|
||||||
val bases = baseSpecs.map { it.name }
|
val bases = baseSpecs.map { it.name }
|
||||||
// Collect constructor fields declared as val/var in primary constructor
|
// Collect constructor fields declared as val/var in primary constructor
|
||||||
val ctorFields = mutableListOf<net.sergeych.lyng.miniast.MiniCtorField>()
|
val ctorFields = mutableListOf<MiniCtorField>()
|
||||||
constructorArgsDeclaration?.let { ad ->
|
constructorArgsDeclaration?.let { ad ->
|
||||||
for (p in ad.params) {
|
for (p in ad.params) {
|
||||||
val at = p.accessType
|
val at = p.accessType
|
||||||
if (at != null) {
|
if (at != null) {
|
||||||
val mutable = at == AccessType.Var
|
val mutable = at == AccessType.Var
|
||||||
ctorFields += net.sergeych.lyng.miniast.MiniCtorField(
|
ctorFields += MiniCtorField(
|
||||||
name = p.name,
|
name = p.name,
|
||||||
mutable = mutable,
|
mutable = mutable,
|
||||||
type = p.miniType,
|
type = p.miniType,
|
||||||
@ -1571,7 +1723,8 @@ class Compiler(
|
|||||||
// accessors, constructor registration, etc.
|
// accessors, constructor registration, etc.
|
||||||
// Resolve parent classes by name at execution time
|
// Resolve parent classes by name at execution time
|
||||||
val parentClasses = baseSpecs.map { baseSpec ->
|
val parentClasses = baseSpecs.map { baseSpec ->
|
||||||
val rec = this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
val rec =
|
||||||
|
this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||||
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2082,7 +2235,7 @@ class Compiler(
|
|||||||
val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet()
|
val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet()
|
||||||
|
|
||||||
// Parse function body while tracking declared locals to compute precise capacity hints
|
// Parse function body while tracking declared locals to compute precise capacity hints
|
||||||
val fnLocalDeclStart = currentLocalDeclCount
|
currentLocalDeclCount
|
||||||
localDeclCountStack.add(0)
|
localDeclCountStack.add(0)
|
||||||
val fnStatements = if (isExtern)
|
val fnStatements = if (isExtern)
|
||||||
statement { raiseError("extern function not provided: $name") }
|
statement { raiseError("extern function not provided: $name") }
|
||||||
@ -2113,7 +2266,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
fnStatements.execute(context)
|
fnStatements.execute(context)
|
||||||
}
|
}
|
||||||
val enclosingCtx = parentContext
|
parentContext
|
||||||
val fnCreateStatement = statement(start) { context ->
|
val fnCreateStatement = statement(start) { context ->
|
||||||
// we added fn in the context. now we must save closure
|
// we added fn in the context. now we must save closure
|
||||||
// for the function, unless we're in the class scope:
|
// for the function, unless we're in the class scope:
|
||||||
@ -2363,7 +2516,7 @@ class Compiler(
|
|||||||
) {
|
) {
|
||||||
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
|
// fun isLeftAssociative() = tokenType != Token.Type.OR && tokenType != Token.Type.AND
|
||||||
|
|
||||||
companion object {}
|
companion object
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2377,15 +2530,24 @@ class Compiler(
|
|||||||
* Compile [source] while streaming a Mini-AST into the provided [sink].
|
* Compile [source] while streaming a Mini-AST into the provided [sink].
|
||||||
* When [sink] is null, behaves like [compile].
|
* When [sink] is null, behaves like [compile].
|
||||||
*/
|
*/
|
||||||
suspend fun compileWithMini(source: Source, importManager: ImportProvider, sink: net.sergeych.lyng.miniast.MiniAstSink?): Script {
|
suspend fun compileWithMini(
|
||||||
return Compiler(CompilerContext(parseLyng(source)), importManager, Settings(miniAstSink = sink)).parseScript()
|
source: Source,
|
||||||
|
importManager: ImportProvider,
|
||||||
|
sink: MiniAstSink?
|
||||||
|
): Script {
|
||||||
|
return Compiler(
|
||||||
|
CompilerContext(parseLyng(source)),
|
||||||
|
importManager,
|
||||||
|
Settings(miniAstSink = sink)
|
||||||
|
).parseScript()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convenience overload to compile raw [code] with a Mini-AST [sink]. */
|
/** Convenience overload to compile raw [code] with a Mini-AST [sink]. */
|
||||||
suspend fun compileWithMini(code: String, sink: net.sergeych.lyng.miniast.MiniAstSink?): Script =
|
suspend fun compileWithMini(code: String, sink: MiniAstSink?): Script =
|
||||||
compileWithMini(Source("<eval>", code), Script.defaultImportManager, sink)
|
compileWithMini(Source("<eval>", code), Script.defaultImportManager, sink)
|
||||||
|
|
||||||
private var lastPriority = 0
|
private var lastPriority = 0
|
||||||
|
|
||||||
// Helpers for conservative constant folding (literal-only). Only pure, side-effect-free ops.
|
// Helpers for conservative constant folding (literal-only). Only pure, side-effect-free ops.
|
||||||
private fun constOf(r: ObjRef): Obj? = (r as? ConstRef)?.constValue
|
private fun constOf(r: ObjRef): Obj? = (r as? ConstRef)?.constValue
|
||||||
|
|
||||||
@ -2404,30 +2566,35 @@ class Compiler(
|
|||||||
a is ObjChar && b is ObjChar -> if (a.value == b.value) ObjTrue else ObjFalse
|
a is ObjChar && b is ObjChar -> if (a.value == b.value) ObjTrue else ObjFalse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.NEQ -> when {
|
BinOp.NEQ -> when {
|
||||||
a is ObjInt && b is ObjInt -> if (a.value != b.value) ObjTrue else ObjFalse
|
a is ObjInt && b is ObjInt -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
a is ObjString && b is ObjString -> if (a.value != b.value) ObjTrue else ObjFalse
|
a is ObjString && b is ObjString -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
a is ObjChar && b is ObjChar -> if (a.value != b.value) ObjTrue else ObjFalse
|
a is ObjChar && b is ObjChar -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.LT -> when {
|
BinOp.LT -> when {
|
||||||
a is ObjInt && b is ObjInt -> if (a.value < b.value) ObjTrue else ObjFalse
|
a is ObjInt && b is ObjInt -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
a is ObjString && b is ObjString -> if (a.value < b.value) ObjTrue else ObjFalse
|
a is ObjString && b is ObjString -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
a is ObjChar && b is ObjChar -> if (a.value < b.value) ObjTrue else ObjFalse
|
a is ObjChar && b is ObjChar -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.LTE -> when {
|
BinOp.LTE -> when {
|
||||||
a is ObjInt && b is ObjInt -> if (a.value <= b.value) ObjTrue else ObjFalse
|
a is ObjInt && b is ObjInt -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
a is ObjString && b is ObjString -> if (a.value <= b.value) ObjTrue else ObjFalse
|
a is ObjString && b is ObjString -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
a is ObjChar && b is ObjChar -> if (a.value <= b.value) ObjTrue else ObjFalse
|
a is ObjChar && b is ObjChar -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.GT -> when {
|
BinOp.GT -> when {
|
||||||
a is ObjInt && b is ObjInt -> if (a.value > b.value) ObjTrue else ObjFalse
|
a is ObjInt && b is ObjInt -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
a is ObjString && b is ObjString -> if (a.value > b.value) ObjTrue else ObjFalse
|
a is ObjString && b is ObjString -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
a is ObjChar && b is ObjChar -> if (a.value > b.value) ObjTrue else ObjFalse
|
a is ObjChar && b is ObjChar -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.GTE -> when {
|
BinOp.GTE -> when {
|
||||||
a is ObjInt && b is ObjInt -> if (a.value >= b.value) ObjTrue else ObjFalse
|
a is ObjInt && b is ObjInt -> if (a.value >= b.value) ObjTrue else ObjFalse
|
||||||
a is ObjString && b is ObjString -> if (a.value >= b.value) ObjTrue else ObjFalse
|
a is ObjString && b is ObjString -> if (a.value >= b.value) ObjTrue else ObjFalse
|
||||||
@ -2441,6 +2608,7 @@ class Compiler(
|
|||||||
a is ObjString && b is ObjString -> ObjString(a.value + b.value)
|
a is ObjString && b is ObjString -> ObjString(a.value + b.value)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null
|
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null
|
||||||
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null
|
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null
|
||||||
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null
|
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null
|
||||||
@ -2468,6 +2636,7 @@ class Compiler(
|
|||||||
is ObjReal -> ObjReal(-a.value)
|
is ObjReal -> ObjReal(-a.value)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null
|
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2638,5 +2807,5 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun eval(code: String) = Compiler.compile(code).execute()
|
suspend fun eval(code: String) = compile(code).execute()
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("INLINE_NOT_NEEDED", "REDUNDANT_INLINE")
|
||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
@ -62,9 +64,9 @@ enum class BinOp {
|
|||||||
/** R-value reference for unary operations. */
|
/** R-value reference for unary operations. */
|
||||||
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val v = if (fastRval) a.evalValue(scope) else a.get(scope).value
|
val v = if (fastRval) a.evalValue(scope) else a.get(scope).value
|
||||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
val rFast: Obj? = when (op) {
|
val rFast: Obj? = when (op) {
|
||||||
UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null
|
UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null
|
||||||
UnaryOp.NEGATE -> when (v) {
|
UnaryOp.NEGATE -> when (v) {
|
||||||
@ -75,7 +77,7 @@ class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
|||||||
UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null
|
UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null
|
||||||
}
|
}
|
||||||
if (rFast != null) {
|
if (rFast != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return rFast.asReadonly
|
return rFast.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,11 +93,11 @@ class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
|||||||
/** R-value reference for binary operations. */
|
/** R-value reference for binary operations. */
|
||||||
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val a = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) left.evalValue(scope) else left.get(scope).value
|
val a = if (PerfFlags.RVAL_FASTPATH) left.evalValue(scope) else left.get(scope).value
|
||||||
val b = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) right.evalValue(scope) else right.get(scope).value
|
val b = if (PerfFlags.RVAL_FASTPATH) right.evalValue(scope) else right.get(scope).value
|
||||||
|
|
||||||
// Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS)
|
// Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS)
|
||||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
// Fast boolean ops when both operands are ObjBool
|
// Fast boolean ops when both operands are ObjBool
|
||||||
if (a is ObjBool && b is ObjBool) {
|
if (a is ObjBool && b is ObjBool) {
|
||||||
val r: Obj? = when (op) {
|
val r: Obj? = when (op) {
|
||||||
@ -106,7 +108,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +136,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +153,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +171,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,19 +179,19 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
if (op == BinOp.PLUS) {
|
if (op == BinOp.PLUS) {
|
||||||
when {
|
when {
|
||||||
a is ObjString && b is ObjInt -> {
|
a is ObjString && b is ObjInt -> {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value + b.value.toString()).asReadonly
|
return ObjString(a.value + b.value.toString()).asReadonly
|
||||||
}
|
}
|
||||||
a is ObjString && b is ObjChar -> {
|
a is ObjString && b is ObjChar -> {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value + b.value).asReadonly
|
return ObjString(a.value + b.value).asReadonly
|
||||||
}
|
}
|
||||||
b is ObjString && a is ObjInt -> {
|
b is ObjString && a is ObjInt -> {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value.toString() + b.value).asReadonly
|
return ObjString(a.value.toString() + b.value).asReadonly
|
||||||
}
|
}
|
||||||
b is ObjString && a is ObjChar -> {
|
b is ObjString && a is ObjChar -> {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value.toString() + b.value).asReadonly
|
return ObjString(a.value.toString() + b.value).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +215,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (rNum != null) {
|
if (rNum != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return rNum.asReadonly
|
return rNum.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,7 +262,7 @@ class ConditionalRef(
|
|||||||
private val ifFalse: ObjRef
|
private val ifFalse: ObjRef
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val condVal = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) condition.evalValue(scope) else condition.get(scope).value
|
val condVal = if (PerfFlags.RVAL_FASTPATH) condition.evalValue(scope) else condition.get(scope).value
|
||||||
val condTrue = when (condVal) {
|
val condTrue = when (condVal) {
|
||||||
is ObjBool -> condVal.value
|
is ObjBool -> condVal.value
|
||||||
is ObjInt -> condVal.value != 0L
|
is ObjInt -> condVal.value != 0L
|
||||||
@ -279,8 +281,8 @@ class CastRef(
|
|||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val v0 = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) valueRef.evalValue(scope) else valueRef.get(scope).value
|
val v0 = if (PerfFlags.RVAL_FASTPATH) valueRef.evalValue(scope) else valueRef.get(scope).value
|
||||||
val t = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) typeRef.evalValue(scope) else typeRef.get(scope).value
|
val t = if (PerfFlags.RVAL_FASTPATH) typeRef.evalValue(scope) else typeRef.get(scope).value
|
||||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${'$'}t is not the class instance")
|
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${'$'}t is not the class instance")
|
||||||
// unwrap qualified views
|
// unwrap qualified views
|
||||||
val v = when (v0) {
|
val v = when (v0) {
|
||||||
@ -323,7 +325,7 @@ class AssignOpRef(
|
|||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val x = target.get(scope).value
|
val x = target.get(scope).value
|
||||||
val y = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
val y = if (PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
||||||
val inPlace: Obj? = when (op) {
|
val inPlace: Obj? = when (op) {
|
||||||
BinOp.PLUS -> x.plusAssign(scope, y)
|
BinOp.PLUS -> x.plusAssign(scope, y)
|
||||||
BinOp.MINUS -> x.minusAssign(scope, y)
|
BinOp.MINUS -> x.minusAssign(scope, y)
|
||||||
@ -380,7 +382,7 @@ class IncDecRef(
|
|||||||
/** Elvis operator reference: a ?: b */
|
/** Elvis operator reference: a ?: b */
|
||||||
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||||
val r = if (a != ObjNull) a else if (fastRval) right.evalValue(scope) else right.get(scope).value
|
val r = if (a != ObjNull) a else if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
@ -390,8 +392,8 @@ class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
|||||||
/** Logical OR with short-circuit: a || b */
|
/** Logical OR with short-circuit: a || b */
|
||||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val fastPrim = net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS
|
val fastPrim = PerfFlags.PRIMITIVE_FASTOPS
|
||||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||||
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
||||||
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||||
@ -408,8 +410,8 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
|
|||||||
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
// Hoist flags to locals for JIT friendliness
|
// Hoist flags to locals for JIT friendliness
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val fastPrim = net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS
|
val fastPrim = PerfFlags.PRIMITIVE_FASTOPS
|
||||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||||
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
||||||
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||||
@ -459,18 +461,18 @@ class FieldRef(
|
|||||||
private var rAccesses: Int = 0; private var rMisses: Int = 0; private var rPromotedTo4: Boolean = false
|
private var rAccesses: Int = 0; private var rMisses: Int = 0; private var rPromotedTo4: Boolean = false
|
||||||
private var wAccesses: Int = 0; private var wMisses: Int = 0; private var wPromotedTo4: Boolean = false
|
private var wAccesses: Int = 0; private var wMisses: Int = 0; private var wPromotedTo4: Boolean = false
|
||||||
private inline fun size4ReadsEnabled(): Boolean =
|
private inline fun size4ReadsEnabled(): Boolean =
|
||||||
net.sergeych.lyng.PerfFlags.FIELD_PIC_SIZE_4 ||
|
PerfFlags.FIELD_PIC_SIZE_4 ||
|
||||||
(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 && rPromotedTo4)
|
(PerfFlags.PIC_ADAPTIVE_2_TO_4 && rPromotedTo4)
|
||||||
private inline fun size4WritesEnabled(): Boolean =
|
private inline fun size4WritesEnabled(): Boolean =
|
||||||
net.sergeych.lyng.PerfFlags.FIELD_PIC_SIZE_4 ||
|
PerfFlags.FIELD_PIC_SIZE_4 ||
|
||||||
(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 && wPromotedTo4)
|
(PerfFlags.PIC_ADAPTIVE_2_TO_4 && wPromotedTo4)
|
||||||
private fun noteReadHit() {
|
private fun noteReadHit() {
|
||||||
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
if (!PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
||||||
rAccesses = a
|
rAccesses = a
|
||||||
}
|
}
|
||||||
private fun noteReadMiss() {
|
private fun noteReadMiss() {
|
||||||
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
if (!PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
||||||
rAccesses = a
|
rAccesses = a
|
||||||
rMisses = (rMisses + 1).coerceAtMost(1_000_000)
|
rMisses = (rMisses + 1).coerceAtMost(1_000_000)
|
||||||
@ -482,12 +484,12 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun noteWriteHit() {
|
private fun noteWriteHit() {
|
||||||
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
if (!PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
||||||
wAccesses = a
|
wAccesses = a
|
||||||
}
|
}
|
||||||
private fun noteWriteMiss() {
|
private fun noteWriteMiss() {
|
||||||
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
if (!PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
||||||
wAccesses = a
|
wAccesses = a
|
||||||
wMisses = (wMisses + 1).coerceAtMost(1_000_000)
|
wMisses = (wMisses + 1).coerceAtMost(1_000_000)
|
||||||
@ -498,15 +500,15 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
val fieldPic = PerfFlags.FIELD_PIC
|
||||||
val picCounters = net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS
|
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
noteReadHit()
|
noteReadHit()
|
||||||
val rec0 = g(base, scope)
|
val rec0 = g(base, scope)
|
||||||
if (base is ObjClass) {
|
if (base is ObjClass) {
|
||||||
@ -516,7 +518,7 @@ class FieldRef(
|
|||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
noteReadHit()
|
noteReadHit()
|
||||||
// move-to-front: promote 2→1
|
// move-to-front: promote 2→1
|
||||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||||
@ -530,7 +532,7 @@ class FieldRef(
|
|||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
noteReadHit()
|
noteReadHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
||||||
@ -545,7 +547,7 @@ class FieldRef(
|
|||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
noteReadHit()
|
noteReadHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
||||||
@ -561,7 +563,7 @@ class FieldRef(
|
|||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicMiss++
|
if (picCounters) PerfStats.fieldPicMiss++
|
||||||
noteReadMiss()
|
noteReadMiss()
|
||||||
val rec = try {
|
val rec = try {
|
||||||
base.readField(scope, name)
|
base.readField(scope, name)
|
||||||
@ -606,8 +608,8 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
val fieldPic = PerfFlags.FIELD_PIC
|
||||||
val picCounters = net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS
|
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||||
val base = target.get(scope).value
|
val base = target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) {
|
if (base == ObjNull && isOptional) {
|
||||||
// no-op on null receiver for optional chaining assignment
|
// no-op on null receiver for optional chaining assignment
|
||||||
@ -629,12 +631,12 @@ class FieldRef(
|
|||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) PerfStats.fieldPicSetHit++
|
||||||
noteWriteHit()
|
noteWriteHit()
|
||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) PerfStats.fieldPicSetHit++
|
||||||
noteWriteHit()
|
noteWriteHit()
|
||||||
// move-to-front: promote 2→1
|
// move-to-front: promote 2→1
|
||||||
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
||||||
@ -643,7 +645,7 @@ class FieldRef(
|
|||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) PerfStats.fieldPicSetHit++
|
||||||
noteWriteHit()
|
noteWriteHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = wKey3; val tV = wVer3; val tS = wSetter3
|
val tK = wKey3; val tV = wVer3; val tS = wSetter3
|
||||||
@ -653,7 +655,7 @@ class FieldRef(
|
|||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) PerfStats.fieldPicSetHit++
|
||||||
noteWriteHit()
|
noteWriteHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = wKey4; val tV = wVer4; val tS = wSetter4
|
val tK = wKey4; val tV = wVer4; val tS = wSetter4
|
||||||
@ -664,7 +666,7 @@ class FieldRef(
|
|||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetMiss++
|
if (picCounters) PerfStats.fieldPicSetMiss++
|
||||||
noteWriteMiss()
|
noteWriteMiss()
|
||||||
base.writeField(scope, name, newValue)
|
base.writeField(scope, name, newValue)
|
||||||
// Install move-to-front with a handle-aware setter; honor PIC size flag
|
// Install move-to-front with a handle-aware setter; honor PIC size flag
|
||||||
@ -707,26 +709,26 @@ class FieldRef(
|
|||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
// Mirror get(), but return raw Obj to avoid transient ObjRecord on R-value paths
|
// Mirror get(), but return raw Obj to avoid transient ObjRecord on R-value paths
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
val fieldPic = PerfFlags.FIELD_PIC
|
||||||
val picCounters = net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS
|
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
return g(base, scope).value
|
return g(base, scope).value
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||||
return g(base, scope).value
|
return g(base, scope).value
|
||||||
} }
|
} }
|
||||||
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
@ -734,7 +736,7 @@ class FieldRef(
|
|||||||
return g(base, scope).value
|
return g(base, scope).value
|
||||||
} }
|
} }
|
||||||
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -742,7 +744,7 @@ class FieldRef(
|
|||||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||||
return g(base, scope).value
|
return g(base, scope).value
|
||||||
} }
|
} }
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicMiss++
|
if (picCounters) PerfStats.fieldPicMiss++
|
||||||
val rec = base.readField(scope, name)
|
val rec = base.readField(scope, name)
|
||||||
// install primary generic getter for this shape
|
// install primary generic getter for this shape
|
||||||
when (base) {
|
when (base) {
|
||||||
@ -789,7 +791,7 @@ class IndexRef(
|
|||||||
else -> 0L to -1
|
else -> 0L to -1
|
||||||
}
|
}
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||||
@ -810,7 +812,7 @@ class IndexRef(
|
|||||||
val v = base.map[idx] ?: ObjNull
|
val v = base.map[idx] ?: ObjNull
|
||||||
return v.asMutable
|
return v.asMutable
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
if (PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for other common shapes
|
// Polymorphic inline cache for other common shapes
|
||||||
val (key, ver) = when (base) {
|
val (key, ver) = when (base) {
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
@ -819,26 +821,26 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -847,9 +849,9 @@ class IndexRef(
|
|||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
// Miss: resolve and install generic handler
|
// Miss: resolve and install generic handler
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicMiss++
|
||||||
val v = base.getAt(scope, idx)
|
val v = base.getAt(scope, idx)
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
}
|
}
|
||||||
@ -863,7 +865,7 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||||
@ -882,7 +884,7 @@ class IndexRef(
|
|||||||
if (base is ObjMap && idx is ObjString) {
|
if (base is ObjMap && idx is ObjString) {
|
||||||
return base.map[idx] ?: ObjNull
|
return base.map[idx] ?: ObjNull
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
if (PerfFlags.INDEX_PIC) {
|
||||||
// PIC path analogous to get(), but returning raw Obj
|
// PIC path analogous to get(), but returning raw Obj
|
||||||
val (key, ver) = when (base) {
|
val (key, ver) = when (base) {
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
@ -891,26 +893,26 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -918,9 +920,9 @@ class IndexRef(
|
|||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicMiss++
|
||||||
val v = base.getAt(scope, idx)
|
val v = base.getAt(scope, idx)
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
}
|
}
|
||||||
@ -934,7 +936,7 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (base == ObjNull && isOptional) {
|
if (base == ObjNull && isOptional) {
|
||||||
// no-op on null receiver for optional chaining assignment
|
// no-op on null receiver for optional chaining assignment
|
||||||
@ -953,7 +955,7 @@ class IndexRef(
|
|||||||
base.map[idx] = newValue
|
base.map[idx] = newValue
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
if (PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for index write
|
// Polymorphic inline cache for index write
|
||||||
val (key, ver) = when (base) {
|
val (key, ver) = when (base) {
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
@ -968,14 +970,14 @@ class IndexRef(
|
|||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
s(base, scope, idx, newValue); return
|
s(base, scope, idx, newValue); return
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||||
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
s(base, scope, idx, newValue); return
|
s(base, scope, idx, newValue); return
|
||||||
} }
|
} }
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||||
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
@ -985,7 +987,7 @@ class IndexRef(
|
|||||||
} }
|
} }
|
||||||
// Miss: perform write and install generic handler
|
// Miss: perform write and install generic handler
|
||||||
base.putAt(scope, idx, newValue)
|
base.putAt(scope, idx, newValue)
|
||||||
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
}
|
}
|
||||||
@ -1016,8 +1018,8 @@ class CallRef(
|
|||||||
private val isOptionalInvoke: Boolean,
|
private val isOptionalInvoke: Boolean,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val usePool = net.sergeych.lyng.PerfFlags.SCOPE_POOL
|
val usePool = PerfFlags.SCOPE_POOL
|
||||||
val callee = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val callee = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
if (callee == ObjNull && isOptionalInvoke) return ObjNull.asReadonly
|
if (callee == ObjNull && isOptionalInvoke) return ObjNull.asReadonly
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
@ -1055,20 +1057,20 @@ class MethodCallRef(
|
|||||||
private var mWindowAccesses: Int = 0
|
private var mWindowAccesses: Int = 0
|
||||||
private var mWindowMisses: Int = 0
|
private var mWindowMisses: Int = 0
|
||||||
private inline fun size4MethodsEnabled(): Boolean =
|
private inline fun size4MethodsEnabled(): Boolean =
|
||||||
net.sergeych.lyng.PerfFlags.METHOD_PIC_SIZE_4 ||
|
PerfFlags.METHOD_PIC_SIZE_4 ||
|
||||||
((net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY) && mPromotedTo4 && mFreezeWindowsLeft == 0)
|
((PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY) && mPromotedTo4 && mFreezeWindowsLeft == 0)
|
||||||
private fun noteMethodHit() {
|
private fun noteMethodHit() {
|
||||||
if (!(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
||||||
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
||||||
mAccesses = a
|
mAccesses = a
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
||||||
// Windowed tracking
|
// Windowed tracking
|
||||||
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
||||||
if (mWindowAccesses >= 256) endHeuristicWindow()
|
if (mWindowAccesses >= 256) endHeuristicWindow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun noteMethodMiss() {
|
private fun noteMethodMiss() {
|
||||||
if (!(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
||||||
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
||||||
mAccesses = a
|
mAccesses = a
|
||||||
mMisses = (mMisses + 1).coerceAtMost(1_000_000)
|
mMisses = (mMisses + 1).coerceAtMost(1_000_000)
|
||||||
@ -1076,7 +1078,7 @@ class MethodCallRef(
|
|||||||
if (mMisses * 100 / a > 20) mPromotedTo4 = true
|
if (mMisses * 100 / a > 20) mPromotedTo4 = true
|
||||||
mAccesses = 0; mMisses = 0
|
mAccesses = 0; mMisses = 0
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
||||||
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
||||||
mWindowMisses = (mWindowMisses + 1).coerceAtMost(1_000_000)
|
mWindowMisses = (mWindowMisses + 1).coerceAtMost(1_000_000)
|
||||||
if (mWindowAccesses >= 256) endHeuristicWindow()
|
if (mWindowAccesses >= 256) endHeuristicWindow()
|
||||||
@ -1106,26 +1108,26 @@ class MethodCallRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||||
val methodPic = net.sergeych.lyng.PerfFlags.METHOD_PIC
|
val methodPic = PerfFlags.METHOD_PIC
|
||||||
val picCounters = net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS
|
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||||
val base = if (fastRval) receiver.evalValue(scope) else receiver.get(scope).value
|
val base = if (fastRval) receiver.evalValue(scope) else receiver.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
if (methodPic) {
|
if (methodPic) {
|
||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) {
|
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) {
|
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
if (size4MethodsEnabled()) mInvoker3?.let { inv -> if (key == mKey3 && ver == mVer3) {
|
if (size4MethodsEnabled()) mInvoker3?.let { inv -> if (key == mKey3 && ver == mVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
|
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
|
||||||
@ -1135,7 +1137,7 @@ class MethodCallRef(
|
|||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
if (size4MethodsEnabled()) mInvoker4?.let { inv -> if (key == mKey4 && ver == mVer4) {
|
if (size4MethodsEnabled()) mInvoker4?.let { inv -> if (key == mKey4 && ver == mVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
|
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
|
||||||
@ -1146,7 +1148,7 @@ class MethodCallRef(
|
|||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicMiss++
|
if (picCounters) PerfStats.methodPicMiss++
|
||||||
noteMethodMiss()
|
noteMethodMiss()
|
||||||
val result = try {
|
val result = try {
|
||||||
base.invokeInstanceMethod(scope, name, callArgs)
|
base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
@ -1232,34 +1234,26 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
// 1) Try fast slot/local
|
// 1) Try fast slot/local
|
||||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||||
scope.getSlotIndexOf(name)?.let {
|
scope.getSlotIndexOf(name)?.let {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicHit++
|
||||||
return scope.getSlotRecord(it)
|
return scope.getSlotRecord(it)
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
||||||
// 2) Fallback to current-scope object or field on `this`
|
// 2) Fallback to current-scope object or field on `this`
|
||||||
scope[name]?.let { return it }
|
scope[name]?.let { return it }
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name)
|
||||||
return when (th) {
|
|
||||||
is Obj -> th.readField(scope, name)
|
|
||||||
else -> scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
||||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||||
if (slot >= 0) {
|
if (slot >= 0) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) {
|
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||||
if (hit) net.sergeych.lyng.PerfStats.localVarPicHit++ else net.sergeych.lyng.PerfStats.localVarPicMiss++
|
if (hit) PerfStats.localVarPicHit++ else PerfStats.localVarPicMiss++
|
||||||
}
|
}
|
||||||
return scope.getSlotRecord(slot)
|
return scope.getSlotRecord(slot)
|
||||||
}
|
}
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicMiss++
|
||||||
// 2) Fallback name in scope or field on `this`
|
// 2) Fallback name in scope or field on `this`
|
||||||
scope[name]?.let { return it }
|
scope[name]?.let { return it }
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name)
|
||||||
return when (th) {
|
|
||||||
is Obj -> th.readField(scope, name)
|
|
||||||
else -> scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
@ -1268,22 +1262,14 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
scope.getSlotIndexOf(name)?.let { return scope.getSlotRecord(it).value }
|
scope.getSlotIndexOf(name)?.let { return scope.getSlotRecord(it).value }
|
||||||
// fallback to current-scope object or field on `this`
|
// fallback to current-scope object or field on `this`
|
||||||
scope[name]?.let { return it.value }
|
scope[name]?.let { return it.value }
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name).value
|
||||||
return when (th) {
|
|
||||||
is Obj -> th.readField(scope, name).value
|
|
||||||
else -> scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
||||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||||
if (slot >= 0) return scope.getSlotRecord(slot).value
|
if (slot >= 0) return scope.getSlotRecord(slot).value
|
||||||
// Fallback name in scope or field on `this`
|
// Fallback name in scope or field on `this`
|
||||||
scope[name]?.let { return it.value }
|
scope[name]?.let { return it.value }
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name).value
|
||||||
return when (th) {
|
|
||||||
is Obj -> th.readField(scope, name).value
|
|
||||||
else -> scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
@ -1301,13 +1287,9 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Fallback: write to field on `this`
|
// Fallback: write to field on `this`
|
||||||
val th = scope.thisObj
|
scope.thisObj.writeField(scope, name, newValue)
|
||||||
if (th is Obj) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
|
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
|
||||||
if (slot >= 0) {
|
if (slot >= 0) {
|
||||||
val rec = scope.getSlotRecord(slot)
|
val rec = scope.getSlotRecord(slot)
|
||||||
@ -1320,13 +1302,9 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
else scope.raiseError("Cannot assign to immutable value")
|
else scope.raiseError("Cannot assign to immutable value")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val th = scope.thisObj
|
scope.thisObj.writeField(scope, name, newValue)
|
||||||
if (th is Obj) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scope.raiseError("symbol not defined: '$name'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1401,8 +1379,8 @@ class FastLocalVarRef(
|
|||||||
val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
||||||
val actualOwner = cachedOwnerScope
|
val actualOwner = cachedOwnerScope
|
||||||
if (slot >= 0 && actualOwner != null) {
|
if (slot >= 0 && actualOwner != null) {
|
||||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) {
|
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||||
if (ownerValid) net.sergeych.lyng.PerfStats.fastLocalHit++ else net.sergeych.lyng.PerfStats.fastLocalMiss++
|
if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++
|
||||||
}
|
}
|
||||||
return actualOwner.getSlotRecord(slot)
|
return actualOwner.getSlotRecord(slot)
|
||||||
}
|
}
|
||||||
@ -1423,9 +1401,7 @@ class FastLocalVarRef(
|
|||||||
// Fallback to standard name lookup (locals or closure chain) if the slot owner changed across suspension
|
// Fallback to standard name lookup (locals or closure chain) if the slot owner changed across suspension
|
||||||
scope[name]?.let { return it }
|
scope[name]?.let { return it }
|
||||||
// As a last resort, treat as field on `this`
|
// As a last resort, treat as field on `this`
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name)
|
||||||
if (th is Obj) return th.readField(scope, name)
|
|
||||||
scope.raiseError("local '$name' is not available in this scope")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
@ -1450,9 +1426,7 @@ class FastLocalVarRef(
|
|||||||
}
|
}
|
||||||
// Fallback to standard name lookup (locals or closure chain)
|
// Fallback to standard name lookup (locals or closure chain)
|
||||||
scope[name]?.let { return it.value }
|
scope[name]?.let { return it.value }
|
||||||
val th = scope.thisObj
|
return scope.thisObj.readField(scope, name).value
|
||||||
if (th is Obj) return th.readField(scope, name).value
|
|
||||||
scope.raiseError("local '$name' is not available in this scope")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
@ -1485,13 +1459,9 @@ class FastLocalVarRef(
|
|||||||
else scope.raiseError("Cannot assign to immutable value")
|
else scope.raiseError("Cannot assign to immutable value")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val th = scope.thisObj
|
scope.thisObj.writeField(scope, name, newValue)
|
||||||
if (th is Obj) {
|
|
||||||
th.writeField(scope, name, newValue)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scope.raiseError("local '$name' is not available in this scope")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
||||||
@ -1502,11 +1472,11 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
|||||||
for (e in entries) {
|
for (e in entries) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is ListEntry.Element -> {
|
is ListEntry.Element -> {
|
||||||
val v = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
val v = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
||||||
list += v
|
list += v
|
||||||
}
|
}
|
||||||
is ListEntry.Spread -> {
|
is ListEntry.Spread -> {
|
||||||
val elements = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
val elements = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
||||||
when (elements) {
|
when (elements) {
|
||||||
is ObjList -> {
|
is ObjList -> {
|
||||||
// Grow underlying array once when possible
|
// Grow underlying array once when possible
|
||||||
@ -1531,8 +1501,8 @@ class RangeRef(
|
|||||||
private val isEndInclusive: Boolean
|
private val isEndInclusive: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val l = left?.let { if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
val l = left?.let { if (PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
||||||
val r = right?.let { if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
val r = right?.let { if (PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
||||||
return ObjRange(l, r, isEndInclusive = isEndInclusive).asReadonly
|
return ObjRange(l, r, isEndInclusive = isEndInclusive).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1544,7 +1514,7 @@ class AssignRef(
|
|||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val v = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
val v = if (PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
||||||
val rec = target.get(scope)
|
val rec = target.get(scope)
|
||||||
if (!rec.isMutable) throw ScriptError(atPos, "cannot assign to immutable variable")
|
if (!rec.isMutable) throw ScriptError(atPos, "cannot assign to immutable variable")
|
||||||
if (rec.value.assign(scope, v) == null) {
|
if (rec.value.assign(scope, v) == null) {
|
||||||
|
|||||||
154
lynglib/src/commonTest/kotlin/NamedArgsTest.kt
Normal file
154
lynglib/src/commonTest/kotlin/NamedArgsTest.kt
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Named arguments and named splats test suite
|
||||||
|
*/
|
||||||
|
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.ExecutionError
|
||||||
|
import net.sergeych.lyng.eval
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class NamedArgsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun basicNamedArgsAndDefaults() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun test(a="foo", b="bar", c="bazz") { [a, b, c] }
|
||||||
|
assertEquals( ["foo", "b", "bazz"], test(b: "b") )
|
||||||
|
assertEquals( ["a", "bar", "c"], test("a", c: "c") )
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun positionalAfterNamedIsError() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(a, b) { [a,b] }
|
||||||
|
f(a: 1, 2)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun namedSplatsBasic() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun test(a="a", b="b", c="c", d="d") { [a, b, c, d] }
|
||||||
|
val r = test("A?", ...Map("d" => "D!", "b" => "B!"))
|
||||||
|
assertEquals(["A?","B!","c","D!"], r)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun namedSplatsNonStringKeysError() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun test(a,b) {}
|
||||||
|
test(1, ...Map(1 => "x"))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun trailingBlockConflictWhenLastNamed() = runTest {
|
||||||
|
// Error: last parameter already assigned by a named argument; trailing block must be rejected
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(x, onDone) { onDone(x) }
|
||||||
|
// Name the last parameter inside parentheses, then try to pass a trailing block
|
||||||
|
f(1, onDone: { it }) { 42 }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Normal case still works when last parameter is not assigned by name
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(x, onDone) { onDone(x) }
|
||||||
|
var res = 0
|
||||||
|
f(1) { it -> res = it }
|
||||||
|
assertEquals(1, res)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun duplicateNamedIsError() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(a,b,c) {}
|
||||||
|
f(a: 1, a: 2)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(a,b,c) {}
|
||||||
|
f(a: 1, ...Map("a" => 2))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unknownParameterIsError() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(a,b) {}
|
||||||
|
f(z: 1)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ellipsisCannotBeNamed() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun g(args..., tail) {}
|
||||||
|
g(args: [1], tail: 2)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun positionalSplatAfterNamedIsError() = runTest {
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun f(a,b,c) {}
|
||||||
|
f(a: 1, ...[2,3])
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2569,7 +2569,6 @@ class ScriptTest {
|
|||||||
x += i
|
x += i
|
||||||
}
|
}
|
||||||
delay(100)
|
delay(100)
|
||||||
println("-> "+x)
|
|
||||||
assert(x == 5050)
|
assert(x == 5050)
|
||||||
}
|
}
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
@ -3544,6 +3543,40 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCallAndResultOrder() = runTest {
|
||||||
|
eval("""
|
||||||
|
import lyng.stdlib
|
||||||
|
|
||||||
|
fun test(a="a", b="b", c="c") { [a, b, c] }
|
||||||
|
|
||||||
|
// the parentheses here are in fact unnecessary:
|
||||||
|
val ok1 = (test { void }).last()
|
||||||
|
assert( ok1 is Callable)
|
||||||
|
|
||||||
|
// it should work without them, as the call test() {} must be executed
|
||||||
|
// first, then the result should be used to call methods on it:
|
||||||
|
|
||||||
|
// the parentheses here are in fact unnecessary:
|
||||||
|
val ok2 = test { void }.last()
|
||||||
|
assert( ok2 is Callable)
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun namedArgsProposal() = runTest {
|
||||||
|
// eval("""
|
||||||
|
// import lyng.stdlib
|
||||||
|
//
|
||||||
|
// fun test(a="a", b="b", c="c") { [a, b, c] }
|
||||||
|
//
|
||||||
|
// val l = (test{ void }).last()
|
||||||
|
// println(l)
|
||||||
|
//
|
||||||
|
// """.trimIndent())
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
// @Ignore
|
// @Ignore
|
||||||
// @Test
|
// @Test
|
||||||
|
|||||||
@ -67,6 +67,7 @@ fun HomePage() {
|
|||||||
I({ classes("bi", "bi-braces", "me-1") })
|
I({ classes("bi", "bi-braces", "me-1") })
|
||||||
Text("Try Lyng")
|
Text("Try Lyng")
|
||||||
}
|
}
|
||||||
|
// (Telegram button moved to the bottom of the page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,4 +106,17 @@ assertEquals([4, 16], evens2)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bottom section with a small Telegram button
|
||||||
|
Div({ classes("text-center", "mt-5", "pb-4") }) {
|
||||||
|
A(attrs = {
|
||||||
|
classes("btn", "btn-outline-primary", "btn-sm")
|
||||||
|
attr("href", "https://t.me/lynglang")
|
||||||
|
attr("target", "_blank")
|
||||||
|
attr("rel", "noopener noreferrer")
|
||||||
|
}) {
|
||||||
|
I({ classes("bi", "bi-telegram", "me-1") })
|
||||||
|
Text("Join our Telegram channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,9 +146,44 @@
|
|||||||
[data-bs-theme="dark"] .hl-lbl { color: #ffa657; }
|
[data-bs-theme="dark"] .hl-lbl { color: #ffa657; }
|
||||||
[data-bs-theme="dark"] .hl-dir { color: #d2a8ff; }
|
[data-bs-theme="dark"] .hl-dir { color: #d2a8ff; }
|
||||||
[data-bs-theme="dark"] .hl-err { color: #ffa198; text-decoration-color: #ffa198; }
|
[data-bs-theme="dark"] .hl-err { color: #ffa198; text-decoration-color: #ffa198; }
|
||||||
|
|
||||||
|
/* Top-left corner ribbon for version label */
|
||||||
|
.corner-ribbon {
|
||||||
|
position: fixed;
|
||||||
|
/* Place below the fixed navbar (Bootstrap fixed-top ~ z-index: 1030) */
|
||||||
|
z-index: 1020; /* above content, below navbar */
|
||||||
|
top: var(--navbar-offset, 56px);
|
||||||
|
left: 0;
|
||||||
|
width: 162px; /* 10% narrower than 180px */
|
||||||
|
text-align: center;
|
||||||
|
/* Slightly asymmetric padding to visually center text within rotated band (desktop) */
|
||||||
|
/* Nudge text a bit lower on large screens */
|
||||||
|
padding: .42rem 0 .28rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: .75rem; /* make text smaller by default */
|
||||||
|
line-height: 1.1; /* stabilize vertical centering */
|
||||||
|
transform: translate(-52px, 10px) rotate(-45deg);
|
||||||
|
box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,.2);
|
||||||
|
border: 1px solid rgba(0,0,0,.15);
|
||||||
|
pointer-events: none; /* decorative; don't block clicks underneath */
|
||||||
|
}
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.corner-ribbon {
|
||||||
|
width: 135px; /* 10% narrower than 150px */
|
||||||
|
transform: translate(-50px, 8px) rotate(-45deg);
|
||||||
|
font-size: .7rem; /* even smaller on phones */
|
||||||
|
padding: .32rem 0 .28rem; /* keep mobile text centered too */
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Top-left version ribbon -->
|
||||||
|
<div class="corner-ribbon bg-danger text-white">
|
||||||
|
<span class="me-3">
|
||||||
|
v1.0.1
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<!-- Fixed top navbar for the whole site -->
|
<!-- Fixed top navbar for the whole site -->
|
||||||
<a href="#root" class="visually-hidden-focusable position-absolute top-0 start-0 m-2 px-2 py-1 bg-body border rounded">Skip to content</a>
|
<a href="#root" class="visually-hidden-focusable position-absolute top-0 start-0 m-2 px-2 py-1 bg-body border rounded">Skip to content</a>
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top border-bottom" role="navigation" aria-label="Primary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top border-bottom" role="navigation" aria-label="Primary">
|
||||||
|
|||||||
@ -63,4 +63,29 @@ class HighlightSmokeTest {
|
|||||||
assertTrue(html.contains("hl-op"))
|
assertTrue(html.contains("hl-op"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun highlightNamedArgsAndMapSplat() {
|
||||||
|
val text = """
|
||||||
|
fun test(a,b,c,d) { [a,b,c,d] }
|
||||||
|
val r = test("A?", c: "C!", ...Map("d" => "D!", "b" => "B!"))
|
||||||
|
""".trimIndent()
|
||||||
|
val spans = SimpleLyngHighlighter().highlight(text)
|
||||||
|
val labeled = spansToLabeled(text, spans)
|
||||||
|
// Ensure identifier for function name appears
|
||||||
|
assertTrue(labeled.any { it.first == "test" && it.second == HighlightKind.Identifier })
|
||||||
|
// Ensure colon for named argument is tokenized as punctuation
|
||||||
|
assertTrue(labeled.any { it.first == ":" && it.second == HighlightKind.Punctuation })
|
||||||
|
// Ensure ellipsis operator present
|
||||||
|
assertTrue(labeled.any { it.first == "..." && it.second == HighlightKind.Operator })
|
||||||
|
// Ensure Map identifier is present
|
||||||
|
assertTrue(labeled.any { it.first == "Map" && it.second == HighlightKind.Identifier })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun highlightNamedArgsHtml() {
|
||||||
|
val text = "val res = test( a: 1, b: 2 )"
|
||||||
|
val html = SiteHighlight.renderHtml(text)
|
||||||
|
// Expect a colon wrapped as punctuation span
|
||||||
|
assertTrue(html.contains("<span class=\"hl-punc\">:</span>"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user