From 792d08eae72d11bd5ad774cbd3c14f92a3d03204 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 23 Jun 2021 01:18:15 -0300 Subject: [PATCH] Move fiber functions to module Fiber (#113) --- docs/TODO.txt | 3 +- .../Getting-Started/learn-in-15-minutes.md | 8 +- docs/pages/Language-API/fibers.md | 44 +++-- src/pk_core.c | 170 +++++++++--------- tests/lang/fibers.pk | 28 +-- 5 files changed, 128 insertions(+), 125 deletions(-) diff --git a/docs/TODO.txt b/docs/TODO.txt index f6ece8f..a23b42c 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -1,7 +1,6 @@ // In good first issue -- Refactor fiber into a module and is_done as an attribute. -- floats like ".5". +- Refactor is_done as an attribute. - Implement argparse. // To implement. diff --git a/docs/pages/Getting-Started/learn-in-15-minutes.md b/docs/pages/Getting-Started/learn-in-15-minutes.md index 08cbc1e..4537d93 100644 --- a/docs/pages/Getting-Started/learn-in-15-minutes.md +++ b/docs/pages/Getting-Started/learn-in-15-minutes.md @@ -78,12 +78,14 @@ str_lower(str_strip('Foo ')) # This can be written as below # Fibers & Coroutine #------------------- +import Fiber + def fn(p1, p2) print(yield(42)) # Prints 3.14 end -fb = fiber_new(fn) -val = fiber_run(fb, 1, 2) +fb = Fiber.new(fn) +val = Fiber.run(fb, 1, 2) print(val) ## Prints 42 -fiber_resume(fb, 3.14) +Fiber.resume(fb, 3.14) ``` diff --git a/docs/pages/Language-API/fibers.md b/docs/pages/Language-API/fibers.md index 865fd09..c705b51 100644 --- a/docs/pages/Language-API/fibers.md +++ b/docs/pages/Language-API/fibers.md @@ -8,12 +8,14 @@ instruction pointer) for that function, which can be run and once yielded resumed. ```ruby +import Fiber + def fn(h, w) print(h, w) end -fb = fiber_new(fn) # Create a fiber. -fiber_run(fb, 'hello', 'world') # Run the fiber. +fb = Fiber.new(fn) # Create a fiber. +Fiber.run(fb, 'hello', 'world') # Run the fiber. ``` ## %% Yielding %% @@ -24,36 +26,42 @@ from (not the caller of the function). And you can pass values between fibers when they yield. ```ruby +import Fiber + def fn() print('running') yield() print('resumed') end -fb = fiber_new(fn) -fiber_run(fb) # Prints 'running'. +fb = Fiber.new(fn) +Fiber.run(fb) # Prints 'running'. print('before resumed') -fiber_resume(fb) # Prints 'resumed'. +Fiber.resume(fb) # Prints 'resumed'. ``` Yield from the fiber with a value. ```ruby +import Fiber + def fn() print('running') yield(42) # Return 42. print('resumed') end -fb = fiber_new(fn) -val = fiber_run(fb) # Prints 'running'. +fb = Fiber.new(fn) +val = Fiber.run(fb) # Prints 'running'. print(val) # Prints 42. -fiber_resume(fb) # Prints 'resumed'. +Fiber.resume(fb) # Prints 'resumed'. ``` Resume the fiber with a value. ```ruby +import Fiber + def fn() print('running') val = yield() # Resumed value. @@ -61,28 +69,30 @@ def fn() print('resumed') end -fb = fiber_new(fn) -val = fiber_run(fb) # Prints 'running'. -fiber_resume(fb, 42) # Resume with 42, Prints 'resumed'. +fb = Fiber.new(fn) +val = Fiber.run(fb) # Prints 'running'. +Fiber.resume(fb, 42) # Resume with 42, Prints 'resumed'. ``` Once a fiber is done execution, trying to resume it will cause a runtime -error. To check if the fiber is finished it's execution use the function -`fiber_is_done` and use the function `fiber_get_func` to get it's function, +error. To check if the fiber is finished check its attribute +`is_done` and use the attribute `function` to get it's function, which could be used to create a new fiber to "re-start" the fiber. ```ruby -fiber_run(fb = fiber_new( +import Fiber + +Fiber.run(fb = Fiber.new( func() for i in 0..5 do yield(i) end end)) -while not fiber_is_done(fb) - fiber_resume(fb) +while not fb.is_done + Fiber.resume(fb) end # Get the function from the fiber. -fn = fiber_get_func(fb) +fn = Fiber.get_func(fb) ``` diff --git a/src/pk_core.c b/src/pk_core.c index 612caf1..1f01ad5 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -663,87 +663,6 @@ DEF(coreMapRemove, RET(mapRemoveKey(vm, map, key)); } -// Fiber functions. -// ---------------- - -DEF(coreFiberNew, - "fiber_new(fn:Function) -> fiber\n" - "Create and return a new fiber from the given function [fn].") { - - Function* fn; - if (!validateArgFunction(vm, 1, &fn)) return; - RET(VAR_OBJ(newFiber(vm, fn))); -} - -DEF(coreFiberGetFunc, - "fiber_get_func(fb:Fiber) -> function\n" - "Retruns the fiber's functions. Which is usefull if you want to re-run the " - "fiber, you can get the function and crate a new fiber.") { - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - RET(VAR_OBJ(fb->func)); -} - -DEF(coreFiberIsDone, - "fiber_is_done(fb:Fiber) -> bool\n" - "Returns true if the fiber [fb] is done running and can no more resumed.") { - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - RET(VAR_BOOL(fb->state == FIBER_DONE)); -} - -DEF(coreFiberRun, - "fiber_run(fb:Fiber, ...) -> var\n" - "Runs the fiber's function with the provided arguments and returns it's " - "return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Missing argument - fiber.")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - // Buffer of argument to call vmPrepareFiber(). - Var* args[MAX_ARGC]; - - // ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). - for (int i = 1; i < argc; i++) { - args[i - 1] = &ARG(i + 1); - } - - // Switch fiber and start execution. - if (vmPrepareFiber(vm, fb, argc - 1, args)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - -DEF(coreFiberResume, - "fiber_resume(fb:Fiber) -> var\n" - "Resumes a yielded function from a previous call of fiber_run() function. " - "Return it's return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Expected at least 1 argument(s).")); - if (argc > 2) // Can only accept 1 argument for resume. - RET_ERR(newString(vm, "Expected at most 2 argument(s).")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - Var value = (argc == 1) ? VAR_NULL : ARG(2); - - // Switch fiber and resume execution. - if (vmSwitchFiber(vm, fb, &value)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - /*****************************************************************************/ /* CORE MODULE METHODS */ /*****************************************************************************/ @@ -822,6 +741,65 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script, // 'lang' library methods. // ----------------------- +DEF(stdFiberNew, + "new(fn:Function) -> fiber\n" + "Create and return a new fiber from the given function [fn].") { + + Function* fn; + if (!validateArgFunction(vm, 1, &fn)) return; + RET(VAR_OBJ(newFiber(vm, fn))); +} + +DEF(stdFiberRun, + "run(fb:Fiber, ...) -> var\n" + "Runs the fiber's function with the provided arguments and returns it's " + "return value or the yielded value if it's yielded.") { + + int argc = ARGC; + if (argc == 0) // Missing the fiber argument. + RET_ERR(newString(vm, "Missing argument - fiber.")); + + Fiber* fb; + if (!validateArgFiber(vm, 1, &fb)) return; + + // Buffer of argument to call vmPrepareFiber(). + Var* args[MAX_ARGC]; + + // ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). + for (int i = 1; i < argc; i++) { + args[i - 1] = &ARG(i + 1); + } + + // Switch fiber and start execution. + if (vmPrepareFiber(vm, fb, argc - 1, args)) { + ASSERT(fb == vm->fiber, OOPS); + fb->state = FIBER_RUNNING; + } +} + +DEF(stdFiberResume, + "fiber_resume(fb:Fiber) -> var\n" + "Resumes a yielded function from a previous call of fiber_run() function. " + "Return it's return value or the yielded value if it's yielded.") { + + int argc = ARGC; + if (argc == 0) // Missing the fiber argument. + RET_ERR(newString(vm, "Expected at least 1 argument(s).")); + if (argc > 2) // Can only accept 1 argument for resume. + RET_ERR(newString(vm, "Expected at most 2 argument(s).")); + + Fiber* fb; + if (!validateArgFiber(vm, 1, &fb)) return; + + Var value = (argc == 1) ? VAR_NULL : ARG(2); + + // Switch fiber and resume execution. + if (vmSwitchFiber(vm, fb, &value)) { + ASSERT(fb == vm->fiber, OOPS); + fb->state = FIBER_RUNNING; + } +} + DEF(stdLangClock, "clock() -> num\n" "Returns the number of seconds since the application started") { @@ -1038,13 +1016,6 @@ void initializeCore(PKVM* vm) { // Map functions. INITIALIZE_BUILTIN_FN("map_remove", coreMapRemove, 2); - // Fiber functions. - INITIALIZE_BUILTIN_FN("fiber_new", coreFiberNew, 1); - INITIALIZE_BUILTIN_FN("fiber_get_func", coreFiberGetFunc, 1); - INITIALIZE_BUILTIN_FN("fiber_run", coreFiberRun, -1); - INITIALIZE_BUILTIN_FN("fiber_is_done", coreFiberIsDone, 1); - INITIALIZE_BUILTIN_FN("fiber_resume", coreFiberResume, -1); - // Core Modules ///////////////////////////////////////////////////////////// Script* lang = newModuleInternal(vm, "lang"); @@ -1070,6 +1041,11 @@ void initializeCore(PKVM* vm) { // TODO: low priority - sinh, cosh, tanh, asin, acos, atan. + Script* Fiber = newModuleInternal(vm, "Fiber"); + MODULE_ADD_FN(Fiber, "new", stdFiberNew, 1); + MODULE_ADD_FN(Fiber, "run", stdFiberRun, -1); + MODULE_ADD_FN(Fiber, "resume", stdFiberResume, -1); + // Note that currently it's mutable (since it's a global variable, not // constant and pocketlang doesn't support constant) so the user shouldn't // modify the PI, like in python. @@ -1459,8 +1435,22 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { } case OBJ_FIBER: - TODO; - UNREACHABLE(); + { + Fiber* fb = (Fiber*)obj; + SWITCH_ATTRIB(attrib->data) { + + CASE_ATTRIB("is_done", 0x789c2706): + return VAR_BOOL(fb->state == FIBER_DONE); + + CASE_ATTRIB("function", 0x9ed64249): + return VAR_OBJ(fb->func); + + CASE_DEFAULT: + ERR_NO_ATTRIB(vm, on, attrib); + return VAR_NULL; + } + UNREACHABLE(); + } case OBJ_CLASS: TODO; diff --git a/tests/lang/fibers.pk b/tests/lang/fibers.pk index cfce5a4..aacaa2e 100644 --- a/tests/lang/fibers.pk +++ b/tests/lang/fibers.pk @@ -3,19 +3,21 @@ def f0() yield("yield value") end -fiber = fiber_new(f0) -yield_value = fiber_run(fiber) +import Fiber + +fiber = Fiber.new(f0) +yield_value = Fiber.run(fiber) assert(yield_value == "yield value") -assert(!fiber_is_done(fiber)) +assert(!fiber.is_done) def f1() assert(yield("y") == "r") yield() end -fiber = fiber_new(f1) -assert(fiber_run(fiber) == "y") -fiber_resume(fiber, "r") -assert(!fiber_is_done(fiber)) +fiber = Fiber.new(f1) +assert(Fiber.run(fiber) == "y") +Fiber.resume(fiber, "r") +assert(!fiber.is_done) def f2(p1, p2, p3) r1 = yield(p3); assert(r1 == 'r1') @@ -25,12 +27,12 @@ def f2(p1, p2, p3) return p1 + p2 * p3 end -fiber = fiber_new(f2) -p3 = fiber_run(fiber, 1, 2, 3); assert(p3 == 3) -p2 = fiber_resume(fiber, 'r1'); assert(p2 == 2) -p1 = fiber_resume(fiber, 'r2'); assert(p1 == 1) -pa = fiber_resume(fiber, 'r3'); assert(pa == 7) -assert(fiber_is_done(fiber)) +fiber = Fiber.new(f2) +p3 = Fiber.run(fiber, 1, 2, 3); assert(p3 == 3) +p2 = Fiber.resume(fiber, 'r1'); assert(p2 == 2) +p1 = Fiber.resume(fiber, 'r2'); assert(p1 == 1) +pa = Fiber.resume(fiber, 'r3'); assert(pa == 7) +assert(fiber.is_done) # If we got here, that means all test were passed. print('All TESTS PASSED')