feat: add pexpect and yaml modules with jinja from-import support

Add new pexpect module for process spawning and interaction, including Spawn class with expect, sendline, and timeout handling. Introduce yaml module for YAML parsing with support for nested structures, lists, and data types. Extend jinja template engine with brace tokenization and FromImportNode for macro imports across templates. Register all new foreign functions in modules.c and include generated demo scripts.
This commit is contained in:
2026-01-25 21:09:29 +00:00
parent 5c2269f6a7
commit 79ff93c9a2
27 changed files with 3435 additions and 2 deletions
+19
View File
@@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader, ChoiceLoader
var loader1 = DictLoader.new({"a.html": "Template A"})
var loader2 = DictLoader.new({"b.html": "Template B"})
var choice = ChoiceLoader.new([loader1, loader2])
System.print(choice.exists("a.html")) // expect: true
System.print(choice.exists("b.html")) // expect: true
System.print(choice.exists("c.html")) // expect: false
System.print(choice.getSource("a.html")) // expect: Template A
System.print(choice.getSource("b.html")) // expect: Template B
var env = Environment.new(choice)
var tplA = env.getTemplate("a.html")
var tplB = env.getTemplate("b.html")
System.print(tplA.render({})) // expect: Template A
System.print(tplB.render({})) // expect: Template B
+24
View File
@@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "jinja" for Environment, DictLoader
var tpl1 = "{\x25 set data = {\"name\": \"Alice\", \"age\": 30} \x25}Name: {{ data.name }}, Age: {{ data.age }}"
var tpl2 = "{\x25 set empty = {} \x25}Done"
var tpl3 = "{\x25 set single = {\"key\": \"value\"} \x25}Key: {{ single.key }}"
var loader = DictLoader.new({
"tpl1.html": tpl1,
"tpl2.html": tpl2,
"tpl3.html": tpl3
})
var env = Environment.new(loader)
var result1 = env.getTemplate("tpl1.html").render({})
System.print(result1) // expect: Name: Alice, Age: 30
var result2 = env.getTemplate("tpl2.html").render({})
System.print(result2) // expect: Done
var result3 = env.getTemplate("tpl3.html").render({})
System.print(result3) // expect: Key: value
+15
View File
@@ -0,0 +1,15 @@
// retoor <retoor@molodetz.nl>
// nontest
import "pexpect" for Spawn
import "timer" for Timer
System.print("Creating child")
var child = Spawn.new("echo test")
System.print("Child created, pid: %(child.pid)")
System.print("isalive: %(child.isalive)")
Timer.sleep(500)
System.print("After sleep, isalive: %(child.isalive)")
System.print("buffer: [%(child.buffer)]")
child.close()
System.print("Done")
+21
View File
@@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "pexpect" for Spawn, Pexpect
var child = Spawn.new("echo 'hello world'")
var result = child.expect(["world", "hello"])
System.print(result) // expect: 1
System.print(child.after) // expect: hello
child.close()
child = Spawn.new("printf 'abc123def'")
result = child.expectExact(["123"])
System.print(result) // expect: 0
System.print(child.before) // expect: abc
System.print(child.after) // expect: 123
child.close()
child = Spawn.new("echo 'test multiple patterns'")
result = child.expect(["foo", "bar", "multiple"])
System.print(result) // expect: 2
child.close()
+16
View File
@@ -0,0 +1,16 @@
// retoor <retoor@molodetz.nl>
import "pexpect" for Spawn, Pexpect
var child = Spawn.new("echo test")
System.print(child.pid > 0) // expect: true
System.print(child.timeout) // expect: 30
child.timeout = 5
System.print(child.timeout) // expect: 5
System.print(child.maxread) // expect: 2000
System.print(child.searchwindowsize) // expect: 0
child.expect(["test"])
child.wait()
System.print(child.exitstatus == 0) // expect: true
child.close()
+23
View File
@@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
// nontest
import "pexpect" for Spawn
import "timer" for Timer
System.print("1. Creating child")
var child = Spawn.new("echo hello")
System.print("2. Sleeping 200ms")
Timer.sleep(200)
System.print("3. Buffer: [%(child.buffer)]")
System.print("4. isalive: %(child.isalive)")
System.print("5. Calling readNonblocking")
var data = child.readNonblocking(100, 1)
System.print("6. Data: [%(data)]")
System.print("7. Buffer after read: [%(child.buffer)]")
System.print("8. Closing")
child.close()
System.print("Done")
+9
View File
@@ -0,0 +1,9 @@
// retoor <retoor@molodetz.nl>
// nontest
import "pexpect" for Spawn, Pexpect
System.print("Testing Pexpect.run")
var output = Pexpect.run("echo test")
System.print("Output: [%(output)]")
System.print("Done")
+14
View File
@@ -0,0 +1,14 @@
// retoor <retoor@molodetz.nl>
import "pexpect" for Spawn, Pexpect
var child = Spawn.new("echo hello")
var result = child.expect(["hello"])
System.print(result) // expect: 0
child = Spawn.new("cat")
child.sendline("test input")
var idx = child.expect(["test input"])
System.print(idx) // expect: 0
child.sendeof()
child.close()
+10
View File
@@ -0,0 +1,10 @@
// retoor <retoor@molodetz.nl>
import "pexpect" for Spawn, Pexpect
var child = Spawn.new("sleep 10")
child.timeout = 0.5
var result = child.expect(["never_match"])
System.print(result) // expect: -2
child.terminate(true)
child.close()
+18
View File
@@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var yaml = "
# This is a comment
name: test
# Another comment
value: 123
"
var data = Yaml.parse(yaml)
System.print(data["name"]) // expect: test
System.print(data["value"]) // expect: 123
var inlineComment = "name: test # inline comment"
var data2 = Yaml.parse(inlineComment)
System.print(data2["name"]) // expect: test
+25
View File
@@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var yaml = "
sections:
- title: Getting Started
pages:
- file: index
title: Overview
- file: installation
title: Installation
- title: API Reference
pages:
- file: json
title: json
"
var nav = Yaml.parse(yaml)
System.print(nav["sections"][0]["title"]) // expect: Getting Started
System.print(nav["sections"][0]["pages"][0]["file"]) // expect: index
System.print(nav["sections"][0]["pages"][0]["title"]) // expect: Overview
System.print(nav["sections"][0]["pages"][1]["file"]) // expect: installation
System.print(nav["sections"][1]["title"]) // expect: API Reference
System.print(nav["sections"][1]["pages"][0]["file"]) // expect: json
+12
View File
@@ -0,0 +1,12 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var empty = Yaml.parse("")
System.print(empty) // expect: null
var nullInput = Yaml.parse(null)
System.print(nullInput) // expect: null
var whitespace = Yaml.parse(" \n \n ")
System.print(whitespace) // expect: null
+21
View File
@@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var yaml = "items:\n - first\n - second\n - third"
var data = Yaml.parse(yaml)
System.print(data["items"][0]) // expect: first
System.print(data["items"][1]) // expect: second
System.print(data["items"][2]) // expect: third
System.print(data["items"].count) // expect: 3
var simple = "- one\n- two\n- three"
var list = Yaml.parse(simple)
System.print(list[0]) // expect: one
System.print(list[1]) // expect: two
System.print(list.count) // expect: 3
var numbers = "- 1\n- 2\n- 3"
var numList = Yaml.parse(numbers)
System.print(numList[0]) // expect: 1
System.print(numList[1]) // expect: 2
+17
View File
@@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var yaml = "server:\n host: localhost\n port: 8080"
var config = Yaml.parse(yaml)
System.print(config["server"]["host"]) // expect: localhost
System.print(config["server"]["port"]) // expect: 8080
var deep = "
level1:
level2:
level3:
value: deep
"
var deepConfig = Yaml.parse(deep)
System.print(deepConfig["level1"]["level2"]["level3"]["value"]) // expect: deep
+27
View File
@@ -0,0 +1,27 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var simple = Yaml.parse("name: test")
System.print(simple["name"]) // expect: test
var num = Yaml.parse("count: 42")
System.print(num["count"]) // expect: 42
var float = Yaml.parse("value: 3.14")
System.print(float["value"]) // expect: 3.14
var bool = Yaml.parse("active: true")
System.print(bool["active"]) // expect: true
var boolFalse = Yaml.parse("active: false")
System.print(boolFalse["active"]) // expect: false
var nullVal = Yaml.parse("value: null")
System.print(nullVal["value"]) // expect: null
var quoted = Yaml.parse("name: \"hello world\"")
System.print(quoted["name"]) // expect: hello world
var singleQuoted = Yaml.parse("name: 'hello world'")
System.print(singleQuoted["name"]) // expect: hello world
+25
View File
@@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "yaml" for Yaml
var simple = {"name": "test", "value": 42}
var str = Yaml.stringify(simple)
System.print(str.contains("name: test")) // expect: true
System.print(str.contains("value: 42")) // expect: true
var withList = {"items": ["one", "two", "three"]}
var listStr = Yaml.stringify(withList)
System.print(listStr.contains("items:")) // expect: true
System.print(listStr.contains("- one")) // expect: true
System.print(listStr.contains("- two")) // expect: true
var nested = {"server": {"host": "localhost", "port": 8080}}
var nestedStr = Yaml.stringify(nested)
System.print(nestedStr.contains("server:")) // expect: true
System.print(nestedStr.contains("host: localhost")) // expect: true
System.print(Yaml.stringify(null)) // expect: null
System.print(Yaml.stringify(true)) // expect: true
System.print(Yaml.stringify(false)) // expect: false
System.print(Yaml.stringify(42)) // expect: 42
System.print(Yaml.stringify("hello")) // expect: hello