diff --git a/examples/data_structures/stack.noja b/examples/data_structures/stack.noja
new file mode 100644
index 0000000..7b985a4
--- /dev/null
+++ b/examples/data_structures/stack.noja
@@ -0,0 +1,101 @@
+cat = string.cat;
+
+fun Node(T) {
+ K = { item: T, next: none };
+ K.next = ?K;
+ return K;
+}
+
+fun makeNode(item)
+ return { item: item, next: none };
+
+fun Stack(T)
+ return { size: int, head: ?Node(T) };
+
+fun StackIterator(T)
+ return { stack: Stack(T), index: ?int, cursor: ?Node(T), next: Callable };
+
+fun makeStackIterator(stack: Stack(any))
+ return {
+ stack: stack,
+ index: none,
+ cursor: stack.head,
+
+ fun toString(iter: StackIterator(stack.type))
+ return cat("StackIterator(", toString(stack), ")");
+
+ fun next(iter: StackIterator(stack.type)) {
+ if iter.index == none: {
+ iter.index = 0;
+ iter.cursor = iter.stack.head;
+ } else {
+ if iter.cursor != none: {
+ iter.cursor = iter.cursor.next;
+ iter.index = iter.index+1;
+ }
+ }
+ if iter.cursor == none:
+ return none;
+ return iter.cursor.item, iter.index;
+ }
+
+ fun haveNext(iter: StackIterator(stack.type)) {
+ return none != iter.cursor
+ and none != iter.cursor.next;
+ }
+ };
+
+fun makeStack(T)
+ return {
+ type: T,
+ size: 0,
+ head: none,
+
+ fun push(stack: Stack(T), item: T) {
+
+ if item == none:
+ error("Can't insert \"none\" into a stack");
+
+ node = makeNode(item);
+ node.next = stack.head;
+ stack.head = node;
+ stack.size = stack.size+1;
+ }
+
+ fun pop(stack: Stack(T)) {
+ node = stack.head;
+ if node != none: {
+ stack.head = node.next;
+ stack.size = stack.size-1;
+ }
+ return node.item;
+ }
+
+ fun isEmpty(stack: Stack(T))
+ return stack.head == none;
+
+ fun toString(stack: Stack(T)) {
+ iter = stack->iter();
+ s = "[";
+ while (item = iter->next()) != none: {
+ s = cat(s, toString(item));
+ if iter->haveNext():
+ s = cat(s, ", ");
+ }
+ s = cat(s, "]");
+ return s;
+ }
+
+ fun print(stack: Stack(T))
+ print(stack->toString());
+
+ fun iter(stack: Stack(T))
+ return makeStackIterator(stack);
+ };
+
+return {
+ Stack: Stack,
+ makeStack: makeStack,
+ StackIterator: StackIterator,
+ makeStackIterator: makeStackIterator
+};
diff --git a/examples/data_structures/stack_test.noja b/examples/data_structures/stack_test.noja
new file mode 100644
index 0000000..bbbd90f
--- /dev/null
+++ b/examples/data_structures/stack_test.noja
@@ -0,0 +1,17 @@
+stack, error = import("stack.noja");
+print(error, "\n");
+Stack = stack.Stack;
+makeStack = stack.makeStack;
+StackIterator = stack.StackIterator;
+makeStackIterator = stack.makeStackIterator;
+
+s1 = makeStack(int);
+s1->push(1);
+s1->push(2);
+s1->push(3);
+print(toString(s1), "\n");
+
+iter = makeIterator(s1);
+print(toString(iter), "\n");
+while (val, key = iter->next()) != none:
+ print(key, " => ", val, "\n");
\ No newline at end of file
diff --git a/examples/http.noja b/examples/http.noja
index 2c20168..ff29a62 100644
--- a/examples/http.noja
+++ b/examples/http.noja
@@ -260,7 +260,7 @@ fun parseVersion(scan: Scanner) {
char = scan->consume(5);
major = ord(char) - ord("0");
- if scan->hint(1) != "." and isDigit(scan->hint(2)): {
+ if scan->hint(1) == "." and isDigit(scan->hint(2)): {
char = scan->consume(2);
minor = ord(char) - ord("0");
} else
@@ -316,9 +316,10 @@ fun parse(src: String) {
if error != none:
return none, error;
- if scan->hint(0) != "\r" or scan->hint(1) != "\n":
+ if scan->hint(0) != "\r" or scan->hint(1) != "\n": {
# Request with no headers or body
return newRequest(method, url, version);
+ }
scan->consume(2); # Consume the "\r\n"
headers = {};
@@ -344,10 +345,18 @@ fun parse(src: String) {
} while char != "\r" and char != none;
headers[name] = body;
+
+ if char == "\r": {
+ if scan->hint(1) != "\n":
+ return none, "Missing \\n after \\r in header body";
+ scan->consume(2);
+ }
}
- if char != none:
+ if char != none: {
+ assert(char == "\r" and scan->hint(1) == "\n");
scan->consume(2); # Consume the final "\r\n"
+ }
return newRequest(method, url, version, headers);
}
diff --git a/examples/http_server/main.noja b/examples/http_server/main.noja
new file mode 100755
index 0000000..69c0f56
--- /dev/null
+++ b/examples/http_server/main.noja
@@ -0,0 +1,109 @@
+server = import("server.noja");
+request = import("request.noja");
+response = import("response.noja");
+Request = request.Request;
+
+fun loadFile(path: String) {
+
+ stream, error = files.openFile(path, files.READ);
+ if error != none:
+ return none, error;
+
+ text = "";
+ size = 512;
+ do {
+ size = 2 * size;
+ data = buffer.new(size);
+
+ num_bytes, error = files.read(stream, data);
+ if error != none:
+ return none, error;
+
+ text = string.cat(text, buffer.toString(data));
+
+ } while num_bytes == size;
+
+ files.close(stream);
+ return text;
+}
+
+fun generateHome() {
+ title = "Homepage";
+ content = "Hello, world!";
+ return string.cat(
+ "",
+ "
",
+ "", title, "",
+ "",
+ "",
+ "", content, "",
+ "",
+ ""
+ );
+}
+
+fun generateLogout() {
+}
+
+table = {
+ '/': 'login.html',
+ '/home': generateHome,
+ '/login': 'login.html',
+ '/signup': 'signup.html',
+ '/logout': generateLogout
+};
+
+default = 'notfound.html';
+debug = true;
+
+fun callback(req: Request) {
+
+ path = req.url.path;
+
+ if path == "/" or path == "/login": {
+
+ if req.method == "POST": {
+
+
+
+ } else if req.method == "GET"; {
+
+ } else {
+ assert(false);
+ }
+ }
+
+ # Generate response body
+ {
+ entry = table[req.url.path];
+ if entry == none: {
+ entry = default;
+ not_found = true;
+ } else
+ not_found = false;
+
+ if isCallable(entry):
+ data, error = entry(req);
+ else
+ data, error = loadFile(entry);
+ }
+
+ # Generate response object
+ {
+ if error == none:
+ res = response.new(200, data);
+ else if not_found:
+ res = response.new(404, data);
+ else if debug:
+ res = response.new(500, error);
+ else
+ res = response.new(500);
+ }
+
+ return res;
+}
+
+s, err = server.new(callback, "127.0.0.1", 8080);
+if s == none: error(err);
+s->loop(); # This doesn't return
+s->close();
diff --git a/examples/http_server/pages/login.html b/examples/http_server/pages/login.html
new file mode 100644
index 0000000..bfc32f4
--- /dev/null
+++ b/examples/http_server/pages/login.html
@@ -0,0 +1,12 @@
+
+
+ Login page
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/http_server/parse.noja b/examples/http_server/parse.noja
new file mode 100644
index 0000000..82ba40f
--- /dev/null
+++ b/examples/http_server/parse.noja
@@ -0,0 +1,359 @@
+scanner = import("scanner.noja");
+
+newScanner = scanner.newScanner;
+Scanner = scanner.Scanner;
+isDigit = scanner.isDigit;
+ord = string.ord;
+cat = string.cat;
+
+fun isAlpha(char: String)
+ return (ord(char) >= ord('a') and ord(char) <= ord('z'))
+ or (ord(char) >= ord('A') and ord(char) <= ord('Z'));
+
+fun isUpper(char: String)
+ return ord(char) >= ord('A')
+ and ord(char) <= ord('Z');
+
+fun isUnreserved(char: String)
+ return isAlpha(char)
+ or isDigit(char)
+ or char == "-"
+ or char == "."
+ or char == "_"
+ or char == "~";
+
+fun isSubdelim(char: String)
+ return char == "!" or char == "$"
+ or char == "&" or char == "'"
+ or char == "(" or char == ")"
+ or char == "*" or char == "+"
+ or char == "," or char == ";"
+ or char == "=";
+
+fun isPChar(char: String)
+ return isUnreserved(char)
+ or isSubdelim(char)
+ or char == ":"
+ or char == "@";
+
+fun parseMethod(scan: Scanner) {
+
+ char = scan->current();
+ if char == none or not isUpper(char):
+ return none, "Missing method";
+
+ method = "";
+ do {
+ method = cat(method, char);
+ char = scan->consume();
+ } while char != none and isUpper(char);
+
+ return method, none;
+}
+
+fun parsePort(scan: Scanner) {
+
+ port = none;
+ char = scan->current();
+ if char == ":" and isDigit(scan->hint(1)): {
+
+ char = scan->consume(); # Skip the ":"
+
+ port = 0;
+ do {
+ digit = ord(char) - ord("0");
+ port = port * 10 + digit;
+ char = scan->consume();
+ } while char != none and isDigit(char);
+ }
+ return port;
+}
+
+fun isHostname(char: String)
+ return isUnreserved(char)
+ or isSubdelim(char);
+
+fun parseHost(scan: Scanner) {
+
+ char = scan->current();
+ if char == none:
+ return none, "Missing host";
+
+ if not isHostname(char):
+ return none, "Invalid character in place of host";
+
+ name = "";
+ do {
+ name = cat(name, char);
+ char = scan->consume();
+ } while char != none and isHostname(char);
+
+ port = parsePort(scan);
+ return {name: name, port: port};
+}
+
+fun parsePath(scan: Scanner) {
+
+ path = "";
+ char = scan->current();
+ if char != none and char == "/": {
+ path = cat(path, char);
+ char = scan->consume();
+ } else {
+ if char == none or not isPChar(char):
+ return none, "Invalid character in place of path";
+ }
+
+ while char != none and isPChar(char): {
+ do {
+ path = cat(path, char);
+ char = scan->consume();
+ } while char != none and isPChar(char);
+ if char == none or char != "/":
+ break;
+ path = cat(path, char);
+ char = scan->consume();
+ }
+
+ return path;
+}
+
+fun isQuery(char: ?String) {
+ if char == none:
+ return false;
+ return isPChar(char)
+ or char == "/"
+ or char == "?";
+}
+
+fun parseQuery(scan: Scanner) {
+
+ char = scan->current();
+ if char != none and char == "?": {
+ query = "";
+ while isQuery(char = scan->consume()):
+ query = cat(query, char);
+ } else
+ query = none;
+
+ return query;
+}
+
+fun isFragment(char: ?String) {
+ if char == none:
+ return false;
+ return isPChar(char) or char == "/";
+}
+
+fun parseFragment(scan: Scanner) {
+
+ char = scan->current();
+ if char != none and char == "#": {
+ fragment = "";
+ while isFragment(char = scan->consume()):
+ fragment = cat(fragment, char);
+ } else
+ fragment = none;
+
+ return fragment;
+}
+
+fun isSchemaFirst(char: ?String) {
+ return char != none and isAlpha(char);
+}
+
+fun isSchema(char: ?String)
+ return char != none
+ and (isAlpha(char)
+ or isDigit(char)
+ or char == "+"
+ or char == "-"
+ or char == ".");
+
+fun parseSchema(scan: Scanner) {
+
+ char = scan->current();
+ schema = none;
+ if isSchemaFirst(char): {
+ start = scan.i;
+ schema = "";
+ do {
+ schema = cat(schema, char);
+ char = scan->consume();
+ } while isSchema(char);
+
+ if char == ":":
+ scan->consume();
+ else {
+ schema = none;
+ scan.i = start;
+ }
+ }
+
+ return schema;
+}
+
+fun followsAuthority(scan: Scanner)
+ return scan->current() == "/" and scan->hint(1) == "/";
+
+fun parseURL(scan: Scanner) {
+
+ schema = parseSchema(scan);
+
+ if followsAuthority(scan): {
+ scan->consume(2);
+
+ host, error = parseHost(scan);
+ if error != none:
+ return none, error;
+
+ path = none;
+ if scan->current() == "/":
+ path = parsePath(scan);
+
+ } else {
+
+ host = none;
+
+ char = scan->current();
+ if char == none or char == "?" or char == "#":
+ return none, "Missing path";
+
+ path, error = parsePath(scan);
+ if error != none:
+ return none, error;
+ }
+
+ query = parseQuery(scan);
+ fragment = parseFragment(scan);
+
+ return {
+ host : host,
+ path : path,
+ query : query,
+ schema : schema,
+ fragment: fragment
+ };
+}
+
+fun parseVersion(scan: Scanner) {
+
+ if scan->hint(0) != "H" or scan->hint(1) != "T"
+ or scan->hint(2) != "T" or scan->hint(3) != "P"
+ or scan->hint(4) != "/" or not isDigit(scan->hint(5)):
+ return none, "Invalid version token";
+
+ char = scan->consume(5);
+ major = ord(char) - ord("0");
+
+ if scan->hint(1) == "." and isDigit(scan->hint(2)): {
+ char = scan->consume(2);
+ minor = ord(char) - ord("0");
+ } else
+ minor = 0;
+ scan->consume();
+
+ return {major: major, minor: minor};
+}
+
+fun parseHead(src: String) {
+
+ scan = newScanner(src);
+
+ method, error = parseMethod(scan);
+ if error != none:
+ return none, error;
+
+ char = scan->current();
+ if char == none or char != " ":
+ return none, "Missing space after method";
+ scan->consume();
+
+ url, error = parseURL(scan);
+ if error != none:
+ return none, error;
+
+ char = scan->current();
+ if char != " ":
+ return none, "Missing space after URL";
+ scan->consume();
+
+ version, error = parseVersion(scan);
+ if error != none:
+ return none, error;
+
+ if scan->hint(0) != "\r"
+ or scan->hint(1) != "\n": {
+
+ # Request with no headers or body
+ return {
+ method: method,
+ url: url,
+ version:
+ version,
+ headers: {},
+ body: none
+ }, none, scan.i;
+ }
+
+ scan->consume(2); # Consume the "\r\n"
+
+ headers = {};
+ while (char = scan->hint(0)) != none and (char != "\r" or scan->hint(1) != "\n"): {
+
+ # Header name until the next ":"
+ name = "";
+ do {
+ name = cat(name, char);
+ char = scan->consume();
+ } while char != ":" and char != none;
+
+ if char == none:
+ return none, "Invalid header (missing \":\" after name)";
+ scan->consume();
+ char = scan->consumeSpaces();
+
+ # Header body until \r
+ body = "";
+ do {
+ body = cat(body, char);
+ char = scan->consume();
+ } while char != "\r" and char != none;
+
+ headers[name] = body;
+
+ if char == "\r": {
+ if scan->hint(1) != "\n":
+ return none, "Missing \\n after \\r in header body";
+ scan->consume(2);
+ }
+ }
+
+ if char != none: {
+ assert(char == "\r" and scan->hint(1) == "\n");
+ scan->consume(2); # Consume the final "\r\n"
+ }
+
+ return {
+ method: method,
+ url: url,
+ version: version,
+ headers: headers,
+ body: none
+ }, none, scan.i;
+}
+
+fun test() {
+ sample = cat(
+ "GET /search?client=firefox-b-d&q=http+request+dataset HTTP/2\r\n",
+ "Host: www.google.com\r\n",
+ "user-agent: curl/7.84.0\r\n",
+ "accept: */*\r\n",
+ "\r\n");
+
+ request, error = parseHead(sample);
+ print("request=", request, "\n");
+ print("error=", error, "\n");
+}
+
+return {parseHead: parseHead, test: test};
diff --git a/examples/http_server/request.noja b/examples/http_server/request.noja
new file mode 100644
index 0000000..fce8f2c
--- /dev/null
+++ b/examples/http_server/request.noja
@@ -0,0 +1,87 @@
+parseHead = import("parse.noja").parseHead;
+
+Version = {
+ major: int,
+ minor: int
+};
+
+Host = {
+ name: String,
+ port: ?int
+};
+
+URL = {
+ schema: ?String,
+ host : ?Host,
+ path : String,
+ query : ?String,
+ fragment: ?String
+};
+
+Request = {
+ method : String,
+ url : URL,
+ version: Version,
+ body : None | Buffer | [Buffer, Buffer]
+};
+
+fun fromSocket(fd: int, max_head: int = 1024) {
+
+ {
+ # Create a buffer to hold the received bytes
+ head_buffer = buffer.new(max_head);
+
+ # Receive bytes up to the buffer's size
+ received_bytes, error = net.recv(fd, head_buffer);
+ if error != none:
+ return none, error, true;
+
+ # Downsize the buffer to the received amount
+ head_buffer = buffer.sliceUp(head_buffer, 0, received_bytes);
+ assert(count(head_buffer) == received_bytes);
+
+ # Convert the received data to a string
+ text, error = buffer.toString(head_buffer);
+ if error != none:
+ return none, error, false; # Request isn't UTF-8
+ }
+
+ {
+ # Parse the request head's information
+ request, error, parsed_bytes = parseHead(text);
+ if error != none:
+ return none, error, false;
+
+ # Split the received data into head and body.
+ head_bytes = buffer.sliceUp(head_buffer, 0, parsed_bytes);
+ body_bytes = buffer.sliceUp(head_buffer, parsed_bytes, count(head_buffer) - parsed_bytes);
+ }
+
+ # Receive the remaining portion of the
+ # request's body (if it wasn't already)
+ {
+ content_length = request.headers['Content-Length'];
+ if content_length == none:
+ content_length = 0;
+
+ # TODO: Limit the content length
+
+ waiting_bytes = content_length - count(body_bytes);
+ assert(waiting_bytes >= 0);
+
+ if waiting_bytes > 0: {
+
+ second_half = buffer.new(waiting_bytes);
+
+ received_bytes, error = net.recv(fd, second_half);
+ if error != none:
+ return none, error, true; # Failed to receive the second half of the body
+ assert(received_bytes == waiting_bytes);
+
+ body_bytes = [body_bytes, second_half];
+ }
+ }
+
+ request.body = body_bytes;
+ return request;
+}
diff --git a/examples/http_server/response.noja b/examples/http_server/response.noja
new file mode 100644
index 0000000..233cc12
--- /dev/null
+++ b/examples/http_server/response.noja
@@ -0,0 +1,113 @@
+Response = {
+ status: int,
+ body: Buffer,
+ headers: Map
+};
+
+status_table = {
+ 100: "Continue",
+ 101: "Switching Protocols",
+ 102: "Processing",
+
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non-Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 207: "Multi-Status",
+ 208: "Already Reported",
+
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Found",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 306: "Switch Proxy",
+ 307: "Temporary Redirect",
+ 308: "Permanent Redirect",
+
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Request Entity Too Large",
+ 414: "Request-URI Too Long",
+ 415: "Unsupported Media Type",
+ 416: "Requested Range Not Satisfiable",
+ 417: "Expectation Failed",
+ 418: "I'm a teapot",
+ 420: "Enhance your calm",
+ 422: "Unprocessable Entity",
+ 426: "Upgrade Required",
+ 429: "Too many requests",
+ 431: "Request Header Fields Too Large",
+ 449: "Retry With",
+ 451: "Unavailable For Legal Reasons",
+
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Timeout",
+ 505: "HTTP Version Not Supported",
+ 509: "Bandwidth Limit Exceeded"
+};
+
+fun getStatusText(code: int) {
+ text = status_table[code];
+ if text == none:
+ return "???";
+ return text;
+}
+
+fun new(status: int = 200,
+ body: Buffer | String = "",
+ headers: Map = {}) {
+
+ if type(body) == String:
+ body = buffer.fromString(body);
+
+ # It's important to cast the body from a
+ # string to a buffer before calculating
+ # the Content-Length because the count of
+ # a string doesn't necessarily match the
+ # number of bytes!
+ headers['Content-Length'] = count(body);
+
+ return {status: status, body: body, headers: headers};
+}
+
+fun toBuffer(res: Response) {
+
+ cat = string.cat;
+ text = cat("HTTP/1.0 ", toString(res.status), " ", getStatusText(res.status), "\r\n");
+
+ i = 0;
+ header_names = keysof(res.headers);
+ while i < count(header_names): {
+ name = header_names[i];
+ body = toString(res.headers[name]);
+ text = cat(text, name, ": ", body, "\r\n");
+ i = i+1;
+ }
+ text = cat(text, "\r\n", buffer.toString(res.body));
+ return buffer.fromString(text);
+}
+
+fun toSocket(fd: int, res: Response) {
+ buf = toBuffer(res);
+ _, error = net.send(fd, buf);
+ return error;
+}
\ No newline at end of file
diff --git a/examples/http_server/scanner.noja b/examples/http_server/scanner.noja
new file mode 100644
index 0000000..3e08edf
--- /dev/null
+++ b/examples/http_server/scanner.noja
@@ -0,0 +1,56 @@
+
+Scanner = {
+ src: String,
+ i: int,
+ hint: Callable,
+ current: Callable,
+ consume: Callable,
+ consumeSpaces: Callable
+};
+
+fun newScanner(src: String) {
+
+ fun isSpace(c: String)
+ return c == ' '
+ or c == '\t'
+ or c == '\n';
+
+ scan = {
+ src: src,
+ i: 0,
+
+ fun hint(scan: Scanner, n: int = 1) {
+ k = scan.i + n;
+ if k < count(scan.src):
+ return scan.src[k];
+ return none;
+ }
+
+ fun current(scan: Scanner)
+ return scan->hint(0);
+
+ fun consume(scan: Scanner, n: int = 1) {
+ assert(n > 0);
+ limit = count(scan.src);
+ scan.i = min(scan.i + n, limit);
+ return scan->current();
+ }
+
+ fun consumeSpaces(scan: Scanner) {
+ char = scan->current();
+ while isSpace(char):
+ char = scan->consume();
+ return char;
+ }
+ };
+ assert(istypeof(Scanner, scan));
+ return scan;
+}
+
+fun isDigit(char: ?String) {
+ if char == none:
+ return false;
+ ord = string.ord;
+ return ord(char) >= ord('0')
+ and ord(char) <= ord('9');
+}
diff --git a/examples/http_server/server.noja b/examples/http_server/server.noja
new file mode 100644
index 0000000..dd678bc
--- /dev/null
+++ b/examples/http_server/server.noja
@@ -0,0 +1,80 @@
+request = import("request.noja");
+response = import("response.noja");
+
+Server = {
+ fd: int,
+ callback: Callable,
+ loop : Callable,
+ close: Callable
+};
+
+fun handleClient(fd: int, server: Server) {
+
+ req, error, error_is_internal = request.fromSocket(fd);
+ if error != none: {
+ if error_is_internal: {
+ if debug:
+ res = response.new(500, error);
+ else
+ res = response.new(500);
+ } else
+ res = response.new(400, error);
+ } else {
+ if req.version.major != 1:
+ res = response.new(505, "HTTP version not supported");
+ else
+ res = server.callback(req);
+ }
+ return response.toSocket(fd, res);
+}
+
+fun fallback_callback(request: Request)
+ return response.new(500, "No callback provided");
+
+return {
+
+ Server: Server,
+
+ fun new(callback: Callable = fallback_callback,
+ addr : String = "127.0.0.1",
+ port : int = 8080,
+ backlog : int = 32) {
+
+ fd, err = net.socket(net.AF_INET, net.SOCK_STREAM, 0);
+ if err != none:
+ return none, err;
+
+ if (err = net.bind(fd, net.AF_INET, port, addr)) != none: {
+ net.close(fd);
+ return none, err;
+ }
+
+ if (err = net.listen(fd, backlog)) != none: {
+ net.close(fd);
+ return none, err;
+ }
+
+ return {
+ fd: fd,
+ callback: callback,
+
+ fun loop(server: Server) {
+
+ while true: {
+ fd, addr_or_err, port = net.accept(server.fd);
+ if fd == none:
+ error = addr_or_err;
+ else {
+ error = handleClient(fd, server);
+ net.close(fd);
+ }
+ if error != none:
+ print(error);
+ }
+ }
+
+ fun close(server: Server)
+ net.close(server.fd);
+ };
+ }
+};
\ No newline at end of file
diff --git a/examples/scan.noja b/examples/scan.noja
index ff963a3..96d594c 100644
--- a/examples/scan.noja
+++ b/examples/scan.noja
@@ -9,11 +9,11 @@ Scanner = {
};
fun newScanner(src: String) {
-
+
fun isSpace(c: String)
- return c == ' '
- or c == '\t'
- or c == '\n';
+ return c == ' '
+ or c == '\t'
+ or c == '\n';
scan = {
src: src,
diff --git a/misc/makegarbage.noja b/misc/makegarbage.noja
index 7b791ac..2af448f 100644
--- a/misc/makegarbage.noja
+++ b/misc/makegarbage.noja
@@ -11,7 +11,7 @@ while i < n: {
i = i + 1;
fun printPercent()
- print(100.0 * i / n, '%\n');
+ print(string.cat('\r', stringFromNumeric(100.0 * i / n), '%'));
printPercent();
}
diff --git a/src/cli/main.c b/src/cli/main.c
index e3385fc..c58d9ab 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -46,7 +46,7 @@ static const char usage[] =
int main(int argc, char **argv)
{
assert(argc > 0);
-
+
if(argc == 1)
{
// $ noja
diff --git a/src/lib/builtins/basic.c b/src/lib/builtins/basic.c
index a6e9129..12ebfa8 100644
--- a/src/lib/builtins/basic.c
+++ b/src/lib/builtins/basic.c
@@ -32,7 +32,9 @@
#include
#include
#include
+#include "net.h"
#include "math.h"
+#include "utils.h"
#include "basic.h"
#include "files.h"
#include "string.h"
@@ -43,41 +45,48 @@
#include "../runtime/runtime.h"
#include "../compiler/compile.h"
+static int bin_typename(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ UNUSED(argc);
+ ASSERT(argc == 1);
+
+ Heap *heap = Runtime_GetHeap(runtime);
+ const char *name = argv[0]->type->name;
+ Object *o_name = Object_FromString(name, -1, heap, error);
+ if (o_name == NULL)
+ return -1;
+
+ rets[0] = o_name;
+ return 1;
+}
+
static int bin_print(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(runtime);
UNUSED(rets);
UNUSED(error);
-
+
for(int i = 0; i < (int) argc; i += 1)
Object_Print(argv[i], stdout);
return 0;
}
-static int bin_import(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+static int bin_import(Runtime *runtime,
+ Object **argv,
+ unsigned int argc,
+ Object *rets[static MAX_RETS],
+ Error *error)
{
UNUSED(argc);
ASSERT(argc == 1);
- Heap *heap = Runtime_GetHeap(runtime);
- ASSERT(heap != NULL);
-
- Object *o_path = argv[0];
- const char *path;
- size_t path_len;
- {
- if(!Object_IsString(o_path))
- {
- Error_Report(error, 0, "Argument #%d is not a string", 1);
- return -1;
- }
-
- path = Object_GetString(o_path, &path_len);
- if (path == NULL)
- return -1;
-
- }
-
+ ParsedArgument pargs[2];
+ if (!parseArgs(error, argv, argc, pargs, "s"))
+ return -1;
+
+ const char *path = pargs[0].as_string.data;
+ size_t path_len = pargs[0].as_string.size;
+
char full_path[1024];
if(path[0] == '/') {
@@ -101,88 +110,28 @@ static int bin_import(Runtime *runtime, Object **argv, unsigned int argc, Object
memcpy(full_path + written, path, path_len);
full_path[written + path_len] = '\0';
}
-
- Error sub_error;
- Error_Init(&sub_error);
- Source *src = Source_FromFile(full_path, &sub_error);
- if(src == NULL) {
- Object *o_none = Object_NewNone(heap, error);
- if(o_none == NULL)
- return -1;
-
- Object *o_err = Object_FromString(sub_error.message, -1, heap, error);
- if(o_err == NULL)
- return -1;
-
- Error_Free(&sub_error);
- rets[0] = o_none;
- rets[1] = o_err;
- return 2;
- }
+ Source *src = Source_FromFile(full_path, error);
+ if(src == NULL)
+ return -1;
CompilationErrorType errtyp;
- Executable *exe = compile(src, &sub_error, &errtyp);
+ Executable *exe = compile(src, error, &errtyp);
if(exe == NULL) {
- const char *errname;
- switch(errtyp) {
- default:
- case CompilationErrorType_INTERNAL: errname = NULL; break;
- case CompilationErrorType_SYNTAX: errname = "Syntax"; break;
- case CompilationErrorType_SEMANTIC: errname = "Semantic"; break;
- }
- UNUSED(errname);
-
- Object *o_none = Object_NewNone(heap, error);
- if(o_none == NULL) {
- Source_Free(src);
- return -1;
- }
-
- Object *o_err = Object_FromString(sub_error.message, -1, heap, error);
- if(o_err == NULL) {
- Source_Free(src);
- return -1;
- }
-
Source_Free(src);
- Error_Free(&sub_error);
- rets[0] = o_none;
- rets[1] = o_err;
- return 2;
+ return -1;
}
Object *sub_rets[8];
- int retc = run(runtime, &sub_error, exe, 0, NULL, NULL, 0, sub_rets);
+ int retc = run(runtime, error, exe, 0, NULL, NULL, 0, sub_rets);
if(retc < 0)
{
- const char *errname = "Runtime";
- // Snapshot?
- UNUSED(errname);
-
- Object *o_none = Object_NewNone(heap, error);
- if(o_none == NULL) {
- Source_Free(src);
- Executable_Free(exe);
- return -1;
- }
-
- Object *o_err = Object_FromString(sub_error.message, -1, heap, error);
- if(o_err == NULL) {
- Source_Free(src);
- Executable_Free(exe);
- return -1;
- }
- Error_Free(&sub_error);
- rets[0] = o_none;
- rets[1] = o_err;
Source_Free(src);
Executable_Free(exe);
- return 2;
+ return -1;
}
ASSERT(retc == 1);
- Error_Free(&sub_error);
Source_Free(src);
Executable_Free(exe);
rets[0] = sub_rets[0];
@@ -355,13 +304,14 @@ void bins_basic_init(StaticMapSlot slots[])
slots[3].as_type = Object_GetBoolType();
slots[4].as_type = Object_GetFloatType();
slots[5].as_type = Object_GetStringType();
- slots[6].as_type = Object_GetListType();
- slots[7].as_type = Object_GetMapType();
- slots[8].as_type = Object_GetFileType();
- slots[9].as_type = Object_GetDirType();
- slots[10].as_type = Object_GetNullableType();
- slots[11].as_type = Object_GetSumType();
- slots[12].as_object = Object_NewAny();
+ slots[6].as_type = Object_GetBufferType();
+ slots[7].as_type = Object_GetListType();
+ slots[8].as_type = Object_GetMapType();
+ slots[9].as_type = Object_GetFileType();
+ slots[10].as_type = Object_GetDirType();
+ slots[11].as_type = Object_GetNullableType();
+ slots[12].as_type = Object_GetSumType();
+ slots[13].as_object = Object_NewAny();
}
StaticMapSlot bins_basic[] = {
@@ -371,6 +321,7 @@ StaticMapSlot bins_basic[] = {
{ TYPENAME_BOOL, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
{ TYPENAME_FLOAT, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
{ TYPENAME_STRING, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
+ { TYPENAME_BUFFER, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
{ TYPENAME_LIST, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
{ TYPENAME_MAP, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
{ TYPENAME_FILE, SM_TYPE, .as_type = NULL /* Until bins_basic_init is called */ },
@@ -378,6 +329,8 @@ StaticMapSlot bins_basic[] = {
{ TYPENAME_NULLABLE, SM_TYPE, .as_type = NULL },
{ TYPENAME_SUM, SM_TYPE, .as_type = NULL },
{ "any", SM_OBJECT, .as_object = NULL },
+
+ { "net", SM_SMAP, .as_smap = bins_net, },
{ "math", SM_SMAP, .as_smap = bins_math, },
{ "files", SM_SMAP, .as_smap = bins_files, },
{ "buffer", SM_SMAP, .as_smap = bins_buffer, },
@@ -391,6 +344,7 @@ StaticMapSlot bins_basic[] = {
{ "error", SM_FUNCT, .as_funct = bin_error, .argc = 1 },
{ "assert", SM_FUNCT, .as_funct = bin_assert, .argc = -1 },
{ "istypeof", SM_FUNCT, .as_funct = bin_istypeof, .argc = 2, },
+ { "typename", SM_FUNCT, .as_funct = bin_typename, .argc = 1, },
{ "keysof", SM_FUNCT, .as_funct = bin_keysof, .argc = 1, },
{ NULL, SM_END, {}, {} },
};
diff --git a/src/lib/builtins/buffer.c b/src/lib/builtins/buffer.c
index 17bc11b..c10c671 100644
--- a/src/lib/builtins/buffer.c
+++ b/src/lib/builtins/buffer.c
@@ -1,5 +1,7 @@
#include "buffer.h"
+#include "utils.h"
#include "../utils/defs.h"
+#include "../utils/utf8.h"
#include "../runtime/runtime.h"
static int bin_new(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
@@ -12,7 +14,7 @@ static int bin_new(Runtime *runtime, Object **argv, unsigned int argc, Object *r
Error_Report(error, 0, "Argument is not an int");
return -1;
}
-
+
long long int size = Object_GetInt(argv[0]);
Object *temp = Object_NewBuffer(size, Runtime_GetHeap(runtime), error);
@@ -53,6 +55,22 @@ static int bin_sliceUp(Runtime *runtime, Object **argv, unsigned int argc, Objec
return 1;
}
+static int bin_fromString(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ ASSERT(argc == 1);
+ ParsedArgument pargs[1];
+ if (!parseArgs(error, argv, argc, pargs, "s"))
+ return -1;
+
+ const char *str = pargs[0].as_string.data;
+ size_t len = pargs[0].as_string.size;
+ Object *buffer = Object_NewBufferFromString(str, len, Runtime_GetHeap(runtime), error);
+ if (buffer == NULL)
+ return -1;
+ rets[0] = buffer;
+ return 1;
+}
+
static int bin_toString(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(argc);
@@ -70,6 +88,9 @@ static int bin_toString(Runtime *runtime, Object **argv, unsigned int argc, Obje
buffaddr = Object_GetBuffer(argv[0], &buffsize);
ASSERT(buffaddr != NULL);
+ if(utf8_strlen(buffaddr, buffsize) < 0)
+ return returnValues2(error, runtime, rets, "ns", "Buffer doesn't contain valid UTF-8");
+
Object *temp = Object_FromString(buffaddr, buffsize, Runtime_GetHeap(runtime), error);
if(temp == NULL)
@@ -80,7 +101,8 @@ static int bin_toString(Runtime *runtime, Object **argv, unsigned int argc, Obje
}
StaticMapSlot bins_buffer[] = {
- { "new", SM_FUNCT, .as_funct = bin_new, .argc = 1 },
+ { "new", SM_FUNCT, .as_funct = bin_new, .argc = 1 },
{ "sliceUp", SM_FUNCT, .as_funct = bin_sliceUp, .argc = 3 },
- { "toString", SM_FUNCT, .as_funct = bin_toString, .argc = 1 },
+ { "toString", SM_FUNCT, .as_funct = bin_toString, .argc = 1 },
+ { "fromString", SM_FUNCT, .as_funct = bin_fromString, .argc = 1 },
};
diff --git a/src/lib/builtins/files.c b/src/lib/builtins/files.c
index 8aadde7..56053ab 100644
--- a/src/lib/builtins/files.c
+++ b/src/lib/builtins/files.c
@@ -30,6 +30,7 @@
#include
#include "files.h"
+#include "utils.h"
#include "../utils/defs.h"
enum {
@@ -38,30 +39,19 @@ enum {
MD_APPEND = 2,
};
-#include
-
static int bin_openFile(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(argc);
ASSERT(argc == 2);
- if(!Object_IsString(argv[0]))
- {
- Error_Report(error, 0, "Expected first argument to be a string, but it's a %s", Object_GetName(argv[0]));
- return -1;
- }
-
- if(!Object_IsInt(argv[1]))
- {
- Error_Report(error, 0, "Expected second argument to be an int, but it's a %s", Object_GetName(argv[1]));
- return -1;
- }
-
Heap *heap = Runtime_GetHeap(runtime);
- int mode = Object_GetInt(argv[1]);
- const char *path = Object_GetString(argv[0], NULL);
- ASSERT(path != NULL); // We know argv[0] is a string.
+ ParsedArgument pargs[2];
+ if (!parseArgs(error, argv, argc, pargs, "si"))
+ return -1;
+
+ const char *path = pargs[0].as_string.data;
+ int mode = pargs[1].as_int;
FILE *fp;
{
@@ -91,39 +81,23 @@ static int bin_openFile(Runtime *runtime, Object **argv, unsigned int argc, Obje
fp = fopen(path, mode2);
if(fp == NULL) {
-
- Object *o_none = Object_NewNone(heap, error);
- if(o_none == NULL)
- return -1;
-
const char *errdesc;
switch(errno) {
- case EACCES: errdesc = "Can't access file"; break;
- case EPERM: errdesc = "Permission denied"; break;
- case EEXIST: errdesc = "File or folder already exists"; break;
- case EISDIR: errdesc = "Entity is a directory"; break;
+ case EACCES: errdesc = "Can't access file"; break;
+ case EPERM: errdesc = "Permission denied"; break;
+ case EEXIST: errdesc = "File or folder already exists"; break;
+ case EISDIR: errdesc = "Entity is a directory"; break;
case ENOTDIR: errdesc = "Entity is not a directory"; break;
- case ELOOP: errdesc = "Too many symbolic links"; break;
+ case ELOOP: errdesc = "Too many symbolic links"; break;
case ENAMETOOLONG: errdesc = "Entity name is too long"; break;
- case ENFILE: errdesc = "Open descriptors limit reached"; break;
- case ENOENT: errdesc = "File or folder doesn't exist"; break;
- default: errdesc = "Unexpected error"; break;
+ case ENFILE: errdesc = "Open descriptors limit reached"; break;
+ case ENOENT: errdesc = "File or folder doesn't exist"; break;
+ default: errdesc = "Unexpected error"; break;
}
-
- Object *o_error = Object_FromString(errdesc, -1, heap, error);
- if(o_error == NULL)
- return -1;
-
- rets[0] = o_none;
- rets[1] = o_error;
- return 2;
+ return returnValues(error, heap, rets, "ns", errdesc);
}
}
-
- rets[0] = Object_FromStream(fp, heap, error);
- if(rets[0] == NULL)
- return -1;
- return 1;
+ return returnValues(error, heap, rets, "F", fp);
}
static int bin_read(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
@@ -135,62 +109,31 @@ static int bin_read(Runtime *runtime, Object **argv, unsigned int argc, Object *
// Arg 1: buffer
// Arg 2: count
- if(!Object_IsFile(argv[0]))
- {
- Error_Report(error, 0, "Expected first argument to be a file, but it's a %s", Object_GetName(argv[0]));
+ ParsedArgument pargs[3];
+ if (!parseArgs(error, argv, argc, pargs, "FB?i"))
return -1;
- }
- if(!Object_IsBuffer(argv[1]))
- {
- Error_Report(error, 0, "Expected second argument to be a buffer, but it's a %s", Object_GetName(argv[1]));
- return -1;
- }
+ FILE *stream;
+ void *dstptr;
+ size_t dstlen;
+ size_t count;
- Heap *heap = Runtime_GetHeap(runtime);
-
- void *buff_addr;
- size_t buff_size;
-
- buff_addr = Object_GetBuffer(argv[1], &buff_size);
-
- size_t read_size;
-
- if(Object_IsNone(argv[2]))
- {
- read_size = buff_size;
- }
- else if(Object_IsInt(argv[2]))
- {
- long long int temp = Object_GetInt(argv[2]);
- if(temp < 0)
- {
- Error_Report(error, 0, "Expected third argument to be a positive integer");
+ stream = pargs[0].as_file;
+ dstptr = pargs[1].as_buffer.data;
+ dstlen = pargs[1].as_buffer.size;
+ if (pargs[2].defined) {
+ int n = pargs[2].as_int;
+ if (n < 0) {
+ Error_Report(error, 0, "Argument count must be a non-negative integer");
return -1;
}
+ count = MIN((size_t) n, dstlen);
+ } else
+ count = (size_t) dstlen;
- read_size = (size_t) temp; // TODO: Handle potential overflow.
-
- if(read_size > buff_size)
- read_size = buff_size;
- }
- else
- {
- Error_Report(error, 0, "Expected third argument to be an int or none, but it's a %s", Object_GetName(argv[0]));
- return -1;
- }
-
- FILE *fp = Object_GetStream(argv[0]);
+ size_t n = fread(dstptr, 1, count, stream);
- if(fp == NULL)
- return -1;
-
- size_t n = fread(buff_addr, 1, read_size, fp);
-
- rets[0] = Object_FromInt(n, heap, error);
- if(rets[0] == NULL)
- return -1;
- return 1;
+ return returnValues2(error, runtime, rets, "i", n);
}
static int bin_write(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
@@ -202,62 +145,31 @@ static int bin_write(Runtime *runtime, Object **argv, unsigned int argc, Object
// Arg 1: buffer
// Arg 2: count
- if(!Object_IsFile(argv[0]))
- {
- Error_Report(error, 0, "Expected first argument to be a file, but it's a %s", Object_GetName(argv[0]));
+ ParsedArgument pargs[3];
+ if (!parseArgs(error, argv, argc, pargs, "FB?i"))
return -1;
- }
- if(!Object_IsBuffer(argv[1]))
- {
- Error_Report(error, 0, "Expected second argument to be a buffer, but it's a %s", Object_GetName(argv[1]));
- return -1;
- }
+ FILE *stream;
+ void *srcptr;
+ size_t srclen;
+ size_t count;
- Heap *heap = Runtime_GetHeap(runtime);
-
- void *buff_addr;
- size_t buff_size;
-
- buff_addr = Object_GetBuffer(argv[1], &buff_size);
-
- size_t write_size;
-
- if(Object_IsNone(argv[2]))
- {
- write_size = buff_size;
- }
- else if(Object_IsInt(argv[2]))
- {
- long long int temp = Object_GetInt(argv[2]);
- if(temp < 0)
- {
- Error_Report(error, 0, "Expected third argument to be a positive integer");
+ stream = pargs[0].as_file;
+ srcptr = pargs[1].as_buffer.data;
+ srclen = pargs[1].as_buffer.size;
+ if (pargs[2].defined) {
+ int n = pargs[2].as_int;
+ if (n < 0) {
+ Error_Report(error, 0, "Argument count must be a non-negative integer");
return -1;
}
+ count = MIN((size_t) n, srclen);
+ } else
+ count = (int) srclen;
- write_size = (size_t) temp; // TODO: Handle potential overflow.
+ size_t n = fwrite(srcptr, 1, count, stream);
- if(write_size > buff_size)
- write_size = buff_size;
- }
- else
- {
- Error_Report(error, 0, "Expected third argument to be an int or none, but it's a %s", Object_GetName(argv[0]));
- return -1;
- }
-
- FILE *fp = Object_GetStream(argv[0]);
-
- if(fp == NULL)
- return -1;
-
- size_t n = fwrite(buff_addr, 1, write_size, fp);
-
- rets[0] = Object_FromInt(n, heap, error);
- if(rets[0] == NULL)
- return -1;
- return 1;
+ return returnValues2(error, runtime, rets, "i", n);
}
static int bin_openDir(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
@@ -310,9 +222,7 @@ static int bin_nextDirItem(Runtime *runtime, Object **argv, unsigned int argc, O
DIR *dir = Object_GetDIR(argv[0]);
ASSERT(dir != NULL);
-
- Heap *heap = Runtime_GetHeap(runtime);
-
+
errno = 0;
struct dirent *ent = readdir(dir);
@@ -327,10 +237,7 @@ static int bin_nextDirItem(Runtime *runtime, Object **argv, unsigned int argc, O
return -1;
}
- rets[0] = Object_FromString(ent->d_name, -1, heap, error);
- if(rets[0] == NULL)
- return -1;
- return 1;
+ return returnValues2(error, runtime, rets, "s", ent->d_name);
}
StaticMapSlot bins_files[] = {
diff --git a/src/lib/builtins/math.c b/src/lib/builtins/math.c
index 4a61465..5510349 100644
--- a/src/lib/builtins/math.c
+++ b/src/lib/builtins/math.c
@@ -81,8 +81,6 @@
return 1; \
}
-WRAP_FUNC(ceil)
-WRAP_FUNC(floor)
WRAP_FUNC(sin)
WRAP_FUNC(cos)
WRAP_FUNC(tan)
@@ -96,6 +94,48 @@ WRAP_FUNC(sqrt)
WRAP_FUNC_2(atan2)
WRAP_FUNC_2(pow)
+static int bin_ceil(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) \
+{
+ UNUSED(argc);
+ ASSERT(argc == 1);
+
+ if(Object_IsFloat(argv[0]))
+ {
+ double v = Object_GetFloat(argv[0]);
+
+ rets[0] = Object_FromInt(ceil(v), Runtime_GetHeap(runtime), error);
+ if(rets[0] == NULL)
+ return -1;
+ return 1;
+ }
+ else
+ {
+ Error_Report(error, 0, "Expected first argument to be a float, but it's a %s", Object_GetName(argv[0]));
+ return -1;
+ }
+}
+
+static int bin_floor(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) \
+{
+ UNUSED(argc);
+ ASSERT(argc == 1);
+
+ if(Object_IsFloat(argv[0]))
+ {
+ double v = Object_GetFloat(argv[0]);
+
+ rets[0] = Object_FromInt(floor(v), Runtime_GetHeap(runtime), error);
+ if(rets[0] == NULL)
+ return -1;
+ return 1;
+ }
+ else
+ {
+ Error_Report(error, 0, "Expected first argument to be a float, but it's a %s", Object_GetName(argv[0]));
+ return -1;
+ }
+}
+
StaticMapSlot bins_math[] = {
{ "PI", SM_FLOAT, .as_float = M_PI },
{ "E", SM_FLOAT, .as_float = M_E },
diff --git a/src/lib/builtins/net.c b/src/lib/builtins/net.c
new file mode 100644
index 0000000..0fb0279
--- /dev/null
+++ b/src/lib/builtins/net.c
@@ -0,0 +1,195 @@
+#include
+#include
+#include
+#include
+#include
+#include "net.h"
+#include "utils.h"
+#include "../utils/defs.h"
+
+static int bin_socket(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ ASSERT(argc == 3);
+ ParsedArgument pargs[3];
+ if (!parseArgs(error, argv, argc, pargs, "iii"))
+ return -1;
+
+ int domain = pargs[0].as_int;
+ int type = pargs[1].as_int;
+ int proto = pargs[2].as_int;
+
+ int fd = socket(domain, type, proto);
+
+ if (fd < 0)
+ return returnValues2(error, runtime, rets, "ns", strerror(errno));
+ return returnValues2(error, runtime, rets, "i", fd);
+}
+
+static int bin_bind(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+ // 1: family
+ // 2: port (family=AF_INET)
+ // 3: addr (family=AF_INET)
+
+ ASSERT(argc == 4);
+ ParsedArgument pargs[4];
+ if (!parseArgs(error, argv, argc, pargs, "iii?s"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+ int family = pargs[1].as_int;
+
+ if (family != AF_INET)
+ return returnValues2(error, runtime, rets, "ns", "Invalid family (only AF_INET is supported at the moment)");
+
+ int port = pargs[2].as_int;
+ const char *addr = pargs[3].defined ? pargs[3].as_string.data : NULL;
+
+ struct in_addr parsed_addr;
+ if (addr == NULL)
+ parsed_addr.s_addr = INADDR_ANY;
+ else {
+ int res = inet_pton(family, addr, &parsed_addr);
+ if (res == 0) return returnValues2(error, runtime, rets, "ns", "Invalid address");
+ if (res < 0) return returnValues2(error, runtime, rets, "ns", "Invalid family");
+ }
+
+ struct sockaddr_in buff;
+ buff.sin_family = family;
+ buff.sin_port = htons(port);
+ buff.sin_addr = parsed_addr;
+ if (bind(fd, (struct sockaddr*) &buff, sizeof(buff)) < 0)
+ return returnValues2(error, runtime, rets, "s", strerror(errno));
+
+ return returnValues2(error, runtime, rets, "n");
+}
+
+static int bin_listen(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+ // 1: backlog
+
+ ASSERT(argc == 2);
+ ParsedArgument pargs[2];
+ if (!parseArgs(error, argv, argc, pargs, "ii"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+ int backlog = pargs[1].as_int;
+
+ if (listen(fd, backlog) < 0)
+ return returnValues2(error, runtime, rets, "s", strerror(errno));
+ return returnValues2(error, runtime, rets, "n");
+}
+
+static int bin_recv(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+ // 1: buffer
+ // 2: count
+
+ ASSERT(argc == 3);
+ ParsedArgument pargs[3];
+ if (!parseArgs(error, argv, argc, pargs, "iB?i"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+ void *dstptr = pargs[1].as_buffer.data;
+ size_t dstlen = pargs[1].as_buffer.size;
+
+ size_t count;
+ if (pargs[2].defined) {
+ int n = pargs[2].as_int;
+ if (n < 0)
+ return returnValues2(error, runtime, rets, "ns", "Invalid negative count");
+ count = MIN((size_t) n, dstlen);
+ } else
+ count = dstlen;
+
+ ssize_t res = recv(fd, dstptr, count, 0);
+ if (res < 0)
+ return returnValues2(error, runtime, rets, "ns", strerror(errno));
+ return returnValues2(error, runtime, rets, "i", res);
+}
+
+static int bin_send(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+ // 1: buffer
+ // 2: count
+
+ ASSERT(argc == 3);
+ ParsedArgument pargs[3];
+ if (!parseArgs(error, argv, argc, pargs, "iB?i"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+ void *srcptr = pargs[1].as_buffer.data;
+ size_t srclen = pargs[1].as_buffer.size;
+
+ size_t count;
+ if (pargs[2].defined) {
+ int n = pargs[2].as_int;
+ if (n < 0)
+ return returnValues2(error, runtime, rets, "ns", "Invalid negative count");
+ count = MIN((size_t) n, srclen);
+ } else
+ count = srclen;
+
+ ssize_t res = send(fd, srcptr, count, 0);
+ if (res < 0)
+ return returnValues2(error, runtime, rets, "ns", strerror(errno));
+ return returnValues2(error, runtime, rets, "i", res);
+}
+
+static int bin_accept(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+
+ ASSERT(argc == 1);
+ ParsedArgument pargs[1];
+ if (!parseArgs(error, argv, argc, pargs, "i"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+
+ struct sockaddr_in new_addr;
+ socklen_t new_addr_size;
+ int new_fd = accept(fd, (struct sockaddr*) &new_addr, &new_addr_size);
+ if (new_fd < 0)
+ return returnValues2(error, runtime, rets, "ns", strerror(errno));
+ ASSERT(new_addr.sin_family == AF_INET);
+ ASSERT(new_addr_size == sizeof(struct sockaddr_in));
+
+ return returnValues2(error, runtime, rets, "iii", new_fd, new_addr.sin_addr.s_addr, new_addr.sin_port);
+}
+
+
+static int bin_close(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
+{
+ // 0: fd
+
+ ASSERT(argc == 1);
+ ParsedArgument pargs[1];
+ if (!parseArgs(error, argv, argc, pargs, "i"))
+ return -1;
+
+ int fd = pargs[0].as_int;
+ int res = close(fd);
+ return returnValues2(error, runtime, rets, "i", res);
+}
+
+StaticMapSlot bins_net[] = {
+ { "AF_INET", SM_INT, .as_int = AF_INET, },
+ { "SOCK_STREAM", SM_INT, .as_int = SOCK_STREAM, },
+ { "SOCK_DGRAM", SM_INT, .as_int = SOCK_DGRAM, },
+ { "socket", SM_FUNCT, .as_funct = bin_socket, .argc = 3, },
+ { "bind", SM_FUNCT, .as_funct = bin_bind, .argc = 4, },
+ { "listen", SM_FUNCT, .as_funct = bin_listen, .argc = 2, },
+ { "accept", SM_FUNCT, .as_funct = bin_accept, .argc = 1, },
+ { "recv", SM_FUNCT, .as_funct = bin_recv, .argc = 3, },
+ { "send", SM_FUNCT, .as_funct = bin_send, .argc = 3, },
+ { "close", SM_FUNCT, .as_funct = bin_close, .argc = 1, },
+ { NULL, SM_END, {}, {} },
+};
diff --git a/src/lib/builtins/net.h b/src/lib/builtins/net.h
new file mode 100644
index 0000000..b4031d7
--- /dev/null
+++ b/src/lib/builtins/net.h
@@ -0,0 +1,2 @@
+#include "../runtime/runtime.h"
+extern StaticMapSlot bins_net[];
\ No newline at end of file
diff --git a/src/lib/builtins/start.noja b/src/lib/builtins/start.noja
index 895fe01..88b30d4 100644
--- a/src/lib/builtins/start.noja
+++ b/src/lib/builtins/start.noja
@@ -1,53 +1,41 @@
-fun abs(n: Numeric) {
- if n < 0:
- return -n;
- return n;
-}
+fun dummy() {}
-fun min(x, y) {
- if x < y:
- return x;
- return y;
-}
-
-fun max(x, y) {
- if x > y:
- return x;
- return y;
-}
-
-Func = type(abs);
+Func = type(dummy);
NFunc = type(print);
Numeric = int | float;
Callable = Func | NFunc;
Collection = List | Map;
+chr = string.chr;
+ord = string.ord;
+cat = string.cat;
+
# Stringify a number between 0 and 9.
fun stringFromDigit(digit: int) {
assert(digit >= 0 and digit <= 9);
- return string.chr(string.ord("0") + digit);
+ return chr(ord("0") + digit);
+}
+
+# This function returns the magnitude of the
+# argument and the power of 10 with the same
+# magnitude.
+fun getIntegerMagnitude(n: int) {
+ assert(n >= 0);
+ magn = 0;
+ powr = 1;
+ # Basically grow the power until it's
+ # bigger than the input.
+ while n >= powr * 10: {
+ powr = 10 * powr;
+ magn = 1 + magn;
+ }
+ return magn, powr;
}
# Stringify an unsigned integer
fun stringFromInteger(n: int) {
- # This function returns the magnitude of the
- # argument and the power of 10 with the same
- # magnitude.
- fun getIntegerMagnitude(n: int) {
- assert(n >= 0);
- magn = 0;
- powr = 1;
- # Basically grow the power until it's
- # bigger than the input.
- while n >= powr * 10: {
- powr = 10 * powr;
- magn = 1 + magn;
- }
- return magn, powr;
- }
-
negative = (n < 0);
# Get the power of 10 with the
@@ -63,27 +51,167 @@ fun stringFromInteger(n: int) {
temp = temp % power; # Remove it
# Add the digit to the output.
- text = string.cat(text, stringFromDigit(digit));
+ text = cat(text, stringFromDigit(digit));
# Prep for the next iteration.
power = power / 10;
}
if negative:
- text = string.cat("-", text);
+ text = cat("-", text);
return text;
}
+fun stringFromFloating(f: float, max_decimals: int = 2) {
+ # This function is just a fast hack
+ epsilon = 0.00000001;
+ asint = math.floor(f);
+ text = stringFromInteger(asint);
+ text = cat(text, ".");
+ magn = 1.0;
+ temp = f - asint;
+ i = 0;
+ do {
+ magn = magn / 10;
+ digit = math.floor(temp / magn);
+ text = cat(text, stringFromDigit(digit));
+ temp = temp - magn * digit; # Remove the handled digit
+ i = i+1;
+ } while temp > epsilon and i < max_decimals;
+ return text;
+}
+
+fun stringFromNumeric(num: Numeric) {
+ if type(num) == int:
+ return stringFromInteger(num);
+ return stringFromFloating(num);
+}
+
fun floatFromInteger(n: int)
return 1.0 * n;
+fun stringFromList(list: List) {
+
+ s = "[";
+ i = 0;
+ while i < count(list): {
+ s = cat(s, toString(list[i]));
+ i = i+1;
+ if i < count(list):
+ s = cat(s, ", ");
+ }
+ s = cat(s, "]");
+ return s;
+}
+
+fun stringFromMap(map: Map, can_use_method=true) {
+ s = none; # Result
+ has_method = istypeof(Callable, map.toString);
+ if can_use_method and has_method:
+ s = map->toString();
+ else {
+ s = "{";
+ i = 0;
+ keys = keysof(map);
+ while i < count(keys): {
+ s = cat(s, toString(keys[i]), ": ", toString(map[keys[i]]));
+ i = i+1;
+ if i < count(keys):
+ s = cat(s, ", ");
+ }
+ s = cat(s, "}");
+ }
+ return s;
+}
+
fun integerFromDigit(char: String) {
- ord = string.ord;
res = ord(char) - ord('0');
if res < 0 or res > 9:
error("String isn't a digit");
return res;
}
-fun append(array: List, item)
- array[count(array)] = item;
\ No newline at end of file
+fun GenericIterator(T) return {
+ set : T,
+ keys : List,
+ index: ?int,
+ next : Callable
+};
+
+fun makeGenericIterator(set: Collection) return {
+ set : set,
+ keys : keysof(set),
+ index: none,
+ fun next(iter: GenericIterator(type(set))) {
+
+ index = iter.index;
+
+ if index == none:
+ index = 0;
+ else {
+ if index < count(iter.keys):
+ index = index+1;
+ }
+
+ if index == count(iter.keys):
+ return none;
+
+ iter.index = index;
+ key = iter.keys[index];
+ val = iter.set[key];
+ return val, key, index;
+ }
+};
+
+return {
+
+ Func: Func,
+ NFunc: NFunc,
+ Numeric: Numeric,
+ Callable: Callable,
+ Collection: Collection,
+
+ fun makeIterator(set: Collection, can_use_method=true) {
+ if can_use_method and type(set) == Map and istypeof(Callable, set.iter):
+ return set->iter();
+ return makeGenericIterator(set);
+ }
+
+ fun toString(value, can_use_method=true) {
+ T = type(value);
+ if T == None : return "none";
+ if T == int : return stringFromInteger(value);
+ if T == float: return stringFromFloating(value);
+ if T == List : return stringFromList(value);
+ if T == Map : return stringFromMap(value, can_use_method);
+ if T == Type : return typename(T);
+ if T == Func : return "Func";
+ if T == NFunc: return "NFunc";
+ if T == String: return value;
+ error(cat("Don't know how to convert ", typename(value), " to a string"));
+ }
+
+ fun isCallable(x)
+ return istypeof(Callable, x);
+
+ fun append(array: List, item)
+ array[count(array)] = item;
+
+ fun abs(n: Numeric) {
+ if n < 0:
+ return -n;
+ return n;
+ }
+
+ fun min(x, y) {
+ if x < y:
+ return x;
+ return y;
+ }
+
+ fun max(x, y) {
+ if x > y:
+ return x;
+ return y;
+ }
+};
diff --git a/src/lib/builtins/utils.c b/src/lib/builtins/utils.c
new file mode 100644
index 0000000..6677032
--- /dev/null
+++ b/src/lib/builtins/utils.c
@@ -0,0 +1,174 @@
+#include "utils.h"
+
+int returnValuesVA(Error *error, Heap *heap, Object *rets[static MAX_RETS], const char *fmt, va_list va)
+{
+ int retc = 0, i = 0;
+ while (fmt[i] != '\0') {
+
+ if (retc == MAX_RETS) {
+ Error_Report(error, 1, "Return value limit reached");
+ return -1;
+ }
+
+ Object *ret;
+ switch (fmt[i]) {
+ case 'o': ret = va_arg(va, Object*); break;
+ case 'n': ret = Object_NewNone(heap, error); break;
+ case 'i': ret = Object_FromInt (va_arg(va, int), heap, error); break;
+ case 'f': ret = Object_FromFloat(va_arg(va, double), heap, error); break;
+ case 's': ret = Object_FromString(va_arg(va, char*), -1, heap, error); break;
+ default:
+ Error_Report(error, 1, "Invalid format specifier '%c'", fmt[i]);
+ return -1;
+ }
+
+ if (ret == NULL)
+ return -1;
+
+ rets[retc++] = ret;
+ i++;
+ }
+ return retc;
+}
+
+bool parseArgs(Error *error,
+ Object **argv,
+ unsigned int argc,
+ ParsedArgument *pargs,
+ const char *fmt)
+{
+ unsigned int current_arg = 0;
+ int i = 0;
+ while (fmt[i] != '\0') {
+
+ if (current_arg == argc) {
+ Error_Report(error, 0, "Missing arguments");
+ return false;
+ }
+ Object *arg = argv[current_arg];
+
+ bool may_be_none = false;
+ if (fmt[i] == '?') {
+ may_be_none = true;
+ i++;
+ if (fmt[i] == '\0') {
+ Error_Report(error, 1, "Format terminated unexpectedly");
+ return false;
+ }
+ }
+
+ if (may_be_none && Object_IsNone(arg)) {
+ pargs[current_arg].defined = false;
+ } else {
+ switch (fmt[i]) {
+
+ case 'o': /* Any object */
+ pargs[current_arg].defined = true;
+ break;
+
+ case 'b': /* Boolean */
+ if (!Object_IsBool(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be bool, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ pargs[current_arg].defined = true;
+ pargs[current_arg].as_bool = Object_GetBool(arg);
+ break;
+
+ case 'B': /* Buffer */
+ {
+ if (!Object_IsBuffer(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be a buffer, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ void *data;
+ size_t size;
+ data = Object_GetBuffer(arg, &size);
+ pargs[current_arg].defined = true;
+ pargs[current_arg].as_buffer.data = data;
+ pargs[current_arg].as_buffer.size = size;
+ break;
+ }
+
+ case 'i': /* Integer */
+ if (!Object_IsInt(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be an int, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ pargs[current_arg].defined = true;
+ pargs[current_arg].as_int = Object_GetInt(arg);
+ break;
+
+ case 'f': /* Float */
+ if (!Object_IsFloat(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be a float, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ pargs[current_arg].defined = true;
+ pargs[current_arg].as_float = Object_GetFloat(arg);
+ break;
+
+ case 'l': /* List */
+ if (!Object_IsList(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be a list, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ pargs[current_arg].defined = true;
+ break;
+
+ case 'm': /* Map */
+ if (!Object_IsMap(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be a map, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ break;
+
+ case 's': /* String */
+ {
+ if (!Object_IsString(arg)) {
+ Error_Report(error, 0, "Argument %d was expected to be a string, but a %s was provided", current_arg+1, arg->type->name);
+ return false;
+ }
+ const void *data;
+ size_t size;
+ data = Object_GetString(arg, &size);
+ pargs[current_arg].defined = true;
+ pargs[current_arg].as_string.data = data;
+ pargs[current_arg].as_string.size = size;
+ break;
+ }
+
+ default:
+ Error_Report(error, 1, "Invalid argument parser format specifier");
+ return false;
+ }
+ }
+ i++;
+ current_arg++;
+ }
+
+#ifndef NDEBUG
+ if (current_arg < argc) {
+ // Ignoring part of the format string
+ }
+#endif
+ return true;
+}
+
+int returnValues2(Error *error, Runtime *runtime, Object *rets[static MAX_RETS], const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int retc = returnValuesVA(error, Runtime_GetHeap(runtime), rets, fmt, va);
+ va_end(va);
+ return retc;
+}
+
+int returnValues(Error *error, Heap *heap, Object *rets[static MAX_RETS], const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int retc = returnValuesVA(error, heap, rets, fmt, va);
+ va_end(va);
+ return retc;
+}
\ No newline at end of file
diff --git a/src/lib/builtins/utils.h b/src/lib/builtins/utils.h
new file mode 100644
index 0000000..f0f7240
--- /dev/null
+++ b/src/lib/builtins/utils.h
@@ -0,0 +1,27 @@
+#include
+#include
+#include "../utils/error.h"
+#include "../objects/objects.h"
+#include "../runtime/runtime.h"
+
+typedef struct {
+ bool defined;
+ union {
+ struct {
+ const char *data;
+ size_t size;
+ } as_string;
+ struct {
+ void *data;
+ size_t size;
+ } as_buffer;
+ bool as_bool;
+ int64_t as_int;
+ double as_float;
+ FILE *as_file;
+ };
+} ParsedArgument;
+
+bool parseArgs(Error *error, Object **argv, unsigned int argc, ParsedArgument *pargs, const char *fmt);
+int returnValues (Error *error, Heap *heap, Object *rets[static MAX_RETS], const char *fmt, ...);
+int returnValues2(Error *error, Runtime *runtime, Object *rets[static MAX_RETS], const char *fmt, ...);
diff --git a/src/lib/objects/o_buffer.c b/src/lib/objects/o_buffer.c
index 95a586f..3e35870 100644
--- a/src/lib/objects/o_buffer.c
+++ b/src/lib/objects/o_buffer.c
@@ -76,6 +76,18 @@ _Bool Object_IsBuffer(Object *obj)
return Object_GetType(obj) == Object_GetBufferType();
}
+Object *Object_NewBufferFromString(const char *str, size_t len, Heap *heap, Error *error)
+{
+ Object *buffer = Object_NewBuffer(len, heap, error);
+ if (buffer == NULL)
+ return NULL;
+
+ void *ptr = Object_GetBuffer(buffer, NULL);
+ memcpy(ptr, str, len);
+
+ return buffer;
+}
+
Object *Object_NewBuffer(size_t size, Heap *heap, Error *error)
{
// Make the thing.
diff --git a/src/lib/objects/objects.c b/src/lib/objects/objects.c
index 3c274a6..2eb49ef 100644
--- a/src/lib/objects/objects.c
+++ b/src/lib/objects/objects.c
@@ -139,7 +139,7 @@ int Object_Call(Object *obj, Object **argv, unsigned int argc, Object *rets[stat
if(type->call == NULL)
{
- Error_Report(err, 0, "Object %s doesn't implement %s", Object_GetName(obj), __func__);
+ Error_Report(err, 0, "Object of type %s isn't callable", Object_GetName(obj), __func__);
return -1;
}
diff --git a/src/lib/objects/objects.h b/src/lib/objects/objects.h
index 778c777..8aa5126 100644
--- a/src/lib/objects/objects.h
+++ b/src/lib/objects/objects.h
@@ -120,6 +120,7 @@ Object* Object_NewList2(int num, Object **items, Heap *heap, Error *error);
Object* Object_NewListOfConsecutiveIntegers(int first, int last, Heap *heap, Error *error);
Object* Object_NewNone(Heap *heap, Error *error);
Object* Object_NewBuffer(size_t size, Heap *heap, Error *error);
+Object* Object_NewBufferFromString(const char *str, size_t len, Heap *heap, Error *error);
Object* Object_NewClosure(Object *parent, Object *new_map, Heap *heap, Error *error);
Object* Object_SliceBuffer(Object *obj, size_t offset, size_t length, Heap *heap, Error *error);
Object* Object_NewNullable(Object *item, Heap *heap, Error *error);
diff --git a/src/lib/runtime/runtime.c b/src/lib/runtime/runtime.c
index 70f2e91..be83a56 100644
--- a/src/lib/runtime/runtime.c
+++ b/src/lib/runtime/runtime.c
@@ -879,6 +879,7 @@ static _Bool step(Runtime *runtime, Error *error)
arg_index = ops[0].as_int;
arg_name = ops[1].as_string;
+ ASSERT(arg_name != NULL);
if(runtime->frame->used < 2)
{
diff --git a/src/lib/utils/error.c b/src/lib/utils/error.c
index a23ca20..9f4c580 100644
--- a/src/lib/utils/error.c
+++ b/src/lib/utils/error.c
@@ -104,7 +104,7 @@ void _Error_Report2(Error *err, _Bool internal,
}
else
{
- snprintf(temp, p+1, fmt, va2);
+ vsnprintf(temp, p+1, fmt, va2);
err->truncated = 0;
err->message = temp;
err->length = p;