From 74a1648af8627e9a27ce5c1b928952efc168e174 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Sun, 15 Jan 2023 00:51:36 +0100 Subject: [PATCH] utility functions for implementing noja functions in c; built-ins for socket management; http server draft in noja --- examples/data_structures/stack.noja | 101 +++++++ examples/data_structures/stack_test.noja | 17 ++ examples/http.noja | 15 +- examples/http_server/main.noja | 109 +++++++ examples/http_server/pages/login.html | 12 + examples/http_server/parse.noja | 359 +++++++++++++++++++++++ examples/http_server/request.noja | 87 ++++++ examples/http_server/response.noja | 113 +++++++ examples/http_server/scanner.noja | 56 ++++ examples/http_server/server.noja | 80 +++++ examples/scan.noja | 8 +- misc/makegarbage.noja | 2 +- src/cli/main.c | 2 +- src/lib/builtins/basic.c | 144 ++++----- src/lib/builtins/buffer.c | 28 +- src/lib/builtins/files.c | 203 ++++--------- src/lib/builtins/math.c | 44 ++- src/lib/builtins/net.c | 195 ++++++++++++ src/lib/builtins/net.h | 2 + src/lib/builtins/start.noja | 208 ++++++++++--- src/lib/builtins/utils.c | 174 +++++++++++ src/lib/builtins/utils.h | 27 ++ src/lib/objects/o_buffer.c | 12 + src/lib/objects/objects.c | 2 +- src/lib/objects/objects.h | 1 + src/lib/runtime/runtime.c | 1 + src/lib/utils/error.c | 2 +- 27 files changed, 1705 insertions(+), 299 deletions(-) create mode 100644 examples/data_structures/stack.noja create mode 100644 examples/data_structures/stack_test.noja create mode 100755 examples/http_server/main.noja create mode 100644 examples/http_server/pages/login.html create mode 100644 examples/http_server/parse.noja create mode 100644 examples/http_server/request.noja create mode 100644 examples/http_server/response.noja create mode 100644 examples/http_server/scanner.noja create mode 100644 examples/http_server/server.noja create mode 100644 src/lib/builtins/net.c create mode 100644 src/lib/builtins/net.h create mode 100644 src/lib/builtins/utils.c create mode 100644 src/lib/builtins/utils.h 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;