Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
0981d8370e | |||
c4122e8243 | |||
c3bf536bab | |||
5ed8b2f123 | |||
6c71f0a2e6 | |||
95aae0b231 | |||
b3f08b4cac | |||
badeea9b28 | |||
a1267c4395 | |||
55fd3ea716 | |||
697bafdcee | |||
28b83f9892 | |||
194fc8aca6 | |||
382532e0e1 | |||
c0eba1ecf0 | |||
b253eed032 | |||
d482401b15 | |||
88b355c40d | |||
652e1d3af4 | |||
2a93e6f7da | |||
20c81dbf2e | |||
fffa3d31bb |
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ xcuserdata
|
||||
*.gpg
|
||||
.gigaide
|
||||
/kotlin-js-store/yarn.lock
|
||||
/test.lyng
|
||||
|
95
README.md
95
README.md
@ -9,6 +9,10 @@ class Point(x,y) {
|
||||
fun dist() { sqrt(x*x + y*y) }
|
||||
}
|
||||
Point(3,4).dist() //< 5
|
||||
|
||||
fun swapEnds(first, args..., last, f) {
|
||||
f( last, ...args, first)
|
||||
}
|
||||
```
|
||||
|
||||
- extremely simple Kotlin integration on any platform
|
||||
@ -31,16 +35,42 @@ and it is multithreaded on platforms supporting it (automatically, no code chang
|
||||
|
||||
## 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:
|
||||
|
||||
```kotlin
|
||||
assertEquals("hello, world", eval("""
|
||||
"hello, " + "world"
|
||||
""").toString())
|
||||
import net.sergeyh.lyng.*
|
||||
|
||||
// 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
|
||||
@ -49,6 +79,8 @@ Script is executed over some `Context`. Create instance of the context,
|
||||
add your specific vars and functions to it, an call over it:
|
||||
|
||||
```kotlin
|
||||
import new.sergeych.lyng.*
|
||||
|
||||
// simple function
|
||||
val context = Context().apply {
|
||||
addFn("addArgs") {
|
||||
@ -83,22 +115,51 @@ Designed to add scripting to kotlin multiplatform application in easy and effici
|
||||
|
||||
# Language
|
||||
|
||||
- dynamic
|
||||
- async
|
||||
- multithreaded (coroutines could be dispatched using threads on appropriate platforms, automatically)
|
||||
- Javascript, WasmJS, native, JVM, android - batteries included.
|
||||
- dynamic types in most elegant and concise way
|
||||
- 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
|
||||
- p-code serialization
|
||||
- static typing
|
||||
- [ ] maps, sets and sequences (flows?)
|
||||
- [ ] regular exceptions
|
||||
- [ ] 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
|
3
bin/lyng_test
Executable file
3
bin/lyng_test
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env lyng
|
||||
|
||||
println("Hello from lyng!")
|
13
docs/Collection.md
Normal file
13
docs/Collection.md
Normal file
@ -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
|
@ -1,13 +1,17 @@
|
||||
# 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:
|
||||
|
||||
interface Iterable {
|
||||
fun hasNext(): Bool
|
||||
class Iterator {
|
||||
abstract fun hasNext(): Bool
|
||||
fun next(): Obj
|
||||
}
|
||||
|
||||
@ -15,19 +19,26 @@ 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:
|
||||
|
||||
## Instance methods
|
||||
|
||||
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
|
||||
|
||||
|
||||
## Abstract methods
|
||||
|
||||
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.
|
||||
|
||||
## Included in interfaces:
|
||||
|
||||
- Collection, Array, [List]
|
||||
- [Collection], Array, [List]
|
||||
|
||||
## Implemented in classes:
|
||||
|
||||
|
79
docs/List.md
79
docs/List.md
@ -20,11 +20,11 @@ indexing is zero-based, as in C/C++/Java/Kotlin, etc.
|
||||
list[1]
|
||||
>>> 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]
|
||||
list[-1]
|
||||
>>> 30
|
||||
[list.last, list.lastIndex]
|
||||
>>> [30, 2]
|
||||
|
||||
__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
|
||||
|
||||
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]
|
||||
val other = [3, 4]
|
||||
@ -57,7 +58,28 @@ To append to lists, use `+=` with elements, lists and any [Iterable] instances,
|
||||
|
||||
>>> 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
|
||||
|
||||
@ -72,19 +94,44 @@ To append to lists, use `+=` with elements, lists and any [Iterable] instances,
|
||||
|
||||
## Members
|
||||
|
||||
| name | meaning | type |
|
||||
|-----------------------------------|-------------------------------------|----------|
|
||||
| `size` | current size | Int |
|
||||
| `add(elements...)` | add one or more elements to the end | Any |
|
||||
| `addAt(index,elements...)` | insert elements at position | Int, Any |
|
||||
| `removeAt(index)` | remove element at position | Int |
|
||||
| `removeRangeInclusive(start,end)` | remove range, inclusive (1) | Int, Int |
|
||||
| | | |
|
||||
| name | meaning | type |
|
||||
|-------------------------------|---------------------------------------|-------------|
|
||||
| `size` | current size | Int |
|
||||
| `add(elements...)` | add one or more elements to the end | Any |
|
||||
| `insertAt(index,elements...)` | insert elements at position | Int, Any |
|
||||
| `removeAt(index)` | remove element at position | 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)
|
||||
: 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
|
||||
|
||||
Could be rewritten using array as a class but List as the interface
|
||||
[Range]: Range.md
|
||||
[Iterable]: Iterable.md
|
109
docs/Map.md
Normal file
109
docs/Map.md
Normal file
@ -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)
|
199
docs/OOP.md
199
docs/OOP.md
@ -1,6 +1,168 @@
|
||||
# OO implementation in Lyng
|
||||
|
||||
Basic principles:
|
||||
## Declaration
|
||||
|
||||
The class clause looks like
|
||||
|
||||
class Point(x,y)
|
||||
assert( Point is Class )
|
||||
>>> void
|
||||
|
||||
It creates new `Class` with two fields. Here is the more practical sample:
|
||||
|
||||
class Point(x,y) {
|
||||
fun length() { sqrt(x*x + y*y) }
|
||||
}
|
||||
|
||||
val p = Point(3,4)
|
||||
assert(p is Point)
|
||||
assertEquals(5, p.length())
|
||||
|
||||
// we can access the fields:
|
||||
assert( p.x == 3 )
|
||||
assert( p.y == 4 )
|
||||
|
||||
// we can assign new values to fields:
|
||||
p.x = 1
|
||||
p.y = 1
|
||||
assertEquals(sqrt(2), p.length())
|
||||
>>> void
|
||||
|
||||
|
||||
Let's see in details. The statement `class Point(x,y)` creates a class,
|
||||
with two field, which are mutable and publicly visible.`(x,y)` here
|
||||
is the [argument list], same as when defining a function. All together creates a class with
|
||||
a _constructor_ that requires two parameters for fields. So when creating it with
|
||||
`Point(10, 20)` we say _calling Point constructor_ with these parameters.
|
||||
|
||||
Form now on `Point` is a class, it's type is `Class`, and we can create instances with it as in the
|
||||
example above.
|
||||
|
||||
Class point has a _method_, or a _member function_ `length()` that uses its _fields_ `x` and `y` to
|
||||
calculate the magnitude. Length is called
|
||||
|
||||
### default values in constructor
|
||||
|
||||
Constructor arguments are the same as function arguments except visibility
|
||||
statements discussed later, there could be default values, ellipsis, etc.
|
||||
|
||||
class Point(x=0,y=0)
|
||||
val p = Point()
|
||||
assert( p.x == 0 && p.y == 0 )
|
||||
>>> void
|
||||
|
||||
## Methods
|
||||
|
||||
Functions defined inside a class body are methods, and unless declared
|
||||
`private` are available to be called from outside the class:
|
||||
|
||||
class Point(x,y) {
|
||||
// public method declaration:
|
||||
fun length() { sqrt(d2()) }
|
||||
|
||||
// private method:
|
||||
private fun d2() {x*x + y*y}
|
||||
}
|
||||
val p = Point(3,4)
|
||||
// private called from inside public: OK
|
||||
assertEquals( 5, p.length() )
|
||||
// but us not available directly
|
||||
assertThrows { p.d2() }
|
||||
void
|
||||
>>> void
|
||||
|
||||
## fields and visibility
|
||||
|
||||
It is possible to add non-constructor fields:
|
||||
|
||||
class Point(x,y) {
|
||||
fun length() { sqrt(x*x + y*y) }
|
||||
|
||||
// set at construction time:
|
||||
val initialLength = length()
|
||||
}
|
||||
val p = Point(3,4)
|
||||
p.x = 3
|
||||
p.y = 0
|
||||
assertEquals( 3, p.length() )
|
||||
// but initial length could not be changed after as declard val:
|
||||
assert( p.initialLength == 5 )
|
||||
>>> void
|
||||
|
||||
### Mutable fields
|
||||
|
||||
Are declared with var
|
||||
|
||||
class Point(x,y) {
|
||||
var isSpecial = false
|
||||
}
|
||||
val p = Point(0,0)
|
||||
assert( p.isSpecial == false )
|
||||
|
||||
p.isSpecial = true
|
||||
assert( p.isSpecial == true )
|
||||
>>> void
|
||||
|
||||
### Private fields
|
||||
|
||||
Private fields are visible only _inside the class instance_:
|
||||
|
||||
class SecretCounter {
|
||||
private var count = 0
|
||||
|
||||
fun increment() {
|
||||
count++
|
||||
void // hide counter
|
||||
}
|
||||
|
||||
fun isEnough() {
|
||||
count > 10
|
||||
}
|
||||
}
|
||||
val c = SecretCounter()
|
||||
assert( c.isEnough() == false )
|
||||
assert( c.increment() == void )
|
||||
for( i in 0..10 ) c.increment()
|
||||
assert( c.isEnough() )
|
||||
|
||||
// but the count is not available outside:
|
||||
assertThrows { c.count }
|
||||
void
|
||||
>>> void
|
||||
|
||||
It is possible to provide private constructor parameters so they can be
|
||||
set at construction but not available outside the class:
|
||||
|
||||
class SecretCounter(private var count = 0) {
|
||||
// ...
|
||||
}
|
||||
val c = SecretCounter(10)
|
||||
assertThrows { c.count }
|
||||
void
|
||||
>>> void
|
||||
|
||||
## Default class methods
|
||||
|
||||
In many cases it is necessary to implement custom comparison and `toString`, still
|
||||
each class is provided with default implementations:
|
||||
|
||||
- default toString outputs class name and its _public_ fields.
|
||||
- default comparison compares compares all public fields in order of appearance.
|
||||
|
||||
For example, for our class Point:
|
||||
|
||||
class Point(x,y)
|
||||
assert( Point(1,2) == Point(1,2) )
|
||||
assert( Point(1,2) !== Point(1,2) )
|
||||
assert( Point(1,2) != Point(1,3) )
|
||||
assert( Point(1,2) < Point(2,2) )
|
||||
assert( Point(1,2) < Point(1,3) )
|
||||
Point(1,1+1)
|
||||
>>> Point(x=1,y=2)
|
||||
|
||||
# Theory
|
||||
|
||||
## Basic principles:
|
||||
|
||||
- Everything is an instance of some class
|
||||
- Every class except Obj has at least one parent
|
||||
@ -71,41 +233,6 @@ Regular methods are called on instances as usual `instance.method()`. The method
|
||||
2. parents method: no guarantee but we enumerate parents in order of appearance;
|
||||
3. possible extension methods (scoped)
|
||||
|
||||
# Defining a new class
|
||||
|
||||
The class is a some data record with named fields and fixed order, in fact. To define a class,
|
||||
just Provide a name and a record like this:
|
||||
|
||||
// creating new class with main constructor
|
||||
// with all fields public and mutable:
|
||||
|
||||
struct Point(x,y)
|
||||
assert( Point is Class )
|
||||
|
||||
// now we can create instance
|
||||
val p1 = Point(3,4)
|
||||
|
||||
// is is of the newly created type:
|
||||
assert( p1 is Point )
|
||||
|
||||
// we can read and write its fields:
|
||||
assert( p1.x == 3 )
|
||||
assert( p1.y == 4 )
|
||||
|
||||
p1.y++
|
||||
assert( p1.y == 5 )
|
||||
|
||||
>>> void
|
||||
|
||||
Let's see in details. The statement `struct Point(x,y)` creates a struct, or public class,
|
||||
with two field, which are mutable and publicly visible, because it is _struct_. `(x,y)` here
|
||||
is the [argument list], same as when defining a function. All together creates a class with
|
||||
a _constructor_ that requires two parameters for fields. So when creating it with
|
||||
`Point(10, 20)` we say _calling Point constructor_ with these parameters.
|
||||
|
||||
Such declaration is identical to `class Point(var x,var y)` which does exactly the same.
|
||||
|
||||
|
||||
TBD
|
||||
|
||||
[argument list](declaring_arguments.md)
|
94
docs/Set.md
Normal file
94
docs/Set.md
Normal file
@ -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
|
0
docs/String.md
Normal file
0
docs/String.md
Normal file
@ -86,4 +86,28 @@ Lambda functions remember their scopes, so it will work the same as previous:
|
||||
val c = createLambda()
|
||||
println(c)
|
||||
>> 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
|
||||
|
||||
,
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Declaring arguments in Lyng
|
||||
|
||||
It is a common thing that occurs in many places in Lyng, function declarations,
|
||||
lambdas, struct and class declarations.
|
||||
lambdas and class declarations.
|
||||
|
||||
## Regular
|
||||
|
||||
|
@ -39,22 +39,5 @@ We can just put the code into the module code:
|
||||
|
||||
## class initialization
|
||||
|
||||
class foo {
|
||||
|
||||
private static var instanceCounter = 0
|
||||
|
||||
val instanceId = instanceCounter
|
||||
|
||||
fun close() {
|
||||
instanceCounter--
|
||||
}
|
||||
|
||||
// instance initializatino could be as this:
|
||||
if( instanceId > 100 )
|
||||
throw Exception("Too many instances")
|
||||
|
||||
static {
|
||||
// static, one-per-class initializer could be posted here
|
||||
instanceCounter = 1
|
||||
}
|
||||
}
|
||||
already done using `ObjInstance` class and instance-bound context with local
|
||||
context stored in ObjInstance and class constructor statement in ObjClass.
|
145
docs/exceptions_handling.md
Normal file
145
docs/exceptions_handling.md
Normal file
@ -0,0 +1,145 @@
|
||||
# Exceptions handling
|
||||
|
||||
Exceptions are widely used in modern programming languages, so
|
||||
they are implemented also in Lyng and in the most complete way.
|
||||
|
||||
# Exception classes
|
||||
|
||||
Exceptions are throwing instances of some class that inherits `Exception`
|
||||
across the code. Below is the list of built-in exceptions. Note that
|
||||
only objects that inherit `Exception` can be thrown. For example:
|
||||
|
||||
assert( IllegalArgumentException() is Exception)
|
||||
>>> void
|
||||
|
||||
# Try statement: catching exceptions
|
||||
|
||||
There general pattern is:
|
||||
|
||||
```
|
||||
try_statement = try_clause, [catch_clause, ...], [finally_clause]
|
||||
|
||||
try_clause = "try", "{", statements, "}"
|
||||
|
||||
catch_clause = "catch", [(full_catch | shorter_catch)], "{", statements "}"
|
||||
|
||||
full_catch = "(", catch_var, ":", exception_class [, excetpion_class...], ")
|
||||
|
||||
shorter_catch = "(", catch_var, ")"
|
||||
|
||||
finally_clause = "{", statements, "}"
|
||||
```
|
||||
|
||||
Let's in details.
|
||||
|
||||
## Full catch block:
|
||||
|
||||
val result = try {
|
||||
throw IllegalArgumentException("the test")
|
||||
}
|
||||
catch( x: IndexOutOfBoundsException, IllegalArgumentException) {
|
||||
x.message
|
||||
}
|
||||
catch(x: Exception) {
|
||||
"bad"
|
||||
}
|
||||
assertEquals(result, "the test")
|
||||
>>> void
|
||||
|
||||
Because our exception is listed in a first catch block, it is processed there.
|
||||
|
||||
The full form allow a single catch block to process exceptions with specified classes and bind actual caught object to
|
||||
the given variable. This is most common and well known form, implemented like this or similar in many other languages,
|
||||
like Kotlin, Java or C++.
|
||||
|
||||
## Shorter form
|
||||
|
||||
When you want to catch _all_ the exceptions, you should write `catch(e: Exception)`,
|
||||
but it is somewhat redundant, so there is simpler variant:
|
||||
|
||||
val sample2 = try {
|
||||
throw IllegalArgumentException("sample 2")
|
||||
}
|
||||
catch(x) {
|
||||
x.message
|
||||
}
|
||||
assertEquals( sample2, "sample 2" )
|
||||
>>> void
|
||||
|
||||
But well most likely you will find default variable `it`, like in Kotlin, more than enough
|
||||
to catch all exceptions to, then you can write it even shorter:
|
||||
|
||||
val sample2 = try {
|
||||
throw IllegalArgumentException("sample 3")
|
||||
}
|
||||
catch {
|
||||
it.message
|
||||
}
|
||||
assertEquals( sample2, "sample 3" )
|
||||
>>> void
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
The simplest way is to provide exception string and `Exception` class:
|
||||
|
||||
try {
|
||||
throw Exception("this is my exception")
|
||||
}
|
||||
catch {
|
||||
it.message
|
||||
}
|
||||
>>> "this is my exception"
|
||||
|
||||
This way, in turn, can also be shortened, as it is overly popular:
|
||||
|
||||
try {
|
||||
throw "this is my exception"
|
||||
}
|
||||
catch {
|
||||
it.message
|
||||
}
|
||||
>>> "this is my exception"
|
||||
|
||||
The trick, though, works with strings only, and always provide `Exception` instances, which is good for debugging but
|
||||
most often not enough.
|
||||
|
||||
# Custom error classes
|
||||
|
||||
_this functionality is not yet released_
|
||||
|
||||
# Standard exception classes
|
||||
|
||||
| class | notes |
|
||||
|----------------------------|-------------------------------------------------------|
|
||||
| Exception | root of al throwable objects |
|
||||
| NullReferenceException | |
|
||||
| AssertionFailedException | |
|
||||
| ClassCastException | |
|
||||
| IndexOutOfBoundsException | |
|
||||
| IllegalArgumentException | |
|
||||
| IllegalAssignmentException | assigning to val, etc. |
|
||||
| SymbolNotDefinedException | |
|
||||
| IterationEndException | attempt to read iterator past end, `hasNext == false` |
|
||||
| AccessException | attempt to access private members or like |
|
||||
| UnknownException | unexpected kotlin exception caught |
|
||||
| | |
|
||||
|
26
docs/samples/sum.lyng
Normal file
26
docs/samples/sum.lyng
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
Calculate the limit of Sum( f(n) )
|
||||
until it reaches asymptotic limit 0.00001% change
|
||||
|
||||
return null or found limit
|
||||
*/
|
||||
fun findSumLimit(f) {
|
||||
var sum = 0.0
|
||||
for( n in 1..1000000 ) {
|
||||
val s0 = sum
|
||||
sum += f(n)
|
||||
if( abs(sum - s0) / abs(sum) < 1.0e-6 ) {
|
||||
println("limit reached after "+n+" rounds")
|
||||
break sum
|
||||
}
|
||||
n++
|
||||
}
|
||||
else {
|
||||
println("limit not reached")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val limit = findSumLimit { n -> 1.0/n/n }
|
||||
|
||||
println("Result: "+limit)
|
53
docs/samples/сумма_ряда.lyng.md
Normal file
53
docs/samples/сумма_ряда.lyng.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Пример расчета суммы ряда
|
||||
|
||||
Рассмотрим как можно посчитать предел суммы ряда на lyng. Для наивной реализации
|
||||
представим что у нас есть функция рассчитывающая n-й член ряда. Тогда мы можем
|
||||
считать сумму до тех пор, пока отклонение при расчете следующего члена не станет
|
||||
меньше чем заданная погрешность:
|
||||
|
||||
fun сумма_ряда(x, погрешность=0.0001, f) {
|
||||
var сумма = 0
|
||||
for( n in 1..100000) {
|
||||
val следующая_сумма = сумма + f(x, n)
|
||||
if( n > 1 && abs(следующая_сумма - сумма) < погрешность )
|
||||
break следующая_сумма
|
||||
сумма = следующая_сумма
|
||||
}
|
||||
else null
|
||||
}
|
||||
|
||||
Для проверки можно посчитать на хорошо известном ряду Меркатора
|
||||
|
||||
$$ \ln(1+x)=x-{\dfrac {x^{2}}{2}}+{\dfrac {x^{3}}{3}}-\cdots =\sum \limits _{n=0}^{\infty }{\dfrac {(-1)^{n}x^{n+1}}{(n+1)}}=\sum \limits _{n=1}^{\infty }{\dfrac {(-1)^{n-1}x^{n}}{n}} $$
|
||||
|
||||
Который в нашем случае для точки $ x = 1 $ можно записать так:
|
||||
|
||||
val x = сумма_ряда(1) { x, n ->
|
||||
val sign = if( n % 2 == 1 ) 1 else -1
|
||||
sign * pow(x, n) / n
|
||||
}
|
||||
|
||||
Проверим:
|
||||
|
||||
assert( x - ln(2) < 0.001 )
|
||||
|
||||
В нашем примере есть изъян - погрешность вычисляется примитивно: `abs(следующая_сумма - сумма) < погрешность`, что совершенно неверно, если значения малы. Значительно более корректно вычислять погрешность, нормированную на диапазон сравниваемых величин:
|
||||
|
||||
|
||||
fun погрешность(x0, x1) {
|
||||
abs( x1 - x0 ) / (abs( x1 + x0 ) / 2.0)
|
||||
}
|
||||
// относительная погрешность одинакова и в разных диапазонах
|
||||
assertEquals( погрешность(5,6), погрешность(0.005,0.006))
|
||||
|
||||
Теперь мы могли бы написать более корректное сравнение для вещественных
|
||||
|
||||
// расхождение не больше 1% или сколько укажете:
|
||||
fun почти_равны(a,b,epsilon=0.01) {
|
||||
погрешность(a,b) <= epsilon
|
||||
}
|
||||
assert( почти_равны( 0.0005, 0.000501 ) )
|
||||
|
||||
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим.
|
||||
|
||||
(продолжение следует)
|
442
docs/tutorial.md
442
docs/tutorial.md
@ -12,11 +12,10 @@ In other word, the code usually works as expected when you see it. So, nothing u
|
||||
__Other documents to read__ maybe after this one:
|
||||
|
||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||
- [OOP notes](OOP.md)
|
||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||
- [math in Lyng](math.md)
|
||||
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md)
|
||||
See [samples folder](samples)
|
||||
- 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)
|
||||
|
||||
# Expressions
|
||||
|
||||
@ -119,6 +118,109 @@ These operators return rvalue, unmodifiable.
|
||||
|
||||
## 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
|
||||
|
||||
It is rather simple, like everywhere else:
|
||||
@ -470,64 +572,97 @@ To add elements to the list:
|
||||
assert( x == [1, 2, 3, "the", "end"])
|
||||
>>> void
|
||||
|
||||
Self-modifying concatenation by `+=` also works:
|
||||
Self-modifying concatenation by `+=` also works (also with single elements):
|
||||
|
||||
val x = [1, 2]
|
||||
x += [3, 4]
|
||||
assert( x == [1, 2, 3, 4])
|
||||
x += 5
|
||||
assert( x == [1, 2, 3, 4, 5])
|
||||
>>> void
|
||||
|
||||
You can insert elements at any position using `addAt`:
|
||||
You can insert elements at any position using `insertAt`:
|
||||
|
||||
val x = [1,2,3]
|
||||
x.addAt(1, "foo", "bar")
|
||||
x.insertAt(1, "foo", "bar")
|
||||
assert( x == [1, "foo", "bar", 2, 3])
|
||||
>>> void
|
||||
|
||||
Using splat arguments can simplify inserting list in list:
|
||||
|
||||
val x = [1, 2, 3]
|
||||
x.addAt( 1, ...[0,100,0])
|
||||
x.insertAt( 1, ...[0,100,0])
|
||||
x
|
||||
>>> [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:
|
||||
|
||||
val x = [1,2,3]
|
||||
x.addAt(3, 10)
|
||||
x.insertAt(3, 10)
|
||||
x
|
||||
>>> [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
|
||||
|
||||
val x = [1, 2, 3, 4, 5]
|
||||
x.removeAt(2)
|
||||
assert( x == [1, 2, 4, 5])
|
||||
// or remove range (start inclusive, end exclusive):
|
||||
x.removeRangeInclusive(1,2)
|
||||
x.removeRange(1..2)
|
||||
assert( x == [1, 5])
|
||||
>>> 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]
|
||||
|
||||
// remove last:
|
||||
x.removeAt(-1)
|
||||
x.removeLast()
|
||||
assert( x == [1, 2, 3, 4])
|
||||
|
||||
// remove 2 last:
|
||||
x.removeRangeInclusive(-2,-1)
|
||||
assert( x == [1, 2])
|
||||
x.removeLast(2)
|
||||
assertEquals( [1, 2], x)
|
||||
>>> 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
|
||||
|
||||
## if-then-else
|
||||
@ -553,18 +688,101 @@ Or, more neat:
|
||||
>>> just 3
|
||||
>>> 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
|
||||
|
||||
Regular pre-condition while loop, as expression, loop returns it's last line result:
|
||||
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
|
||||
|
||||
var count = 0
|
||||
while( count < 5 ) {
|
||||
count++
|
||||
count * 10
|
||||
}
|
||||
>>> 50
|
||||
val result = while( count < 5 ) count++
|
||||
result
|
||||
>>> 4
|
||||
|
||||
We can break as usual:
|
||||
Notice it _is 4 because when count became 5, the loop body was not executed!_.
|
||||
|
||||
We can break while as usual:
|
||||
|
||||
var count = 0
|
||||
while( count < 5 ) {
|
||||
@ -683,6 +901,36 @@ So the returned value, as seen from diagram could be one of:
|
||||
- value returned from the `else` clause, of the loop was not broken
|
||||
- value returned from the last execution of loop body, if there was no `break` and no `else` clause.
|
||||
|
||||
## do-while loops
|
||||
|
||||
There works exactly as while loops but the body is executed prior to checking the while condition:
|
||||
|
||||
var i = 0
|
||||
do { i++ } while( i < 1 )
|
||||
i
|
||||
>>> 1
|
||||
|
||||
The important feature of the do-while loop is that the condition expression is
|
||||
evaluated on the body scope, e.g., variables, intruduced in the loop body are
|
||||
available in the condition:
|
||||
|
||||
do {
|
||||
var continueLoop = false
|
||||
"OK"
|
||||
} while( continueLoop )
|
||||
>>> "OK"
|
||||
|
||||
This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value returning by the loop:
|
||||
|
||||
fun readLine() { "done: result" }
|
||||
val result = do {
|
||||
val line = readLine()
|
||||
} while( !line.startsWith("done:") )
|
||||
result.drop(6)
|
||||
>>> "result"
|
||||
|
||||
Suppose readLine() here reads some stream of lines.
|
||||
|
||||
|
||||
## For loops
|
||||
|
||||
@ -724,6 +972,62 @@ We can use labels too:
|
||||
assert( search(["hello", "world"], 'z') == null)
|
||||
>>> void
|
||||
|
||||
# Exception handling
|
||||
|
||||
Very much like in Kotlin. Try block returns its body block result, if no exception was cauht, or the result from the catch block that caught the exception:
|
||||
|
||||
var error = "not caught"
|
||||
var finallyCaught = false
|
||||
val result = try {
|
||||
throw IllegalArgumentException()
|
||||
"invalid"
|
||||
}
|
||||
catch(nd: SymbolNotDefinedException) {
|
||||
error = "bad catch"
|
||||
}
|
||||
catch(x: IllegalArgumentException) {
|
||||
error = "no error"
|
||||
"OK"
|
||||
}
|
||||
finally {
|
||||
// finally does not affect returned value
|
||||
"too bad"
|
||||
}
|
||||
assertEquals( "no error", error)
|
||||
assertEquals( "OK", result)
|
||||
>>> void
|
||||
|
||||
There is shorter form of catch block when you want to catch any exception:
|
||||
|
||||
var caught = null
|
||||
try {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
catch(t) { // same as catch(t: Exception), but simpler
|
||||
caught = t
|
||||
}
|
||||
assert( caught is IllegalArgumentException )
|
||||
>>> void
|
||||
|
||||
And even shortest, for the Lying lang tradition, missing var is `it`:
|
||||
|
||||
var caught = null
|
||||
try {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
catch { // same as catch(it: Exception), but simpler
|
||||
caught = it
|
||||
}
|
||||
assert( caught is IllegalArgumentException )
|
||||
>>> void
|
||||
|
||||
It is possible to catch several exceptions in the same block too, use
|
||||
`catch( varName: ExceptionClass1, ExceptionClass2)`, etc, use short form of throw and
|
||||
many more.
|
||||
|
||||
- see [exception handling](exceptions_handling.md) for detailed exceptions tutorial and reference.
|
||||
|
||||
|
||||
# Self-assignments in expression
|
||||
|
||||
There are auto-increments and auto-decrements:
|
||||
@ -792,7 +1096,7 @@ See [Ranges](Range.md) for detailed documentation on it.
|
||||
|
||||
// single line comment
|
||||
var result = null // here we will store the result
|
||||
>>> void
|
||||
>>> null
|
||||
|
||||
# Integral data types
|
||||
|
||||
@ -838,10 +1142,84 @@ Are the same as in string literals with little difference:
|
||||
|
||||
## String details
|
||||
|
||||
Strings are arrays of Unicode characters. It can be indexed, and indexing will
|
||||
return a valid Unicode character at position. No utf hassle:
|
||||
|
||||
"Парашют"[5]
|
||||
>>> 'ю'
|
||||
|
||||
Its length is, of course, in characters:
|
||||
|
||||
"разум".length
|
||||
>>> 5
|
||||
|
||||
And supports growing set of kotlin-borrowed operations, see below, for example:
|
||||
|
||||
assertEquals("Hell", "Hello".dropLast(1))
|
||||
>>> 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
|
||||
|
||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
||||
|
||||
Typical set of String functions includes:
|
||||
|
||||
| fun/prop | description / notes |
|
||||
|--------------------|------------------------------------------------------------|
|
||||
| lower() | change case to unicode upper |
|
||||
| upper() | change case to unicode lower |
|
||||
| startsWith(prefix) | true if starts with a prefix |
|
||||
| endsWith(prefix) | true if ends with a prefix |
|
||||
| take(n) | get a new string from up to n first characters |
|
||||
| takeLast(n) | get a new string from up to n last characters |
|
||||
| drop(n) | get a new string dropping n first chars, or empty string |
|
||||
| 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 |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Literals
|
||||
|
||||
String literal could be multiline:
|
||||
@ -872,5 +1250,7 @@ See [math functions](math.md). Other general purpose functions are:
|
||||
[Iterator]: Iterator.md
|
||||
[Real]: Real.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
|
11
happy.py
11
happy.py
@ -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,54 +0,0 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||
|
||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context): Arguments {
|
||||
val list = mutableListOf<Arguments.Info>()
|
||||
|
||||
for (x in this) {
|
||||
val value = x.value.execute(context)
|
||||
if (x.isSplat) {
|
||||
when {
|
||||
value is ObjList -> {
|
||||
for (subitem in value.list) list.add(Arguments.Info(subitem, x.pos))
|
||||
}
|
||||
|
||||
value.isInstanceOf(ObjIterable) -> {
|
||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
||||
i.forEach { list.add(Arguments.Info(it, x.pos)) }
|
||||
}
|
||||
|
||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
||||
}
|
||||
} else
|
||||
list.add(Arguments.Info(value, x.pos))
|
||||
}
|
||||
return Arguments(list)
|
||||
}
|
||||
|
||||
data class Arguments(val list: List<Info>) : Iterable<Obj> {
|
||||
|
||||
data class Info(val value: Obj, val pos: Pos)
|
||||
|
||||
val size by list::size
|
||||
|
||||
operator fun get(index: Int): Obj = list[index].value
|
||||
|
||||
val values: List<Obj> by lazy { list.map { it.value } }
|
||||
|
||||
fun firstAndOnly(): Obj {
|
||||
if (list.size != 1) throw IllegalArgumentException("Expected one argument, got ${list.size}")
|
||||
return list.first().value
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
fun from(values: Collection<Obj>) = Arguments(values.map { Info(it, Pos.UNKNOWN) })
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Obj> {
|
||||
return list.map { it.value }.iterator()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,153 +0,0 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
val ObjClassType by lazy { ObjClass("Class") }
|
||||
|
||||
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 {
|
||||
println("callOn $this constructing....")
|
||||
println("on context: $context")
|
||||
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: Compiler.Visibility = Compiler.Visibility.Public,
|
||||
pos: Pos = Pos.builtIn
|
||||
) {
|
||||
if (name in members || allParentsSet.any { name in it.members } == true)
|
||||
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(ObjIterationFinishedError(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,29 +0,0 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
internal val publicFields = mutableSetOf<String>()
|
||||
internal val protectedFields = mutableSetOf<String>()
|
||||
|
||||
internal lateinit var instanceContext: Context
|
||||
|
||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
||||
return if( name in publicFields ) instanceContext[name]!!
|
||||
else super.readField(context, name)
|
||||
}
|
||||
|
||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
if( name in publicFields ) {
|
||||
val f = instanceContext[name]!!
|
||||
if( !f.isMutable ) ObjIllegalAssignmentError(context, "can't reassign val $name").raise()
|
||||
if( f.value.assign(context, newValue) == null)
|
||||
f.value = newValue
|
||||
}
|
||||
else super.writeField(context, name, newValue)
|
||||
}
|
||||
|
||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj {
|
||||
if( name in publicFields ) return instanceContext[name]!!.value.invoke(context, this, args)
|
||||
return super.invokeInstanceMethod(context, name, args)
|
||||
}
|
||||
}
|
@ -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,46 +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("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
|
||||
|
||||
*/
|
@ -1,5 +0,0 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.buildconfig.BuildConfig
|
||||
|
||||
val LyngVersion = BuildConfig.VERSION
|
@ -29,11 +29,11 @@ kotlin {
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":library"))
|
||||
implementation(kotlin("stdlib-common"))
|
||||
implementation(project(":lynglib"))
|
||||
implementation(libs.okio)
|
||||
|
||||
implementation(libs.clikt)
|
||||
|
||||
implementation(kotlin("stdlib-common"))
|
||||
// optional support for rendering markdown in help messages
|
||||
// implementation(libs.clikt.markdown)
|
||||
}
|
||||
@ -42,9 +42,15 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.okio.fakefilesystem)
|
||||
}
|
||||
}
|
||||
// val nativeMain by getting {
|
||||
// dependencies {
|
||||
// implementation(kotlin("stdlib-common"))
|
||||
// }
|
||||
// }
|
||||
val linuxX64Main by getting {
|
||||
|
||||
}
|
||||
|
@ -17,14 +17,31 @@ import okio.use
|
||||
|
||||
expect fun exit(code: Int)
|
||||
|
||||
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 baseContext = Context().apply {
|
||||
addFn("exit") {
|
||||
exit(requireOnlyArg<ObjInt>().toInt())
|
||||
ObjVoid
|
||||
}
|
||||
// ObjString.type.addFn("shell") {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
class LyngCLI(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||
class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||
|
||||
override val printHelpOnEmptyArgs = true
|
||||
|
||||
@ -90,11 +107,16 @@ class LyngCLI(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||
}
|
||||
|
||||
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 ->
|
||||
bs.readUtf8()
|
||||
}
|
||||
}
|
||||
if( text.startsWith("#!") ) {
|
||||
// skip shebang
|
||||
val pos = text.indexOf('\n')
|
||||
text = text.substring(pos + 1)
|
||||
}
|
||||
processErrors {
|
||||
Compiler().compile(Source(fileName, text)).execute(baseContext)
|
||||
}
|
||||
|
23
lyng/src/jvmMain/kotlin/Shell.jvm.kt
Normal file
23
lyng/src/jvmMain/kotlin/Shell.jvm.kt
Normal file
@ -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()
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ package net.sergeych.lyng_cli
|
||||
|
||||
import com.github.ajalt.clikt.core.main
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.sergeych.LyngCLI
|
||||
import net.sergeych.Lyng
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
LyngCLI({ runBlocking { it() } }).main(args)
|
||||
Lyng({ runBlocking { it() } }).main(args)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import com.github.ajalt.clikt.core.main
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.sergeych.LyngCLI
|
||||
import net.sergeych.Lyng
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
LyngCLI( { runBlocking { it() } }).main(args)
|
||||
Lyng( { runBlocking { it() } }).main(args)
|
||||
}
|
@ -1,7 +1,48 @@
|
||||
@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class, ExperimentalForeignApi::class)
|
||||
|
||||
package net.sergeych
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.fgets
|
||||
import platform.posix.pclose
|
||||
import platform.posix.popen
|
||||
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) {
|
||||
exitProcess(code)
|
||||
}
|
@ -1,17 +1,40 @@
|
||||
import com.vanniktech.maven.publish.SonatypeHost
|
||||
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.6.8-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0")
|
||||
classpath("com.codingfeline.buildkonfig:buildkonfig-gradle-plugin:latest_version")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.vanniktech.mavenPublish)
|
||||
// alias(libs.plugins.vanniktech.mavenPublish)
|
||||
kotlin("plugin.serialization") version "2.1.20"
|
||||
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.2.0-SNAPSHOT"
|
||||
buildkonfig {
|
||||
packageName = "net.sergeych.lyng"
|
||||
// objectName = "YourAwesomeConfig"
|
||||
// exposeObjectWithName = "YourAwesomePublicConfig"
|
||||
|
||||
defaultConfigs {
|
||||
buildConfigField(STRING, "bcprovider", "codingfeline")
|
||||
buildConfigField(STRING, "version", version.toString())
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
@ -39,6 +62,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
|
||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||
}
|
||||
@ -77,73 +101,54 @@ dependencies {
|
||||
implementation(libs.firebase.crashlytics.buildtools)
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
||||
|
||||
signAllPublications()
|
||||
|
||||
coordinates(group.toString(), "library", version.toString())
|
||||
|
||||
pom {
|
||||
name = "Lyng language"
|
||||
description = "Kotlin-bound scripting loanguage"
|
||||
inceptionYear = "2025"
|
||||
// url = "https://sergeych.net"
|
||||
licenses {
|
||||
license {
|
||||
name = "XXX"
|
||||
url = "YYY"
|
||||
distribution = "ZZZ"
|
||||
publishing {
|
||||
val mavenToken by lazy {
|
||||
File("${System.getProperty("user.home")}/.gitea_token").readText()
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
credentials(HttpHeaderCredentials::class) {
|
||||
name = "Authorization"
|
||||
value = mavenToken
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "XXX"
|
||||
name = "YYY"
|
||||
url = "ZZZ"
|
||||
url = uri("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||
authentication {
|
||||
create("Authorization", HttpHeaderAuthentication::class)
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "XXX"
|
||||
connection = "YYY"
|
||||
developerConnection = "ZZZ"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val projectVersion by project.extra(provider {
|
||||
// Compute value lazily
|
||||
(version as String)
|
||||
})
|
||||
|
||||
val generateBuildConfig by tasks.registering {
|
||||
// Declare outputs safely
|
||||
val outputDir = layout.buildDirectory.dir("generated/buildConfig/commonMain/kotlin")
|
||||
outputs.dir(outputDir)
|
||||
|
||||
val version = projectVersion.get()
|
||||
|
||||
// Inputs: Version is tracked as an input
|
||||
inputs.property("version", version)
|
||||
|
||||
doLast {
|
||||
val packageName = "net.sergeych.lyng.buildconfig"
|
||||
val packagePath = packageName.replace('.', '/')
|
||||
val buildConfigFile = outputDir.get().file("$packagePath/BuildConfig.kt").asFile
|
||||
|
||||
buildConfigFile.parentFile?.mkdirs()
|
||||
buildConfigFile.writeText(
|
||||
"""
|
||||
|package $packageName
|
||||
|
|
||||
|object BuildConfig {
|
||||
| const val VERSION = "$version"
|
||||
|}
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
dependsOn(generateBuildConfig)
|
||||
}
|
||||
//mavenPublishing {
|
||||
// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
||||
//
|
||||
// signAllPublications()
|
||||
//
|
||||
// coordinates(group.toString(), "library", version.toString())
|
||||
//
|
||||
// pom {
|
||||
// name = "Lyng language"
|
||||
// description = "Kotlin-bound scripting loanguage"
|
||||
// inceptionYear = "2025"
|
||||
//// url = "https://sergeych.net"
|
||||
// licenses {
|
||||
// license {
|
||||
// name = "XXX"
|
||||
// url = "YYY"
|
||||
// distribution = "ZZZ"
|
||||
// }
|
||||
// }
|
||||
// developers {
|
||||
// developer {
|
||||
// id = "XXX"
|
||||
// name = "YYY"
|
||||
// url = "ZZZ"
|
||||
// }
|
||||
// }
|
||||
// scm {
|
||||
// url = "XXX"
|
||||
// connection = "YYY"
|
||||
// developerConnection = "ZZZ"
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -0,0 +1,8 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
enum class AccessType(val isMutable: Boolean) {
|
||||
Val(false), Var(true),
|
||||
|
||||
@Suppress("unused")
|
||||
Initialization(false)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* Special version of the [Context] used to `apply` new this object to
|
||||
* _parent context property.
|
||||
*
|
||||
* @param _parent context to apply to
|
||||
* @param args arguments for the new context
|
||||
* @param appliedContext the new context to apply, it will have lower priority except for `this` which
|
||||
* will be reset by appliedContext's `this`.
|
||||
*/
|
||||
class AppliedContext(_parent: Context, args: Arguments, val appliedContext: Context)
|
||||
: Context(_parent, args, appliedContext.pos, appliedContext.thisObj) {
|
||||
override fun get(name: String): ObjRecord? =
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else super.get(name) ?: appliedContext[name]
|
||||
}
|
@ -11,7 +11,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
val start = params.indexOfFirst { it.defaultValue != null }
|
||||
if (start >= 0)
|
||||
for (j in start + 1 until params.size)
|
||||
if (params[j].defaultValue == null) throw ScriptError(
|
||||
// last non-default could be lambda:
|
||||
if (params[j].defaultValue == null && j != params.size - 1) throw ScriptError(
|
||||
params[j].pos,
|
||||
"required argument can't follow default one"
|
||||
)
|
||||
@ -22,22 +23,41 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
*/
|
||||
suspend fun assignToContext(
|
||||
context: Context,
|
||||
fromArgs: Arguments = context.args,
|
||||
defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var
|
||||
arguments: Arguments = context.args,
|
||||
defaultAccessType: AccessType = AccessType.Var,
|
||||
defaultVisibility: Visibility = Visibility.Public
|
||||
) {
|
||||
fun assign(a: Item, value: Obj) {
|
||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value)
|
||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||
a.visibility ?: defaultVisibility)
|
||||
}
|
||||
|
||||
// will be used with last lambda arg fix
|
||||
val callArgs: List<Obj>
|
||||
val paramsSize: Int
|
||||
|
||||
if( arguments.tailBlockMode ) {
|
||||
paramsSize = params.size - 1
|
||||
assign(params.last(), arguments.list.last())
|
||||
callArgs = arguments.list.dropLast(1)
|
||||
} else {
|
||||
paramsSize = params.size
|
||||
callArgs = arguments.list
|
||||
}
|
||||
|
||||
suspend fun processHead(index: Int): Int {
|
||||
var i = index
|
||||
while (i != params.size) {
|
||||
while (i != paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
i < fromArgs.size -> fromArgs[i]
|
||||
i < callArgs.size -> callArgs[i]
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> context.raiseArgumentError("too few arguments for the call")
|
||||
else -> {
|
||||
println("callArgs: ${callArgs.joinToString()}")
|
||||
println("tailBlockMode: ${arguments.tailBlockMode}")
|
||||
context.raiseIllegalArgument("too few arguments for the call")
|
||||
}
|
||||
}
|
||||
assign(a, value)
|
||||
i++
|
||||
@ -46,18 +66,18 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
}
|
||||
|
||||
suspend fun processTail(index: Int): Int {
|
||||
var i = params.size - 1
|
||||
var j = fromArgs.size - 1
|
||||
var i = paramsSize - 1
|
||||
var j = callArgs.size - 1
|
||||
while (i > index) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
j >= index -> {
|
||||
fromArgs[j--]
|
||||
callArgs[j--]
|
||||
}
|
||||
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> context.raiseArgumentError("too few arguments for the call")
|
||||
else -> context.raiseIllegalArgument("too few arguments for the call")
|
||||
}
|
||||
assign(a, value)
|
||||
i--
|
||||
@ -68,17 +88,17 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
||||
val a = params[index]
|
||||
val l = if (index > toFromIndex) ObjList()
|
||||
else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList())
|
||||
else ObjList(callArgs.subList(index, toFromIndex + 1).toMutableList())
|
||||
assign(a, l)
|
||||
}
|
||||
|
||||
val leftIndex = processHead(0)
|
||||
if (leftIndex < params.size) {
|
||||
if (leftIndex < paramsSize) {
|
||||
val end = processTail(leftIndex)
|
||||
processEllipsis(leftIndex, end)
|
||||
} else {
|
||||
if (leftIndex < fromArgs.size)
|
||||
context.raiseArgumentError("too many arguments for the call")
|
||||
if (leftIndex < callArgs.size)
|
||||
context.raiseIllegalArgument("too many arguments for the call")
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +110,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
*/
|
||||
data class Item(
|
||||
val name: String,
|
||||
val type: TypeDecl = TypeDecl.Obj,
|
||||
val type: TypeDecl = TypeDecl.TypeAny,
|
||||
val pos: Pos = Pos.builtIn,
|
||||
val isEllipsis: Boolean = false,
|
||||
/**
|
||||
@ -98,7 +118,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
* So it is a [Statement] that must be executed on __caller context__.
|
||||
*/
|
||||
val defaultValue: Statement? = null,
|
||||
val accessType: Compiler.AccessType? = null,
|
||||
val visibility: Compiler.Visibility? = null,
|
||||
val accessType: AccessType? = null,
|
||||
val visibility: Visibility? = null,
|
||||
)
|
||||
}
|
50
lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt
Normal file
50
lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||
|
||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
|
||||
val list = mutableListOf<Obj>()
|
||||
|
||||
for (x in this) {
|
||||
val value = x.value.execute(context)
|
||||
if (x.isSplat) {
|
||||
when {
|
||||
value is ObjList -> {
|
||||
for (subitem in value.list) list.add(subitem)
|
||||
}
|
||||
|
||||
value.isInstanceOf(ObjIterable) -> {
|
||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
||||
i.forEach { list.add(it) }
|
||||
}
|
||||
|
||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
||||
}
|
||||
} else
|
||||
list.add(value)
|
||||
}
|
||||
return Arguments(list,tailBlockMode)
|
||||
}
|
||||
|
||||
data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : List<Obj> by list {
|
||||
|
||||
constructor(vararg values: Obj) : this(values.toList())
|
||||
|
||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||
*/
|
||||
suspend fun toKotlinList(context: Context): List<Any?> {
|
||||
return list.map { it.toKotlin(context) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||
}
|
||||
}
|
||||
|
@ -11,18 +11,20 @@ class Compiler(
|
||||
class Settings
|
||||
|
||||
fun compile(source: Source): Script {
|
||||
return parseScript(source.startPos,
|
||||
return parseScript(
|
||||
source.startPos,
|
||||
CompilerContext(parseLyng(source))
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseScript(start: Pos, cc: CompilerContext): Script {
|
||||
val statements = mutableListOf<Statement>()
|
||||
// val returnScope = cc.startReturnScope()
|
||||
while (parseStatement(cc, braceMeansLambda = true)?.also {
|
||||
statements += it
|
||||
} != null) {/**/
|
||||
}
|
||||
return Script(start, statements)
|
||||
return Script(start, statements)//returnScope.needCatch)
|
||||
}
|
||||
|
||||
private fun parseStatement(cc: CompilerContext, braceMeansLambda: Boolean = false): Statement? {
|
||||
@ -57,7 +59,7 @@ class Compiler(
|
||||
parseBlock(cc)
|
||||
}
|
||||
|
||||
Token.Type.RBRACE -> {
|
||||
Token.Type.RBRACE, Token.Type.RBRACKET -> {
|
||||
cc.previous()
|
||||
return null
|
||||
}
|
||||
@ -119,49 +121,91 @@ class Compiler(
|
||||
operand = Accessor { op.getter(it).value.logicalNot(it).asReadonly }
|
||||
}
|
||||
|
||||
Token.Type.DOT -> {
|
||||
Token.Type.DOT, Token.Type.NULL_COALESCE -> {
|
||||
val isOptional = t.type == Token.Type.NULL_COALESCE
|
||||
operand?.let { left ->
|
||||
// dotcall: calling method on the operand, if next is ID, "("
|
||||
var isCall = false
|
||||
val next = cc.next()
|
||||
if (next.type == Token.Type.ID) {
|
||||
cc.ifNextIs(Token.Type.LPAREN) {
|
||||
// instance method call
|
||||
val args = parseArgs(cc)
|
||||
isCall = true
|
||||
operand = Accessor { context ->
|
||||
context.pos = next.pos
|
||||
val v = left.getter(context).value
|
||||
ObjRecord(
|
||||
v.invokeInstanceMethod(
|
||||
context,
|
||||
next.value,
|
||||
args.toArguments(context)
|
||||
), isMutable = false
|
||||
)
|
||||
// could be () call or obj.method {} call
|
||||
val nt = cc.current()
|
||||
when (nt.type) {
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
// instance method call
|
||||
val args = parseArgs(cc).first
|
||||
isCall = true
|
||||
operand = Accessor { context ->
|
||||
context.pos = next.pos
|
||||
val v = left.getter(context).value
|
||||
if (v == ObjNull && isOptional)
|
||||
ObjNull.asReadonly
|
||||
else
|
||||
ObjRecord(
|
||||
v.invokeInstanceMethod(
|
||||
context,
|
||||
next.value,
|
||||
args.toArguments(context, false)
|
||||
), isMutable = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||
// isOptional = nt.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
||||
// single lambda arg, like assertTrows { ... }
|
||||
cc.next()
|
||||
isCall = true
|
||||
val lambda =
|
||||
parseLambdaExpression(cc)
|
||||
println(cc.current())
|
||||
operand = Accessor { context ->
|
||||
context.pos = next.pos
|
||||
val v = left.getter(context).value
|
||||
if (v == ObjNull && isOptional)
|
||||
ObjNull.asReadonly
|
||||
else
|
||||
ObjRecord(
|
||||
v.invokeInstanceMethod(
|
||||
context,
|
||||
next.value,
|
||||
Arguments(listOf(lambda.getter(context).value), true)
|
||||
), isMutable = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
if (!isCall) {
|
||||
operand = Accessor({ context ->
|
||||
left.getter(context).value.readField(context, next.value)
|
||||
val x = left.getter(context).value
|
||||
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
||||
else x.readField(context, next.value)
|
||||
}) { cc, newValue ->
|
||||
left.getter(cc).value.writeField(cc, next.value, newValue)
|
||||
}
|
||||
}
|
||||
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
|
||||
}
|
||||
|
||||
?: throw ScriptError(t.pos, "Expecting expression before dot")
|
||||
}
|
||||
|
||||
Token.Type.COLONCOLON -> {
|
||||
operand = parseScopeOperator(operand, cc)
|
||||
}
|
||||
|
||||
Token.Type.LPAREN -> {
|
||||
Token.Type.LPAREN, Token.Type.NULL_COALESCE_INVOKE -> {
|
||||
operand?.let { left ->
|
||||
// this is function call from <left>
|
||||
operand = parseFunctionCall(
|
||||
cc,
|
||||
left,
|
||||
false,
|
||||
t.type == Token.Type.NULL_COALESCE_INVOKE
|
||||
)
|
||||
} ?: run {
|
||||
// Expression in parentheses
|
||||
@ -174,15 +218,17 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.LBRACKET -> {
|
||||
Token.Type.LBRACKET, Token.Type.NULL_COALESCE_INDEX -> {
|
||||
operand?.let { left ->
|
||||
// array access
|
||||
val isOptional = t.type == Token.Type.NULL_COALESCE_INDEX
|
||||
val index = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting index expression")
|
||||
cc.skipTokenOfType(Token.Type.RBRACKET, "missing ']' at the end of the list literal")
|
||||
operand = Accessor({ cxt ->
|
||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
||||
?: cxt.raiseError("index must be integer")
|
||||
left.getter(cxt).value.getAt(cxt, i).asMutable
|
||||
val i = index.execute(cxt)
|
||||
val x = left.getter(cxt).value
|
||||
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
||||
else x.getAt(cxt, i).asMutable
|
||||
}) { cxt, newValue ->
|
||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
||||
?: cxt.raiseError("index must be integer")
|
||||
@ -293,10 +339,10 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
||||
// closed-range operator
|
||||
// range operator
|
||||
val isEndInclusive = t.type == Token.Type.DOTDOT
|
||||
val left = operand
|
||||
val right = parseStatement(cc)
|
||||
val right = parseExpression(cc)
|
||||
operand = Accessor {
|
||||
ObjRange(
|
||||
left?.getter?.invoke(it)?.value ?: ObjNull,
|
||||
@ -306,17 +352,27 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.LBRACE -> {
|
||||
if (operand != null) {
|
||||
throw ScriptError(t.pos, "syntax error: lambda expression not allowed here")
|
||||
} else operand = parseLambdaExpression(cc)
|
||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||
operand = operand?.let { left ->
|
||||
cc.previous()
|
||||
parseFunctionCall(
|
||||
cc,
|
||||
left,
|
||||
blockArgument = true,
|
||||
t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
||||
)
|
||||
} ?: parseLambdaExpression(cc)
|
||||
}
|
||||
|
||||
Token.Type.RBRACKET, Token.Type.RPAREN -> {
|
||||
cc.previous()
|
||||
return operand
|
||||
}
|
||||
|
||||
else -> {
|
||||
cc.previous()
|
||||
operand?.let { return it }
|
||||
operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
operand = parseAccessor(cc) ?: return null //throw ScriptError(t.pos, "Expecting expression")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,16 +387,17 @@ class Compiler(
|
||||
val argsDeclaration = parseArgsDeclaration(cc)
|
||||
if (argsDeclaration != null && argsDeclaration.endTokenType != Token.Type.ARROW)
|
||||
throw ScriptError(startPos, "lambda must have either valid arguments declaration with '->' or no arguments")
|
||||
val pos = cc.currentPos()
|
||||
|
||||
val body = parseBlock(cc, skipLeadingBrace = true)
|
||||
|
||||
var closure: Context? = null
|
||||
|
||||
val callStatement = statement {
|
||||
val context = closure!!.copy(pos, args)
|
||||
// and the source closure of the lambda which might have other thisObj.
|
||||
val context = AppliedContext(closure!!, args, this)
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.values
|
||||
val l = args.list
|
||||
val itValue: Obj = when (l.size) {
|
||||
// no args: it == void
|
||||
0 -> ObjVoid
|
||||
@ -401,16 +458,6 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
enum class AccessType(val isMutable: Boolean) {
|
||||
Val(false), Var(true),
|
||||
@Suppress("unused")
|
||||
Initialization(false)
|
||||
}
|
||||
|
||||
enum class Visibility {
|
||||
Public, Private, Protected, Internal
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument declaration, used in lambda (and later in fn too)
|
||||
* @return declaration or null if there is no valid list of arguments
|
||||
@ -429,43 +476,14 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.NEWLINE -> {}
|
||||
|
||||
Token.Type.ID -> {
|
||||
// visibility
|
||||
val visibility = when (t.value) {
|
||||
"private" -> {
|
||||
if (!isClassDeclaration) {
|
||||
cc.restorePos(startPos); return null
|
||||
}
|
||||
t = cc.next()
|
||||
Visibility.Private
|
||||
}
|
||||
val visibility = if (isClassDeclaration && t.value == "private") {
|
||||
t = cc.next()
|
||||
Visibility.Private
|
||||
} else Visibility.Public
|
||||
|
||||
"protected" -> {
|
||||
if (!isClassDeclaration) {
|
||||
cc.restorePos(startPos); return null
|
||||
}
|
||||
t = cc.next()
|
||||
Visibility.Protected
|
||||
}
|
||||
|
||||
"internal" -> {
|
||||
if (!isClassDeclaration) {
|
||||
cc.restorePos(startPos); return null
|
||||
}
|
||||
t = cc.next()
|
||||
Visibility.Internal
|
||||
}
|
||||
|
||||
"public" -> {
|
||||
if (!isClassDeclaration) {
|
||||
cc.restorePos(startPos); return null
|
||||
}
|
||||
t = cc.next()
|
||||
Visibility.Public
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
// val/var?
|
||||
val access = when (t.value) {
|
||||
"val" -> {
|
||||
@ -542,14 +560,19 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun parseTypeDeclaration(cc: CompilerContext): TypeDecl {
|
||||
val result = TypeDecl.Obj
|
||||
cc.ifNextIs(Token.Type.COLON) {
|
||||
TODO("parse type declaration here")
|
||||
}
|
||||
return result
|
||||
return if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
val tt = cc.requireToken(Token.Type.ID, "type name or type expression required")
|
||||
val isNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
|
||||
TypeDecl.Simple(tt.value, isNullable)
|
||||
} else TypeDecl.TypeAny
|
||||
}
|
||||
|
||||
private fun parseArgs(cc: CompilerContext): List<ParsedArgument> {
|
||||
/**
|
||||
* Parse arguments list during the call and detect last block argument
|
||||
* _following the parenthesis_ call: `(1,2) { ... }`
|
||||
*/
|
||||
private fun parseArgs(cc: CompilerContext): Pair<List<ParsedArgument>, Boolean> {
|
||||
|
||||
val args = mutableListOf<ParsedArgument>()
|
||||
do {
|
||||
val t = cc.next()
|
||||
@ -567,6 +590,8 @@ class Compiler(
|
||||
cc.previous()
|
||||
parseExpression(cc)?.let { args += ParsedArgument(it, t.pos) }
|
||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||
if (cc.current().type == Token.Type.COLON)
|
||||
parseTypeDeclaration(cc)
|
||||
// Here should be a valid termination:
|
||||
}
|
||||
}
|
||||
@ -574,6 +599,7 @@ class Compiler(
|
||||
// block after?
|
||||
val pos = cc.savePos()
|
||||
val end = cc.next()
|
||||
var lastBlockArgument = false
|
||||
if (end.type == Token.Type.LBRACE) {
|
||||
// last argument - callable
|
||||
val callableAccessor = parseLambdaExpression(cc)
|
||||
@ -584,22 +610,40 @@ class Compiler(
|
||||
},
|
||||
end.pos
|
||||
)
|
||||
lastBlockArgument = true
|
||||
} else
|
||||
cc.restorePos(pos)
|
||||
return args
|
||||
return args to lastBlockArgument
|
||||
}
|
||||
|
||||
|
||||
private fun parseFunctionCall(cc: CompilerContext, left: Accessor): Accessor {
|
||||
private fun parseFunctionCall(
|
||||
cc: CompilerContext,
|
||||
left: Accessor,
|
||||
blockArgument: Boolean,
|
||||
isOptional: Boolean
|
||||
): Accessor {
|
||||
// insofar, functions always return lvalue
|
||||
val args = parseArgs(cc)
|
||||
var detectedBlockArgument = blockArgument
|
||||
val args = if (blockArgument) {
|
||||
val blockArg = ParsedArgument(
|
||||
parseExpression(cc)
|
||||
?: throw ScriptError(cc.currentPos(), "lambda body expected"), cc.currentPos()
|
||||
)
|
||||
listOf(blockArg)
|
||||
} else {
|
||||
val r = parseArgs(cc)
|
||||
detectedBlockArgument = r.second
|
||||
r.first
|
||||
}
|
||||
|
||||
return Accessor { context ->
|
||||
val v = left.getter(context)
|
||||
if (v.value == ObjNull && isOptional) return@Accessor v.value.asReadonly
|
||||
v.value.callOn(
|
||||
context.copy(
|
||||
context.pos,
|
||||
args.toArguments(context)
|
||||
args.toArguments(context, detectedBlockArgument)
|
||||
// Arguments(
|
||||
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||
// ),
|
||||
@ -639,7 +683,7 @@ class Compiler(
|
||||
"void" -> Accessor { ObjVoid.asReadonly }
|
||||
"null" -> Accessor { ObjNull.asReadonly }
|
||||
"true" -> Accessor { ObjBool(true).asReadonly }
|
||||
"false" -> Accessor { ObjBool(false).asReadonly }
|
||||
"false" -> Accessor { ObjFalse.asReadonly }
|
||||
else -> {
|
||||
Accessor({
|
||||
it.pos = t.pos
|
||||
@ -682,28 +726,281 @@ class Compiler(
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keyword-starting statenment.
|
||||
* Parse keyword-starting statement.
|
||||
* @return parsed statement or null if, for example. [id] is not among keywords
|
||||
*/
|
||||
private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) {
|
||||
"val" -> parseVarDeclaration(id.value, false, cc)
|
||||
"var" -> parseVarDeclaration(id.value, true, cc)
|
||||
"val" -> parseVarDeclaration(false, Visibility.Public, cc)
|
||||
"var" -> parseVarDeclaration(true, Visibility.Public, cc)
|
||||
"while" -> parseWhileStatement(cc)
|
||||
"do" -> parseDoWhileStatement(cc)
|
||||
"for" -> parseForStatement(cc)
|
||||
"break" -> parseBreakStatement(id.pos, cc)
|
||||
"continue" -> parseContinueStatement(id.pos, cc)
|
||||
"fn", "fun" -> parseFunctionDeclaration(cc)
|
||||
"if" -> parseIfStatement(cc)
|
||||
"class" -> parseClassDeclaration(cc, false)
|
||||
"struct" -> parseClassDeclaration(cc, true)
|
||||
else -> null
|
||||
"try" -> parseTryStatement(cc)
|
||||
"throw" -> parseThrowStatement(cc)
|
||||
"when" -> parseWhenStatement(cc)
|
||||
else -> {
|
||||
// triples
|
||||
cc.previous()
|
||||
val isExtern = cc.skipId("extern")
|
||||
when {
|
||||
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern)
|
||||
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private, isExtern)
|
||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true, isExtern = isExtern)
|
||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true, isExtern = isExtern)
|
||||
|
||||
cc.matchQualifiers("fun") -> parseFunctionDeclaration(cc, isOpen = false, isExtern = isExtern)
|
||||
cc.matchQualifiers("fn") -> parseFunctionDeclaration(cc, isOpen = false, isExtern = isExtern)
|
||||
|
||||
cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private, cc)
|
||||
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private, cc)
|
||||
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, cc, true)
|
||||
cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, cc, true)
|
||||
else -> {
|
||||
cc.next()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class WhenCase(val condition: Statement, val block: Statement)
|
||||
|
||||
private fun parseWhenStatement(cc: CompilerContext): Statement {
|
||||
// has a value, when(value) ?
|
||||
var t = cc.skipWsTokens()
|
||||
return if (t.type == Token.Type.LPAREN) {
|
||||
// when(value)
|
||||
val value = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when(value) expected")
|
||||
cc.skipTokenOfType(Token.Type.RPAREN)
|
||||
t = cc.next()
|
||||
if (t.type != Token.Type.LBRACE) throw ScriptError(t.pos, "when { ... } expected")
|
||||
val cases = mutableListOf<WhenCase>()
|
||||
var elseCase: Statement? = null
|
||||
lateinit var whenValue: Obj
|
||||
|
||||
// there could be 0+ then clauses
|
||||
// condition could be a value, in and is clauses:
|
||||
// parse several conditions for one then clause
|
||||
|
||||
// loop cases
|
||||
outer@ while (true) {
|
||||
|
||||
var skipParseBody = false
|
||||
val currentCondition = mutableListOf<Statement>()
|
||||
|
||||
// loop conditions
|
||||
while (true) {
|
||||
t = cc.skipWsTokens()
|
||||
|
||||
when (t.type) {
|
||||
Token.Type.IN,
|
||||
Token.Type.NOTIN -> {
|
||||
// we need a copy in the closure:
|
||||
val isIn = t.type == Token.Type.IN
|
||||
val container = parseExpression(cc) ?: throw ScriptError(cc.currentPos(), "type expected")
|
||||
currentCondition += statement {
|
||||
val r = container.execute(this).contains(this, whenValue)
|
||||
ObjBool(if (isIn) r else !r)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.IS, Token.Type.NOTIS -> {
|
||||
// we need a copy in the closure:
|
||||
val isIn = t.type == Token.Type.IS
|
||||
val caseType = parseExpression(cc) ?: throw ScriptError(cc.currentPos(), "type expected")
|
||||
currentCondition += statement {
|
||||
val r = whenValue.isInstanceOf(caseType.execute(this))
|
||||
ObjBool(if (isIn) r else !r)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.COMMA ->
|
||||
continue
|
||||
|
||||
Token.Type.ARROW ->
|
||||
break
|
||||
|
||||
Token.Type.RBRACE ->
|
||||
break@outer
|
||||
|
||||
else -> {
|
||||
if (t.value == "else") {
|
||||
cc.skipTokens(Token.Type.ARROW)
|
||||
if (elseCase != null) throw ScriptError(
|
||||
cc.currentPos(),
|
||||
"when else block already defined"
|
||||
)
|
||||
elseCase =
|
||||
parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when else block expected")
|
||||
skipParseBody = true
|
||||
} else {
|
||||
cc.previous()
|
||||
val x = parseExpression(cc)
|
||||
?: throw ScriptError(cc.currentPos(), "when case condition expected")
|
||||
currentCondition += statement {
|
||||
ObjBool(x.execute(this).compareTo(this, whenValue) == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// parsed conditions?
|
||||
if (!skipParseBody) {
|
||||
val block = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when case block expected")
|
||||
for (c in currentCondition) cases += WhenCase(c, block)
|
||||
}
|
||||
}
|
||||
statement {
|
||||
var result: Obj = ObjVoid
|
||||
// in / is and like uses whenValue from closure:
|
||||
whenValue = value.execute(this)
|
||||
var found = false
|
||||
for (c in cases)
|
||||
if (c.condition.execute(this).toBool()) {
|
||||
result = c.block.execute(this)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if (!found && elseCase != null) result = elseCase.execute(this)
|
||||
result
|
||||
}
|
||||
} else {
|
||||
// when { cond -> ... }
|
||||
TODO("when without object is not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseThrowStatement(cc: CompilerContext): Statement {
|
||||
val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected")
|
||||
return statement {
|
||||
var errorObject = throwStatement.execute(this)
|
||||
if (errorObject is ObjString)
|
||||
errorObject = ObjException(this, errorObject.value)
|
||||
if (errorObject is ObjException)
|
||||
raiseError(errorObject)
|
||||
else raiseError("this is not an exception object: $errorObject")
|
||||
}
|
||||
}
|
||||
|
||||
private data class CatchBlockData(
|
||||
val catchVar: Token,
|
||||
val classNames: List<String>,
|
||||
val block: Statement
|
||||
)
|
||||
|
||||
private fun parseTryStatement(cc: CompilerContext): Statement {
|
||||
val body = parseBlock(cc)
|
||||
val catches = mutableListOf<CatchBlockData>()
|
||||
cc.skipTokens(Token.Type.NEWLINE)
|
||||
var t = cc.next()
|
||||
while (t.value == "catch") {
|
||||
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
t = cc.next()
|
||||
if (t.type != Token.Type.ID) throw ScriptError(t.pos, "expected catch variable")
|
||||
val catchVar = t
|
||||
|
||||
val exClassNames = mutableListOf<String>()
|
||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
// load list of exception classes
|
||||
do {
|
||||
t = cc.next()
|
||||
if (t.type != Token.Type.ID)
|
||||
throw ScriptError(t.pos, "expected exception class name")
|
||||
exClassNames += t.value
|
||||
t = cc.next()
|
||||
when (t.type) {
|
||||
Token.Type.COMMA -> {
|
||||
continue
|
||||
}
|
||||
|
||||
Token.Type.RPAREN -> {
|
||||
break
|
||||
}
|
||||
|
||||
else -> throw ScriptError(t.pos, "syntax error: expected ',' or ')'")
|
||||
}
|
||||
} while (true)
|
||||
} else {
|
||||
// no type!
|
||||
exClassNames += "Exception"
|
||||
cc.skipTokenOfType(Token.Type.RPAREN)
|
||||
}
|
||||
val block = parseBlock(cc)
|
||||
catches += CatchBlockData(catchVar, exClassNames, block)
|
||||
cc.skipTokens(Token.Type.NEWLINE)
|
||||
t = cc.next()
|
||||
} else {
|
||||
// no (e: Exception) block: should be shortest variant `catch { ... }`
|
||||
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
|
||||
catches += CatchBlockData(
|
||||
Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
|
||||
parseBlock(cc, true)
|
||||
)
|
||||
t = cc.next()
|
||||
}
|
||||
}
|
||||
val finallyClause = if (t.value == "finally") {
|
||||
parseBlock(cc)
|
||||
} else {
|
||||
cc.previous()
|
||||
null
|
||||
}
|
||||
|
||||
if (catches.isEmpty() && finallyClause == null)
|
||||
throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
|
||||
|
||||
return statement {
|
||||
var result: Obj = ObjVoid
|
||||
try {
|
||||
// body is a parsed block, it already has separate context
|
||||
result = body.execute(this)
|
||||
} catch (e: Exception) {
|
||||
// convert to appropriate exception
|
||||
val objException = when (e) {
|
||||
is ExecutionError -> e.errorObject
|
||||
else -> ObjUnknownException(this, e.message ?: e.toString())
|
||||
}
|
||||
// let's see if we should catch it:
|
||||
var isCaught = false
|
||||
for (cdata in catches) {
|
||||
var exceptionObject: ObjException? = null
|
||||
for (exceptionClassName in cdata.classNames) {
|
||||
val exObj = ObjException.getErrorClass(exceptionClassName)
|
||||
?: raiseSymbolNotFound("error clas not exists: $exceptionClassName")
|
||||
if (objException.isInstanceOf(exObj)) {
|
||||
exceptionObject = objException
|
||||
break
|
||||
}
|
||||
}
|
||||
if (exceptionObject != null) {
|
||||
val catchContext = this.copy(pos = cdata.catchVar.pos)
|
||||
catchContext.addItem(cdata.catchVar.value, false, objException)
|
||||
result = cdata.block.execute(catchContext)
|
||||
isCaught = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// rethrow if not caught this exception
|
||||
if (!isCaught)
|
||||
throw e
|
||||
} finally {
|
||||
// finally clause does not alter result!
|
||||
finallyClause?.execute(this)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val constructorArgsDeclaration =
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||
parseArgsDeclaration(cc)
|
||||
parseArgsDeclaration(cc, isClassDeclaration = true)
|
||||
else null
|
||||
|
||||
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||
@ -743,41 +1040,12 @@ class Compiler(
|
||||
// fields. Note that 'this' is already set by class
|
||||
constructorArgsDeclaration?.assignToContext(this)
|
||||
bodyInit?.execute(this)
|
||||
// export public
|
||||
for( (name,record) in objects ) {
|
||||
println("-- $name $record")
|
||||
when(record.visibility) {
|
||||
Visibility.Public -> {
|
||||
thisObj.publicFields += name
|
||||
thisObj.protectedFields += name
|
||||
}
|
||||
Visibility.Protected ->
|
||||
thisObj.protectedFields += name
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thisObj
|
||||
}
|
||||
// inheritance must alter this code:
|
||||
val newClass = ObjClass(className).apply {
|
||||
instanceConstructor = constructorCode
|
||||
constructorArgsDeclaration?.let { cad ->
|
||||
// we need accessors for all fields:
|
||||
for (f in cad.params) {
|
||||
createField(
|
||||
f.name,
|
||||
statement {
|
||||
val context = (thisObj as ObjInstance).instanceContext
|
||||
println("called on $thisObj")
|
||||
context[f.name]?.value ?: raiseError("field is not initialized: ${f.name}")
|
||||
},
|
||||
true,
|
||||
// (f.accessType ?: defaultAccess).isMutable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statement {
|
||||
@ -856,7 +1124,7 @@ class Compiler(
|
||||
var breakCaught = false
|
||||
|
||||
if (size > 0) {
|
||||
var current = runCatching { sourceObj.getAt(forContext, 0) }
|
||||
var current = runCatching { sourceObj.getAt(forContext, ObjInt(0)) }
|
||||
.getOrElse {
|
||||
throw ScriptError(
|
||||
tOp.pos,
|
||||
@ -883,7 +1151,7 @@ class Compiler(
|
||||
}
|
||||
} else result = body.execute(forContext)
|
||||
if (++index >= size) break
|
||||
current = sourceObj.getAt(forContext, index)
|
||||
current = sourceObj.getAt(forContext, ObjInt(index.toLong()))
|
||||
}
|
||||
}
|
||||
if (!breakCaught && elseStatement != null) {
|
||||
@ -913,8 +1181,9 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
return lbe.result
|
||||
}
|
||||
return lbe.result
|
||||
throw lbe
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -941,8 +1210,9 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
return lbe.result
|
||||
}
|
||||
return lbe.result
|
||||
throw lbe
|
||||
}
|
||||
else {
|
||||
loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
|
||||
@ -952,6 +1222,59 @@ class Compiler(
|
||||
return elseStatement?.execute(forContext) ?: result
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
private fun parseDoWhileStatement(cc: CompilerContext): Statement {
|
||||
val label = getLabel(cc)?.also { cc.labels += it }
|
||||
val (breakFound, body) = cc.parseLoop {
|
||||
parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "Bad while statement: expected statement")
|
||||
}
|
||||
label?.also { cc.labels -= it }
|
||||
|
||||
cc.skipTokens(Token.Type.NEWLINE)
|
||||
|
||||
val t = cc.next()
|
||||
if (t.type != Token.Type.ID && t.value != "while")
|
||||
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
|
||||
|
||||
val conditionStart = ensureLparen(cc)
|
||||
val condition =
|
||||
parseExpression(cc) ?: throw ScriptError(conditionStart, "Bad while statement: expected expression")
|
||||
ensureRparen(cc)
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
||||
parseStatement(cc)
|
||||
} else {
|
||||
cc.previous()
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
return statement(body.pos) {
|
||||
var wasBroken = false
|
||||
var result: Obj = ObjVoid
|
||||
lateinit var doContext: Context
|
||||
do {
|
||||
doContext = it.copy().apply { skipContextCreation = true }
|
||||
try {
|
||||
result = body.execute(doContext)
|
||||
} catch (e: LoopBreakContinueException) {
|
||||
if (e.label == label || e.label == null) {
|
||||
if (e.doContinue) continue
|
||||
else {
|
||||
result = e.result
|
||||
wasBroken = true
|
||||
break
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
} while (condition.execute(doContext).toBool())
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
||||
val label = getLabel(cc)?.also { cc.labels += it }
|
||||
val start = ensureLparen(cc)
|
||||
@ -1101,26 +1424,36 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
||||
var t = tokens.next()
|
||||
private fun parseFunctionDeclaration(
|
||||
cc: CompilerContext,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||
isExtern: Boolean = false
|
||||
): Statement {
|
||||
var t = cc.next()
|
||||
val start = t.pos
|
||||
val name = if (t.type != Token.Type.ID)
|
||||
throw ScriptError(t.pos, "Expected identifier after 'fn'")
|
||||
else t.value
|
||||
|
||||
t = tokens.next()
|
||||
t = cc.next()
|
||||
if (t.type != Token.Type.LPAREN)
|
||||
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
|
||||
|
||||
val argsDeclaration = parseArgsDeclaration(tokens)
|
||||
val argsDeclaration = parseArgsDeclaration(cc)
|
||||
if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||
throw ScriptError(
|
||||
t.pos,
|
||||
"Bad function definition: expected valid argument declaration or () after 'fn ${name}'"
|
||||
)
|
||||
|
||||
if (cc.current().type == Token.Type.COLON) parseTypeDeclaration(cc)
|
||||
|
||||
// Here we should be at open body
|
||||
val fnStatements = parseBlock(tokens)
|
||||
val fnStatements = if (isExtern)
|
||||
statement { raiseError("extern function not provided: $name") }
|
||||
else
|
||||
parseBlock(cc)
|
||||
|
||||
var closure: Context? = null
|
||||
|
||||
@ -1138,7 +1471,7 @@ class Compiler(
|
||||
// we added fn in the context. now we must save closure
|
||||
// for the function
|
||||
closure = context
|
||||
context.addItem(name, false, fnBody)
|
||||
context.addItem(name, false, fnBody, visibility)
|
||||
// as the function can be called from anywhere, we have
|
||||
// saved the proper context in the closure
|
||||
fnBody
|
||||
@ -1155,7 +1488,7 @@ class Compiler(
|
||||
val block = parseScript(startPos, cc)
|
||||
return statement(startPos) {
|
||||
// block run on inner context:
|
||||
block.execute(it.copy(startPos))
|
||||
block.execute(if (it.skipContextCreation) it else it.copy(startPos))
|
||||
}.also {
|
||||
val t1 = cc.next()
|
||||
if (t1.type != Token.Type.RBRACE)
|
||||
@ -1163,17 +1496,22 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
||||
private fun parseVarDeclaration(
|
||||
isMutable: Boolean,
|
||||
visibility: Visibility,
|
||||
tokens: CompilerContext,
|
||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
||||
): Statement {
|
||||
val nameToken = tokens.next()
|
||||
val start = nameToken.pos
|
||||
if (nameToken.type != Token.Type.ID)
|
||||
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
|
||||
throw ScriptError(nameToken.pos, "Expected identifier here")
|
||||
val name = nameToken.value
|
||||
|
||||
val eqToken = tokens.next()
|
||||
var setNull = false
|
||||
if (eqToken.type != Token.Type.ASSIGN) {
|
||||
if (!mutable)
|
||||
if (!isMutable)
|
||||
throw ScriptError(start, "val must be initialized")
|
||||
else {
|
||||
tokens.previous()
|
||||
@ -1181,7 +1519,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
val initialExpression = if (setNull) null else parseExpression(tokens)
|
||||
val initialExpression = if (setNull) null else parseStatement(tokens, true)
|
||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||
|
||||
return statement(nameToken.pos) { context ->
|
||||
@ -1192,8 +1530,8 @@ class Compiler(
|
||||
// create a separate copy:
|
||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||
|
||||
context.addItem(name, mutable, initValue)
|
||||
ObjVoid
|
||||
context.addItem(name, isMutable, initValue, visibility)
|
||||
initValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -1291,6 +1629,8 @@ class Compiler(
|
||||
// bitwise or 2
|
||||
// bitwise and 3
|
||||
// equality/ne 4
|
||||
Operator.simple(Token.Type.EQARROW, ++lastPrty) { c, a, b -> ObjMapEntry(a, b) },
|
||||
//
|
||||
Operator.simple(Token.Type.EQ, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) == 0) },
|
||||
Operator.simple(Token.Type.NEQ, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) },
|
||||
Operator.simple(Token.Type.REF_EQ, lastPrty) { _, a, b -> ObjBool(a === b) },
|
||||
@ -1305,6 +1645,9 @@ class Compiler(
|
||||
Operator.simple(Token.Type.NOTIN, lastPrty) { c, a, b -> ObjBool(!b.contains(c, a)) },
|
||||
Operator.simple(Token.Type.IS, lastPrty) { c, a, b -> ObjBool(a.isInstanceOf(b)) },
|
||||
Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) },
|
||||
|
||||
Operator.simple(Token.Type.ELVIS, ++lastPrty) { c, a, b -> if (a == ObjNull) b else a },
|
||||
|
||||
// shuttle <=> 6
|
||||
Operator.simple(Token.Type.SHUTTLE, ++lastPrty) { c, a, b ->
|
||||
ObjInt(a.compareTo(c, b).toLong())
|
||||
@ -1336,7 +1679,8 @@ class Compiler(
|
||||
/**
|
||||
* The keywords that stop processing of expression term
|
||||
*/
|
||||
val stopKeywords = setOf("break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct")
|
||||
val stopKeywords =
|
||||
setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class")
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
||||
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
|
||||
val result = f()
|
||||
return Pair(breakFound, result).also {
|
||||
@ -21,7 +21,11 @@ internal class CompilerContext(val tokens: List<Token>) {
|
||||
|
||||
fun hasNext() = currentIndex < tokens.size
|
||||
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 savePos() = currentIndex
|
||||
@ -47,9 +51,21 @@ internal class CompilerContext(val tokens: List<Token>) {
|
||||
throw ScriptError(at, message)
|
||||
}
|
||||
|
||||
fun currentPos() =
|
||||
if (hasNext()) next().pos.also { previous() }
|
||||
else previous().pos.also { next() }
|
||||
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -98,4 +114,63 @@ internal class CompilerContext(val tokens: List<Token>) {
|
||||
breakFound = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of the next token if it is an identifier, null otherwise.
|
||||
* Does not change position.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun nextIdValue(): String? {
|
||||
return if (hasNext()) {
|
||||
val nt = tokens[currentIndex]
|
||||
if (nt.type == Token.Type.ID)
|
||||
nt.value
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun current(): Token = tokens[currentIndex]
|
||||
|
||||
/**
|
||||
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun atOffset(offset: Int): Token? =
|
||||
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||
|
||||
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
||||
val pos = savePos()
|
||||
var count = 0
|
||||
while( count < qualifiers.size) {
|
||||
val t = next()
|
||||
when(t.type) {
|
||||
Token.Type.ID -> {
|
||||
if( t.value in qualifiers ) count++
|
||||
else { restorePos(pos); return false }
|
||||
}
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||
else -> { restorePos(pos); return false }
|
||||
}
|
||||
}
|
||||
val t = next()
|
||||
if( t.type == Token.Type.ID && t.value == keyword ) {
|
||||
return true
|
||||
} else {
|
||||
restorePos(pos)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip newlines and comments. Returns (and reads) first non-whitespace token.
|
||||
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||
*/
|
||||
fun skipWsTokens(): Token {
|
||||
while( current().type in wstokens ) next()
|
||||
return next()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class Context(
|
||||
open class Context(
|
||||
val parent: Context?,
|
||||
val args: Arguments = Arguments.EMPTY,
|
||||
var pos: Pos = Pos.builtIn,
|
||||
val thisObj: Obj = ObjVoid
|
||||
var thisObj: Obj = ObjVoid,
|
||||
var skipContextCreation: Boolean = false,
|
||||
) {
|
||||
constructor(
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
@ -15,33 +16,38 @@ class Context(
|
||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullReferenceException(this))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||
raiseError(ObjIndexOutOfBoundsError(this, message))
|
||||
raiseError(ObjIndexOutOfBoundsException(this, message))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
||||
raiseError(ObjIllegalArgumentError(this, message))
|
||||
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||
fun raiseIllegalArgument(message: String = "Illegal argument error"): Nothing =
|
||||
raiseError(ObjIllegalArgumentException(this, message))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseSymbolNotFound(name: String): Nothing = raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
||||
fun raiseNoSuchElement(message: String = "No such element"): Nothing =
|
||||
raiseError(ObjIllegalArgumentException(this, message))
|
||||
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseSymbolNotFound(name: String): Nothing =
|
||||
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
||||
|
||||
fun raiseError(message: String): Nothing {
|
||||
throw ExecutionError(ObjError(this, message))
|
||||
throw ExecutionError(ObjException(this, message))
|
||||
}
|
||||
|
||||
fun raiseError(obj: ObjError): Nothing {
|
||||
fun raiseError(obj: ObjException): Nothing {
|
||||
throw ExecutionError(obj)
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
||||
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||
return (args.list[index].value as? T)
|
||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}")
|
||||
return (args.list[index] as? T)
|
||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> requireOnlyArg(): T {
|
||||
@ -61,9 +67,12 @@ class Context(
|
||||
|
||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
operator fun get(name: String): ObjRecord? =
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
open operator fun get(name: String): ObjRecord? =
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else {
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
}
|
||||
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
@ -73,8 +82,13 @@ class Context(
|
||||
|
||||
fun copy() = Context(this, args, pos, thisObj)
|
||||
|
||||
fun addItem(name: String, isMutable: Boolean, value: Obj): ObjRecord {
|
||||
return ObjRecord(value, isMutable).also { objects.put(name, it) }
|
||||
fun addItem(
|
||||
name: String,
|
||||
isMutable: Boolean,
|
||||
value: Obj,
|
||||
visibility: Visibility = Visibility.Public
|
||||
): ObjRecord {
|
||||
return ObjRecord(value, isMutable, visibility).also { objects[name] = it }
|
||||
}
|
||||
|
||||
fun getOrCreateNamespace(name: String): ObjClass {
|
||||
@ -112,5 +126,4 @@ class Context(
|
||||
|
||||
fun containsLocal(name: String): Boolean = name in objects
|
||||
|
||||
|
||||
}
|
@ -4,7 +4,10 @@ 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].
|
||||
@ -12,7 +15,7 @@ import net.sergeych.synctools.ProtectedOp
|
||||
data class ObjRecord(
|
||||
var value: Obj,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Compiler.Visibility = Compiler.Visibility.Public
|
||||
val visibility: Visibility = Visibility.Public
|
||||
)
|
||||
|
||||
/**
|
||||
@ -39,6 +42,9 @@ data class Accessor(
|
||||
}
|
||||
|
||||
open class Obj {
|
||||
|
||||
val isNull by lazy { this === ObjNull }
|
||||
|
||||
var isFrozen: Boolean = false
|
||||
|
||||
private val monitor = Mutex()
|
||||
@ -58,12 +64,16 @@ open class Obj {
|
||||
*/
|
||||
open fun byValueCopy(): Obj = this
|
||||
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||
@Suppress("SuspiciousEqualsCombination")
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||
objClass.allParentsSet.contains(someClass) ||
|
||||
someClass == rootObjectType
|
||||
|
||||
|
||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||
invokeInstanceMethod(context, name, Arguments(args.map { Arguments.Info(it, context.pos) }))
|
||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
||||
|
||||
inline suspend fun <reified T : Obj> callMethod(
|
||||
suspend inline fun <reified T : Obj> callMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
@ -86,7 +96,7 @@ open class Obj {
|
||||
}
|
||||
|
||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
context.raiseNotImplemented()
|
||||
return invokeInstanceMethod(context, "contains", other).toBool()
|
||||
}
|
||||
|
||||
open val asStr: ObjString by lazy {
|
||||
@ -97,16 +107,7 @@ open class Obj {
|
||||
* 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 val objClass: ObjClass = rootObjectType
|
||||
|
||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
@ -173,17 +174,23 @@ open class Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Lyng object to its Kotlin counterpart
|
||||
*/
|
||||
open suspend fun toKotlin(context: Context): Any? {
|
||||
return toString()
|
||||
}
|
||||
|
||||
fun willMutate(context: Context) {
|
||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
||||
}
|
||||
|
||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||
|
||||
suspend open fun readField(context: Context, name: String): ObjRecord {
|
||||
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")
|
||||
val value = obj.value
|
||||
return when (value) {
|
||||
return when (val value = obj.value) {
|
||||
is Statement -> {
|
||||
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
||||
}
|
||||
@ -199,7 +206,7 @@ open class Obj {
|
||||
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 {
|
||||
open suspend fun getAt(context: Context, index: Obj): Obj {
|
||||
context.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
@ -218,7 +225,7 @@ open class Obj {
|
||||
callOn(
|
||||
context.copy(
|
||||
context.pos,
|
||||
args = Arguments(args.map { Arguments.Info(it, context.pos) }),
|
||||
args = Arguments(args.toList()),
|
||||
newThisObj = thisObj
|
||||
)
|
||||
)
|
||||
@ -241,7 +248,33 @@ open class Obj {
|
||||
|
||||
|
||||
companion object {
|
||||
inline fun <reified T> from(obj: T): Obj {
|
||||
|
||||
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)?.instanceContext ?: 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)
|
||||
@ -251,8 +284,15 @@ open class 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")
|
||||
}
|
||||
}
|
||||
@ -286,6 +326,32 @@ object ObjNull : Obj() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ObjNull || other == null
|
||||
}
|
||||
|
||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
||||
context.raiseNPE()
|
||||
}
|
||||
|
||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj {
|
||||
context.raiseNPE()
|
||||
}
|
||||
|
||||
override suspend fun getAt(context: Context, index: Obj): Obj {
|
||||
context.raiseNPE()
|
||||
}
|
||||
|
||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
context.raiseNPE()
|
||||
}
|
||||
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
context.raiseNPE()
|
||||
}
|
||||
|
||||
override fun toString(): String = "null"
|
||||
|
||||
override suspend fun toKotlin(context: Context): Any? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
interface Numeric {
|
||||
@ -325,20 +391,112 @@ data class ObjNamespace(val name: String) : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
open class ObjError(val context: Context, val message: String) : Obj() {
|
||||
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
||||
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(
|
||||
"NullReferenceException",
|
||||
"AssertionFailedException",
|
||||
"ClassCastException",
|
||||
"IndexOutOfBoundsException",
|
||||
"IllegalArgumentException",
|
||||
"NoSuchElementException",
|
||||
"IllegalAssignmentException",
|
||||
"SymbolNotDefinedException",
|
||||
"IterationEndException",
|
||||
"AccessException",
|
||||
"UnknownException",
|
||||
)) {
|
||||
context.addConst(name, getOrCreateExceptionClass(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
||||
class ObjNullReferenceException(context: Context) : ObjException("NullReferenceException", context, "object is null")
|
||||
|
||||
class ObjAssertionError(context: Context, message: String) : ObjError(context, message)
|
||||
class ObjClassCastError(context: Context, message: String) : ObjError(context, message)
|
||||
class ObjIndexOutOfBoundsError(context: Context, message: String = "index out of bounds") : ObjError(context, message)
|
||||
class ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : ObjError(context, message)
|
||||
class ObjIllegalAssignmentError(context: Context, message: String = "illegal assignment") : ObjError(context, message)
|
||||
class ObjSymbolNotDefinedError(context: Context, message: String = "symbol is not defined") : ObjError(context, message)
|
||||
class ObjIterationFinishedError(context: Context) : ObjError(context, "iteration finished")
|
||||
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)
|
||||
|
||||
@Suppress("unused")
|
||||
class ObjNoSuchElementException(context: Context, message: String = "no such element") :
|
||||
ObjException("IllegalArgumentException", context, message)
|
||||
|
||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
||||
ObjException("NoSuchElementException", 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)
|
38
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjArray.kt
Normal file
38
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjArray.kt
Normal file
@ -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(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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,10 @@ data class ObjBool(val value: Boolean) : Obj() {
|
||||
|
||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||
|
||||
override suspend fun toKotlin(context: Context): Any {
|
||||
return value
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Bool")
|
||||
}
|
73
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt
Normal file
73
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt
Normal file
@ -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(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
|
||||
) {
|
||||
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 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 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 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
|
||||
}
|
||||
}
|
@ -9,6 +9,10 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
|
||||
override fun byValueCopy(): Obj = ObjInt(value)
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
|
||||
override suspend fun getAndIncrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value++ }
|
||||
}
|
||||
@ -72,7 +76,22 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
} else null
|
||||
}
|
||||
|
||||
override suspend fun toKotlin(context: Context): 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 {
|
||||
val Zero = ObjInt(0)
|
||||
val One = ObjInt(1)
|
||||
val type = ObjClass("Int")
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
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 = mutableListOf<Obj>()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result += it.invokeInstanceMethod(this, "next")
|
||||
}
|
||||
ObjMap(ObjMap.listToMap(this, 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 it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
val x = it.invokeInstanceMethod(this, "next")
|
||||
result += fn.execute(this.copy(Arguments(listOf(x))))
|
||||
}
|
||||
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,39 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* 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() }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
230
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt
Normal file
230
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt
Normal file
@ -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(context: Context, 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 -> context.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
val i = 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 suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun toKotlin(context: Context): Any {
|
||||
return list.map { it.toKotlin(context) }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
114
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt
Normal file
114
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt
Normal file
@ -0,0 +1,114 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjMapEntry) return -1
|
||||
val c = key.compareTo(context, other.key)
|
||||
if (c != 0) return c
|
||||
return value.compareTo(context, other.value)
|
||||
}
|
||||
|
||||
override suspend fun getAt(context: Context, index: Obj): Obj = when (index.toInt()) {
|
||||
0 -> key
|
||||
1 -> value
|
||||
else -> context.raiseIndexOutOfBounds()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "$key=>$value"
|
||||
}
|
||||
|
||||
override val objClass = type
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("MapEntry", ObjArray) {
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
return ObjMapEntry(context.requiredArg<Obj>(0), context.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>) : Obj() {
|
||||
|
||||
override val objClass = type
|
||||
|
||||
override suspend fun getAt(context: Context, index: Obj): Obj =
|
||||
map.getOrElse(index) { context.raiseNoSuchElement() }
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return other in map
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, 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(context: Context, 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(context, "size").toInt() != 2)
|
||||
context.raiseIllegalArgument(
|
||||
"list to construct map entry should exactly be 2 element Array like [key,value], got $list"
|
||||
)
|
||||
} else context.raiseIllegalArgument("first element of map list be a Collection of 2 elements; got $first")
|
||||
|
||||
|
||||
|
||||
list.forEach {
|
||||
map[it.getAt(context, ObjInt.Zero)] = it.getAt(context, ObjInt.One)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
|
||||
val type = object : ObjClass("Map", ObjCollection) {
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
return ObjMap(listToMap(context, context.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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@ package net.sergeych.lyng
|
||||
|
||||
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 fun toString(): String {
|
@ -33,7 +33,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||
}
|
||||
else {
|
||||
context.raiseError(ObjIterationFinishedError(context))
|
||||
context.raiseError(ObjIterationFinishedException(context))
|
||||
}
|
||||
|
||||
companion object {
|
@ -10,6 +10,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override fun byValueCopy(): Obj = ObjReal(value)
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
@ -19,7 +21,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
|
||||
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 =
|
||||
ObjReal(this.value + other.toDouble())
|
||||
@ -36,6 +40,22 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value % other.toDouble())
|
||||
|
||||
/**
|
||||
* Returns unboxed Double value
|
||||
*/
|
||||
override suspend fun toKotlin(context: Context): 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 {
|
||||
val type: ObjClass = ObjClass("Real").apply {
|
||||
createField(
|
99
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjSet.kt
Normal file
99
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjSet.kt
Normal file
@ -0,0 +1,99 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
|
||||
override val objClass = type
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return set.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||
return ObjSet(
|
||||
if (other is ObjSet)
|
||||
(set + other.set).toMutableSet()
|
||||
else
|
||||
(set + other).toMutableSet()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
||||
when (other) {
|
||||
is ObjSet -> {
|
||||
set += other.set
|
||||
}
|
||||
|
||||
is ObjList -> {
|
||||
set += other.list
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (other.isInstanceOf(ObjIterable)) {
|
||||
val i = other.invokeInstanceMethod(context, "iterable")
|
||||
while (i.invokeInstanceMethod(context, "hasNext").toBool()) {
|
||||
set += i.invokeInstanceMethod(context, "next")
|
||||
}
|
||||
}
|
||||
set += other
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj {
|
||||
return if (other is ObjSet) {
|
||||
ObjSet(set.intersect(other.set).toMutableSet())
|
||||
} else
|
||||
context.raiseIllegalArgument("set operator * requires another set")
|
||||
}
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj {
|
||||
if (other !is ObjSet)
|
||||
context.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(context: Context, 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(context: Context): Obj {
|
||||
return ObjSet(context.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt
Normal file
117
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt
Normal file
@ -0,0 +1,117 @@
|
||||
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(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: 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))
|
||||
}
|
||||
context.raiseIllegalArgument("String index must be Int or Range")
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
return ObjString(this.value.sprintf(*context.args.toKotlinList(context).toTypedArray()))
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return if (other is ObjString)
|
||||
value.contains(other.value)
|
||||
else if (other is ObjChar)
|
||||
value.contains(other.value)
|
||||
else context.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("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()) }
|
||||
}
|
||||
}
|
||||
}
|
@ -45,12 +45,18 @@ private class Parser(fromPos: Pos) {
|
||||
'=' -> {
|
||||
if (pos.currentChar == '=') {
|
||||
pos.advance()
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("===", from, Token.Type.REF_EQ)
|
||||
} else
|
||||
Token("==", from, Token.Type.EQ)
|
||||
} else
|
||||
when (currentChar) {
|
||||
'=' -> {
|
||||
pos.advance()
|
||||
Token("===", from, Token.Type.REF_EQ)
|
||||
}
|
||||
else -> Token("==", from, Token.Type.EQ)
|
||||
}
|
||||
} else if( currentChar == '>' ) {
|
||||
pos.advance()
|
||||
Token("=>", from, Token.Type.EQARROW)
|
||||
}
|
||||
else
|
||||
Token("=", from, Token.Type.ASSIGN)
|
||||
}
|
||||
|
||||
@ -137,14 +143,20 @@ private class Parser(fromPos: Pos) {
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
// .. already parsed:
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
} else if (currentChar == '<') {
|
||||
pos.advance()
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
} else {
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
when (currentChar) {
|
||||
'.' -> {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
}
|
||||
|
||||
'<' -> {
|
||||
pos.advance()
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
}
|
||||
}
|
||||
} else
|
||||
Token(".", from, Token.Type.DOT)
|
||||
@ -153,11 +165,10 @@ private class Parser(fromPos: Pos) {
|
||||
'<' -> {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
if( currentChar == '>' ) {
|
||||
if (currentChar == '>') {
|
||||
pos.advance()
|
||||
Token("<=>", from, Token.Type.SHUTTLE)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Token("<=", from, Token.Type.LTE)
|
||||
}
|
||||
} else
|
||||
@ -236,6 +247,7 @@ private class Parser(fromPos: Pos) {
|
||||
}
|
||||
|
||||
'"' -> loadStringToken()
|
||||
|
||||
in digitsSet -> {
|
||||
pos.back()
|
||||
decodeNumber(loadChars(digits), from)
|
||||
@ -261,6 +273,21 @@ private class Parser(fromPos: Pos) {
|
||||
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 -> {
|
||||
// text infix operators:
|
||||
// Labels processing is complicated!
|
||||
@ -291,7 +318,19 @@ private class Parser(fromPos: Pos) {
|
||||
private fun decodeNumber(p1: String, start: Pos): Token =
|
||||
if (pos.end)
|
||||
Token(p1, start, Token.Type.INT)
|
||||
else if (currentChar == '.') {
|
||||
else if (currentChar == 'e' || currentChar == 'E') {
|
||||
pos.advance()
|
||||
var negative = false
|
||||
if (currentChar == '+')
|
||||
pos.advance()
|
||||
else if (currentChar == '-') {
|
||||
negative = true
|
||||
pos.advance()
|
||||
}
|
||||
var p3 = loadChars(digits)
|
||||
if (negative) p3 = "-$p3"
|
||||
Token("${p1}e$p3", start, Token.Type.REAL)
|
||||
} else if (currentChar == '.') {
|
||||
// could be decimal
|
||||
pos.advance()
|
||||
if (currentChar in digitsSet) {
|
||||
@ -324,7 +363,7 @@ private class Parser(fromPos: Pos) {
|
||||
// could be integer, also hex:
|
||||
if (currentChar == 'x' && p1 == "0") {
|
||||
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())
|
||||
raise("invalid hex literal")
|
||||
}
|
||||
@ -337,10 +376,11 @@ private class Parser(fromPos: Pos) {
|
||||
private val currentChar: Char get() = pos.currentChar
|
||||
|
||||
private fun loadStringToken(): Token {
|
||||
var start = currentPos
|
||||
val start = currentPos
|
||||
|
||||
if (currentChar == '"') pos.advance()
|
||||
else start = start.back()
|
||||
// if (currentChar == '"') pos.advance()
|
||||
// else start = start.back()
|
||||
// start = start.back()
|
||||
|
||||
val sb = StringBuilder()
|
||||
while (currentChar != '"') {
|
@ -5,6 +5,7 @@ data class Pos(val source: Source, val line: Int, val column: Int) {
|
||||
return "${source.fileName}:${line+1}:${column}"
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun back(): Pos =
|
||||
if( column > 0) Pos(source, line, column-1)
|
||||
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
|
@ -6,6 +6,7 @@ import kotlin.math.*
|
||||
class Script(
|
||||
override val pos: Pos,
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
// private val catchReturn: Boolean = false,
|
||||
) : Statement() {
|
||||
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
@ -16,10 +17,11 @@ class Script(
|
||||
return lastResult
|
||||
}
|
||||
|
||||
suspend fun execute() = execute(defaultContext.copy(pos))
|
||||
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
||||
|
||||
companion object {
|
||||
val defaultContext: Context = Context().apply {
|
||||
ObjException.addExceptionsToContext(this)
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.asStr.value)
|
||||
@ -116,14 +118,14 @@ class Script(
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
if( !cond.value == true )
|
||||
raiseError(ObjAssertionError(this,"Assertion failed"))
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed"))
|
||||
}
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) != 0 )
|
||||
raiseError(ObjAssertionError(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
||||
}
|
||||
addFn("assertThrows") {
|
||||
val code = requireOnlyArg<Statement>()
|
||||
@ -137,33 +139,39 @@ class Script(
|
||||
catch (e: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
result ?: raiseError(ObjAssertionError(this,"Expected exception but nothing was thrown"))
|
||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
}
|
||||
|
||||
addConst("Object", rootObjectType)
|
||||
addConst("Real", ObjReal.type)
|
||||
addConst("String", ObjString.type)
|
||||
addConst("Int", ObjInt.type)
|
||||
addConst("Bool", ObjBool.type)
|
||||
addConst("Char", ObjChar.type)
|
||||
addConst("List", ObjList.type)
|
||||
addConst("Set", ObjSet.type)
|
||||
addConst("Range", ObjRange.type)
|
||||
addConst("Map", ObjMap.type)
|
||||
addConst("MapEntry", ObjMapEntry.type)
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
addConst("Callable", Statement.type)
|
||||
// interfaces
|
||||
addConst("Iterable", ObjIterable)
|
||||
addConst("Collection", ObjCollection)
|
||||
addConst("Array", ObjArray)
|
||||
addConst("Class", ObjClassType)
|
||||
addConst("Object", Obj().objClass)
|
||||
|
||||
val pi = ObjReal(PI)
|
||||
addConst("π", pi)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
addConst("PI", pi)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -12,4 +12,4 @@ open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=
|
||||
cause
|
||||
)
|
||||
|
||||
class ExecutionError(val errorObject: ObjError) : ScriptError(errorObject.context.pos, errorObject.message)
|
||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.context.pos, errorObject.message)
|
@ -14,12 +14,18 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
IN, NOTIN, IS, NOTIS,
|
||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
|
||||
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,
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||
NEWLINE,
|
||||
EOF,
|
||||
NULL_COALESCE,
|
||||
ELVIS,
|
||||
NULL_COALESCE_INDEX,
|
||||
NULL_COALESCE_INVOKE,
|
||||
NULL_COALESCE_BLOCKINVOKE,
|
||||
}
|
||||
|
||||
companion object {
|
14
lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt
Normal file
14
lynglib/src/commonMain/kotlin/net/sergeych/lyng/TypeDecl.kt
Normal file
@ -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)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
enum class Visibility {
|
||||
Public, Private, Protected;//, Internal
|
||||
val isPublic by lazy { this == Public }
|
||||
@Suppress("unused")
|
||||
val isProtected by lazy { this == Protected }
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
|
||||
val LyngVersion = BuildKonfig.version
|
File diff suppressed because it is too large
Load Diff
53
lynglib/src/commonTest/kotlin/TypesTest.kt
Normal file
53
lynglib/src/commonTest/kotlin/TypesTest.kt
Normal file
@ -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())
|
||||
}
|
||||
}
|
@ -255,6 +255,16 @@ class BookTest {
|
||||
runDocTests("../docs/Range.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSet() = runTest {
|
||||
runDocTests("../docs/Set.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMap() = runTest {
|
||||
runDocTests("../docs/Map.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSampleBooks() = runTest {
|
||||
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
||||
@ -268,4 +278,9 @@ class BookTest {
|
||||
fun testArgumentBooks() = runTest {
|
||||
runDocTests("../docs/declaring_arguments.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionsBooks() = runTest {
|
||||
runDocTests("../docs/exceptions_handling.md")
|
||||
}
|
||||
}
|
@ -18,5 +18,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
rootProject.name = "lyng"
|
||||
include(":library")
|
||||
include(":lynglib")
|
||||
include(":lyng")
|
||||
|
Loading…
x
Reference in New Issue
Block a user