This commit is contained in:
retoor 2026-01-25 11:42:49 +01:00
parent 4de99d477f
commit 90618f2336
14 changed files with 694 additions and 25 deletions

144
apps/generator2.wren vendored Executable file
View File

@ -0,0 +1,144 @@
#!/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)
}
}
}
SourceCodeGenerator.run()

2
apps/merge_all_wren.wren vendored Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/usr/local/bin/wren
import "io" for File, Directory
class WrenFileReader {

2
apps/merge_docs.wren vendored Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/usr/local/bin/wren
import "pathlib" for Path
var startTime = System.clock

1
apps/phonebook.wren vendored Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/local/bin/wren
// wren source code generator v1.0
// Generation time: 0.101419

View File

@ -116,6 +116,7 @@ OBJECTS += $(OBJDIR)/crypto.o
OBJECTS += $(OBJDIR)/datetime.o
OBJECTS += $(OBJDIR)/dns.o
OBJECTS += $(OBJDIR)/env.o
OBJECTS += $(OBJDIR)/fswatch.o
OBJECTS += $(OBJDIR)/io.o
OBJECTS += $(OBJDIR)/json.o
OBJECTS += $(OBJDIR)/math_module.o
@ -143,12 +144,14 @@ OBJECTS += $(OBJDIR)/repl.o
OBJECTS += $(OBJDIR)/scheduler.o
OBJECTS += $(OBJDIR)/signal.o
OBJECTS += $(OBJDIR)/signal_module.o
OBJECTS += $(OBJDIR)/sysinfo.o
OBJECTS += $(OBJDIR)/sqlite3.o
OBJECTS += $(OBJDIR)/sqlite_module.o
OBJECTS += $(OBJDIR)/subprocess.o
OBJECTS += $(OBJDIR)/strutil.o
OBJECTS += $(OBJDIR)/pathlib.o
OBJECTS += $(OBJDIR)/tls.o
OBJECTS += $(OBJDIR)/udp_module.o
OBJECTS += $(OBJDIR)/stream.o
OBJECTS += $(OBJDIR)/strscpy.o
OBJECTS += $(OBJDIR)/sysinfo-loadavg.o
@ -401,6 +404,9 @@ $(OBJDIR)/dns.o: ../../src/module/dns.c
$(OBJDIR)/env.o: ../../src/module/env.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/fswatch.o: ../../src/module/fswatch.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/io.o: ../../src/module/io.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
@ -428,6 +434,9 @@ $(OBJDIR)/timer1.o: ../../src/module/timer.c
$(OBJDIR)/signal_module.o: ../../src/module/signal_module.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/sysinfo.o: ../../src/module/sysinfo.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/subprocess.o: ../../src/module/subprocess.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
@ -449,6 +458,9 @@ $(OBJDIR)/pathlib.o: ../../src/module/pathlib.c
$(OBJDIR)/tls.o: ../../src/module/tls.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/udp_module.o: ../../src/module/udp.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
-include $(OBJECTS:%.o=%.d)
ifneq (,$(PCH))

View File

@ -12,6 +12,7 @@
#include "datetime.wren.inc"
#include "dns.wren.inc"
#include "env.wren.inc"
#include "fswatch.wren.inc"
#include "html.wren.inc"
#include "http.wren.inc"
#include "io.wren.inc"
@ -26,11 +27,13 @@
#include "repl.wren.inc"
#include "scheduler.wren.inc"
#include "signal.wren.inc"
#include "sysinfo.wren.inc"
#include "sqlite.wren.inc"
#include "subprocess.wren.inc"
#include "tempfile.wren.inc"
#include "timer.wren.inc"
#include "tls.wren.inc"
#include "udp.wren.inc"
#include "uuid.wren.inc"
#include "wdantic.wren.inc"
#include "web.wren.inc"
@ -60,6 +63,11 @@ extern void envGet(WrenVM* vm);
extern void envSet(WrenVM* vm);
extern void envDelete(WrenVM* vm);
extern void envAll(WrenVM* vm);
extern void fswatchAllocate(WrenVM* vm);
extern void fswatchFinalize(void* data);
extern void fswatchStart(WrenVM* vm);
extern void fswatchStop(WrenVM* vm);
extern void fswatchIsActive(WrenVM* vm);
extern void jsonParse(WrenVM* vm);
extern void mathSin(WrenVM* vm);
extern void mathCos(WrenVM* vm);
@ -84,7 +92,9 @@ extern void mathRound(WrenVM* vm);
extern void mathAbs(WrenVM* vm);
extern void fileAllocate(WrenVM* vm);
extern void fileFinalize(void* data);
extern void fileCopy(WrenVM* vm);
extern void fileDelete(WrenVM* vm);
extern void fileMove(WrenVM* vm);
extern void fileOpen(WrenVM* vm);
extern void fileSizePath(WrenVM* vm);
extern void fileClose(WrenVM* vm);
@ -115,15 +125,23 @@ extern void statSpecialDevice(WrenVM* vm);
extern void statUser(WrenVM* vm);
extern void statIsDirectory(WrenVM* vm);
extern void statIsFile(WrenVM* vm);
extern void statMtime(WrenVM* vm);
extern void statAtime(WrenVM* vm);
extern void statCtime(WrenVM* vm);
extern void stdinIsRaw(WrenVM* vm);
extern void stdinIsRawSet(WrenVM* vm);
extern void stdinIsTerminal(WrenVM* vm);
extern void stdinWindowSize(WrenVM* vm);
extern void stdinReadStart(WrenVM* vm);
extern void stdinReadStop(WrenVM* vm);
extern void stdoutFlush(WrenVM* vm);
extern void stderrWrite(WrenVM* vm);
extern void schedulerCaptureMethods(WrenVM* vm);
extern void timerStartTimer(WrenVM* vm);
extern void timerStartInterval(WrenVM* vm);
extern void timerStopInterval(WrenVM* vm);
extern void timerIsIntervalActive(WrenVM* vm);
extern void timerStartImmediate(WrenVM* vm);
extern void socketAllocate(WrenVM* vm);
extern void socketFinalize(void* data);
extern void socketConnect(WrenVM* vm);
@ -140,6 +158,17 @@ extern void serverClose(WrenVM* vm);
extern void signalTrap(WrenVM* vm);
extern void signalIgnore(WrenVM* vm);
extern void signalReset(WrenVM* vm);
extern void sysinfoGetCpuInfo(WrenVM* vm);
extern void sysinfoGetCpuCount(WrenVM* vm);
extern void sysinfoGetLoadAverage(WrenVM* vm);
extern void sysinfoGetTotalMemory(WrenVM* vm);
extern void sysinfoGetFreeMemory(WrenVM* vm);
extern void sysinfoGetUptime(WrenVM* vm);
extern void sysinfoGetHostname(WrenVM* vm);
extern void sysinfoGetNetworkInterfaces(WrenVM* vm);
extern void sysinfoGetHrtime(WrenVM* vm);
extern void sysinfoGetResidentMemory(WrenVM* vm);
extern void sysinfoGetConstrainedMemory(WrenVM* vm);
extern void sqliteAllocate(WrenVM* vm);
extern void sqliteFinalize(void* data);
extern void sqliteExecute(WrenVM* vm);
@ -166,6 +195,19 @@ extern void tlsSocketConnect(WrenVM* vm);
extern void tlsSocketWrite(WrenVM* vm);
extern void tlsSocketRead(WrenVM* vm);
extern void tlsSocketClose(WrenVM* vm);
extern void udpSocketAllocate(WrenVM* vm);
extern void udpSocketFinalize(void* data);
extern void udpSocketBind(WrenVM* vm);
extern void udpSocketSend(WrenVM* vm);
extern void udpSocketReceive(WrenVM* vm);
extern void udpSocketClose(WrenVM* vm);
extern void udpSocketSetBroadcast(WrenVM* vm);
extern void udpSocketSetMulticastTTL(WrenVM* vm);
extern void udpSocketSetMulticastLoopback(WrenVM* vm);
extern void udpSocketJoinMulticast(WrenVM* vm);
extern void udpSocketLeaveMulticast(WrenVM* vm);
extern void udpSocketLocalAddress(WrenVM* vm);
extern void udpSocketLocalPort(WrenVM* vm);
extern void strutilToLower(WrenVM* vm);
extern void strutilToUpper(WrenVM* vm);
extern void strutilHexEncode(WrenVM* vm);
@ -309,6 +351,15 @@ static ModuleRegistry modules[] =
STATIC_METHOD("all", envAll)
END_CLASS
END_MODULE
MODULE(fswatch)
CLASS(FileWatcher)
ALLOCATE(fswatchAllocate)
FINALIZE(fswatchFinalize)
METHOD("start_(_)", fswatchStart)
METHOD("stop_()", fswatchStop)
METHOD("isActive", fswatchIsActive)
END_CLASS
END_MODULE
MODULE(html)
END_MODULE
MODULE(http)
@ -322,7 +373,9 @@ static ModuleRegistry modules[] =
CLASS(File)
ALLOCATE(fileAllocate)
FINALIZE(fileFinalize)
STATIC_METHOD("copy_(_,_,_)", fileCopy)
STATIC_METHOD("delete_(_,_)", fileDelete)
STATIC_METHOD("move_(_,_,_)", fileMove)
STATIC_METHOD("open_(_,_,_)", fileOpen)
STATIC_METHOD("realPath_(_,_)", fileRealPath)
STATIC_METHOD("sizePath_(_,_)", fileSizePath)
@ -347,11 +400,15 @@ static ModuleRegistry modules[] =
METHOD("user", statUser)
METHOD("isDirectory", statIsDirectory)
METHOD("isFile", statIsFile)
METHOD("mtime", statMtime)
METHOD("atime", statAtime)
METHOD("ctime", statCtime)
END_CLASS
CLASS(Stdin)
STATIC_METHOD("isRaw", stdinIsRaw)
STATIC_METHOD("isRaw=(_)", stdinIsRawSet)
STATIC_METHOD("isTerminal", stdinIsTerminal)
STATIC_METHOD("windowSize", stdinWindowSize)
STATIC_METHOD("readStart_()", stdinReadStart)
STATIC_METHOD("readStop_()", stdinReadStop)
END_CLASS
@ -472,6 +529,21 @@ static ModuleRegistry modules[] =
STATIC_METHOD("reset_(_)", signalReset)
END_CLASS
END_MODULE
MODULE(sysinfo)
CLASS(SysInfo)
STATIC_METHOD("cpuInfo", sysinfoGetCpuInfo)
STATIC_METHOD("cpuCount", sysinfoGetCpuCount)
STATIC_METHOD("loadAverage", sysinfoGetLoadAverage)
STATIC_METHOD("totalMemory", sysinfoGetTotalMemory)
STATIC_METHOD("freeMemory", sysinfoGetFreeMemory)
STATIC_METHOD("uptime", sysinfoGetUptime)
STATIC_METHOD("hostname", sysinfoGetHostname)
STATIC_METHOD("networkInterfaces", sysinfoGetNetworkInterfaces)
STATIC_METHOD("hrtime", sysinfoGetHrtime)
STATIC_METHOD("residentMemory", sysinfoGetResidentMemory)
STATIC_METHOD("constrainedMemory", sysinfoGetConstrainedMemory)
END_CLASS
END_MODULE
MODULE(strutil)
CLASS(Str)
STATIC_METHOD("toLower(_)", strutilToLower)
@ -510,6 +582,10 @@ static ModuleRegistry modules[] =
MODULE(timer)
CLASS(Timer)
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
STATIC_METHOD("startInterval_(_,_)", timerStartInterval)
STATIC_METHOD("stopInterval_(_)", timerStopInterval)
STATIC_METHOD("isIntervalActive_(_)", timerIsIntervalActive)
STATIC_METHOD("startImmediate_(_)", timerStartImmediate)
END_CLASS
END_MODULE
MODULE(tls)
@ -522,6 +598,23 @@ static ModuleRegistry modules[] =
METHOD("close_()", tlsSocketClose)
END_CLASS
END_MODULE
MODULE(udp)
CLASS(UdpSocket)
ALLOCATE(udpSocketAllocate)
FINALIZE(udpSocketFinalize)
METHOD("bind_(_,_)", udpSocketBind)
METHOD("send_(_,_,_,_)", udpSocketSend)
METHOD("receive_(_)", udpSocketReceive)
METHOD("close_()", udpSocketClose)
METHOD("setBroadcast_(_)", udpSocketSetBroadcast)
METHOD("setMulticastTTL_(_)", udpSocketSetMulticastTTL)
METHOD("setMulticastLoopback_(_)", udpSocketSetMulticastLoopback)
METHOD("joinMulticast_(_,_)", udpSocketJoinMulticast)
METHOD("leaveMulticast_(_,_)", udpSocketLeaveMulticast)
METHOD("localAddress", udpSocketLocalAddress)
METHOD("localPort", udpSocketLocalPort)
END_CLASS
END_MODULE
MODULE(uuid)
END_MODULE
MODULE(wdantic)

View File

@ -189,6 +189,23 @@ void fileFinalize(void* data)
uv_fs_req_cleanup(&request);
}
static void fileCopyCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
schedulerResume(freeRequest(request), false);
}
void fileCopy(WrenVM* vm)
{
const char* src = wrenGetSlotString(vm, 1);
const char* dst = wrenGetSlotString(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
uv_fs_t* request = createRequest(fiber);
int error = uv_fs_copyfile(getLoop(), request, src, dst, 0, fileCopyCallback);
if (error != 0) schedulerResumeError(fiber, uv_strerror(error));
}
static void fileDeleteCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
@ -200,11 +217,28 @@ void fileDelete(WrenVM* vm)
const char* path = wrenGetSlotString(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
uv_fs_t* request = createRequest(fiber);
int error = uv_fs_unlink(getLoop(), request, path, fileDeleteCallback);
if (error != 0 ) schedulerResumeError(fiber, uv_strerror(error));
}
static void fileMoveCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
schedulerResume(freeRequest(request), false);
}
void fileMove(WrenVM* vm)
{
const char* src = wrenGetSlotString(vm, 1);
const char* dst = wrenGetSlotString(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
uv_fs_t* request = createRequest(fiber);
int error = uv_fs_rename(getLoop(), request, src, dst, fileMoveCallback);
if (error != 0) schedulerResumeError(fiber, uv_strerror(error));
}
static void fileOpenCallback(uv_fs_t* request)
{
if (handleRequestError(request)) return;
@ -502,6 +536,24 @@ void statIsFile(WrenVM* vm)
wrenSetSlotBool(vm, 0, S_ISREG(stat->st_mode));
}
void statMtime(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_mtim.tv_sec + (double)stat->st_mtim.tv_nsec / 1e9);
}
void statAtime(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_atim.tv_sec + (double)stat->st_atim.tv_nsec / 1e9);
}
void statCtime(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)stat->st_ctim.tv_sec + (double)stat->st_ctim.tv_nsec / 1e9);
}
// Sets up the stdin stream if not already initialized.
static void initStdin()
{
@ -563,6 +615,34 @@ void stdinIsTerminal(WrenVM* vm)
wrenSetSlotBool(vm, 0, uv_guess_handle(stdinDescriptor) == UV_TTY);
}
void stdinWindowSize(WrenVM* vm)
{
initStdin();
wrenEnsureSlots(vm, 2);
if (uv_guess_handle(stdinDescriptor) != UV_TTY)
{
wrenSetSlotNull(vm, 0);
return;
}
uv_tty_t* handle = (uv_tty_t*)stdinStream;
int width, height;
int result = uv_tty_get_winsize(handle, &width, &height);
if (result != 0)
{
wrenSetSlotNull(vm, 0);
return;
}
wrenSetSlotNewList(vm, 0);
wrenSetSlotDouble(vm, 1, (double)width);
wrenInsertInList(vm, 0, -1, 1);
wrenSetSlotDouble(vm, 1, (double)height);
wrenInsertInList(vm, 0, -1, 1);
}
void stdoutFlush(WrenVM* vm)
{
fflush(stdout);

23
src/module/io.wren vendored
View File

@ -100,6 +100,22 @@ foreign class File {
return File.open(path) {|file| file.readBytes(file.size) }
}
static write(path, data) {
return File.create(path) {|file| file.writeBytes(data, 0) }
}
static copy(src, dst) {
ensureString_(src)
ensureString_(dst)
return Scheduler.await_ { copy_(src, dst, Fiber.current) }
}
static move(src, dst) {
ensureString_(src)
ensureString_(dst)
return Scheduler.await_ { move_(src, dst, Fiber.current) }
}
// TODO: This works for directories too, so putting it on File is kind of
// lame. Consider reorganizing these classes some.
static realPath(path) {
@ -167,7 +183,9 @@ foreign class File {
if (value < 0) Fiber.abort("%(name) cannot be negative.")
}
foreign static copy_(src, dst, fiber)
foreign static delete_(path, fiber)
foreign static move_(src, dst, fiber)
foreign static open_(path, flags, fiber)
foreign static realPath_(path, fiber)
foreign static sizePath_(path, fiber)
@ -213,13 +231,16 @@ foreign class Stat {
foreign isFile
foreign isDirectory
// TODO: Other mode checks.
foreign mtime
foreign atime
foreign ctime
}
class Stdin {
foreign static isRaw
foreign static isRaw=(value)
foreign static isTerminal
foreign static windowSize
static readByte() {
return read_ {

View File

@ -104,6 +104,22 @@ static const char* ioModuleSource =
" return File.open(path) {|file| file.readBytes(file.size) }\n"
" }\n"
"\n"
" static write(path, data) {\n"
" return File.create(path) {|file| file.writeBytes(data, 0) }\n"
" }\n"
"\n"
" static copy(src, dst) {\n"
" ensureString_(src)\n"
" ensureString_(dst)\n"
" return Scheduler.await_ { copy_(src, dst, Fiber.current) }\n"
" }\n"
"\n"
" static move(src, dst) {\n"
" ensureString_(src)\n"
" ensureString_(dst)\n"
" return Scheduler.await_ { move_(src, dst, Fiber.current) }\n"
" }\n"
"\n"
" // TODO: This works for directories too, so putting it on File is kind of\n"
" // lame. Consider reorganizing these classes some.\n"
" static realPath(path) {\n"
@ -171,7 +187,9 @@ static const char* ioModuleSource =
" if (value < 0) Fiber.abort(\"%(name) cannot be negative.\")\n"
" }\n"
"\n"
" foreign static copy_(src, dst, fiber)\n"
" foreign static delete_(path, fiber)\n"
" foreign static move_(src, dst, fiber)\n"
" foreign static open_(path, flags, fiber)\n"
" foreign static realPath_(path, fiber)\n"
" foreign static sizePath_(path, fiber)\n"
@ -217,13 +235,16 @@ static const char* ioModuleSource =
"\n"
" foreign isFile\n"
" foreign isDirectory\n"
" // TODO: Other mode checks.\n"
" foreign mtime\n"
" foreign atime\n"
" foreign ctime\n"
"}\n"
"\n"
"class Stdin {\n"
" foreign static isRaw\n"
" foreign static isRaw=(value)\n"
" foreign static isTerminal\n"
" foreign static windowSize\n"
"\n"
" static readByte() {\n"
" return read_ {\n"

View File

@ -509,8 +509,27 @@ class Path is PurePath {
}
copyfile(dest) {
var content = readBytes()
var destPath = dest is Path ? dest : Path.new(dest)
destPath.writeBytes(content)
var destPath = dest is Path ? dest.toString : dest.toString
File.copy(toString, destPath)
}
size() {
var s = stat()
return s.size
}
mtime() {
var s = lstat()
return s.mtime
}
atime() {
var s = lstat()
return s.atime
}
ctime() {
var s = lstat()
return s.ctime
}
}

View File

@ -513,8 +513,27 @@ static const char* pathlibModuleSource =
" }\n"
"\n"
" copyfile(dest) {\n"
" var content = readBytes()\n"
" var destPath = dest is Path ? dest : Path.new(dest)\n"
" destPath.writeBytes(content)\n"
" var destPath = dest is Path ? dest.toString : dest.toString\n"
" File.copy(toString, destPath)\n"
" }\n"
"\n"
" size() {\n"
" var s = stat()\n"
" return s.size\n"
" }\n"
"\n"
" mtime() {\n"
" var s = lstat()\n"
" return s.mtime\n"
" }\n"
"\n"
" atime() {\n"
" var s = lstat()\n"
" return s.atime\n"
" }\n"
"\n"
" ctime() {\n"
" var s = lstat()\n"
" return s.ctime\n"
" }\n"
"}\n";

View File

@ -1,3 +1,5 @@
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
@ -7,33 +9,212 @@
#include "vm.h"
#include "wren.h"
// Called by libuv when the timer finished closing.
static void timerCloseCallback(uv_handle_t* handle)
#define MAX_INTERVAL_TIMERS 64
static WrenHandle* schedulerClass = NULL;
static WrenHandle* resumeMethod = NULL;
typedef struct {
uv_timer_t* handle;
WrenHandle* fiber;
int isActive;
int id;
} IntervalTimerData;
static IntervalTimerData intervalTimers[MAX_INTERVAL_TIMERS];
static int nextTimerId = 1;
static int initialized = 0;
static void ensureSchedulerMethods()
{
free(handle);
if (schedulerClass != NULL) return;
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "scheduler", "Scheduler", 0);
schedulerClass = wrenGetSlotHandle(vm, 0);
resumeMethod = wrenMakeCallHandle(vm, "resume_(_)");
}
static void resumeFiberWithoutRelease(WrenHandle* fiber)
{
WrenVM* vm = getVM();
ensureSchedulerMethods();
wrenEnsureSlots(vm, 2);
wrenSetSlotHandle(vm, 0, schedulerClass);
wrenSetSlotHandle(vm, 1, fiber);
WrenInterpretResult result = wrenCall(vm, resumeMethod);
if (result == WREN_RESULT_RUNTIME_ERROR)
{
uv_stop(getLoop());
setExitCode(70);
}
}
static void initTimerSystem()
{
if (initialized) return;
for (int i = 0; i < MAX_INTERVAL_TIMERS; i++) {
intervalTimers[i].handle = NULL;
intervalTimers[i].fiber = NULL;
intervalTimers[i].isActive = 0;
intervalTimers[i].id = 0;
}
initialized = 1;
}
static IntervalTimerData* findTimerSlot()
{
initTimerSystem();
for (int i = 0; i < MAX_INTERVAL_TIMERS; i++) {
if (!intervalTimers[i].isActive) {
return &intervalTimers[i];
}
}
return NULL;
}
static IntervalTimerData* findTimerById(int id)
{
initTimerSystem();
for (int i = 0; i < MAX_INTERVAL_TIMERS; i++) {
if (intervalTimers[i].isActive && intervalTimers[i].id == id) {
return &intervalTimers[i];
}
}
return NULL;
}
static void timerCloseCallback(uv_handle_t* handle)
{
free(handle);
}
// Called by libuv when the timer has completed.
static void timerCallback(uv_timer_t* handle)
{
WrenHandle* fiber = (WrenHandle*)handle->data;
WrenHandle* fiber = (WrenHandle*)handle->data;
// Tell libuv that we don't need the timer anymore.
uv_close((uv_handle_t*)handle, timerCloseCallback);
uv_close((uv_handle_t*)handle, timerCloseCallback);
// Run the fiber that was sleeping.
schedulerResume(fiber, false);
schedulerResume(fiber, false);
}
void timerStartTimer(WrenVM* vm)
{
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
// Store the fiber to resume when the timer completes.
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
handle->data = fiber;
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
handle->data = fiber;
uv_timer_init(getLoop(), handle);
uv_timer_start(handle, timerCallback, milliseconds, 0);
uv_timer_init(getLoop(), handle);
uv_timer_start(handle, timerCallback, milliseconds, 0);
}
static void intervalCallback(uv_timer_t* handle)
{
IntervalTimerData* data = (IntervalTimerData*)handle->data;
if (!data->isActive) return;
resumeFiberWithoutRelease(data->fiber);
}
static void intervalCloseCallback(uv_handle_t* handle)
{
free(handle);
}
void timerStartInterval(WrenVM* vm)
{
int milliseconds = (int)wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
IntervalTimerData* timerData = findTimerSlot();
if (timerData == NULL) {
wrenSetSlotString(vm, 0, "Too many active interval timers.");
wrenAbortFiber(vm, 0);
return;
}
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
uv_timer_init(getLoop(), handle);
int id = nextTimerId++;
timerData->handle = handle;
timerData->fiber = fiber;
timerData->isActive = 1;
timerData->id = id;
handle->data = timerData;
uv_timer_start(handle, intervalCallback, milliseconds, milliseconds);
wrenEnsureSlots(vm, 1);
wrenSetSlotDouble(vm, 0, (double)id);
}
void timerStopInterval(WrenVM* vm)
{
int id = (int)wrenGetSlotDouble(vm, 1);
IntervalTimerData* timerData = findTimerById(id);
if (timerData == NULL) {
return;
}
if (timerData->handle != NULL && timerData->isActive) {
uv_timer_stop(timerData->handle);
uv_close((uv_handle_t*)timerData->handle, intervalCloseCallback);
timerData->handle = NULL;
}
if (timerData->fiber != NULL) {
wrenReleaseHandle(getVM(), timerData->fiber);
timerData->fiber = NULL;
}
timerData->isActive = 0;
}
void timerIsIntervalActive(WrenVM* vm)
{
int id = (int)wrenGetSlotDouble(vm, 1);
IntervalTimerData* timerData = findTimerById(id);
wrenEnsureSlots(vm, 1);
wrenSetSlotBool(vm, 0, timerData != NULL && timerData->isActive);
}
static void immediateCallback(uv_timer_t* handle)
{
WrenHandle* fiber = (WrenHandle*)handle->data;
uv_close((uv_handle_t*)handle, timerCloseCallback);
schedulerResume(fiber, false);
}
void timerStartImmediate(WrenVM* vm)
{
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t));
if (handle == NULL) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
handle->data = fiber;
uv_timer_init(getLoop(), handle);
uv_timer_start(handle, immediateCallback, 0, 0);
}

37
src/module/timer.wren vendored
View File

@ -1,5 +1,16 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler
class TimerHandle {
construct new_(id) {
_id = id
}
stop() { Timer.stopInterval_(_id) }
isActive { Timer.isIntervalActive_(_id) }
}
class Timer {
static sleep(milliseconds) {
if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.")
@ -8,5 +19,31 @@ class Timer {
return Scheduler.await_ { startTimer_(milliseconds, Fiber.current) }
}
static interval(milliseconds, fn) {
if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.")
if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.")
if (!(fn is Fn)) Fiber.abort("Callback must be a function.")
var fiber = Fiber.new {
while (true) {
fn.call()
Fiber.yield()
}
}
var id = startInterval_(milliseconds, fiber)
return TimerHandle.new_(id)
}
static immediate(fn) {
if (!(fn is Fn)) Fiber.abort("Callback must be a function.")
var fiber = Fiber.new { fn.call() }
startImmediate_(fiber)
}
foreign static startTimer_(milliseconds, fiber)
foreign static startInterval_(milliseconds, fiber)
foreign static stopInterval_(id)
foreign static isIntervalActive_(id)
foreign static startImmediate_(fiber)
}

View File

@ -2,8 +2,19 @@
// from `src/module/timer.wren` using `util/wren_to_c_string.py`
static const char* timerModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"scheduler\" for Scheduler\n"
"\n"
"class TimerHandle {\n"
" construct new_(id) {\n"
" _id = id\n"
" }\n"
"\n"
" stop() { Timer.stopInterval_(_id) }\n"
" isActive { Timer.isIntervalActive_(_id) }\n"
"}\n"
"\n"
"class Timer {\n"
" static sleep(milliseconds) {\n"
" if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n"
@ -12,5 +23,31 @@ static const char* timerModuleSource =
" return Scheduler.await_ { startTimer_(milliseconds, Fiber.current) }\n"
" }\n"
"\n"
" static interval(milliseconds, fn) {\n"
" if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n"
" if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n"
" if (!(fn is Fn)) Fiber.abort(\"Callback must be a function.\")\n"
"\n"
" var fiber = Fiber.new {\n"
" while (true) {\n"
" fn.call()\n"
" Fiber.yield()\n"
" }\n"
" }\n"
" var id = startInterval_(milliseconds, fiber)\n"
" return TimerHandle.new_(id)\n"
" }\n"
"\n"
" static immediate(fn) {\n"
" if (!(fn is Fn)) Fiber.abort(\"Callback must be a function.\")\n"
"\n"
" var fiber = Fiber.new { fn.call() }\n"
" startImmediate_(fiber)\n"
" }\n"
"\n"
" foreign static startTimer_(milliseconds, fiber)\n"
" foreign static startInterval_(milliseconds, fiber)\n"
" foreign static stopInterval_(id)\n"
" foreign static isIntervalActive_(id)\n"
" foreign static startImmediate_(fiber)\n"
"}\n";