Update.
This commit is contained in:
parent
e4a94d576b
commit
735596fdf6
3
Makefile
3
Makefile
@ -11,6 +11,9 @@ debug:
|
||||
tests: build
|
||||
python3 util/test.py
|
||||
|
||||
create_merge:
|
||||
wren apps/merge_all_wren.wren
|
||||
|
||||
create_docs:
|
||||
wren apps/merge_docs.wren
|
||||
|
||||
|
||||
146
apps/generator.wren
vendored
Executable file
146
apps/generator.wren
vendored
Executable file
@ -0,0 +1,146 @@
|
||||
#!/usr/local/bin/wren
|
||||
|
||||
import "pathlib" for Path
|
||||
import "http" for Http
|
||||
import "json" for Json
|
||||
import "env" for Environment
|
||||
import "argparse" for ArgumentParser
|
||||
import "os" for Process
|
||||
|
||||
class SourceCodeGenerator {
|
||||
static VERSION { "1.0" }
|
||||
static DEFAULT_MODEL { "anthropic/claude-haiku-4.5" }
|
||||
static API_ENDPOINT { "https://openrouter.ai/api/v1/chat/completions" }
|
||||
static TUTORIAL_PATH { "apps/minitut.md" }
|
||||
|
||||
static DEFAULT_PROMPT {
|
||||
"A small but useful application that combines several modules / libraries that is plug and play to use! Small description on top commenten what the application is and what it does and where it can be used for."
|
||||
}
|
||||
|
||||
static run() {
|
||||
System.print("// wren source code generator v%(this.VERSION)")
|
||||
|
||||
var startTime = System.clock
|
||||
var args = parseArguments()
|
||||
var apiKey = getApiKey()
|
||||
var tutorial = loadTutorial()
|
||||
var messages = buildMessages(tutorial, args["prompt"])
|
||||
var sourceCode = generateCode(apiKey, messages)
|
||||
|
||||
printResults(startTime, sourceCode)
|
||||
saveOutput(args["output"], sourceCode)
|
||||
}
|
||||
|
||||
static parseArguments() {
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("prompt", {"default": this.DEFAULT_PROMPT})
|
||||
parser.addArgument("-o", {"long": "--output", "default": ""})
|
||||
return parser.parseArgs()
|
||||
}
|
||||
|
||||
static getApiKey() {
|
||||
var apiKey = Environment.get("OPENROUTER_API_KEY")
|
||||
if (apiKey == null || apiKey.count == 0) {
|
||||
System.print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
Fiber.abort("Missing API key")
|
||||
}
|
||||
return apiKey
|
||||
}
|
||||
|
||||
static loadTutorial() {
|
||||
return Path.new(this.TUTORIAL_PATH).readText()
|
||||
}
|
||||
|
||||
static buildMessages(tutorial, userPrompt) {
|
||||
var messages = []
|
||||
messages.add({
|
||||
"role": "system",
|
||||
"content": "You are an application generator that will produce wren source code exclusively as described below:\n" + tutorial
|
||||
})
|
||||
messages.add({
|
||||
"role": "user",
|
||||
"content": "Please generate source code based on everything what i say from now on."
|
||||
})
|
||||
messages.add({
|
||||
"role": "assistant",
|
||||
"content": "I will respond in literally valid JSON only in this format: {\"source_code\":\"<the source code>\"} without any markup or markdown formatting."
|
||||
})
|
||||
messages.add({
|
||||
"role": "user",
|
||||
"content": userPrompt
|
||||
})
|
||||
return messages
|
||||
}
|
||||
|
||||
static generateCode(apiKey, messages) {
|
||||
var requestBody = {
|
||||
"model": "%(this.DEFAULT_MODEL)",
|
||||
"messages": messages
|
||||
}
|
||||
|
||||
var headers = {
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
var response = Http.post(this.API_ENDPOINT, requestBody, headers)
|
||||
|
||||
if (!response.ok) {
|
||||
System.print("Error: %(response.body)")
|
||||
return ""
|
||||
}
|
||||
|
||||
return extractSourceCode(response.body)
|
||||
}
|
||||
|
||||
static extractSourceCode(responseBody) {
|
||||
var data = Json.parse(responseBody)
|
||||
|
||||
if (data == null || data["choices"] == null || data["choices"].count == 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var message = data["choices"][0]["message"]
|
||||
if (message == null || message["content"] == null) {
|
||||
return ""
|
||||
}
|
||||
|
||||
var text = message["content"]
|
||||
text = stripMarkdownWrapper(text)
|
||||
text = extractFromJson(text)
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
static stripMarkdownWrapper(text) {
|
||||
if (text.startsWith("```")) {
|
||||
return text[7..-4].trim()
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
static extractFromJson(text) {
|
||||
if (text.startsWith("{")) {
|
||||
return Json.parse(text)["source_code"]
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
static printResults(startTime, output) {
|
||||
var elapsedTime = System.clock - startTime
|
||||
System.print("// Generation time: %(elapsedTime)")
|
||||
System.print("")
|
||||
System.print(output)
|
||||
}
|
||||
|
||||
static saveOutput(outputPath, content) {
|
||||
if (!outputPath.isEmpty) {
|
||||
Path.new(outputPath).writeText(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.print(SourceCodeGenerator.DEFAULT_MODEL)
|
||||
|
||||
SourceCodeGenerator.run()
|
||||
|
||||
107
apps/minitut.md
Normal file
107
apps/minitut.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Wren Tutorial: Core to Advanced
|
||||
|
||||
## Basics
|
||||
Wren: OOP scripting. Classes: `class Name { construct new(args) { _vars } methods }`. Inherit: `is Super`. Fn: `Fn.new { |args| body }`. Import: `import "mod" for Items`. Vars: `var x = val`. Loops: `for (i in seq) { }`, `while (cond) { }`. Conditionals: `if/else`. Errors: `Fiber.abort("msg")`.
|
||||
|
||||
Example:
|
||||
```
|
||||
class Point {
|
||||
construct new(x, y) { _x = x; _y = y }
|
||||
x { _x }
|
||||
}
|
||||
var p = Point.new(1, 2)
|
||||
System.print(p.x) // 1
|
||||
```
|
||||
|
||||
## Core Classes
|
||||
- **Bool/Fiber/Fn/Null/Num**: Base.
|
||||
- **Sequence**: `all/any(f)`, `contains(el)`, `count/f`, `each(f)`, `isEmpty`, `map(f)`, `skip/take(n)`, `where(f)`, `reduce/acc+f`, `join/sep`, `toList`.
|
||||
- **String**: `bytes/codePoints` (seqs), `split(delim)`, `replace(from/to)`, `trim/chars/start/end`, `*(n)`.
|
||||
- **List**: `addAll(other)`, `sort/comparer` (quicksort), `toString`, `+/ *(other/n)`.
|
||||
- **Map**: `keys/values` (seqs), `toString`, `iteratorValue` → `MapEntry(key/val)`.
|
||||
- **Range**: Seq empty.
|
||||
- **System**: `print/obj/All`, `write/obj/All`, `clock`.
|
||||
|
||||
## Meta/Random
|
||||
- **Meta**: `getModuleVariables(mod)`, `eval(src)`, `compileExpression/src`.
|
||||
- **Random**: `new/seed`, `float/int/range`, `sample(list/n)`, `shuffle(list)`.
|
||||
|
||||
## IO/OS
|
||||
- **File**: `read/write(path)`, `exists/delete`.
|
||||
- **Directory**: `list/exists/mkdir/rmdir/delete(path)`.
|
||||
- **Stdin/Stdout**: `readLine`, `flush`.
|
||||
- **Process**: `args`, timed exec.
|
||||
|
||||
## Fibers/Scheduler/Timer
|
||||
- Fiber: `new { body }`, `call/try`.
|
||||
- Scheduler: `add { body }` (runs on IO).
|
||||
- Timer: `sleep(ms)` (suspends).
|
||||
|
||||
## Modules/Examples
|
||||
- **Argparse**: `new(desc)`, `addArgument(opt, {help/default/action/type})`, `parseArgs(args) → map`.
|
||||
- **Base64**: `encode/decode(str)`, `encodeUrl/decodeUrl`.
|
||||
- **Bytes**: `fromList/toList`, `length/concat/slice`, `xorMask(payload/mask)`.
|
||||
- **Crypto**: `randomBytes(n)/Int(min,max)`; Hash: `md5/sha1/sha256(str)`, `toHex`.
|
||||
- **Dataset**: `memory()["table"]`: `insert(map) → uid`, `all/find(query)/findOne`, `update/delete(uid)`, `columns/tables`.
|
||||
- **Datetime**: `now/fromTimestamp`, components, `format(fmt)`, `+/- Duration` (fromHours/etc, seconds/+/-/*).
|
||||
- **Dns**: `lookup(host,4/6) → [ips]`.
|
||||
- **Pathlib**: `new(path)`, `exists/isDir/File`, `expanduser/glob/rglob`, `iterdir/joinpath/match/mkdir`, `name/stem/suffix/es`, `parent/parents/parts`, `readText/writeText/unlink`, `relativeTo/rename/walk`, `withName/Stem/Suffix`.
|
||||
- **Regex**: `new(pat/flags)`, `test/replace/All/split`.
|
||||
- **Signal**: Constants (SIGINT=2, etc.).
|
||||
- **Sqlite**: `memory()`: `execute(sql,params)`, `lastInsertId/changes`, `query → rows`.
|
||||
- **String Utils**: `toLower/Upper/hexEncode/Decode/repeat/padLeft/Right/escapeHtml/Json/urlEncode/Decode`.
|
||||
- **Strutil**: As above.
|
||||
- **Subprocess**: `run(cmd/args) → ProcessResult(success/exitCode/stdout)`.
|
||||
- **Tempfile**: `gettempdir/mkdtemp/stemp/temp(suffix/prefix/dir)`, NamedTemporaryFile/TemporaryDirectory (name/write/read/close/delete/use/cleanup).
|
||||
- **Uuid**: `v4() → str`, `isValid/V4`.
|
||||
- **Wdantic**: Schema: `new({field: Field.type(opts)})`, `validate(data) → ValidationResult(isValid/errors/data)`; Validators: email/domain/etc.
|
||||
- **Web**: Client: `parseUrl_(url) → {scheme/host/port/path}`; Request: `new_(method/path/query/headers/body/params/files)`, `header/json/cookies/form`; Response: `text/html/json/redirect/new(status/body/header/cookie/build)`; Router: `new()`, `get/post/etc(path,handler)`, `match(method/path) → {handler/params}`; SessionStore: `create/get/save/destroy`.
|
||||
- **Websocket**: `computeAcceptKey(base64)`, Message: `new_(opcode/payload/fin)`, `isText/etc`; Server: `bind/accept/receive/sendBinary/close`.
|
||||
|
||||
## Tests/Benchmarks
|
||||
- Fib: Recursive fib(28).
|
||||
- Nbody: Solar system sim (bodies, energy, advance).
|
||||
- Demos: Animals game (tree nodes), chat server (TCP fibers), browser auto (WS commands), etc.
|
||||
|
||||
## Usage
|
||||
Import modules, use classes/fns. Run: Compile/eval via Meta. Async: Fibers + Scheduler/Timer.
|
||||
|
||||
## Feature-Packed Example
|
||||
```
|
||||
import "io" for File, Directory, Stdin
|
||||
import "timer" for Timer
|
||||
import "scheduler" for Scheduler
|
||||
import "random" for Random
|
||||
import "meta" for Meta
|
||||
import "os" for Process
|
||||
|
||||
class Demo is Sequence {
|
||||
construct new() { _list = [1,2,3] }
|
||||
iterate(i) { _list.iterate(i) }
|
||||
iteratorValue(i) { _list.iteratorValue(i) * 2 }
|
||||
}
|
||||
|
||||
var rand = Random.new()
|
||||
System.print(rand.int(10)) // Random num
|
||||
|
||||
var seq = Demo.new()
|
||||
System.print(seq.map { |x| x + 1 }.toList) // [3,5,7]
|
||||
|
||||
Scheduler.add {
|
||||
Timer.sleep(100)
|
||||
System.print("Async fiber")
|
||||
}
|
||||
|
||||
var modVars = Meta.getModuleVariables("io")
|
||||
System.print(modVars) // Module vars
|
||||
|
||||
var file = "temp.txt"
|
||||
File.create(file) { |f| f.writeBytes("data") }
|
||||
System.print(File.read(file)) // data
|
||||
File.delete(file)
|
||||
|
||||
var sub = Subprocess.run("echo hello")
|
||||
System.print(sub.stdout.trim()) // hello
|
||||
|
||||
System.print(Process.args) // Args
|
||||
```
|
||||
97
apps/phonebook.wren
vendored
Normal file
97
apps/phonebook.wren
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
// wren source code generator v1.0
|
||||
// Generation time: 0.101419
|
||||
|
||||
import "io" for File, Directory, Stdin, Stdout
|
||||
import "sqlite" for Database
|
||||
|
||||
class Phonebook {
|
||||
construct new() {
|
||||
_db = Database.memory()
|
||||
init_()
|
||||
}
|
||||
|
||||
init_() {
|
||||
_db.execute("CREATE TABLE IF NOT EXISTS contacts (id INTEGER PRIMARY KEY, name TEXT, phone TEXT)", [])
|
||||
}
|
||||
|
||||
add(name, phone) {
|
||||
_db.execute("INSERT INTO contacts (name, phone) VALUES (?, ?)", [name, phone])
|
||||
System.print("Contact added: %(name)")
|
||||
}
|
||||
|
||||
list() {
|
||||
var rows = _db.query("SELECT * FROM contacts", [])
|
||||
if (rows.isEmpty) {
|
||||
System.print("No contacts found.")
|
||||
return
|
||||
}
|
||||
System.print("\n--- Phonebook ---")
|
||||
for (row in rows) {
|
||||
System.print("%(row["name"]) : %(row["phone"])")
|
||||
}
|
||||
System.print("")
|
||||
}
|
||||
|
||||
search(name) {
|
||||
var rows = _db.query("SELECT * FROM contacts WHERE name LIKE ?", ["\%" + name + "\%"])
|
||||
if (rows.isEmpty) {
|
||||
System.print("No contacts found for '%(name)'.")
|
||||
return
|
||||
}
|
||||
System.print("\n--- Search Results ---")
|
||||
for (row in rows) {
|
||||
System.print("%(row[1]) : %(row[2])")
|
||||
}
|
||||
System.print("")
|
||||
}
|
||||
|
||||
delete(name) {
|
||||
_db.execute("DELETE FROM contacts WHERE name = ?", [name])
|
||||
System.print("Contact deleted: %(name)")
|
||||
}
|
||||
|
||||
run() {
|
||||
System.print("\n=== CLI Phonebook ===")
|
||||
var running = true
|
||||
while (running) {
|
||||
System.print("\n1. Add contact")
|
||||
System.print("2. List contacts")
|
||||
System.print("3. Search contact")
|
||||
System.print("4. Delete contact")
|
||||
System.print("5. Exit")
|
||||
System.write("Choose option: ")
|
||||
Stdout.flush()
|
||||
var choice = Stdin.readLine().trim()
|
||||
|
||||
if (choice == "1") {
|
||||
System.write("Name: ")
|
||||
Stdout.flush()
|
||||
var name = Stdin.readLine().trim()
|
||||
System.write("Phone: ")
|
||||
Stdout.flush()
|
||||
var phone = Stdin.readLine().trim()
|
||||
add(name, phone)
|
||||
} else if (choice == "2") {
|
||||
list()
|
||||
} else if (choice == "3") {
|
||||
System.write("Search name: ")
|
||||
Stdout.flush()
|
||||
var name = Stdin.readLine().trim()
|
||||
search(name)
|
||||
} else if (choice == "4") {
|
||||
System.write("Contact name to delete: ")
|
||||
Stdout.flush()
|
||||
var name = Stdin.readLine().trim()
|
||||
delete(name)
|
||||
} else if (choice == "5") {
|
||||
System.print("Goodbye!")
|
||||
running = false
|
||||
} else {
|
||||
System.print("Invalid option.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pb = Phonebook.new()
|
||||
pb.run()
|
||||
90
deps/wren/src/vm/wren_compiler.c
vendored
90
deps/wren/src/vm/wren_compiler.c
vendored
@ -2534,7 +2534,52 @@ static void await_(Compiler* compiler, bool canAssign)
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
ignoreNewlines(compiler);
|
||||
expression(compiler);
|
||||
|
||||
if (peek(compiler) == TOKEN_NAME)
|
||||
{
|
||||
Token nameToken = compiler->parser->current;
|
||||
Variable variable = resolveNonmodule(compiler, nameToken.start, nameToken.length);
|
||||
|
||||
if (variable.index == -1)
|
||||
{
|
||||
variable.scope = SCOPE_MODULE;
|
||||
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
|
||||
nameToken.start, nameToken.length);
|
||||
}
|
||||
|
||||
if (variable.index != -1)
|
||||
{
|
||||
TokenType following = peekNext(compiler);
|
||||
|
||||
if (following == TOKEN_LEFT_PAREN)
|
||||
{
|
||||
nextToken(compiler->parser);
|
||||
loadVariable(compiler, variable);
|
||||
|
||||
Signature signature = { "call", 4, SIG_GETTER, 0 };
|
||||
methodCall(compiler, CODE_CALL_0, &signature);
|
||||
|
||||
allowLineBeforeDot(compiler);
|
||||
while (match(compiler, TOKEN_DOT))
|
||||
{
|
||||
namedCall(compiler, false, CODE_CALL_0);
|
||||
allowLineBeforeDot(compiler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expression(compiler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expression(compiler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expression(compiler);
|
||||
}
|
||||
|
||||
callMethod(compiler, 1, "await_(_)", 9);
|
||||
}
|
||||
@ -2544,18 +2589,45 @@ static void async_(Compiler* compiler, bool canAssign)
|
||||
int schedulerSymbol = resolveScheduler(compiler);
|
||||
if (schedulerSymbol == -1) return;
|
||||
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after 'async'.");
|
||||
|
||||
Compiler fnCompiler;
|
||||
initCompiler(&fnCompiler, compiler->parser, compiler, false);
|
||||
fnCompiler.fn->arity = 0;
|
||||
if (match(compiler, TOKEN_PIPE))
|
||||
{
|
||||
Compiler outerCompiler;
|
||||
initCompiler(&outerCompiler, compiler->parser, compiler, false);
|
||||
|
||||
finishBody(&fnCompiler);
|
||||
endCompiler(&fnCompiler, "[async]", 7);
|
||||
Signature outerSignature = { "", 0, SIG_METHOD, 0 };
|
||||
finishParameterList(&outerCompiler, &outerSignature);
|
||||
consume(compiler, TOKEN_PIPE, "Expect '|' after parameters.");
|
||||
outerCompiler.fn->arity = outerSignature.arity;
|
||||
|
||||
callMethod(compiler, 1, "async_(_)", 9);
|
||||
emitShortArg(&outerCompiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
Compiler innerCompiler;
|
||||
initCompiler(&innerCompiler, outerCompiler.parser, &outerCompiler, false);
|
||||
innerCompiler.fn->arity = 0;
|
||||
|
||||
finishBody(&innerCompiler);
|
||||
endCompiler(&innerCompiler, "[async]", 7);
|
||||
|
||||
callMethod(&outerCompiler, 1, "async_(_)", 9);
|
||||
emitOp(&outerCompiler, CODE_RETURN);
|
||||
|
||||
endCompiler(&outerCompiler, "[async fn]", 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
Compiler fnCompiler;
|
||||
initCompiler(&fnCompiler, compiler->parser, compiler, false);
|
||||
fnCompiler.fn->arity = 0;
|
||||
|
||||
finishBody(&fnCompiler);
|
||||
endCompiler(&fnCompiler, "[async]", 7);
|
||||
|
||||
callMethod(compiler, 1, "async_(_)", 9);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscript or "array indexing" operator like `foo[bar]`.
|
||||
|
||||
243
example/await_demo.wren
vendored
243
example/await_demo.wren
vendored
@ -3,20 +3,239 @@
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
System.print("=== Await Demo ===\n")
|
||||
System.print("=== Async/Await Demo ===\n")
|
||||
|
||||
System.print("--- Basic Await ---")
|
||||
await Timer.sleep(1)
|
||||
System.print("Timer completed")
|
||||
System.print("Timer completed after 1ms")
|
||||
|
||||
System.print("\n--- Concurrent Async ---")
|
||||
var task1 = async { Timer.sleep(1) }
|
||||
var task2 = async { Timer.sleep(1) }
|
||||
await task1
|
||||
await task2
|
||||
System.print("Both tasks completed concurrently")
|
||||
var getValue = async { 42 }
|
||||
var result = await getValue()
|
||||
System.print("Async block returned: %(result)")
|
||||
|
||||
System.print("\n--- Async With Result ---")
|
||||
var task = async { "computed value" }
|
||||
var result = await task
|
||||
System.print("Result: %(result)")
|
||||
System.print("\n--- Parameterized Async Functions ---")
|
||||
var double = async { |x| x * 2 }
|
||||
var add = async { |a, b| a + b }
|
||||
var multiply = async { |a, b| a * b }
|
||||
|
||||
System.print("double(21) = %(await double(21))")
|
||||
System.print("add(3, 4) = %(await add(3, 4))")
|
||||
System.print("multiply(6, 7) = %(await multiply(6, 7))")
|
||||
|
||||
System.print("\n--- Nested Awaits ---")
|
||||
var addTen = async { |x| x + 10 }
|
||||
System.print("addTen(double(5)) = %(await addTen(await double(5)))")
|
||||
System.print("double(double(double(2))) = %(await double(await double(await double(2))))")
|
||||
|
||||
System.print("\n--- Await in Expressions ---")
|
||||
System.print("1 + double(5) = %(1 + await double(5))")
|
||||
System.print("double(3) + double(4) = %(await double(3) + await double(4))")
|
||||
|
||||
System.print("\n--- Await in Conditions ---")
|
||||
var isPositive = async { |x| x > 0 }
|
||||
if (await isPositive(5)) System.print("5 is positive: true")
|
||||
var ternaryResult = await isPositive(10) ? "yes" : "no"
|
||||
System.print("10 is positive? %(ternaryResult)")
|
||||
|
||||
System.print("\n--- Await in Loops ---")
|
||||
var squares = []
|
||||
for (i in 1..5) {
|
||||
var sq = async { |x| x * x }
|
||||
squares.add(await sq(i))
|
||||
}
|
||||
System.print("Squares of 1-5: %(squares)")
|
||||
|
||||
System.print("\n=== Class-Based Async Patterns ===\n")
|
||||
|
||||
System.print("--- Static Async Getters ---")
|
||||
|
||||
class Calculator {
|
||||
static add { async { |a, b| a + b } }
|
||||
static multiply { async { |a, b| a * b } }
|
||||
static square { async { |x| x * x } }
|
||||
static pi { async { 3.14159 } }
|
||||
|
||||
static compute(a, b) {
|
||||
var addFn = Calculator.add
|
||||
var multiplyFn = Calculator.multiply
|
||||
var sum = await addFn(a, b)
|
||||
var product = await multiplyFn(a, b)
|
||||
return {"sum": sum, "product": product}
|
||||
}
|
||||
}
|
||||
|
||||
var addFn = Calculator.add
|
||||
var multiplyFn = Calculator.multiply
|
||||
var squareFn = Calculator.square
|
||||
var piFn = Calculator.pi
|
||||
|
||||
System.print("add(3, 4) = %(await addFn(3, 4))")
|
||||
System.print("multiply(5, 6) = %(await multiplyFn(5, 6))")
|
||||
System.print("square(8) = %(await squareFn(8))")
|
||||
System.print("pi() = %(await piFn())")
|
||||
|
||||
var results = Calculator.compute(5, 3)
|
||||
System.print("Calculator.compute(5, 3) = %(results)")
|
||||
|
||||
System.print("\n--- Instance Methods with Async ---")
|
||||
|
||||
class Counter {
|
||||
construct new(start) {
|
||||
_value = start
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
add(n) {
|
||||
var op = async { |x|
|
||||
_value = _value + x
|
||||
return _value
|
||||
}
|
||||
return await op(n)
|
||||
}
|
||||
|
||||
subtract(n) {
|
||||
var op = async { |x|
|
||||
_value = _value - x
|
||||
return _value
|
||||
}
|
||||
return await op(n)
|
||||
}
|
||||
|
||||
reset() {
|
||||
var op = async {
|
||||
_value = 0
|
||||
return _value
|
||||
}
|
||||
return await op()
|
||||
}
|
||||
}
|
||||
|
||||
var counter = Counter.new(100)
|
||||
System.print("Initial value: %(counter.value)")
|
||||
System.print("After add(25): %(counter.add(25))")
|
||||
System.print("After subtract(10): %(counter.subtract(10))")
|
||||
System.print("After reset(): %(counter.reset())")
|
||||
System.print("Final value: %(counter.value)")
|
||||
|
||||
System.print("\n--- Mock API Service ---")
|
||||
|
||||
class UserApi {
|
||||
static fetchUser { async { |id|
|
||||
Timer.sleep(1)
|
||||
return {"id": id, "name": "User%(id)", "email": "user%(id)@example.com"}
|
||||
}}
|
||||
|
||||
static fetchPosts { async { |userId|
|
||||
Timer.sleep(1)
|
||||
return [
|
||||
{"id": 1, "userId": userId, "title": "First Post"},
|
||||
{"id": 2, "userId": userId, "title": "Second Post"}
|
||||
]
|
||||
}}
|
||||
|
||||
static getUserWithPosts(userId) {
|
||||
var fetchUser = UserApi.fetchUser
|
||||
var fetchPosts = UserApi.fetchPosts
|
||||
var user = await fetchUser(userId)
|
||||
var posts = await fetchPosts(userId)
|
||||
|
||||
user["posts"] = posts
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
var fetchUser = UserApi.fetchUser
|
||||
var user = await fetchUser(42)
|
||||
System.print("User: %(user["name"]) <%(user["email"])>")
|
||||
|
||||
var fetchPosts = UserApi.fetchPosts
|
||||
var posts = await fetchPosts(42)
|
||||
System.print("Posts count: %(posts.count)")
|
||||
|
||||
var fullUser = UserApi.getUserWithPosts(123)
|
||||
System.print("Full user: %(fullUser["name"]) with %(fullUser["posts"].count) posts")
|
||||
|
||||
System.print("\n--- Data Pipeline ---")
|
||||
|
||||
class DataPipeline {
|
||||
static parse { async { |csv| csv.split(",") } }
|
||||
static filter { async { |list, predicate|
|
||||
var result = []
|
||||
for (item in list) {
|
||||
if (predicate.call(item)) result.add(item)
|
||||
}
|
||||
return result
|
||||
}}
|
||||
static transform { async { |list, mapper|
|
||||
var result = []
|
||||
for (item in list) {
|
||||
result.add(mapper.call(item))
|
||||
}
|
||||
return result
|
||||
}}
|
||||
static reduce { async { |list, initial, reducer|
|
||||
var acc = initial
|
||||
for (item in list) {
|
||||
acc = reducer.call(acc, item)
|
||||
}
|
||||
return acc
|
||||
}}
|
||||
|
||||
static process(csv) {
|
||||
var parse = DataPipeline.parse
|
||||
var filter = DataPipeline.filter
|
||||
var transform = DataPipeline.transform
|
||||
var reduce = DataPipeline.reduce
|
||||
var items = await parse(csv)
|
||||
var filtered = await filter(items) { |s| s.count > 3 }
|
||||
var lengths = await transform(filtered) { |s| s.count }
|
||||
var total = await reduce(lengths, 0) { |acc, n| acc + n }
|
||||
return total
|
||||
}
|
||||
}
|
||||
|
||||
var data = "one,two,three,four,five,six,seven"
|
||||
|
||||
var parse = DataPipeline.parse
|
||||
var parsed = await parse(data)
|
||||
System.print("Parsed: %(parsed)")
|
||||
|
||||
var filter = DataPipeline.filter
|
||||
var filtered = await filter(parsed) { |s| s.count > 3 }
|
||||
System.print("Filtered (length > 3): %(filtered)")
|
||||
|
||||
var transform = DataPipeline.transform
|
||||
var lengths = await transform(filtered) { |s| s.count }
|
||||
System.print("Lengths: %(lengths)")
|
||||
|
||||
var reduce = DataPipeline.reduce
|
||||
var sum = await reduce(lengths, 0) { |acc, n| acc + n }
|
||||
System.print("Sum of lengths: %(sum)")
|
||||
|
||||
var total = DataPipeline.process("apple,banana,cherry,date,elderberry")
|
||||
System.print("Pipeline result: %(total)")
|
||||
|
||||
System.print("\n--- Parallel Task Execution ---")
|
||||
|
||||
var fetchA = async { |x|
|
||||
Timer.sleep(10)
|
||||
return "A:%(x)"
|
||||
}
|
||||
var fetchB = async { |x|
|
||||
Timer.sleep(10)
|
||||
return "B:%(x)"
|
||||
}
|
||||
var fetchC = async { |x|
|
||||
Timer.sleep(10)
|
||||
return "C:%(x)"
|
||||
}
|
||||
|
||||
var futureA = fetchA.call(1)
|
||||
var futureB = fetchB.call(2)
|
||||
var futureC = fetchC.call(3)
|
||||
|
||||
System.print("Started 3 parallel tasks")
|
||||
System.print("Results: %(await futureA), %(await futureB), %(await futureC)")
|
||||
|
||||
System.print("\n=== Demo Complete ===")
|
||||
|
||||
10
example/openrouter_demo.wren
vendored
10
example/openrouter_demo.wren
vendored
@ -39,7 +39,15 @@ System.print("Status: %(response.statusCode)")
|
||||
System.print("")
|
||||
|
||||
if (response.ok) {
|
||||
var data = Json.parse(response.body)
|
||||
let text = response.body
|
||||
if(text.startsWith("```")){
|
||||
text = text.trim("```")
|
||||
text = text.trim("json")
|
||||
text = text.trim("\n")
|
||||
|
||||
}
|
||||
System.print(text)
|
||||
var data = Json.parse(text)
|
||||
if (data != null && data["choices"] != null && data["choices"].count > 0) {
|
||||
var message = data["choices"][0]["message"]
|
||||
if (message != null && message["content"] != null) {
|
||||
|
||||
@ -126,6 +126,99 @@
|
||||
</div>
|
||||
<p>Resumes a fiber with a result value.</p>
|
||||
|
||||
<h2 id="await-syntax">Await Syntax</h2>
|
||||
|
||||
<p>Wren-CLI provides a convenient syntax for awaiting async functions. Instead of using <code>.call()</code>, you can call async functions directly after <code>await</code>:</p>
|
||||
|
||||
<h3>Before and After</h3>
|
||||
<pre><code>// Old syntax (still works)
|
||||
var result = await asyncFn.call(arg1, arg2)
|
||||
|
||||
// New syntax (recommended)
|
||||
var result = await asyncFn(arg1, arg2)</code></pre>
|
||||
|
||||
<p>The compiler automatically translates <code>await fn(args)</code> to <code>await fn.call(args)</code>.</p>
|
||||
|
||||
<h3>Supported Patterns</h3>
|
||||
|
||||
<table class="params-table">
|
||||
<thead>
|
||||
<tr><th>Pattern</th><th>Example</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>No arguments</td><td><code>await getValue()</code></td></tr>
|
||||
<tr><td>Single argument</td><td><code>await double(21)</code></td></tr>
|
||||
<tr><td>Multiple arguments</td><td><code>await add(3, 4, 5)</code></td></tr>
|
||||
<tr><td>Nested awaits</td><td><code>await outer(await inner(x))</code></td></tr>
|
||||
<tr><td>In expressions</td><td><code>var x = 1 + await fn(2)</code></td></tr>
|
||||
<tr><td>In conditions</td><td><code>if (await check(x)) { }</code></td></tr>
|
||||
<tr><td>Block argument</td><td><code>await map(list) { |x| x * 2 }</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Method chaining after <code>await fn(args)</code> requires assigning the result first:</p>
|
||||
<pre><code>var list = await getList(5)
|
||||
System.print(list.count)</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Examples</h3>
|
||||
|
||||
<pre><code>import "scheduler" for Scheduler, Future
|
||||
|
||||
var double = async { |x| x * 2 }
|
||||
var add = async { |a, b| a + b }
|
||||
|
||||
// Basic usage
|
||||
System.print(await double(21)) // 42
|
||||
System.print(await add(3, 4)) // 7
|
||||
|
||||
// Nested awaits
|
||||
var result = await double(await add(5, 5)) // 20
|
||||
|
||||
// Method chaining
|
||||
var getList = async { |n| [1, 2, 3, n] }
|
||||
System.print(await getList(4).count) // 4
|
||||
|
||||
// In expressions
|
||||
var total = await add(10, 20) + await double(5) // 40</code></pre>
|
||||
|
||||
<h3>Class-Based Async Patterns</h3>
|
||||
|
||||
<p>Static getters that return async functions must first be assigned to a variable:</p>
|
||||
|
||||
<pre><code>import "scheduler" for Scheduler, Future
|
||||
|
||||
class Calculator {
|
||||
static add { async { |a, b| a + b } }
|
||||
static multiply { async { |a, b| a * b } }
|
||||
static square { async { |x| x * x } }
|
||||
|
||||
static compute(a, b) {
|
||||
var addFn = Calculator.add
|
||||
var multiplyFn = Calculator.multiply
|
||||
var sum = await addFn(a, b)
|
||||
var product = await multiplyFn(a, b)
|
||||
return [sum, product]
|
||||
}
|
||||
}
|
||||
|
||||
// Assign getter to variable first, then await
|
||||
var add = Calculator.add
|
||||
System.print(await add(3, 4)) // 7
|
||||
|
||||
var multiply = Calculator.multiply
|
||||
System.print(await multiply(5, 6)) // 30
|
||||
|
||||
var square = Calculator.square
|
||||
System.print(await square(8)) // 64</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>The <code>fn(args)</code> syntax only works directly after <code>await</code>. Outside of <code>await</code>, use <code>fn.call(args)</code> to invoke async functions.</p>
|
||||
</div>
|
||||
|
||||
<h2>How Async Works</h2>
|
||||
|
||||
<p>Wren-CLI uses an event loop (libuv) for non-blocking I/O. Here is how async operations work:</p>
|
||||
|
||||
24
src/module/http.wren
vendored
24
src/module/http.wren
vendored
@ -137,18 +137,21 @@ class Http {
|
||||
requestHeaders["User-Agent"] = "Wren-CLI/1.0"
|
||||
|
||||
if (bodyStr.count > 0) {
|
||||
requestHeaders["Content-Length"] = bodyStr.count.toString
|
||||
requestHeaders["Content-Length"] = bodyStr.bytes.count.toString
|
||||
}
|
||||
|
||||
for (entry in headers) {
|
||||
requestHeaders[entry.key] = entry.value
|
||||
}
|
||||
|
||||
var request = "%(method) %(parsed.fullPath) HTTP/1.1\r\n"
|
||||
var parts = []
|
||||
parts.add("%(method) %(parsed.fullPath) HTTP/1.1\r\n")
|
||||
for (entry in requestHeaders) {
|
||||
request = request + "%(entry.key): %(entry.value)\r\n"
|
||||
parts.add("%(entry.key): %(entry.value)\r\n")
|
||||
}
|
||||
request = request + "\r\n" + bodyStr
|
||||
parts.add("\r\n")
|
||||
parts.add(bodyStr)
|
||||
var request = parts.join("")
|
||||
|
||||
var addresses = Dns.lookup(parsed.host, 4)
|
||||
if (addresses.count == 0) {
|
||||
@ -163,12 +166,13 @@ class Http {
|
||||
}
|
||||
socket.write(request)
|
||||
|
||||
var response = ""
|
||||
var chunks = []
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) break
|
||||
response = response + chunk
|
||||
if (chunk == null || chunk.bytes.count == 0) break
|
||||
chunks.add(chunk)
|
||||
}
|
||||
var response = chunks.join("")
|
||||
socket.close()
|
||||
|
||||
return parseResponse_(response)
|
||||
@ -251,7 +255,7 @@ class Http {
|
||||
}
|
||||
|
||||
static decodeChunked_(body) {
|
||||
var result = ""
|
||||
var parts = []
|
||||
var pos = 0
|
||||
|
||||
while (pos < body.count) {
|
||||
@ -266,11 +270,11 @@ class Http {
|
||||
pos = lineEnd + 2
|
||||
if (pos + size > body.count) break
|
||||
|
||||
result = result + body[pos...(pos + size)]
|
||||
parts.add(body[pos...(pos + size)])
|
||||
pos = pos + size + 2
|
||||
}
|
||||
|
||||
return result
|
||||
return parts.join("")
|
||||
}
|
||||
|
||||
static parseHex_(str) {
|
||||
|
||||
@ -141,18 +141,21 @@ static const char* httpModuleSource =
|
||||
" requestHeaders[\"User-Agent\"] = \"Wren-CLI/1.0\"\n"
|
||||
"\n"
|
||||
" if (bodyStr.count > 0) {\n"
|
||||
" requestHeaders[\"Content-Length\"] = bodyStr.count.toString\n"
|
||||
" requestHeaders[\"Content-Length\"] = bodyStr.bytes.count.toString\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (entry in headers) {\n"
|
||||
" requestHeaders[entry.key] = entry.value\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var request = \"%(method) %(parsed.fullPath) HTTP/1.1\\r\\n\"\n"
|
||||
" var parts = []\n"
|
||||
" parts.add(\"%(method) %(parsed.fullPath) HTTP/1.1\\r\\n\")\n"
|
||||
" for (entry in requestHeaders) {\n"
|
||||
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
|
||||
" parts.add(\"%(entry.key): %(entry.value)\\r\\n\")\n"
|
||||
" }\n"
|
||||
" request = request + \"\\r\\n\" + bodyStr\n"
|
||||
" parts.add(\"\\r\\n\")\n"
|
||||
" parts.add(bodyStr)\n"
|
||||
" var request = parts.join(\"\")\n"
|
||||
"\n"
|
||||
" var addresses = Dns.lookup(parsed.host, 4)\n"
|
||||
" if (addresses.count == 0) {\n"
|
||||
@ -167,12 +170,13 @@ static const char* httpModuleSource =
|
||||
" }\n"
|
||||
" socket.write(request)\n"
|
||||
"\n"
|
||||
" var response = \"\"\n"
|
||||
" var chunks = []\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) break\n"
|
||||
" response = response + chunk\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) break\n"
|
||||
" chunks.add(chunk)\n"
|
||||
" }\n"
|
||||
" var response = chunks.join(\"\")\n"
|
||||
" socket.close()\n"
|
||||
"\n"
|
||||
" return parseResponse_(response)\n"
|
||||
@ -255,7 +259,7 @@ static const char* httpModuleSource =
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeChunked_(body) {\n"
|
||||
" var result = \"\"\n"
|
||||
" var parts = []\n"
|
||||
" var pos = 0\n"
|
||||
"\n"
|
||||
" while (pos < body.count) {\n"
|
||||
@ -270,11 +274,11 @@ static const char* httpModuleSource =
|
||||
" pos = lineEnd + 2\n"
|
||||
" if (pos + size > body.count) break\n"
|
||||
"\n"
|
||||
" result = result + body[pos...(pos + size)]\n"
|
||||
" parts.add(body[pos...(pos + size)])\n"
|
||||
" pos = pos + size + 2\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" return parts.join(\"\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseHex_(str) {\n"
|
||||
|
||||
106
src/module/io.c
106
src/module/io.c
@ -31,6 +31,9 @@ static WrenHandle* stdinOnData = NULL;
|
||||
// The stream used to read from stdin. Initialized on the first read.
|
||||
static uv_stream_t* stdinStream = NULL;
|
||||
|
||||
// True if stdin is a file/device that doesn't support async I/O.
|
||||
static bool stdinIsFile = false;
|
||||
|
||||
// True if stdin has been set to raw mode.
|
||||
static bool isStdinRaw = false;
|
||||
|
||||
@ -514,11 +517,19 @@ static void initStdin()
|
||||
}
|
||||
else
|
||||
{
|
||||
// stdin is a pipe or a file.
|
||||
uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t));
|
||||
uv_pipe_init(getLoop(), handle, false);
|
||||
uv_pipe_open(handle, stdinDescriptor);
|
||||
stdinStream = (uv_stream_t*)handle;
|
||||
uv_handle_type htype = uv_guess_handle(stdinDescriptor);
|
||||
if (htype == UV_FILE || htype == UV_UNKNOWN_HANDLE)
|
||||
{
|
||||
stdinIsFile = true;
|
||||
stdinStream = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t));
|
||||
uv_pipe_init(getLoop(), handle, false);
|
||||
uv_pipe_open(handle, stdinDescriptor);
|
||||
stdinStream = (uv_stream_t*)handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -577,53 +588,104 @@ static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
|
||||
const uv_buf_t* buffer)
|
||||
{
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
|
||||
if (stdinClass == NULL)
|
||||
{
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "io", "Stdin", 0);
|
||||
stdinClass = wrenGetSlotHandle(vm, 0);
|
||||
}
|
||||
|
||||
|
||||
if (stdinOnData == NULL)
|
||||
{
|
||||
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
|
||||
}
|
||||
|
||||
// If stdin was closed, send null to let io.wren know.
|
||||
if (numRead == UV_EOF)
|
||||
|
||||
if (numRead < 0)
|
||||
{
|
||||
if (numRead != UV_EOF)
|
||||
{
|
||||
uv_read_stop(stdinStream);
|
||||
uv_stop(getLoop());
|
||||
setExitCode(70);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotHandle(vm, 0, stdinClass);
|
||||
wrenSetSlotNull(vm, 1);
|
||||
wrenCall(vm, stdinOnData);
|
||||
|
||||
shutdownStdin();
|
||||
WrenInterpretResult result = wrenCall(vm, stdinOnData);
|
||||
|
||||
if (result == WREN_RESULT_RUNTIME_ERROR)
|
||||
{
|
||||
uv_stop(getLoop());
|
||||
setExitCode(70);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Handle other errors.
|
||||
|
||||
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
|
||||
// embedding API supported a way to *give* it bytes that were previously
|
||||
// allocated using Wren's own allocator.
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotHandle(vm, 0, stdinClass);
|
||||
wrenSetSlotBytes(vm, 1, buffer->base, numRead);
|
||||
wrenCall(vm, stdinOnData);
|
||||
WrenInterpretResult result = wrenCall(vm, stdinOnData);
|
||||
|
||||
// TODO: Likewise, freeing this after we resume is lame.
|
||||
free(buffer->base);
|
||||
|
||||
if (result == WREN_RESULT_RUNTIME_ERROR)
|
||||
{
|
||||
uv_stop(getLoop());
|
||||
setExitCode(70);
|
||||
}
|
||||
}
|
||||
|
||||
static uv_timer_t stdinFileTimer;
|
||||
|
||||
static void stdinFileTimerCallback(uv_timer_t* handle)
|
||||
{
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
if (stdinClass == NULL)
|
||||
{
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "io", "Stdin", 0);
|
||||
stdinClass = wrenGetSlotHandle(vm, 0);
|
||||
}
|
||||
|
||||
if (stdinOnData == NULL)
|
||||
{
|
||||
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotHandle(vm, 0, stdinClass);
|
||||
wrenSetSlotNull(vm, 1);
|
||||
WrenInterpretResult result = wrenCall(vm, stdinOnData);
|
||||
|
||||
if (result == WREN_RESULT_RUNTIME_ERROR)
|
||||
{
|
||||
uv_stop(getLoop());
|
||||
setExitCode(70);
|
||||
}
|
||||
}
|
||||
|
||||
void stdinReadStart(WrenVM* vm)
|
||||
{
|
||||
initStdin();
|
||||
|
||||
if (stdinIsFile)
|
||||
{
|
||||
uv_timer_init(getLoop(), &stdinFileTimer);
|
||||
uv_timer_start(&stdinFileTimer, stdinFileTimerCallback, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_read_start(stdinStream, allocCallback, stdinReadCallback);
|
||||
// TODO: Check return.
|
||||
}
|
||||
|
||||
void stdinReadStop(WrenVM* vm)
|
||||
{
|
||||
uv_read_stop(stdinStream);
|
||||
if (stdinStream != NULL)
|
||||
{
|
||||
uv_read_stop(stdinStream);
|
||||
}
|
||||
}
|
||||
|
||||
1
src/module/io.wren
vendored
1
src/module/io.wren
vendored
@ -280,6 +280,7 @@ class Stdin {
|
||||
} else {
|
||||
__waitingFiber.transferError("Stdin was closed.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Append to the buffer.
|
||||
|
||||
@ -284,6 +284,7 @@ static const char* ioModuleSource =
|
||||
" } else {\n"
|
||||
" __waitingFiber.transferError(\"Stdin was closed.\")\n"
|
||||
" }\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" // Append to the buffer.\n"
|
||||
|
||||
12
src/module/repl.wren
vendored
12
src/module/repl.wren
vendored
@ -1,6 +1,7 @@
|
||||
import "meta" for Meta
|
||||
import "io" for Stdin, Stdout
|
||||
import "os" for Platform
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
/// Abstract base class for the REPL. Manages the input line and history, but
|
||||
/// does not render.
|
||||
@ -65,7 +66,7 @@ class Repl {
|
||||
} else {
|
||||
// TODO: Handle ESC 0 sequences.
|
||||
}
|
||||
} else if (byte == Chars.carriageReturn) {
|
||||
} else if (byte == Chars.carriageReturn || byte == Chars.lineFeed) {
|
||||
executeInput()
|
||||
} else if (byte == Chars.delete) {
|
||||
deleteLeft()
|
||||
@ -197,8 +198,15 @@ class Repl {
|
||||
var fiber = Fiber.new(closure)
|
||||
|
||||
var result = fiber.try()
|
||||
|
||||
while (!fiber.isDone && fiber.error == null && Scheduler.hasScheduled_) {
|
||||
Scheduler.runNextScheduled_()
|
||||
if (!fiber.isDone && fiber.error == null) {
|
||||
result = fiber.try()
|
||||
}
|
||||
}
|
||||
|
||||
if (fiber.error != null) {
|
||||
// TODO: Include callstack.
|
||||
showRuntimeError("Runtime error: %(fiber.error)")
|
||||
return
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ static const char* replModuleSource =
|
||||
"import \"meta\" for Meta\n"
|
||||
"import \"io\" for Stdin, Stdout\n"
|
||||
"import \"os\" for Platform\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"/// Abstract base class for the REPL. Manages the input line and history, but\n"
|
||||
"/// does not render.\n"
|
||||
@ -69,7 +70,7 @@ static const char* replModuleSource =
|
||||
" } else {\n"
|
||||
" // TODO: Handle ESC 0 sequences.\n"
|
||||
" }\n"
|
||||
" } else if (byte == Chars.carriageReturn) {\n"
|
||||
" } else if (byte == Chars.carriageReturn || byte == Chars.lineFeed) {\n"
|
||||
" executeInput()\n"
|
||||
" } else if (byte == Chars.delete) {\n"
|
||||
" deleteLeft()\n"
|
||||
@ -201,8 +202,15 @@ static const char* replModuleSource =
|
||||
" var fiber = Fiber.new(closure)\n"
|
||||
"\n"
|
||||
" var result = fiber.try()\n"
|
||||
"\n"
|
||||
" while (!fiber.isDone && fiber.error == null && Scheduler.hasScheduled_) {\n"
|
||||
" Scheduler.runNextScheduled_()\n"
|
||||
" if (!fiber.isDone && fiber.error == null) {\n"
|
||||
" result = fiber.try()\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (fiber.error != null) {\n"
|
||||
" // TODO: Include callstack.\n"
|
||||
" showRuntimeError(\"Runtime error: %(fiber.error)\")\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
|
||||
6
src/module/scheduler.wren
vendored
6
src/module/scheduler.wren
vendored
@ -40,6 +40,10 @@ class Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
static hasScheduled_ {
|
||||
return __scheduled != null && !__scheduled.isEmpty
|
||||
}
|
||||
|
||||
foreign static captureMethods_()
|
||||
}
|
||||
|
||||
@ -71,6 +75,8 @@ class Future {
|
||||
|
||||
isDone { _resolved }
|
||||
result { _result }
|
||||
|
||||
call() { this }
|
||||
}
|
||||
|
||||
Scheduler.captureMethods_()
|
||||
|
||||
@ -44,6 +44,10 @@ static const char* schedulerModuleSource =
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hasScheduled_ {\n"
|
||||
" return __scheduled != null && !__scheduled.isEmpty\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static captureMethods_()\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
@ -75,6 +79,8 @@ static const char* schedulerModuleSource =
|
||||
"\n"
|
||||
" isDone { _resolved }\n"
|
||||
" result { _result }\n"
|
||||
"\n"
|
||||
" call() { this }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"Scheduler.captureMethods_()\n";
|
||||
|
||||
@ -336,14 +336,32 @@ void tlsSocketWrite(WrenVM* vm) {
|
||||
data->fiber = fiber;
|
||||
data->writeInProgress = true;
|
||||
|
||||
int written = SSL_write(data->ssl, text, length);
|
||||
if (written <= 0) {
|
||||
int err = SSL_get_error(data->ssl, written);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
data->writeInProgress = false;
|
||||
data->fiber = NULL;
|
||||
schedulerResumeError(fiber, "SSL write error");
|
||||
return;
|
||||
int totalWritten = 0;
|
||||
while (totalWritten < length) {
|
||||
int written = SSL_write(data->ssl, text + totalWritten, length - totalWritten);
|
||||
if (written > 0) {
|
||||
totalWritten += written;
|
||||
} else {
|
||||
int err = SSL_get_error(data->ssl, written);
|
||||
if (err == SSL_ERROR_WANT_WRITE) {
|
||||
char buffer[16384];
|
||||
int pending;
|
||||
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
|
||||
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
|
||||
wr->buf.base = (char*)malloc(pending);
|
||||
wr->buf.len = pending;
|
||||
memcpy(wr->buf.base, buffer, pending);
|
||||
wr->data = data;
|
||||
wr->isLast = false;
|
||||
wr->req.data = wr;
|
||||
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
|
||||
}
|
||||
} else if (err != SSL_ERROR_WANT_READ) {
|
||||
data->writeInProgress = false;
|
||||
data->fiber = NULL;
|
||||
schedulerResumeError(fiber, "SSL write error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
src/module/web.wren
vendored
4
src/module/web.wren
vendored
@ -431,7 +431,7 @@ class Application {
|
||||
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
if (chunk == null || chunk.bytes.count == 0) {
|
||||
socket.close()
|
||||
return
|
||||
}
|
||||
@ -637,7 +637,7 @@ class Client {
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) break
|
||||
if (chunk == null || chunk.bytes.count == 0) break
|
||||
response = response + chunk
|
||||
}
|
||||
socket.close()
|
||||
|
||||
@ -435,7 +435,7 @@ static const char* webModuleSource =
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) {\n"
|
||||
" socket.close()\n"
|
||||
" return\n"
|
||||
" }\n"
|
||||
@ -641,7 +641,7 @@ static const char* webModuleSource =
|
||||
" var response = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) break\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) break\n"
|
||||
" response = response + chunk\n"
|
||||
" }\n"
|
||||
" socket.close()\n"
|
||||
|
||||
6
src/module/websocket.wren
vendored
6
src/module/websocket.wren
vendored
@ -112,7 +112,7 @@ class WebSocket {
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
if (chunk == null || chunk.bytes.count == 0) {
|
||||
Fiber.abort("Connection closed during handshake")
|
||||
}
|
||||
response = response + chunk
|
||||
@ -428,7 +428,7 @@ class WebSocket {
|
||||
readBytes_(count) {
|
||||
while (Bytes.length(_readBuffer) < count) {
|
||||
var chunk = _socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
if (chunk == null || chunk.bytes.count == 0) {
|
||||
if (Bytes.length(_readBuffer) == 0) return null
|
||||
var result = _readBuffer
|
||||
_readBuffer = ""
|
||||
@ -469,7 +469,7 @@ class WebSocketServer {
|
||||
var request = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
if (chunk == null || chunk.bytes.count == 0) {
|
||||
socket.close()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ static const char* websocketModuleSource =
|
||||
" var response = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) {\n"
|
||||
" Fiber.abort(\"Connection closed during handshake\")\n"
|
||||
" }\n"
|
||||
" response = response + chunk\n"
|
||||
@ -432,7 +432,7 @@ static const char* websocketModuleSource =
|
||||
" readBytes_(count) {\n"
|
||||
" while (Bytes.length(_readBuffer) < count) {\n"
|
||||
" var chunk = _socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) {\n"
|
||||
" if (Bytes.length(_readBuffer) == 0) return null\n"
|
||||
" var result = _readBuffer\n"
|
||||
" _readBuffer = \"\"\n"
|
||||
@ -473,7 +473,7 @@ static const char* websocketModuleSource =
|
||||
" var request = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" if (chunk == null || chunk.bytes.count == 0) {\n"
|
||||
" socket.close()\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
|
||||
8
test/await/async_parameterized.wren
vendored
Normal file
8
test/await/async_parameterized.wren
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var delayed = async { |ms| ms }
|
||||
|
||||
System.print(await delayed(1)) // expect: 1
|
||||
System.print(await delayed(2)) // expect: 2
|
||||
7
test/await/async_parameterized_capture.wren
vendored
Normal file
7
test/await/async_parameterized_capture.wren
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var base = 100
|
||||
var addBase = async { |x| base + x }
|
||||
|
||||
var f = addBase.call(42)
|
||||
System.print(await f) // expect: 142
|
||||
17
test/await/async_parameterized_concurrent.wren
vendored
Normal file
17
test/await/async_parameterized_concurrent.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
var order = []
|
||||
|
||||
var task = async { |name, ms|
|
||||
await Timer.sleep(ms)
|
||||
order.add(name)
|
||||
name
|
||||
}
|
||||
|
||||
var f1 = task.call("slow", 2)
|
||||
var f2 = task.call("fast", 1)
|
||||
|
||||
await f1
|
||||
await f2
|
||||
System.print(order) // expect: [fast, slow]
|
||||
7
test/await/async_parameterized_multi.wren
vendored
Normal file
7
test/await/async_parameterized_multi.wren
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var add = async { |a, b| a + b }
|
||||
|
||||
System.print(await add(3, 4)) // expect: 7
|
||||
17
test/await/await_fn_block_arg.wren
vendored
Normal file
17
test/await/await_fn_block_arg.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var transform = async { |list, fn|
|
||||
var result = []
|
||||
for (item in list) {
|
||||
result.add(fn.call(item))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var doubled = await transform([1, 2, 3]) { |x| x * 2 }
|
||||
System.print(doubled) // expect: [2, 4, 6]
|
||||
|
||||
var squared = await transform([2, 3, 4]) { |x| x * x }
|
||||
System.print(squared) // expect: [4, 9, 16]
|
||||
7
test/await/await_fn_call.wren
vendored
Normal file
7
test/await/await_fn_call.wren
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var double = async { |x| x * 2 }
|
||||
|
||||
System.print(await double(21)) // expect: 42
|
||||
18
test/await/await_fn_chained.wren
vendored
Normal file
18
test/await/await_fn_chained.wren
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var getList = async { |x| [x, x * 2, x * 3] }
|
||||
var getString = async { |s| s }
|
||||
|
||||
var list = await getList(5)
|
||||
System.print(list.count) // expect: 3
|
||||
|
||||
var list2 = await getList(10)
|
||||
System.print(list2[1]) // expect: 20
|
||||
|
||||
var str = await getString("hello")
|
||||
System.print(str.count) // expect: 5
|
||||
|
||||
var str2 = await getString("world")
|
||||
System.print(str2.toList[0]) // expect: w
|
||||
50
test/await/await_fn_class_api.wren
vendored
Normal file
50
test/await/await_fn_class_api.wren
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
class MockApi {
|
||||
static fetchUser { async { |id|
|
||||
Timer.sleep(1)
|
||||
return {"id": id, "name": "User%(id)", "active": true}
|
||||
}}
|
||||
|
||||
static fetchPosts { async { |userId|
|
||||
Timer.sleep(1)
|
||||
return [
|
||||
{"id": 1, "userId": userId, "title": "Post 1"},
|
||||
{"id": 2, "userId": userId, "title": "Post 2"}
|
||||
]
|
||||
}}
|
||||
|
||||
static createPost { async { |userId, title|
|
||||
Timer.sleep(1)
|
||||
return {"id": 3, "userId": userId, "title": title, "created": true}
|
||||
}}
|
||||
|
||||
static getUserWithPosts(userId) {
|
||||
var fetchUser = MockApi.fetchUser
|
||||
var fetchPosts = MockApi.fetchPosts
|
||||
var user = await fetchUser(userId)
|
||||
var posts = await fetchPosts(userId)
|
||||
|
||||
user["posts"] = posts
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
var fetchUser = MockApi.fetchUser
|
||||
var user = await fetchUser(42)
|
||||
System.print(user["name"]) // expect: User42
|
||||
|
||||
var fetchPosts = MockApi.fetchPosts
|
||||
var posts = await fetchPosts(42)
|
||||
System.print(posts.count) // expect: 2
|
||||
|
||||
var createPost = MockApi.createPost
|
||||
var newPost = await createPost(42, "New Post")
|
||||
System.print(newPost["created"]) // expect: true
|
||||
|
||||
var fullUser = MockApi.getUserWithPosts(99)
|
||||
System.print(fullUser["name"]) // expect: User99
|
||||
System.print(fullUser["posts"].count) // expect: 2
|
||||
54
test/await/await_fn_class_chain.wren
vendored
Normal file
54
test/await/await_fn_class_chain.wren
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
class DataProcessor {
|
||||
static parse { async { |text| text.split(",") } }
|
||||
static filter { async { |list, fn|
|
||||
var result = []
|
||||
for (item in list) {
|
||||
if (fn.call(item)) result.add(item)
|
||||
}
|
||||
return result
|
||||
}}
|
||||
static transform { async { |list, fn|
|
||||
var result = []
|
||||
for (item in list) {
|
||||
result.add(fn.call(item))
|
||||
}
|
||||
return result
|
||||
}}
|
||||
static join { async { |list, sep| list.join(sep) } }
|
||||
|
||||
static pipeline(data) {
|
||||
var parse = DataProcessor.parse
|
||||
var filter = DataProcessor.filter
|
||||
var transform = DataProcessor.transform
|
||||
var join = DataProcessor.join
|
||||
var parsed = await parse(data)
|
||||
var filtered = await filter(parsed) { |s| s.count > 4 }
|
||||
var transformed = await transform(filtered) { |s| s.toList[0] }
|
||||
return await join(transformed, "-")
|
||||
}
|
||||
}
|
||||
|
||||
var data = "apple,banana,cherry,date"
|
||||
|
||||
var parse = DataProcessor.parse
|
||||
var parsed = await parse(data)
|
||||
System.print(parsed) // expect: [apple, banana, cherry, date]
|
||||
|
||||
var filter = DataProcessor.filter
|
||||
var filtered = await filter(parsed) { |s| s.count > 5 }
|
||||
System.print(filtered) // expect: [banana, cherry]
|
||||
|
||||
var transform = DataProcessor.transform
|
||||
var transformed = await transform(filtered) { |s| s.toList[0] }
|
||||
System.print(transformed) // expect: [b, c]
|
||||
|
||||
var join = DataProcessor.join
|
||||
var joined = await join(transformed, "-")
|
||||
System.print(joined) // expect: b-c
|
||||
|
||||
var result = DataProcessor.pipeline("one,two,three,four,five")
|
||||
System.print(result) // expect: t
|
||||
50
test/await/await_fn_class_instance.wren
vendored
Normal file
50
test/await/await_fn_class_instance.wren
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
class Counter {
|
||||
construct new(start) {
|
||||
_value = start
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
add(n) {
|
||||
var op = async { |x|
|
||||
_value = _value + x
|
||||
return _value
|
||||
}
|
||||
return await op(n)
|
||||
}
|
||||
|
||||
subtract(n) {
|
||||
var op = async { |x|
|
||||
_value = _value - x
|
||||
return _value
|
||||
}
|
||||
return await op(n)
|
||||
}
|
||||
|
||||
reset() {
|
||||
var op = async {
|
||||
_value = 0
|
||||
return _value
|
||||
}
|
||||
return await op()
|
||||
}
|
||||
}
|
||||
|
||||
var counter = Counter.new(10)
|
||||
System.print(counter.value) // expect: 10
|
||||
|
||||
System.print(counter.add(5)) // expect: 15
|
||||
System.print(counter.value) // expect: 15
|
||||
|
||||
System.print(counter.subtract(3)) // expect: 12
|
||||
System.print(counter.value) // expect: 12
|
||||
|
||||
System.print(counter.reset()) // expect: 0
|
||||
System.print(counter.value) // expect: 0
|
||||
|
||||
System.print(counter.add(10)) // expect: 10
|
||||
System.print(counter.subtract(3)) // expect: 7
|
||||
65
test/await/await_fn_class_service.wren
vendored
Normal file
65
test/await/await_fn_class_service.wren
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
class CacheService {
|
||||
construct new() {
|
||||
_cache = {}
|
||||
}
|
||||
|
||||
get(key) {
|
||||
var op = async { |k|
|
||||
Timer.sleep(1)
|
||||
return _cache.containsKey(k) ? _cache[k] : null
|
||||
}
|
||||
return await op(key)
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
var op = async { |k, v|
|
||||
Timer.sleep(1)
|
||||
_cache[k] = v
|
||||
return true
|
||||
}
|
||||
return await op(key, value)
|
||||
}
|
||||
|
||||
has(key) {
|
||||
var op = async { |k|
|
||||
return _cache.containsKey(k)
|
||||
}
|
||||
return await op(key)
|
||||
}
|
||||
|
||||
clear() {
|
||||
var op = async {
|
||||
_cache = {}
|
||||
return true
|
||||
}
|
||||
return await op()
|
||||
}
|
||||
|
||||
getOrSet(key, defaultValue) {
|
||||
if (this.has(key)) {
|
||||
return this.get(key)
|
||||
}
|
||||
this.set(key, defaultValue)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
var cache = CacheService.new()
|
||||
|
||||
System.print(cache.has("foo")) // expect: false
|
||||
System.print(cache.get("foo")) // expect: null
|
||||
|
||||
System.print(cache.set("foo", "bar")) // expect: true
|
||||
System.print(cache.has("foo")) // expect: true
|
||||
System.print(cache.get("foo")) // expect: bar
|
||||
|
||||
System.print(cache.clear()) // expect: true
|
||||
System.print(cache.has("foo")) // expect: false
|
||||
|
||||
System.print(cache.getOrSet("baz", "default")) // expect: default
|
||||
System.print(cache.get("baz")) // expect: default
|
||||
33
test/await/await_fn_class_static.wren
vendored
Normal file
33
test/await/await_fn_class_static.wren
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
class Calculator {
|
||||
static add { async { |a, b| a + b } }
|
||||
static multiply { async { |a, b| a * b } }
|
||||
static square { async { |x| x * x } }
|
||||
static constant { async { 42 } }
|
||||
|
||||
static compute(a, b) {
|
||||
var addFn = Calculator.add
|
||||
var multiplyFn = Calculator.multiply
|
||||
var sum = await addFn(a, b)
|
||||
var product = await multiplyFn(a, b)
|
||||
return [sum, product]
|
||||
}
|
||||
}
|
||||
|
||||
var add = Calculator.add
|
||||
System.print(await add(3, 4)) // expect: 7
|
||||
|
||||
var multiply = Calculator.multiply
|
||||
System.print(await multiply(5, 6)) // expect: 30
|
||||
|
||||
var square = Calculator.square
|
||||
System.print(await square(8)) // expect: 64
|
||||
|
||||
var constant = Calculator.constant
|
||||
System.print(await constant()) // expect: 42
|
||||
|
||||
var results = Calculator.compute(2, 3)
|
||||
System.print(results) // expect: [5, 6]
|
||||
23
test/await/await_fn_closure_capture.wren
vendored
Normal file
23
test/await/await_fn_closure_capture.wren
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var multiplier = 10
|
||||
var scale = async { |x| x * multiplier }
|
||||
|
||||
System.print(await scale(5)) // expect: 50
|
||||
|
||||
multiplier = 100
|
||||
System.print(await scale(5)) // expect: 500
|
||||
|
||||
var prefix = "Result: "
|
||||
var format = async { |x| prefix + x.toString }
|
||||
System.print(await format(42)) // expect: Result: 42
|
||||
|
||||
var outer = "outer"
|
||||
var fn = Fn.new {
|
||||
var inner = "inner"
|
||||
var capture = async { |x| outer + "-" + inner + "-" + x }
|
||||
return await capture("arg")
|
||||
}
|
||||
System.print(fn.call()) // expect: outer-inner-arg
|
||||
24
test/await/await_fn_in_condition.wren
vendored
Normal file
24
test/await/await_fn_in_condition.wren
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var isPositive = async { |x| x > 0 }
|
||||
var getValue = async { |x| x }
|
||||
|
||||
if (await isPositive(5)) {
|
||||
System.print("positive") // expect: positive
|
||||
}
|
||||
|
||||
if (await isPositive(-5)) {
|
||||
System.print("unreachable")
|
||||
} else {
|
||||
System.print("negative") // expect: negative
|
||||
}
|
||||
|
||||
var result = await isPositive(10) ? "yes" : "no"
|
||||
System.print(result) // expect: yes
|
||||
|
||||
while (await getValue(false)) {
|
||||
System.print("unreachable")
|
||||
}
|
||||
System.print("after while") // expect: after while
|
||||
12
test/await/await_fn_in_expression.wren
vendored
Normal file
12
test/await/await_fn_in_expression.wren
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var getValue = async { |x| x }
|
||||
var double = async { |x| x * 2 }
|
||||
|
||||
System.print(1 + await getValue(2)) // expect: 3
|
||||
System.print(await double(3) + await double(4)) // expect: 14
|
||||
System.print(await getValue(10) * 5) // expect: 50
|
||||
System.print(100 - await getValue(30)) // expect: 70
|
||||
System.print((await getValue(4)) * (await getValue(5))) // expect: 20
|
||||
17
test/await/await_fn_in_loop.wren
vendored
Normal file
17
test/await/await_fn_in_loop.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var double = async { |x| x * 2 }
|
||||
|
||||
var sum = 0
|
||||
for (i in [1, 2, 3, 4, 5]) {
|
||||
sum = sum + await double(i)
|
||||
}
|
||||
System.print(sum) // expect: 30
|
||||
|
||||
var results = []
|
||||
for (i in 1..3) {
|
||||
results.add(await double(i))
|
||||
}
|
||||
System.print(results) // expect: [2, 4, 6]
|
||||
21
test/await/await_fn_local_var.wren
vendored
Normal file
21
test/await/await_fn_local_var.wren
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
class Test {
|
||||
static run() {
|
||||
var localDouble = async { |x| x * 2 }
|
||||
var localAdd = async { |a, b| a + b }
|
||||
|
||||
System.print(await localDouble(5)) // expect: 10
|
||||
System.print(await localAdd(3, 4)) // expect: 7
|
||||
|
||||
var nested = Fn.new {
|
||||
var innerFn = async { |x| x * 100 }
|
||||
return await innerFn(3)
|
||||
}
|
||||
System.print(nested.call()) // expect: 300
|
||||
}
|
||||
}
|
||||
|
||||
Test.run()
|
||||
9
test/await/await_fn_many_args.wren
vendored
Normal file
9
test/await/await_fn_many_args.wren
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var sum4 = async { |a, b, c, d| a + b + c + d }
|
||||
var sum5 = async { |a, b, c, d, e| a + b + c + d + e }
|
||||
|
||||
System.print(await sum4(1, 2, 3, 4)) // expect: 10
|
||||
System.print(await sum5(1, 2, 3, 4, 5)) // expect: 15
|
||||
17
test/await/await_fn_mixed_syntax.wren
vendored
Normal file
17
test/await/await_fn_mixed_syntax.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var double = async { |x| x * 2 }
|
||||
var triple = async { |x| x * 3 }
|
||||
|
||||
System.print(await double(5)) // expect: 10
|
||||
System.print(await double.call(5)) // expect: 10
|
||||
|
||||
var f1 = double.call(10)
|
||||
var f2 = triple.call(10)
|
||||
System.print(await f1) // expect: 20
|
||||
System.print(await f2) // expect: 30
|
||||
|
||||
System.print(await double(await triple.call(2))) // expect: 12
|
||||
System.print(await double.call(await triple(2))) // expect: 12
|
||||
20
test/await/await_fn_module_var.wren
vendored
Normal file
20
test/await/await_fn_module_var.wren
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var moduleDouble = async { |x| x * 2 }
|
||||
var moduleAdd = async { |a, b| a + b }
|
||||
|
||||
class Helper {
|
||||
static useModuleVar(x) {
|
||||
return await moduleDouble(x)
|
||||
}
|
||||
|
||||
static combineModuleVars(a, b) {
|
||||
return await moduleAdd(await moduleDouble(a), await moduleDouble(b))
|
||||
}
|
||||
}
|
||||
|
||||
System.print(await moduleDouble(10)) // expect: 20
|
||||
System.print(Helper.useModuleVar(15)) // expect: 30
|
||||
System.print(Helper.combineModuleVars(5, 10)) // expect: 30
|
||||
10
test/await/await_fn_nested.wren
vendored
Normal file
10
test/await/await_fn_nested.wren
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var double = async { |x| x * 2 }
|
||||
var addTen = async { |x| x + 10 }
|
||||
|
||||
System.print(await addTen(await double(5))) // expect: 20
|
||||
System.print(await double(await addTen(3))) // expect: 26
|
||||
System.print(await double(await double(await double(2)))) // expect: 16
|
||||
7
test/await/await_fn_no_args.wren
vendored
Normal file
7
test/await/await_fn_no_args.wren
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var getValue = async { 42 }
|
||||
|
||||
System.print(await getValue()) // expect: 42
|
||||
30
test/await/await_fn_parallel.wren
vendored
Normal file
30
test/await/await_fn_parallel.wren
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
var delayedValue = async { |ms, val|
|
||||
Timer.sleep(ms)
|
||||
return val
|
||||
}
|
||||
|
||||
var f1 = delayedValue.call(10, "first")
|
||||
var f2 = delayedValue.call(10, "second")
|
||||
var f3 = delayedValue.call(10, "third")
|
||||
|
||||
System.print(await f1) // expect: first
|
||||
System.print(await f2) // expect: second
|
||||
System.print(await f3) // expect: third
|
||||
|
||||
var compute = async { |x| x * x }
|
||||
|
||||
var futures = []
|
||||
for (i in 1..5) {
|
||||
futures.add(compute.call(i))
|
||||
}
|
||||
|
||||
var results = []
|
||||
for (f in futures) {
|
||||
results.add(await f)
|
||||
}
|
||||
System.print(results) // expect: [1, 4, 9, 16, 25]
|
||||
18
test/await/await_fn_return_types.wren
vendored
Normal file
18
test/await/await_fn_return_types.wren
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var getString = async { |x| x.toString }
|
||||
var getNum = async { |x| x * 2 }
|
||||
var getList = async { |x| [x, x, x] }
|
||||
var getMap = async { |k, v| {k: v} }
|
||||
var getNull = async { null }
|
||||
var getBool = async { |x| x > 0 }
|
||||
|
||||
System.print(await getString("hello")) // expect: hello
|
||||
System.print(await getNum(21)) // expect: 42
|
||||
System.print(await getList(7)) // expect: [7, 7, 7]
|
||||
System.print(await getMap("key", 123)) // expect: {key: 123}
|
||||
System.print(await getNull()) // expect: null
|
||||
System.print(await getBool(5)) // expect: true
|
||||
System.print(await getBool(-5)) // expect: false
|
||||
25
test/await/await_fn_sequential.wren
vendored
Normal file
25
test/await/await_fn_sequential.wren
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var step1 = async { |x| x + 10 }
|
||||
var step2 = async { |x| x * 2 }
|
||||
var step3 = async { |x| x - 5 }
|
||||
|
||||
var result = 5
|
||||
result = await step1(result)
|
||||
System.print(result) // expect: 15
|
||||
result = await step2(result)
|
||||
System.print(result) // expect: 30
|
||||
result = await step3(result)
|
||||
System.print(result) // expect: 25
|
||||
|
||||
var pipeline = async { |x|
|
||||
var r1 = await step1(x)
|
||||
var r2 = await step2(r1)
|
||||
var r3 = await step3(r2)
|
||||
return r3
|
||||
}
|
||||
|
||||
System.print(await pipeline(0)) // expect: 15
|
||||
System.print(await pipeline(10)) // expect: 35
|
||||
28
test/await/await_fn_upvalue.wren
vendored
Normal file
28
test/await/await_fn_upvalue.wren
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
|
||||
var makeMultiplier = Fn.new { |factor|
|
||||
var multiply = async { |x| x * factor }
|
||||
return multiply
|
||||
}
|
||||
|
||||
var times2 = makeMultiplier.call(2)
|
||||
var times10 = makeMultiplier.call(10)
|
||||
|
||||
System.print(await times2(5)) // expect: 10
|
||||
System.print(await times10(5)) // expect: 50
|
||||
|
||||
var counter = 0
|
||||
var makeCounter = Fn.new {
|
||||
var increment = async { |step|
|
||||
counter = counter + step
|
||||
return counter
|
||||
}
|
||||
return increment
|
||||
}
|
||||
|
||||
var inc = makeCounter.call()
|
||||
System.print(await inc(1)) // expect: 1
|
||||
System.print(await inc(5)) // expect: 6
|
||||
System.print(await inc(10)) // expect: 16
|
||||
Loading…
Reference in New Issue
Block a user