Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline includes and mark them as jsNode where necessary #612

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions libraries/common/io.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@ module io
import ref
import queue

extern include llvm "../llvm/io.ll"
extern include js "../js/io.js"


// Event Loop
// ----------

type Task[T] = () => T at {io, async, global}

extern llvm """
declare void @c_yield(%Stack)
"""
b-studios marked this conversation as resolved.
Show resolved Hide resolved

extern async def spawn(task: Task[Unit]): Unit =
js "$effekt.callcc(callback => { setTimeout(() => callback($effekt.unit), 0); ${task}().run()})"
llvm """
call void @c_timer_start(%Int 0, %Stack %stack)
call void @c_yield(%Stack %stack)
call void @run(%Neg ${task})
ret void
"""

extern async def yield(): Unit =
js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), 0))"
llvm """
call void @c_timer_start(%Int 0, %Stack %stack)
call void @c_yield(%Stack %stack)
ret void
"""

Expand All @@ -48,6 +48,12 @@ def promise[T](task: Task[T]): Promise[T] = {
return p
}

extern llvm """
declare %Pos @c_promise_make()
declare void @c_promise_resolve(%Pos, %Pos)
declare void @c_promise_await(%Pos, %Neg)
"""

extern async def await[T](promise: Promise[T]): T =
js "$effekt.callcc(callback => ${promise}.promise.then(res => callback(res)))"
llvm """
Expand Down
10 changes: 5 additions & 5 deletions libraries/common/io/console.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,26 @@ def console[R] { program: () => R / Console }: R = {

namespace js {

extern js """
extern jsNode """
const readline = require('node:readline');
"""
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specific to node js and doesn't work in the webbrowser, for now simply disable


extern type JSConsole

extern io def newConsole(): JSConsole =
js """readline.createInterface({
jsNode """readline.createInterface({
input: process.stdin,
output: process.stdout,
})"""

extern io def close(console: JSConsole): Unit =
js "${console}.close()"
jsNode "${console}.close()"

extern async def readLine(console: JSConsole): String =
js "$effekt.callcc(callback => ${console}.once('line', callback))"
jsNode "$effekt.callcc(callback => ${console}.once('line', callback))"

extern io def writeLine(console: JSConsole, message: String): Unit =
js "${console}.output.write(${message} + '\\n')"
jsNode "${console}.output.write(${message} + '\\n')"
}

namespace examples {
Expand Down
100 changes: 99 additions & 1 deletion libraries/common/io/error.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,106 @@ def fromNumber(number: Int): IOError = number match {

namespace internal {

extern llvm """
declare %Int @c_error_number(%Int)
"""

extern jsNode """
const os = require('node:os');

/**
* Maps the error code to a Effekt-stable (platform independent) numeric value.
*
* Tries to use most common errno integer values, but introduces fresh values (> 200)
* for those without common errno values.
*/
function errorNumber(errno) {
const errnoMap = {
[os.constants.errno.EPERM]: 1,
[os.constants.errno.ENOENT]: 2,
[os.constants.errno.ESRCH]: 3,
[os.constants.errno.EINTR]: 4,
[os.constants.errno.EIO]: 5,
[os.constants.errno.ENXIO]: 6,
[os.constants.errno.E2BIG]: 7,
[os.constants.errno.EBADF]: 9,
[os.constants.errno.EAGAIN]: 11,
[os.constants.errno.ENOMEM]: 12,
[os.constants.errno.EACCES]: 13,
[os.constants.errno.EFAULT]: 14,
[os.constants.errno.EBUSY]: 16,
[os.constants.errno.EEXIST]: 17,
[os.constants.errno.EXDEV]: 18,
[os.constants.errno.ENODEV]: 19,
[os.constants.errno.ENOTDIR]: 20,
[os.constants.errno.EISDIR]: 21,
[os.constants.errno.EINVAL]: 22,
[os.constants.errno.ENFILE]: 23,
[os.constants.errno.EMFILE]: 24,
[os.constants.errno.ENOTTY]: 25,
[os.constants.errno.ETXTBSY]: 26,
[os.constants.errno.EFBIG]: 27,
[os.constants.errno.ENOSPC]: 28,
[os.constants.errno.ESPIPE]: 29,
[os.constants.errno.EROFS]: 30,
[os.constants.errno.EMLINK]: 31,
[os.constants.errno.EPIPE]: 32,
[os.constants.errno.ERANGE]: 34,
[os.constants.errno.ENAMETOOLONG]: 36,
[os.constants.errno.ELOOP]: 40,
[os.constants.errno.EOVERFLOW]: 75,
[os.constants.errno.EFTYPE]: 79,
[os.constants.errno.EILSEQ]: 84,
[os.constants.errno.ENOTSOCK]: 88,
[os.constants.errno.EDESTADDRREQ]: 89,
[os.constants.errno.EMSGSIZE]: 90,
[os.constants.errno.EPROTOTYPE]: 91,
[os.constants.errno.ENOPROTOOPT]: 92,
[os.constants.errno.EPROTONOSUPPORT]: 93,
[os.constants.errno.ESOCKTNOSUPPORT]: 94,
[os.constants.errno.ENOTSUP]: 95,
[os.constants.errno.EAFNOSUPPORT]: 97,
[os.constants.errno.EADDRINUSE]: 98,
[os.constants.errno.EADDRNOTAVAIL]: 99,
[os.constants.errno.ENETDOWN]: 100,
[os.constants.errno.ENETUNREACH]: 101,
[os.constants.errno.ECONNABORTED]: 103,
[os.constants.errno.ECONNRESET]: 104,
[os.constants.errno.ENOBUFS]: 105,
[os.constants.errno.EISCONN]: 106,
[os.constants.errno.ENOTCONN]: 107,
[os.constants.errno.ETIMEDOUT]: 110,
[os.constants.errno.ECONNREFUSED]: 111,
[os.constants.errno.EHOSTUNREACH]: 113,
[os.constants.errno.EALREADY]: 114,
[os.constants.errno.ECANCELED]: 125,
[os.constants.errno.EAI_ADDRFAMILY]: 200,
[os.constants.errno.EAI_AGAIN]: 201,
[os.constants.errno.EAI_BADFLAGS]: 202,
[os.constants.errno.EAI_BADHINTS]: 203,
[os.constants.errno.EAI_CANCELED]: 204,
[os.constants.errno.EAI_FAIL]: 205,
[os.constants.errno.EAI_FAMILY]: 206,
[os.constants.errno.EAI_MEMORY]: 207,
[os.constants.errno.EAI_NODATA]: 208,
[os.constants.errno.EAI_NONAME]: 209,
[os.constants.errno.EAI_OVERFLOW]: 210,
[os.constants.errno.EAI_PROTOCOL]: 211,
[os.constants.errno.EAI_SERVICE]: 212,
[os.constants.errno.EAI_SOCKTYPE]: 213,
[os.constants.errno.ECHARSET]: 215,
[os.constants.errno.ENONET]: 216,
[os.constants.errno.UNKNOWN]: 217,
[os.constants.errno.EOF]: 218,
[os.constants.errno.EUNATCH]: 219,
[os.constants.errno.ESHUTDOWN]: 220
};
return errnoMap[-errno] || -1; // Default to -1 for unknown error names
}
"""

extern pure def errorNumber(errno: Int): Int =
js "errorNumber(${errno})"
jsNode "errorNumber(${errno})"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using os

llvm """
%z = call %Int @c_error_number(%Int ${errno})
ret %Int %z
Expand Down
89 changes: 83 additions & 6 deletions libraries/common/io/filesystem.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type File = Int
*/
def readFile(path: String): String / Exception[IOError] = {
val fd = open(path, ReadOnly());
with on[IOError].finalize { close(fd); () }
with on[IOError].finalize { close(fd) }

val readSize = 1048576 // 1MB
var size = readSize
Expand Down Expand Up @@ -76,7 +76,7 @@ def readFile(path: String): String / Exception[IOError] = {
*/
def writeFile(path: String, contents: String): Unit / Exception[IOError] = {
val fd = open(path, WriteOnly());
with on[IOError].finalize { close(fd); () }
with on[IOError].finalize { close(fd) }

val writeSize = 1048576 // 1MB

Expand Down Expand Up @@ -139,32 +139,109 @@ def close(fd: File): Unit / Exception[IOError] = {
namespace internal {

extern js """
function modeName(mode) {
switch (mode.__tag) {
case 0: // ReadOnly()
return 'r';
case 1: // WriteOnly()
return 'w';
case 2: // AppendOnly()
return 'a';
case 3: // ReadWrite()
return 'w+';
case 4: // ReadAppend()
return 'a+';
case 5: // AppendExclusive()
return 'ax';
case 6: // ReadAppendExclusive()
return 'ax+';
case 7: // AppendSync()
return 'as';
case 8: // ReadAppendSync()
return 'as+';
case 9: // ReadSync()
return 'rs';
case 10: // ReadWriteSync()
return 'rs+';
case 11: // WriteExclusive()
return 'wx';
case 12: // ReadWriteExclusive()
return 'wx+';
default:
// Invalid tag value
return null;
}
}

/**
* Nodejs file operations expect buffers, but we represent buffers as typed arrays.
* This function converts between the two without copying.
*/
function toBuffer(buffer) {
return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}
"""

extern jsNode """
const fs = require("fs");

function open(path, mode, callback) {
fs.open(path, modeName(mode), (err, fd) => {
if (err) { callback(err.errno) } else { callback(fd) }
})
}

function read(fd, buffer, offset, callback) {
let position = offset === -1 ? null : offset;
fs.read(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesRead) => {
if (err) { callback(err.errno) } else { callback(bytesRead) }
})
}

function write(fd, buffer, offset, callback) {
let position = offset === -1 ? null : offset;
fs.write(fd, toBuffer(buffer), 0, buffer.length, position, (err, bytesWritten) => {
if (err) { callback(err.errno) } else { callback(bytesWritten) }
})
}

function close(fd, callback) {
fs.close(fd, (err) => {
if (err) { callback(err.errno) } else { callback(0) }
})
}
"""

extern llvm """
declare void @c_fs_open(%Pos, %Pos, %Stack)
declare void @c_fs_read(%Int, %Pos, %Int, %Stack)
declare void @c_fs_write(%Int, %Pos, %Int, %Stack)
declare void @c_fs_close(%Int, %Stack)
"""

extern async def open(path: String, mode: Mode): Int =
js "$effekt.callcc(callback => open(${path}, ${mode}, callback))"
jsNode "$effekt.callcc(callback => open(${path}, ${mode}, callback))"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fs which is only available in node, but not web

llvm """
call void @c_fs_open(%Pos ${path}, %Pos ${mode}, %Stack %stack)
ret void
"""

extern async def read(fd: Int, buffer: Bytes, offset: Int): Int =
js "$effekt.callcc(callback => read(${fd}, ${buffer}, ${offset}, callback))"
jsNode "$effekt.callcc(callback => read(${fd}, ${buffer}, ${offset}, callback))"
llvm """
call void @c_fs_read(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
ret void
"""

extern async def write(fd: Int, buffer: Bytes, offset: Int): Int =
js "$effekt.callcc(callback => write(${fd}, ${buffer}, ${offset}, callback))"
jsNode "$effekt.callcc(callback => write(${fd}, ${buffer}, ${offset}, callback))"
llvm """
call void @c_fs_write(%Int ${fd}, %Pos ${buffer}, %Int ${offset}, %Stack %stack)
ret void
"""

extern async def close(fd: Int): Int =
js "$effekt.callcc(callback => close(${fd}, callback))"
jsNode "$effekt.callcc(callback => close(${fd}, callback))"
llvm """
call void @c_fs_close(%Int ${fd}, %Stack %stack)
ret void
Expand Down
12 changes: 6 additions & 6 deletions libraries/common/io/network.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import bytes
import io

namespace js {
extern js """
extern jsNode """
const net = require('node:net');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node:net is only available on node


function listen(server, port, host, listener) {
Expand All @@ -16,18 +16,18 @@ namespace js {
extern type JSServer // = net.Server
extern type JSSocket // = net.Socket
extern io def server(): JSServer =
js "net.createServer()"
jsNode "net.createServer()"
extern io def listen(server: JSServer, port: Int, host: String, listener: JSSocket => Unit at {io, async, global}): Unit =
js "listen(${server}, ${port}, ${host}, (socket) => (${listener})(socket).run())"
jsNode "listen(${server}, ${port}, ${host}, (socket) => (${listener})(socket).run())"

extern async def send(socket: JSSocket, data: Bytes): Unit =
js "$effekt.callcc(callback => ${socket}.write(${data}, callback))"
jsNode "$effekt.callcc(callback => ${socket}.write(${data}, callback))"

extern async def receive(socket: JSSocket): Bytes =
js "$effekt.callcc(callback => ${socket}.once('data', callback))"
jsNode "$effekt.callcc(callback => ${socket}.once('data', callback))"

extern async def end(socket: JSSocket): Unit =
js "$effekt.callcc(callback => ${socket}.end(callback))"
jsNode "$effekt.callcc(callback => ${socket}.end(callback))"
}

interface Socket {
Expand Down
3 changes: 3 additions & 0 deletions libraries/common/io/time.effekt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module io/time

extern llvm """
declare void @c_timer_start(%Int, %Stack)
"""

extern async def wait(millis: Int): Unit =
js "$effekt.callcc(callback => setTimeout(() => callback($effekt.unit), ${millis}))"
Expand Down
Loading