fix #49 regular expressions, operator match and docs
This commit is contained in:
		
							parent
							
								
									ead2f7168e
								
							
						
					
					
						commit
						f45fa7f7a0
					
				
							
								
								
									
										91
									
								
								docs/Regex.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								docs/Regex.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					# Regular expressions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In lyng, you create regular expressions using class `Regex` or `String.re` methods:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "\d*".re is Regex )
 | 
				
			||||||
 | 
					    assert( Regex("\d*") is Regex )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We plan to add slash syntax at some point.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To check that some string matches as whole to some regex:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "123".matches("\d{3}".re) )
 | 
				
			||||||
 | 
					    assert( !"123".matches("\d{4}".re) )
 | 
				
			||||||
 | 
					    assert( !"1234".matches("\d".re) )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To check that _part of the string_ matches some regular expession, use _match operator_ `=~` just like in Ruby, and its
 | 
				
			||||||
 | 
					counterpart, _not match_ operator `!~`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "abc123def" =~ "\d\d\d".re )
 | 
				
			||||||
 | 
					    assert( "abc" !~ "\d\d\d".re )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When you need to find groups, and more detailed match information, use `Regex.find`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val result = Regex("abc(\d)(\d)(\d)").find( "bad456 good abc123")
 | 
				
			||||||
 | 
					    assert( result  != null )
 | 
				
			||||||
 | 
					    assertEquals( 12 .. 17, result.range )
 | 
				
			||||||
 | 
					    assertEquals( "abc123", result[0] )
 | 
				
			||||||
 | 
					    assertEquals( "1", result[1] )
 | 
				
			||||||
 | 
					    assertEquals( "2", result[2] )
 | 
				
			||||||
 | 
					    assertEquals( "3", result[3] )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that the object `RegexMatch`, returned by [Regex.find], behaves much like in many other languages: it provides the
 | 
				
			||||||
 | 
					index range and groups matches as indexes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Match operator actually also provides `RegexMatch` in `$~` reserved variable (borrowed from Ruby too):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "bad456 good abc123" =~ "abc(\d)(\d)(\d)".re )
 | 
				
			||||||
 | 
					    assertEquals( 12 .. 17, $~.range )
 | 
				
			||||||
 | 
					    assertEquals( "abc123", $~[0] )
 | 
				
			||||||
 | 
					    assertEquals( "1", $~[1] )
 | 
				
			||||||
 | 
					    assertEquals( "2", $~[2] )
 | 
				
			||||||
 | 
					    assertEquals( "3", $~[3] )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is often more readable than calling `find`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that `=~` and `!~` operators against strings and regular expressions are commutative, e.g. regular expression and a
 | 
				
			||||||
 | 
					string can be either left or right operator, but not both:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "abc" =~ "\wc".re )
 | 
				
			||||||
 | 
					    assert( "abc" !~ "\w1c".re )
 | 
				
			||||||
 | 
					    assert( "a\wc".re =~ "abcd" )
 | 
				
			||||||
 | 
					    assert( "a[a-z]c".re !~ "a2cd" )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Also, string indexing is Regex-aware, and works like `Regex.find` (_not findall!_):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "cd" == "abcdef"[ "c.".re ].value )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Regex class reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| name         | description                         | notes |
 | 
				
			||||||
 | 
					|--------------|-------------------------------------|-------|
 | 
				
			||||||
 | 
					| matches(str) | true if the whole `str` matches     |       |
 | 
				
			||||||
 | 
					| find(str)    | find first match in `str` or null   | (1)   |
 | 
				
			||||||
 | 
					| findAll(str) | find all matches in `str` as [List] | (1)   |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(1)
 | 
				
			||||||
 | 
					:: See `RegexMatch` class description below
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# RegexMatch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| name  | description                               | notes |
 | 
				
			||||||
 | 
					|-------|-------------------------------------------|-------|
 | 
				
			||||||
 | 
					| range | the [Range] of the match in source string |       |
 | 
				
			||||||
 | 
					| value | the value that matches                    |       |
 | 
				
			||||||
 | 
					| [n]   | [List] of group matches                   | (1)   |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(1)
 | 
				
			||||||
 | 
					:: the [0] element is always value, [1] is group 1 match of any, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[List]: List.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Range]: Range.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -275,6 +275,8 @@ Logical operation could be used the same
 | 
				
			|||||||
|   ===    |     | Any               | (2)      |
 | 
					|   ===    |     | Any               | (2)      |
 | 
				
			||||||
|   !==    |     | Any               | (2)      |
 | 
					|   !==    |     | Any               | (2)      |
 | 
				
			||||||
|    !=    |     | Any               | (1)      |
 | 
					|    !=    |     | Any               | (1)      |
 | 
				
			||||||
 | 
					|    =~    |     |                   | (3)      |
 | 
				
			||||||
 | 
					|    !~    |     |                   | (3)      |
 | 
				
			||||||
| ++a, a++ |     | Int               |          |
 | 
					| ++a, a++ |     | Int               |          |
 | 
				
			||||||
| --a, a-- |     | Int               |          |
 | 
					| --a, a-- |     | Int               |          |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -286,6 +288,9 @@ Logical operation could be used the same
 | 
				
			|||||||
singleton object, like `null`, are referentially equal too, while string different literals even being equal are most
 | 
					singleton object, like `null`, are referentially equal too, while string different literals even being equal are most
 | 
				
			||||||
likely referentially not equal
 | 
					likely referentially not equal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(3)
 | 
				
			||||||
 | 
					: Implemented now in String and Regex as regular expression match and not match, see [Regex].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Reference quality and object equality example:
 | 
					Reference quality and object equality example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert( null == null)  // singletons
 | 
					    assert( null == null)  // singletons
 | 
				
			||||||
@ -1285,9 +1290,26 @@ Open-ended ranges could be used to get start and end too:
 | 
				
			|||||||
    assertEquals( "pult", "catapult"[ 4.. ])
 | 
					    assertEquals( "pult", "catapult"[ 4.. ])
 | 
				
			||||||
    >>> void
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### String operations
 | 
					### String operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
 | 
					Concatenation is a `+`: `"hello " + name` works as expected. No confusion. There is also
 | 
				
			||||||
 | 
					[Regex] support for strings, see the link, for example, whole string match:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( !"123".matches( "\d\d".re ) )
 | 
				
			||||||
 | 
					    assert( "123".matches( "\d\d\d".re ) )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Extraction:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "abcd42def"[ "\d+".re ].value
 | 
				
			||||||
 | 
					    >>> "42"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Part match:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert( "abc foo def" =~ "f[oO]+".re )
 | 
				
			||||||
 | 
					    assert( "foo" == $~.value )
 | 
				
			||||||
 | 
					    >>> void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Typical set of String functions includes:
 | 
					Typical set of String functions includes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1305,17 +1327,24 @@ Typical set of String functions includes:
 | 
				
			|||||||
| size               | size in characters like `length` because String is [Array] |
 | 
					| size               | size in characters like `length` because String is [Array] |
 | 
				
			||||||
| (args...)          | sprintf-like formatting, see [string formatting]           |
 | 
					| (args...)          | sprintf-like formatting, see [string formatting]           |
 | 
				
			||||||
| [index]            | character at index                                         | 
 | 
					| [index]            | character at index                                         | 
 | 
				
			||||||
| [Range]            | substring at range                                         |
 | 
					| [Range]            | substring at range (2)                                     |
 | 
				
			||||||
 | 
					| [Regex]            | find first match of regex, like [Regex.find] (2)           |
 | 
				
			||||||
| s1 + s2            | concatenation                                              |
 | 
					| s1 + s2            | concatenation                                              |
 | 
				
			||||||
| s1 += s2           | self-modifying concatenation                               |
 | 
					| s1 += s2           | self-modifying concatenation                               |
 | 
				
			||||||
| toReal()           | attempts to parse string as a Real value                   |
 | 
					| toReal()           | attempts to parse string as a Real value                   |
 | 
				
			||||||
| toInt()            | parse string to Int value                                  |
 | 
					| toInt()            | parse string to Int value                                  |
 | 
				
			||||||
| characters()       | create [List] of characters (1)                            |
 | 
					| characters()       | create [List] of characters (1)                            |
 | 
				
			||||||
| encodeUtf8()       | returns [Buffer] with characters encoded to utf8           |
 | 
					| encodeUtf8()       | returns [Buffer] with characters encoded to utf8           |
 | 
				
			||||||
 | 
					| matches(re)        | matches the regular expression (2)                         |
 | 
				
			||||||
 | 
					|                    |                                                            |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(1)
 | 
					(1)
 | 
				
			||||||
: List is mutable therefore a new copy is created on each call.
 | 
					: List is mutable therefore a new copy is created on each call.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(2)
 | 
				
			||||||
 | 
					: See [Regex]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Literals
 | 
					### Literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String literal could be multiline:
 | 
					String literal could be multiline:
 | 
				
			||||||
@ -1390,4 +1419,6 @@ See [math functions](math.md). Other general purpose functions are:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[Collection]: Collection.md
 | 
					[Collection]: Collection.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Array]: Array.md
 | 
					[Array]: Array.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Regex]: Regex.md
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
 | 
				
			|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
					import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group = "net.sergeych"
 | 
					group = "net.sergeych"
 | 
				
			||||||
version = "0.8.15-SNAPSHOT"
 | 
					version = "0.9.0-SNAPSHOT"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
buildscript {
 | 
					buildscript {
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
 | 
				
			|||||||
@ -1000,6 +1000,7 @@ class Compiler(
 | 
				
			|||||||
            // condition could be a value, in and is clauses:
 | 
					            // condition could be a value, in and is clauses:
 | 
				
			||||||
            // parse several conditions for one then clause
 | 
					            // parse several conditions for one then clause
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // loop cases
 | 
					            // loop cases
 | 
				
			||||||
            outer@ while (true) {
 | 
					            outer@ while (true) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1466,7 +1467,7 @@ class Compiler(
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            for (i in start ..< end) {
 | 
					            for (i in start..<end) {
 | 
				
			||||||
                iVar.value = i
 | 
					                iVar.value = i
 | 
				
			||||||
                result = body.execute(forScope)
 | 
					                result = body.execute(forScope)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -2007,6 +2008,8 @@ class Compiler(
 | 
				
			|||||||
            Operator.simple(Token.Type.NEQ, lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) },
 | 
					            Operator.simple(Token.Type.NEQ, lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) },
 | 
				
			||||||
            Operator.simple(Token.Type.REF_EQ, lastPriority) { _, a, b -> ObjBool(a === b) },
 | 
					            Operator.simple(Token.Type.REF_EQ, lastPriority) { _, a, b -> ObjBool(a === b) },
 | 
				
			||||||
            Operator.simple(Token.Type.REF_NEQ, lastPriority) { _, a, b -> ObjBool(a !== b) },
 | 
					            Operator.simple(Token.Type.REF_NEQ, lastPriority) { _, a, b -> ObjBool(a !== b) },
 | 
				
			||||||
 | 
					            Operator.simple(Token.Type.MATCH, lastPriority) { s, a, b -> a.operatorMatch(s,b) },
 | 
				
			||||||
 | 
					            Operator.simple(Token.Type.NOTMATCH, lastPriority) { s, a, b -> a.operatorNotMatch(s,b) },
 | 
				
			||||||
            // relational <=,... 5
 | 
					            // relational <=,... 5
 | 
				
			||||||
            Operator.simple(Token.Type.LTE, ++lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) <= 0) },
 | 
					            Operator.simple(Token.Type.LTE, ++lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) <= 0) },
 | 
				
			||||||
            Operator.simple(Token.Type.LT, lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
 | 
					            Operator.simple(Token.Type.LT, lastPriority) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
 | 
				
			||||||
 | 
				
			|||||||
@ -20,10 +20,10 @@ package net.sergeych.lyng
 | 
				
			|||||||
val digitsSet = ('0'..'9').toSet()
 | 
					val digitsSet = ('0'..'9').toSet()
 | 
				
			||||||
val digits = { d: Char -> d in digitsSet }
 | 
					val digits = { d: Char -> d in digitsSet }
 | 
				
			||||||
val hexDigits = digitsSet + ('a'..'f') + ('A'..'F')
 | 
					val hexDigits = digitsSet + ('a'..'f') + ('A'..'F')
 | 
				
			||||||
val idNextChars = { d: Char -> d.isLetter() || d == '_' || d.isDigit() }
 | 
					val idNextChars = { d: Char -> d.isLetter() || d == '_' || d.isDigit() || d == '$' || d == '~' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Suppress("unused")
 | 
					@Suppress("unused")
 | 
				
			||||||
val idFirstChars = { d: Char -> d.isLetter() || d == '_' }
 | 
					val idFirstChars = { d: Char -> d.isLetter() || d == '_' || d == '$' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun parseLyng(source: Source): List<Token> {
 | 
					fun parseLyng(source: Source): List<Token> {
 | 
				
			||||||
    val p = Parser(fromPos = source.startPos)
 | 
					    val p = Parser(fromPos = source.startPos)
 | 
				
			||||||
@ -67,13 +67,16 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
                            pos.advance()
 | 
					                            pos.advance()
 | 
				
			||||||
                            Token("===", from, Token.Type.REF_EQ)
 | 
					                            Token("===", from, Token.Type.REF_EQ)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        else -> Token("==", from, Token.Type.EQ)
 | 
					                        else -> Token("==", from, Token.Type.EQ)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else if( currentChar == '>' ) {
 | 
					                } else if (currentChar == '>') {
 | 
				
			||||||
                    pos.advance()
 | 
					                    pos.advance()
 | 
				
			||||||
                    Token("=>", from, Token.Type.EQARROW)
 | 
					                    Token("=>", from, Token.Type.EQARROW)
 | 
				
			||||||
                }
 | 
					                } else if (currentChar == '~') {
 | 
				
			||||||
                else
 | 
					                    pos.advance()
 | 
				
			||||||
 | 
					                    Token("=~", from, Token.Type.MATCH)
 | 
				
			||||||
 | 
					                } else
 | 
				
			||||||
                    Token("=", from, Token.Type.ASSIGN)
 | 
					                    Token("=", from, Token.Type.ASSIGN)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -227,6 +230,9 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
                            Token("!==", from, Token.Type.REF_NEQ)
 | 
					                            Token("!==", from, Token.Type.REF_NEQ)
 | 
				
			||||||
                        } else
 | 
					                        } else
 | 
				
			||||||
                            Token("!=", from, Token.Type.NEQ)
 | 
					                            Token("!=", from, Token.Type.NEQ)
 | 
				
			||||||
 | 
					                    } else if (currentChar == '~') {
 | 
				
			||||||
 | 
					                        pos.advance()
 | 
				
			||||||
 | 
					                        Token("!~", from, Token.Type.NOTMATCH)
 | 
				
			||||||
                    } else
 | 
					                    } else
 | 
				
			||||||
                        Token("!", from, Token.Type.NOT)
 | 
					                        Token("!", from, Token.Type.NOT)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -267,7 +273,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            in digitsSet -> {
 | 
					            in digitsSet -> {
 | 
				
			||||||
                pos.back()
 | 
					                pos.back()
 | 
				
			||||||
                decodeNumber(loadChars { it in digitsSet || it == '_'}, from)
 | 
					                decodeNumber(loadChars { it in digitsSet || it == '_' }, from)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            '\'' -> {
 | 
					            '\'' -> {
 | 
				
			||||||
@ -291,7 +297,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            '?' -> {
 | 
					            '?' -> {
 | 
				
			||||||
                when(currentChar.also { pos.advance() }) {
 | 
					                when (currentChar.also { pos.advance() }) {
 | 
				
			||||||
                    ':' -> Token("??", from, Token.Type.ELVIS)
 | 
					                    ':' -> Token("??", from, Token.Type.ELVIS)
 | 
				
			||||||
                    '?' -> Token("??", from, Token.Type.ELVIS)
 | 
					                    '?' -> Token("??", from, Token.Type.ELVIS)
 | 
				
			||||||
                    '.' -> Token("?.", from, Token.Type.NULL_COALESCE)
 | 
					                    '.' -> Token("?.", from, Token.Type.NULL_COALESCE)
 | 
				
			||||||
@ -310,7 +316,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
                // Labels processing is complicated!
 | 
					                // Labels processing is complicated!
 | 
				
			||||||
                // some@ statement: label 'some', ID 'statement'
 | 
					                // some@ statement: label 'some', ID 'statement'
 | 
				
			||||||
                // statement@some: ID 'statement', LABEL 'some'!
 | 
					                // statement@some: ID 'statement', LABEL 'some'!
 | 
				
			||||||
                if (ch.isLetter() || ch == '_') {
 | 
					                if (idNextChars(ch)) {
 | 
				
			||||||
                    val text = ch + loadChars(idNextChars)
 | 
					                    val text = ch + loadChars(idNextChars)
 | 
				
			||||||
                    if (currentChar == '@') {
 | 
					                    if (currentChar == '@') {
 | 
				
			||||||
                        pos.advance()
 | 
					                        pos.advance()
 | 
				
			||||||
@ -395,25 +401,24 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
    private fun fixMultilineStringLiteral(source: String): String {
 | 
					    private fun fixMultilineStringLiteral(source: String): String {
 | 
				
			||||||
        val sizes = mutableListOf<Int>()
 | 
					        val sizes = mutableListOf<Int>()
 | 
				
			||||||
        val lines = source.lines().toMutableList()
 | 
					        val lines = source.lines().toMutableList()
 | 
				
			||||||
        if( lines.size == 0 ) return ""
 | 
					        if (lines.size == 0) return ""
 | 
				
			||||||
        if( lines[0].isBlank() ) lines.removeFirst()
 | 
					        if (lines[0].isBlank()) lines.removeFirst()
 | 
				
			||||||
        if( lines.isEmpty()) return ""
 | 
					        if (lines.isEmpty()) return ""
 | 
				
			||||||
        if( lines.last().isBlank() ) lines.removeLast()
 | 
					        if (lines.last().isBlank()) lines.removeLast()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val normalized = lines.map { l ->
 | 
					        val normalized = lines.map { l ->
 | 
				
			||||||
            if( l.isBlank() ) {
 | 
					            if (l.isBlank()) {
 | 
				
			||||||
                sizes.add(-1)
 | 
					                sizes.add(-1)
 | 
				
			||||||
                ""
 | 
					                ""
 | 
				
			||||||
            }
 | 
					            } else {
 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
                val margin = leftMargin(l)
 | 
					                val margin = leftMargin(l)
 | 
				
			||||||
                sizes += margin
 | 
					                sizes += margin
 | 
				
			||||||
                " ".repeat(margin) + l.trim()
 | 
					                " ".repeat(margin) + l.trim()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        val commonMargin = sizes.filter { it >= 0 }.min()
 | 
					        val commonMargin = sizes.filter { it >= 0 }.min()
 | 
				
			||||||
        val fixed = if( commonMargin < 1 ) lines else normalized.map {
 | 
					        val fixed = if (commonMargin < 1) lines else normalized.map {
 | 
				
			||||||
            if( it.isBlank() ) "" else it.drop(commonMargin)
 | 
					            if (it.isBlank()) "" else it.drop(commonMargin)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return fixed.joinToString("\n")
 | 
					        return fixed.joinToString("\n")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -433,11 +438,26 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
                '\\' -> {
 | 
					                '\\' -> {
 | 
				
			||||||
                    pos.advance() ?: raise("unterminated string")
 | 
					                    pos.advance() ?: raise("unterminated string")
 | 
				
			||||||
                    when (currentChar) {
 | 
					                    when (currentChar) {
 | 
				
			||||||
                        'n' -> {sb.append('\n'); pos.advance()}
 | 
					                        'n' -> {
 | 
				
			||||||
                        'r' -> {sb.append('\r'); pos.advance()}
 | 
					                            sb.append('\n'); pos.advance()
 | 
				
			||||||
                        't' -> {sb.append('\t'); pos.advance()}
 | 
					                        }
 | 
				
			||||||
                        '"' -> {sb.append('"'); pos.advance()}
 | 
					
 | 
				
			||||||
                        '\\' -> {sb.append('\\'); pos.advance()}
 | 
					                        'r' -> {
 | 
				
			||||||
 | 
					                            sb.append('\r'); pos.advance()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        't' -> {
 | 
				
			||||||
 | 
					                            sb.append('\t'); pos.advance()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        '"' -> {
 | 
				
			||||||
 | 
					                            sb.append('"'); pos.advance()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        '\\' -> {
 | 
				
			||||||
 | 
					                            sb.append('\\'); pos.advance()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        else -> {
 | 
					                        else -> {
 | 
				
			||||||
                            sb.append('\\').append(currentChar)
 | 
					                            sb.append('\\').append(currentChar)
 | 
				
			||||||
                            pos.advance()
 | 
					                            pos.advance()
 | 
				
			||||||
@ -445,7 +465,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                '\n', '\r'-> {
 | 
					                '\n', '\r' -> {
 | 
				
			||||||
                    newlineDetected = true
 | 
					                    newlineDetected = true
 | 
				
			||||||
                    sb.append(currentChar)
 | 
					                    sb.append(currentChar)
 | 
				
			||||||
                    pos.advance()
 | 
					                    pos.advance()
 | 
				
			||||||
@ -459,7 +479,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        pos.advance()
 | 
					        pos.advance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val result = sb.toString().let { if( newlineDetected ) fixMultilineStringLiteral(it) else it }
 | 
					        val result = sb.toString().let { if (newlineDetected) fixMultilineStringLiteral(it) else it }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Token(result, start, Token.Type.STRING)
 | 
					        return Token(result, start, Token.Type.STRING)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -538,7 +558,7 @@ private class Parser(fromPos: Pos) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    init {
 | 
					    init {
 | 
				
			||||||
        // skip shebang
 | 
					        // skip shebang
 | 
				
			||||||
        if( pos.readFragment("#!") )
 | 
					        if (pos.readFragment("#!"))
 | 
				
			||||||
            loadToEndOfLine()
 | 
					            loadToEndOfLine()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
 | 
				
			|||||||
        ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
 | 
					        ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
 | 
				
			||||||
        PLUS2, MINUS2,
 | 
					        PLUS2, MINUS2,
 | 
				
			||||||
        IN, NOTIN, IS, NOTIS,
 | 
					        IN, NOTIN, IS, NOTIS,
 | 
				
			||||||
        EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
 | 
					        EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
 | 
				
			||||||
        SHUTTLE,
 | 
					        SHUTTLE,
 | 
				
			||||||
        AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
 | 
					        AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
 | 
				
			||||||
        SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
 | 
					        SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
 | 
				
			||||||
 | 
				
			|||||||
@ -178,6 +178,14 @@ open class Obj {
 | 
				
			|||||||
        scope.raiseNotImplemented()
 | 
					        scope.raiseNotImplemented()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    open suspend fun operatorMatch(scope: Scope, other: Obj): Obj {
 | 
				
			||||||
 | 
					        scope.raiseNotImplemented()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    open suspend fun operatorNotMatch(scope: Scope, other: Obj): Obj {
 | 
				
			||||||
 | 
					        return operatorMatch(scope,other).logicalNot(scope)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    open suspend fun assign(scope: Scope, other: Obj): Obj? = null
 | 
					    open suspend fun assign(scope: Scope, other: Obj): Obj? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    open fun getValue(scope: Scope) = this
 | 
					    open fun getValue(scope: Scope) = this
 | 
				
			||||||
@ -301,6 +309,18 @@ open class Obj {
 | 
				
			|||||||
        return scope
 | 
					        return scope
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fun <reified R: Obj> cast(scope: Scope): R {
 | 
				
			||||||
 | 
					        castOrNull<R>()?.let { return it }
 | 
				
			||||||
 | 
					        scope.raiseClassCastError("can't cast ${this::class.simpleName} to ${R::class.simpleName}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inline fun <reified R: Obj> castOrNull(): R? {
 | 
				
			||||||
 | 
					        (this as? R)?.let { return it }
 | 
				
			||||||
 | 
					        // todo: check for subclasses
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val rootObjectType = ObjClass("Obj").apply {
 | 
					        val rootObjectType = ObjClass("Obj").apply {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,11 +22,20 @@ import net.sergeych.lyng.Scope
 | 
				
			|||||||
class ObjRegex(val regex: Regex) : Obj() {
 | 
					class ObjRegex(val regex: Regex) : Obj() {
 | 
				
			||||||
    override val objClass = type
 | 
					    override val objClass = type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun operatorMatch(scope: Scope, other: Obj): Obj {
 | 
				
			||||||
 | 
					        return regex.find(other.cast<ObjString>(scope).value)?.let {
 | 
				
			||||||
 | 
					            scope.addConst("$~", ObjRegexMatch(it))
 | 
				
			||||||
 | 
					            ObjTrue
 | 
				
			||||||
 | 
					        } ?: ObjFalse
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun find(s: ObjString): Obj =
 | 
				
			||||||
 | 
					        regex.find(s.value)?.let { ObjRegexMatch(it) } ?: ObjNull
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        val type by lazy {
 | 
					        val type by lazy {
 | 
				
			||||||
            object : ObjClass("Regex") {
 | 
					            object : ObjClass("Regex") {
 | 
				
			||||||
                override suspend fun callOn(scope: Scope): Obj {
 | 
					                override suspend fun callOn(scope: Scope): Obj {
 | 
				
			||||||
                    println(scope.requireOnlyArg<ObjString>().value)
 | 
					 | 
				
			||||||
                    return ObjRegex(
 | 
					                    return ObjRegex(
 | 
				
			||||||
                        scope.requireOnlyArg<ObjString>().value.toRegex()
 | 
					                        scope.requireOnlyArg<ObjString>().value.toRegex()
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
@ -36,8 +45,7 @@ class ObjRegex(val regex: Regex) : Obj() {
 | 
				
			|||||||
                    ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex))
 | 
					                    ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                addFn("find") {
 | 
					                addFn("find") {
 | 
				
			||||||
                    val s = requireOnlyArg<ObjString>().value
 | 
					                    thisAs<ObjRegex>().find(requireOnlyArg<ObjString>())
 | 
				
			||||||
                    thisAs<ObjRegex>().regex.find(s)?.let { ObjRegexMatch(it) } ?: ObjNull
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                addFn("findAll") {
 | 
					                addFn("findAll") {
 | 
				
			||||||
                    val s = requireOnlyArg<ObjString>().value
 | 
					                    val s = requireOnlyArg<ObjString>().value
 | 
				
			||||||
@ -61,6 +69,7 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    val objRange: ObjRange by lazy {
 | 
					    val objRange: ObjRange by lazy {
 | 
				
			||||||
        val r = match.range
 | 
					        val r = match.range
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ObjRange(
 | 
					        ObjRange(
 | 
				
			||||||
            ObjInt(r.first.toLong()),
 | 
					            ObjInt(r.first.toLong()),
 | 
				
			||||||
            ObjInt(r.last.toLong()),
 | 
					            ObjInt(r.last.toLong()),
 | 
				
			||||||
@ -68,6 +77,19 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun toString(scope: Scope,calledFromLyng: Boolean): ObjString {
 | 
				
			||||||
 | 
					        return ObjString("RegexMath(${objRange.toString(scope)},${objGroups.toString(scope)})")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun getAt(scope: Scope, index: Obj): Obj {
 | 
				
			||||||
 | 
					        return objGroups.getAt(scope, index)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun compareTo(scope: Scope, other: Obj): Int {
 | 
				
			||||||
 | 
					        if( other === this) return 0
 | 
				
			||||||
 | 
					        return -2
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        val type by lazy {
 | 
					        val type by lazy {
 | 
				
			||||||
            object : ObjClass("RegexMatch") {
 | 
					            object : ObjClass("RegexMatch") {
 | 
				
			||||||
 | 
				
			|||||||
@ -56,16 +56,23 @@ data class ObjString(val value: String) : Obj() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override suspend fun getAt(scope: Scope, index: Obj): Obj {
 | 
					    override suspend fun getAt(scope: Scope, index: Obj): Obj {
 | 
				
			||||||
        if (index is ObjInt) return ObjChar(value[index.toInt()])
 | 
					        when (index) {
 | 
				
			||||||
        if (index is ObjRange) {
 | 
					            is ObjInt -> return ObjChar(value[index.toInt()])
 | 
				
			||||||
            val start = if (index.start == null || index.start.isNull) 0 else index.start.toInt()
 | 
					            is ObjRange -> {
 | 
				
			||||||
            val end = if (index.end == null || index.end.isNull) value.length else {
 | 
					                val start = if (index.start == null || index.start.isNull) 0 else index.start.toInt()
 | 
				
			||||||
                val e = index.end.toInt()
 | 
					                val end = if (index.end == null || index.end.isNull) value.length else {
 | 
				
			||||||
                if (index.isEndInclusive) e + 1 else e
 | 
					                    val e = index.end.toInt()
 | 
				
			||||||
 | 
					                    if (index.isEndInclusive) e + 1 else e
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return ObjString(value.substring(start, end))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return ObjString(value.substring(start, end))
 | 
					
 | 
				
			||||||
 | 
					            is ObjRegex -> {
 | 
				
			||||||
 | 
					                return index.find(this)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else -> scope.raiseIllegalArgument("String index must be Int, Regex or Range")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        scope.raiseIllegalArgument("String index must be Int or Range")
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun hashCode(): Int {
 | 
					    override fun hashCode(): Int {
 | 
				
			||||||
@ -96,6 +103,11 @@ data class ObjString(val value: String) : Obj() {
 | 
				
			|||||||
        return value == other.value
 | 
					        return value == other.value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun operatorMatch(scope: Scope, other: Obj): Obj {
 | 
				
			||||||
 | 
					        val re = other.cast<ObjRegex>(scope)
 | 
				
			||||||
 | 
					        return re.operatorMatch(scope, this)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override suspend fun lynonType(): LynonType = LynonType.String
 | 
					    override suspend fun lynonType(): LynonType = LynonType.String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
 | 
					    override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
 | 
				
			||||||
@ -108,8 +120,9 @@ data class ObjString(val value: String) : Obj() {
 | 
				
			|||||||
                ObjString(decoder.unpackBinaryData().decodeToString())
 | 
					                ObjString(decoder.unpackBinaryData().decodeToString())
 | 
				
			||||||
        }.apply {
 | 
					        }.apply {
 | 
				
			||||||
            addFn("toInt") {
 | 
					            addFn("toInt") {
 | 
				
			||||||
                ObjInt(thisAs<ObjString>().value.toLongOrNull()
 | 
					                ObjInt(
 | 
				
			||||||
                    ?: raiseIllegalArgument("can't convert to int: $thisObj")
 | 
					                    thisAs<ObjString>().value.toLongOrNull()
 | 
				
			||||||
 | 
					                        ?: raiseIllegalArgument("can't convert to int: $thisObj")
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            addFn("startsWith") {
 | 
					            addFn("startsWith") {
 | 
				
			||||||
@ -160,6 +173,22 @@ data class ObjString(val value: String) : Obj() {
 | 
				
			|||||||
            addFn("trim") {
 | 
					            addFn("trim") {
 | 
				
			||||||
                thisAs<ObjString>().value.trim().let(::ObjString)
 | 
					                thisAs<ObjString>().value.trim().let(::ObjString)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            addFn("matches") {
 | 
				
			||||||
 | 
					                val s = requireOnlyArg<Obj>()
 | 
				
			||||||
 | 
					                val self = thisAs<ObjString>().value
 | 
				
			||||||
 | 
					                ObjBool(
 | 
				
			||||||
 | 
					                    when (s) {
 | 
				
			||||||
 | 
					                        is ObjRegex -> self.matches(s.regex)
 | 
				
			||||||
 | 
					                        is ObjString -> {
 | 
				
			||||||
 | 
					                            if (s.value == ".*") true
 | 
				
			||||||
 | 
					                            else self.matches(s.value.toRegex())
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        else ->
 | 
				
			||||||
 | 
					                            raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -2242,6 +2242,45 @@ class ScriptTest {
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun testParseSpecialVars() {
 | 
				
			||||||
 | 
					        val l = parseLyng("$~".toSource("test$~"))
 | 
				
			||||||
 | 
					        println(l)
 | 
				
			||||||
 | 
					        assertEquals(Token.Type.ID, l[0].type)
 | 
				
			||||||
 | 
					        assertEquals("$~", l[0].value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun testMatchOperator() = runTest {
 | 
				
			||||||
 | 
					        eval("""
 | 
				
			||||||
 | 
					            assert( "abc123".matches(".*\d{3}") )
 | 
				
			||||||
 | 
					            assert( ".*\d{3}".re =~ "abc123" )
 | 
				
			||||||
 | 
					            assert( "abc123" =~ ".*\d{3}".re )
 | 
				
			||||||
 | 
					            assert( "abc123" !~ ".*\d{4}".re )
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            "abc123" =~ ".*(\d)(\d)(\d)$".re
 | 
				
			||||||
 | 
					            println($~)
 | 
				
			||||||
 | 
					            assertEquals("1", $~[1])
 | 
				
			||||||
 | 
					            assertEquals("2", $~[2])
 | 
				
			||||||
 | 
					            assertEquals("3", $~[3])
 | 
				
			||||||
 | 
					            assertEquals("abc123", $~[0])
 | 
				
			||||||
 | 
					        """.trimIndent())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    @Test
 | 
				
			||||||
 | 
					//    fun testWhenMatch() = runTest {
 | 
				
			||||||
 | 
					//        eval(
 | 
				
			||||||
 | 
					//            """
 | 
				
			||||||
 | 
					//            when("abc123") {
 | 
				
			||||||
 | 
					//                ".*(\d)(\d)(\d)".re -> { x ->
 | 
				
			||||||
 | 
					//                    assertEquals("123", x[0])
 | 
				
			||||||
 | 
					//                }
 | 
				
			||||||
 | 
					//                else -> assert(false)
 | 
				
			||||||
 | 
					//            }
 | 
				
			||||||
 | 
					//            """.trimIndent()
 | 
				
			||||||
 | 
					//        )
 | 
				
			||||||
 | 
					//    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    fun testWhenSample1() = runTest {
 | 
					    fun testWhenSample1() = runTest {
 | 
				
			||||||
        eval(
 | 
					        eval(
 | 
				
			||||||
@ -3247,7 +3286,7 @@ class ScriptTest {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    @Test
 | 
					    //    @Test
 | 
				
			||||||
    fun testMinimumOptimization() = runTest {
 | 
					    fun testMinimumOptimization() = runTest {
 | 
				
			||||||
        val x = Scope().eval(
 | 
					        val x = Scope().eval(
 | 
				
			||||||
            """
 | 
					            """
 | 
				
			||||||
 | 
				
			|||||||
@ -332,4 +332,8 @@ class BookTest {
 | 
				
			|||||||
        runDocTests("../docs/Array.md")
 | 
					        runDocTests("../docs/Array.md")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun testRegex() = runBlocking {
 | 
				
			||||||
 | 
					        runDocTests("../docs/Regex.md")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user