From f3276eb54b748ba6f9f9afe1f3ae28f1fbed6c33 Mon Sep 17 00:00:00 2001 From: Kefniark Date: Wed, 5 May 2021 12:47:01 +0900 Subject: [PATCH 1/4] Add while & infinite loops --- _sidebar.md | 3 +- docs/grammar_loop.md | 37 +++++++++++++++++++++++ samples/vec/main.ako | 4 +-- src/ako_grammar.txt | 2 +- src/elements/loop.ts | 70 +++++++++++++++++++++++++++++++++++++++++--- tests/loop.test.ts | 43 ++++++++++++++++++++------- 6 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 docs/grammar_loop.md diff --git a/_sidebar.md b/_sidebar.md index c30b93c..92533d5 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -1,6 +1,7 @@ * [Home](/) * [Grammar](./docs/grammar_basic.md) * [Basic](./docs/grammar_basic.md) + * [Loop](./docs/grammar_loop.md) * [Task](./docs/grammar_task.md) * [Interpreter](./docs/interpreter_basic.md) - * [Basic](./docs/interpreter_basic.md) \ No newline at end of file + * [Basic](./docs/interpreter_basic.md) diff --git a/docs/grammar_loop.md b/docs/grammar_loop.md new file mode 100644 index 0000000..8c7e889 --- /dev/null +++ b/docs/grammar_loop.md @@ -0,0 +1,37 @@ +# Loop + +In Ako there is only one loop `For` but with 3 different behaviours: + +## Iterate List +To iterate a list of elements, the syntax is `for [val] in [list] [block]` +```js +for a in [1,2,3,4,5,6,7,8] { + @print("{a}") +} +``` +you can also have the iteration index +```js +for val, index in [1,2,3,4,5,6,7,8] { + @print("{index} = {val}") +} +``` + +## Until condition +This is commonly named `until` or `while` loop in other language. + +The syntax is `for [condition] [block]` +```js +for counter < 10 { + counter += 1 +} +``` + +## Infinite Loop +This is an infinite loop, make sure to put some + +The syntax is `for [block]` +```js +for { + counter += 1 +} +``` diff --git a/samples/vec/main.ako b/samples/vec/main.ako index c6814dc..fbb6deb 100644 --- a/samples/vec/main.ako +++ b/samples/vec/main.ako @@ -1,8 +1,8 @@ -for countdown := [5,4,3,2,1,0] { +for countdown in [5,4,3,2,1,0] { if countdown > 0 { @print("{countdown} !") @sleep(0.1) } else { @print("Countdown Finish !") } -} \ No newline at end of file +} diff --git a/src/ako_grammar.txt b/src/ako_grammar.txt index 3cf4f6c..ac2cac6 100644 --- a/src/ako_grammar.txt +++ b/src/ako_grammar.txt @@ -64,7 +64,7 @@ Ako { // Loop ForLoop = Foreach | While | Infinite | Continue | Return - Foreach = "for" id ("," id)? ":=" Expr Block + Foreach = "for" id ("," id)? "in" Expr Block While = "for" Expr Block Infinite = "for" Block Continue = "continue" diff --git a/src/elements/loop.ts b/src/elements/loop.ts index 72cf32f..2d3a36f 100644 --- a/src/elements/loop.ts +++ b/src/elements/loop.ts @@ -1,4 +1,11 @@ import {Context} from '../core' +import {Number} from './scalar' + +export interface LoopWhileData { + type: string + cond: any + block: any +} export interface LoopForData { type: string @@ -12,12 +19,70 @@ export interface LoopForData { export const LoopInfinite = { create: (block) => { return {type: 'LoopInfinite', block} + }, + initialize: (ctx: Context, entry: LoopWhileData, entryData: any) => { + entry.cond = Number.create(1) + }, + execute: (ctx: Context, entry: LoopWhileData, entryData: any, timeRemains: number) => { + // Initialize + if (!entryData.meta) LoopInfinite.initialize(ctx, entry, entryData) + return LoopWhile.execute(ctx, entry, entryData, timeRemains) } } export const LoopWhile = { create: (cond, block) => { return {type: 'LoopWhile', cond, block} + }, + initialize: (ctx: Context, entry: LoopWhileData, entryData: any) => { + entryData.meta = { + block: 0, + cond: ctx.vm.evaluate(ctx, entry.cond, true) + } + + console.log('WHILE LOOP', entryData.meta.cond) + if (!entryData.meta.cond) return + const block = ctx.vm.createStack(entry.block.statements, ctx.stack.parent ? ctx.stack.parent : ctx.stack.uid) + entryData.meta.block = block.uid + }, + next: (ctx: Context, entry: LoopWhileData, entryData: any, timeRemains: number) => { + if (timeRemains <= 0) return + + entryData.meta.cond = ctx.vm.evaluate(ctx, entry.cond, true) + console.log('WHILE LOOP', entryData.meta.cond) + if (!entryData.meta.cond) return + const block = ctx.vm.createStack(entry.block.statements, ctx.stack.parent ? ctx.stack.parent : ctx.stack.uid) + entryData.meta.block = block.uid + }, + execute: (ctx: Context, entry: LoopWhileData, entryData: any, timeRemains: number) => { + // Initialize + if (!entryData.meta) LoopWhile.initialize(ctx, entry, entryData) + + // Iterate + while (timeRemains > 0 && entryData.meta.cond) { + const stack = ctx.vm.stacks.get(entryData.meta.block) + + if (!stack.done) { + const res = ctx.vm.updateStack(stack, timeRemains, true) + timeRemains = res.timeRemains + + if ('continue' in stack && stack.continue) { + LoopWhile.next(ctx, entry, entryData, timeRemains) + stack.continue = false + continue + } + + if (res.done && 'result' in stack) { + ctx.vm.callReturn(ctx, stack.result) + return {timeRemains, done: true} + } + } + + // Prepare next loop + if (stack.done) LoopWhile.next(ctx, entry, entryData, timeRemains) + } + + return {timeRemains, done: !entryData.meta.cond} } } @@ -86,10 +151,7 @@ export const LoopFor = { } } - return { - timeRemains, - done: entryData.meta.index >= entryData.meta.iterator.length - } + return {timeRemains, done: entryData.meta.index >= entryData.meta.iterator.length} } } diff --git a/tests/loop.test.ts b/tests/loop.test.ts index 6b82000..6872fa7 100644 --- a/tests/loop.test.ts +++ b/tests/loop.test.ts @@ -5,7 +5,7 @@ describe('Loop', function () { it('Simple loop', () => { const {stack} = runCode(` b = 38 -for a := [1,2,3,4,5,6,7,8] { +for a in [1,2,3,4,5,6,7,8] { b -= a } `) @@ -15,9 +15,9 @@ for a := [1,2,3,4,5,6,7,8] { it('Nested Loop', () => { const {stack} = runCode(` counter = 0 -for a := [1,2] { - for b := [3,4] { - for c, index := [1,2,3,4] { +for a in [1,2] { + for b in [3,4] { + for c, index in [1,2,3,4] { counter += c } counter += b @@ -31,9 +31,9 @@ for a := [1,2] { it('Nested Loop with return', () => { const {stack} = runCode(` counter = 0 -for a := [1,2] { - for b := [3,4] { - for c, index := [1,2,3,4] { +for a in [1,2] { + for b in [3,4] { + for c, index in [1,2,3,4] { counter += c if counter >= 3 { return @@ -50,9 +50,9 @@ for a := [1,2] { it('Nested Loop with continue', () => { const {stack} = runCode(` counter = 0 -for a := [1,2] { - for b := [3,4] { - for c, index := [1,2,3,4] { +for a in [1,2] { + for b in [3,4] { + for c, index in [1,2,3,4] { if counter >= 3 { continue } @@ -66,12 +66,33 @@ for a := [1,2] { assert.strictEqual((stack.data as any)['counter'], 20) }) + it('Until Loop', () => { + const {stack} = runCode(` +counter = 0 +for counter < 10 { + counter += 1 +} + `) + assert.strictEqual((stack.data as any)['counter'], 10) + }) + + it('Infinite Loop', () => { + const {stack} = runCode(` +counter = 0 +for { + counter += 1 + if counter >= 10 { return } +} + `) + assert.strictEqual((stack.data as any)['counter'], 10) + }) + // TODO: Implement Infinite loop /* it('Check Index', () => { const {stack} = runCode(` b = 0 -for val, index := [1,2,3,4,5,6,7,8] { +for val, index in [1,2,3,4,5,6,7,8] { @print("this is {val} {index}") if index > 1 { return 0 } b += val From 8b6666f4a108547402077397981d4eee9515c883 Mon Sep 17 00:00:00 2001 From: Kefniark Date: Wed, 5 May 2021 12:50:11 +0900 Subject: [PATCH 2/4] Remove some console.log --- src/dist/ako-cli.ts | 2 +- src/elements/loop.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dist/ako-cli.ts b/src/dist/ako-cli.ts index 1a82b35..3bcae41 100644 --- a/src/dist/ako-cli.ts +++ b/src/dist/ako-cli.ts @@ -46,7 +46,7 @@ function loadAkoModule(vm: Interpreter, projectFolder: string) { const method = namespace ? `${namespace}.${fileId}` : fileId const match = grammar.match(`@${method}()`) const ast = ASTBuilder(match).toAST() - console.log('Create Stack >', fileId) + // console.log('Create Stack >', fileId) vm.createStack(ast) } } diff --git a/src/elements/loop.ts b/src/elements/loop.ts index 2d3a36f..57e5d3a 100644 --- a/src/elements/loop.ts +++ b/src/elements/loop.ts @@ -40,7 +40,6 @@ export const LoopWhile = { cond: ctx.vm.evaluate(ctx, entry.cond, true) } - console.log('WHILE LOOP', entryData.meta.cond) if (!entryData.meta.cond) return const block = ctx.vm.createStack(entry.block.statements, ctx.stack.parent ? ctx.stack.parent : ctx.stack.uid) entryData.meta.block = block.uid @@ -49,7 +48,6 @@ export const LoopWhile = { if (timeRemains <= 0) return entryData.meta.cond = ctx.vm.evaluate(ctx, entry.cond, true) - console.log('WHILE LOOP', entryData.meta.cond) if (!entryData.meta.cond) return const block = ctx.vm.createStack(entry.block.statements, ctx.stack.parent ? ctx.stack.parent : ctx.stack.uid) entryData.meta.block = block.uid From 3884c80ed390315b6f872bf516431a7c6092946d Mon Sep 17 00:00:00 2001 From: Kefniark Date: Wed, 5 May 2021 13:08:44 +0900 Subject: [PATCH 3/4] Add more unit tests --- tests/expr.test.ts | 28 ++++++++++++++++++++++++++++ tests/function.test.ts | 8 +++++--- tests/loop.test.ts | 15 --------------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/tests/expr.test.ts b/tests/expr.test.ts index f10fa35..1001066 100644 --- a/tests/expr.test.ts +++ b/tests/expr.test.ts @@ -1,6 +1,34 @@ import assert from 'assert' import {runCode} from './helper' +describe('Math Expression', () => { + it('Math Functions', () => { + const {stack: stack1} = runCode(` +a = Math.PI() +b = Math.max(1,2,12,2) +c = Math.min(1,2,12,2) +d = List.map([1,-2,3], (val) => Math.abs(val)) +e = List.map([1.2,-2.6,3.6], (val) => Math.ceil(val)) +f = List.map([1.2,-2.6,3.6], (val) => Math.floor(val)) + `) + assert.strictEqual((stack1.data as any)['a'], Math.PI) + assert.strictEqual((stack1.data as any)['b'], 12) + assert.strictEqual((stack1.data as any)['c'], 1) + assert.deepStrictEqual((stack1.data as any)['d'], [1, 2, 3]) + assert.deepStrictEqual((stack1.data as any)['e'], [2, -2, 4]) + assert.deepStrictEqual((stack1.data as any)['f'], [1, -3, 3]) + }) + + it('Degree Functions', () => { + const {stack: stack1} = runCode(` +a = List.map([0, 45, 90, 180], (val) => Angle.toRad(val)) +b = List.map([0, 45, 90, 180], (val) => Angle.toDeg(Angle.toRad(val))) + `) + assert.deepStrictEqual((stack1.data as any)['a'], [0, Math.PI / 4, Math.PI / 2, Math.PI]) + assert.deepStrictEqual((stack1.data as any)['b'], [0, 45, 90, 180]) + }) +}) + describe('Equality Expression', () => { it('Equality', () => { const {stack: stack1} = runCode(` diff --git a/tests/function.test.ts b/tests/function.test.ts index f5ddbe5..9dc80a2 100644 --- a/tests/function.test.ts +++ b/tests/function.test.ts @@ -12,16 +12,18 @@ a = Math.max(1,2,3,4,5) it('Array Function', () => { const {stack} = runCode(` -list = [1,5,2,3,4] +list = [1,5,2,3,4,1] sorted = List.sort(list) sorted2 = List.sort(list, (a, b) => b - a) filtered = List.filter(sorted2, (a) => a >= 3) rev = List.reverse(filtered) +rev2 = List.map(filtered, (a) => a * 2) `) - assert.deepStrictEqual((stack.data as any)['sorted'], [1, 2, 3, 4, 5]) - assert.deepStrictEqual((stack.data as any)['sorted2'], [5, 4, 3, 2, 1]) + assert.deepStrictEqual((stack.data as any)['sorted'], [1, 1, 2, 3, 4, 5]) + assert.deepStrictEqual((stack.data as any)['sorted2'], [5, 4, 3, 2, 1, 1]) assert.deepStrictEqual((stack.data as any)['filtered'], [5, 4, 3]) assert.deepStrictEqual((stack.data as any)['rev'], [3, 4, 5]) + assert.deepStrictEqual((stack.data as any)['rev2'], [10, 8, 6]) }) it('Vector', () => { diff --git a/tests/loop.test.ts b/tests/loop.test.ts index 6872fa7..309a904 100644 --- a/tests/loop.test.ts +++ b/tests/loop.test.ts @@ -86,19 +86,4 @@ for { `) assert.strictEqual((stack.data as any)['counter'], 10) }) - - // TODO: Implement Infinite loop - /* - it('Check Index', () => { - const {stack} = runCode(` -b = 0 -for val, index in [1,2,3,4,5,6,7,8] { - @print("this is {val} {index}") - if index > 1 { return 0 } - b += val -} - `) - assert.strictEqual((stack.data as any)['b'], 3) - }) - */ }) From ca32083c2ef878ffa221efa863fe91df4482b9f1 Mon Sep 17 00:00:00 2001 From: Kefniark Date: Wed, 5 May 2021 13:32:28 +0900 Subject: [PATCH 4/4] Fix issue with sleep in infinite loop --- src/elements/loop.ts | 11 ++++++----- tests/function.test.ts | 8 ++++++++ tests/loop.test.ts | 14 ++++++++++++++ tests/task.test.ts | 8 ++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/elements/loop.ts b/src/elements/loop.ts index 57e5d3a..aec3d27 100644 --- a/src/elements/loop.ts +++ b/src/elements/loop.ts @@ -57,10 +57,13 @@ export const LoopWhile = { if (!entryData.meta) LoopWhile.initialize(ctx, entry, entryData) // Iterate - while (timeRemains > 0 && entryData.meta.cond) { + while (timeRemains > 0 && !!entryData.meta.cond) { const stack = ctx.vm.stacks.get(entryData.meta.block) - if (!stack.done) { + // Prepare next loop + if (!stack || stack.done) LoopWhile.next(ctx, entry, entryData, timeRemains) + + if (stack && !stack.done) { const res = ctx.vm.updateStack(stack, timeRemains, true) timeRemains = res.timeRemains @@ -72,12 +75,10 @@ export const LoopWhile = { if (res.done && 'result' in stack) { ctx.vm.callReturn(ctx, stack.result) + entryData.meta.cond = false return {timeRemains, done: true} } } - - // Prepare next loop - if (stack.done) LoopWhile.next(ctx, entry, entryData, timeRemains) } return {timeRemains, done: !entryData.meta.cond} diff --git a/tests/function.test.ts b/tests/function.test.ts index 9dc80a2..b26920a 100644 --- a/tests/function.test.ts +++ b/tests/function.test.ts @@ -2,6 +2,14 @@ import assert from 'assert' import {runCode} from './helper' describe('Function', function () { + it('Unexisting Function', () => { + assert.throws(() => { + runCode(` +a = unknown(1,2,3,4,5) + `) + }) + }) + it('Check Expression Function', () => { const {stack} = runCode(` a = Math.max(1,2,3,4,5) diff --git a/tests/loop.test.ts b/tests/loop.test.ts index 309a904..a90b238 100644 --- a/tests/loop.test.ts +++ b/tests/loop.test.ts @@ -86,4 +86,18 @@ for { `) assert.strictEqual((stack.data as any)['counter'], 10) }) + + it('Infinite Loop with delay', () => { + const {vm, stack} = runCode(` +counter = 0 +for { + @print("{counter}") + counter += 1 + if counter >= 10 { return } + @sleep(1) +} + `) + vm.update(16) + assert.strictEqual((stack.data as any)['counter'], 10) + }) }) diff --git a/tests/task.test.ts b/tests/task.test.ts index 6c575d9..ddb9be5 100644 --- a/tests/task.test.ts +++ b/tests/task.test.ts @@ -2,6 +2,14 @@ import assert from 'assert' import {runCode, runFileCode} from './helper' describe('Task', () => { + it('Unexisting Task', () => { + assert.throws(() => { + runCode(` +a = @unknown(1,2,3,4,5) + `) + }) + }) + it('Create and run task', () => { const {stack} = runCode(` task Method1 {