Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
No files matched your search
@@ -13,3 +13,4 @@ xcuserdata
|
||||
*.gpg
|
||||
.gigaide
|
||||
/kotlin-js-store/yarn.lock
|
||||
/test.lyng
|
||||
+163
-36
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
| NullPointerException | |
|
||||
| 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 |
|
||||
| | |
|
||||
|
||||
@@ -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читается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим.
|
||||
|
||||
(продолжение следует)
|
||||
+205
-11
@@ -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 samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||
|
||||
# Expressions
|
||||
|
||||
@@ -553,18 +552,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 +765,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 +836,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 +960,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 +1006,36 @@ Are the same as in string literals with little difference:
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
String literal could be multiline:
|
||||
|
||||
+63
-39
@@ -1,17 +1,40 @@
|
||||
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
|
||||
import com.vanniktech.maven.publish.SonatypeHost
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.6.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 {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.vanniktech.mavenPublish)
|
||||
kotlin("plugin.serialization") version "2.1.20"
|
||||
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||
}
|
||||
|
||||
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::class")
|
||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||
}
|
||||
@@ -110,40 +134,40 @@ mavenPublishing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
//
|
||||
//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)
|
||||
//}
|
||||
@@ -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 }
|
||||
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.raiseArgumentError("too few arguments for the call")
|
||||
}
|
||||
}
|
||||
assign(a, value)
|
||||
i++
|
||||
@@ -46,14 +66,14 @@ 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)
|
||||
@@ -68,16 +88,16 @@ 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)
|
||||
if (leftIndex < callArgs.size)
|
||||
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__.
|
||||
*/
|
||||
val defaultValue: Statement? = null,
|
||||
val accessType: Compiler.AccessType? = null,
|
||||
val visibility: Compiler.Visibility? = null,
|
||||
val accessType: AccessType? = 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)
|
||||
|
||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context): Arguments {
|
||||
val list = mutableListOf<Arguments.Info>()
|
||||
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(Arguments.Info(subitem, x.pos))
|
||||
for (subitem in value.list) list.add(subitem)
|
||||
}
|
||||
|
||||
value.isInstanceOf(ObjIterable) -> {
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
}
|
||||
|
||||
companion object {
|
||||
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
|
||||
|
||||
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? {
|
||||
@@ -125,9 +127,13 @@ class Compiler(
|
||||
var isCall = false
|
||||
val next = cc.next()
|
||||
if (next.type == Token.Type.ID) {
|
||||
cc.ifNextIs(Token.Type.LPAREN) {
|
||||
// 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)
|
||||
val args = parseArgs(cc).first
|
||||
isCall = true
|
||||
operand = Accessor { context ->
|
||||
context.pos = next.pos
|
||||
@@ -136,11 +142,35 @@ class Compiler(
|
||||
v.invokeInstanceMethod(
|
||||
context,
|
||||
next.value,
|
||||
args.toArguments(context)
|
||||
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) {
|
||||
operand = Accessor({ context ->
|
||||
@@ -162,6 +192,7 @@ class Compiler(
|
||||
operand = parseFunctionCall(
|
||||
cc,
|
||||
left,
|
||||
false,
|
||||
)
|
||||
} ?: run {
|
||||
// Expression in parentheses
|
||||
@@ -307,9 +338,10 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.LBRACE -> {
|
||||
if (operand != null) {
|
||||
throw ScriptError(t.pos, "syntax error: lambda expression not allowed here")
|
||||
} else operand = parseLambdaExpression(cc)
|
||||
operand = operand?.let { left ->
|
||||
cc.previous()
|
||||
parseFunctionCall(cc, left, blockArgument = true)
|
||||
} ?: parseLambdaExpression(cc)
|
||||
}
|
||||
|
||||
|
||||
@@ -340,7 +372,7 @@ class Compiler(
|
||||
val context = closure!!.copy(pos, args)
|
||||
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 +433,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 +451,14 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.NEWLINE -> {}
|
||||
|
||||
Token.Type.ID -> {
|
||||
// visibility
|
||||
val visibility = when (t.value) {
|
||||
"private" -> {
|
||||
if (!isClassDeclaration) {
|
||||
cc.restorePos(startPos); return null
|
||||
}
|
||||
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" -> {
|
||||
@@ -549,7 +542,12 @@ class Compiler(
|
||||
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>()
|
||||
do {
|
||||
val t = cc.next()
|
||||
@@ -574,6 +572,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 +583,34 @@ 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): 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)
|
||||
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 +650,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
|
||||
@@ -686,24 +697,273 @@ class Compiler(
|
||||
* @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()
|
||||
when {
|
||||
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private)
|
||||
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private)
|
||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true)
|
||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true)
|
||||
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 +1003,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 {
|
||||
@@ -913,9 +1144,10 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
}
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i in start.toLong()..<end.toLong()) {
|
||||
@@ -941,9 +1173,10 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
}
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
else {
|
||||
loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
|
||||
result = body.execute(forContext)
|
||||
@@ -952,6 +1185,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,7 +1387,11 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
||||
private fun parseFunctionDeclaration(
|
||||
tokens: CompilerContext,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
||||
): Statement {
|
||||
var t = tokens.next()
|
||||
val start = t.pos
|
||||
val name = if (t.type != Token.Type.ID)
|
||||
@@ -1138,7 +1428,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 +1445,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 +1453,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 +1476,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 +1487,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1336,7 +1631,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", "struct")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 @@ 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
|
||||
|
||||
/**
|
||||
* Skips next token if its type is `tokenType`, returns `true` if so.
|
||||
@@ -98,4 +100,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)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ class Context(
|
||||
val parent: Context?,
|
||||
val args: Arguments = Arguments.EMPTY,
|
||||
var pos: Pos = Pos.builtIn,
|
||||
val thisObj: Obj = ObjVoid
|
||||
val thisObj: Obj = ObjVoid,
|
||||
var skipContextCreation: Boolean = false,
|
||||
) {
|
||||
constructor(
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
@@ -15,33 +16,34 @@ 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(ObjNullPointerException(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))
|
||||
raiseError(ObjIllegalArgumentException(this, message))
|
||||
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseSymbolNotFound(name: String): Nothing = raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
||||
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 {
|
||||
@@ -73,8 +75,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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -61,9 +64,9 @@ open class Obj {
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||
|
||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||
invokeInstanceMethod(context, name, Arguments(args.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 +89,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 {
|
||||
@@ -179,11 +182,10 @@ open class Obj {
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -218,7 +220,7 @@ open class Obj {
|
||||
callOn(
|
||||
context.copy(
|
||||
context.pos,
|
||||
args = Arguments(args.map { Arguments.Info(it, context.pos) }),
|
||||
args = Arguments(args.toList()),
|
||||
newThisObj = thisObj
|
||||
)
|
||||
)
|
||||
@@ -286,6 +288,8 @@ object ObjNull : Obj() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is ObjNull || other == null
|
||||
}
|
||||
|
||||
override fun toString(): String = "null"
|
||||
}
|
||||
|
||||
interface Numeric {
|
||||
@@ -325,20 +329,100 @@ 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(
|
||||
"NullPointerException",
|
||||
"AssertionFailedException",
|
||||
"ClassCastException",
|
||||
"IndexOutOfBoundsException",
|
||||
"IllegalArgumentException",
|
||||
"IllegalAssignmentException",
|
||||
"SymbolNotDefinedException",
|
||||
"IterationEndException",
|
||||
"AccessException",
|
||||
"UnknownException",
|
||||
)) {
|
||||
context.addConst(name, getOrCreateExceptionClass(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
||||
class ObjNullPointerException(context: Context) : ObjException("NullPointerException", 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)
|
||||
|
||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
||||
ObjException("IllegalAssignmentException", context, message)
|
||||
|
||||
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
|
||||
ObjException("SymbolNotDefinedException", context, message)
|
||||
|
||||
class ObjIterationFinishedException(context: Context) :
|
||||
ObjException("IterationEndException", context, "iteration finished")
|
||||
|
||||
class ObjAccessException(context: Context, message: String = "access not allowed error") :
|
||||
ObjException("AccessException", context, message)
|
||||
|
||||
class ObjUnknownException(context: Context, message: String = "access not allowed error") :
|
||||
ObjException("UnknownException", context, message)
|
||||
@@ -2,7 +2,7 @@ package net.sergeych.lyng
|
||||
|
||||
val ObjClassType by lazy { ObjClass("Class") }
|
||||
|
||||
class ObjClass(
|
||||
open class ObjClass(
|
||||
val className: String,
|
||||
vararg val parents: ObjClass,
|
||||
) : Obj() {
|
||||
@@ -23,8 +23,6 @@ class ObjClass(
|
||||
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) {
|
||||
@@ -41,10 +39,11 @@ class ObjClass(
|
||||
name: String,
|
||||
initialValue: Obj,
|
||||
isMutable: Boolean = false,
|
||||
visibility: Compiler.Visibility = Compiler.Visibility.Public,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
pos: Pos = Pos.builtIn
|
||||
) {
|
||||
if (name in members || allParentsSet.any { name in it.members } == true)
|
||||
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)
|
||||
}
|
||||
@@ -99,7 +98,19 @@ val ObjIterable by lazy {
|
||||
*/
|
||||
val ObjCollection by lazy {
|
||||
val i: ObjClass = ObjIterable
|
||||
ObjClass("Collection", i)
|
||||
ObjClass("Collection", i).apply {
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val ObjIterator by lazy { ObjClass("Iterator") }
|
||||
@@ -124,7 +135,7 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||
} else raiseError(ObjIterationFinishedError(this))
|
||||
} else raiseError(ObjIterationFinishedException(this))
|
||||
}
|
||||
addFn("hasNext") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
@@ -147,6 +158,15 @@ val ObjArray by lazy {
|
||||
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, i).compareTo(this, obj) == 0 ) return@addFn ObjTrue
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
|
||||
addFn("isample") { "ok".toObj() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,54 @@ 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)
|
||||
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) {
|
||||
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)
|
||||
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
|
||||
}
|
||||
else super.writeField(context, name, newValue)
|
||||
} ?: 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -31,6 +31,14 @@ data class ObjString(val value: String) : Obj() {
|
||||
return ObjChar(value[index])
|
||||
}
|
||||
|
||||
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.raiseArgumentError("String.contains can't take $other")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("String").apply {
|
||||
addConst("startsWith",
|
||||
@@ -40,6 +48,32 @@ data class ObjString(val value: String) : Obj() {
|
||||
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()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +137,19 @@ private class Parser(fromPos: Pos) {
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
// .. already parsed:
|
||||
if (currentChar == '.') {
|
||||
when (currentChar) {
|
||||
'.' -> {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
} else if (currentChar == '<') {
|
||||
}
|
||||
'<' -> {
|
||||
pos.advance()
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
} else {
|
||||
}
|
||||
else -> {
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
}
|
||||
}
|
||||
} else
|
||||
Token(".", from, Token.Type.DOT)
|
||||
}
|
||||
@@ -291,7 +295,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 +340,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")
|
||||
}
|
||||
|
||||
@@ -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,7 +139,7 @@ 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") {
|
||||
|
||||
@@ -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)
|
||||
@@ -17,6 +17,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
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.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("1e-22", Token.Type.REAL, 0, 0, "1E-22")
|
||||
|
||||
// hex
|
||||
check("1", Token.Type.HEX, 0, 0, "0x1")
|
||||
@@ -172,7 +174,7 @@ class ScriptTest {
|
||||
fun varsAndConstsTest() = runTest {
|
||||
val context = Context(pos = Pos.builtIn)
|
||||
assertEquals(
|
||||
ObjVoid, context.eval(
|
||||
ObjInt(3L), context.eval(
|
||||
"""
|
||||
val a = 17
|
||||
var b = 3
|
||||
@@ -296,9 +298,9 @@ class ScriptTest {
|
||||
@Test
|
||||
fun eqNeqTest() = runTest {
|
||||
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
||||
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
||||
assertEquals(ObjFalse, eval("val x = 3; x == 2"))
|
||||
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
||||
assertEquals(ObjBool(false), eval("val x = 3; x != 3"))
|
||||
assertEquals(ObjFalse, eval("val x = 3; x != 3"))
|
||||
|
||||
assertTrue { eval("1 == 1").toBool() }
|
||||
assertTrue { eval("true == true").toBool() }
|
||||
@@ -311,17 +313,17 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun logicTest() = runTest {
|
||||
assertEquals(ObjBool(false), eval("true && false"))
|
||||
assertEquals(ObjBool(false), eval("false && false"))
|
||||
assertEquals(ObjBool(false), eval("false && true"))
|
||||
assertEquals(ObjFalse, eval("true && false"))
|
||||
assertEquals(ObjFalse, eval("false && false"))
|
||||
assertEquals(ObjFalse, eval("false && true"))
|
||||
assertEquals(ObjBool(true), eval("true && true"))
|
||||
|
||||
assertEquals(ObjBool(true), eval("true || false"))
|
||||
assertEquals(ObjBool(false), eval("false || false"))
|
||||
assertEquals(ObjFalse, eval("false || false"))
|
||||
assertEquals(ObjBool(true), eval("false || true"))
|
||||
assertEquals(ObjBool(true), eval("true || true"))
|
||||
|
||||
assertEquals(ObjBool(false), eval("!true"))
|
||||
assertEquals(ObjFalse, eval("!true"))
|
||||
assertEquals(ObjBool(true), eval("!false"))
|
||||
}
|
||||
|
||||
@@ -432,6 +434,18 @@ 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
|
||||
fun whileTest() = runTest {
|
||||
assertEquals(
|
||||
@@ -490,37 +504,41 @@ class ScriptTest {
|
||||
fun testAssignArgumentsNoEllipsis() = runTest {
|
||||
// equal args, no ellipsis, no defaults, ok
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
var pa = ArgsDeclaration(listOf(
|
||||
var pa = ArgsDeclaration(
|
||||
listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
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)
|
||||
assertEquals( ObjInt(1), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(3), c["c"]?.value)
|
||||
assertEquals(ObjInt(1), c["a"]?.value)
|
||||
assertEquals(ObjInt(2), c["b"]?.value)
|
||||
assertEquals(ObjInt(3), c["c"]?.value)
|
||||
// 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> {
|
||||
pa.assignToContext(c)
|
||||
}
|
||||
// less args, no ellipsis, defaults, ok
|
||||
pa = ArgsDeclaration(listOf(
|
||||
pa = ArgsDeclaration(
|
||||
listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c", defaultValue = statement { ObjInt(100) }),
|
||||
), ttEnd)
|
||||
), ttEnd
|
||||
)
|
||||
pa.assignToContext(c)
|
||||
assertEquals( ObjInt(1), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(100), c["c"]?.value)
|
||||
assertEquals(ObjInt(1), c["a"]?.value)
|
||||
assertEquals(ObjInt(2), c["b"]?.value)
|
||||
assertEquals(ObjInt(100), c["c"]?.value)
|
||||
// enough args. default value is ignored:
|
||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
||||
pa.assignToContext(c)
|
||||
assertEquals( ObjInt(10), c["a"]?.value)
|
||||
assertEquals( ObjInt(2), c["b"]?.value)
|
||||
assertEquals( ObjInt(5), c["c"]?.value)
|
||||
assertEquals(ObjInt(10), c["a"]?.value)
|
||||
assertEquals(ObjInt(2), c["b"]?.value)
|
||||
assertEquals(ObjInt(5), c["c"]?.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -528,16 +546,18 @@ class ScriptTest {
|
||||
// equal args,
|
||||
// less args, no ellipsis, defaults, ok
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
val pa = ArgsDeclaration(listOf(
|
||||
val pa = ArgsDeclaration(
|
||||
listOf(
|
||||
ArgsDeclaration.Item("a"),
|
||||
ArgsDeclaration.Item("b", isEllipsis = true),
|
||||
), ttEnd)
|
||||
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)
|
||||
c.eval("assert( a == 1 ); println(b)")
|
||||
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)
|
||||
c.eval("assertEquals( a, 1 ); println(b)")
|
||||
c.eval("assertEquals( b, [2] )")
|
||||
@@ -551,24 +571,26 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testAssignArgumentsStartEllipsis() = runTest {
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
val pa = ArgsDeclaration(listOf(
|
||||
val pa = ArgsDeclaration(
|
||||
listOf(
|
||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
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)
|
||||
c.eval("assertEquals( a,[0,1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
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)
|
||||
c.eval("assertEquals( a,[1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
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)
|
||||
c.eval("assertEquals( a,[] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
@@ -583,34 +605,36 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testAssignArgumentsmiddleEllipsis() = runTest {
|
||||
val ttEnd = Token.Type.RBRACE
|
||||
val pa = ArgsDeclaration(listOf(
|
||||
val pa = ArgsDeclaration(
|
||||
listOf(
|
||||
ArgsDeclaration.Item("i"),
|
||||
ArgsDeclaration.Item("a", isEllipsis = true),
|
||||
ArgsDeclaration.Item("b"),
|
||||
ArgsDeclaration.Item("c"),
|
||||
), ttEnd)
|
||||
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)
|
||||
c.eval("assertEquals( i, -1 )")
|
||||
c.eval("assertEquals( a,[0,1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
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)
|
||||
c.eval("assertEquals( i, 0 )")
|
||||
c.eval("assertEquals( a,[1] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
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)
|
||||
c.eval("assertEquals( i, 1)")
|
||||
c.eval("assertEquals( a,[] )")
|
||||
c.eval("assertEquals( b, 2 )")
|
||||
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> {
|
||||
pa.assignToContext(c)
|
||||
}
|
||||
@@ -1385,8 +1409,9 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testSimpleStruct() = runTest {
|
||||
val c = Context()
|
||||
c.eval("""
|
||||
struct Point(x,y)
|
||||
c.eval(
|
||||
"""
|
||||
class Point(x,y)
|
||||
assert( Point::class is Class )
|
||||
val p = Point(2,3)
|
||||
assert(p is Point)
|
||||
@@ -1398,14 +1423,16 @@ class ScriptTest {
|
||||
val p2 = Point(p.x+1,p.y+1)
|
||||
p.x = 0
|
||||
assertEquals( 0, p.x )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNonAssignalbeFieldInStruct() = runTest {
|
||||
val c = Context()
|
||||
c.eval("""
|
||||
struct Point(x,y)
|
||||
c.eval(
|
||||
"""
|
||||
class Point(x,y)
|
||||
val p = Point("2",3)
|
||||
assert(p is Point)
|
||||
assert( p.x == "2" )
|
||||
@@ -1413,14 +1440,16 @@ class ScriptTest {
|
||||
|
||||
p.x = 0
|
||||
assertEquals( 0, p.x )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStructBodyVal() = runTest {
|
||||
val c = Context()
|
||||
c.eval("""
|
||||
struct Point(x,y) {
|
||||
c.eval(
|
||||
"""
|
||||
class Point(x,y) {
|
||||
val length = sqrt(x*x+y*y)
|
||||
var foo = "zero"
|
||||
}
|
||||
@@ -1433,14 +1462,16 @@ class ScriptTest {
|
||||
assert( p.foo == "bar")
|
||||
// length is a val, is shoud not change
|
||||
assert( p.length == 5 )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStructBodyFun() = runTest {
|
||||
val c = Context()
|
||||
c.eval("""
|
||||
struct Point(x,y) {
|
||||
c.eval(
|
||||
"""
|
||||
class Point(x,y) {
|
||||
fun length() {
|
||||
sqrt(x*x+y*y)
|
||||
}
|
||||
@@ -1451,6 +1482,635 @@ class ScriptTest {
|
||||
p.y = 10
|
||||
println(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()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForContinue() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var x = 0
|
||||
for( i in 1..10 ) {
|
||||
if( i % 2 == 0 ) continue
|
||||
println(i)
|
||||
x++
|
||||
}
|
||||
assertEquals(5, x)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLabelNreakTest() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var x = 0
|
||||
var y = 0
|
||||
FOR0@ for( i in 1..10 ) {
|
||||
x = i
|
||||
for( j in 1..20 ) {
|
||||
y = j
|
||||
if( i == 3 && j == 5 ) {
|
||||
println("bb")
|
||||
break@FOR0
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals( 5, y )
|
||||
assertEquals( 3, x )
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThrowExisting() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val x = IllegalArgumentException("test")
|
||||
assert( x is Exception )
|
||||
|
||||
var t = 0
|
||||
var finallyCaught = false
|
||||
try {
|
||||
t = 1
|
||||
throw x
|
||||
t = 2
|
||||
}
|
||||
catch( e: SymbolNotDefinedException ) {
|
||||
t = 101
|
||||
}
|
||||
catch( e: IllegalArgumentException ) {
|
||||
t = 3
|
||||
}
|
||||
finally {
|
||||
finallyCaught = true
|
||||
}
|
||||
assertEquals(3, t)
|
||||
assert(finallyCaught)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCatchShort1() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val x = IllegalArgumentException("test")
|
||||
var t = 0
|
||||
var finallyCaught = false
|
||||
try {
|
||||
t = 1
|
||||
throw x
|
||||
t = 2
|
||||
}
|
||||
catch(e) {
|
||||
t = 31
|
||||
}
|
||||
finally {
|
||||
finallyCaught = true
|
||||
}
|
||||
assertEquals(31, t)
|
||||
assert(finallyCaught)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCatchShort2() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val x = IllegalArgumentException("test")
|
||||
var caught = null
|
||||
try {
|
||||
throw x
|
||||
}
|
||||
catch {
|
||||
caught = it
|
||||
}
|
||||
assert( caught is IllegalArgumentException )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAccessEHData() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val x = IllegalArgumentException("test")
|
||||
val m = try {
|
||||
throw x
|
||||
null
|
||||
}
|
||||
catch(e) {
|
||||
println(e)
|
||||
println(e::class)
|
||||
println(e.message)
|
||||
println("--------------")
|
||||
e.message
|
||||
}
|
||||
println(m)
|
||||
assert( m == "test" )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTryFinally() = runTest {
|
||||
val c = Context()
|
||||
assertFails {
|
||||
c.eval(
|
||||
"""
|
||||
var resource = "used"
|
||||
try {
|
||||
throw "test"
|
||||
}
|
||||
finally {
|
||||
resource = "freed"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
c.eval(
|
||||
"""
|
||||
assertEquals("freed", resource)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThrowFromKotlin() = runTest {
|
||||
val c = Context()
|
||||
c.addFn("callThrow") {
|
||||
raiseArgumentError("fromKotlin")
|
||||
}
|
||||
c.eval(
|
||||
"""
|
||||
val result = try {
|
||||
callThrow()
|
||||
"fail"
|
||||
}
|
||||
catch(e) {
|
||||
println("caught:"+e)
|
||||
println(e.message)
|
||||
assert( e is IllegalArgumentException )
|
||||
assertEquals("fromKotlin", e.message)
|
||||
"ok"
|
||||
}
|
||||
assertEquals(result, "ok")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReturnValue1() = runTest {
|
||||
val r = eval(
|
||||
"""
|
||||
class Point(x,y) {
|
||||
println("1")
|
||||
fun length() { sqrt(d2()) }
|
||||
println("2")
|
||||
private fun d2() {x*x + y*y}
|
||||
println("3")
|
||||
}
|
||||
println("Helluva")
|
||||
val p = Point(3,4)
|
||||
// assertEquals( 5, p.length() )
|
||||
// assertThrows { p.d2() }
|
||||
"111"
|
||||
""".trimIndent()
|
||||
)
|
||||
assertEquals("111", r.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doWhileValuesTest() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
val result = do {
|
||||
count++
|
||||
if( count < 10 )
|
||||
continue
|
||||
if( count % 2 == 1 )
|
||||
break "found "+count
|
||||
} while(count < 100)
|
||||
else "no"
|
||||
assertEquals("found 11", result)
|
||||
""".trimIndent()
|
||||
)
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
val result = do {
|
||||
count++
|
||||
if( count < 10 )
|
||||
continue
|
||||
if( count % 2 == 1 )
|
||||
break "found "+count
|
||||
} while(count < 5)
|
||||
else "no"
|
||||
assertEquals("no", result)
|
||||
""".trimIndent()
|
||||
)
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
val result = do {
|
||||
count++
|
||||
if( count % 2 == 3 )
|
||||
break "found "+count
|
||||
"proc "+count
|
||||
} while(count < 5)
|
||||
assertEquals("proc 5", result)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doWhileValuesLabelTest() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
var count2 = 0
|
||||
var count3 = 0
|
||||
val result = outer@ do {
|
||||
count2++
|
||||
count = 0
|
||||
do {
|
||||
count++
|
||||
if( count < 10 || count2 < 5 ) {
|
||||
continue
|
||||
}
|
||||
if( count % 2 == 1 )
|
||||
break@outer "found "+count + "/" + count2
|
||||
} while(count < 14)
|
||||
count3++
|
||||
} while( count2 < 100 )
|
||||
else "no"
|
||||
assertEquals("found 11/5", result)
|
||||
assertEquals( 4, count3)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleWhen() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var result = when("a") {
|
||||
"a" -> "ok"
|
||||
else -> "fail"
|
||||
}
|
||||
assertEquals(result, "ok")
|
||||
result = when(5) {
|
||||
3 -> "fail1"
|
||||
4 -> "fail2"
|
||||
else -> "ok2"
|
||||
}
|
||||
assert(result == "ok2")
|
||||
result = when(5) {
|
||||
3 -> "fail"
|
||||
4 -> "fail2"
|
||||
}
|
||||
assert(result == void)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWhenIs() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var result = when("a") {
|
||||
is Int -> "fail2"
|
||||
is String -> "ok"
|
||||
else -> "fail"
|
||||
}
|
||||
assertEquals(result, "ok")
|
||||
result = when(5) {
|
||||
3 -> "fail1"
|
||||
4 -> "fail2"
|
||||
else -> "ok2"
|
||||
}
|
||||
assert(result == "ok2")
|
||||
result = when(5) {
|
||||
3 -> "fail"
|
||||
4 -> "fail2"
|
||||
}
|
||||
assert(result == void)
|
||||
result = when(5) {
|
||||
!is String -> "ok"
|
||||
4 -> "fail2"
|
||||
}
|
||||
assert(result == "ok")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWhenIn() = runTest {
|
||||
eval(
|
||||
"""
|
||||
var result = when('e') {
|
||||
in 'a'..'c' -> "fail2"
|
||||
in 'a'..'z' -> "ok"
|
||||
else -> "fail"
|
||||
}
|
||||
// assertEquals(result, "ok")
|
||||
result = when(5) {
|
||||
in [1,2,3,4,6] -> "fail1"
|
||||
in [7, 0, 9] -> "fail2"
|
||||
else -> "ok2"
|
||||
}
|
||||
assert(result == "ok2")
|
||||
result = when(5) {
|
||||
in [1,2,3,4,6] -> "fail1"
|
||||
in [7, 0, 9] -> "fail2"
|
||||
in [-1, 5, 11] -> "ok3"
|
||||
else -> "fail3"
|
||||
}
|
||||
assert(result == "ok3")
|
||||
result = when(5) {
|
||||
!in [1,2,3,4,6, 5] -> "fail1"
|
||||
!in [7, 0, 9, 5] -> "fail2"
|
||||
!in [-1, 15, 11] -> "ok4"
|
||||
else -> "fail3"
|
||||
}
|
||||
assert(result == "ok4")
|
||||
result = when(5) {
|
||||
in [1,3] -> "fail"
|
||||
in 2..4 -> "fail2"
|
||||
}
|
||||
assert(result == void)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWhenSample1() = runTest {
|
||||
eval(
|
||||
"""
|
||||
fun type(x) {
|
||||
when(x) {
|
||||
in 'a'..'z', in 'A'..'Z' -> "letter"
|
||||
in '0'..'9' -> "digit"
|
||||
in "$%&" -> "hate char"
|
||||
else -> "unknown"
|
||||
}
|
||||
}
|
||||
assertEquals("digit", type('3'))
|
||||
assertEquals("letter", type('E'))
|
||||
assertEquals("hate char", type('%'))
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWhenSample2() = runTest {
|
||||
eval(
|
||||
"""
|
||||
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))
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -268,4 +268,9 @@ class BookTest {
|
||||
fun testArgumentBooks() = runTest {
|
||||
runDocTests("../docs/declaring_arguments.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionsBooks() = runTest {
|
||||
runDocTests("../docs/exceptions_handling.md")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user