advances of the http server example; built-ins for generating random numbers; built-ins for slicing, trimming and converting strings to integers

This commit is contained in:
Francesco Cozzuto
2023-01-15 18:22:18 +01:00
parent 74a1648af8
commit 0f40181e58
15 changed files with 664 additions and 78 deletions
+107
View File
@@ -0,0 +1,107 @@
fun isSeparator(char: String)
return char == "(" or char == ")"
or char == "<" or char == ">"
or char == "@" or char == ","
or char == ";" or char == ":"
or char == "\\" or char == '"'
or char == "/" or char == "?"
or char == "[" or char == "]"
or char == "=" or char == " "
or char == "{" or char == "}"
or char == "\t";
fun isControl(char: String) {
n = string.ord(char);
return (n >= 0 and n <= 31) or n == 127;
}
fun isChar(char: String)
return string.ord(char) < 128;
fun isToken(char: String)
return isChar(char)
and not isControl(char)
and not isSeparator(char);
fun parseToken(src: String, i: int) {
offset = i;
while i < count(src) and isToken(src[i]):
i = i+1;
length = i - offset;
return string.slice(src, offset, length), i;
}
fun isCookieOctet(char: String) {
n = string.ord(char);
return n == 33
or (n >= 35 and n <= 43)
or (n >= 45 and n <= 58)
or (n >= 60 and n <= 91)
or (n >= 93 and n <= 126);
}
fun parseCookieValue(src: String, i: int) {
dquote = false;
if i < count(src) and src[i] == '"': {
dquote = true;
i = i+1;
}
if i == count(src) or not isCookieOctet(src[i]):
return none, none, "Missing value";
offset = i;
do
i = i+1;
while i < count(src) and isCookieOctet(src[i]);
length = i - offset;
if dquote: {
if i == count(src) or src[i] != '"':
return none, none, "Missing closing double quote";
i = i+1;
}
return string.slice(src, offset, length), i;
}
fun parseCookiePair(src: String, i: int) {
while i < count(src) and src[i] == " ":
i = i+1;
name, i = parseToken(src, i);
if i == count(src) or src[i] != "=":
return none, none, none, "Missing \"=\" after name";
i = i+1; # Skip the "="
value, i, error = parseCookieValue(src, i);
if error != none:
return none, none, none, error;
while i < count(src) and src[i] == " ":
i = i+1;
if i < count(src) and src[i] == ";":
i = i+1;
return name, value, i;
}
return {
fun parseCookieHeader(src: String) {
cookies = {};
i = 0;
while i < count(src): {
name, value, i, error = parseCookiePair(src, i);
if error != none:
return none, error;
cookies[name] = value;
}
return cookies;
}
};
+219 -58
View File
@@ -1,10 +1,39 @@
server = import("server.noja"); server = import("server.noja");
request = import("request.noja"); request = import("request.noja");
response = import("response.noja"); response = import("response.noja");
cookie = import("cookie.noja");
urlencoded = import("urlencoded.noja");
Request = request.Request; Request = request.Request;
respond = response.new;
fun getCookie(req: Request, name: String) {
src = req.headers["Cookie"];
if src == none:
return none;
cookies, error = cookie.parseCookieHeader(src);
if error != none:
return none, error;
return cookies[name];
}
fun getSessionID(req: Request) {
session_id, error = getCookie(req, "SESSID");
if error != none:
return none, error;
session_id = string.toInt(session_id);
return session_id;
}
fun getUsername(req: Request) {
session_id, error = getSessionID(req);
if error != none:
return none, none, error;
username = session_table[session_id];
return username, session_id;
}
fun loadFile(path: String) { fun loadFile(path: String) {
stream, error = files.openFile(path, files.READ); stream, error = files.openFile(path, files.READ);
if error != none: if error != none:
return none, error; return none, error;
@@ -23,77 +52,209 @@ fun loadFile(path: String) {
} while num_bytes == size; } while num_bytes == size;
files.close(stream); #files.close(stream);
return text; return text;
} }
fun generateHome() { user_table = {};
title = "Homepage"; session_table = {};
content = "Hello, world!";
return string.cat( fun redirect(endpoint) {
"<html>", fun callback()
"<head>", return response.new(307, none, {'Location': endpoint});
"<title>", title, "</title>", return callback;
"</head>",
"<body>",
"<a>", content, "</a>",
"</body>",
"</html>"
);
} }
fun generateLogout() { route_table = {
} '/': {
GET: redirect("/login")
},
'/home': {
fun GET(req: Request) {
username, _, error = getUsername(req);
if error != none:
return respond(400, error);
if username == none:
return respond(401, "You're not logged in");
content = string.cat(
"<html>",
"<head>",
"<title>Homepage</title>",
"</head>",
"<body>",
"<a>Hello, ", username, "!</a>",
"</body>",
"</html>"
);
return respond(200, content);
}
},
'/login': {
GET: 'pages/login.html',
fun POST(req: Request) {
table = { username, _, error = getUsername(req);
'/': 'login.html', if error != none:
'/home': generateHome, return respond(400, error);
'/login': 'login.html', if username != none:
'/signup': 'signup.html', return respond(400, "You're already logged in");
'/logout': generateLogout
fun areValid(params) {
expect = {user: String, pass: String};
return istypeof(expect, params)
and count(params.user) > 0
and count(params.pass) > 0;
}
params = urlencoded.parse(req.body);
if not areValid(params):
return respond(400, "Invalid parameters");
username = params.user;
password = params.pass;
info = user_table[username];
if info == none:
return respond(200, "No such user");
if info.pass != password:
return respond(200, "Wrong password");
# Logged in!
session_id = generateRandomValue(0, 1000);
session_table[session_id] = username;
message = cat("Welcome, ", username, "!");
headers = {
"Location" : "/home",
"Set-Cookie": cat("SESSID=", session_id)
};
return respond(303, message, headers);
}
},
'/signup': {
GET: 'pages/signup.html',
fun POST(req: Request) {
username, _, error = getUsername(req);
if error != none:
return respond(400, error);
if username != none:
return respond(400, "You're already logged in");
fun areValid(params) {
expect = {user: String, pass: String};
return istypeof(expect, params)
and count(params.user) > 0
and count(params.pass) > 0;
}
params = urlencoded.parse(req.body);
if not areValid(params):
return respond(400, "Invalid parameters");
username = params.user;
password = params.pass;
if user_table[password] != none:
return respond(200, "User already exists");
# Signed up!
user_table[username] = {pass: password};
session_id = random.generate(0, 1000);
session_table[session_id] = username;
# Respond
message = string.cat("Welcome, ", username, "!");
headers = {
"Location" : "/home",
"Set-Cookie": string.cat("SESSID=", toString(session_id))
};
return respond(303, message, headers);
}
},
'/logout': {
fun GET(req: Request) {
username, session_id, error = getUsername(req);
if error != none:
return respond(400, error);
if username == none:
return respond(401, "You're not logged in");
session_table[session_id] = none;
return respond(303, "Goodbye!", {"Location": "/login"});
}
}
}; };
default = 'notfound.html'; fun join(list: List, glue="") {
if count(list) == 0:
text = "";
else {
i = 1;
text = toString(list[0]);
while i < count(list): {
text = string.cat(text, glue, toString(list[i]));
i = i+1;
}
}
return text;
}
debug = true; debug = true;
fun joinPath(a, b) {
u = a[count(a)-1] == '/';
v = b[count(b)-1] == '/';
if u and v:
return string.cat(string.slice(a, 0, count(a)-1), b);
if u or v:
return string.cat(a, b);
return string.cat(a, '/', b);
}
fun getRouteAllowedMethods(route) {
method_table = route_table[route];
if method_table == none:
return none;
allowed = keysof(method_table);
if method_table.HEAD == none and method_table.GET != none:
append(allowed, "HEAD");
if method_table.OPTIONS == none:
append(allowed, "OPTIONS");
return allowed;
}
fun callback(req: Request) { fun callback(req: Request) {
path = req.url.path; endpoint = req.url.path;
method_table = route_table[endpoint];
if method_table == none:
return response.new(404);
if path == "/" or path == "/login": { method_item = method_table[req.method];
if method_item == none: {
if req.method == "POST": { allowed = getRouteAllowedMethods(endpoint);
if req.method == "OPTIONS":
status = 204;
} 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 else
data, error = loadFile(entry); status = 405;
return response.new(status, none, {"Allow": join(allowed, ", ")});
} }
# Generate response object if isCallable(method_item):
{ res = method_item(req);
if error == none: else {
file = joinPath(getCurrentWorkingDirectory(), method_item);
data, error = loadFile(file);
if error == none:
res = response.new(200, data); res = response.new(200, data);
else if not_found:
res = response.new(404, data);
else if debug: else if debug:
res = response.new(500, error); res = response.new(500, error);
else else
+4 -4
View File
@@ -1,12 +1,12 @@
<html> <html>
<head> <head>
<title>Login page</title> <title>Log-in page</title>
</head> </head>
<body> <body>
<form action="/login" method="POST"> <form action="/login" method="POST">
<input type="text" name="user" placeholder="[Username]" /> <input type="text" name="user" placeholder="[Username]" />
<input type="password" name="pass" placeholder="[Password]" /> <input type="password" name="pass" placeholder="[Password]" />
<input type="submit" value="Enter" /> <input type="submit" value="Log-in" />
</form> </form>
</body> </body>
</html> </html>
+12
View File
@@ -0,0 +1,12 @@
<html>
<head>
<title>Sign-up page</title>
</head>
<body>
<form action="/signup" method="POST">
<input type="text" name="user" placeholder="[Username]" />
<input type="password" name="pass" placeholder="[Password]" />
<input type="submit" value="Sign-up!" />
</form>
</body>
</html>
+21 -5
View File
@@ -22,9 +22,23 @@ Request = {
method : String, method : String,
url : URL, url : URL,
version: Version, version: Version,
body : None | Buffer | [Buffer, Buffer] body : String
}; };
fun bodyBytesToString(body: None | Buffer | [Buffer, Buffer]) {
if type(body) == None:
body = "";
else if type(body) == Buffer:
body = buffer.toString(body);
else
body = cat(buffer.toString(body[0]),
buffer.toString(body[1]));
return body;
}
fun printBody(body: None | Buffer | [Buffer, Buffer])
print(bodyToString(body));
fun fromSocket(fd: int, max_head: int = 1024) { fun fromSocket(fd: int, max_head: int = 1024) {
{ {
@@ -60,9 +74,11 @@ fun fromSocket(fd: int, max_head: int = 1024) {
# Receive the remaining portion of the # Receive the remaining portion of the
# request's body (if it wasn't already) # request's body (if it wasn't already)
{ {
content_length = request.headers['Content-Length']; x = request.headers['Content-Length'];
if content_length == none: if x == none:
content_length = 0; content_length = 0;
else
content_length = string.toInt(string.trim(x));
# TODO: Limit the content length # TODO: Limit the content length
@@ -81,7 +97,7 @@ fun fromSocket(fd: int, max_head: int = 1024) {
body_bytes = [body_bytes, second_half]; body_bytes = [body_bytes, second_half];
} }
} }
request.body = body_bytes; request.body = bodyBytesToString(body_bytes);
return request; return request;
} }
+1 -1
View File
@@ -40,7 +40,7 @@ return {
port : int = 8080, port : int = 8080,
backlog : int = 32) { backlog : int = 32) {
fd, err = net.socket(net.AF_INET, net.SOCK_STREAM, 0); fd, err = net.socket(net.AF_INET, net.SOCK_STREAM, 0, true);
if err != none: if err != none:
return none, err; return none, err;
+77
View File
@@ -0,0 +1,77 @@
fun undoEscaping(src: String) {
assert(src[0] == "%");
x = src[1];
y = src[2];
if not isHex(x)
or not isHex(y):
return none, "Invalid %xx token";
x = hexToInt(x);
y = hexToInt(y);
byte = x * 16 + y;
char = chr(byte);
return char, i;
}
fun getStringToken(src: String, i: int)
{
token = "";
while i < count(src)
and src[i] != "="
and src[i] != "&": {
char = src[i];
if char == "%": {
# Slice the "%xx" token
if i+2 >= count(src):
return none, none, "Invalid %xx escape token";
slice = string.slice(src, i, 3);
# Convert it to a character
char, error = undoEscaping(slice);
if error != none:
return none, none, error;
i = i+3;
} else {
if char == "+":
char = " ";
i = i+1;
}
token = string.cat(token, char);
}
return token, i;
}
fun parse(src: String) {
result = {};
i = 0;
while i < count(src): {
name, i, error = getStringToken(src, i);
if error != none:
return none, error;
if i < count(src) and src[i] == "=": {
i = i+1; # Skip the "="
value, i, error = getStringToken(src, i);
if error != none:
return error;
} else
value = none;
result[name] = value;
if i < count(src) and src[i] == "&":
i = i+1;
}
return result;
}
return {parse: parse};
+18
View File
@@ -39,12 +39,28 @@
#include "files.h" #include "files.h"
#include "string.h" #include "string.h"
#include "buffer.h" #include "buffer.h"
#include "random.h"
#include "../utils/defs.h" #include "../utils/defs.h"
#include "../common/defs.h" #include "../common/defs.h"
#include "../objects/objects.h" #include "../objects/objects.h"
#include "../runtime/runtime.h" #include "../runtime/runtime.h"
#include "../compiler/compile.h" #include "../compiler/compile.h"
static int bin_getCurrentWorkingDirectory(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(argc);
UNUSED(argv);
ASSERT(argc == 0);
char path[1024];
if(getcwd(path, sizeof(path)) == NULL) {
Error_Report(error, 1, "Couldn't get current working directory because a buffer is too small");
return -1;
}
return returnValues2(error, runtime, rets, "s", path);
}
static int bin_typename(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) static int bin_typename(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{ {
UNUSED(argc); UNUSED(argc);
@@ -335,6 +351,7 @@ StaticMapSlot bins_basic[] = {
{ "files", SM_SMAP, .as_smap = bins_files, }, { "files", SM_SMAP, .as_smap = bins_files, },
{ "buffer", SM_SMAP, .as_smap = bins_buffer, }, { "buffer", SM_SMAP, .as_smap = bins_buffer, },
{ "string", SM_SMAP, .as_smap = bins_string, }, { "string", SM_SMAP, .as_smap = bins_string, },
{ "random", SM_SMAP, .as_smap = bins_random, },
{ "import", SM_FUNCT, .as_funct = bin_import, .argc = 1, }, { "import", SM_FUNCT, .as_funct = bin_import, .argc = 1, },
{ "type", SM_FUNCT, .as_funct = bin_type, .argc = 1 }, { "type", SM_FUNCT, .as_funct = bin_type, .argc = 1 },
@@ -346,5 +363,6 @@ StaticMapSlot bins_basic[] = {
{ "istypeof", SM_FUNCT, .as_funct = bin_istypeof, .argc = 2, }, { "istypeof", SM_FUNCT, .as_funct = bin_istypeof, .argc = 2, },
{ "typename", SM_FUNCT, .as_funct = bin_typename, .argc = 1, }, { "typename", SM_FUNCT, .as_funct = bin_typename, .argc = 1, },
{ "keysof", SM_FUNCT, .as_funct = bin_keysof, .argc = 1, }, { "keysof", SM_FUNCT, .as_funct = bin_keysof, .argc = 1, },
{ "getCurrentWorkingDirectory", SM_FUNCT, .as_funct = bin_getCurrentWorkingDirectory, .argc = 0 },
{ NULL, SM_END, {}, {} }, { NULL, SM_END, {}, {} },
}; };
+13 -5
View File
@@ -9,19 +9,27 @@
static int bin_socket(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) static int bin_socket(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{ {
ASSERT(argc == 3); ASSERT(argc == 4);
ParsedArgument pargs[3]; ParsedArgument pargs[4];
if (!parseArgs(error, argv, argc, pargs, "iii")) if (!parseArgs(error, argv, argc, pargs, "iii?b"))
return -1; return -1;
int domain = pargs[0].as_int; int domain = pargs[0].as_int;
int type = pargs[1].as_int; int type = pargs[1].as_int;
int proto = pargs[2].as_int; int proto = pargs[2].as_int;
bool reuse = pargs[3].defined ? pargs[3].as_bool : false;
int fd = socket(domain, type, proto); int fd = socket(domain, type, proto);
if (fd < 0) if (fd < 0)
return returnValues2(error, runtime, rets, "ns", strerror(errno)); return returnValues2(error, runtime, rets, "ns", strerror(errno));
if (reuse) {
int value = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0)
return returnValues2(error, runtime, rets, "ns", strerror(errno));
}
return returnValues2(error, runtime, rets, "i", fd); return returnValues2(error, runtime, rets, "i", fd);
} }
@@ -184,7 +192,7 @@ StaticMapSlot bins_net[] = {
{ "AF_INET", SM_INT, .as_int = AF_INET, }, { "AF_INET", SM_INT, .as_int = AF_INET, },
{ "SOCK_STREAM", SM_INT, .as_int = SOCK_STREAM, }, { "SOCK_STREAM", SM_INT, .as_int = SOCK_STREAM, },
{ "SOCK_DGRAM", SM_INT, .as_int = SOCK_DGRAM, }, { "SOCK_DGRAM", SM_INT, .as_int = SOCK_DGRAM, },
{ "socket", SM_FUNCT, .as_funct = bin_socket, .argc = 3, }, { "socket", SM_FUNCT, .as_funct = bin_socket, .argc = 4, },
{ "bind", SM_FUNCT, .as_funct = bin_bind, .argc = 4, }, { "bind", SM_FUNCT, .as_funct = bin_bind, .argc = 4, },
{ "listen", SM_FUNCT, .as_funct = bin_listen, .argc = 2, }, { "listen", SM_FUNCT, .as_funct = bin_listen, .argc = 2, },
{ "accept", SM_FUNCT, .as_funct = bin_accept, .argc = 1, }, { "accept", SM_FUNCT, .as_funct = bin_accept, .argc = 1, },
+42
View File
@@ -0,0 +1,42 @@
#include <stdlib.h>
#include "utils.h"
#include "random.h"
#include "../utils/defs.h"
static int bin_seed(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(argc);
UNUSED(argv);
ASSERT(argc == 1);
ParsedArgument pargs[1];
if (!parseArgs(error, argv, argc, pargs, "i"))
return -1;
int64_t seed = pargs[0].as_int;
srand(seed);
return returnValues2(error, runtime, rets, "n");
}
static int bin_generate(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
UNUSED(argc);
UNUSED(argv);
ASSERT(argc == 2);
ParsedArgument pargs[2];
if (!parseArgs(error, argv, argc, pargs, "?i?i"))
return -1;
int64_t min, max;
if (pargs[0].defined) min = pargs[0].as_int; else min = 0;
if (pargs[1].defined) max = pargs[1].as_int; else max = INT64_MAX;
int64_t value = min + (max - min) * (double) rand() / RAND_MAX;
return returnValues2(error, runtime, rets, "i", value);
}
StaticMapSlot bins_random[] = {
{"seed", SM_FUNCT, .as_funct = bin_seed, .argc = 1},
{"generate", SM_FUNCT, .as_funct = bin_generate, .argc = 2},
{ NULL, SM_END, {}, {} },
};
+2
View File
@@ -0,0 +1,2 @@
#include "../runtime/runtime.h"
extern StaticMapSlot bins_random[];
+135 -3
View File
@@ -1,6 +1,8 @@
#include <ctype.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include "utils.h"
#include "string.h" #include "string.h"
#include "../utils/defs.h" #include "../utils/defs.h"
#include "../utils/utf8.h" #include "../utils/utf8.h"
@@ -135,8 +137,138 @@ static int bin_cat(Runtime *runtime, Object **argv, unsigned int argc, Object *r
return 1; return 1;
} }
static int bin_slice(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
// 0: string
// 1: start offset or none
// 2: end offset or none
ParsedArgument pargs[3];
if (!parseArgs(error, argv, argc, pargs, "s?i?i"))
return -1;
const char *srcstr = pargs[0].as_string.data;
size_t srclen = pargs[0].as_string.size;
size_t offset;
size_t length;
if (pargs[1].defined) {
int64_t n = pargs[1].as_int;
if (n < 0) {
Error_Report(error, 0, "starting offset of string slice must be non-negative");
return -1;
}
offset = (size_t) n;
} else
offset = 0;
if (pargs[2].defined) {
int64_t n = pargs[2].as_int;
if (n < 0) {
Error_Report(error, 0, "length of string slice must be non-negative");
return -1;
}
length = (size_t) n;
} else
length = srclen - offset;
if (offset > srclen) {
Error_Report(error, 0, "string slice offset is out of bounds");
return -1;
}
if (offset + length > srclen) {
Error_Report(error, 0, "string slice length is out of bounds");
return -1;
}
Heap *heap = Runtime_GetHeap(runtime);
Object *slice = Object_FromString(srcstr + offset, length, heap, error);
if (slice == NULL)
return -1;
rets[0] = slice;
return 1;
}
static int bin_trim(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
// 0: string
ParsedArgument pargs[1];
if (!parseArgs(error, argv, argc, pargs, "s"))
return -1;
const char *srcstr = pargs[0].as_string.data;
size_t srclen = pargs[0].as_string.size;
size_t offset = 0;
size_t length = srclen;
while (offset < srclen && srcstr[offset] == ' ')
offset++;
Heap *heap = Runtime_GetHeap(runtime);
Object *result;
if (offset == srclen)
// The string only contained whitespace.
// Return an empty string.
result = Object_FromString("", 0, heap, error);
else {
// NOTE: The string doesn't contain only
// whitespace, so the length can't
// go under 1.
while (srcstr[offset + length - 1] == ' ')
length--;
result = Object_FromString(srcstr + offset, length, heap, error);
}
if (result == NULL)
return -1;
rets[0] = result;
return 1;
}
static int bin_toInt(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error)
{
// 0: string
ParsedArgument pargs[1];
if (!parseArgs(error, argv, argc, pargs, "s"))
return -1;
const char *srcstr = pargs[0].as_string.data;
size_t srclen = pargs[0].as_string.size;
if (srclen == 0 || !isdigit(srcstr[0]))
return returnValues2(error, runtime, rets, "ns", "String doesn't contain an integer");
int64_t result = 0;
size_t i = 0;
do {
int digit = srcstr[i] - '0';
if (result > (INT64_MAX - digit) / 10)
return returnValues2(error, runtime, rets, "ns", "An overflow occurred");
result = result * 10 + digit;
i++;
} while (i < srclen && isdigit(srcstr[i]));
if (i < srclen)
// Found something other than a digit after the
// sequence of digits, therefore the string can't
// be considered an integer.
return returnValues2(error, runtime, rets, "ns", "String doesn't contain an integer");
return returnValues2(error, runtime, rets, "i", result);
}
StaticMapSlot bins_string[] = { StaticMapSlot bins_string[] = {
{ "ord", SM_FUNCT, .as_funct = bin_ord, .argc = 1 }, { "ord", SM_FUNCT, .as_funct = bin_ord, .argc = 1 },
{ "chr", SM_FUNCT, .as_funct = bin_chr, .argc = 1 }, { "chr", SM_FUNCT, .as_funct = bin_chr, .argc = 1 },
{ "cat", SM_FUNCT, .as_funct = bin_cat, .argc = -1 }, { "cat", SM_FUNCT, .as_funct = bin_cat, .argc = -1 },
{ "trim", SM_FUNCT, .as_funct = bin_trim, .argc = 1 },
{ "slice", SM_FUNCT, .as_funct = bin_slice, .argc = 3 },
{ "toInt", SM_FUNCT, .as_funct = bin_toInt, .argc = 1 },
}; };
+11 -1
View File
@@ -17,6 +17,7 @@ int returnValuesVA(Error *error, Heap *heap, Object *rets[static MAX_RETS], cons
case 'i': ret = Object_FromInt (va_arg(va, int), 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 'f': ret = Object_FromFloat(va_arg(va, double), heap, error); break;
case 's': ret = Object_FromString(va_arg(va, char*), -1, heap, error); break; case 's': ret = Object_FromString(va_arg(va, char*), -1, heap, error); break;
case 'F': ret = Object_FromStream(va_arg(va, FILE*), heap, error); break;
default: default:
Error_Report(error, 1, "Invalid format specifier '%c'", fmt[i]); Error_Report(error, 1, "Invalid format specifier '%c'", fmt[i]);
return -1; return -1;
@@ -138,8 +139,17 @@ bool parseArgs(Error *error,
break; break;
} }
case 'F': /* File */
if (!Object_IsFile(arg)) {
Error_Report(error, 0, "Argument %d was expected to be a file, but a %s was provided", current_arg+1, arg->type->name);
return false;
}
pargs[current_arg].defined = true;
pargs[current_arg].as_file = Object_GetStream(arg);
break;
default: default:
Error_Report(error, 1, "Invalid argument parser format specifier"); Error_Report(error, 1, "Invalid argument parser format specifier '%c'", fmt[i]);
return false; return false;
} }
} }
+1 -1
View File
@@ -287,7 +287,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error)
if(str[i] == '\\') if(str[i] == '\\')
{ {
i += 1; // Consume the \. i += 1; // Consume the \.
if(i < len && (str[i] == '\'' || str[i] == '"')) if(i < len && (str[i] == '\'' || str[i] == '"' || str[i] == '\\'))
i += 1; i += 1;
} }
else break; else break;
+1
View File
@@ -73,6 +73,7 @@ keysof(Object *self,
Error *error) Error *error)
{ {
MapObject *map = (MapObject*) self; MapObject *map = (MapObject*) self;
#warning "This shouldn't return items that contain none"
return Object_NewList2(map->count, map->keys, heap, error); return Object_NewList2(map->count, map->keys, heap, error);
} }