Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
868379ce22 | ||
|
|
f37354f382 | ||
|
|
ddbcbf9e4e | ||
|
|
6cf99fbd13 | ||
|
|
950e8301c3 | ||
|
|
ea2cf1f373 | ||
|
|
eee6d75587 | ||
|
|
a8067d0a6b | ||
|
|
75a6f20150 | ||
|
|
d969d6d572 | ||
|
|
2d4c4d345d | ||
|
|
f9416105ec | ||
|
|
c002204420 | ||
|
|
a4448ab2ff | ||
|
|
8a4363bd84 | ||
|
|
19eae213ec | ||
|
|
1db1f12be3 | ||
|
|
dcde11d722 | ||
|
|
83e79f47c7 | ||
|
|
e0bb183929 | ||
|
|
b961296425 | ||
|
|
bd2b6bf06e | ||
|
|
253480e32a | ||
|
|
eb8110cbf0 | ||
|
|
8c6a1979ed | ||
|
|
185aa4e0cf | ||
|
|
ef266b73a2 | ||
|
|
89427de5cd | ||
|
|
cfb2f7f128 | ||
|
|
be4f2c7f45 | ||
|
|
7cc80e2433 | ||
|
|
dacdcd7faa | ||
|
|
59a76efdce | ||
|
|
bb862e6cb5 | ||
|
|
aea819b89a | ||
|
|
88974e0f2d |
No files matched your search
@@ -9,6 +9,10 @@ class Point(x,y) {
|
|||||||
fun dist() { sqrt(x*x + y*y) }
|
fun dist() { sqrt(x*x + y*y) }
|
||||||
}
|
}
|
||||||
Point(3,4).dist() //< 5
|
Point(3,4).dist() //< 5
|
||||||
|
|
||||||
|
fun swapEnds(first, args..., last, f) {
|
||||||
|
f( last, ...args, first)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- extremely simple Kotlin integration on any platform
|
- extremely simple Kotlin integration on any platform
|
||||||
@@ -31,33 +35,63 @@ and it is multithreaded on platforms supporting it (automatically, no code chang
|
|||||||
|
|
||||||
## Integration in Kotlin multiplatform
|
## Integration in Kotlin multiplatform
|
||||||
|
|
||||||
### Add library
|
### Add dependency to your project
|
||||||
|
|
||||||
TBD
|
```kotlin
|
||||||
|
// update to current please:
|
||||||
|
val lyngVersion = "0.6.1-SNAPSHOT"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// ...
|
||||||
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And add dependency to the proper place in your project, it could look like:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
comminMain by getting {
|
||||||
|
dependencies {
|
||||||
|
// ...
|
||||||
|
implementation("net.sergeych:lynglib:$lyngVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can import lyng and use it:
|
||||||
|
|
||||||
### Execute script:
|
### Execute script:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
assertEquals("hello, world", eval("""
|
import net.sergeyh.lyng.*
|
||||||
"hello, " + "world"
|
|
||||||
""").toString())
|
// we need a coroutine to start, as Lyng
|
||||||
|
// is a coroutine based language, async topdown
|
||||||
|
runBlocking {
|
||||||
|
assert(5 == eval(""" 3*3 - 4 """).toInt())
|
||||||
|
eval(""" println("Hello, Lyng!") """)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exchanging information
|
### Exchanging information
|
||||||
|
|
||||||
Script is executed over some `Context`. Create instance of the context,
|
Script is executed over some `Scope`. Create instance,
|
||||||
add your specific vars and functions to it, an call over it:
|
add your specific vars and functions to it, and call:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|
||||||
|
import com.sun.source.tree.Scope
|
||||||
|
import new.sergeych.lyng.*
|
||||||
|
|
||||||
// simple function
|
// simple function
|
||||||
val context = Context().apply {
|
val scope = Scope().apply {
|
||||||
addFn("addArgs") {
|
addFn("addArgs") {
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
for( a in args) sum += a.toDouble()
|
for (a in args) sum += a.toDouble()
|
||||||
ObjReal(sum)
|
ObjReal(sum)
|
||||||
}
|
}
|
||||||
addConst("LIGHT_SPEED", ObjReal(299_792_458.0))
|
addConst("LIGHT_SPEED", ObjReal(299_792_458.0))
|
||||||
|
|
||||||
// callback back to kotlin to some suspend fn, for example::
|
// callback back to kotlin to some suspend fn, for example::
|
||||||
// suspend fun doSomeWork(text: String): Int
|
// suspend fun doSomeWork(text: String): Int
|
||||||
addFn("doSomeWork") {
|
addFn("doSomeWork") {
|
||||||
@@ -67,9 +101,9 @@ val context = Context().apply {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// adding constant:
|
// adding constant:
|
||||||
context.eval("addArgs(1,2,3)") // <- 6
|
scope.eval("addArgs(1,2,3)") // <- 6
|
||||||
```
|
```
|
||||||
Note that the context stores all changes in it so you can make calls on a single context to preserve state between calls.
|
Note that the scope stores all changes in it so you can make calls on a single scope to preserve state between calls.
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
@@ -83,22 +117,51 @@ Designed to add scripting to kotlin multiplatform application in easy and effici
|
|||||||
|
|
||||||
# Language
|
# Language
|
||||||
|
|
||||||
- dynamic
|
- Javascript, WasmJS, native, JVM, android - batteries included.
|
||||||
- async
|
- dynamic types in most elegant and concise way
|
||||||
- multithreaded (coroutines could be dispatched using threads on appropriate platforms, automatically)
|
- async, 100% coroutines, supports multiple cores where platofrm supports thread
|
||||||
|
- good for functional an object-oriented style
|
||||||
|
|
||||||
## By-stage
|
# Language Roadmap
|
||||||
|
|
||||||
Here are plans to develop it:
|
## v1.0.0
|
||||||
|
|
||||||
### First stage
|
Planned autumn 2025. Complete dynamic language with sufficient standard library:
|
||||||
|
|
||||||
Interpreted, precompiled into threaded code, actually. Dynamic types.
|
Ready features:
|
||||||
|
|
||||||
### Second stage
|
- [x] Language platform and independent command-line launcher
|
||||||
|
- [x] Integral types and user classes, variables and constants, functions
|
||||||
|
- [x] lambdas and closures, coroutines for all callables
|
||||||
|
- [x] while-else, do-while-else, for-else loops with break-continue returning values and labels support
|
||||||
|
- [x] ranges, lists, strings, interfaces: Iterable, Iterator, Collection, Array
|
||||||
|
- [x] when(value), if-then-else
|
||||||
|
- [x] exception handling: throw, try-catch-finally, exception classes.
|
||||||
|
- [x] multiplatform maven publication
|
||||||
|
- [x] documentation for the current state
|
||||||
|
|
||||||
Will add:
|
Under way:
|
||||||
|
|
||||||
- optimizations
|
- [ ] maps, sets and sequences (flows?)
|
||||||
- p-code serialization
|
- [ ] regular exceptions
|
||||||
- static typing
|
- [ ] modules
|
||||||
|
- [ ] string interpolation and more string tools
|
||||||
|
- [ ] multiple inheritance for user classes
|
||||||
|
- [ ] launch, deferred, coroutineScope, mutex, etc.
|
||||||
|
- [ ] site with integrated interpreter to give a try
|
||||||
|
- [ ] kotlin part public API good docs, integration focused
|
||||||
|
- [ ] better stack reporting
|
||||||
|
|
||||||
|
## v1.1+
|
||||||
|
|
||||||
|
Planned features.
|
||||||
|
|
||||||
|
- [ ] type specifications
|
||||||
|
- [ ] source docs and maybe lyng.md to a standard
|
||||||
|
- [ ] moacro-style kotlin integration or something else to simplify it
|
||||||
|
|
||||||
|
Further
|
||||||
|
|
||||||
|
- [ ] client with GUI support based on compose multiplatform somehow
|
||||||
|
- [ ] notebook - style workbooks with graphs, formulaes, etc.
|
||||||
|
- [ ] language server or compose-based lyng-aware editor
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env lyng
|
||||||
|
|
||||||
|
println("Hello from lyng!")
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Collection
|
||||||
|
|
||||||
|
Is a [Iterable] with known `size`, a finite [Iterable]:
|
||||||
|
|
||||||
|
class Collection : Iterable {
|
||||||
|
val size
|
||||||
|
}
|
||||||
|
|
||||||
|
See [List], [Set] and [Iterable]
|
||||||
|
|
||||||
|
[Iterable]: Iterable.md
|
||||||
|
[List]: List.md
|
||||||
|
[Set]: Set.md
|
||||||
+35
-9
@@ -1,13 +1,17 @@
|
|||||||
# Iterable interface
|
# Iterable interface
|
||||||
|
|
||||||
The inteface which requires iterator to be implemented:
|
Iterable is a class that provides function that creates _the iterator_:
|
||||||
|
|
||||||
fun iterator(): Iterator
|
class Iterable {
|
||||||
|
abstract fun iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that each call of `iterator()` must provide an independent iterator.
|
||||||
|
|
||||||
Iterator itself is a simple interface that should provide only to method:
|
Iterator itself is a simple interface that should provide only to method:
|
||||||
|
|
||||||
interface Iterable {
|
class Iterator {
|
||||||
fun hasNext(): Bool
|
abstract fun hasNext(): Bool
|
||||||
fun next(): Obj
|
fun next(): Obj
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,19 +19,41 @@ Just remember at this stage typed declarations are not yet supported.
|
|||||||
|
|
||||||
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available:
|
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available:
|
||||||
|
|
||||||
|
## Instance methods
|
||||||
|
|
||||||
|
| fun/method | description |
|
||||||
|
|-----------------|---------------------------------------------------------------------------------|
|
||||||
|
| toList() | create a list from iterable |
|
||||||
|
| toSet() | create a set from iterable |
|
||||||
|
| contains(i) | check that iterable contains `i` |
|
||||||
|
| `i in iterator` | same as `contains(i)` |
|
||||||
|
| isEmpty() | check iterable is empty |
|
||||||
|
| forEach(f) | call f for each element |
|
||||||
|
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
|
||||||
|
| map(f) | create a list of values returned by `f` called for each element of the iterable |
|
||||||
|
| indexOf(i) | return index if the first encounter of i or a negative value if not found |
|
||||||
|
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
|
||||||
|
|
||||||
|
|
||||||
|
fun Iterable.toList(): List
|
||||||
|
fun Iterable.toSet(): Set
|
||||||
|
fun Iterable.indexOf(element): Int
|
||||||
|
fun Iterable.contains(element): Bool
|
||||||
|
fun Iterable.isEmpty(element): Bool
|
||||||
|
fun Iterable.forEach(block: (Any?)->Void ): Void
|
||||||
|
fun Iterable.map(block: (Any?)->Void ): List
|
||||||
|
fun Iterable.associateBy( keyMaker: (Any?)->Any): Map
|
||||||
|
|
||||||
|
|
||||||
## Abstract methods
|
## Abstract methods
|
||||||
|
|
||||||
fun iterator(): Iterator
|
fun iterator(): Iterator
|
||||||
|
|
||||||
## Instance methods
|
|
||||||
|
|
||||||
### toList()
|
|
||||||
|
|
||||||
Creates a list by iterating to the end. So, the Iterator should be finite to be used with it.
|
Creates a list by iterating to the end. So, the Iterator should be finite to be used with it.
|
||||||
|
|
||||||
## Included in interfaces:
|
## Included in interfaces:
|
||||||
|
|
||||||
- Collection, Array, [List]
|
- [Collection], Array, [List]
|
||||||
|
|
||||||
## Implemented in classes:
|
## Implemented in classes:
|
||||||
|
|
||||||
|
|||||||
+63
-16
@@ -20,11 +20,11 @@ indexing is zero-based, as in C/C++/Java/Kotlin, etc.
|
|||||||
list[1]
|
list[1]
|
||||||
>>> 20
|
>>> 20
|
||||||
|
|
||||||
Using negative indexes has a special meaning: _offset from the end of the list_:
|
There is a shortcut for the last:
|
||||||
|
|
||||||
val list = [10, 20, 30]
|
val list = [10, 20, 30]
|
||||||
list[-1]
|
[list.last, list.lastIndex]
|
||||||
>>> 30
|
>>> [30, 2]
|
||||||
|
|
||||||
__Important__ negative indexes works wherever indexes are used, e.g. in insertion and removal methods too.
|
__Important__ negative indexes works wherever indexes are used, e.g. in insertion and removal methods too.
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ You can concatenate lists or iterable objects:
|
|||||||
|
|
||||||
## Appending
|
## Appending
|
||||||
|
|
||||||
To append to lists, use `+=` with elements, lists and any [Iterable] instances, but beware it will concatenate [Iterable] objects instead of appending them. To append [Iterable] instance itself, use `list.add`:
|
To append to lists, use `+=` with elements, lists and any [Iterable] instances, but beware it will
|
||||||
|
concatenate [Iterable] objects instead of appending them. To append [Iterable] instance itself, use `list.add`:
|
||||||
|
|
||||||
var list = [1, 2]
|
var list = [1, 2]
|
||||||
val other = [3, 4]
|
val other = [3, 4]
|
||||||
@@ -57,7 +58,28 @@ To append to lists, use `+=` with elements, lists and any [Iterable] instances,
|
|||||||
|
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## Removing elements
|
||||||
|
|
||||||
|
List is mutable, so it is possible to remove its contents. To remove a single element
|
||||||
|
by index use:
|
||||||
|
|
||||||
|
assertEquals( [1,2,3].removeAt(1), [1,3] )
|
||||||
|
assertEquals( [1,2,3].removeAt(0), [2,3] )
|
||||||
|
assertEquals( [1,2,3].removeLast(), [1,2] )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
There is a way to remove a range (see [Range] for more on ranges):
|
||||||
|
|
||||||
|
assertEquals( [1, 4], [1,2,3,4].removeRange(1..2))
|
||||||
|
assertEquals( [1, 4], [1,2,3,4].removeRange(1..<3))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Open end ranges remove head and tail elements:
|
||||||
|
|
||||||
|
assertEquals( [3, 4, 5], [1,2,3,4,5].removeRange(..1))
|
||||||
|
assertEquals( [3, 4, 5], [1,2,3,4,5].removeRange(..<2))
|
||||||
|
assertEquals( [1, 2], [1,2,3,4,5].removeRange( (2..) ))
|
||||||
|
>>> void
|
||||||
|
|
||||||
## Comparisons
|
## Comparisons
|
||||||
|
|
||||||
@@ -72,19 +94,44 @@ To append to lists, use `+=` with elements, lists and any [Iterable] instances,
|
|||||||
|
|
||||||
## Members
|
## Members
|
||||||
|
|
||||||
| name | meaning | type |
|
| name | meaning | type |
|
||||||
|-----------------------------------|-------------------------------------|----------|
|
|-------------------------------|---------------------------------------|-------------|
|
||||||
| `size` | current size | Int |
|
| `size` | current size | Int |
|
||||||
| `add(elements...)` | add one or more elements to the end | Any |
|
| `add(elements...)` | add one or more elements to the end | Any |
|
||||||
| `addAt(index,elements...)` | insert elements at position | Int, Any |
|
| `insertAt(index,elements...)` | insert elements at position | Int, Any |
|
||||||
| `removeAt(index)` | remove element at position | Int |
|
| `removeAt(index)` | remove element at position | Int |
|
||||||
| `removeRangeInclusive(start,end)` | remove range, inclusive (1) | Int, Int |
|
| `remove(from,toNonInclusive)` | remove range from (incl) to (nonincl) | Int, Int |
|
||||||
| | | |
|
| `remove(Range)` | remove range | Range |
|
||||||
|
| `removeLast()` | remove last element | |
|
||||||
|
| `removeLast(n)` | remove n last elements | Int |
|
||||||
|
| `contains(element)` | check the element is in the list (1) | |
|
||||||
|
| `[index]` | get or set element at index | Int |
|
||||||
|
| `[Range]` | get slice of the array (copy) | Range |
|
||||||
|
| `+=` | append element(s) | List or Obj |
|
||||||
|
|
||||||
(1)
|
(1)
|
||||||
: end-inclisiveness allows to use negative indexes to, for exampe, remove several last elements, like `list.removeRangeInclusive(-2, -1)` will remove two last elements.
|
: optimized implementation that override `Array` one
|
||||||
|
|
||||||
|
(2)
|
||||||
|
: `+=` append either a single element, or all elements if the List or other Iterable
|
||||||
|
instance is appended. If you want to append an Iterable object itself, use `add` instead.
|
||||||
|
|
||||||
|
It inherits from [Iterable] too.
|
||||||
|
|
||||||
|
## Member inherited from Array
|
||||||
|
|
||||||
|
| name | meaning | type |
|
||||||
|
|------------------|--------------------------------|-------|
|
||||||
|
| `last` | last element (throws if empty) | |
|
||||||
|
| `lastOrNull` | last element or null | |
|
||||||
|
| `lastIndex` | | Int |
|
||||||
|
| `indices` | range of indexes | Range |
|
||||||
|
| `contains(item)` | test that item is in the list | |
|
||||||
|
|
||||||
|
(1)
|
||||||
|
: end-inclisiveness allows to use negative indexes to, for exampe, remove several last elements, like
|
||||||
|
`list.removeRangeInclusive(-2, -1)` will remove two last elements.
|
||||||
|
|
||||||
|
|
||||||
# Notes
|
[Range]: Range.md
|
||||||
|
[Iterable]: Iterable.md
|
||||||
Could be rewritten using array as a class but List as the interface
|
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
# Map
|
||||||
|
|
||||||
|
Map is a mutable collection of key-value pars, where keys are unique. Maps could be created with
|
||||||
|
constructor or `.toMap` methods. When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List].
|
||||||
|
|
||||||
|
Constructed map instance is of class `Map` and implements `Collection` (and therefore `Iterable`)
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"] )
|
||||||
|
assert(map is Map)
|
||||||
|
assert(map.size == 2)
|
||||||
|
assert(map is Iterable)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Map keys could be any objects (hashable, e.g. with reasonable hashCode, most of standard types are). You can access elements with indexing operator:
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
assert( map["bar"] == "buzz")
|
||||||
|
assert( map[42] == "answer" )
|
||||||
|
assertThrows { map["nonexistent"] }
|
||||||
|
assert( map.getOrNull(101) == null )
|
||||||
|
assert( map.getOrPut(911) { "nine-eleven" } == "nine-eleven" )
|
||||||
|
// now 91 entry is set:
|
||||||
|
assert( map[911] == "nine-eleven" )
|
||||||
|
map["foo"] = -1
|
||||||
|
assert( map["foo"] == -1)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
To remove item from the collection. use `remove`. It returns last removed item or null. Be careful if you
|
||||||
|
hold nulls in the map - this is not a recommended practice when using `remove` returned value. `clear()`
|
||||||
|
removes all.
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
assertEquals( 1, map.remove("foo") )
|
||||||
|
assert( map.getOrNull("foo") == null)
|
||||||
|
assert( map.size == 2 )
|
||||||
|
map.clear()
|
||||||
|
assert( map.size == 0 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Map implements [contains] method that checks _the presence of the key_ in the map:
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
assert( "foo" in map )
|
||||||
|
assert( "answer" !in map )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
To iterate maps it is convenient to use `keys` method that returns [Set] of keys (keys are unique:
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
for( k in map.keys ) println(map[k])
|
||||||
|
>>> 1
|
||||||
|
>>> buzz
|
||||||
|
>>> answer
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Or iterate its key-value pairs that are instances of [MapEntry] class:
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
for( entry in map ) {
|
||||||
|
println("map[%s] = %s"(entry.key, entry.value))
|
||||||
|
}
|
||||||
|
void
|
||||||
|
>>> map[foo] = 1
|
||||||
|
>>> map[bar] = buzz
|
||||||
|
>>> map[42] = answer
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
There is a shortcut to use `MapEntry` to create maps: operator `=>` which creates `MapEntry`:
|
||||||
|
|
||||||
|
val entry = "answer" => 42
|
||||||
|
assert( entry is MapEntry )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
And you can use it to construct maps:
|
||||||
|
|
||||||
|
val map = Map( "foo" => 1, "bar" => 22)
|
||||||
|
assertEquals(1, map["foo"])
|
||||||
|
assertEquals(22, map["bar"])
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Or use `.toMap` on anything that implements [Iterable] and which elements implements [Array] with 2 elements size, for example, `MapEntry`:
|
||||||
|
|
||||||
|
val map = ["foo" => 1, "bar" => 22].toMap()
|
||||||
|
assert( map is Map )
|
||||||
|
assertEquals(1, map["foo"])
|
||||||
|
assertEquals(22, map["bar"])
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
|
||||||
|
It is possible also to get values as [List] (values are not unique):
|
||||||
|
|
||||||
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
|
assertEquals(map.values, [1, "buzz", "answer"] )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Map could be tested to be equal: when all it key-value pairs are equal, the map
|
||||||
|
is equal.
|
||||||
|
|
||||||
|
val m1 = Map(["foo", 1])
|
||||||
|
val m2 = Map(["foo", 1])
|
||||||
|
val m3 = Map(["foo", 2])
|
||||||
|
assert( m1 == m2 )
|
||||||
|
// but the references are different:
|
||||||
|
assert( m1 !== m2 )
|
||||||
|
// different maps:
|
||||||
|
assert( m1 != m3 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
[Collection](Collection.md)
|
||||||
+34
@@ -160,6 +160,40 @@ For example, for our class Point:
|
|||||||
Point(1,1+1)
|
Point(1,1+1)
|
||||||
>>> Point(x=1,y=2)
|
>>> Point(x=1,y=2)
|
||||||
|
|
||||||
|
# Extending classes
|
||||||
|
|
||||||
|
It sometimes happen that the class is missing some particular functionality that can be _added to it_ without rewriting its inner logic and using its private state. In this case _extension methods_ could be used, for example. we want to create an extension method
|
||||||
|
that would test if some object of unknown type contains something that can be interpreted
|
||||||
|
as an integer. In this case we _extend_ class `Object`, as it is the parent class for any instance of any type:
|
||||||
|
|
||||||
|
fun Object.isInteger() {
|
||||||
|
when(this) {
|
||||||
|
// already Int?
|
||||||
|
is Int -> true
|
||||||
|
|
||||||
|
// real, but with no declimal part?
|
||||||
|
is Real -> toInt() == this
|
||||||
|
|
||||||
|
// string with int or real reuusig code above
|
||||||
|
is String -> toReal().isInteger()
|
||||||
|
|
||||||
|
// otherwise, no:
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's test:
|
||||||
|
assert( 12.isInteger() == true )
|
||||||
|
assert( 12.1.isInteger() == false )
|
||||||
|
assert( "5".isInteger() )
|
||||||
|
assert( ! "5.2".isInteger() )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
__Important note__ as for version 0.6.9, extensions are in __global scope__. It means, that once applied to a global type (Int in our sample), they will be available for _all_ contexts, even new created,
|
||||||
|
as they are modifying the type, not the context.
|
||||||
|
|
||||||
|
Beware of it. We might need to reconsider it later.
|
||||||
|
|
||||||
# Theory
|
# Theory
|
||||||
|
|
||||||
## Basic principles:
|
## Basic principles:
|
||||||
|
|||||||
+8
-8
@@ -15,11 +15,11 @@ you can use it's class to ensure type:
|
|||||||
|
|
||||||
## Member functions
|
## Member functions
|
||||||
|
|
||||||
| name | meaning | type |
|
| name | meaning | type |
|
||||||
|-----------------|------------------------------------|------|
|
|-----------------|-------------------------------------------------------------|------|
|
||||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||||
| | | |
|
| `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
# List built-in class
|
||||||
|
|
||||||
|
Mutable set of any objects: a group of different objects, no repetitions.
|
||||||
|
Sets are not ordered, order of appearance does not matter.
|
||||||
|
|
||||||
|
val set = Set(1,2,3, "foo")
|
||||||
|
assert( 1 in set )
|
||||||
|
assert( "foo" in set)
|
||||||
|
assert( "bar" !in set)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Set is collection and therefore [Iterable]:
|
||||||
|
|
||||||
|
assert( Set(1,2) is Set)
|
||||||
|
assert( Set(1,2) is Iterable)
|
||||||
|
assert( Set(1,2) is Collection)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
So it supports all methods from [Iterable]; set is not, though, an [Array] and has
|
||||||
|
no indexing. Use [set.toList] as needed.
|
||||||
|
|
||||||
|
## Set operations
|
||||||
|
|
||||||
|
// Union
|
||||||
|
assertEquals( Set(1,2,3,4), Set(3, 1) + Set(2, 4))
|
||||||
|
|
||||||
|
// intersection
|
||||||
|
assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) )
|
||||||
|
// or simple
|
||||||
|
assertEquals( Set(1,4), Set(3, 1, 4) * Set(2, 4, 1) )
|
||||||
|
|
||||||
|
// To find collection elements not present in another collection, use the
|
||||||
|
// subtract() or `-`:
|
||||||
|
assertEquals( Set( 1, 2), Set(1, 2, 4, 3) - Set(3, 4))
|
||||||
|
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Adding elements
|
||||||
|
|
||||||
|
var s = Set()
|
||||||
|
s += 1
|
||||||
|
assertEquals( Set(1), s)
|
||||||
|
|
||||||
|
s += [3, 3, 4]
|
||||||
|
assertEquals( Set(3, 4, 1), s)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Removing elements
|
||||||
|
|
||||||
|
List is mutable, so it is possible to remove its contents. To remove a single element
|
||||||
|
by index use:
|
||||||
|
|
||||||
|
var s = Set(1,2,3)
|
||||||
|
s.remove(2)
|
||||||
|
assertEquals( s, Set(1,3) )
|
||||||
|
|
||||||
|
s = Set(1,2,3)
|
||||||
|
s.remove(2,1)
|
||||||
|
assertEquals( s, Set(3) )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Note that `remove` returns true if at least one element was actually removed and false
|
||||||
|
if the set has not been changed.
|
||||||
|
|
||||||
|
## Comparisons and inclusion
|
||||||
|
|
||||||
|
Sets are only equal when contains exactly same elements, order, as was said, is not significant:
|
||||||
|
|
||||||
|
assert( Set(1, 2) == Set(2, 1) )
|
||||||
|
assert( Set(1, 2, 2) == Set(2, 1) )
|
||||||
|
assert( Set(1, 3) != Set(2, 1) )
|
||||||
|
assert( 1 in Set(5,1))
|
||||||
|
assert( 10 !in Set(5,1))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Members
|
||||||
|
|
||||||
|
| name | meaning | type |
|
||||||
|
|---------------------|--------------------------------------|-------|
|
||||||
|
| `size` | current size | Int |
|
||||||
|
| `+=` | add one or more elements | Any |
|
||||||
|
| `+`, `union` | union sets | Any |
|
||||||
|
| `-`, `subtract` | subtract sets | Any |
|
||||||
|
| `*`, `intersect` | subtract sets | Any |
|
||||||
|
| `remove(items...)` | remove one or more items | Range |
|
||||||
|
| `contains(element)` | check the element is in the list (1) | |
|
||||||
|
|
||||||
|
(1)
|
||||||
|
: optimized implementation that override `Iterable` one
|
||||||
|
|
||||||
|
Also, it inherits methods from [Iterable].
|
||||||
|
|
||||||
|
|
||||||
|
[Range]: Range.md
|
||||||
Whitespace-only changes.
+25
-1
@@ -86,4 +86,28 @@ Lambda functions remember their scopes, so it will work the same as previous:
|
|||||||
val c = createLambda()
|
val c = createLambda()
|
||||||
println(c)
|
println(c)
|
||||||
>> 1
|
>> 1
|
||||||
>> void
|
>> void
|
||||||
|
|
||||||
|
# Elements of functional programming
|
||||||
|
|
||||||
|
With ellipsis and splats you can create partial functions, manipulate
|
||||||
|
arguments list in almost arbitrary ways. For example:
|
||||||
|
|
||||||
|
// Swap first and last arguments for call
|
||||||
|
|
||||||
|
fun swap_args(first, others..., last, f) {
|
||||||
|
f(last, ...others, first)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun glue(args...) {
|
||||||
|
var result = ""
|
||||||
|
for( a in args ) result += a
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"4231",
|
||||||
|
swap_args( 1, 2, 3, 4, glue)
|
||||||
|
)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
,
|
||||||
@@ -81,6 +81,22 @@ to catch all exceptions to, then you can write it even shorter:
|
|||||||
You can even check the type of the `it` and create more convenient and sophisticated processing logic. Such approach is
|
You can even check the type of the `it` and create more convenient and sophisticated processing logic. Such approach is
|
||||||
used, for example, in Scala.
|
used, for example, in Scala.
|
||||||
|
|
||||||
|
## finally block
|
||||||
|
|
||||||
|
If `finally` block present, it will be executed after body (until first exception)
|
||||||
|
and catch block, if any will match. finally statement is executed even if the
|
||||||
|
exception will be thrown and not caught locally. It does not alter try/catch block result:
|
||||||
|
|
||||||
|
try {
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
println("called finally")
|
||||||
|
}
|
||||||
|
>>> called finally
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
- and yes, there could be try-finally block, no catching, but perform some guaranteed cleanup.
|
||||||
|
|
||||||
# Conveying data with exceptions
|
# Conveying data with exceptions
|
||||||
|
|
||||||
The simplest way is to provide exception string and `Exception` class:
|
The simplest way is to provide exception string and `Exception` class:
|
||||||
@@ -115,7 +131,7 @@ _this functionality is not yet released_
|
|||||||
| class | notes |
|
| class | notes |
|
||||||
|----------------------------|-------------------------------------------------------|
|
|----------------------------|-------------------------------------------------------|
|
||||||
| Exception | root of al throwable objects |
|
| Exception | root of al throwable objects |
|
||||||
| NullPointerException | |
|
| NullReferenceException | |
|
||||||
| AssertionFailedException | |
|
| AssertionFailedException | |
|
||||||
| ClassCastException | |
|
| ClassCastException | |
|
||||||
| IndexOutOfBoundsException | |
|
| IndexOutOfBoundsException | |
|
||||||
|
|||||||
+303
-31
@@ -14,7 +14,7 @@ __Other documents to read__ maybe after this one:
|
|||||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||||
- [math in Lyng](math.md)
|
- [math in Lyng](math.md)
|
||||||
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator]
|
||||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||||
|
|
||||||
# Expressions
|
# Expressions
|
||||||
@@ -118,6 +118,109 @@ These operators return rvalue, unmodifiable.
|
|||||||
|
|
||||||
## Assignment return r-value!
|
## Assignment return r-value!
|
||||||
|
|
||||||
|
Naturally, assignment returns its value:
|
||||||
|
|
||||||
|
var x
|
||||||
|
x = 11
|
||||||
|
>>> 11
|
||||||
|
|
||||||
|
rvalue means you cant assign the result if the assignment
|
||||||
|
|
||||||
|
var x
|
||||||
|
assertThrows { (x = 11) = 5 }
|
||||||
|
void
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
This also prevents chain assignments so use parentheses:
|
||||||
|
|
||||||
|
var x
|
||||||
|
var y
|
||||||
|
x = (y = 1)
|
||||||
|
>>> 1
|
||||||
|
|
||||||
|
## Nullability
|
||||||
|
|
||||||
|
When the value is `null`, it might throws `NullReferenceException`, the name is somewhat a tradition. To avoid it
|
||||||
|
one can check it against null or use _null coalescing_. The null coalescing means, if the operand (left) is null,
|
||||||
|
the operation won't be performed and the result will be null. Here is the difference:
|
||||||
|
|
||||||
|
val ref = null
|
||||||
|
assertThrows { ref.field }
|
||||||
|
assertThrows { ref.method() }
|
||||||
|
assertThrows { ref.array[1] }
|
||||||
|
assertThrows { ref[1] }
|
||||||
|
assertThrows { ref() }
|
||||||
|
|
||||||
|
assert( ref?.field == null )
|
||||||
|
assert( ref?.method() == null )
|
||||||
|
assert( ref?.array?[1] == null )
|
||||||
|
assert( ref?[1] == null )
|
||||||
|
assert( ref?() == null )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
There is also "elvis operator", null-coalesce infix operator '?:' that returns rvalue if lvalue is `null`:
|
||||||
|
|
||||||
|
null ?: "nothing"
|
||||||
|
>>> "nothing"
|
||||||
|
|
||||||
|
## Utility functions
|
||||||
|
|
||||||
|
The following functions simplify nullable values processing and
|
||||||
|
allow to improve code look and readability. There are borrowed from Kotlin:
|
||||||
|
|
||||||
|
### let
|
||||||
|
|
||||||
|
`value.let {}` passes to the block value as the single parameter (by default it is assigned to `it`) and return block's returned value. It is useful dealing with null or to
|
||||||
|
get a snapshot of some externally varying value, or with `?.` to process nullable value in a safe manner:
|
||||||
|
|
||||||
|
// this state is changed from parallel processes
|
||||||
|
class GlobalState(nullableParam)
|
||||||
|
|
||||||
|
val state = GlobalState(null)
|
||||||
|
|
||||||
|
fun sample() {
|
||||||
|
state.nullableParam?.let { "it's not null: "+it} ?: "it's null"
|
||||||
|
}
|
||||||
|
assertEquals(sample(), "it's null")
|
||||||
|
state.nullableParam = 5
|
||||||
|
assertEquals(sample(), "it's not null: 5")
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
This is the same as:
|
||||||
|
|
||||||
|
fun sample() {
|
||||||
|
val it = state.nullableParam
|
||||||
|
if( it != null ) "it's not null: "+it else "it's null"
|
||||||
|
}
|
||||||
|
|
||||||
|
The important is that nullableParam got a local copy that can't be changed from any
|
||||||
|
parallel thread/coroutine. Remember: Lyng _is __not__ a single-threaded language_.
|
||||||
|
|
||||||
|
## Also
|
||||||
|
|
||||||
|
Much like let, but it does not alter returned value:
|
||||||
|
|
||||||
|
assert( "test".also { println( it + "!") } == "test" )
|
||||||
|
>>> test!
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
While it is not altering return value, the source object could be changed:
|
||||||
|
|
||||||
|
class Point(x,y)
|
||||||
|
val p = Point(1,2).also { it.x++ }
|
||||||
|
assertEquals(p.x, 2)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## apply
|
||||||
|
|
||||||
|
It works much like `also`, but is executed in the context of the source object:
|
||||||
|
|
||||||
|
class Point(x,y)
|
||||||
|
// see the difference: apply changes this to newly created Point:
|
||||||
|
val p = Point(1,2).apply { x++; y++ }
|
||||||
|
assertEquals(p, Point(2,3))
|
||||||
|
>>> void
|
||||||
|
|
||||||
## Math
|
## Math
|
||||||
|
|
||||||
It is rather simple, like everywhere else:
|
It is rather simple, like everywhere else:
|
||||||
@@ -469,64 +572,97 @@ To add elements to the list:
|
|||||||
assert( x == [1, 2, 3, "the", "end"])
|
assert( x == [1, 2, 3, "the", "end"])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
Self-modifying concatenation by `+=` also works:
|
Self-modifying concatenation by `+=` also works (also with single elements):
|
||||||
|
|
||||||
val x = [1, 2]
|
val x = [1, 2]
|
||||||
x += [3, 4]
|
x += [3, 4]
|
||||||
assert( x == [1, 2, 3, 4])
|
x += 5
|
||||||
|
assert( x == [1, 2, 3, 4, 5])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
You can insert elements at any position using `addAt`:
|
You can insert elements at any position using `insertAt`:
|
||||||
|
|
||||||
val x = [1,2,3]
|
val x = [1,2,3]
|
||||||
x.addAt(1, "foo", "bar")
|
x.insertAt(1, "foo", "bar")
|
||||||
assert( x == [1, "foo", "bar", 2, 3])
|
assert( x == [1, "foo", "bar", 2, 3])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
Using splat arguments can simplify inserting list in list:
|
Using splat arguments can simplify inserting list in list:
|
||||||
|
|
||||||
val x = [1, 2, 3]
|
val x = [1, 2, 3]
|
||||||
x.addAt( 1, ...[0,100,0])
|
x.insertAt( 1, ...[0,100,0])
|
||||||
x
|
x
|
||||||
>>> [1, 0, 100, 0, 2, 3]
|
>>> [1, 0, 100, 0, 2, 3]
|
||||||
|
|
||||||
Using negative indexes can insert elements as offset from the end, for example:
|
|
||||||
|
|
||||||
val x = [1,2,3]
|
|
||||||
x.addAt(-1, 10)
|
|
||||||
x
|
|
||||||
>>> [1, 2, 10, 3]
|
|
||||||
|
|
||||||
Note that to add to the end you still need to use `add` or positive index of the after-last element:
|
Note that to add to the end you still need to use `add` or positive index of the after-last element:
|
||||||
|
|
||||||
val x = [1,2,3]
|
val x = [1,2,3]
|
||||||
x.addAt(3, 10)
|
x.insertAt(3, 10)
|
||||||
x
|
x
|
||||||
>>> [1, 2, 3, 10]
|
>>> [1, 2, 3, 10]
|
||||||
|
|
||||||
|
but it is much simpler, and we recommend to use '+='
|
||||||
|
|
||||||
|
val x = [1,2,3]
|
||||||
|
x += 10
|
||||||
|
>>> [1, 2, 3, 10]
|
||||||
|
|
||||||
## Removing list items
|
## Removing list items
|
||||||
|
|
||||||
val x = [1, 2, 3, 4, 5]
|
val x = [1, 2, 3, 4, 5]
|
||||||
x.removeAt(2)
|
x.removeAt(2)
|
||||||
assert( x == [1, 2, 4, 5])
|
assert( x == [1, 2, 4, 5])
|
||||||
// or remove range (start inclusive, end exclusive):
|
// or remove range (start inclusive, end exclusive):
|
||||||
x.removeRangeInclusive(1,2)
|
x.removeRange(1..2)
|
||||||
assert( x == [1, 5])
|
assert( x == [1, 5])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
Again, you can use negative indexes. For example, removing last elements like:
|
There is a shortcut to remove the last elements:
|
||||||
|
|
||||||
val x = [1, 2, 3, 4, 5]
|
val x = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
// remove last:
|
// remove last:
|
||||||
x.removeAt(-1)
|
x.removeLast()
|
||||||
assert( x == [1, 2, 3, 4])
|
assert( x == [1, 2, 3, 4])
|
||||||
|
|
||||||
// remove 2 last:
|
// remove 2 last:
|
||||||
x.removeRangeInclusive(-2,-1)
|
x.removeLast(2)
|
||||||
assert( x == [1, 2])
|
assertEquals( [1, 2], x)
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
You can get ranges to extract a portion from a list:
|
||||||
|
|
||||||
|
val list = [1, 2, 3, 4, 5]
|
||||||
|
assertEquals( [1,2,3], list[..2])
|
||||||
|
assertEquals( [1,2,], list[..<2])
|
||||||
|
assertEquals( [4,5], list[3..])
|
||||||
|
assertEquals( [2,3], list[1..2])
|
||||||
|
assertEquals( [2,3], list[1..<3])
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
# Sets
|
||||||
|
|
||||||
|
Set are unordered collection of unique elements, see [Set]. Sets are [Iterable] but have no indexing access.
|
||||||
|
|
||||||
|
assertEquals( Set(3, 2, 1), Set( 1, 2, 3))
|
||||||
|
assert( 5 !in Set(1, 2, 6) )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Please see [Set] for detailed description.
|
||||||
|
|
||||||
|
# Maps
|
||||||
|
|
||||||
|
Maps are unordered collection of key-value pairs, where keys are unique. See [Map] for details. Map also
|
||||||
|
are [Iterable]:
|
||||||
|
|
||||||
|
val m = Map( "foo" => 77, "bar" => "buzz" )
|
||||||
|
assertEquals( m["foo"], 77 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Please see [Map] reference for detailed description on using Maps.
|
||||||
|
|
||||||
|
|
||||||
# Flow control operators
|
# Flow control operators
|
||||||
|
|
||||||
## if-then-else
|
## if-then-else
|
||||||
@@ -552,6 +688,89 @@ Or, more neat:
|
|||||||
>>> just 3
|
>>> just 3
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## When
|
||||||
|
|
||||||
|
It is very much like the kotlin's:
|
||||||
|
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
in 'a'..'z', in 'A'..'Z' -> "letter"
|
||||||
|
in '0'..'9' -> "digit"
|
||||||
|
'$' -> "dollar"
|
||||||
|
"EUR" -> "crap"
|
||||||
|
in ['@', '#', '^'] -> "punctuation1"
|
||||||
|
in "*&.," -> "punctuation2"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("digit", type('3'))
|
||||||
|
assertEquals("dollar", type('$'))
|
||||||
|
assertEquals("crap", type("EUR"))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Notice, several conditions can be grouped with a comma.
|
||||||
|
Also, you can check the type too:
|
||||||
|
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
"42", 42 -> "answer to the great question"
|
||||||
|
is Real, is Int -> "number"
|
||||||
|
is String -> {
|
||||||
|
for( d in x ) {
|
||||||
|
if( d !in '0'..'9' )
|
||||||
|
break "unknown"
|
||||||
|
}
|
||||||
|
else "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("number", type(5))
|
||||||
|
assertEquals("number", type("153"))
|
||||||
|
assertEquals("number", type(π/2))
|
||||||
|
assertEquals("unknown", type("12%"))
|
||||||
|
assertEquals("answer to the great question", type(42))
|
||||||
|
assertEquals("answer to the great question", type("42"))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
### supported when conditions:
|
||||||
|
|
||||||
|
#### Contains:
|
||||||
|
|
||||||
|
You can thest that _when expression_ is _contained_, or not contained, in some object using `in container` and `!in container`. The container is any object that provides `contains` method, otherwise the runtime exception will be thrown.
|
||||||
|
|
||||||
|
Typical builtin types that are containers (e.g. support `conain`):
|
||||||
|
|
||||||
|
| class | notes |
|
||||||
|
|------------|--------------------------------------------|
|
||||||
|
| Collection | contains an element (1) |
|
||||||
|
| Array | faster maybe that Collection's |
|
||||||
|
| List | faster than Array's |
|
||||||
|
| String | character in string or substring in string |
|
||||||
|
| Range | object is included in the range (2) |
|
||||||
|
|
||||||
|
(1)
|
||||||
|
: Iterable is not the container as it can be infinite
|
||||||
|
|
||||||
|
(2)
|
||||||
|
: Depending on the inclusivity and open/closed range parameters. BE careful here: String range is allowed, but it is usually not what you expect of it:
|
||||||
|
|
||||||
|
assert( "more" in "a".."z") // string range ok
|
||||||
|
assert( 'x' !in "a".."z") // char in string range: probably error
|
||||||
|
assert( 'x' in 'a'..'z') // character range: ok
|
||||||
|
assert( "x" !in 'a'..'z') // string in character range: could be error
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
So we recommend not to mix characters and string ranges; use `ch in str` that works
|
||||||
|
as expected:
|
||||||
|
|
||||||
|
"foo" in "foobar"
|
||||||
|
>>> true
|
||||||
|
|
||||||
|
and also character inclusion:
|
||||||
|
|
||||||
|
'o' in "foobar"
|
||||||
|
>>> true
|
||||||
|
|
||||||
## while
|
## while
|
||||||
|
|
||||||
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
|
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
|
||||||
@@ -923,31 +1142,82 @@ Are the same as in string literals with little difference:
|
|||||||
|
|
||||||
## String details
|
## String details
|
||||||
|
|
||||||
Strings are much like Kotlin ones:
|
Strings are arrays of Unicode characters. It can be indexed, and indexing will
|
||||||
|
return a valid Unicode character at position. No utf hassle:
|
||||||
|
|
||||||
"Hello".length
|
"Парашют"[5]
|
||||||
|
>>> 'ю'
|
||||||
|
|
||||||
|
Its length is, of course, in characters:
|
||||||
|
|
||||||
|
"разум".length
|
||||||
>>> 5
|
>>> 5
|
||||||
|
|
||||||
And supports growing set of kotlin-borrowed operations, see below, for example:
|
And supports growing set of kotlin-borrowed operations, see below, for example:
|
||||||
|
|
||||||
assertEquals("Hell", "Hello".dropLast(1))
|
assertEquals("Hell", "Hello".dropLast(1))
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
To format a string use sprintf-style modifiers like:
|
||||||
|
|
||||||
|
val a = "hello"
|
||||||
|
val b = 11
|
||||||
|
assertEquals( "hello:11", "%s:%d"(a, b) )
|
||||||
|
assertEquals( " hello: 11", "%6s:%6d"(a, b) )
|
||||||
|
assertEquals( "hello :11 ", "%-6s:%-6d"(a, b) )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
List of format specifiers closely resembles C sprintf() one. See [format specifiers](https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary), this is doe using [mp_stools kotlin multiplatform library](https://github.com/sergeych/mp_stools). Currently supported Lyng types are `String`, `Int`, `Real`, `Bool`, the rest are displayed using their `toString()` representation.
|
||||||
|
|
||||||
|
This list will be extended.
|
||||||
|
|
||||||
|
To get the substring use:
|
||||||
|
|
||||||
|
assertEquals("pult", "catapult".takeLast(4))
|
||||||
|
assertEquals("cat", "catapult".take(3))
|
||||||
|
assertEquals("cat", "catapult".dropLast(5))
|
||||||
|
assertEquals("pult", "catapult".drop(4))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
And to get a portion you can slice it with range as the index:
|
||||||
|
|
||||||
|
assertEquals( "tap", "catapult"[ 2 .. 4 ])
|
||||||
|
assertEquals( "tap", "catapult"[ 2 ..< 5 ])
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Open-ended ranges could be used to get start and end too:
|
||||||
|
|
||||||
|
assertEquals( "cat", "catapult"[ ..< 3 ])
|
||||||
|
assertEquals( "cat", "catapult"[ .. 2 ])
|
||||||
|
assertEquals( "pult", "catapult"[ 4.. ])
|
||||||
|
>>> void
|
||||||
|
|
||||||
### String operations
|
### String operations
|
||||||
|
|
||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
||||||
|
|
||||||
Typical set of String functions includes:
|
Typical set of String functions includes:
|
||||||
|
|
||||||
| fun/prop | description / notes |
|
| fun/prop | description / notes |
|
||||||
|------------------|------------------------------------------------------------|
|
|--------------------|------------------------------------------------------------|
|
||||||
| lower() | change case to unicode upper |
|
| lower() | change case to unicode upper |
|
||||||
| upper() | change case to unicode lower |
|
| upper() | change case to unicode lower |
|
||||||
| startsWith(prefix) | true if starts with a prefix |
|
| startsWith(prefix) | true if starts with a prefix |
|
||||||
| take(n) | get a new string from up to n first characters |
|
| endsWith(prefix) | true if ends with a prefix |
|
||||||
| takeLast(n) | get a new string from up to n last characters |
|
| take(n) | get a new string from up to n first characters |
|
||||||
| drop(n) | get a new string dropping n first chars, or empty string |
|
| takeLast(n) | get a new string from up to n last characters |
|
||||||
| dropLast(n) | get a new string dropping n last chars, or empty string |
|
| drop(n) | get a new string dropping n first chars, or empty string |
|
||||||
| size | size in characters like `length` because String is [Array] |
|
| dropLast(n) | get a new string dropping n last chars, or empty string |
|
||||||
|
| size | size in characters like `length` because String is [Array] |
|
||||||
|
| (args...) | sprintf-like formatting, see [string formatting] |
|
||||||
|
| [index] | character at index |
|
||||||
|
| [Range] | substring at range |
|
||||||
|
| s1 + s2 | concatenation |
|
||||||
|
| s1 += s2 | self-modifying concatenation |
|
||||||
|
| toReal() | attempts to parse string as a Real value |
|
||||||
|
| toInt() | parse string to Int value |
|
||||||
|
| | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -983,5 +1253,7 @@ See [math functions](math.md). Other general purpose functions are:
|
|||||||
[Iterator]: Iterator.md
|
[Iterator]: Iterator.md
|
||||||
[Real]: Real.md
|
[Real]: Real.md
|
||||||
[Range]: Range.md
|
[Range]: Range.md
|
||||||
|
[String]: String.md
|
||||||
|
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
||||||
|
[Set]: Set.md
|
||||||
|
[Map]: Map.md
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
count = 0
|
|
||||||
for n1 in range(10):
|
|
||||||
for n2 in range(10):
|
|
||||||
for n3 in range(10):
|
|
||||||
for n4 in range(10):
|
|
||||||
for n5 in range(10):
|
|
||||||
for n6 in range(10):
|
|
||||||
if n1 + n2 + n3 == n4 + n5 + n6:
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
print(count)
|
|
||||||
@@ -1,428 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.bintools.encodeToHex
|
|
||||||
import net.sergeych.synctools.ProtectedOp
|
|
||||||
import net.sergeych.synctools.withLock
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record to store object with access rules, e.g. [isMutable] and access level [visibility].
|
|
||||||
*/
|
|
||||||
data class ObjRecord(
|
|
||||||
var value: Obj,
|
|
||||||
val isMutable: Boolean,
|
|
||||||
val visibility: Visibility = Visibility.Public
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When we need read-write access to an object in some abstract storage, we need Accessor,
|
|
||||||
* as in-site assigning is not always sufficient, in general case we need to replace the object
|
|
||||||
* in the storage.
|
|
||||||
*
|
|
||||||
* Note that assigning new value is more complex than just replacing the object, see how assignment
|
|
||||||
* operator is implemented in [Compiler.allOps].
|
|
||||||
*/
|
|
||||||
data class Accessor(
|
|
||||||
val getter: suspend (Context) -> ObjRecord,
|
|
||||||
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Simplified constructor for immutable stores.
|
|
||||||
*/
|
|
||||||
constructor(getter: suspend (Context) -> ObjRecord) : this(getter, null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the setter or throw.
|
|
||||||
*/
|
|
||||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
|
||||||
}
|
|
||||||
|
|
||||||
open class Obj {
|
|
||||||
var isFrozen: Boolean = false
|
|
||||||
|
|
||||||
private val monitor = Mutex()
|
|
||||||
|
|
||||||
// private val memberMutex = Mutex()
|
|
||||||
internal var parentInstances: MutableList<Obj> = mutableListOf()
|
|
||||||
|
|
||||||
private val opInstances = ProtectedOp()
|
|
||||||
|
|
||||||
open fun inspect(): String = toString()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such.
|
|
||||||
* When initializing a var with it, by value objects must be copied. By-reference ones aren't.
|
|
||||||
*
|
|
||||||
* Almost all objects are by-reference.
|
|
||||||
*/
|
|
||||||
open fun byValueCopy(): Obj = this
|
|
||||||
|
|
||||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
|
||||||
|
|
||||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
|
||||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
|
||||||
|
|
||||||
suspend inline fun <reified T : Obj> callMethod(
|
|
||||||
context: Context,
|
|
||||||
name: String,
|
|
||||||
args: Arguments = Arguments.EMPTY
|
|
||||||
): T = invokeInstanceMethod(context, name, args) as T
|
|
||||||
|
|
||||||
open suspend fun invokeInstanceMethod(
|
|
||||||
context: Context,
|
|
||||||
name: String,
|
|
||||||
args: Arguments = Arguments.EMPTY
|
|
||||||
): Obj =
|
|
||||||
// note that getInstanceMember traverses the hierarchy
|
|
||||||
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
|
||||||
|
|
||||||
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
|
|
||||||
|
|
||||||
// methods that to override
|
|
||||||
|
|
||||||
open suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open val asStr: ObjString by lazy {
|
|
||||||
if (this is ObjString) this else ObjString(this.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class of the object: definition of member functions (top-level), etc.
|
|
||||||
* Note that using lazy allows to avoid endless recursion here
|
|
||||||
*/
|
|
||||||
open val objClass: ObjClass by lazy {
|
|
||||||
ObjClass("Obj").apply {
|
|
||||||
addFn("toString") {
|
|
||||||
thisObj.asStr
|
|
||||||
}
|
|
||||||
addFn("contains") {
|
|
||||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun minus(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun mul(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun div(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun mod(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun logicalNot(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun logicalAnd(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun logicalOr(context: Context, other: Obj): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun assign(context: Context, other: Obj): Obj? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a += b
|
|
||||||
* if( the operation is not defined, it returns null and the compiler would try
|
|
||||||
* to generate it as 'this = this + other', reassigning its variable
|
|
||||||
*/
|
|
||||||
open suspend fun plusAssign(context: Context, other: Obj): Obj? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `-=` operations, see [plusAssign]
|
|
||||||
*/
|
|
||||||
open suspend fun minusAssign(context: Context, other: Obj): Obj? = null
|
|
||||||
open suspend fun mulAssign(context: Context, other: Obj): Obj? = null
|
|
||||||
open suspend fun divAssign(context: Context, other: Obj): Obj? = null
|
|
||||||
open suspend fun modAssign(context: Context, other: Obj): Obj? = null
|
|
||||||
|
|
||||||
open suspend fun getAndIncrement(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun incrementAndGet(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun decrementAndGet(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun getAndDecrement(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun willMutate(context: Context) {
|
|
||||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
|
||||||
|
|
||||||
open suspend fun readField(context: Context, name: String): ObjRecord {
|
|
||||||
// could be property or class field:
|
|
||||||
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
|
||||||
return when (val value = obj.value) {
|
|
||||||
is Statement -> {
|
|
||||||
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
|
||||||
}
|
|
||||||
// could be writable property naturally
|
|
||||||
// null -> ObjNull.asReadonly
|
|
||||||
else -> obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
|
||||||
willMutate(context)
|
|
||||||
val field = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
|
||||||
if (field.isMutable) field.value = newValue else context.raiseError("can't assign to read-only field: $name")
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun getAt(context: Context, index: Int): Obj {
|
|
||||||
context.raiseNotImplemented("indexing")
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
|
||||||
context.raiseNotImplemented("indexing")
|
|
||||||
}
|
|
||||||
|
|
||||||
open suspend fun callOn(context: Context): Obj {
|
|
||||||
context.raiseNotImplemented()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
|
|
||||||
callOn(context.copy(context.pos, args = args, newThisObj = thisObj))
|
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj, vararg args: Obj): Obj =
|
|
||||||
callOn(
|
|
||||||
context.copy(
|
|
||||||
context.pos,
|
|
||||||
args = Arguments(args.toList()),
|
|
||||||
newThisObj = thisObj
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj): Obj =
|
|
||||||
callOn(
|
|
||||||
context.copy(
|
|
||||||
context.pos,
|
|
||||||
args = Arguments.EMPTY,
|
|
||||||
newThisObj = thisObj
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
|
||||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
|
||||||
|
|
||||||
|
|
||||||
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
|
||||||
val asMutable: ObjRecord by lazy { ObjRecord(this, true) }
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
inline fun <reified T> from(obj: T): Obj {
|
|
||||||
return when (obj) {
|
|
||||||
is Obj -> obj
|
|
||||||
is Double -> ObjReal(obj)
|
|
||||||
is Float -> ObjReal(obj.toDouble())
|
|
||||||
is Int -> ObjInt(obj.toLong())
|
|
||||||
is Long -> ObjInt(obj)
|
|
||||||
is String -> ObjString(obj)
|
|
||||||
is CharSequence -> ObjString(obj.toString())
|
|
||||||
is Boolean -> ObjBool(obj)
|
|
||||||
Unit -> ObjVoid
|
|
||||||
null -> ObjNull
|
|
||||||
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
inline fun <reified T> T.toObj(): Obj = Obj.from(this)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("void")
|
|
||||||
object ObjVoid : Obj() {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return other is ObjVoid || other is Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
return if (other === this) 0 else -1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "void"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("null")
|
|
||||||
object ObjNull : Obj() {
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
return if (other === this) 0 else -1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return other is ObjNull || other == null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "null"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Numeric {
|
|
||||||
val longValue: Long
|
|
||||||
val doubleValue: Double
|
|
||||||
val toObjInt: ObjInt
|
|
||||||
val toObjReal: ObjReal
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Obj.toDouble(): Double =
|
|
||||||
(this as? Numeric)?.doubleValue
|
|
||||||
?: (this as? ObjString)?.value?.toDouble()
|
|
||||||
?: throw IllegalArgumentException("cannot convert to double $this")
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun Obj.toLong(): Long =
|
|
||||||
when (this) {
|
|
||||||
is Numeric -> longValue
|
|
||||||
is ObjString -> value.toLong()
|
|
||||||
is ObjChar -> value.code.toLong()
|
|
||||||
else -> throw IllegalArgumentException("cannot convert to double $this")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Obj.toInt(): Int = toLong().toInt()
|
|
||||||
|
|
||||||
fun Obj.toBool(): Boolean =
|
|
||||||
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
|
||||||
|
|
||||||
|
|
||||||
data class ObjNamespace(val name: String) : Obj() {
|
|
||||||
override val objClass by lazy { ObjClass(name) }
|
|
||||||
|
|
||||||
override fun inspect(): String = "Ns[$name]"
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "package $name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
|
||||||
constructor(name: String,context: Context, message: String) : this(getOrCreateExceptionClass(name), context, message)
|
|
||||||
constructor(context: Context, message: String) : this(Root, context, message)
|
|
||||||
|
|
||||||
fun raise(): Nothing {
|
|
||||||
throw ExecutionError(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val objClass: ObjClass = exceptionClass
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "ObjException:${objClass.className}:${context.pos}@${hashCode().encodeToHex()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
class ExceptionClass(val name: String,vararg parents: ObjClass) : ObjClass(name, *parents) {
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
|
||||||
val message = context.args.getOrNull(0)?.toString() ?: name
|
|
||||||
return ObjException(this, context, message)
|
|
||||||
}
|
|
||||||
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
|
||||||
}
|
|
||||||
val Root = ExceptionClass("Throwable").apply {
|
|
||||||
addConst("message", statement {
|
|
||||||
(thisObj as ObjException).message.toObj()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private val op = ProtectedOp()
|
|
||||||
private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
|
|
||||||
return op.withLock {
|
|
||||||
existingErrorClasses.getOrPut(name) {
|
|
||||||
ExceptionClass(name, Root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get [ObjClass] for error class by name if exists.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
|
||||||
fun getErrorClass(name: String): ObjClass? = op.withLock {
|
|
||||||
existingErrorClasses[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addExceptionsToContext(context: Context) {
|
|
||||||
context.addConst("Exception", Root)
|
|
||||||
existingErrorClasses["Exception"] = Root
|
|
||||||
for (name in listOf(
|
|
||||||
"NullPointerException",
|
|
||||||
"AssertionFailedException",
|
|
||||||
"ClassCastException",
|
|
||||||
"IndexOutOfBoundsException",
|
|
||||||
"IllegalArgumentException",
|
|
||||||
"IllegalAssignmentException",
|
|
||||||
"SymbolNotDefinedException",
|
|
||||||
"IterationEndException",
|
|
||||||
"AccessException",
|
|
||||||
"UnknownException",
|
|
||||||
)) {
|
|
||||||
context.addConst(name, getOrCreateExceptionClass(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ObjNullPointerException(context: Context) : ObjException("NullPointerException", context, "object is null")
|
|
||||||
|
|
||||||
class ObjAssertionFailedException(context: Context, message: String) :
|
|
||||||
ObjException("AssertionFailedException", context, message)
|
|
||||||
|
|
||||||
class ObjClassCastException(context: Context, message: String) : ObjException("ClassCastException", context, message)
|
|
||||||
class ObjIndexOutOfBoundsException(context: Context, message: String = "index out of bounds") :
|
|
||||||
ObjException("IndexOutOfBoundsException", context, message)
|
|
||||||
|
|
||||||
class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") :
|
|
||||||
ObjException("IllegalArgumentException", context, message)
|
|
||||||
|
|
||||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
|
||||||
ObjException("IllegalAssignmentException", context, message)
|
|
||||||
|
|
||||||
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
|
|
||||||
ObjException("SymbolNotDefinedException", context, message)
|
|
||||||
|
|
||||||
class ObjIterationFinishedException(context: Context) :
|
|
||||||
ObjException("IterationEndException", context, "iteration finished")
|
|
||||||
|
|
||||||
class ObjAccessException(context: Context, message: String = "access not allowed error") :
|
|
||||||
ObjException("AccessException", context, message)
|
|
||||||
|
|
||||||
class ObjUnknownException(context: Context, message: String = "access not allowed error") :
|
|
||||||
ObjException("UnknownException", context, message)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
data class ObjBool(val value: Boolean) : Obj() {
|
|
||||||
override val asStr by lazy { ObjString(value.toString()) }
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
if (other !is ObjBool) return -2
|
|
||||||
return value.compareTo(other.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
|
||||||
|
|
||||||
override val objClass: ObjClass = type
|
|
||||||
|
|
||||||
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
|
||||||
|
|
||||||
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
|
||||||
|
|
||||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val type = ObjClass("Bool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val ObjTrue = ObjBool(true)
|
|
||||||
val ObjFalse = ObjBool(false)
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
val ObjClassType by lazy { ObjClass("Class") }
|
|
||||||
|
|
||||||
open class ObjClass(
|
|
||||||
val className: String,
|
|
||||||
vararg val parents: ObjClass,
|
|
||||||
) : Obj() {
|
|
||||||
|
|
||||||
var instanceConstructor: Statement? = null
|
|
||||||
|
|
||||||
val allParentsSet: Set<ObjClass> = parents.flatMap {
|
|
||||||
listOf(it) + it.allParentsSet
|
|
||||||
}.toSet()
|
|
||||||
|
|
||||||
override val objClass: ObjClass by lazy { ObjClassType }
|
|
||||||
|
|
||||||
// members: fields most often
|
|
||||||
private val members = mutableMapOf<String, ObjRecord>()
|
|
||||||
|
|
||||||
override fun toString(): String = className
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
|
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
|
||||||
val instance = ObjInstance(this)
|
|
||||||
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
|
|
||||||
if (instanceConstructor != null) {
|
|
||||||
instanceConstructor!!.execute(instance.instanceContext)
|
|
||||||
}
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
fun defaultInstance(): Obj = object : Obj() {
|
|
||||||
override val objClass: ObjClass = this@ObjClass
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createField(
|
|
||||||
name: String,
|
|
||||||
initialValue: Obj,
|
|
||||||
isMutable: Boolean = false,
|
|
||||||
visibility: Visibility = Visibility.Public,
|
|
||||||
pos: Pos = Pos.builtIn
|
|
||||||
) {
|
|
||||||
if (name in members || allParentsSet.any { name in it.members })
|
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
|
||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.() -> Obj) {
|
|
||||||
createField(name, statement { code() }, isOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
|
||||||
*/
|
|
||||||
fun getInstanceMemberOrNull(name: String): ObjRecord? {
|
|
||||||
members[name]?.let { return it }
|
|
||||||
allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
|
||||||
getInstanceMemberOrNull(name)
|
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
|
|
||||||
*/
|
|
||||||
val ObjIterable by lazy {
|
|
||||||
ObjClass("Iterable").apply {
|
|
||||||
|
|
||||||
addFn("toList") {
|
|
||||||
val result = mutableListOf<Obj>()
|
|
||||||
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
|
|
||||||
|
|
||||||
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
|
|
||||||
result += iterator.invokeInstanceMethod(this, "next")
|
|
||||||
|
|
||||||
|
|
||||||
// val next = iterator.getMemberOrNull("next")!!
|
|
||||||
// val hasNext = iterator.getMemberOrNull("hasNext")!!
|
|
||||||
// while( hasNext.invoke(this, iterator).toBool() )
|
|
||||||
// result += next.invoke(this, iterator)
|
|
||||||
ObjList(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection is an iterator with `size`]
|
|
||||||
*/
|
|
||||||
val ObjCollection by lazy {
|
|
||||||
val i: ObjClass = ObjIterable
|
|
||||||
ObjClass("Collection", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
val ObjIterator by lazy { ObjClass("Iterator") }
|
|
||||||
|
|
||||||
class ObjArrayIterator(val array: Obj) : Obj() {
|
|
||||||
|
|
||||||
override val objClass: ObjClass by lazy { type }
|
|
||||||
|
|
||||||
private var nextIndex = 0
|
|
||||||
private var lastIndex = 0
|
|
||||||
|
|
||||||
suspend fun init(context: Context) {
|
|
||||||
nextIndex = 0
|
|
||||||
lastIndex = array.invokeInstanceMethod(context, "size").toInt()
|
|
||||||
ObjVoid
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val type by lazy {
|
|
||||||
ObjClass("ArrayIterator", ObjIterator).apply {
|
|
||||||
addFn("next") {
|
|
||||||
val self = thisAs<ObjArrayIterator>()
|
|
||||||
if (self.nextIndex < self.lastIndex) {
|
|
||||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
|
||||||
} else raiseError(ObjIterationFinishedException(this))
|
|
||||||
}
|
|
||||||
addFn("hasNext") {
|
|
||||||
val self = thisAs<ObjArrayIterator>()
|
|
||||||
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val ObjArray by lazy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array abstract class is a [ObjCollection] with `getAt` method.
|
|
||||||
*/
|
|
||||||
ObjClass("Array", ObjCollection).apply {
|
|
||||||
// we can create iterators using size/getat:
|
|
||||||
|
|
||||||
addFn("iterator") {
|
|
||||||
ObjArrayIterator(thisObj).also { it.init(this) }
|
|
||||||
}
|
|
||||||
addFn("isample") { "ok".toObj() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|
||||||
|
|
||||||
internal lateinit var instanceContext: Context
|
|
||||||
|
|
||||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
|
||||||
return instanceContext[name]?.let {
|
|
||||||
if (it.visibility.isPublic)
|
|
||||||
it
|
|
||||||
else
|
|
||||||
context.raiseError(ObjAccessException(context, "can't access non-public field $name"))
|
|
||||||
}
|
|
||||||
?: super.readField(context, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
|
||||||
instanceContext[name]?.let { f ->
|
|
||||||
if (!f.visibility.isPublic)
|
|
||||||
ObjIllegalAssignmentException(context, "can't assign to non-public field $name")
|
|
||||||
if (!f.isMutable) ObjIllegalAssignmentException(context, "can't reassign val $name").raise()
|
|
||||||
if (f.value.assign(context, newValue) == null)
|
|
||||||
f.value = newValue
|
|
||||||
} ?: super.writeField(context, name, newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
|
||||||
instanceContext[name]?.let {
|
|
||||||
if (it.visibility.isPublic)
|
|
||||||
it.value.invoke(context, this, args)
|
|
||||||
else
|
|
||||||
context.raiseError(ObjAccessException(context, "can't invoke non-public method $name"))
|
|
||||||
}
|
|
||||||
?: super.invokeInstanceMethod(context, name, args)
|
|
||||||
|
|
||||||
private val publicFields: Map<String, ObjRecord>
|
|
||||||
get() = instanceContext.objects.filter { it.value.visibility.isPublic }
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
|
||||||
return "${objClass.className}($fields)"
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
if (other !is ObjInstance) return -1
|
|
||||||
if (other.objClass != objClass) return -1
|
|
||||||
for (f in publicFields) {
|
|
||||||
val a = f.value.value
|
|
||||||
val b = other.instanceContext[f.key]!!.value
|
|
||||||
val d = a.compareTo(context, b)
|
|
||||||
if (d != 0) return d
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
for (p in objClass.parents)
|
|
||||||
parentInstances.add(p.defaultInstance())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "[${
|
|
||||||
list.joinToString(separator = ", ") { it.inspect() }
|
|
||||||
}]"
|
|
||||||
|
|
||||||
fun normalize(context: Context, index: Int, allowisEndInclusive: Boolean = false): Int {
|
|
||||||
val i = if (index < 0) list.size + index else index
|
|
||||||
if (allowisEndInclusive && i == list.size) return i
|
|
||||||
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
|
||||||
val i = normalize(context, index)
|
|
||||||
return list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
|
||||||
val i = normalize(context, index)
|
|
||||||
list[i] = newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
if (other !is ObjList) return -2
|
|
||||||
val mySize = list.size
|
|
||||||
val otherSize = other.list.size
|
|
||||||
val commonSize = minOf(mySize, otherSize)
|
|
||||||
for (i in 0..<commonSize) {
|
|
||||||
if (list[i].compareTo(context, other.list[i]) != 0) {
|
|
||||||
return list[i].compareTo(context, other.list[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// equal so far, longer is greater:
|
|
||||||
return when {
|
|
||||||
mySize < otherSize -> -1
|
|
||||||
mySize > otherSize -> 1
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
|
||||||
when {
|
|
||||||
other is ObjList ->
|
|
||||||
ObjList((list + other.list).toMutableList())
|
|
||||||
|
|
||||||
other.isInstanceOf(ObjIterable) -> {
|
|
||||||
val l = other.callMethod<ObjList>(context, "toList")
|
|
||||||
ObjList((list + l.list).toMutableList())
|
|
||||||
}
|
|
||||||
|
|
||||||
else ->
|
|
||||||
context.raiseError("'+': can't concatenate $this with $other")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
|
||||||
// optimization
|
|
||||||
if (other is ObjList) {
|
|
||||||
list += other.list
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
if (other.isInstanceOf(ObjIterable)) {
|
|
||||||
val otherList = other.invokeInstanceMethod(context, "toList") as ObjList
|
|
||||||
list += otherList.list
|
|
||||||
} else
|
|
||||||
list += other
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override val objClass: ObjClass
|
|
||||||
get() = type
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val type = ObjClass("List", ObjArray).apply {
|
|
||||||
|
|
||||||
createField("size",
|
|
||||||
statement {
|
|
||||||
(thisObj as ObjList).list.size.toObj()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
addFn("getAt") {
|
|
||||||
requireExactCount(1)
|
|
||||||
thisAs<ObjList>().getAt(this, requiredArg<ObjInt>(0).value.toInt())
|
|
||||||
}
|
|
||||||
addFn("putAt") {
|
|
||||||
requireExactCount(2)
|
|
||||||
val newValue = args[1]
|
|
||||||
thisAs<ObjList>().putAt(this, requiredArg<ObjInt>(0).value.toInt(), newValue)
|
|
||||||
newValue
|
|
||||||
}
|
|
||||||
createField("add",
|
|
||||||
statement {
|
|
||||||
val l = thisAs<ObjList>().list
|
|
||||||
for (a in args) l.add(a)
|
|
||||||
ObjVoid
|
|
||||||
}
|
|
||||||
)
|
|
||||||
createField("addAt",
|
|
||||||
statement {
|
|
||||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
|
||||||
val l = thisAs<ObjList>()
|
|
||||||
var index = l.normalize(
|
|
||||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
|
||||||
allowisEndInclusive = true
|
|
||||||
)
|
|
||||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
|
||||||
ObjVoid
|
|
||||||
}
|
|
||||||
)
|
|
||||||
addFn("removeAt") {
|
|
||||||
val self = thisAs<ObjList>()
|
|
||||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
|
||||||
if (args.size == 2) {
|
|
||||||
val end = requireOnlyArg<ObjInt>().value.toInt()
|
|
||||||
self.list.subList(start, self.normalize(this, end)).clear()
|
|
||||||
} else
|
|
||||||
self.list.removeAt(start)
|
|
||||||
self
|
|
||||||
}
|
|
||||||
addFn("removeRangeInclusive") {
|
|
||||||
val self = thisAs<ObjList>()
|
|
||||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
|
||||||
val end = self.normalize(this, requiredArg<ObjInt>(1).value.toInt()) + 1
|
|
||||||
self.list.subList(start, end).clear()
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("string")
|
|
||||||
data class ObjString(val value: String) : Obj() {
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
if (other !is ObjString) return -2
|
|
||||||
return this.value.compareTo(other.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = value
|
|
||||||
|
|
||||||
override val asStr: ObjString by lazy { this }
|
|
||||||
|
|
||||||
override fun inspect(): String {
|
|
||||||
return "\"$value\""
|
|
||||||
}
|
|
||||||
|
|
||||||
override val objClass: ObjClass
|
|
||||||
get() = type
|
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
|
||||||
return ObjString(value + other.asStr.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
|
||||||
return ObjChar(value[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val type = ObjClass("String").apply {
|
|
||||||
addConst("startsWith",
|
|
||||||
statement {
|
|
||||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
|
||||||
})
|
|
||||||
addConst("length",
|
|
||||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
|
||||||
)
|
|
||||||
addFn("takeLast") {
|
|
||||||
thisAs<ObjString>().value.takeLast(
|
|
||||||
requiredArg<ObjInt>(0).toInt()
|
|
||||||
).let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("take") {
|
|
||||||
thisAs<ObjString>().value.take(
|
|
||||||
requiredArg<ObjInt>(0).toInt()
|
|
||||||
).let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("drop") {
|
|
||||||
thisAs<ObjString>().value.drop(
|
|
||||||
requiredArg<ObjInt>(0).toInt()
|
|
||||||
).let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("dropLast") {
|
|
||||||
thisAs<ObjString>().value.dropLast(
|
|
||||||
requiredArg<ObjInt>(0).toInt()
|
|
||||||
).let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("lower") {
|
|
||||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("upper") {
|
|
||||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
|
||||||
}
|
|
||||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Symbols(
|
|
||||||
unitType: UnitType,
|
|
||||||
val name: String,
|
|
||||||
val x: TypeDecl
|
|
||||||
) {
|
|
||||||
enum class UnitType {
|
|
||||||
Module, Function, Lambda
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package net.sergeych.lyng
|
|
||||||
|
|
||||||
sealed class TypeDecl {
|
|
||||||
// ??
|
|
||||||
data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
|
||||||
object Obj : TypeDecl()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To use in the compiler, we need symbol information when:
|
|
||||||
|
|
||||||
- declaring a class: the only way to export its public/protected symbols is to know it in compiler time
|
|
||||||
- importing a module: actually, we cam try to do it in a more efficient way.
|
|
||||||
|
|
||||||
Importing module:
|
|
||||||
|
|
||||||
The moudule is efficiently a statement, that initializes it with all its symbols modifying some context.
|
|
||||||
|
|
||||||
The thing is, we need only
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -29,11 +29,11 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":library"))
|
implementation(kotlin("stdlib-common"))
|
||||||
|
implementation(project(":lynglib"))
|
||||||
implementation(libs.okio)
|
implementation(libs.okio)
|
||||||
|
|
||||||
implementation(libs.clikt)
|
implementation(libs.clikt)
|
||||||
|
implementation(kotlin("stdlib-common"))
|
||||||
// optional support for rendering markdown in help messages
|
// optional support for rendering markdown in help messages
|
||||||
// implementation(libs.clikt.markdown)
|
// implementation(libs.clikt.markdown)
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,15 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("test-common"))
|
implementation(kotlin("test-common"))
|
||||||
implementation(kotlin("test-annotations-common"))
|
implementation(kotlin("test-annotations-common"))
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.okio.fakefilesystem)
|
implementation(libs.okio.fakefilesystem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// val nativeMain by getting {
|
||||||
|
// dependencies {
|
||||||
|
// implementation(kotlin("stdlib-common"))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
val linuxX64Main by getting {
|
val linuxX64Main by getting {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,28 @@ import okio.use
|
|||||||
|
|
||||||
expect fun exit(code: Int)
|
expect fun exit(code: Int)
|
||||||
|
|
||||||
val baseContext = Context().apply {
|
expect class ShellCommandExecutor {
|
||||||
|
fun executeCommand(command: String): CommandResult
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(): ShellCommandExecutor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CommandResult(
|
||||||
|
val exitCode: Int,
|
||||||
|
val output: String,
|
||||||
|
val error: String
|
||||||
|
)
|
||||||
|
|
||||||
|
val baseScope = Scope().apply {
|
||||||
addFn("exit") {
|
addFn("exit") {
|
||||||
exit(requireOnlyArg<ObjInt>().toInt())
|
exit(requireOnlyArg<ObjInt>().toInt())
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
// ObjString.type.addFn("shell") {
|
||||||
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||||
@@ -57,7 +74,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
val objargs = mutableListOf<String>()
|
val objargs = mutableListOf<String>()
|
||||||
script?.let { objargs += it }
|
script?.let { objargs += it }
|
||||||
objargs += args
|
objargs += args
|
||||||
baseContext.addConst(
|
baseScope.addConst(
|
||||||
"ARGV", ObjList(
|
"ARGV", ObjList(
|
||||||
objargs.map { ObjString(it) }.toMutableList()
|
objargs.map { ObjString(it) }.toMutableList()
|
||||||
)
|
)
|
||||||
@@ -65,7 +82,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
launcher {
|
launcher {
|
||||||
// there is no script name, it is a first argument instead:
|
// there is no script name, it is a first argument instead:
|
||||||
processErrors {
|
processErrors {
|
||||||
baseContext.eval(execute!!)
|
baseScope.eval(execute!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +98,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
)
|
)
|
||||||
echoFormattedHelp()
|
echoFormattedHelp()
|
||||||
} else {
|
} else {
|
||||||
baseContext.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
launcher { executeFile(script!!) }
|
launcher { executeFile(script!!) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,13 +107,18 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun executeFile(fileName: String) {
|
suspend fun executeFile(fileName: String) {
|
||||||
val text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
||||||
fileSource.buffer().use { bs ->
|
fileSource.buffer().use { bs ->
|
||||||
bs.readUtf8()
|
bs.readUtf8()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if( text.startsWith("#!") ) {
|
||||||
|
// skip shebang
|
||||||
|
val pos = text.indexOf('\n')
|
||||||
|
text = text.substring(pos + 1)
|
||||||
|
}
|
||||||
processErrors {
|
processErrors {
|
||||||
Compiler().compile(Source(fileName, text)).execute(baseContext)
|
Compiler.compile(Source(fileName, text)).execute(baseScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||||
|
|
||||||
|
package net.sergeych
|
||||||
|
|
||||||
|
// Alternative implementation for native targets
|
||||||
|
actual class ShellCommandExecutor() {
|
||||||
|
actual fun executeCommand(command: String): CommandResult {
|
||||||
|
val process = ProcessBuilder("/bin/sh", "-c", command).start()
|
||||||
|
val exitCode = process.waitFor()
|
||||||
|
val output = process.inputStream.bufferedReader().readText()
|
||||||
|
val error = process.errorStream.bufferedReader().readText()
|
||||||
|
|
||||||
|
return CommandResult(
|
||||||
|
exitCode = exitCode,
|
||||||
|
output = output.trim(),
|
||||||
|
error = error.trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual fun create(): ShellCommandExecutor = ShellCommandExecutor()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,48 @@
|
|||||||
|
@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class, ExperimentalForeignApi::class)
|
||||||
|
|
||||||
package net.sergeych
|
package net.sergeych
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import platform.posix.fgets
|
||||||
|
import platform.posix.pclose
|
||||||
|
import platform.posix.popen
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
actual class ShellCommandExecutor() {
|
||||||
|
actual fun executeCommand(command: String): CommandResult {
|
||||||
|
val outputBuilder = StringBuilder()
|
||||||
|
val errorBuilder = StringBuilder()
|
||||||
|
|
||||||
|
val fp = popen(command, "r") ?: return CommandResult(
|
||||||
|
exitCode = -1,
|
||||||
|
output = "",
|
||||||
|
error = "Failed to execute command"
|
||||||
|
)
|
||||||
|
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
while (true) {
|
||||||
|
val bytesRead = buffer.usePinned { pinned ->
|
||||||
|
fgets(pinned.addressOf(0), buffer.size.convert(), fp)
|
||||||
|
}
|
||||||
|
if (bytesRead == null) break
|
||||||
|
outputBuilder.append(bytesRead.toKString())
|
||||||
|
}
|
||||||
|
|
||||||
|
val status = pclose(fp)
|
||||||
|
val exitCode = if (status == 0) 0 else 1
|
||||||
|
|
||||||
|
return CommandResult(
|
||||||
|
exitCode = exitCode,
|
||||||
|
output = outputBuilder.toString().trim(),
|
||||||
|
error = errorBuilder.toString().trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual fun create(): ShellCommandExecutor = ShellCommandExecutor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual fun exit(code: Int) {
|
actual fun exit(code: Int) {
|
||||||
exitProcess(code)
|
exitProcess(code)
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
|
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
|
||||||
import com.vanniktech.maven.publish.SonatypeHost
|
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
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 = "0.5.1-SNAPSHOT"
|
version = "0.7.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
@@ -20,9 +19,10 @@ buildscript {
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.androidLibrary)
|
alias(libs.plugins.androidLibrary)
|
||||||
alias(libs.plugins.vanniktech.mavenPublish)
|
// alias(libs.plugins.vanniktech.mavenPublish)
|
||||||
kotlin("plugin.serialization") version "2.1.20"
|
kotlin("plugin.serialization") version "2.1.20"
|
||||||
id("com.codingfeline.buildkonfig") version "0.17.1"
|
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||||
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
buildkonfig {
|
buildkonfig {
|
||||||
@@ -62,7 +62,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
languageSettings.optIn("kotlin.contracts.ExperimentalContracts::class")
|
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
|
||||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||||
}
|
}
|
||||||
@@ -101,73 +101,54 @@ dependencies {
|
|||||||
implementation(libs.firebase.crashlytics.buildtools)
|
implementation(libs.firebase.crashlytics.buildtools)
|
||||||
}
|
}
|
||||||
|
|
||||||
mavenPublishing {
|
publishing {
|
||||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
val mavenToken by lazy {
|
||||||
|
File("${System.getProperty("user.home")}/.gitea_token").readText()
|
||||||
signAllPublications()
|
}
|
||||||
|
repositories {
|
||||||
coordinates(group.toString(), "library", version.toString())
|
maven {
|
||||||
|
credentials(HttpHeaderCredentials::class) {
|
||||||
pom {
|
name = "Authorization"
|
||||||
name = "Lyng language"
|
value = mavenToken
|
||||||
description = "Kotlin-bound scripting loanguage"
|
|
||||||
inceptionYear = "2025"
|
|
||||||
// url = "https://sergeych.net"
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name = "XXX"
|
|
||||||
url = "YYY"
|
|
||||||
distribution = "ZZZ"
|
|
||||||
}
|
}
|
||||||
}
|
url = uri("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
developers {
|
authentication {
|
||||||
developer {
|
create("Authorization", HttpHeaderAuthentication::class)
|
||||||
id = "XXX"
|
|
||||||
name = "YYY"
|
|
||||||
url = "ZZZ"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scm {
|
|
||||||
url = "XXX"
|
|
||||||
connection = "YYY"
|
|
||||||
developerConnection = "ZZZ"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//mavenPublishing {
|
||||||
|
// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
||||||
//
|
//
|
||||||
//val projectVersion by project.extra(provider {
|
// signAllPublications()
|
||||||
// // Compute value lazily
|
|
||||||
// (version as String)
|
|
||||||
//})
|
|
||||||
//
|
//
|
||||||
//val generateBuildConfig by tasks.registering {
|
// coordinates(group.toString(), "library", version.toString())
|
||||||
// // Declare outputs safely
|
|
||||||
// val outputDir = layout.buildDirectory.dir("generated/buildConfig/commonMain/kotlin")
|
|
||||||
// outputs.dir(outputDir)
|
|
||||||
//
|
//
|
||||||
// val version = projectVersion.get()
|
// pom {
|
||||||
//
|
// name = "Lyng language"
|
||||||
// // Inputs: Version is tracked as an input
|
// description = "Kotlin-bound scripting loanguage"
|
||||||
// inputs.property("version", version)
|
// inceptionYear = "2025"
|
||||||
//
|
//// url = "https://sergeych.net"
|
||||||
// doLast {
|
// licenses {
|
||||||
// val packageName = "net.sergeych.lyng.buildconfig"
|
// license {
|
||||||
// val packagePath = packageName.replace('.', '/')
|
// name = "XXX"
|
||||||
// val buildConfigFile = outputDir.get().file("$packagePath/BuildConfig.kt").asFile
|
// url = "YYY"
|
||||||
//
|
// distribution = "ZZZ"
|
||||||
// buildConfigFile.parentFile?.mkdirs()
|
// }
|
||||||
// buildConfigFile.writeText(
|
// }
|
||||||
// """
|
// developers {
|
||||||
// |package $packageName
|
// developer {
|
||||||
// |
|
// id = "XXX"
|
||||||
// |object BuildConfig {
|
// name = "YYY"
|
||||||
// | const val VERSION = "$version"
|
// url = "ZZZ"
|
||||||
// |}
|
// }
|
||||||
// """.trimMargin()
|
// }
|
||||||
// )
|
// scm {
|
||||||
|
// url = "XXX"
|
||||||
|
// connection = "YYY"
|
||||||
|
// developerConnection = "ZZZ"
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
//
|
|
||||||
//tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
|
||||||
// dependsOn(generateBuildConfig)
|
|
||||||
//}
|
|
||||||
File renamed without changes.
@@ -0,0 +1,17 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special version of the [Scope] used to `apply` new this object to
|
||||||
|
* _parent context property.
|
||||||
|
*
|
||||||
|
* @param _parent context to apply to
|
||||||
|
* @param args arguments for the new context
|
||||||
|
* @param appliedScope the new context to apply, it will have lower priority except for `this` which
|
||||||
|
* will be reset by appliedContext's `this`.
|
||||||
|
*/
|
||||||
|
class AppliedScope(_parent: Scope, args: Arguments, val appliedScope: Scope)
|
||||||
|
: Scope(_parent, args, appliedScope.pos, appliedScope.thisObj) {
|
||||||
|
override fun get(name: String): ObjRecord? =
|
||||||
|
if (name == "this") thisObj.asReadonly
|
||||||
|
else super.get(name) ?: appliedScope[name]
|
||||||
|
}
|
||||||
+9
-9
@@ -22,13 +22,13 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
* parse args and create local vars in a given context
|
* parse args and create local vars in a given context
|
||||||
*/
|
*/
|
||||||
suspend fun assignToContext(
|
suspend fun assignToContext(
|
||||||
context: Context,
|
scope: Scope,
|
||||||
arguments: Arguments = context.args,
|
arguments: Arguments = scope.args,
|
||||||
defaultAccessType: AccessType = AccessType.Var,
|
defaultAccessType: AccessType = AccessType.Var,
|
||||||
defaultVisibility: Visibility = Visibility.Public
|
defaultVisibility: Visibility = Visibility.Public
|
||||||
) {
|
) {
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||||
a.visibility ?: defaultVisibility)
|
a.visibility ?: defaultVisibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
i < callArgs.size -> callArgs[i]
|
i < callArgs.size -> callArgs[i]
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
a.defaultValue != null -> a.defaultValue.execute(scope)
|
||||||
else -> {
|
else -> {
|
||||||
println("callArgs: ${callArgs.joinToString()}")
|
println("callArgs: ${callArgs.joinToString()}")
|
||||||
println("tailBlockMode: ${arguments.tailBlockMode}")
|
println("tailBlockMode: ${arguments.tailBlockMode}")
|
||||||
context.raiseArgumentError("too few arguments for the call")
|
scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
@@ -76,8 +76,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
callArgs[j--]
|
callArgs[j--]
|
||||||
}
|
}
|
||||||
|
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
a.defaultValue != null -> a.defaultValue.execute(scope)
|
||||||
else -> context.raiseArgumentError("too few arguments for the call")
|
else -> scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
}
|
}
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
i--
|
i--
|
||||||
@@ -98,7 +98,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
processEllipsis(leftIndex, end)
|
processEllipsis(leftIndex, end)
|
||||||
} else {
|
} else {
|
||||||
if (leftIndex < callArgs.size)
|
if (leftIndex < callArgs.size)
|
||||||
context.raiseArgumentError("too many arguments for the call")
|
scope.raiseIllegalArgument("too many arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
*/
|
*/
|
||||||
data class Item(
|
data class Item(
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: TypeDecl = TypeDecl.Obj,
|
val type: TypeDecl = TypeDecl.TypeAny,
|
||||||
val pos: Pos = Pos.builtIn,
|
val pos: Pos = Pos.builtIn,
|
||||||
val isEllipsis: Boolean = false,
|
val isEllipsis: Boolean = false,
|
||||||
/**
|
/**
|
||||||
+13
-4
@@ -2,11 +2,11 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
||||||
val list = mutableListOf<Obj>()
|
val list = mutableListOf<Obj>()
|
||||||
|
|
||||||
for (x in this) {
|
for (x in this) {
|
||||||
val value = x.value.execute(context)
|
val value = x.value.execute(scope)
|
||||||
if (x.isSplat) {
|
if (x.isSplat) {
|
||||||
when {
|
when {
|
||||||
value is ObjList -> {
|
value is ObjList -> {
|
||||||
@@ -14,11 +14,11 @@ suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
value.isInstanceOf(ObjIterable) -> {
|
||||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
i.forEach { list.add(it) }
|
i.forEach { list.add(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
list.add(value)
|
list.add(value)
|
||||||
@@ -28,11 +28,20 @@ suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMod
|
|||||||
|
|
||||||
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) : List<Obj> by list {
|
||||||
|
|
||||||
|
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}")
|
||||||
return list.first()
|
return list.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||||
|
*/
|
||||||
|
suspend fun toKotlinList(scope: Scope): List<Any?> {
|
||||||
|
return list.map { it.toKotlin(scope) }
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
val EMPTY = Arguments(emptyList())
|
||||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||||
File renamed without changes.
+541
-310
File diff suppressed because it is too large.
Load diff
+54
-32
@@ -1,13 +1,11 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
internal class CompilerContext(val tokens: List<Token>) {
|
class CompilerContext(val tokens: List<Token>) {
|
||||||
val labels = mutableSetOf<String>()
|
val labels = mutableSetOf<String>()
|
||||||
|
|
||||||
var breakFound = false
|
var breakFound = false
|
||||||
private set
|
|
||||||
|
|
||||||
var loopLevel = 0
|
var loopLevel = 0
|
||||||
private set
|
|
||||||
|
|
||||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||||
if (++loopLevel == 0) breakFound = false
|
if (++loopLevel == 0) breakFound = false
|
||||||
@@ -21,7 +19,11 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
|
|
||||||
fun hasNext() = currentIndex < tokens.size
|
fun hasNext() = currentIndex < tokens.size
|
||||||
fun hasPrevious() = currentIndex > 0
|
fun hasPrevious() = currentIndex > 0
|
||||||
fun next() = tokens.getOrElse(currentIndex) { throw IllegalStateException("No next token") }.also { currentIndex++ }
|
fun next() =
|
||||||
|
if( currentIndex < tokens.size ) tokens[currentIndex++]
|
||||||
|
else Token("", tokens.last().pos, Token.Type.EOF)
|
||||||
|
// throw IllegalStateException("No more tokens")
|
||||||
|
|
||||||
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
||||||
|
|
||||||
fun savePos() = currentIndex
|
fun savePos() = currentIndex
|
||||||
@@ -47,9 +49,21 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
throw ScriptError(at, message)
|
throw ScriptError(at, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentPos() =
|
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||||
if (hasNext()) next().pos.also { previous() }
|
|
||||||
else previous().pos.also { next() }
|
/**
|
||||||
|
* If the next token is identifier `name`, skip it and return `true`.
|
||||||
|
* else leave where it is and return `false`
|
||||||
|
*/
|
||||||
|
fun skipId(name: String): Boolean {
|
||||||
|
current().let { t ->
|
||||||
|
if( t.type == Token.Type.ID && t.value == name ) {
|
||||||
|
next()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips next token if its type is `tokenType`, returns `true` if so.
|
* Skips next token if its type is `tokenType`, returns `true` if so.
|
||||||
@@ -83,7 +97,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
inline fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
||||||
val t = next()
|
val t = next()
|
||||||
return if (t.type == typeId) {
|
return if (t.type == typeId) {
|
||||||
f(t)
|
f(t)
|
||||||
@@ -102,6 +116,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
* Return value of the next token if it is an identifier, null otherwise.
|
* Return value of the next token if it is an identifier, null otherwise.
|
||||||
* Does not change position.
|
* Does not change position.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
fun nextIdValue(): String? {
|
fun nextIdValue(): String? {
|
||||||
return if (hasNext()) {
|
return if (hasNext()) {
|
||||||
val nt = tokens[currentIndex]
|
val nt = tokens[currentIndex]
|
||||||
@@ -117,36 +132,43 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
/**
|
/**
|
||||||
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
fun atOffset(offset: Int): Token? =
|
fun atOffset(offset: Int): Token? =
|
||||||
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||||
|
|
||||||
/**
|
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
||||||
* Scan backwards as deep as specified looking for visibility token. Does not change position.
|
val pos = savePos()
|
||||||
*/
|
var count = 0
|
||||||
fun getVisibility(default: Visibility = Visibility.Public, depths: Int = 2): Visibility {
|
while( count < qualifiers.size) {
|
||||||
for( i in -depths .. -1) {
|
val t = next()
|
||||||
when( atOffset(i)?.type) {
|
when(t.type) {
|
||||||
Token.Type.PROTECTED -> return Visibility.Protected
|
Token.Type.ID -> {
|
||||||
Token.Type.PRIVATE -> return Visibility.Private
|
if( t.value in qualifiers ) count++
|
||||||
else -> {}
|
else { restorePos(pos); return false }
|
||||||
|
}
|
||||||
|
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||||
|
else -> { restorePos(pos); return false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return default
|
val t = next()
|
||||||
|
if( t.type == Token.Type.ID && t.value == keyword ) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
restorePos(pos)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun expectKeyword(vararg keyword: String): String {
|
/**
|
||||||
// val t = next()
|
* Skip newlines and comments. Returns (and reads) first non-whitespace token.
|
||||||
// if (t.type != Token.Type.ID && t.value !in keyword) {
|
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||||
// throw ScriptError(t.pos, "expected one of ${keyword.joinToString()}")
|
*/
|
||||||
//
|
fun skipWsTokens(): Token {
|
||||||
// }
|
while( current().type in wstokens ) next()
|
||||||
|
return next()
|
||||||
// data class ReturnScope(val needCatch: Boolean = false)
|
}
|
||||||
|
|
||||||
// private val
|
|
||||||
|
|
||||||
// fun startReturnScope(): ReturnScope {
|
|
||||||
// return ReturnScope()
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import net.sergeych.mp_tools.globalDefer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package manager. Chained manager, too simple. Override [createModuleScope] to return either
|
||||||
|
* valid [ModuleScope] or call [parent] - or return null.
|
||||||
|
*/
|
||||||
|
abstract class Pacman(
|
||||||
|
val parent: Pacman? = null,
|
||||||
|
val rootScope: Scope = parent!!.rootScope,
|
||||||
|
val securityManager: SecurityManager = parent!!.securityManager
|
||||||
|
) {
|
||||||
|
private val opScopes = Mutex()
|
||||||
|
|
||||||
|
private val cachedScopes = mutableMapOf<String, Scope>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new module scope if this pacman can import the module, return null otherwise so
|
||||||
|
* the manager can decide what to do
|
||||||
|
*/
|
||||||
|
abstract suspend fun createModuleScope(name: String): ModuleScope?
|
||||||
|
|
||||||
|
suspend fun prepareImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
if (!securityManager.canImportModule(name))
|
||||||
|
throw ImportException(pos, "Module $name is not allowed")
|
||||||
|
symbols?.keys?.forEach {
|
||||||
|
if (!securityManager.canImportSymbol(name, it)) throw ImportException(
|
||||||
|
pos,
|
||||||
|
"Symbol $name.$it is not allowed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if we can import the module, cache it, or go further
|
||||||
|
opScopes.withLock {
|
||||||
|
cachedScopes[name] ?: createModuleScope(name)?.let { cachedScopes[name] = it }
|
||||||
|
} ?: parent?.prepareImport(pos, name, symbols) ?: throw ImportException(pos, "Module $name is not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun performImport(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
val module = opScopes.withLock { cachedScopes[name] }
|
||||||
|
?: scope.raiseSymbolNotFound("module $name not found")
|
||||||
|
val symbolsToImport = symbols?.keys?.toMutableSet()
|
||||||
|
for ((symbol, record) in module.objects) {
|
||||||
|
if (record.visibility.isPublic) {
|
||||||
|
println("import $name: $symbol: $record")
|
||||||
|
val newName = symbols?.let { ss: Map<String, String> ->
|
||||||
|
ss[symbol]
|
||||||
|
?.also { symbolsToImport!!.remove(it) }
|
||||||
|
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
|
||||||
|
} ?: symbol
|
||||||
|
println("import $name.$symbol as $newName")
|
||||||
|
if (newName in scope.objects)
|
||||||
|
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
|
||||||
|
scope.objects[newName] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!symbolsToImport.isNullOrEmpty())
|
||||||
|
scope.raiseSymbolNotFound("symbols $name.{$symbolsToImport} are.were not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val emptyAllowAll = object : Pacman(rootScope = Script.defaultScope, securityManager = SecurityManager.allowAll) {
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope supports importing and contains the [pacman]; it should be the same
|
||||||
|
* used in [Compiler];
|
||||||
|
*/
|
||||||
|
class ModuleScope(
|
||||||
|
val pacman: Pacman,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
|
val packageName: String
|
||||||
|
) : Scope(pacman.rootScope, Arguments.EMPTY, pos) {
|
||||||
|
|
||||||
|
constructor(pacman: Pacman,source: Source) : this(pacman, source.startPos, source.fileName)
|
||||||
|
|
||||||
|
override suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.prepareImport(pos, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import symbols into the scope. It _is called_ after the module is imported by [checkImport].
|
||||||
|
* If [checkImport] was not called, the symbols will not be imported with exception as module is not found.
|
||||||
|
*/
|
||||||
|
override suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.performImport(scope, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageNameObj by lazy { ObjString(packageName).asReadonly}
|
||||||
|
|
||||||
|
override fun get(name: String): ObjRecord? {
|
||||||
|
return if( name == "__PACKAGE__")
|
||||||
|
packageNameObj
|
||||||
|
else
|
||||||
|
super.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InlineSourcesPacman(pacman: Pacman,val sources: List<Source>) : Pacman(pacman) {
|
||||||
|
|
||||||
|
val modules: Deferred<Map<String,Deferred<ModuleScope>>> = globalDefer {
|
||||||
|
val result = mutableMapOf<String, Deferred<ModuleScope>>()
|
||||||
|
for (source in sources) {
|
||||||
|
// retrieve the module name and script for deferred execution:
|
||||||
|
val (name, script) = Compiler.compilePackage(source)
|
||||||
|
// scope is created used pacman's root scope:
|
||||||
|
val scope = ModuleScope(this@InlineSourcesPacman, source.startPos, name)
|
||||||
|
// we execute scripts in parallel which allow cross-imports to some extent:
|
||||||
|
result[name] = globalDefer { script.execute(scope); scope }
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? =
|
||||||
|
modules.await()[name]?.await()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,515 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.bintools.encodeToHex
|
||||||
|
import net.sergeych.synctools.ProtectedOp
|
||||||
|
import net.sergeych.synctools.withLock
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record to store object with access rules, e.g. [isMutable] and access level [visibility].
|
||||||
|
*/
|
||||||
|
data class ObjRecord(
|
||||||
|
var value: Obj,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility = Visibility.Public
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we need read-write access to an object in some abstract storage, we need Accessor,
|
||||||
|
* as in-site assigning is not always sufficient, in general case we need to replace the object
|
||||||
|
* in the storage.
|
||||||
|
*
|
||||||
|
* Note that assigning new value is more complex than just replacing the object, see how assignment
|
||||||
|
* operator is implemented in [Compiler.allOps].
|
||||||
|
*/
|
||||||
|
data class Accessor(
|
||||||
|
val getter: suspend (Scope) -> ObjRecord,
|
||||||
|
val setterOrNull: (suspend (Scope, Obj) -> Unit)?
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Simplified constructor for immutable stores.
|
||||||
|
*/
|
||||||
|
constructor(getter: suspend (Scope) -> ObjRecord) : this(getter, null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the setter or throw.
|
||||||
|
*/
|
||||||
|
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Obj {
|
||||||
|
|
||||||
|
val isNull by lazy { this === ObjNull }
|
||||||
|
|
||||||
|
var isFrozen: Boolean = false
|
||||||
|
|
||||||
|
private val monitor = Mutex()
|
||||||
|
|
||||||
|
// private val memberMutex = Mutex()
|
||||||
|
internal var parentInstances: MutableList<Obj> = mutableListOf()
|
||||||
|
|
||||||
|
private val opInstances = ProtectedOp()
|
||||||
|
|
||||||
|
open fun inspect(): String = toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such.
|
||||||
|
* When initializing a var with it, by value objects must be copied. By-reference ones aren't.
|
||||||
|
*
|
||||||
|
* Almost all objects are by-reference.
|
||||||
|
*/
|
||||||
|
open fun byValueCopy(): Obj = this
|
||||||
|
|
||||||
|
@Suppress("SuspiciousEqualsCombination")
|
||||||
|
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||||
|
objClass.allParentsSet.contains(someClass) ||
|
||||||
|
someClass == rootObjectType
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun invokeInstanceMethod(scope: Scope, name: String, vararg args: Obj): Obj =
|
||||||
|
invokeInstanceMethod(scope, name, Arguments(args.toList()))
|
||||||
|
|
||||||
|
suspend inline fun <reified T : Obj> callMethod(
|
||||||
|
scope: Scope,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
|
): T = invokeInstanceMethod(scope, name, args) as T
|
||||||
|
|
||||||
|
open suspend fun invokeInstanceMethod(
|
||||||
|
scope: Scope,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
|
): Obj =
|
||||||
|
// note that getInstanceMember traverses the hierarchy
|
||||||
|
objClass.getInstanceMember(scope.pos, name).value.invoke(scope, this, args)
|
||||||
|
|
||||||
|
open suspend fun getInstanceMethod(
|
||||||
|
scope: Scope,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
|
): Obj =
|
||||||
|
// note that getInstanceMember traverses the hierarchy
|
||||||
|
objClass.getInstanceMember(scope.pos, name).value
|
||||||
|
|
||||||
|
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
|
||||||
|
|
||||||
|
// methods that to override
|
||||||
|
|
||||||
|
open suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
return invokeInstanceMethod(scope, "contains", other).toBool()
|
||||||
|
}
|
||||||
|
|
||||||
|
open val asStr: ObjString by lazy {
|
||||||
|
if (this is ObjString) this else ObjString(this.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class of the object: definition of member functions (top-level), etc.
|
||||||
|
* Note that using lazy allows to avoid endless recursion here
|
||||||
|
*/
|
||||||
|
open val objClass: ObjClass = rootObjectType
|
||||||
|
|
||||||
|
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun div(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun logicalNot(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun logicalAnd(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun logicalOr(scope: Scope, other: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun assign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a += b
|
||||||
|
* if( the operation is not defined, it returns null and the compiler would try
|
||||||
|
* to generate it as 'this = this + other', reassigning its variable
|
||||||
|
*/
|
||||||
|
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-=` operations, see [plusAssign]
|
||||||
|
*/
|
||||||
|
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
open suspend fun divAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
open suspend fun modAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
|
open suspend fun getAndIncrement(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun incrementAndGet(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun decrementAndGet(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun getAndDecrement(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Lyng object to its Kotlin counterpart
|
||||||
|
*/
|
||||||
|
open suspend fun toKotlin(scope: Scope): Any? {
|
||||||
|
return toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun willMutate(scope: Scope) {
|
||||||
|
if (isFrozen) scope.raiseError("attempt to mutate frozen object")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
|
open suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
|
// could be property or class field:
|
||||||
|
val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||||
|
return when (val value = obj.value) {
|
||||||
|
is Statement -> {
|
||||||
|
ObjRecord(value.execute(scope.copy(scope.pos, newThisObj = this)), obj.isMutable)
|
||||||
|
}
|
||||||
|
// could be writable property naturally
|
||||||
|
// null -> ObjNull.asReadonly
|
||||||
|
else -> obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
|
willMutate(scope)
|
||||||
|
val field = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||||
|
if (field.isMutable) field.value = newValue else scope.raiseError("can't assign to read-only field: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
|
scope.raiseNotImplemented("indexing")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
||||||
|
|
||||||
|
open suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
|
scope.raiseNotImplemented("indexing")
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun callOn(scope: Scope): Obj {
|
||||||
|
scope.raiseNotImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj =
|
||||||
|
callOn(scope.copy(scope.pos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||||
|
callOn(
|
||||||
|
scope.copy(
|
||||||
|
scope.pos,
|
||||||
|
args = Arguments(args.toList()),
|
||||||
|
newThisObj = thisObj
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
||||||
|
callOn(
|
||||||
|
scope.copy(
|
||||||
|
scope.pos,
|
||||||
|
args = Arguments.EMPTY,
|
||||||
|
newThisObj = thisObj
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||||
|
callOn(scope.copy(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
|
||||||
|
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
||||||
|
val asMutable: ObjRecord by lazy { ObjRecord(this, true) }
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val rootObjectType = ObjClass("Obj").apply {
|
||||||
|
addFn("toString") {
|
||||||
|
thisObj.asStr
|
||||||
|
}
|
||||||
|
addFn("contains") {
|
||||||
|
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||||
|
}
|
||||||
|
// utilities
|
||||||
|
addFn("let") {
|
||||||
|
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||||
|
}
|
||||||
|
addFn("apply") {
|
||||||
|
val newContext = ( thisObj as? ObjInstance)?.instanceScope ?: this
|
||||||
|
args.firstAndOnly()
|
||||||
|
.callOn(newContext)
|
||||||
|
thisObj
|
||||||
|
}
|
||||||
|
addFn("also") {
|
||||||
|
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||||
|
thisObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline fun from(obj: Any?): Obj {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return when (obj) {
|
||||||
|
is Obj -> obj
|
||||||
|
is Double -> ObjReal(obj)
|
||||||
|
is Float -> ObjReal(obj.toDouble())
|
||||||
|
is Int -> ObjInt(obj.toLong())
|
||||||
|
is Long -> ObjInt(obj)
|
||||||
|
is String -> ObjString(obj)
|
||||||
|
is CharSequence -> ObjString(obj.toString())
|
||||||
|
is Boolean -> ObjBool(obj)
|
||||||
|
is Set<*> -> ObjSet((obj as Set<Obj>).toMutableSet())
|
||||||
|
Unit -> ObjVoid
|
||||||
|
null -> ObjNull
|
||||||
|
is Iterator<*> -> ObjKotlinIterator(obj)
|
||||||
|
is Map.Entry<*, *> -> {
|
||||||
|
obj as MutableMap.MutableEntry<Obj, Obj>
|
||||||
|
ObjMapEntry(obj.key, obj.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
inline fun <reified T> T.toObj(): Obj = Obj.from(this)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("void")
|
||||||
|
object ObjVoid : Obj() {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is ObjVoid || other is Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
return if (other === this) 0 else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "void"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("null")
|
||||||
|
object ObjNull : Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
return if (other === this) 0 else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is ObjNull || other == null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
|
scope.raiseNPE()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
|
||||||
|
scope.raiseNPE()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
|
scope.raiseNPE()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
|
scope.raiseNPE()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
scope.raiseNPE()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "null"
|
||||||
|
|
||||||
|
override suspend fun toKotlin(scope: Scope): Any? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Numeric {
|
||||||
|
val longValue: Long
|
||||||
|
val doubleValue: Double
|
||||||
|
val toObjInt: ObjInt
|
||||||
|
val toObjReal: ObjReal
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Obj.toDouble(): Double =
|
||||||
|
(this as? Numeric)?.doubleValue
|
||||||
|
?: (this as? ObjString)?.value?.toDouble()
|
||||||
|
?: throw IllegalArgumentException("cannot convert to double $this")
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun Obj.toLong(): Long =
|
||||||
|
when (this) {
|
||||||
|
is Numeric -> longValue
|
||||||
|
is ObjString -> value.toLong()
|
||||||
|
is ObjChar -> value.code.toLong()
|
||||||
|
else -> throw IllegalArgumentException("cannot convert to double $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Obj.toInt(): Int = toLong().toInt()
|
||||||
|
|
||||||
|
fun Obj.toBool(): Boolean =
|
||||||
|
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
||||||
|
|
||||||
|
|
||||||
|
data class ObjNamespace(val name: String) : Obj() {
|
||||||
|
override val objClass by lazy { ObjClass(name) }
|
||||||
|
|
||||||
|
override fun inspect(): String = "Ns[$name]"
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "package $name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val message: String) : Obj() {
|
||||||
|
constructor(name: String, scope: Scope, message: String) : this(
|
||||||
|
getOrCreateExceptionClass(name),
|
||||||
|
scope,
|
||||||
|
message
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(scope: Scope, message: String) : this(Root, scope, message)
|
||||||
|
|
||||||
|
fun raise(): Nothing {
|
||||||
|
throw ExecutionError(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val objClass: ObjClass = exceptionClass
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ObjException:${objClass.className}:${scope.pos}@${hashCode().encodeToHex()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val message = scope.args.getOrNull(0)?.toString() ?: name
|
||||||
|
return ObjException(this, scope, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
val Root = ExceptionClass("Throwable").apply {
|
||||||
|
addConst("message", statement {
|
||||||
|
(thisObj as ObjException).message.toObj()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private val op = ProtectedOp()
|
||||||
|
private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
|
||||||
|
return op.withLock {
|
||||||
|
existingErrorClasses.getOrPut(name) {
|
||||||
|
ExceptionClass(name, Root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get [ObjClass] for error class by name if exists.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun getErrorClass(name: String): ObjClass? = op.withLock {
|
||||||
|
existingErrorClasses[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addExceptionsToContext(scope: Scope) {
|
||||||
|
scope.addConst("Exception", Root)
|
||||||
|
existingErrorClasses["Exception"] = Root
|
||||||
|
for (name in listOf(
|
||||||
|
"NullReferenceException",
|
||||||
|
"AssertionFailedException",
|
||||||
|
"ClassCastException",
|
||||||
|
"IndexOutOfBoundsException",
|
||||||
|
"IllegalArgumentException",
|
||||||
|
"NoSuchElementException",
|
||||||
|
"IllegalAssignmentException",
|
||||||
|
"SymbolNotDefinedException",
|
||||||
|
"IterationEndException",
|
||||||
|
"AccessException",
|
||||||
|
"UnknownException",
|
||||||
|
)) {
|
||||||
|
scope.addConst(name, getOrCreateExceptionClass(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjNullReferenceException(scope: Scope) : ObjException("NullReferenceException", scope, "object is null")
|
||||||
|
|
||||||
|
class ObjAssertionFailedException(scope: Scope, message: String) :
|
||||||
|
ObjException("AssertionFailedException", scope, message)
|
||||||
|
|
||||||
|
class ObjClassCastException(scope: Scope, message: String) : ObjException("ClassCastException", scope, message)
|
||||||
|
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
|
||||||
|
ObjException("IndexOutOfBoundsException", scope, message)
|
||||||
|
|
||||||
|
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
|
||||||
|
ObjException("IllegalArgumentException", scope, message)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class ObjNoSuchElementException(scope: Scope, message: String = "no such element") :
|
||||||
|
ObjException("IllegalArgumentException", scope, message)
|
||||||
|
|
||||||
|
class ObjIllegalAssignmentException(scope: Scope, message: String = "illegal assignment") :
|
||||||
|
ObjException("NoSuchElementException", scope, message)
|
||||||
|
|
||||||
|
class ObjSymbolNotDefinedException(scope: Scope, message: String = "symbol is not defined") :
|
||||||
|
ObjException("SymbolNotDefinedException", scope, message)
|
||||||
|
|
||||||
|
class ObjIterationFinishedException(scope: Scope) :
|
||||||
|
ObjException("IterationEndException", scope, "iteration finished")
|
||||||
|
|
||||||
|
class ObjAccessException(scope: Scope, message: String = "access not allowed error") :
|
||||||
|
ObjException("AccessException", scope, message)
|
||||||
|
|
||||||
|
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
|
||||||
|
ObjException("UnknownException", scope, message)
|
||||||
|
|
||||||
|
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
|
||||||
|
ObjException("IllegalOperationException", scope, message)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
val ObjArray by lazy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array abstract class is a [ObjCollection] with `getAt` method.
|
||||||
|
*/
|
||||||
|
ObjClass("Array", ObjCollection).apply {
|
||||||
|
// we can create iterators using size/getat:
|
||||||
|
|
||||||
|
addFn("iterator") {
|
||||||
|
ObjArrayIterator(thisObj).also { it.init(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("contains", isOpen = true) {
|
||||||
|
val obj = args.firstAndOnly()
|
||||||
|
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
||||||
|
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFn ObjTrue
|
||||||
|
}
|
||||||
|
ObjFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("last") {
|
||||||
|
thisObj.invokeInstanceMethod(
|
||||||
|
this,
|
||||||
|
"getAt",
|
||||||
|
(thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("lastIndex") { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
||||||
|
|
||||||
|
addFn("indices") {
|
||||||
|
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
class ObjArrayIterator(val array: Obj) : Obj() {
|
||||||
|
|
||||||
|
override val objClass: ObjClass by lazy { type }
|
||||||
|
|
||||||
|
private var nextIndex = 0
|
||||||
|
private var lastIndex = 0
|
||||||
|
|
||||||
|
suspend fun init(scope: Scope) {
|
||||||
|
nextIndex = 0
|
||||||
|
lastIndex = array.invokeInstanceMethod(scope, "size").toInt()
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type by lazy {
|
||||||
|
ObjClass("ArrayIterator", ObjIterator).apply {
|
||||||
|
addFn("next") {
|
||||||
|
val self = thisAs<ObjArrayIterator>()
|
||||||
|
if (self.nextIndex < self.lastIndex) {
|
||||||
|
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||||
|
} else raiseError(ObjIterationFinishedException(this))
|
||||||
|
}
|
||||||
|
addFn("hasNext") {
|
||||||
|
val self = thisAs<ObjArrayIterator>()
|
||||||
|
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
data class ObjBool(val value: Boolean) : Obj() {
|
||||||
|
override val asStr by lazy { ObjString(value.toString()) }
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if (other !is ObjBool) return -2
|
||||||
|
return value.compareTo(other.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
|
override suspend fun logicalNot(scope: Scope): Obj = ObjBool(!value)
|
||||||
|
|
||||||
|
override suspend fun logicalAnd(scope: Scope, other: Obj): Obj = ObjBool(value && other.toBool())
|
||||||
|
|
||||||
|
override suspend fun logicalOr(scope: Scope, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||||
|
|
||||||
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("Bool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ObjTrue = ObjBool(true)
|
||||||
|
val ObjFalse = ObjBool(false)
|
||||||
+1
-1
@@ -4,7 +4,7 @@ class ObjChar(val value: Char): Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int =
|
override suspend fun compareTo(scope: Scope, other: Obj): Int =
|
||||||
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
override fun toString(): String = value.toString()
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
val ObjClassType by lazy { ObjClass("Class") }
|
||||||
|
|
||||||
|
open class ObjClass(
|
||||||
|
val className: String,
|
||||||
|
vararg val parents: ObjClass,
|
||||||
|
) : Obj() {
|
||||||
|
|
||||||
|
var instanceConstructor: Statement? = null
|
||||||
|
|
||||||
|
val allParentsSet: Set<ObjClass> =
|
||||||
|
parents.flatMap {
|
||||||
|
listOf(it) + it.allParentsSet
|
||||||
|
}.toMutableSet()
|
||||||
|
|
||||||
|
override val objClass: ObjClass by lazy { ObjClassType }
|
||||||
|
|
||||||
|
// members: fields most often
|
||||||
|
private val members = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
|
override fun toString(): String = className
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val instance = ObjInstance(this)
|
||||||
|
instance.instanceScope = scope.copy(newThisObj = instance,args = scope.args)
|
||||||
|
if (instanceConstructor != null) {
|
||||||
|
instanceConstructor!!.execute(instance.instanceScope)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultInstance(): Obj = object : Obj() {
|
||||||
|
override val objClass: ObjClass = this@ObjClass
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createField(
|
||||||
|
name: String,
|
||||||
|
initialValue: Obj,
|
||||||
|
isMutable: Boolean = false,
|
||||||
|
visibility: Visibility = Visibility.Public,
|
||||||
|
pos: Pos = Pos.builtIn
|
||||||
|
) {
|
||||||
|
val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] }
|
||||||
|
if( existing?.isMutable == false)
|
||||||
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||||
|
createField(name, statement { code() }, isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
||||||
|
*/
|
||||||
|
fun getInstanceMemberOrNull(name: String): ObjRecord? {
|
||||||
|
members[name]?.let { return it }
|
||||||
|
allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||||
|
return rootObjectType.members[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
||||||
|
getInstanceMemberOrNull(name)
|
||||||
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection is an iterator with `size`]
|
||||||
|
*/
|
||||||
|
val ObjCollection = ObjClass("Collection", ObjIterable).apply {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||||
|
|
||||||
|
internal lateinit var instanceScope: Scope
|
||||||
|
|
||||||
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
|
return instanceScope[name]?.let {
|
||||||
|
if (it.visibility.isPublic)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
scope.raiseError(ObjAccessException(scope, "can't access non-public field $name"))
|
||||||
|
}
|
||||||
|
?: super.readField(scope, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
|
instanceScope[name]?.let { f ->
|
||||||
|
if (!f.visibility.isPublic)
|
||||||
|
ObjIllegalAssignmentException(scope, "can't assign to non-public field $name")
|
||||||
|
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||||
|
if (f.value.assign(scope, newValue) == null)
|
||||||
|
f.value = newValue
|
||||||
|
} ?: super.writeField(scope, name, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj =
|
||||||
|
instanceScope[name]?.let {
|
||||||
|
if (it.visibility.isPublic)
|
||||||
|
it.value.invoke(scope, this, args)
|
||||||
|
else
|
||||||
|
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
|
||||||
|
}
|
||||||
|
?: super.invokeInstanceMethod(scope, name, args)
|
||||||
|
|
||||||
|
private val publicFields: Map<String, ObjRecord>
|
||||||
|
get() = instanceScope.objects.filter { it.value.visibility.isPublic }
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||||
|
return "${objClass.className}($fields)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if (other !is ObjInstance) return -1
|
||||||
|
if (other.objClass != objClass) return -1
|
||||||
|
for (f in publicFields) {
|
||||||
|
val a = f.value.value
|
||||||
|
val b = other.instanceScope[f.key]!!.value
|
||||||
|
val d = a.compareTo(scope, b)
|
||||||
|
if (d != 0) return d
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-11
@@ -9,23 +9,27 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
|
|
||||||
override fun byValueCopy(): Obj = ObjInt(value)
|
override fun byValueCopy(): Obj = ObjInt(value)
|
||||||
|
|
||||||
override suspend fun getAndIncrement(context: Context): Obj {
|
override fun hashCode(): Int {
|
||||||
|
return value.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAndIncrement(scope: Scope): Obj {
|
||||||
return ObjInt(value).also { value++ }
|
return ObjInt(value).also { value++ }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAndDecrement(context: Context): Obj {
|
override suspend fun getAndDecrement(scope: Scope): Obj {
|
||||||
return ObjInt(value).also { value-- }
|
return ObjInt(value).also { value-- }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun incrementAndGet(context: Context): Obj {
|
override suspend fun incrementAndGet(scope: Scope): Obj {
|
||||||
return ObjInt(++value)
|
return ObjInt(++value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun decrementAndGet(context: Context): Obj {
|
override suspend fun decrementAndGet(scope: Scope): Obj {
|
||||||
return ObjInt(--value)
|
return ObjInt(--value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is Numeric) return -2
|
if (other !is Numeric) return -2
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
@@ -34,29 +38,29 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value + other.value)
|
ObjInt(this.value + other.value)
|
||||||
else
|
else
|
||||||
ObjReal(this.doubleValue + other.toDouble())
|
ObjReal(this.doubleValue + other.toDouble())
|
||||||
|
|
||||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value - other.value)
|
ObjInt(this.value - other.value)
|
||||||
else
|
else
|
||||||
ObjReal(this.doubleValue - other.toDouble())
|
ObjReal(this.doubleValue - other.toDouble())
|
||||||
|
|
||||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt) {
|
if (other is ObjInt) {
|
||||||
ObjInt(this.value * other.value)
|
ObjInt(this.value * other.value)
|
||||||
} else ObjReal(this.value * other.toDouble())
|
} else ObjReal(this.value * other.toDouble())
|
||||||
|
|
||||||
override suspend fun div(context: Context, other: Obj): Obj =
|
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value / other.value)
|
ObjInt(this.value / other.value)
|
||||||
else ObjReal(this.value / other.toDouble())
|
else ObjReal(this.value / other.toDouble())
|
||||||
|
|
||||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value % other.value)
|
ObjInt(this.value % other.value)
|
||||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||||
@@ -65,14 +69,29 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
||||||
* assignment
|
* assignment
|
||||||
*/
|
*/
|
||||||
override suspend fun assign(context: Context, other: Obj): Obj? {
|
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
||||||
return if (other is ObjInt) {
|
return if (other is ObjInt) {
|
||||||
value = other.value
|
value = other.value
|
||||||
this
|
this
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ObjInt
|
||||||
|
|
||||||
|
return value == other.value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val Zero = ObjInt(0)
|
||||||
|
val One = ObjInt(1)
|
||||||
val type = ObjClass("Int")
|
val type = ObjClass("Int")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
|
||||||
|
*/
|
||||||
|
val ObjIterable by lazy {
|
||||||
|
ObjClass("Iterable").apply {
|
||||||
|
|
||||||
|
addFn("toList") {
|
||||||
|
val result = mutableListOf<Obj>()
|
||||||
|
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
|
||||||
|
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
|
||||||
|
result += iterator.invokeInstanceMethod(this, "next")
|
||||||
|
ObjList(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it is not effective, but it is open:
|
||||||
|
addFn("contains", isOpen = true) {
|
||||||
|
val obj = args.firstAndOnly()
|
||||||
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
|
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
|
||||||
|
return@addFn ObjTrue
|
||||||
|
}
|
||||||
|
ObjFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("indexOf", isOpen = true) {
|
||||||
|
val obj = args.firstAndOnly()
|
||||||
|
var index = 0
|
||||||
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
|
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
|
||||||
|
return@addFn ObjInt(index.toLong())
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
ObjInt(-1L)
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("toSet") {
|
||||||
|
val result = mutableSetOf<Obj>()
|
||||||
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
|
result += it.invokeInstanceMethod(this, "next")
|
||||||
|
}
|
||||||
|
ObjSet(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("toMap") {
|
||||||
|
val result = ObjMap()
|
||||||
|
thisObj.toFlow(this).collect { pair ->
|
||||||
|
result.map[pair.getAt(this,0)] = pair.getAt(this, 1)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("associateBy") {
|
||||||
|
val association = requireOnlyArg<Statement>()
|
||||||
|
val result = ObjMap()
|
||||||
|
thisObj.toFlow(this).collect {
|
||||||
|
result.map[association.call(this, it)] = it
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("forEach", isOpen = true) {
|
||||||
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
val fn = requiredArg<Statement>(0)
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
|
val x = it.invokeInstanceMethod(this, "next")
|
||||||
|
fn.execute(this.copy(Arguments(listOf(x))))
|
||||||
|
}
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("map", isOpen = true) {
|
||||||
|
val fn = requiredArg<Statement>(0)
|
||||||
|
val result = mutableListOf<Obj>()
|
||||||
|
thisObj.toFlow(this).collect {
|
||||||
|
result += fn.call(this, it)
|
||||||
|
}
|
||||||
|
ObjList(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("isEmpty") {
|
||||||
|
ObjBool(
|
||||||
|
thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
.invokeInstanceMethod(this, "hasNext").toBool()
|
||||||
|
.not()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
val ObjIterator by lazy { ObjClass("Iterator") }
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
|
||||||
|
* each object is converted to a Lyng object.
|
||||||
|
*/
|
||||||
|
class ObjKotlinIterator(val iterator: Iterator<Any?>) : Obj() {
|
||||||
|
|
||||||
|
override val objClass = type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||||
|
addFn("next") { thisAs<ObjKotlinIterator>().iterator.next().toObj() }
|
||||||
|
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate kotlin iterator that already produces Lyng objects, no conversion
|
||||||
|
* is applied
|
||||||
|
*/
|
||||||
|
class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
|
||||||
|
|
||||||
|
override val objClass = type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||||
|
addFn("next") {
|
||||||
|
thisAs<ObjKotlinObjIterator>().iterator.next()
|
||||||
|
}
|
||||||
|
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Lyng's Iterable to Kotlin's Flow.
|
||||||
|
*
|
||||||
|
* As Lyng is totally asynchronous, its iterator can't be trivially converted to Kotlin's synchronous iterator.
|
||||||
|
* It is, though, trivially convertible to Kotlin's Flow.
|
||||||
|
*/
|
||||||
|
fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
|
||||||
|
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||||
|
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||||
|
val next = iterator.getInstanceMethod(scope, "next")
|
||||||
|
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||||
|
emit(next.invoke(scope, iterator))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (p in objClass.parents)
|
||||||
|
parentInstances.add(p.defaultInstance())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "[${
|
||||||
|
list.joinToString(separator = ", ") { it.inspect() }
|
||||||
|
}]"
|
||||||
|
|
||||||
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
|
return when (index) {
|
||||||
|
is ObjInt -> {
|
||||||
|
list[index.toInt()]
|
||||||
|
}
|
||||||
|
|
||||||
|
is ObjRange -> {
|
||||||
|
when {
|
||||||
|
index.start is ObjInt && index.end is ObjInt -> {
|
||||||
|
if (index.isEndInclusive)
|
||||||
|
ObjList(list.subList(index.start.toInt(), index.end.toInt() + 1).toMutableList())
|
||||||
|
else
|
||||||
|
ObjList(list.subList(index.start.toInt(), index.end.toInt()).toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenStart && !index.isOpenEnd -> {
|
||||||
|
if (index.isEndInclusive)
|
||||||
|
ObjList(list.subList(0, index.end!!.toInt() + 1).toMutableList())
|
||||||
|
else
|
||||||
|
ObjList(list.subList(0, index.end!!.toInt()).toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenEnd && !index.isOpenStart -> {
|
||||||
|
ObjList(list.subList(index.start!!.toInt(), list.size).toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenStart && index.isOpenEnd -> {
|
||||||
|
ObjList(list.toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
throw RuntimeException("Can't apply range for index: $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> scope.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
|
val i = index
|
||||||
|
list[i] = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if (other !is ObjList) return -2
|
||||||
|
val mySize = list.size
|
||||||
|
val otherSize = other.list.size
|
||||||
|
val commonSize = minOf(mySize, otherSize)
|
||||||
|
for (i in 0..<commonSize) {
|
||||||
|
if (list[i].compareTo(scope, other.list[i]) != 0) {
|
||||||
|
return list[i].compareTo(scope, other.list[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// equal so far, longer is greater:
|
||||||
|
return when {
|
||||||
|
mySize < otherSize -> -1
|
||||||
|
mySize > otherSize -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
|
when {
|
||||||
|
other is ObjList ->
|
||||||
|
ObjList((list + other.list).toMutableList())
|
||||||
|
|
||||||
|
other.isInstanceOf(ObjIterable) -> {
|
||||||
|
val l = other.callMethod<ObjList>(scope, "toList")
|
||||||
|
ObjList((list + l.list).toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
else ->
|
||||||
|
scope.raiseError("'+': can't concatenate $this with $other")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
|
// optimization
|
||||||
|
if (other is ObjList) {
|
||||||
|
list += other.list
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
|
val otherList = other.invokeInstanceMethod(scope, "toList") as ObjList
|
||||||
|
list += otherList.list
|
||||||
|
} else
|
||||||
|
list += other
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
return list.contains(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val objClass: ObjClass
|
||||||
|
get() = type
|
||||||
|
|
||||||
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
|
return list.map { it.toKotlin(scope) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
// check?
|
||||||
|
return list.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ObjList
|
||||||
|
|
||||||
|
return list == other.list
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("List", ObjArray).apply {
|
||||||
|
|
||||||
|
createField("size",
|
||||||
|
statement {
|
||||||
|
(thisObj as ObjList).list.size.toObj()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addFn("getAt") {
|
||||||
|
requireExactCount(1)
|
||||||
|
thisAs<ObjList>().getAt(this, requiredArg<Obj>(0))
|
||||||
|
}
|
||||||
|
addFn("putAt") {
|
||||||
|
requireExactCount(2)
|
||||||
|
val newValue = args[1]
|
||||||
|
thisAs<ObjList>().putAt(this, requiredArg<ObjInt>(0).value.toInt(), newValue)
|
||||||
|
newValue
|
||||||
|
}
|
||||||
|
createField("add",
|
||||||
|
statement {
|
||||||
|
val l = thisAs<ObjList>().list
|
||||||
|
for (a in args) l.add(a)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addFn("insertAt") {
|
||||||
|
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||||
|
val l = thisAs<ObjList>()
|
||||||
|
var index = requiredArg<ObjInt>(0).value.toInt()
|
||||||
|
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("removeAt") {
|
||||||
|
val self = thisAs<ObjList>()
|
||||||
|
val start = requiredArg<ObjInt>(0).value.toInt()
|
||||||
|
if (args.size == 2) {
|
||||||
|
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||||
|
self.list.subList(start, end).clear()
|
||||||
|
} else
|
||||||
|
self.list.removeAt(start)
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("removeLast") {
|
||||||
|
val self = thisAs<ObjList>()
|
||||||
|
if (args.isNotEmpty()) {
|
||||||
|
val count = requireOnlyArg<ObjInt>().value.toInt()
|
||||||
|
val size = self.list.size
|
||||||
|
if (count >= size) self.list.clear()
|
||||||
|
else self.list.subList(size - count, size).clear()
|
||||||
|
} else self.list.removeLast()
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("removeRange") {
|
||||||
|
val self = thisAs<ObjList>()
|
||||||
|
val list = self.list
|
||||||
|
val range = requiredArg<Obj>(0)
|
||||||
|
if (range is ObjRange) {
|
||||||
|
val index = range
|
||||||
|
when {
|
||||||
|
index.start is ObjInt && index.end is ObjInt -> {
|
||||||
|
if (index.isEndInclusive)
|
||||||
|
list.subList(index.start.toInt(), index.end.toInt() + 1)
|
||||||
|
else
|
||||||
|
list.subList(index.start.toInt(), index.end.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenStart && !index.isOpenEnd -> {
|
||||||
|
if (index.isEndInclusive)
|
||||||
|
list.subList(0, index.end!!.toInt() + 1)
|
||||||
|
else
|
||||||
|
list.subList(0, index.end!!.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenEnd && !index.isOpenStart -> {
|
||||||
|
list.subList(index.start!!.toInt(), list.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
index.isOpenStart && index.isOpenEnd -> {
|
||||||
|
list
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
throw RuntimeException("Can't apply range for index: $index")
|
||||||
|
}
|
||||||
|
}.clear()
|
||||||
|
} else {
|
||||||
|
val start = range.toInt()
|
||||||
|
val end = requiredArg<ObjInt>(1).value.toInt() + 1
|
||||||
|
self.list.subList(start, end).clear()
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if (other !is ObjMapEntry) return -1
|
||||||
|
val c = key.compareTo(scope, other.key)
|
||||||
|
if (c != 0) return c
|
||||||
|
return value.compareTo(scope, other.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAt(scope: Scope, index: Obj): Obj = when (index.toInt()) {
|
||||||
|
0 -> key
|
||||||
|
1 -> value
|
||||||
|
else -> scope.raiseIndexOutOfBounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$key=>$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
override val objClass = type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = object : ObjClass("MapEntry", ObjArray) {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
return ObjMapEntry(scope.requiredArg<Obj>(0), scope.requiredArg<Obj>(1))
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
addFn("key") { thisAs<ObjMapEntry>().key }
|
||||||
|
addFn("value") { thisAs<ObjMapEntry>().value }
|
||||||
|
addFn("size") { 2.toObj() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||||
|
|
||||||
|
override val objClass = type
|
||||||
|
|
||||||
|
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
||||||
|
map.getOrElse(index) { scope.raiseNoSuchElement() }
|
||||||
|
|
||||||
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
return other in map
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if( other is ObjMap && other.map == map) return 0
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
override fun toString(): String = map.toString()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
||||||
|
val map = mutableMapOf<Obj, Obj>()
|
||||||
|
if (list.isEmpty()) return map
|
||||||
|
|
||||||
|
val first = list.first()
|
||||||
|
if (first.isInstanceOf(ObjArray)) {
|
||||||
|
if (first.invokeInstanceMethod(scope, "size").toInt() != 2)
|
||||||
|
scope.raiseIllegalArgument(
|
||||||
|
"list to construct map entry should exactly be 2 element Array like [key,value], got $list"
|
||||||
|
)
|
||||||
|
} else scope.raiseIllegalArgument("first element of map list be a Collection of 2 elements; got $first")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
list.forEach {
|
||||||
|
map[it.getAt(scope, ObjInt.Zero)] = it.getAt(scope, ObjInt.One)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val type = object : ObjClass("Map", ObjCollection) {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
return ObjMap(listToMap(scope, scope.args.list))
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
addFn("getOrNull") {
|
||||||
|
val key = args.firstAndOnly(pos)
|
||||||
|
thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
|
||||||
|
}
|
||||||
|
addFn("getOrPut") {
|
||||||
|
val key = requiredArg<Obj>(0)
|
||||||
|
thisAs<ObjMap>().map.getOrPut(key) {
|
||||||
|
val lambda = requiredArg<Statement>(1)
|
||||||
|
lambda.execute(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFn("size") {
|
||||||
|
thisAs<ObjMap>().map.size.toObj()
|
||||||
|
}
|
||||||
|
addFn("remove") {
|
||||||
|
thisAs<ObjMap>().map.remove(requiredArg<Obj>(0))?.toObj() ?: ObjNull
|
||||||
|
}
|
||||||
|
addFn("clear") {
|
||||||
|
thisAs<ObjMap>().map.clear()
|
||||||
|
thisObj
|
||||||
|
}
|
||||||
|
addFn("keys") {
|
||||||
|
thisAs<ObjMap>().map.keys.toObj()
|
||||||
|
}
|
||||||
|
addFn("values") {
|
||||||
|
ObjList(thisAs<ObjMap>().map.values.toMutableList())
|
||||||
|
}
|
||||||
|
addFn("iterator") {
|
||||||
|
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-11
@@ -2,6 +2,9 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
||||||
|
|
||||||
|
val isOpenStart by lazy { start == null || start.isNull }
|
||||||
|
val isOpenEnd by lazy { end == null || end.isNull }
|
||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@@ -12,10 +15,10 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
// our start is not -∞ so other start should be GTE or is not contained:
|
// our start is not -∞ so other start should be GTE or is not contained:
|
||||||
if (other.start != null && start.compareTo(context, other.start) > 0) return false
|
if (other.start != null && start.compareTo(scope, other.start) > 0) return false
|
||||||
}
|
}
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
// same with the end: if it is open, it can't be contained in ours:
|
// same with the end: if it is open, it can't be contained in ours:
|
||||||
@@ -23,16 +26,16 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
// both exists, now there could be 4 cases:
|
// both exists, now there could be 4 cases:
|
||||||
return when {
|
return when {
|
||||||
other.isEndInclusive && isEndInclusive ->
|
other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
!other.isEndInclusive && !isEndInclusive ->
|
!other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
other.isEndInclusive && !isEndInclusive ->
|
other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) > 0
|
end.compareTo(scope, other.end) > 0
|
||||||
|
|
||||||
!other.isEndInclusive && isEndInclusive ->
|
!other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
else -> throw IllegalStateException("unknown comparison")
|
else -> throw IllegalStateException("unknown comparison")
|
||||||
}
|
}
|
||||||
@@ -40,17 +43,17 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
|
||||||
if (other is ObjRange)
|
if (other is ObjRange)
|
||||||
return containsRange(context, other)
|
return containsRange(scope, other)
|
||||||
|
|
||||||
if (start == null && end == null) return true
|
if (start == null && end == null) return true
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
if (start.compareTo(context, other) > 0) return false
|
if (start.compareTo(scope, other) > 0) return false
|
||||||
}
|
}
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
val cmp = end.compareTo(context, other)
|
val cmp = end.compareTo(scope, other)
|
||||||
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -64,7 +67,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
start is ObjChar && end is ObjChar
|
start is ObjChar && end is ObjChar
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return (other as? ObjRange)?.let {
|
return (other as? ObjRange)?.let {
|
||||||
if( start == other.start && end == other.end ) 0 else -1
|
if( start == other.start && end == other.end ) 0 else -1
|
||||||
}
|
}
|
||||||
+3
-3
@@ -8,7 +8,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
fun Context.init() {
|
fun Scope.init() {
|
||||||
if (self.start == null || self.end == null)
|
if (self.start == null || self.end == null)
|
||||||
raiseError("next is only available for finite ranges")
|
raiseError("next is only available for finite ranges")
|
||||||
isCharRange = self.isCharRange
|
isCharRange = self.isCharRange
|
||||||
@@ -24,7 +24,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
|
|
||||||
fun hasNext(): Boolean = nextIndex < lastIndex
|
fun hasNext(): Boolean = nextIndex < lastIndex
|
||||||
|
|
||||||
fun next(context: Context): Obj =
|
fun next(scope: Scope): Obj =
|
||||||
if (nextIndex < lastIndex) {
|
if (nextIndex < lastIndex) {
|
||||||
val x = if (self.isEndInclusive)
|
val x = if (self.isEndInclusive)
|
||||||
self.start!!.toLong() + nextIndex++
|
self.start!!.toLong() + nextIndex++
|
||||||
@@ -33,7 +33,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.raiseError(ObjIterationFinishedException(context))
|
scope.raiseError(ObjIterationFinishedException(scope))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
+30
-7
@@ -10,32 +10,52 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override fun byValueCopy(): Obj = ObjReal(value)
|
override fun byValueCopy(): Obj = ObjReal(value)
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is Numeric) return -2
|
if (other !is Numeric) return -2
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
override fun toString(): String = value.toString()
|
||||||
|
|
||||||
override val objClass: ObjClass = type
|
override fun hashCode(): Int {
|
||||||
|
return value.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value + other.toDouble())
|
ObjReal(this.value + other.toDouble())
|
||||||
|
|
||||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value - other.toDouble())
|
ObjReal(this.value - other.toDouble())
|
||||||
|
|
||||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value * other.toDouble())
|
ObjReal(this.value * other.toDouble())
|
||||||
|
|
||||||
override suspend fun div(context: Context, other: Obj): Obj =
|
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value / other.toDouble())
|
ObjReal(this.value / other.toDouble())
|
||||||
|
|
||||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value % other.toDouble())
|
ObjReal(this.value % other.toDouble())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unboxed Double value
|
||||||
|
*/
|
||||||
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ObjReal
|
||||||
|
|
||||||
|
return value == other.value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type: ObjClass = ObjClass("Real").apply {
|
val type: ObjClass = ObjClass("Real").apply {
|
||||||
createField(
|
createField(
|
||||||
@@ -44,6 +64,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
addFn("toInt") {
|
||||||
|
ObjInt(thisAs<ObjReal>().value.toLong())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||||
|
|
||||||
|
override val objClass = type
|
||||||
|
|
||||||
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
return set.contains(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
|
return ObjSet(
|
||||||
|
if (other is ObjSet)
|
||||||
|
(set + other.set).toMutableSet()
|
||||||
|
else
|
||||||
|
(set + other).toMutableSet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
|
when (other) {
|
||||||
|
is ObjSet -> {
|
||||||
|
set += other.set
|
||||||
|
}
|
||||||
|
|
||||||
|
is ObjList -> {
|
||||||
|
set += other.list
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
|
val i = other.invokeInstanceMethod(scope, "iterable")
|
||||||
|
while (i.invokeInstanceMethod(scope, "hasNext").toBool()) {
|
||||||
|
set += i.invokeInstanceMethod(scope, "next")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set += other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
|
return if (other is ObjSet) {
|
||||||
|
ObjSet(set.intersect(other.set).toMutableSet())
|
||||||
|
} else
|
||||||
|
scope.raiseIllegalArgument("set operator * requires another set")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
|
if (other !is ObjSet)
|
||||||
|
scope.raiseIllegalArgument("set operator - requires another set")
|
||||||
|
return ObjSet(set.minus(other.set).toMutableSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Set(${set.joinToString(", ")})"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
return if (other !is ObjSet) -1
|
||||||
|
else {
|
||||||
|
if (set == other.set) 0
|
||||||
|
else -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
|
||||||
|
val type = object : ObjClass("Set", ObjCollection) {
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
return ObjSet(scope.args.list.toMutableSet())
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
addFn("size") {
|
||||||
|
thisAs<ObjSet>().set.size.toObj()
|
||||||
|
}
|
||||||
|
addFn("intersect") {
|
||||||
|
thisAs<ObjSet>().mul(this, args.firstAndOnly())
|
||||||
|
}
|
||||||
|
addFn("iterator") {
|
||||||
|
thisAs<ObjSet>().set.iterator().toObj()
|
||||||
|
}
|
||||||
|
addFn("union") {
|
||||||
|
thisAs<ObjSet>().plus(this, args.firstAndOnly())
|
||||||
|
}
|
||||||
|
addFn("subtract") {
|
||||||
|
thisAs<ObjSet>().minus(this, args.firstAndOnly())
|
||||||
|
}
|
||||||
|
addFn("remove") {
|
||||||
|
val set = thisAs<ObjSet>().set
|
||||||
|
val n = set.size
|
||||||
|
for( x in args.list ) set -= x
|
||||||
|
if( n == set.size ) ObjFalse else ObjTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.sprintf.sprintf
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("string")
|
||||||
|
data class ObjString(val value: String) : Obj() {
|
||||||
|
|
||||||
|
// fun normalize(context: Context, index: Int, allowsEndInclusive: Boolean = false): Int {
|
||||||
|
// val i = if (index < 0) value.length + index else index
|
||||||
|
// if (allowsEndInclusive && i == value.length) return i
|
||||||
|
// if (i !in value.indices) context.raiseError("index $index out of bounds for length ${value.length} of \"$value\"")
|
||||||
|
// return i
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
if (other !is ObjString) return -2
|
||||||
|
return this.value.compareTo(other.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = value
|
||||||
|
|
||||||
|
override val asStr: ObjString by lazy { this }
|
||||||
|
|
||||||
|
override fun inspect(): String {
|
||||||
|
return "\"$value\""
|
||||||
|
}
|
||||||
|
|
||||||
|
override val objClass: ObjClass
|
||||||
|
get() = type
|
||||||
|
|
||||||
|
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
|
return ObjString(value + other.asStr.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
|
if( index is ObjInt ) return ObjChar(value[index.toInt()])
|
||||||
|
if( index is ObjRange ) {
|
||||||
|
val start = if(index.start == null || index.start.isNull) 0 else index.start.toInt()
|
||||||
|
val end = if( index.end == null || index.end.isNull ) value.length else {
|
||||||
|
val e = index.end.toInt()
|
||||||
|
if( index.isEndInclusive) e + 1 else e
|
||||||
|
}
|
||||||
|
return ObjString(value.substring(start, end))
|
||||||
|
}
|
||||||
|
scope.raiseIllegalArgument("String index must be Int or Range")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return value.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
return ObjString(this.value.sprintf(*scope.args.toKotlinList(scope).toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
return if (other is ObjString)
|
||||||
|
value.contains(other.value)
|
||||||
|
else if (other is ObjChar)
|
||||||
|
value.contains(other.value)
|
||||||
|
else scope.raiseIllegalArgument("String.contains can't take $other")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as ObjString
|
||||||
|
|
||||||
|
return value == other.value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("String").apply {
|
||||||
|
addFn("toInt") {
|
||||||
|
ObjInt(thisAs<ObjString>().value.toLong())
|
||||||
|
}
|
||||||
|
addFn("startsWith") {
|
||||||
|
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||||
|
}
|
||||||
|
addFn("endsWith") {
|
||||||
|
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
||||||
|
}
|
||||||
|
addConst("length",
|
||||||
|
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
|
)
|
||||||
|
addFn("takeLast") {
|
||||||
|
thisAs<ObjString>().value.takeLast(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("take") {
|
||||||
|
thisAs<ObjString>().value.take(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("drop") {
|
||||||
|
thisAs<ObjString>().value.drop(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("dropLast") {
|
||||||
|
thisAs<ObjString>().value.dropLast(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("lower") {
|
||||||
|
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("upper") {
|
||||||
|
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
|
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
-24
@@ -45,12 +45,18 @@ private class Parser(fromPos: Pos) {
|
|||||||
'=' -> {
|
'=' -> {
|
||||||
if (pos.currentChar == '=') {
|
if (pos.currentChar == '=') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
if (currentChar == '=') {
|
when (currentChar) {
|
||||||
pos.advance()
|
'=' -> {
|
||||||
Token("===", from, Token.Type.REF_EQ)
|
pos.advance()
|
||||||
} else
|
Token("===", from, Token.Type.REF_EQ)
|
||||||
Token("==", from, Token.Type.EQ)
|
}
|
||||||
} else
|
else -> Token("==", from, Token.Type.EQ)
|
||||||
|
}
|
||||||
|
} else if( currentChar == '>' ) {
|
||||||
|
pos.advance()
|
||||||
|
Token("=>", from, Token.Type.EQARROW)
|
||||||
|
}
|
||||||
|
else
|
||||||
Token("=", from, Token.Type.ASSIGN)
|
Token("=", from, Token.Type.ASSIGN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,14 +143,20 @@ private class Parser(fromPos: Pos) {
|
|||||||
if (currentChar == '.') {
|
if (currentChar == '.') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
// .. already parsed:
|
// .. already parsed:
|
||||||
if (currentChar == '.') {
|
when (currentChar) {
|
||||||
pos.advance()
|
'.' -> {
|
||||||
Token("...", from, Token.Type.ELLIPSIS)
|
pos.advance()
|
||||||
} else if (currentChar == '<') {
|
Token("...", from, Token.Type.ELLIPSIS)
|
||||||
pos.advance()
|
}
|
||||||
Token("..<", from, Token.Type.DOTDOTLT)
|
|
||||||
} else {
|
'<' -> {
|
||||||
Token("..", from, Token.Type.DOTDOT)
|
pos.advance()
|
||||||
|
Token("..<", from, Token.Type.DOTDOTLT)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Token("..", from, Token.Type.DOTDOT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Token(".", from, Token.Type.DOT)
|
Token(".", from, Token.Type.DOT)
|
||||||
@@ -153,11 +165,10 @@ private class Parser(fromPos: Pos) {
|
|||||||
'<' -> {
|
'<' -> {
|
||||||
if (currentChar == '=') {
|
if (currentChar == '=') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
if( currentChar == '>' ) {
|
if (currentChar == '>') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
Token("<=>", from, Token.Type.SHUTTLE)
|
Token("<=>", from, Token.Type.SHUTTLE)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Token("<=", from, Token.Type.LTE)
|
Token("<=", from, Token.Type.LTE)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -236,6 +247,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
'"' -> loadStringToken()
|
'"' -> loadStringToken()
|
||||||
|
|
||||||
in digitsSet -> {
|
in digitsSet -> {
|
||||||
pos.back()
|
pos.back()
|
||||||
decodeNumber(loadChars(digits), from)
|
decodeNumber(loadChars(digits), from)
|
||||||
@@ -261,6 +273,21 @@ private class Parser(fromPos: Pos) {
|
|||||||
Token(value.toString(), start, Token.Type.CHAR)
|
Token(value.toString(), start, Token.Type.CHAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'?' -> {
|
||||||
|
when(currentChar.also { pos.advance() }) {
|
||||||
|
':' -> Token("??", from, Token.Type.ELVIS)
|
||||||
|
'?' -> Token("??", from, Token.Type.ELVIS)
|
||||||
|
'.' -> Token("?.", from, Token.Type.NULL_COALESCE)
|
||||||
|
'[' -> Token("?(", from, Token.Type.NULL_COALESCE_INDEX)
|
||||||
|
'(' -> Token("?(", from, Token.Type.NULL_COALESCE_INVOKE)
|
||||||
|
'{' -> Token("?{", from, Token.Type.NULL_COALESCE_BLOCKINVOKE)
|
||||||
|
else -> {
|
||||||
|
pos.back()
|
||||||
|
Token("?", from, Token.Type.QUESTION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// text infix operators:
|
// text infix operators:
|
||||||
// Labels processing is complicated!
|
// Labels processing is complicated!
|
||||||
@@ -280,8 +307,6 @@ private class Parser(fromPos: Pos) {
|
|||||||
when (text) {
|
when (text) {
|
||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
"protected" -> Token("protected", from, Token.Type.PROTECTED)
|
|
||||||
"private" -> Token("private", from, Token.Type.PRIVATE)
|
|
||||||
else -> Token(text, from, Token.Type.ID)
|
else -> Token(text, from, Token.Type.ID)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -293,7 +318,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
private fun decodeNumber(p1: String, start: Pos): Token =
|
private fun decodeNumber(p1: String, start: Pos): Token =
|
||||||
if (pos.end)
|
if (pos.end)
|
||||||
Token(p1, start, Token.Type.INT)
|
Token(p1, start, Token.Type.INT)
|
||||||
else if( currentChar == 'e' || currentChar == 'E' ) {
|
else if (currentChar == 'e' || currentChar == 'E') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
var negative = false
|
var negative = false
|
||||||
if (currentChar == '+')
|
if (currentChar == '+')
|
||||||
@@ -338,7 +363,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
// could be integer, also hex:
|
// could be integer, also hex:
|
||||||
if (currentChar == 'x' && p1 == "0") {
|
if (currentChar == 'x' && p1 == "0") {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also {
|
Token(loadChars { it in hexDigits }, start, Token.Type.HEX).also {
|
||||||
if (currentChar.isLetter())
|
if (currentChar.isLetter())
|
||||||
raise("invalid hex literal")
|
raise("invalid hex literal")
|
||||||
}
|
}
|
||||||
@@ -351,10 +376,11 @@ private class Parser(fromPos: Pos) {
|
|||||||
private val currentChar: Char get() = pos.currentChar
|
private val currentChar: Char get() = pos.currentChar
|
||||||
|
|
||||||
private fun loadStringToken(): Token {
|
private fun loadStringToken(): Token {
|
||||||
var start = currentPos
|
val start = currentPos
|
||||||
|
|
||||||
if (currentChar == '"') pos.advance()
|
// if (currentChar == '"') pos.advance()
|
||||||
else start = start.back()
|
// else start = start.back()
|
||||||
|
// start = start.back()
|
||||||
|
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
while (currentChar != '"') {
|
while (currentChar != '"') {
|
||||||
+1
@@ -5,6 +5,7 @@ data class Pos(val source: Source, val line: Int, val column: Int) {
|
|||||||
return "${source.fileName}:${line+1}:${column}"
|
return "${source.fileName}:${line+1}:${column}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun back(): Pos =
|
fun back(): Pos =
|
||||||
if( column > 0) Pos(source, line, column-1)
|
if( column > 0) Pos(source, line, column-1)
|
||||||
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
|
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
|
||||||
+48
-19
@@ -1,29 +1,42 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
class Context(
|
/**
|
||||||
val parent: Context?,
|
* Scope is where local variables and methods are stored. Scope is also a parent scope for other scopes.
|
||||||
|
* Each block usually creates a scope. Accessing Lyng closures usually is done via a scope.
|
||||||
|
*
|
||||||
|
* There are special types of scopes:
|
||||||
|
*
|
||||||
|
* - [Script.defaultScope] - root scope for a script, safe one
|
||||||
|
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
|
||||||
|
*/
|
||||||
|
open class Scope(
|
||||||
|
val parent: Scope?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
var pos: Pos = Pos.builtIn,
|
var pos: Pos = Pos.builtIn,
|
||||||
val thisObj: Obj = ObjVoid,
|
var thisObj: Obj = ObjVoid,
|
||||||
var skipContextCreation: Boolean = false,
|
var skipScopeCreation: Boolean = false,
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
)
|
)
|
||||||
: this(Script.defaultContext, args, pos)
|
: this(Script.defaultScope, args, pos)
|
||||||
|
|
||||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerException(this))
|
fun raiseNPE(): Nothing = raiseError(ObjNullReferenceException(this))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||||
raiseError(ObjIndexOutOfBoundsException(this, message))
|
raiseError(ObjIndexOutOfBoundsException(this, message))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
fun raiseIllegalArgument(message: String = "Illegal argument error"): Nothing =
|
||||||
|
raiseError(ObjIllegalArgumentException(this, message))
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun raiseNoSuchElement(message: String = "No such element"): Nothing =
|
||||||
raiseError(ObjIllegalArgumentException(this, message))
|
raiseError(ObjIllegalArgumentException(this, message))
|
||||||
|
|
||||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
||||||
@@ -63,17 +76,21 @@ class Context(
|
|||||||
|
|
||||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
operator fun get(name: String): ObjRecord? =
|
open operator fun get(name: String): ObjRecord? =
|
||||||
objects[name]
|
if (name == "this") thisObj.asReadonly
|
||||||
?: parent?.get(name)
|
else {
|
||||||
|
objects[name]
|
||||||
|
?: parent?.get(name)
|
||||||
|
?: thisObj.objClass.getInstanceMemberOrNull(name)
|
||||||
|
}
|
||||||
|
|
||||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
|
||||||
Context(this, args, pos, newThisObj ?: thisObj)
|
Scope(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
|
||||||
Context(this, args, pos, newThisObj ?: thisObj)
|
Scope(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun copy() = Context(this, args, pos, thisObj)
|
fun copy() = Scope(this, args, pos, thisObj)
|
||||||
|
|
||||||
fun addItem(
|
fun addItem(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -89,18 +106,18 @@ class Context(
|
|||||||
return ns.objClass
|
return ns.objClass
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Context.() -> Unit) {
|
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
|
||||||
addFn<ObjVoid>(*names) {
|
addFn<ObjVoid>(*names) {
|
||||||
fn(this)
|
fn(this)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
|
inline fun <reified T : Obj> addFn(vararg names: String, crossinline fn: suspend Scope.() -> T) {
|
||||||
val newFn = object : Statement() {
|
val newFn = object : Statement() {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj = context.fn()
|
override suspend fun execute(scope: Scope): Obj = scope.fn()
|
||||||
|
|
||||||
}
|
}
|
||||||
for (name in names) {
|
for (name in names) {
|
||||||
@@ -115,9 +132,21 @@ class Context(
|
|||||||
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
||||||
|
|
||||||
suspend fun eval(code: String): Obj =
|
suspend fun eval(code: String): Obj =
|
||||||
Compiler().compile(code.toSource()).execute(this)
|
Compiler.compile(code.toSource()).execute(this)
|
||||||
|
|
||||||
|
suspend fun eval(source: Source): Obj =
|
||||||
|
Compiler.compile(
|
||||||
|
source,
|
||||||
|
(this as? ModuleScope)?.pacman?.also { println("pacman found: $pacman")} ?: Pacman.emptyAllowAll
|
||||||
|
).execute(this)
|
||||||
|
|
||||||
fun containsLocal(name: String): Boolean = name in objects
|
fun containsLocal(name: String): Boolean = name in objects
|
||||||
|
|
||||||
|
open suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
throw ImportException(pos, "Import is not allowed here: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
scope.raiseError(ObjIllegalOperationException(scope,"Import is not allowed here: import $name"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+11
-5
@@ -9,18 +9,18 @@ class Script(
|
|||||||
// private val catchReturn: Boolean = false,
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
var lastResult: Obj = ObjVoid
|
var lastResult: Obj = ObjVoid
|
||||||
for (s in statements) {
|
for (s in statements) {
|
||||||
lastResult = s.execute(context)
|
lastResult = s.execute(scope)
|
||||||
}
|
}
|
||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
suspend fun execute() = execute(defaultScope.copy(pos = pos))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context().apply {
|
val defaultScope: Scope = Scope().apply {
|
||||||
ObjException.addExceptionsToContext(this)
|
ObjException.addExceptionsToContext(this)
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
for ((i, a) in args.withIndex()) {
|
for ((i, a) in args.withIndex()) {
|
||||||
@@ -146,26 +146,32 @@ class Script(
|
|||||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addConst("Object", rootObjectType)
|
||||||
addConst("Real", ObjReal.type)
|
addConst("Real", ObjReal.type)
|
||||||
addConst("String", ObjString.type)
|
addConst("String", ObjString.type)
|
||||||
addConst("Int", ObjInt.type)
|
addConst("Int", ObjInt.type)
|
||||||
addConst("Bool", ObjBool.type)
|
addConst("Bool", ObjBool.type)
|
||||||
addConst("Char", ObjChar.type)
|
addConst("Char", ObjChar.type)
|
||||||
addConst("List", ObjList.type)
|
addConst("List", ObjList.type)
|
||||||
|
addConst("Set", ObjSet.type)
|
||||||
addConst("Range", ObjRange.type)
|
addConst("Range", ObjRange.type)
|
||||||
|
addConst("Map", ObjMap.type)
|
||||||
|
addConst("MapEntry", ObjMapEntry.type)
|
||||||
@Suppress("RemoveRedundantQualifierName")
|
@Suppress("RemoveRedundantQualifierName")
|
||||||
addConst("Callable", Statement.type)
|
addConst("Callable", Statement.type)
|
||||||
// interfaces
|
// interfaces
|
||||||
addConst("Iterable", ObjIterable)
|
addConst("Iterable", ObjIterable)
|
||||||
|
addConst("Collection", ObjCollection)
|
||||||
addConst("Array", ObjArray)
|
addConst("Array", ObjArray)
|
||||||
addConst("Class", ObjClassType)
|
addConst("Class", ObjClassType)
|
||||||
addConst("Object", Obj().objClass)
|
|
||||||
|
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
addConst("π", pi)
|
addConst("π", pi)
|
||||||
getOrCreateNamespace("Math").apply {
|
getOrCreateNamespace("Math").apply {
|
||||||
addConst("PI", pi)
|
addConst("PI", pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=null) : Exception(
|
open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable? = null) : Exception(
|
||||||
"""
|
"""
|
||||||
$pos: Error: $errorMessage
|
$pos: Error: $errorMessage
|
||||||
|
|
||||||
@@ -12,4 +12,6 @@ open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=
|
|||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.context.pos, errorObject.message)
|
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message)
|
||||||
|
|
||||||
|
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
interface SecurityManager {
|
||||||
|
/**
|
||||||
|
* Check that any symbol from the corresponding module can be imported. If it returns false,
|
||||||
|
* the module will not be imported and no further processing will be done
|
||||||
|
*/
|
||||||
|
fun canImportModule(name: String): Boolean
|
||||||
|
|
||||||
|
/**\
|
||||||
|
* if [canImportModule] this method allows fine-grained control over symbols.
|
||||||
|
*/
|
||||||
|
fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val allowAll: SecurityManager = object : SecurityManager {
|
||||||
|
override fun canImportModule(name: String): Boolean = true
|
||||||
|
override fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File renamed without changes.
+6
-2
@@ -14,14 +14,18 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
IN, NOTIN, IS, NOTIS,
|
IN, NOTIN, IS, NOTIS,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
|
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
|
||||||
SHUTTLE,
|
SHUTTLE,
|
||||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL, ATLABEL, // label@ at@label
|
LABEL, ATLABEL, // label@ at@label
|
||||||
PRIVATE, PROTECTED,
|
|
||||||
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
EOF,
|
EOF,
|
||||||
|
NULL_COALESCE,
|
||||||
|
ELVIS,
|
||||||
|
NULL_COALESCE_INDEX,
|
||||||
|
NULL_COALESCE_INVOKE,
|
||||||
|
NULL_COALESCE_BLOCKINVOKE,
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
// this is highly experimental and subject to complete redesign
|
||||||
|
// very soon
|
||||||
|
sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||||
|
// ??
|
||||||
|
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
||||||
|
object TypeAny : TypeDecl()
|
||||||
|
object TypeNullableAny : TypeDecl(true)
|
||||||
|
|
||||||
|
class Simple(val name: String,isNullable: Boolean) : TypeDecl(isNullable)
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
+10
-8
@@ -18,14 +18,14 @@ abstract class Statement(
|
|||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
abstract val pos: Pos
|
abstract val pos: Pos
|
||||||
abstract suspend fun execute(context: Context): Obj
|
abstract suspend fun execute(scope: Scope): Obj
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context,other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
throw UnsupportedOperationException("not comparable")
|
throw UnsupportedOperationException("not comparable")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return execute(context)
|
return execute(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||||
@@ -34,6 +34,8 @@ abstract class Statement(
|
|||||||
val type = ObjClass("Callable")
|
val type = ObjClass("Callable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.copy(args = Arguments(*args)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Statement.raise(text: String): Nothing {
|
fun Statement.raise(text: String): Nothing {
|
||||||
@@ -45,16 +47,16 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
|||||||
if (!cond) raise(message())
|
if (!cond) raise(message())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
|
||||||
object : Statement(isStaticConst, isConst) {
|
object : Statement(isStaticConst, isConst) {
|
||||||
override val pos: Pos = pos
|
override val pos: Pos = pos
|
||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Context.() -> Obj): Statement =
|
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
|
||||||
object : Statement(isStaticConst, isConst) {
|
object : Statement(isStaticConst, isConst) {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
+656
-124
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
|||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.eval
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TypesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection1() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Point(x: Real, y: Real)
|
||||||
|
assert(Point(1,2).x == 1)
|
||||||
|
assert(Point(1,2).y == 2)
|
||||||
|
assert(Point(1,2) is Point)
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection2() = runTest {
|
||||||
|
eval("""
|
||||||
|
fun fn1(x: Real, y: Real): Real { x + y }
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection3() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Test(a: Int) {
|
||||||
|
fun fn1(x: Real, y: Real): Real { x + y }
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExternDeclarations() = runTest {
|
||||||
|
eval("""
|
||||||
|
extern fun foo1(a: String): Void
|
||||||
|
assertThrows { foo1("1") }
|
||||||
|
class Test(a: Int) {
|
||||||
|
extern fun fn1(x: Real, y: Real): Real
|
||||||
|
// extern val b: Int
|
||||||
|
}
|
||||||
|
// println("1")
|
||||||
|
val t = Test(0)
|
||||||
|
// println(t.b)
|
||||||
|
// println("2")
|
||||||
|
assertThrows {
|
||||||
|
t.fn1(1,2)
|
||||||
|
}
|
||||||
|
// println("4")
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Context
|
|
||||||
import net.sergeych.lyng.ObjVoid
|
import net.sergeych.lyng.ObjVoid
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Files.readAllLines
|
import java.nio.file.Files.readAllLines
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@@ -158,10 +158,10 @@ fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow<DocTest> =
|
|||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun DocTest.test(context: Context = Context()) {
|
suspend fun DocTest.test(scope: Scope = Scope()) {
|
||||||
val collectedOutput = StringBuilder()
|
val collectedOutput = StringBuilder()
|
||||||
val currentTest = this
|
val currentTest = this
|
||||||
context.apply {
|
scope.apply {
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
if( bookMode ) {
|
if( bookMode ) {
|
||||||
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}")
|
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}")
|
||||||
@@ -177,7 +177,7 @@ suspend fun DocTest.test(context: Context = Context()) {
|
|||||||
}
|
}
|
||||||
var error: Throwable? = null
|
var error: Throwable? = null
|
||||||
val result = try {
|
val result = try {
|
||||||
context.eval(code)
|
scope.eval(code)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
error = e
|
error = e
|
||||||
null
|
null
|
||||||
@@ -204,10 +204,10 @@ suspend fun DocTest.test(context: Context = Context()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
|
suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
|
||||||
val bookContext = Context()
|
val bookScope = Scope()
|
||||||
var count = 0
|
var count = 0
|
||||||
parseDocTests(fileName, bookMode).collect { dt ->
|
parseDocTests(fileName, bookMode).collect { dt ->
|
||||||
if (bookMode) dt.test(bookContext)
|
if (bookMode) dt.test(bookScope)
|
||||||
else dt.test()
|
else dt.test()
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@@ -255,6 +255,16 @@ class BookTest {
|
|||||||
runDocTests("../docs/Range.md")
|
runDocTests("../docs/Range.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSet() = runTest {
|
||||||
|
runDocTests("../docs/Set.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMap() = runTest {
|
||||||
|
runDocTests("../docs/Map.md")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSampleBooks() = runTest {
|
fun testSampleBooks() = runTest {
|
||||||
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
||||||
+2
-2
@@ -2,7 +2,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import net.sergeych.lyng.Context
|
import net.sergeych.lyng.Scope
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.io.path.extension
|
import kotlin.io.path.extension
|
||||||
@@ -13,7 +13,7 @@ suspend fun executeSampleTests(fileName: String) {
|
|||||||
Files.readString(Paths.get(fileName))
|
Files.readString(Paths.get(fileName))
|
||||||
}
|
}
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
val start = Clock.System.now()
|
val start = Clock.System.now()
|
||||||
c.eval(sample)
|
c.eval(sample)
|
||||||
val time = Clock.System.now() - start
|
val time = Clock.System.now() - start
|
||||||
+1
-1
@@ -18,5 +18,5 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "lyng"
|
rootProject.name = "lyng"
|
||||||
include(":library")
|
include(":lynglib")
|
||||||
include(":lyng")
|
include(":lyng")
|
||||||
Reference in new issue
Block a user