Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
badeea9b28 | ||
|
|
a1267c4395 | ||
|
|
55fd3ea716 | ||
|
|
697bafdcee | ||
|
|
28b83f9892 | ||
|
|
194fc8aca6 | ||
|
|
382532e0e1 | ||
|
|
c0eba1ecf0 | ||
|
|
b253eed032 | ||
|
|
d482401b15 | ||
|
|
88b355c40d | ||
|
|
652e1d3af4 | ||
|
|
2a93e6f7da | ||
|
|
20c81dbf2e | ||
|
|
fffa3d31bb |
No files matched your search
@@ -13,3 +13,4 @@ xcuserdata
|
|||||||
*.gpg
|
*.gpg
|
||||||
.gigaide
|
.gigaide
|
||||||
/kotlin-js-store/yarn.lock
|
/kotlin-js-store/yarn.lock
|
||||||
|
/test.lyng
|
||||||
+163
-36
@@ -1,6 +1,168 @@
|
|||||||
# OO implementation in Lyng
|
# 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
|
- Everything is an instance of some class
|
||||||
- Every class except Obj has at least one parent
|
- 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;
|
2. parents method: no guarantee but we enumerate parents in order of appearance;
|
||||||
3. possible extension methods (scoped)
|
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
|
TBD
|
||||||
|
|
||||||
[argument list](declaring_arguments.md)
|
[argument list](declaring_arguments.md)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Declaring arguments in Lyng
|
# Declaring arguments in Lyng
|
||||||
|
|
||||||
It is a common thing that occurs in many places in Lyng, function declarations,
|
It is a common thing that occurs in many places in Lyng, function declarations,
|
||||||
lambdas, struct and class declarations.
|
lambdas and class declarations.
|
||||||
|
|
||||||
## Regular
|
## Regular
|
||||||
|
|
||||||
|
|||||||
@@ -39,22 +39,5 @@ We can just put the code into the module code:
|
|||||||
|
|
||||||
## class initialization
|
## class initialization
|
||||||
|
|
||||||
class foo {
|
already done using `ObjInstance` class and instance-bound context with local
|
||||||
|
context stored in ObjInstance and class constructor statement in ObjClass.
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
@@ -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читается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим.
|
||||||
|
|
||||||
|
(продолжение следует)
|
||||||
+65
-10
@@ -15,8 +15,7 @@ __Other documents to read__ maybe after this one:
|
|||||||
- [OOP notes](OOP.md)
|
- [OOP notes](OOP.md)
|
||||||
- [math in Lyng](math.md)
|
- [math in Lyng](math.md)
|
||||||
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
||||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md)
|
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||||
See [samples folder](samples)
|
|
||||||
|
|
||||||
# Expressions
|
# Expressions
|
||||||
|
|
||||||
@@ -555,16 +554,16 @@ Or, more neat:
|
|||||||
|
|
||||||
## while
|
## 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
|
var count = 0
|
||||||
while( count < 5 ) {
|
val result = while( count < 5 ) count++
|
||||||
count++
|
result
|
||||||
count * 10
|
>>> 4
|
||||||
}
|
|
||||||
>>> 50
|
|
||||||
|
|
||||||
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
|
var count = 0
|
||||||
while( count < 5 ) {
|
while( count < 5 ) {
|
||||||
@@ -683,6 +682,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 `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.
|
- 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
|
## For loops
|
||||||
|
|
||||||
@@ -792,7 +821,7 @@ See [Ranges](Range.md) for detailed documentation on it.
|
|||||||
|
|
||||||
// single line comment
|
// single line comment
|
||||||
var result = null // here we will store the result
|
var result = null // here we will store the result
|
||||||
>>> void
|
>>> null
|
||||||
|
|
||||||
# Integral data types
|
# Integral data types
|
||||||
|
|
||||||
@@ -838,10 +867,36 @@ Are the same as in string literals with little difference:
|
|||||||
|
|
||||||
## String details
|
## String details
|
||||||
|
|
||||||
|
Strings are much like Kotlin ones:
|
||||||
|
|
||||||
|
"Hello".length
|
||||||
|
>>> 5
|
||||||
|
And supports growing set of kotlin-borrowed operations, see below, for example:
|
||||||
|
|
||||||
|
assertEquals("Hell", "Hello".dropLast(1))
|
||||||
|
>>> void
|
||||||
|
|
||||||
### String operations
|
### String operations
|
||||||
|
|
||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
||||||
|
|
||||||
|
Typical set of String functions includes:
|
||||||
|
|
||||||
|
| fun/prop | description / notes |
|
||||||
|
|------------------|------------------------------------------------------------|
|
||||||
|
| lower() | change case to unicode upper |
|
||||||
|
| upper() | change case to unicode lower |
|
||||||
|
| startsWith(prefix) | true if starts 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] |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Literals
|
### Literals
|
||||||
|
|
||||||
String literal could be multiline:
|
String literal could be multiline:
|
||||||
|
|||||||
+62
-39
@@ -1,17 +1,40 @@
|
|||||||
|
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
|
||||||
import com.vanniktech.maven.publish.SonatypeHost
|
import com.vanniktech.maven.publish.SonatypeHost
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
group = "net.sergeych"
|
||||||
|
version = "0.4.0-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 {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.androidLibrary)
|
alias(libs.plugins.androidLibrary)
|
||||||
alias(libs.plugins.vanniktech.mavenPublish)
|
alias(libs.plugins.vanniktech.mavenPublish)
|
||||||
kotlin("plugin.serialization") version "2.1.20"
|
kotlin("plugin.serialization") version "2.1.20"
|
||||||
|
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych"
|
buildkonfig {
|
||||||
version = "0.2.0-SNAPSHOT"
|
packageName = "net.sergeych.lyng"
|
||||||
|
// objectName = "YourAwesomeConfig"
|
||||||
|
// exposeObjectWithName = "YourAwesomePublicConfig"
|
||||||
|
|
||||||
|
defaultConfigs {
|
||||||
|
buildConfigField(STRING, "bcprovider", "codingfeline")
|
||||||
|
buildConfigField(STRING, "version", version.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
@@ -110,40 +133,40 @@ mavenPublishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
val projectVersion by project.extra(provider {
|
//val projectVersion by project.extra(provider {
|
||||||
// Compute value lazily
|
// // Compute value lazily
|
||||||
(version as String)
|
// (version as String)
|
||||||
})
|
//})
|
||||||
|
//
|
||||||
val generateBuildConfig by tasks.registering {
|
//val generateBuildConfig by tasks.registering {
|
||||||
// Declare outputs safely
|
// // Declare outputs safely
|
||||||
val outputDir = layout.buildDirectory.dir("generated/buildConfig/commonMain/kotlin")
|
// val outputDir = layout.buildDirectory.dir("generated/buildConfig/commonMain/kotlin")
|
||||||
outputs.dir(outputDir)
|
// outputs.dir(outputDir)
|
||||||
|
//
|
||||||
val version = projectVersion.get()
|
// val version = projectVersion.get()
|
||||||
|
//
|
||||||
// Inputs: Version is tracked as an input
|
// // Inputs: Version is tracked as an input
|
||||||
inputs.property("version", version)
|
// inputs.property("version", version)
|
||||||
|
//
|
||||||
doLast {
|
// doLast {
|
||||||
val packageName = "net.sergeych.lyng.buildconfig"
|
// val packageName = "net.sergeych.lyng.buildconfig"
|
||||||
val packagePath = packageName.replace('.', '/')
|
// val packagePath = packageName.replace('.', '/')
|
||||||
val buildConfigFile = outputDir.get().file("$packagePath/BuildConfig.kt").asFile
|
// val buildConfigFile = outputDir.get().file("$packagePath/BuildConfig.kt").asFile
|
||||||
|
//
|
||||||
buildConfigFile.parentFile?.mkdirs()
|
// buildConfigFile.parentFile?.mkdirs()
|
||||||
buildConfigFile.writeText(
|
// buildConfigFile.writeText(
|
||||||
"""
|
// """
|
||||||
|package $packageName
|
// |package $packageName
|
||||||
|
|
// |
|
||||||
|object BuildConfig {
|
// |object BuildConfig {
|
||||||
| const val VERSION = "$version"
|
// | const val VERSION = "$version"
|
||||||
|}
|
// |}
|
||||||
""".trimMargin()
|
// """.trimMargin()
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
//tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
dependsOn(generateBuildConfig)
|
// dependsOn(generateBuildConfig)
|
||||||
}
|
//}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
enum class AccessType(val isMutable: Boolean) {
|
||||||
|
Val(false), Var(true),
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
Initialization(false)
|
||||||
|
}
|
||||||
@@ -11,7 +11,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
val start = params.indexOfFirst { it.defaultValue != null }
|
val start = params.indexOfFirst { it.defaultValue != null }
|
||||||
if (start >= 0)
|
if (start >= 0)
|
||||||
for (j in start + 1 until params.size)
|
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,
|
params[j].pos,
|
||||||
"required argument can't follow default one"
|
"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(
|
suspend fun assignToContext(
|
||||||
context: Context,
|
context: Context,
|
||||||
fromArgs: Arguments = context.args,
|
arguments: Arguments = context.args,
|
||||||
defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var
|
defaultAccessType: AccessType = AccessType.Var,
|
||||||
|
defaultVisibility: Visibility = Visibility.Public
|
||||||
) {
|
) {
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value)
|
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 {
|
suspend fun processHead(index: Int): Int {
|
||||||
var i = index
|
var i = index
|
||||||
while (i != params.size) {
|
while (i != paramsSize) {
|
||||||
val a = params[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
i < fromArgs.size -> fromArgs[i]
|
i < callArgs.size -> callArgs[i]
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
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.raiseArgumentError("too few arguments for the call")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
i++
|
i++
|
||||||
@@ -46,14 +66,14 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processTail(index: Int): Int {
|
suspend fun processTail(index: Int): Int {
|
||||||
var i = params.size - 1
|
var i = paramsSize - 1
|
||||||
var j = fromArgs.size - 1
|
var j = callArgs.size - 1
|
||||||
while (i > index) {
|
while (i > index) {
|
||||||
val a = params[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
j >= index -> {
|
j >= index -> {
|
||||||
fromArgs[j--]
|
callArgs[j--]
|
||||||
}
|
}
|
||||||
|
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||||
@@ -68,16 +88,16 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
fun processEllipsis(index: Int, toFromIndex: Int) {
|
||||||
val a = params[index]
|
val a = params[index]
|
||||||
val l = if (index > toFromIndex) ObjList()
|
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)
|
assign(a, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
val leftIndex = processHead(0)
|
val leftIndex = processHead(0)
|
||||||
if (leftIndex < params.size) {
|
if (leftIndex < paramsSize) {
|
||||||
val end = processTail(leftIndex)
|
val end = processTail(leftIndex)
|
||||||
processEllipsis(leftIndex, end)
|
processEllipsis(leftIndex, end)
|
||||||
} else {
|
} else {
|
||||||
if (leftIndex < fromArgs.size)
|
if (leftIndex < callArgs.size)
|
||||||
context.raiseArgumentError("too many arguments for the call")
|
context.raiseArgumentError("too many arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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__.
|
* So it is a [Statement] that must be executed on __caller context__.
|
||||||
*/
|
*/
|
||||||
val defaultValue: Statement? = null,
|
val defaultValue: Statement? = null,
|
||||||
val accessType: Compiler.AccessType? = null,
|
val accessType: AccessType? = null,
|
||||||
val visibility: Compiler.Visibility? = null,
|
val visibility: Visibility? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2,53 +2,40 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||||
|
|
||||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
|
||||||
val list = mutableListOf<Arguments.Info>()
|
val list = mutableListOf<Obj>()
|
||||||
|
|
||||||
for (x in this) {
|
for (x in this) {
|
||||||
val value = x.value.execute(context)
|
val value = x.value.execute(context)
|
||||||
if (x.isSplat) {
|
if (x.isSplat) {
|
||||||
when {
|
when {
|
||||||
value is ObjList -> {
|
value is ObjList -> {
|
||||||
for (subitem in value.list) list.add(Arguments.Info(subitem, x.pos))
|
for (subitem in value.list) list.add(subitem)
|
||||||
}
|
}
|
||||||
|
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
value.isInstanceOf(ObjIterable) -> {
|
||||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
||||||
i.forEach { list.add(Arguments.Info(it, x.pos)) }
|
i.forEach { list.add(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
list.add(Arguments.Info(value, x.pos))
|
list.add(value)
|
||||||
}
|
}
|
||||||
return Arguments(list)
|
return Arguments(list,tailBlockMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Arguments(val list: List<Info>) : Iterable<Obj> {
|
data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : List<Obj> by list {
|
||||||
|
|
||||||
data class Info(val value: Obj, val pos: Pos)
|
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||||
|
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||||
val size by list::size
|
return list.first()
|
||||||
|
|
||||||
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 {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
val EMPTY = Arguments(emptyList())
|
||||||
fun from(values: Collection<Obj>) = Arguments(values.map { Info(it, Pos.UNKNOWN) })
|
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<Obj> {
|
|
||||||
return list.map { it.value }.iterator()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,18 +11,20 @@ class Compiler(
|
|||||||
class Settings
|
class Settings
|
||||||
|
|
||||||
fun compile(source: Source): Script {
|
fun compile(source: Source): Script {
|
||||||
return parseScript(source.startPos,
|
return parseScript(
|
||||||
|
source.startPos,
|
||||||
CompilerContext(parseLyng(source))
|
CompilerContext(parseLyng(source))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseScript(start: Pos, cc: CompilerContext): Script {
|
private fun parseScript(start: Pos, cc: CompilerContext): Script {
|
||||||
val statements = mutableListOf<Statement>()
|
val statements = mutableListOf<Statement>()
|
||||||
|
// val returnScope = cc.startReturnScope()
|
||||||
while (parseStatement(cc, braceMeansLambda = true)?.also {
|
while (parseStatement(cc, braceMeansLambda = true)?.also {
|
||||||
statements += it
|
statements += it
|
||||||
} != null) {/**/
|
} != null) {/**/
|
||||||
}
|
}
|
||||||
return Script(start, statements)
|
return Script(start, statements)//returnScope.needCatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatement(cc: CompilerContext, braceMeansLambda: Boolean = false): Statement? {
|
private fun parseStatement(cc: CompilerContext, braceMeansLambda: Boolean = false): Statement? {
|
||||||
@@ -37,6 +39,13 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.PRIVATE, Token.Type.PROTECTED -> {
|
||||||
|
if (cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) {
|
||||||
|
continue
|
||||||
|
} else
|
||||||
|
throw ScriptError(t.pos, "unexpected keyword ${t.value}")
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
parseExpression(cc)
|
parseExpression(cc)
|
||||||
@@ -125,21 +134,49 @@ class Compiler(
|
|||||||
var isCall = false
|
var isCall = false
|
||||||
val next = cc.next()
|
val next = cc.next()
|
||||||
if (next.type == Token.Type.ID) {
|
if (next.type == Token.Type.ID) {
|
||||||
cc.ifNextIs(Token.Type.LPAREN) {
|
// could be () call or obj.method {} call
|
||||||
// instance method call
|
val nt = cc.current()
|
||||||
val args = parseArgs(cc)
|
when (nt.type) {
|
||||||
isCall = true
|
Token.Type.LPAREN -> {
|
||||||
operand = Accessor { context ->
|
cc.next()
|
||||||
context.pos = next.pos
|
// instance method call
|
||||||
val v = left.getter(context).value
|
val args = parseArgs(cc).first
|
||||||
ObjRecord(
|
isCall = true
|
||||||
v.invokeInstanceMethod(
|
operand = Accessor { context ->
|
||||||
context,
|
context.pos = next.pos
|
||||||
next.value,
|
val v = left.getter(context).value
|
||||||
args.toArguments(context)
|
ObjRecord(
|
||||||
), isMutable = false
|
v.invokeInstanceMethod(
|
||||||
)
|
context,
|
||||||
|
next.value,
|
||||||
|
args.toArguments(context,false)
|
||||||
|
), isMutable = false
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.LBRACE -> {
|
||||||
|
// single lambda arg, like assertTrows { ... }
|
||||||
|
cc.next()
|
||||||
|
isCall = true
|
||||||
|
val lambda =
|
||||||
|
parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here")
|
||||||
|
println(cc.current())
|
||||||
|
cc.skipTokenOfType(Token.Type.RBRACE)
|
||||||
|
operand = Accessor { context ->
|
||||||
|
context.pos = next.pos
|
||||||
|
val v = left.getter(context).value
|
||||||
|
ObjRecord(
|
||||||
|
v.invokeInstanceMethod(
|
||||||
|
context,
|
||||||
|
next.value,
|
||||||
|
Arguments(listOf(lambda),true)
|
||||||
|
), isMutable = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isCall) {
|
if (!isCall) {
|
||||||
@@ -162,6 +199,7 @@ class Compiler(
|
|||||||
operand = parseFunctionCall(
|
operand = parseFunctionCall(
|
||||||
cc,
|
cc,
|
||||||
left,
|
left,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// Expression in parentheses
|
// Expression in parentheses
|
||||||
@@ -307,9 +345,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LBRACE -> {
|
Token.Type.LBRACE -> {
|
||||||
if (operand != null) {
|
operand = operand?.let { left ->
|
||||||
throw ScriptError(t.pos, "syntax error: lambda expression not allowed here")
|
cc.previous()
|
||||||
} else operand = parseLambdaExpression(cc)
|
parseFunctionCall(cc, left, blockArgument = true)
|
||||||
|
} ?: parseLambdaExpression(cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -340,7 +379,7 @@ class Compiler(
|
|||||||
val context = closure!!.copy(pos, args)
|
val context = closure!!.copy(pos, args)
|
||||||
if (argsDeclaration == null) {
|
if (argsDeclaration == null) {
|
||||||
// no args: automatic var 'it'
|
// no args: automatic var 'it'
|
||||||
val l = args.values
|
val l = args.list
|
||||||
val itValue: Obj = when (l.size) {
|
val itValue: Obj = when (l.size) {
|
||||||
// no args: it == void
|
// no args: it == void
|
||||||
0 -> ObjVoid
|
0 -> ObjVoid
|
||||||
@@ -401,16 +440,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)
|
* Parse argument declaration, used in lambda (and later in fn too)
|
||||||
* @return declaration or null if there is no valid list of arguments
|
* @return declaration or null if there is no valid list of arguments
|
||||||
@@ -429,43 +458,17 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NEWLINE -> {}
|
Token.Type.NEWLINE -> {}
|
||||||
|
Token.Type.PROTECTED, Token.Type.PRIVATE -> {
|
||||||
|
if (!isClassDeclaration) {
|
||||||
|
cc.restorePos(startPos); return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// visibility
|
// visibility
|
||||||
val visibility = when (t.value) {
|
val visibility = if (isClassDeclaration)
|
||||||
"private" -> {
|
cc.getVisibility(Visibility.Public)
|
||||||
if (!isClassDeclaration) {
|
else Visibility.Public
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
t = cc.next()
|
|
||||||
Visibility.Private
|
|
||||||
}
|
|
||||||
|
|
||||||
"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/var?
|
||||||
val access = when (t.value) {
|
val access = when (t.value) {
|
||||||
"val" -> {
|
"val" -> {
|
||||||
@@ -549,7 +552,12 @@ class Compiler(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
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>()
|
val args = mutableListOf<ParsedArgument>()
|
||||||
do {
|
do {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
@@ -574,6 +582,7 @@ class Compiler(
|
|||||||
// block after?
|
// block after?
|
||||||
val pos = cc.savePos()
|
val pos = cc.savePos()
|
||||||
val end = cc.next()
|
val end = cc.next()
|
||||||
|
var lastBlockArgument = false
|
||||||
if (end.type == Token.Type.LBRACE) {
|
if (end.type == Token.Type.LBRACE) {
|
||||||
// last argument - callable
|
// last argument - callable
|
||||||
val callableAccessor = parseLambdaExpression(cc)
|
val callableAccessor = parseLambdaExpression(cc)
|
||||||
@@ -584,22 +593,34 @@ class Compiler(
|
|||||||
},
|
},
|
||||||
end.pos
|
end.pos
|
||||||
)
|
)
|
||||||
|
lastBlockArgument = true
|
||||||
} else
|
} else
|
||||||
cc.restorePos(pos)
|
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): Accessor {
|
||||||
// insofar, functions always return lvalue
|
// 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 ->
|
return Accessor { context ->
|
||||||
val v = left.getter(context)
|
val v = left.getter(context)
|
||||||
v.value.callOn(
|
v.value.callOn(
|
||||||
context.copy(
|
context.copy(
|
||||||
context.pos,
|
context.pos,
|
||||||
args.toArguments(context)
|
args.toArguments(context, detectedBlockArgument)
|
||||||
// Arguments(
|
// Arguments(
|
||||||
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||||
// ),
|
// ),
|
||||||
@@ -689,6 +710,7 @@ class Compiler(
|
|||||||
"val" -> parseVarDeclaration(id.value, false, cc)
|
"val" -> parseVarDeclaration(id.value, false, cc)
|
||||||
"var" -> parseVarDeclaration(id.value, true, cc)
|
"var" -> parseVarDeclaration(id.value, true, cc)
|
||||||
"while" -> parseWhileStatement(cc)
|
"while" -> parseWhileStatement(cc)
|
||||||
|
"do" -> parseDoWhileStatement(cc)
|
||||||
"for" -> parseForStatement(cc)
|
"for" -> parseForStatement(cc)
|
||||||
"break" -> parseBreakStatement(id.pos, cc)
|
"break" -> parseBreakStatement(id.pos, cc)
|
||||||
"continue" -> parseContinueStatement(id.pos, cc)
|
"continue" -> parseContinueStatement(id.pos, cc)
|
||||||
@@ -703,7 +725,7 @@ class Compiler(
|
|||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
val nameToken = cc.requireToken(Token.Type.ID)
|
||||||
val constructorArgsDeclaration =
|
val constructorArgsDeclaration =
|
||||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||||
parseArgsDeclaration(cc)
|
parseArgsDeclaration(cc, isClassDeclaration = true)
|
||||||
else null
|
else null
|
||||||
|
|
||||||
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
@@ -743,41 +765,12 @@ class Compiler(
|
|||||||
// fields. Note that 'this' is already set by class
|
// fields. Note that 'this' is already set by class
|
||||||
constructorArgsDeclaration?.assignToContext(this)
|
constructorArgsDeclaration?.assignToContext(this)
|
||||||
bodyInit?.execute(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
|
thisObj
|
||||||
}
|
}
|
||||||
// inheritance must alter this code:
|
// inheritance must alter this code:
|
||||||
val newClass = ObjClass(className).apply {
|
val newClass = ObjClass(className).apply {
|
||||||
instanceConstructor = constructorCode
|
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 {
|
return statement {
|
||||||
@@ -952,6 +945,59 @@ class Compiler(
|
|||||||
return elseStatement?.execute(forContext) ?: result
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while( condition.execute(doContext).toBool() )
|
||||||
|
if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) }
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
||||||
val label = getLabel(cc)?.also { cc.labels += it }
|
val label = getLabel(cc)?.also { cc.labels += it }
|
||||||
val start = ensureLparen(cc)
|
val start = ensureLparen(cc)
|
||||||
@@ -1102,6 +1148,8 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
||||||
|
val visibility = tokens.getVisibility()
|
||||||
|
|
||||||
var t = tokens.next()
|
var t = tokens.next()
|
||||||
val start = t.pos
|
val start = t.pos
|
||||||
val name = if (t.type != Token.Type.ID)
|
val name = if (t.type != Token.Type.ID)
|
||||||
@@ -1138,7 +1186,7 @@ class Compiler(
|
|||||||
// we added fn in the context. now we must save closure
|
// we added fn in the context. now we must save closure
|
||||||
// for the function
|
// for the function
|
||||||
closure = context
|
closure = context
|
||||||
context.addItem(name, false, fnBody)
|
context.addItem(name, false, fnBody, visibility)
|
||||||
// as the function can be called from anywhere, we have
|
// as the function can be called from anywhere, we have
|
||||||
// saved the proper context in the closure
|
// saved the proper context in the closure
|
||||||
fnBody
|
fnBody
|
||||||
@@ -1155,7 +1203,7 @@ class Compiler(
|
|||||||
val block = parseScript(startPos, cc)
|
val block = parseScript(startPos, cc)
|
||||||
return statement(startPos) {
|
return statement(startPos) {
|
||||||
// block run on inner context:
|
// block run on inner context:
|
||||||
block.execute(it.copy(startPos))
|
block.execute(if( it.skipContextCreation ) it else it.copy(startPos))
|
||||||
}.also {
|
}.also {
|
||||||
val t1 = cc.next()
|
val t1 = cc.next()
|
||||||
if (t1.type != Token.Type.RBRACE)
|
if (t1.type != Token.Type.RBRACE)
|
||||||
@@ -1164,6 +1212,15 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
||||||
|
// we are just after var/val, visibility if exists is 2 steps behind
|
||||||
|
val visibility = when (tokens.atOffset(-2)?.type) {
|
||||||
|
Token.Type.PRIVATE ->
|
||||||
|
Visibility.Private
|
||||||
|
|
||||||
|
Token.Type.PROTECTED -> Visibility.Protected
|
||||||
|
else -> Visibility.Public
|
||||||
|
}
|
||||||
|
|
||||||
val nameToken = tokens.next()
|
val nameToken = tokens.next()
|
||||||
val start = nameToken.pos
|
val start = nameToken.pos
|
||||||
if (nameToken.type != Token.Type.ID)
|
if (nameToken.type != Token.Type.ID)
|
||||||
@@ -1181,7 +1238,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")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
|
|
||||||
return statement(nameToken.pos) { context ->
|
return statement(nameToken.pos) { context ->
|
||||||
@@ -1192,8 +1249,8 @@ class Compiler(
|
|||||||
// create a separate copy:
|
// create a separate copy:
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
|
||||||
context.addItem(name, mutable, initValue)
|
context.addItem(name, mutable, initValue, visibility)
|
||||||
ObjVoid
|
initValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,7 +1393,7 @@ class Compiler(
|
|||||||
/**
|
/**
|
||||||
* The keywords that stop processing of expression term
|
* 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", "struct")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
var loopLevel = 0
|
var loopLevel = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean,T> {
|
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||||
if (++loopLevel == 0) breakFound = false
|
if (++loopLevel == 0) breakFound = false
|
||||||
val result = f()
|
val result = f()
|
||||||
return Pair(breakFound, result).also {
|
return Pair(breakFound, result).also {
|
||||||
@@ -98,4 +98,55 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
breakFound = true
|
breakFound = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return value of the next token if it is an identifier, null otherwise.
|
||||||
|
* Does not change position.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
fun atOffset(offset: Int): Token? =
|
||||||
|
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan backwards as deep as specified looking for visibility token. Does not change position.
|
||||||
|
*/
|
||||||
|
fun getVisibility(default: Visibility = Visibility.Public, depths: Int = 2): Visibility {
|
||||||
|
for( i in -depths .. -1) {
|
||||||
|
when( atOffset(i)?.type) {
|
||||||
|
Token.Type.PROTECTED -> return Visibility.Protected
|
||||||
|
Token.Type.PRIVATE -> return Visibility.Private
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return default
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun expectKeyword(vararg keyword: String): String {
|
||||||
|
// val t = next()
|
||||||
|
// if (t.type != Token.Type.ID && t.value !in keyword) {
|
||||||
|
// throw ScriptError(t.pos, "expected one of ${keyword.joinToString()}")
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
// data class ReturnScope(val needCatch: Boolean = false)
|
||||||
|
|
||||||
|
// private val
|
||||||
|
|
||||||
|
// fun startReturnScope(): ReturnScope {
|
||||||
|
// return ReturnScope()
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,8 @@ class Context(
|
|||||||
val parent: Context?,
|
val parent: Context?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
var pos: Pos = Pos.builtIn,
|
var pos: Pos = Pos.builtIn,
|
||||||
val thisObj: Obj = ObjVoid
|
val thisObj: Obj = ObjVoid,
|
||||||
|
var skipContextCreation: Boolean = false,
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
@@ -28,7 +29,8 @@ class Context(
|
|||||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseSymbolNotFound(name: String): Nothing = raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
fun raiseSymbolNotFound(name: String): Nothing =
|
||||||
|
raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
fun raiseError(message: String): Nothing {
|
||||||
throw ExecutionError(ObjError(this, message))
|
throw ExecutionError(ObjError(this, message))
|
||||||
@@ -40,8 +42,8 @@ class Context(
|
|||||||
|
|
||||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
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}")
|
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||||
return (args.list[index].value as? T)
|
return (args.list[index] as? T)
|
||||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}")
|
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> requireOnlyArg(): T {
|
inline fun <reified T : Obj> requireOnlyArg(): T {
|
||||||
@@ -73,8 +75,13 @@ class Context(
|
|||||||
|
|
||||||
fun copy() = Context(this, args, pos, thisObj)
|
fun copy() = Context(this, args, pos, thisObj)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj): ObjRecord {
|
fun addItem(
|
||||||
return ObjRecord(value, isMutable).also { objects.put(name, it) }
|
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 {
|
fun getOrCreateNamespace(name: String): ObjClass {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import net.sergeych.synctools.ProtectedOp
|
|||||||
data class ObjRecord(
|
data class ObjRecord(
|
||||||
var value: Obj,
|
var value: Obj,
|
||||||
val isMutable: Boolean,
|
val isMutable: Boolean,
|
||||||
val visibility: Compiler.Visibility = Compiler.Visibility.Public
|
val visibility: Visibility = Visibility.Public
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +61,7 @@ open class Obj {
|
|||||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||||
|
|
||||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
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(
|
inline suspend fun <reified T : Obj> callMethod(
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -218,7 +218,7 @@ open class Obj {
|
|||||||
callOn(
|
callOn(
|
||||||
context.copy(
|
context.copy(
|
||||||
context.pos,
|
context.pos,
|
||||||
args = Arguments(args.map { Arguments.Info(it, context.pos) }),
|
args = Arguments(args.toList()),
|
||||||
newThisObj = thisObj
|
newThisObj = thisObj
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -286,6 +286,8 @@ object ObjNull : Obj() {
|
|||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is ObjNull || other == null
|
return other is ObjNull || other == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "null"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Numeric {
|
interface Numeric {
|
||||||
@@ -341,4 +343,5 @@ class ObjIndexOutOfBoundsError(context: Context, message: String = "index out of
|
|||||||
class ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : 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 ObjIllegalAssignmentError(context: Context, message: String = "illegal assignment") : ObjError(context, message)
|
||||||
class ObjSymbolNotDefinedError(context: Context, message: String = "symbol is not defined") : 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 ObjIterationFinishedError(context: Context) : ObjError(context, "iteration finished")
|
||||||
|
class ObjAccessError(context: Context, message: String = "access not allowed error") : ObjError(context, message)
|
||||||
@@ -23,8 +23,6 @@ class ObjClass(
|
|||||||
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(context: Context): Obj {
|
||||||
println("callOn $this constructing....")
|
|
||||||
println("on context: $context")
|
|
||||||
val instance = ObjInstance(this)
|
val instance = ObjInstance(this)
|
||||||
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
|
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
|
||||||
if (instanceConstructor != null) {
|
if (instanceConstructor != null) {
|
||||||
@@ -41,10 +39,10 @@ class ObjClass(
|
|||||||
name: String,
|
name: String,
|
||||||
initialValue: Obj,
|
initialValue: Obj,
|
||||||
isMutable: Boolean = false,
|
isMutable: Boolean = false,
|
||||||
visibility: Compiler.Visibility = Compiler.Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
pos: Pos = Pos.builtIn
|
pos: Pos = Pos.builtIn
|
||||||
) {
|
) {
|
||||||
if (name in members || allParentsSet.any { name in it.members } == true)
|
if (name in members || allParentsSet.any { name in it.members })
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,54 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||||
|
|
||||||
internal val publicFields = mutableSetOf<String>()
|
|
||||||
internal val protectedFields = mutableSetOf<String>()
|
|
||||||
|
|
||||||
internal lateinit var instanceContext: Context
|
internal lateinit var instanceContext: Context
|
||||||
|
|
||||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
override suspend fun readField(context: Context, name: String): ObjRecord {
|
||||||
return if( name in publicFields ) instanceContext[name]!!
|
return instanceContext[name]?.let {
|
||||||
else super.readField(context, name)
|
if (it.visibility.isPublic)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
context.raiseError(ObjAccessError(context, "can't access non-public field $name"))
|
||||||
|
}
|
||||||
|
?: super.readField(context, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||||
if( name in publicFields ) {
|
instanceContext[name]?.let { f ->
|
||||||
val f = instanceContext[name]!!
|
if (!f.visibility.isPublic)
|
||||||
if( !f.isMutable ) ObjIllegalAssignmentError(context, "can't reassign val $name").raise()
|
ObjIllegalAssignmentError(context, "can't assign to non-public field $name")
|
||||||
if( f.value.assign(context, newValue) == null)
|
if (!f.isMutable) ObjIllegalAssignmentError(context, "can't reassign val $name").raise()
|
||||||
|
if (f.value.assign(context, newValue) == null)
|
||||||
f.value = newValue
|
f.value = newValue
|
||||||
}
|
} ?: super.writeField(context, name, newValue)
|
||||||
else super.writeField(context, name, newValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj {
|
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
||||||
if( name in publicFields ) return instanceContext[name]!!.value.invoke(context, this, args)
|
instanceContext[name]?.let {
|
||||||
return super.invokeInstanceMethod(context, name, args)
|
if (it.visibility.isPublic)
|
||||||
|
it.value.invoke(context, this, args)
|
||||||
|
else
|
||||||
|
context.raiseError(ObjAccessError(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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,32 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
addConst("length",
|
addConst("length",
|
||||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
)
|
)
|
||||||
|
addFn("takeLast") {
|
||||||
|
thisAs<ObjString>().value.takeLast(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("take") {
|
||||||
|
thisAs<ObjString>().value.take(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("drop") {
|
||||||
|
thisAs<ObjString>().value.drop(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("dropLast") {
|
||||||
|
thisAs<ObjString>().value.dropLast(
|
||||||
|
requiredArg<ObjInt>(0).toInt()
|
||||||
|
).let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("lower") {
|
||||||
|
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||||
|
}
|
||||||
|
addFn("upper") {
|
||||||
|
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||||
|
}
|
||||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,8 @@ private class Parser(fromPos: Pos) {
|
|||||||
when (text) {
|
when (text) {
|
||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
|
"protected" -> Token("protected", from, Token.Type.PROTECTED)
|
||||||
|
"private" -> Token("private", from, Token.Type.PRIVATE)
|
||||||
else -> Token(text, from, Token.Type.ID)
|
else -> Token(text, from, Token.Type.ID)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -291,7 +293,19 @@ private class Parser(fromPos: Pos) {
|
|||||||
private fun decodeNumber(p1: String, start: Pos): Token =
|
private fun decodeNumber(p1: String, start: Pos): Token =
|
||||||
if (pos.end)
|
if (pos.end)
|
||||||
Token(p1, start, Token.Type.INT)
|
Token(p1, start, Token.Type.INT)
|
||||||
else if (currentChar == '.') {
|
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
|
// could be decimal
|
||||||
pos.advance()
|
pos.advance()
|
||||||
if (currentChar in digitsSet) {
|
if (currentChar in digitsSet) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import kotlin.math.*
|
|||||||
class Script(
|
class Script(
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
private val statements: List<Statement> = emptyList(),
|
private val statements: List<Statement> = emptyList(),
|
||||||
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(context: Context): Obj {
|
||||||
@@ -16,7 +17,7 @@ class Script(
|
|||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute() = execute(defaultContext.copy(pos))
|
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context().apply {
|
val defaultContext: Context = Context().apply {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL, ATLABEL, // label@ at@label
|
LABEL, ATLABEL, // label@ at@label
|
||||||
|
PRIVATE, PROTECTED,
|
||||||
|
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
EOF,
|
EOF,
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.buildconfig.BuildConfig
|
|
||||||
|
|
||||||
val LyngVersion = BuildConfig.VERSION
|
val LyngVersion = BuildKonfig.version
|
||||||
@@ -57,6 +57,8 @@ class ScriptTest {
|
|||||||
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E+22")
|
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E+22")
|
||||||
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E22")
|
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E22")
|
||||||
check("17.2e-22", Token.Type.REAL, 0, 0, "17.2E-22")
|
check("17.2e-22", Token.Type.REAL, 0, 0, "17.2E-22")
|
||||||
|
check("17.2e-22", Token.Type.REAL, 0, 0, "17.2E-22")
|
||||||
|
check("1e-22", Token.Type.REAL, 0, 0, "1E-22")
|
||||||
|
|
||||||
// hex
|
// hex
|
||||||
check("1", Token.Type.HEX, 0, 0, "0x1")
|
check("1", Token.Type.HEX, 0, 0, "0x1")
|
||||||
@@ -172,7 +174,7 @@ class ScriptTest {
|
|||||||
fun varsAndConstsTest() = runTest {
|
fun varsAndConstsTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val context = Context(pos = Pos.builtIn)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ObjVoid, context.eval(
|
ObjInt(3L), context.eval(
|
||||||
"""
|
"""
|
||||||
val a = 17
|
val a = 17
|
||||||
var b = 3
|
var b = 3
|
||||||
@@ -432,6 +434,16 @@ class ScriptTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun whileAssignTest() = runTest {
|
||||||
|
eval("""
|
||||||
|
var t = 0
|
||||||
|
val x = while( t < 5 ) { t++ }
|
||||||
|
// last returned value is 4 - when t was 5 body was not executed
|
||||||
|
assertEquals( 4, x )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun whileTest() = runTest {
|
fun whileTest() = runTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@@ -490,37 +502,41 @@ class ScriptTest {
|
|||||||
fun testAssignArgumentsNoEllipsis() = runTest {
|
fun testAssignArgumentsNoEllipsis() = runTest {
|
||||||
// equal args, no ellipsis, no defaults, ok
|
// equal args, no ellipsis, no defaults, ok
|
||||||
val ttEnd = Token.Type.RBRACE
|
val ttEnd = Token.Type.RBRACE
|
||||||
var pa = ArgsDeclaration(listOf(
|
var pa = ArgsDeclaration(
|
||||||
ArgsDeclaration.Item("a"),
|
listOf(
|
||||||
ArgsDeclaration.Item("b"),
|
ArgsDeclaration.Item("a"),
|
||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("b"),
|
||||||
), ttEnd)
|
ArgsDeclaration.Item("c"),
|
||||||
var c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
), ttEnd
|
||||||
|
)
|
||||||
|
var c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
assertEquals( ObjInt(1), c["a"]?.value)
|
assertEquals(ObjInt(1), c["a"]?.value)
|
||||||
assertEquals( ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
assertEquals( ObjInt(3), c["c"]?.value)
|
assertEquals(ObjInt(3), c["c"]?.value)
|
||||||
// less args: error
|
// less args: error
|
||||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1,2).map { it.toObj() }))
|
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
||||||
assertFailsWith<ScriptError> {
|
assertFailsWith<ScriptError> {
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
}
|
}
|
||||||
// less args, no ellipsis, defaults, ok
|
// less args, no ellipsis, defaults, ok
|
||||||
pa = ArgsDeclaration(listOf(
|
pa = ArgsDeclaration(
|
||||||
ArgsDeclaration.Item("a"),
|
listOf(
|
||||||
ArgsDeclaration.Item("b"),
|
ArgsDeclaration.Item("a"),
|
||||||
ArgsDeclaration.Item("c", defaultValue = statement { ObjInt(100) }),
|
ArgsDeclaration.Item("b"),
|
||||||
), ttEnd)
|
ArgsDeclaration.Item("c", defaultValue = statement { ObjInt(100) }),
|
||||||
|
), ttEnd
|
||||||
|
)
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
assertEquals( ObjInt(1), c["a"]?.value)
|
assertEquals(ObjInt(1), c["a"]?.value)
|
||||||
assertEquals( ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
assertEquals( ObjInt(100), c["c"]?.value)
|
assertEquals(ObjInt(100), c["c"]?.value)
|
||||||
// enough args. default value is ignored:
|
// enough args. default value is ignored:
|
||||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
assertEquals( ObjInt(10), c["a"]?.value)
|
assertEquals(ObjInt(10), c["a"]?.value)
|
||||||
assertEquals( ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
assertEquals( ObjInt(5), c["c"]?.value)
|
assertEquals(ObjInt(5), c["c"]?.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -528,16 +544,18 @@ class ScriptTest {
|
|||||||
// equal args,
|
// equal args,
|
||||||
// less args, no ellipsis, defaults, ok
|
// less args, no ellipsis, defaults, ok
|
||||||
val ttEnd = Token.Type.RBRACE
|
val ttEnd = Token.Type.RBRACE
|
||||||
val pa = ArgsDeclaration(listOf(
|
val pa = ArgsDeclaration(
|
||||||
ArgsDeclaration.Item("a"),
|
listOf(
|
||||||
ArgsDeclaration.Item("b", isEllipsis = true),
|
ArgsDeclaration.Item("a"),
|
||||||
), ttEnd)
|
ArgsDeclaration.Item("b", isEllipsis = true),
|
||||||
var c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
), ttEnd
|
||||||
|
)
|
||||||
|
var c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assert( a == 1 ); println(b)")
|
c.eval("assert( a == 1 ); println(b)")
|
||||||
c.eval("assert( b == [2,3] )")
|
c.eval("assert( b == [2,3] )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1,2).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a, 1 ); println(b)")
|
c.eval("assertEquals( a, 1 ); println(b)")
|
||||||
c.eval("assertEquals( b, [2] )")
|
c.eval("assertEquals( b, [2] )")
|
||||||
@@ -551,24 +569,26 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testAssignArgumentsStartEllipsis() = runTest {
|
fun testAssignArgumentsStartEllipsis() = runTest {
|
||||||
val ttEnd = Token.Type.RBRACE
|
val ttEnd = Token.Type.RBRACE
|
||||||
val pa = ArgsDeclaration(listOf(
|
val pa = ArgsDeclaration(
|
||||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
listOf(
|
||||||
ArgsDeclaration.Item("b"),
|
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("b"),
|
||||||
), ttEnd)
|
ArgsDeclaration.Item("c"),
|
||||||
var c = Context(args = Arguments.from(listOf(0,1,2,3).map { it.toObj() }))
|
), ttEnd
|
||||||
|
)
|
||||||
|
var c = Context(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[0,1] )")
|
c.eval("assertEquals( a,[0,1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[1] )")
|
c.eval("assertEquals( a,[1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(2,3).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[] )")
|
c.eval("assertEquals( a,[] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
@@ -583,34 +603,36 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testAssignArgumentsmiddleEllipsis() = runTest {
|
fun testAssignArgumentsmiddleEllipsis() = runTest {
|
||||||
val ttEnd = Token.Type.RBRACE
|
val ttEnd = Token.Type.RBRACE
|
||||||
val pa = ArgsDeclaration(listOf(
|
val pa = ArgsDeclaration(
|
||||||
ArgsDeclaration.Item("i"),
|
listOf(
|
||||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
ArgsDeclaration.Item("i"),
|
||||||
ArgsDeclaration.Item("b"),
|
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("b"),
|
||||||
), ttEnd)
|
ArgsDeclaration.Item("c"),
|
||||||
var c = Context(args = Arguments.from(listOf(-1,0,1,2,3).map { it.toObj() }))
|
), ttEnd
|
||||||
|
)
|
||||||
|
var c = Context(args = Arguments.from(listOf(-1, 0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, -1 )")
|
c.eval("assertEquals( i, -1 )")
|
||||||
c.eval("assertEquals( a,[0,1] )")
|
c.eval("assertEquals( a,[0,1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(0, 1,2,3).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, 0 )")
|
c.eval("assertEquals( i, 0 )")
|
||||||
c.eval("assertEquals( a,[1] )")
|
c.eval("assertEquals( a,[1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1,2,3).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, 1)")
|
c.eval("assertEquals( i, 1)")
|
||||||
c.eval("assertEquals( a,[] )")
|
c.eval("assertEquals( a,[] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(2,3).map { it.toObj() }))
|
c = Context(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
||||||
assertFailsWith<ExecutionError> {
|
assertFailsWith<ExecutionError> {
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
}
|
}
|
||||||
@@ -1385,8 +1407,9 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSimpleStruct() = runTest {
|
fun testSimpleStruct() = runTest {
|
||||||
val c = Context()
|
val c = Context()
|
||||||
c.eval("""
|
c.eval(
|
||||||
struct Point(x,y)
|
"""
|
||||||
|
class Point(x,y)
|
||||||
assert( Point::class is Class )
|
assert( Point::class is Class )
|
||||||
val p = Point(2,3)
|
val p = Point(2,3)
|
||||||
assert(p is Point)
|
assert(p is Point)
|
||||||
@@ -1398,14 +1421,16 @@ class ScriptTest {
|
|||||||
val p2 = Point(p.x+1,p.y+1)
|
val p2 = Point(p.x+1,p.y+1)
|
||||||
p.x = 0
|
p.x = 0
|
||||||
assertEquals( 0, p.x )
|
assertEquals( 0, p.x )
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNonAssignalbeFieldInStruct() = runTest {
|
fun testNonAssignalbeFieldInStruct() = runTest {
|
||||||
val c = Context()
|
val c = Context()
|
||||||
c.eval("""
|
c.eval(
|
||||||
struct Point(x,y)
|
"""
|
||||||
|
class Point(x,y)
|
||||||
val p = Point("2",3)
|
val p = Point("2",3)
|
||||||
assert(p is Point)
|
assert(p is Point)
|
||||||
assert( p.x == "2" )
|
assert( p.x == "2" )
|
||||||
@@ -1413,14 +1438,16 @@ class ScriptTest {
|
|||||||
|
|
||||||
p.x = 0
|
p.x = 0
|
||||||
assertEquals( 0, p.x )
|
assertEquals( 0, p.x )
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStructBodyVal() = runTest {
|
fun testStructBodyVal() = runTest {
|
||||||
val c = Context()
|
val c = Context()
|
||||||
c.eval("""
|
c.eval(
|
||||||
struct Point(x,y) {
|
"""
|
||||||
|
class Point(x,y) {
|
||||||
val length = sqrt(x*x+y*y)
|
val length = sqrt(x*x+y*y)
|
||||||
var foo = "zero"
|
var foo = "zero"
|
||||||
}
|
}
|
||||||
@@ -1433,14 +1460,16 @@ class ScriptTest {
|
|||||||
assert( p.foo == "bar")
|
assert( p.foo == "bar")
|
||||||
// length is a val, is shoud not change
|
// length is a val, is shoud not change
|
||||||
assert( p.length == 5 )
|
assert( p.length == 5 )
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStructBodyFun() = runTest {
|
fun testStructBodyFun() = runTest {
|
||||||
val c = Context()
|
val c = Context()
|
||||||
c.eval("""
|
c.eval(
|
||||||
struct Point(x,y) {
|
"""
|
||||||
|
class Point(x,y) {
|
||||||
fun length() {
|
fun length() {
|
||||||
sqrt(x*x+y*y)
|
sqrt(x*x+y*y)
|
||||||
}
|
}
|
||||||
@@ -1451,6 +1480,220 @@ class ScriptTest {
|
|||||||
p.y = 10
|
p.y = 10
|
||||||
println(p.length())
|
println(p.length())
|
||||||
assertEquals(sqrt(109), p.length())
|
assertEquals(sqrt(109), p.length())
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPrivateConstructorParams() = runTest {
|
||||||
|
val c = Context()
|
||||||
|
c.eval(
|
||||||
|
"""
|
||||||
|
class Point(private var x,y)
|
||||||
|
val p = Point(1,2)
|
||||||
|
p.y = 101
|
||||||
|
assertThrows { p.x = 10 }
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLBraceMethodCall() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class Foo() {
|
||||||
|
fun cond(block) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val f = Foo()
|
||||||
|
assertEquals( 1, f.cond { 1 } )
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLBraceFnCall() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun cond(block) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
assertEquals( 1, cond { 1 } )
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClasstoString() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class Point {
|
||||||
|
var x
|
||||||
|
var y
|
||||||
|
}
|
||||||
|
val p = Point()
|
||||||
|
p.x = 1
|
||||||
|
p.y = 2
|
||||||
|
println(p)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClassDefaultCompare() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
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) )
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAccessShortcuts() {
|
||||||
|
assertTrue(Visibility.Public.isPublic)
|
||||||
|
assertFalse(Visibility.Private.isPublic)
|
||||||
|
assertFalse(Visibility.Protected.isPublic)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun segfault1Test() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
|
||||||
|
fun findSumLimit(f) {
|
||||||
|
var sum = 0.0
|
||||||
|
for( n in 1..1000000 ) {
|
||||||
|
val s0 = sum
|
||||||
|
sum += f(n)
|
||||||
|
if( abs(sum - s0) < 0.00001 ) {
|
||||||
|
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)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIntExponentRealForm() = runTest {
|
||||||
|
assertEquals("1.0E-6", eval("1e-6").toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCallLastBlockAfterDetault() = runTest {
|
||||||
|
eval("""
|
||||||
|
// this means last is lambda:
|
||||||
|
fun f(e=1, f) {
|
||||||
|
"e="+e+"f="+f()
|
||||||
|
}
|
||||||
|
assertEquals("e=1f=xx", f { "xx" })
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCallLastBlockWithEllipsis() = runTest {
|
||||||
|
eval("""
|
||||||
|
// this means last is lambda:
|
||||||
|
fun f(e..., f) {
|
||||||
|
"e="+e+"f="+f()
|
||||||
|
}
|
||||||
|
assertEquals("e=[]f=xx", f { "xx" })
|
||||||
|
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" })
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMethodCallLastBlockAfterDefault() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Foo {
|
||||||
|
// this means last is lambda:
|
||||||
|
fun f(e=1, f) {
|
||||||
|
"e="+e+"f="+f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val f = Foo()
|
||||||
|
assertEquals("e=1f=xx", f.f { "xx" })
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMethodCallLastBlockWithEllipsis() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Foo {
|
||||||
|
// this means last is lambda:
|
||||||
|
fun f(e..., f) {
|
||||||
|
"e="+e+"f="+f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val f = Foo()
|
||||||
|
assertEquals("e=[]f=xx", f.f { "xx" })
|
||||||
|
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" })
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nationalCharsTest() = runTest {
|
||||||
|
eval("""
|
||||||
|
fun сумма_ряда(x, погрешность=0.0001, f) {
|
||||||
|
var сумма = 0
|
||||||
|
for( n in 1..100000) {
|
||||||
|
val следующая_сумма = сумма + f(x, n)
|
||||||
|
if( n > 1 && abs(следующая_сумма - сумма) < погрешность )
|
||||||
|
break следующая_сумма
|
||||||
|
сумма = следующая_сумма
|
||||||
|
}
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
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 )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doWhileSimpleTest() = runTest {
|
||||||
|
eval("""
|
||||||
|
var sum = 0
|
||||||
|
var x = do {
|
||||||
|
val s = sum
|
||||||
|
sum += 1
|
||||||
|
} while( s < 10 )
|
||||||
|
assertEquals(11, x)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFailDoWhileSample1() = runTest {
|
||||||
|
eval("""
|
||||||
|
fun readLine() { "done: result" }
|
||||||
|
val result = do {
|
||||||
|
val line = readLine()
|
||||||
|
} while( !line.startsWith("done:") )
|
||||||
|
assertEquals("result", result.drop(6))
|
||||||
|
result
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ val baseContext = Context().apply {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LyngCLI(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||||
|
|
||||||
override val printHelpOnEmptyArgs = true
|
override val printHelpOnEmptyArgs = true
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package net.sergeych.lyng_cli
|
|||||||
|
|
||||||
import com.github.ajalt.clikt.core.main
|
import com.github.ajalt.clikt.core.main
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.sergeych.LyngCLI
|
import net.sergeych.Lyng
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
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 com.github.ajalt.clikt.core.main
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.sergeych.LyngCLI
|
import net.sergeych.Lyng
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
LyngCLI( { runBlocking { it() } }).main(args)
|
Lyng( { runBlocking { it() } }).main(args)
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user