Compare commits

..

No commits in common. "eca746b189e2f11348894315e55afc11ee17eddb" and "202e70a99ab6e134d6600f89d9f5d96d7be3f5e5" have entirely different histories.

12 changed files with 64 additions and 228 deletions

View File

@ -26,58 +26,36 @@ Just remember at this stage typed declarations are not yet supported.
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available, for example
val r = 1..10 // Range is Iterable!
assertEquals( [9,10], r.takeLast(2).toList() )
assertEquals( [1,2,3], r.take(3).toList() )
assertEquals( [9,10], r.drop(8).toList() )
assertEquals( [1,2], r.dropLast(8).toList() )
>>> void
## joinToString
This methods convert any iterable to a string joining string representation of each element, optionally transforming it and joining using specified suffix.
Iterable.joinToString(suffux=' ', transform=null)
- if `Iterable` `isEmpty`, the empty string `""` is returned.
- `suffix` is inserted between items when there are more than one.
- `transform` of specified is applied to each element, otherwise its `toString()` method is used.
Here is the sample:
assertEquals( (1..3).joinToString(), "1 2 3")
assertEquals( (1..3).joinToString(":"), "1:2:3")
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
assertEquals( [9,10] r.takeLast(2) )
assertEquals( [1,2,3] r.take(3) )
assertEquals( [9,10] r.drop(8) )
assertEquals( [1,2] r.dropLast(8) )
>>> void
## Instance methods:
| fun/method | description |
|-------------------|---------------------------------------------------------------------------|
| toList() | create a list from iterable |
| toSet() | create a set from iterable |
| contains(i) | check that iterable contains `i` |
| `i in iterator` | same as `contains(i)` |
| isEmpty() | check iterable is empty |
| forEach(f) | call f for each element |
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
| map(f) | create a list of values returned by `f` called for each element of the iterable |
| indexOf(i) | return index if the first encounter of i or a negative value if not found |
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
| first | first element (1) |
| last | last element (1) |
| take(n) | return [Iterable] of up to n first elements |
| taleLast(n) | return [Iterable] of up to n last elements |
| drop(n) | return new [Iterable] without first n elements |
| dropLast(n) | return new [Iterable] without last n elements |
| joinToString(s,t) | convert iterable to string, see (2) |
| fun/method | description |
|-----------------|---------------------------------------------------------------------------------|
| toList() | create a list from iterable |
| toSet() | create a set from iterable |
| contains(i) | check that iterable contains `i` |
| `i in iterator` | same as `contains(i)` |
| isEmpty() | check iterable is empty |
| forEach(f) | call f for each element |
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
| map(f) | create a list of values returned by `f` called for each element of the iterable |
| indexOf(i) | return index if the first encounter of i or a negative value if not found |
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
| first | first element (1) |
| last | last element (1) |
| take(n) | return [Iterable] of up to n first elements |
| taleLast(n) | return [Iterable] of up to n last elements |
| drop(n) | return new [Iterable] without first n elements |
| dropLast(n) | return new [Iterable] without last n elements |
(1)
: throws `NoSuchElementException` if there is no such element
(2)
: `joinToString(suffix=" ",transform=null)`: suffix is inserted between items if there are more than one, trasnfom is optional function applied to each item that must return result string for an item, otherwise `item.toString()` is used.
fun Iterable.toList(): List
fun Iterable.toSet(): Set
fun Iterable.indexOf(element): Int

View File

@ -747,7 +747,7 @@ You can thest that _when expression_ is _contained_, or not contained, in some o
`!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 `contains`):
Typical builtin types that are containers (e.g. support `conain`):
| class | notes |
|------------|------------------------------------------------|
@ -756,8 +756,6 @@ Typical builtin types that are containers (e.g. support `contains`):
| List | faster than Array's |
| String | character in string or substring in string (3) |
| Range | object is included in the range (2) |
| Buffer | byte is in buffer |
| RingBuffer | object is in buffer |
(1)
: Iterable is not the container as it can be infinite
@ -1298,20 +1296,18 @@ if blank, will be removed too, for example:
See [math functions](math.md). Other general purpose functions are:
| name | description |
|----------------------------------------------|------------------------------------------------------------|
| assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them |
| assertEquals(a,b) | |
| assertNotEquals(a,b) | |
| assertTrows { /* block */ } | |
| check(condition, message=<default>) | throws IllegalStateException" of condition isn't met |
| require(condition, message=<default>) | throws IllegalArgumentException" of condition isn't met |
| println(args...) | Open for overriding, it prints to stdout with newline. |
| print(args...) | Open for overriding, it prints to stdout without newline. |
| flow {} | create flow sequence, see [parallelism] |
| delay, launch, yield | see [parallelism] |
| cached(builder) | remembers builder() on first invocation and return it then |
| name | description |
|--------------------------------------------|-----------------------------------------------------------|
| assert(condition,message="assertion failed") | runtime code check. There will be an option to skip them |
| assertEquals(a,b) | |
| assertNotEquals(a,b) | |
| assertTrows { /* block */ } | |
| check(condition, message=<default>) | throws IllegalStateException" of condition isn't met |
| require(condition, message=<default>) | throws IllegalArgumentException" of condition isn't met |
| println(args...) | Open for overriding, it prints to stdout with newline. |
| print(args...) | Open for overriding, it prints to stdout without newline. |
| flow {} | create flow sequence, see [parallelism] |
| delay, launch, yield | see [parallelism] |
# Built-in constants

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych"
version = "0.8.10-SNAPSHOT"
version = "0.8.9-SNAPSHOT"
buildscript {
repositories {

View File

@ -87,7 +87,7 @@ class Compiler(
statements += it
}
if (s == null) {
when (t.type) {
when( t.type ) {
Token.Type.RBRACE, Token.Type.EOF, Token.Type.SEMICOLON -> {}
else ->
throw ScriptError(t.pos, "unexpeced `${t.value}` here")
@ -114,7 +114,7 @@ class Compiler(
return result.toString()
}
private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null
private var lastAnnotation: (suspend (Scope, ObjString,Statement) -> Statement)? = null
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
lastAnnotation = null
@ -138,7 +138,6 @@ class Compiler(
lastAnnotation = parseAnnotation(t)
continue
}
Token.Type.LABEL -> continue
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
@ -844,7 +843,7 @@ class Compiler(
return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number")
}
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) {
suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString,Statement)->Statement) {
val extraArgs = parseArgsOrNull()
println("annotation ${t.value}: args: $extraArgs")
return { scope, name, body ->
@ -852,14 +851,14 @@ class Compiler(
val required = listOf(name, body)
val args = extras?.let { required + it } ?: required
val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}")
if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
if( fn !is Statement ) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}")
(fn.execute(scope.copy(Arguments(args))) as? Statement)
?: scope.raiseClassCastError("function annotation must return callable")
}
}
suspend fun parseArgsOrNull(): Pair<List<ParsedArgument>, Boolean>? =
if (cc.skipNextIf(Token.Type.LPAREN))
if( cc.skipNextIf(Token.Type.LPAREN))
parseArgs()
else
null
@ -1174,24 +1173,22 @@ class Compiler(
do {
val t = cc.skipWsTokens()
when (t.type) {
when(t.type) {
Token.Type.ID -> {
names += t.value
val t1 = cc.skipWsTokens()
when (t1.type) {
when(t1.type) {
Token.Type.COMMA ->
continue
Token.Type.RBRACE -> break
else -> {
t1.raiseSyntax("unexpected token")
}
}
}
else -> t.raiseSyntax("expected enum entry name")
}
} while (true)
} while(true)
return statement {
ObjEnumClass.createSimpleEnum(nameToken.value, names).also {
@ -1266,6 +1263,7 @@ class Compiler(
newClass.classScope = classScope
for (s in initScope)
s.execute(classScope)
.also { println("executed, ${classScope.objects}") }
}
newClass
}
@ -1768,31 +1766,24 @@ class Compiler(
val eqToken = cc.next()
var setNull = false
val isDelegate = if (eqToken.isId("by")) {
true
} else {
if (eqToken.type != Token.Type.ASSIGN) {
if (!isMutable)
throw ScriptError(start, "val must be initialized")
else {
cc.previous()
setNull = true
}
if (eqToken.type != Token.Type.ASSIGN) {
if (!isMutable)
throw ScriptError(start, "val must be initialized")
else {
cc.previous()
setNull = true
}
false
}
val initialExpression = if (setNull) null
else parseStatement(true)
val initialExpression = if (setNull) null else parseStatement(true)
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
if (isStatic) {
// find objclass instance: this is tricky: this code executes in object initializer,
// when creating instance, but we need to execute it in the class initializer which
// is missing as for now. Add it to the compiler context?
// if (isDelegate) throw ScriptError(start, "static delegates are not yet implemented")
// add there
// return
currentInitScope += statement {
val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull
(thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, pos)
@ -1806,32 +1797,12 @@ class Compiler(
if (context.containsLocal(name))
throw ScriptError(nameToken.pos, "Variable $name is already defined")
if (isDelegate) {
TODO()
// println("initial expr = $initialExpression")
// val initValue =
// (initialExpression?.execute(context.copy(Arguments(ObjString(name)))) as? Statement)
// ?.execute(context.copy(Arguments(ObjString(name))))
// ?: context.raiseError("delegate initialization required")
// println("delegate init: $initValue")
// if (!initValue.isInstanceOf(ObjArray))
// context.raiseIllegalArgument("delegate initialized must be an array")
// val s = initValue.getAt(context, 1)
// val setter = if (s == ObjNull) statement { raiseNotImplemented("setter is not provided") }
// else (s as? Statement) ?: context.raiseClassCastError("setter must be a callable")
// ObjDelegate(
// (initValue.getAt(context, 0) as? Statement)
// ?: context.raiseClassCastError("getter must be a callable"), setter
// ).also {
// context.addItem(name, isMutable, it, visibility, recordType = ObjRecord.Type.Field)
// }
} else {
// init value could be a val; when we initialize by-value type var with it, we need to
// create a separate copy:
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
initValue
}
// init value could be a val; when we initialize by-value type var with it, we need to
// create a separate copy:
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
initValue
}
}

View File

@ -24,10 +24,6 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
fun isId(text: String) =
type == Type.ID && value == text
@Suppress("unused")
enum class Type {
ID, INT, REAL, HEX, STRING, CHAR,

View File

@ -161,8 +161,6 @@ open class Obj {
open suspend fun assign(scope: Scope, other: Obj): Obj? = null
open fun getValue(scope: Scope) = this
/**
* a += b
* if( the operation is not defined, it returns null and the compiler would try

View File

@ -1,47 +0,0 @@
/*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.statement
class ObjDelegateContext()
class ObjDelegate(
val getter: Statement,
val setter: Statement = statement { raiseNotImplemented("setter is not implemented") }
): Obj() {
override suspend fun assign(scope: Scope, other: Obj): Obj? {
setter.execute(scope.copy(Arguments(other)))
return other
}
companion object {
val type = object: ObjClass("Delegate") {
override suspend fun callOn(scope: Scope): Obj {
scope.raiseError("Delegate should not be constructed directly")
}
}.apply {
}
}
}

View File

@ -56,7 +56,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
}
companion object {
val type = ObjClass("RangeIterator", ObjIterator).apply {
val type = ObjClass("RangeIterator", ObjIterable).apply {
addFn("hasNext") {
thisAs<ObjRangeIterator>().hasNext().toObj()
}

View File

@ -19,18 +19,6 @@ package net.sergeych.lyng.stdlib_included
internal val rootLyng = """
package lyng.stdlib
fun cached(builder) {
var calculated = false
var value = null
{
if( !calculated ) {
value = builder()
calculated = true
}
value
}
}
fun Iterable.filter(predicate) {
val list = this
flow {
@ -50,7 +38,7 @@ fun Iterable.drop(n) {
fun Iterable.first() {
val i = iterator()
if( !i.hasNext() ) throw NoSuchElementException()
i.next().also { i.cancelIteration() }
i.next()
}
fun Iterable.last() {
@ -83,15 +71,5 @@ fun Iterable.takeLast(n) {
buffer
}
fun Iterable.joinToString(prefix=" ", transformer=null) {
var result = null
for( part in this ) {
val transformed = transformer?(part)?.toString() ?: part.toString()
if( result == null ) result = transformed
else result += prefix + transformed
}
result ?: ""
}
""".trimIndent()

View File

@ -2913,33 +2913,5 @@ class ScriptTest {
""".trimIndent())
}
@Test
fun cachedTest() = runTest {
eval( """
var counter = 0
var value = cached {
counter++
"ok"
}
assertEquals(0, counter)
assertEquals("ok", value())
assertEquals(1, counter)
assertEquals("ok", value())
assertEquals(1, counter)
""".trimIndent())
}
@Test
fun testJoinToString() = runTest {
eval("""
assertEquals( (1..3).joinToString(), "1 2 3")
assertEquals( (1..3).joinToString(":"), "1:2:3")
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
""".trimIndent())
}
}

View File

@ -40,9 +40,8 @@ class StdlibTest {
@Test
fun testTake() = runTest {
eval("""
val r = 1..8
assertEquals([1,2,3], r.take(3).toList() )
assertEquals([7,8], r.takeLast(2).toList() )
assertEquals([1,2,3], (1..8).take(3).toList() )
assertEquals([7,8], (1..8).takeLast(2).toList() )
""".trimIndent())
}

View File

@ -317,9 +317,4 @@ class BookTest {
runDocTests("../docs/RingBuffer.md")
}
@Test
fun testIterable() = runBlocking {
runDocTests("../docs/Iterable.md")
}
}