arrays started: literals, size, splats, index access RW
This commit is contained in:
parent
f881faf89f
commit
c18345823b
24
docs/List.md
Normal file
24
docs/List.md
Normal file
@ -0,0 +1,24 @@
|
||||
# List built-in class
|
||||
|
||||
Mutable list of any objects.
|
||||
|
||||
It's class in Ling is `List`:
|
||||
|
||||
[1,2,3]::class
|
||||
>>> List
|
||||
|
||||
you can use it's class to ensure type:
|
||||
|
||||
[]::class == List
|
||||
>>> true
|
||||
|
||||
## Members
|
||||
|
||||
| name | meaning | type |
|
||||
|---------|-------------------------------|------|
|
||||
| `.size` | property returns current size | Int |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
16
docs/Real.md
16
docs/Real.md
@ -15,11 +15,11 @@ you can use it's class to ensure type:
|
||||
|
||||
## Member functions
|
||||
|
||||
| name | meaning | type |
|
||||
|--------------|------------------------------------|------|
|
||||
| `roundToInt` | round to nearest int like round(x) | Int |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| name | meaning | type |
|
||||
|-----------------|------------------------------------|------|
|
||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
@ -103,8 +103,7 @@ Notice the parentheses here: the assignment has low priority!
|
||||
|
||||
These operators return rvalue, unmodifiable.
|
||||
|
||||
## Assignemnt return r-value!
|
||||
|
||||
## Assignment return r-value!
|
||||
|
||||
## Math
|
||||
|
||||
@ -205,7 +204,7 @@ There are default parameters in Ling:
|
||||
}
|
||||
assert( "do: more" == check(10, "do: ") )
|
||||
check(120)
|
||||
>>> answer: enough
|
||||
>>> "answer: enough"
|
||||
|
||||
## Closures
|
||||
|
||||
@ -245,6 +244,43 @@ to call it:
|
||||
|
||||
If you need to create _unnamed_ function, use alternative syntax (TBD, like { -> } ?)
|
||||
|
||||
# Lists (arrays)
|
||||
|
||||
Ling has built-in mutable array class `List` with simple literals:
|
||||
|
||||
[1, "two", 3.33].size
|
||||
>>> 3
|
||||
|
||||
Lists can contain any type of objects, lists too:
|
||||
|
||||
val list = [1, [2, 3], 4]
|
||||
assert(list.size == 3)
|
||||
// second element is a list too:
|
||||
assert(list[1].size == 2)
|
||||
>>> void
|
||||
|
||||
Notice usage of indexing.
|
||||
|
||||
When you want to "flatten" it to single array, you can use splat syntax:
|
||||
|
||||
[1, ...[2,3], 4]
|
||||
>>> [1, 2, 3, 4]
|
||||
|
||||
Of course, you can splat from anything that is List (or list-like, but it will be defined later):
|
||||
|
||||
val a = ["one", "two"]
|
||||
val b = [10.1, 20.2]
|
||||
["start", ...b, ...a, "end"]
|
||||
>>> ["start", 10.1, 20.2, "one", "two", "end"]
|
||||
|
||||
Of course, you can set any array element:
|
||||
|
||||
val a = [1, 2, 3]
|
||||
a[1] = 200
|
||||
a
|
||||
>>> [1, 200, 3]
|
||||
|
||||
|
||||
# Flow control operators
|
||||
|
||||
## if-then-else
|
||||
@ -299,7 +335,7 @@ exit value in the case:
|
||||
count = ++count * 10
|
||||
"wrong "+count
|
||||
}
|
||||
>>> too much
|
||||
>>> "too much"
|
||||
|
||||
### Breaking nested loops
|
||||
|
||||
@ -319,7 +355,7 @@ If you have several loops and want to exit not the inner one, use labels:
|
||||
count = count + 1
|
||||
count * 10
|
||||
}
|
||||
>>> 5/2 situation
|
||||
>>> "5/2 situation"
|
||||
|
||||
### and continue
|
||||
|
||||
@ -333,7 +369,7 @@ We can skip the rest of the loop and restart it, as usual, with `continue` opera
|
||||
countEven = countEven + 1
|
||||
}
|
||||
"found even numbers: " + countEven
|
||||
>>> found even numbers: 5
|
||||
>>> "found even numbers: 5"
|
||||
|
||||
`continue` can't "return" anything: it just restarts the loop. It can use labeled loops to restart outer ones:
|
||||
|
||||
|
@ -91,7 +91,7 @@ class Compiler(
|
||||
|
||||
private fun parseExpressionLevel(tokens: CompilerContext, level: Int = 0): Accessor? {
|
||||
if (level == lastLevel)
|
||||
return parseTerm3(tokens)
|
||||
return parseTerm(tokens)
|
||||
var lvalue = parseExpressionLevel(tokens, level + 1)
|
||||
if (lvalue == null) return null
|
||||
|
||||
@ -113,80 +113,21 @@ class Compiler(
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Term compiler
|
||||
|
||||
Fn calls could be:
|
||||
|
||||
1) Fn(...)
|
||||
2) thisObj.method(...)
|
||||
|
||||
1 is a shortcut to this.Fn(...)
|
||||
|
||||
In general, we can assume any Fn to be of the same king, with `this` that always exist, and set by invocation.
|
||||
|
||||
In the case of (1), so called regular, or not bound function, it takes current this from the context.
|
||||
In the case of (2), bound function, it creates sub-context binding thisObj to `this` in it.
|
||||
|
||||
Suppose we do regular parsing. THen we get lparam = statement, and parse to the `(`. Now we have to
|
||||
compile the invocation of lparam, which can be thisObj.method or just method(). Unfortunately we
|
||||
already compiled it and can't easily restore its type, so we have to parse it different way.
|
||||
|
||||
EBNF to parse term having lparam.
|
||||
|
||||
boundcall = "." , identifier, "("
|
||||
|
||||
We then call instance method bound to `lparam`.
|
||||
|
||||
call = "(', args, ")
|
||||
|
||||
we treat current lparam as callable and invoke it on the current context with current value of 'this.
|
||||
|
||||
Just traversing fields:
|
||||
|
||||
traverse = ".", not (identifier , ".")
|
||||
|
||||
Other cases to parse:
|
||||
|
||||
index = lparam, "[" , ilist , "]"
|
||||
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Lower level of expr:
|
||||
*
|
||||
* assigning expressions:
|
||||
*
|
||||
* expr = expr: assignment
|
||||
* ++expr, expr++, --expr, expr--,
|
||||
*
|
||||
* update-assigns:
|
||||
* expr += expr, ...
|
||||
*
|
||||
* Dot!: expr , '.', ID
|
||||
* Lambda: { <expr> }
|
||||
* index: expr[ ilist ]
|
||||
* call: <expr>( ilist )
|
||||
* self updating: ++expr, expr++, --expr, expr--, expr+=<expr>,
|
||||
* expr-=<expr>, expr*=<expr>, expr/=<expr>
|
||||
* read expr: <expr>
|
||||
*/
|
||||
private fun parseTerm3(cc: CompilerContext): Accessor? {
|
||||
private fun parseTerm(cc: CompilerContext): Accessor? {
|
||||
var operand: Accessor? = null
|
||||
|
||||
while (true) {
|
||||
val t = cc.next()
|
||||
val startPos = t.pos
|
||||
when (t.type) {
|
||||
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF -> {
|
||||
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF, Token.Type.RBRACE, Token.Type.COMMA -> {
|
||||
cc.previous()
|
||||
return operand
|
||||
}
|
||||
|
||||
Token.Type.NOT -> {
|
||||
if (operand != null) throw ScriptError(t.pos, "unexpected operator not '!'")
|
||||
val op = parseTerm3(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
val op = parseTerm(cc) ?: throw ScriptError(t.pos, "Expecting expression")
|
||||
operand = Accessor { op.getter(it).value.logicalNot(it).asReadonly }
|
||||
}
|
||||
|
||||
@ -243,6 +184,45 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.LBRACKET -> {
|
||||
operand?.let { left ->
|
||||
// array access
|
||||
val index = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting index expression")
|
||||
cc.skipTokenOfType(Token.Type.RBRACKET, "missing ']' at the end of the list literal")
|
||||
operand = Accessor({ cxt ->
|
||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
||||
?: cxt.raiseError("index must be integer")
|
||||
left.getter(cxt).value.getAt(cxt, i).asMutable
|
||||
}) { cxt, newValue ->
|
||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
||||
?: cxt.raiseError("index must be integer")
|
||||
left.getter(cxt).value.putAt(cxt, i, newValue)
|
||||
}
|
||||
} ?: run {
|
||||
// array literal
|
||||
val entries = parseArrayLiteral(cc)
|
||||
// if it didn't throw, ot parsed ot and consumed it all
|
||||
operand = Accessor { cxt ->
|
||||
val list = mutableListOf<Obj>()
|
||||
for (e in entries) {
|
||||
when(e) {
|
||||
is ListEntry.Element -> {
|
||||
list += e.accessor.getter(cxt).value
|
||||
}
|
||||
is ListEntry.Spread -> {
|
||||
val elements=e.accessor.getter(cxt).value
|
||||
when {
|
||||
elements is ObjList -> list.addAll(elements.list)
|
||||
else -> cxt.raiseError("Spread element must be list")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ObjList(list).asReadonly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.ID -> {
|
||||
// there could be terminal operators or keywords:// variable to read or like
|
||||
when (t.value) {
|
||||
@ -276,9 +256,6 @@ class Compiler(
|
||||
operand = parseAccessor(cc)
|
||||
}
|
||||
}
|
||||
// selector: <lvalue>, '.' , <id>
|
||||
// we replace operand with selector code, that
|
||||
// is RW:
|
||||
}
|
||||
|
||||
Token.Type.PLUS2 -> {
|
||||
@ -335,6 +312,28 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseArrayLiteral(cc: CompilerContext): List<ListEntry> {
|
||||
// it should be called after LBRACKET is consumed
|
||||
val entries = mutableListOf<ListEntry>()
|
||||
while(true) {
|
||||
val t = cc.next()
|
||||
when(t.type) {
|
||||
Token.Type.COMMA -> {
|
||||
// todo: check commas sequences like [,] [,,] before, after or instead of expressions
|
||||
}
|
||||
Token.Type.RBRACKET -> return entries
|
||||
Token.Type.ELLIPSIS -> {
|
||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Spread(it) }
|
||||
}
|
||||
else -> {
|
||||
cc.previous()
|
||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Element(it) }
|
||||
?: throw ScriptError(t.pos, "invalid list literal: expecting expression")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseScopeOperator(operand: Accessor?, cc: CompilerContext): Accessor {
|
||||
// implement global scope maybe?
|
||||
if (operand == null) throw ScriptError(cc.next().pos, "Expecting expression before ::")
|
||||
@ -389,7 +388,7 @@ class Compiler(
|
||||
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||
cc.previous()
|
||||
val n = parseNumber(true, cc)
|
||||
Accessor{
|
||||
Accessor {
|
||||
n.asReadonly
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class Context(
|
||||
)
|
||||
: this(Script.defaultContext, args, pos)
|
||||
|
||||
fun raiseNotImplemented(): Nothing = raiseError("operation not implemented")
|
||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||
|
@ -0,0 +1,7 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
sealed class ListEntry {
|
||||
data class Element(val accessor: Accessor) : ListEntry()
|
||||
|
||||
data class Spread(val accessor: Accessor) : ListEntry()
|
||||
}
|
@ -148,7 +148,19 @@ sealed class Obj {
|
||||
|
||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||
|
||||
fun readField(context: Context, name: String): WithAccess<Obj> = getInstanceMember(context.pos, name)
|
||||
suspend fun readField(context: Context, name: String): WithAccess<Obj> {
|
||||
// could be property or class field:
|
||||
val obj = objClass.getInstanceMemberOrNull(name)
|
||||
val value = obj?.value
|
||||
return when(value) {
|
||||
is Statement -> {
|
||||
// readonly property, important: call it on this
|
||||
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
|
||||
}
|
||||
// could be writable property naturally
|
||||
else -> getInstanceMember(context.pos, name)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
willMutate(context)
|
||||
@ -156,6 +168,14 @@ sealed class Obj {
|
||||
?: context.raiseError("Can't reassign member: $name")
|
||||
}
|
||||
|
||||
open suspend fun getAt(context: Context, index: Int): Obj {
|
||||
context.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
open suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
context.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
|
||||
if (name in members || parentInstances.any { name in it.members })
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
@ -235,7 +255,7 @@ data class ObjString(val value: String) : Obj() {
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
override fun toString(): String = "\"$value\""
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
@ -418,6 +438,40 @@ data class ObjBool(val value: Boolean) : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
//open class ObjProperty(var value: Obj =ObjVoid) {
|
||||
// open suspend fun get(context: Context): Obj = value
|
||||
// open suspend fun set(context: Context,newValue: Obj): Obj {
|
||||
// return value.also { value = newValue }
|
||||
// }
|
||||
//}
|
||||
|
||||
class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||
|
||||
override fun toString(): String = "[${list.joinToString(separator = ", ")}]"
|
||||
|
||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
||||
return list[index]
|
||||
}
|
||||
|
||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
list[index] = newValue
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("List").apply {
|
||||
createField("size",
|
||||
statement(Pos.builtIn) {
|
||||
(it.thisObj as ObjList).list.size.toObj()
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ObjNamespace(val name: String) : Obj() {
|
||||
override fun toString(): String {
|
||||
return "namespace ${name}"
|
||||
|
@ -33,7 +33,7 @@ private class Parser(fromPos: Pos) {
|
||||
skipws()
|
||||
if (pos.end) return Token("", currentPos, Token.Type.EOF)
|
||||
val from = currentPos
|
||||
return when (val ch = pos.currentChar.also { advance() }) {
|
||||
return when (val ch = pos.currentChar.also { pos.advance() }) {
|
||||
'(' -> Token("(", from, Token.Type.LPAREN)
|
||||
')' -> Token(")", from, Token.Type.RPAREN)
|
||||
'{' -> Token("{", from, Token.Type.LBRACE)
|
||||
@ -44,7 +44,7 @@ private class Parser(fromPos: Pos) {
|
||||
';' -> Token(";", from, Token.Type.SEMICOLON)
|
||||
'=' -> {
|
||||
if (pos.currentChar == '=') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("==", from, Token.Type.EQ)
|
||||
} else
|
||||
Token("=", from, Token.Type.ASSIGN)
|
||||
@ -53,12 +53,12 @@ private class Parser(fromPos: Pos) {
|
||||
'+' -> {
|
||||
when (currentChar) {
|
||||
'+' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("+", from, Token.Type.PLUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("+", from, Token.Type.PLUSASSIGN)
|
||||
}
|
||||
|
||||
@ -70,12 +70,12 @@ private class Parser(fromPos: Pos) {
|
||||
'-' -> {
|
||||
when (currentChar) {
|
||||
'-' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("--", from, Token.Type.MINUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("-", from, Token.Type.MINUSASSIGN)
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'*' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("*=", from, Token.Type.STARASSIGN)
|
||||
} else
|
||||
Token("*", from, Token.Type.STAR)
|
||||
@ -93,24 +93,47 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'/' -> when (currentChar) {
|
||||
'/' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("/=", from, Token.Type.SLASHASSIGN)
|
||||
}
|
||||
|
||||
else -> Token("/", from, Token.Type.SLASH)
|
||||
}
|
||||
|
||||
'%' -> when(currentChar) {
|
||||
'=' -> { advance(); Token("%=", from, Token.Type.PERCENTASSIGN) }
|
||||
else -> Token("%", from, Token.Type.PERCENT) }
|
||||
'%' -> when (currentChar) {
|
||||
'=' -> {
|
||||
pos.advance(); Token("%=", from, Token.Type.PERCENTASSIGN)
|
||||
}
|
||||
|
||||
else -> Token("%", from, Token.Type.PERCENT)
|
||||
}
|
||||
|
||||
'.' -> {
|
||||
// could be: dot, range .. or ..<, or ellipsis ...:
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
// .. already parsed:
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
} else if (currentChar == '<') {
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
} else {
|
||||
pos.back()
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
}
|
||||
} else
|
||||
Token(".", from, Token.Type.DOT)
|
||||
}
|
||||
|
||||
'.' -> Token(".", from, Token.Type.DOT)
|
||||
'<' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("<=", from, Token.Type.LTE)
|
||||
} else
|
||||
Token("<", from, Token.Type.LT)
|
||||
@ -118,7 +141,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'>' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token(">=", from, Token.Type.GTE)
|
||||
} else
|
||||
Token(">", from, Token.Type.GT)
|
||||
@ -126,7 +149,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'!' -> {
|
||||
if (currentChar == '=') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("!=", from, Token.Type.NEQ)
|
||||
} else
|
||||
Token("!", from, Token.Type.NOT)
|
||||
@ -134,7 +157,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'|' -> {
|
||||
if (currentChar == '|') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("||", from, Token.Type.OR)
|
||||
} else
|
||||
Token("|", from, Token.Type.BITOR)
|
||||
@ -142,7 +165,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
'&' -> {
|
||||
if (currentChar == '&') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("&&", from, Token.Type.AND)
|
||||
} else
|
||||
Token("&", from, Token.Type.BITAND)
|
||||
@ -158,7 +181,7 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
':' -> {
|
||||
if (currentChar == ':') {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token("::", from, Token.Type.COLONCOLON)
|
||||
} else
|
||||
Token(":", from, Token.Type.COLON)
|
||||
@ -177,7 +200,7 @@ private class Parser(fromPos: Pos) {
|
||||
if (ch.isLetter() || ch == '_') {
|
||||
val text = ch + loadChars(idNextChars)
|
||||
if (currentChar == '@') {
|
||||
advance()
|
||||
pos.advance()
|
||||
if (currentChar.isLetter()) {
|
||||
// break@label or like
|
||||
pos.back()
|
||||
@ -197,19 +220,19 @@ private class Parser(fromPos: Pos) {
|
||||
Token(p1, start, Token.Type.INT)
|
||||
else if (currentChar == '.') {
|
||||
// could be decimal
|
||||
advance()
|
||||
pos.advance()
|
||||
if (currentChar in digitsSet) {
|
||||
// decimal part
|
||||
val p2 = loadChars(digits)
|
||||
// with exponent?
|
||||
if (currentChar == 'e' || currentChar == 'E') {
|
||||
advance()
|
||||
pos.advance()
|
||||
var negative = false
|
||||
if (currentChar == '+')
|
||||
advance()
|
||||
pos.advance()
|
||||
else if (currentChar == '-') {
|
||||
negative = true
|
||||
advance()
|
||||
pos.advance()
|
||||
}
|
||||
var p3 = loadChars(digits)
|
||||
if (negative) p3 = "-$p3"
|
||||
@ -227,7 +250,7 @@ private class Parser(fromPos: Pos) {
|
||||
} else {
|
||||
// could be integer, also hex:
|
||||
if (currentChar == 'x' && p1 == "0") {
|
||||
advance()
|
||||
pos.advance()
|
||||
Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also {
|
||||
if (currentChar.isLetter())
|
||||
raise("invalid hex literal")
|
||||
@ -243,15 +266,15 @@ private class Parser(fromPos: Pos) {
|
||||
private fun loadStringToken(): Token {
|
||||
var start = currentPos
|
||||
|
||||
if (currentChar == '"') advance()
|
||||
if (currentChar == '"') pos.advance()
|
||||
else start = start.back()
|
||||
|
||||
val sb = StringBuilder()
|
||||
while (currentChar != '"') {
|
||||
if (pos.end) raise("unterminated string")
|
||||
if (pos.end) throw ScriptError(start, "unterminated string started there")
|
||||
when (currentChar) {
|
||||
'\\' -> {
|
||||
advance() ?: raise("unterminated string")
|
||||
pos.advance() ?: raise("unterminated string")
|
||||
when (currentChar) {
|
||||
'n' -> sb.append('\n')
|
||||
'r' -> sb.append('\r')
|
||||
@ -263,11 +286,11 @@ private class Parser(fromPos: Pos) {
|
||||
|
||||
else -> {
|
||||
sb.append(currentChar)
|
||||
advance()
|
||||
pos.advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
advance()
|
||||
pos.advance()
|
||||
return Token(sb.toString(), start, Token.Type.STRING)
|
||||
}
|
||||
|
||||
@ -287,7 +310,7 @@ private class Parser(fromPos: Pos) {
|
||||
val ch = pos.currentChar
|
||||
if (isValidChar(ch)) {
|
||||
result.append(ch)
|
||||
advance()
|
||||
pos.advance()
|
||||
} else
|
||||
break
|
||||
}
|
||||
@ -314,7 +337,7 @@ private class Parser(fromPos: Pos) {
|
||||
val l = pos.line
|
||||
do {
|
||||
result.append(pos.currentChar)
|
||||
advance()
|
||||
pos.advance()
|
||||
} while (pos.line == l)
|
||||
return result.toString()
|
||||
}
|
||||
@ -327,12 +350,11 @@ private class Parser(fromPos: Pos) {
|
||||
val ch = pos.currentChar
|
||||
if (ch == '\n') break
|
||||
if (ch.isWhitespace())
|
||||
advance()
|
||||
pos.advance()
|
||||
else
|
||||
return ch
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun advance() = pos.advance()
|
||||
}
|
@ -57,9 +57,8 @@ class Script(
|
||||
addConst("String", ObjString.type)
|
||||
addConst("Int", ObjInt.type)
|
||||
addConst("Bool", ObjBool.type)
|
||||
addConst("List", ObjList.type)
|
||||
val pi = ObjReal(PI)
|
||||
val z = pi.objClass
|
||||
println("PI class $z")
|
||||
addConst("π", pi)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
addConst("PI", pi)
|
||||
|
@ -13,7 +13,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
EQ, NEQ, LT, LTE, GT, GTE,
|
||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL,ATLABEL, // label@ at@label
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||
NEWLINE,
|
||||
EOF,
|
||||
}
|
||||
|
@ -485,7 +485,8 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testWhileBlockIsolation3() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
var outer = 7
|
||||
var sum = 0
|
||||
var cnt1 = 0
|
||||
@ -504,7 +505,7 @@ class ScriptTest {
|
||||
}
|
||||
println("sum "+sum)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -713,20 +714,96 @@ class ScriptTest {
|
||||
assertEquals(11, cxt.eval("x").toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testValVarConverting() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val x = 5
|
||||
var y = x
|
||||
y = 1
|
||||
assert(x == 5)
|
||||
""".trimIndent()
|
||||
)
|
||||
assertFails {
|
||||
eval(
|
||||
"""
|
||||
val x = 5
|
||||
fun fna(t) {
|
||||
t = 11
|
||||
}
|
||||
fna(1)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
eval(
|
||||
"""
|
||||
var x = 5
|
||||
val y = x
|
||||
x = 10
|
||||
assert(y == 5)
|
||||
assert(x == 10)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListLiteral() = runTest {
|
||||
eval("""
|
||||
val list = [1,22,3]
|
||||
assert(list[0] == 1)
|
||||
assert(list[1] == 22)
|
||||
assert(list[2] == 3)
|
||||
""".trimIndent())
|
||||
|
||||
eval("""
|
||||
val x0 = 100
|
||||
val list = [x0 + 1, x0 * 10, 3]
|
||||
assert(list[0] == 101)
|
||||
assert(list[1] == 1000)
|
||||
assert(list[2] == 3)
|
||||
""".trimIndent())
|
||||
|
||||
eval("""
|
||||
val x0 = 100
|
||||
val list = [x0 + 1, x0 * 10, if(x0 < 100) "low" else "high", 5]
|
||||
assert(list[0] == 101)
|
||||
assert(list[1] == 1000)
|
||||
assert(list[2] == "high")
|
||||
assert(list[3] == 5)
|
||||
""".trimIndent())
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListLiteralSpread() = runTest {
|
||||
eval("""
|
||||
val list1 = [1,22,3]
|
||||
val list = ["start", ...list1, "end"]
|
||||
assert(list[0] == "start")
|
||||
assert(list[1] == 1)
|
||||
assert(list[2] == 22)
|
||||
assert(list[3] == 3)
|
||||
assert(list[4] == "end")
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListSize() = runTest {
|
||||
eval("""
|
||||
val a = [4,3]
|
||||
assert(a.size == 2)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun testMultiAssign() = runTest {
|
||||
// assertEquals(
|
||||
// 7,
|
||||
// eval("""
|
||||
// var x = 10
|
||||
// var y = 2
|
||||
// (x = 1) = 5
|
||||
// println(x)
|
||||
// println(y)
|
||||
// x + y
|
||||
// """.trimIndent()).toInt()
|
||||
// )
|
||||
// fun testLambda1() = runTest {
|
||||
// val l = eval("""
|
||||
// x = {
|
||||
// 122
|
||||
// }
|
||||
// x
|
||||
// """.trimIndent())
|
||||
// println(l)
|
||||
// }
|
||||
//
|
||||
|
||||
}
|
@ -139,7 +139,7 @@ suspend fun DocTest.test() {
|
||||
val context = Context().apply {
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) collectedOutput.append(' '); collectedOutput.append(a)
|
||||
if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.asStr.value)
|
||||
collectedOutput.append('\n')
|
||||
}
|
||||
ObjVoid
|
||||
@ -198,4 +198,9 @@ class BookTest {
|
||||
runDocTests("../docs/Real.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromList() = runTest {
|
||||
runDocTests("../docs/List.md")
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user