Compare commits
No commits in common. "0e065e0d7c22b19fd9a09c5ce325027974ed4fe8" and "95e68d6e2a244939cd86b1a54ff141e62fc68eac" have entirely different histories.
0e065e0d7c
...
95e68d6e2a
159
docs/tutorial.md
159
docs/tutorial.md
@ -1,159 +0,0 @@
|
|||||||
# Ling tutorial
|
|
||||||
|
|
||||||
Ling is a very simple language, where we take only most important and popular features from
|
|
||||||
other scripts and languages. In particular, we adopt _principle of minimal confusion_[^1].
|
|
||||||
In other word, the code usually works as expected when you see it. So, nothing unusual.
|
|
||||||
|
|
||||||
# Expressions and blocks.
|
|
||||||
|
|
||||||
Everything is an expression in Ling. Even an empty block:
|
|
||||||
|
|
||||||
{
|
|
||||||
// empty block
|
|
||||||
}
|
|
||||||
>>> void
|
|
||||||
|
|
||||||
Block returns it last expression as "return value":
|
|
||||||
|
|
||||||
{
|
|
||||||
2 + 2
|
|
||||||
3 + 3
|
|
||||||
}
|
|
||||||
>>> 6
|
|
||||||
|
|
||||||
Same is without block:
|
|
||||||
|
|
||||||
3 + 3
|
|
||||||
>>> 6
|
|
||||||
|
|
||||||
If you don't want block to return anything, use `void`:
|
|
||||||
|
|
||||||
{
|
|
||||||
3 + 4
|
|
||||||
void
|
|
||||||
}
|
|
||||||
>>> void
|
|
||||||
|
|
||||||
Every construction is an expression that returns something (or `void`):
|
|
||||||
|
|
||||||
val limited = if( x > 100 ) 100 else x
|
|
||||||
|
|
||||||
You can use blocks in if statement, as expected:
|
|
||||||
|
|
||||||
val limited = if( x > 100 ) {
|
|
||||||
100 + x * 0.1
|
|
||||||
}
|
|
||||||
else
|
|
||||||
x
|
|
||||||
|
|
||||||
So the principles are:
|
|
||||||
|
|
||||||
- everything is an expression returning its last calculated value or `void`
|
|
||||||
- expression could be a `{ block }`
|
|
||||||
|
|
||||||
## Expression details
|
|
||||||
|
|
||||||
It is rather simple, like everywhere else:
|
|
||||||
|
|
||||||
sin(x * π/4) / 2.0
|
|
||||||
|
|
||||||
See [math](math.md) for more on it.
|
|
||||||
|
|
||||||
# Defining functions
|
|
||||||
|
|
||||||
fun check(amount) {
|
|
||||||
if( amount > 100 )
|
|
||||||
"anough"
|
|
||||||
else
|
|
||||||
"more"
|
|
||||||
}
|
|
||||||
|
|
||||||
You can use both `fn` and `fun`. Note that function declaration _is an expression returning callable_.
|
|
||||||
|
|
||||||
There are default parameters in Ling:
|
|
||||||
|
|
||||||
fn check(amount, prefix = "answer: ") {
|
|
||||||
prefix + if( amount > 100 )
|
|
||||||
"anough"
|
|
||||||
else
|
|
||||||
"more"
|
|
||||||
}
|
|
||||||
|
|
||||||
## Closures
|
|
||||||
|
|
||||||
Each __block has an isolated context that can be accessed from closures__. For example:
|
|
||||||
|
|
||||||
var counter = 1
|
|
||||||
|
|
||||||
// this is ok: coumter is incremented
|
|
||||||
def increment(amount=1) {
|
|
||||||
// use counter from a closure:
|
|
||||||
counter = counter + amount
|
|
||||||
}
|
|
||||||
|
|
||||||
val taskAlias = def someTask() {
|
|
||||||
// this obscures global outer var with a local one
|
|
||||||
var counter = 0
|
|
||||||
// ...
|
|
||||||
counter = 1
|
|
||||||
// ...
|
|
||||||
counter
|
|
||||||
}
|
|
||||||
|
|
||||||
As was told, `def` statement return callable for the function, it could be used as a parameter, or elsewhere
|
|
||||||
to call it:
|
|
||||||
|
|
||||||
// call the callable stored in the var
|
|
||||||
taskAlias()
|
|
||||||
// or directly:
|
|
||||||
someTask()
|
|
||||||
|
|
||||||
If you need to create _unnamed_ function, use alternative syntax (TBD, like { -> } ?)
|
|
||||||
|
|
||||||
# Integral data types
|
|
||||||
|
|
||||||
| type | description | literal samples |
|
|
||||||
|--------|---------------------------------|---------------------|
|
|
||||||
| Int | 64 bit signed | `1` `-22` `0x1FF` |
|
|
||||||
| Real | 64 bit double | `1.0`, `2e-11` |
|
|
||||||
| Bool | boolean | `true` `false` |
|
|
||||||
| String | unicode string, no limits | "hello" (see below) |
|
|
||||||
| Void | no value could exist, singleton | void |
|
|
||||||
| Null | missing value, singleton | null |
|
|
||||||
| Fn | callable type | |
|
|
||||||
|
|
||||||
## String details
|
|
||||||
|
|
||||||
### String operations
|
|
||||||
|
|
||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
|
||||||
|
|
||||||
### Literals
|
|
||||||
|
|
||||||
String literal could be multiline:
|
|
||||||
|
|
||||||
"
|
|
||||||
Hello,
|
|
||||||
World!
|
|
||||||
"
|
|
||||||
>>> "Hello
|
|
||||||
World"
|
|
||||||
|
|
||||||
In that case compiler removes left margin and first/last empty lines. Note that it won't remove margin:
|
|
||||||
|
|
||||||
"Hello,
|
|
||||||
World
|
|
||||||
"
|
|
||||||
>>> "Hello,
|
|
||||||
World
|
|
||||||
"
|
|
||||||
|
|
||||||
because the first line has no margin in the literal.
|
|
||||||
|
|
||||||
# Comments
|
|
||||||
|
|
||||||
// single line comment
|
|
||||||
var result = null // here we will store the result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
import com.vanniktech.maven.publish.SonatypeHost
|
import com.vanniktech.maven.publish.SonatypeHost
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@ -30,11 +29,6 @@ kotlin {
|
|||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
@OptIn(ExperimentalWasmDsl::class)
|
|
||||||
wasmJs() {
|
|
||||||
browser()
|
|
||||||
nodejs()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
|
@ -88,15 +88,8 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
|
||||||
|
|
||||||
Token.Type.SEMICOLON -> continue
|
Token.Type.SEMICOLON -> continue
|
||||||
|
|
||||||
Token.Type.LBRACE -> {
|
|
||||||
tokens.previous()
|
|
||||||
parseBlock(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
Token.Type.RBRACE -> {
|
Token.Type.RBRACE -> {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
return null
|
return null
|
||||||
@ -155,8 +148,6 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.STRING -> statement(t.pos, true) { ObjString(t.value) }
|
|
||||||
|
|
||||||
Token.Type.LPAREN -> {
|
Token.Type.LPAREN -> {
|
||||||
// ( subexpr )
|
// ( subexpr )
|
||||||
parseExpression(tokens)?.also {
|
parseExpression(tokens)?.also {
|
||||||
@ -283,49 +274,9 @@ class Compiler {
|
|||||||
"val" -> parseVarDeclaration(id.value, false, tokens)
|
"val" -> parseVarDeclaration(id.value, false, tokens)
|
||||||
"var" -> parseVarDeclaration(id.value, true, tokens)
|
"var" -> parseVarDeclaration(id.value, true, tokens)
|
||||||
"fn", "fun" -> parseFunctionDeclaration(tokens)
|
"fn", "fun" -> parseFunctionDeclaration(tokens)
|
||||||
"if" -> parseIfStatement(tokens)
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseIfStatement(tokens: ListIterator<Token>): Statement {
|
|
||||||
var t = tokens.next()
|
|
||||||
val start = t.pos
|
|
||||||
if( t.type != Token.Type.LPAREN)
|
|
||||||
throw ScriptError(t.pos, "Bad if statement: expected '('")
|
|
||||||
|
|
||||||
val condition = parseExpression(tokens)
|
|
||||||
?: throw ScriptError(t.pos, "Bad if statement: expected expression")
|
|
||||||
|
|
||||||
t = tokens.next()
|
|
||||||
if( t.type != Token.Type.RPAREN)
|
|
||||||
throw ScriptError(t.pos, "Bad if statement: expected ')' after condition expression")
|
|
||||||
|
|
||||||
val ifBody = parseStatement(tokens) ?: throw ScriptError(t.pos, "Bad if statement: expected statement")
|
|
||||||
|
|
||||||
// could be else block:
|
|
||||||
val t2 = tokens.next()
|
|
||||||
|
|
||||||
// we generate different statements: optimization
|
|
||||||
return if( t2.type == Token.Type.ID && t2.value == "else") {
|
|
||||||
val elseBody = parseStatement(tokens) ?: throw ScriptError(t.pos, "Bad else statement: expected statement")
|
|
||||||
return statement(start) {
|
|
||||||
if (condition.execute(it).toBool())
|
|
||||||
ifBody.execute(it)
|
|
||||||
else
|
|
||||||
elseBody.execute(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tokens.previous()
|
|
||||||
statement(start) {
|
|
||||||
if (condition.execute(it).toBool())
|
|
||||||
ifBody.execute(it)
|
|
||||||
else
|
|
||||||
ObjVoid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FnParamDef(
|
data class FnParamDef(
|
||||||
val name: String,
|
val name: String,
|
||||||
val pos: Pos,
|
val pos: Pos,
|
||||||
@ -364,6 +315,8 @@ class Compiler {
|
|||||||
params.add(FnParamDef(t.value, t.pos, defaultValue))
|
params.add(FnParamDef(t.value, t.pos, defaultValue))
|
||||||
} while (true)
|
} while (true)
|
||||||
|
|
||||||
|
println("arglist: $params")
|
||||||
|
|
||||||
// Here we should be at open body
|
// Here we should be at open body
|
||||||
val fnStatements = parseBlock(tokens)
|
val fnStatements = parseBlock(tokens)
|
||||||
|
|
||||||
@ -396,11 +349,7 @@ class Compiler {
|
|||||||
val t = tokens.next()
|
val t = tokens.next()
|
||||||
if (t.type != Token.Type.LBRACE)
|
if (t.type != Token.Type.LBRACE)
|
||||||
throw ScriptError(t.pos, "Expected block body start: {")
|
throw ScriptError(t.pos, "Expected block body start: {")
|
||||||
val block = parseScript(t.pos, tokens)
|
return parseScript(t.pos, tokens).also {
|
||||||
return statement(t.pos) {
|
|
||||||
// block run on inner context:
|
|
||||||
block.execute(it.copy())
|
|
||||||
}.also {
|
|
||||||
val t1 = tokens.next()
|
val t1 = tokens.next()
|
||||||
if (t1.type != Token.Type.RBRACE)
|
if (t1.type != Token.Type.RBRACE)
|
||||||
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
|
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
|
||||||
@ -416,7 +365,7 @@ class Compiler {
|
|||||||
var setNull = false
|
var setNull = false
|
||||||
if (eqToken.type != Token.Type.ASSIGN) {
|
if (eqToken.type != Token.Type.ASSIGN) {
|
||||||
if (!mutable)
|
if (!mutable)
|
||||||
throw ScriptError(eqToken.pos, "Expected initializer: '=' after '$kind ${name}'")
|
throw ScriptError(eqToken.pos, "Expected initializator: '=' after '$kind ${name}'")
|
||||||
else {
|
else {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
setNull = true
|
setNull = true
|
||||||
|
@ -52,14 +52,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
'+' -> Token("+", from, Token.Type.PLUS)
|
'+' -> Token("+", from, Token.Type.PLUS)
|
||||||
'-' -> Token("-", from, Token.Type.MINUS)
|
'-' -> Token("-", from, Token.Type.MINUS)
|
||||||
'*' -> Token("*", from, Token.Type.STAR)
|
'*' -> Token("*", from, Token.Type.STAR)
|
||||||
'/' -> {
|
'/' -> Token("/", from, Token.Type.SLASH)
|
||||||
if( currentChar == '/') {
|
|
||||||
advance()
|
|
||||||
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Token("/", from, Token.Type.SLASH)
|
|
||||||
}
|
|
||||||
'%' -> Token("%", from, Token.Type.PERCENT)
|
'%' -> Token("%", from, Token.Type.PERCENT)
|
||||||
'.' -> Token(".", from, Token.Type.DOT)
|
'.' -> Token(".", from, Token.Type.DOT)
|
||||||
'<' -> {
|
'<' -> {
|
||||||
@ -213,31 +206,6 @@ private class Parser(fromPos: Pos) {
|
|||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
private fun loadUntil(endChars: Set<Char>): String {
|
|
||||||
return if (pos.end) ""
|
|
||||||
else {
|
|
||||||
val result = StringBuilder()
|
|
||||||
while (!pos.end) {
|
|
||||||
val ch = pos.currentChar
|
|
||||||
if (ch in endChars) break
|
|
||||||
result.append(ch)
|
|
||||||
pos.advance()
|
|
||||||
}
|
|
||||||
result.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadToEnd(): String {
|
|
||||||
val result = StringBuilder()
|
|
||||||
val l = pos.line
|
|
||||||
do {
|
|
||||||
result.append(pos.currentChar)
|
|
||||||
advance()
|
|
||||||
} while (pos.line == l)
|
|
||||||
return result.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* next non-whitespace char (newline are skipped too) or null if EOF
|
* next non-whitespace char (newline are skipped too) or null if EOF
|
||||||
*/
|
*/
|
||||||
|
@ -8,7 +8,6 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
PLUS, MINUS, STAR, SLASH, ASSIGN,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE,
|
EQ, NEQ, LT, LTE, GT, GTE,
|
||||||
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
AND, BITAND, OR, BITOR, NOT, DOT, ARROW, QUESTION, COLONCOLON, PERCENT,
|
||||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
|
||||||
EOF,
|
EOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class PlusStatement(
|
|||||||
val l = left.execute(context)
|
val l = left.execute(context)
|
||||||
|
|
||||||
if (l is ObjString)
|
if (l is ObjString)
|
||||||
return ObjString(l.toString() + right.execute(context).asStr)
|
return ObjString(l.toString() + right.execute(context).toString())
|
||||||
|
|
||||||
if (l !is Numeric)
|
if (l !is Numeric)
|
||||||
raise("left operand is not number: $l")
|
raise("left operand is not number: $l")
|
||||||
|
@ -8,7 +8,7 @@ import kotlin.test.*
|
|||||||
class ScriptTest {
|
class ScriptTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseNumbersTest() {
|
fun parseNumbers() {
|
||||||
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
||||||
val source = src.toSource()
|
val source = src.toSource()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -48,7 +48,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parse0Test() {
|
fun parse0() {
|
||||||
val src = """
|
val src = """
|
||||||
println("Hello")
|
println("Hello")
|
||||||
println( "world" )
|
println( "world" )
|
||||||
@ -68,7 +68,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parse1Test() {
|
fun parse1() {
|
||||||
val src = "2 + 7".toSource()
|
val src = "2 + 7".toSource()
|
||||||
|
|
||||||
val p = parseLing(src).listIterator()
|
val p = parseLing(src).listIterator()
|
||||||
@ -79,7 +79,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun compileNumbersTest() = runTest {
|
fun compileNumbers() = runTest {
|
||||||
assertEquals(ObjInt(17), eval("17"))
|
assertEquals(ObjInt(17), eval("17"))
|
||||||
assertEquals(ObjInt(17), eval("+17"))
|
assertEquals(ObjInt(17), eval("+17"))
|
||||||
assertEquals(ObjInt(-17), eval("-17"))
|
assertEquals(ObjInt(-17), eval("-17"))
|
||||||
@ -95,7 +95,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun compileBuiltinCallsTest() = runTest {
|
fun compileBuiltinCalls() = runTest {
|
||||||
// println(eval("π"))
|
// println(eval("π"))
|
||||||
val pi = eval("Math.PI")
|
val pi = eval("Math.PI")
|
||||||
assertIs<ObjReal>(pi)
|
assertIs<ObjReal>(pi)
|
||||||
@ -107,7 +107,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun varsAndConstsTest() = runTest {
|
fun varsAndConsts() = runTest {
|
||||||
val context = Context()
|
val context = Context()
|
||||||
assertEquals(ObjVoid,context.eval("""
|
assertEquals(ObjVoid,context.eval("""
|
||||||
val a = 17
|
val a = 17
|
||||||
@ -171,7 +171,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmeticOperatorsTest() = runTest {
|
fun testArithmeticOperators() = runTest {
|
||||||
assertEquals(2, eval("5/2").toInt())
|
assertEquals(2, eval("5/2").toInt())
|
||||||
assertEquals(2.5, eval("5.0/2").toDouble())
|
assertEquals(2.5, eval("5.0/2").toDouble())
|
||||||
assertEquals(2.5, eval("5/2.0").toDouble())
|
assertEquals(2.5, eval("5/2.0").toDouble())
|
||||||
@ -190,7 +190,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arithmeticParenthesisTest() = runTest {
|
fun testArithmeticParenthesis() = runTest {
|
||||||
assertEquals(17, eval("2 + 3 * 5").toInt())
|
assertEquals(17, eval("2 + 3 * 5").toInt())
|
||||||
assertEquals(17, eval("2 + (3 * 5)").toInt())
|
assertEquals(17, eval("2 + (3 * 5)").toInt())
|
||||||
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
||||||
@ -198,13 +198,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun stringOpTest() = runTest {
|
fun testEqNeq() = runTest {
|
||||||
assertEquals("foobar", eval(""" "foo" + "bar" """).toString())
|
|
||||||
assertEquals("foo17", eval(""" "foo" + 17 """).toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun eqNeqTest() = runTest {
|
|
||||||
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
||||||
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
||||||
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
||||||
@ -220,7 +214,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun gtLtTest() = runTest {
|
fun testGtLt() = runTest {
|
||||||
assertTrue { eval("3 > 2").toBool() }
|
assertTrue { eval("3 > 2").toBool() }
|
||||||
assertFalse { eval("3 > 3").toBool() }
|
assertFalse { eval("3 > 3").toBool() }
|
||||||
assertTrue { eval("3 >= 2").toBool() }
|
assertTrue { eval("3 >= 2").toBool() }
|
||||||
@ -234,68 +228,5 @@ class ScriptTest {
|
|||||||
assertFalse { eval("4 <= 3").toBool()}
|
assertFalse { eval("4 <= 3").toBool()}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ifTest() = runTest {
|
|
||||||
// if - single line
|
|
||||||
var context = Context()
|
|
||||||
context.eval("""
|
|
||||||
fn test1(n) {
|
|
||||||
var result = "more"
|
|
||||||
if( n >= 10 )
|
|
||||||
result = "enough"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
""".trimIndent())
|
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
|
||||||
|
|
||||||
// if - multiline (block)
|
|
||||||
context = Context()
|
|
||||||
context.eval("""
|
|
||||||
fn test1(n) {
|
|
||||||
var prefix = "answer: "
|
|
||||||
var result = "more"
|
|
||||||
if( n >= 10 ) {
|
|
||||||
var prefix = "bad:" // local prefix
|
|
||||||
prefix = "too bad:"
|
|
||||||
result = "enough"
|
|
||||||
}
|
|
||||||
prefix + result
|
|
||||||
}
|
|
||||||
""".trimIndent())
|
|
||||||
assertEquals("answer: enough", context.eval("test1(11)").toString())
|
|
||||||
assertEquals("answer: more", context.eval("test1(1)").toString())
|
|
||||||
|
|
||||||
// else single line1
|
|
||||||
context = Context()
|
|
||||||
context.eval("""
|
|
||||||
fn test1(n) {
|
|
||||||
if( n >= 10 )
|
|
||||||
"enough"
|
|
||||||
else
|
|
||||||
"more"
|
|
||||||
}
|
|
||||||
""".trimIndent())
|
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
|
||||||
|
|
||||||
// if/else with blocks
|
|
||||||
context = Context()
|
|
||||||
context.eval("""
|
|
||||||
fn test1(n) {
|
|
||||||
if( n > 20 ) {
|
|
||||||
"too much"
|
|
||||||
} else if( n >= 10 ) {
|
|
||||||
"enough"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
"more"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent())
|
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
|
||||||
assertEquals("too much", context.eval("test1(100)").toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user