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:
@@ -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;
|
||||
}
|
||||
};
|
||||
+206
-45
@@ -1,7 +1,36 @@
|
||||
server = import("server.noja");
|
||||
request = import("request.noja");
|
||||
response = import("response.noja");
|
||||
cookie = import("cookie.noja");
|
||||
urlencoded = import("urlencoded.noja");
|
||||
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) {
|
||||
|
||||
@@ -23,77 +52,209 @@ fun loadFile(path: String) {
|
||||
|
||||
} while num_bytes == size;
|
||||
|
||||
files.close(stream);
|
||||
#files.close(stream);
|
||||
return text;
|
||||
}
|
||||
|
||||
fun generateHome() {
|
||||
title = "Homepage";
|
||||
content = "Hello, world!";
|
||||
return string.cat(
|
||||
user_table = {};
|
||||
session_table = {};
|
||||
|
||||
fun redirect(endpoint) {
|
||||
fun callback()
|
||||
return response.new(307, none, {'Location': endpoint});
|
||||
return callback;
|
||||
}
|
||||
|
||||
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>", title, "</title>",
|
||||
"<title>Homepage</title>",
|
||||
"</head>",
|
||||
"<body>",
|
||||
"<a>", content, "</a>",
|
||||
"<a>Hello, ", username, "!</a>",
|
||||
"</body>",
|
||||
"</html>"
|
||||
);
|
||||
return respond(200, content);
|
||||
}
|
||||
},
|
||||
'/login': {
|
||||
GET: 'pages/login.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;
|
||||
}
|
||||
|
||||
fun generateLogout() {
|
||||
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;
|
||||
}
|
||||
|
||||
table = {
|
||||
'/': 'login.html',
|
||||
'/home': generateHome,
|
||||
'/login': 'login.html',
|
||||
'/signup': 'signup.html',
|
||||
'/logout': generateLogout
|
||||
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;
|
||||
|
||||
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) {
|
||||
|
||||
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": {
|
||||
|
||||
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);
|
||||
method_item = method_table[req.method];
|
||||
if method_item == none: {
|
||||
allowed = getRouteAllowedMethods(endpoint);
|
||||
if req.method == "OPTIONS":
|
||||
status = 204;
|
||||
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);
|
||||
else {
|
||||
file = joinPath(getCurrentWorkingDirectory(), method_item);
|
||||
data, error = loadFile(file);
|
||||
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
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Login page</title>
|
||||
<title>Log-in page</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="POST">
|
||||
<input type="text" name="user" placeholder="[Username]" />
|
||||
<input type="password" name="pass" placeholder="[Password]" />
|
||||
<input type="submit" value="Enter" />
|
||||
<input type="submit" value="Log-in" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
@@ -22,9 +22,23 @@ Request = {
|
||||
method : String,
|
||||
url : URL,
|
||||
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) {
|
||||
|
||||
{
|
||||
@@ -60,9 +74,11 @@ fun fromSocket(fd: int, max_head: int = 1024) {
|
||||
# Receive the remaining portion of the
|
||||
# request's body (if it wasn't already)
|
||||
{
|
||||
content_length = request.headers['Content-Length'];
|
||||
if content_length == none:
|
||||
x = request.headers['Content-Length'];
|
||||
if x == none:
|
||||
content_length = 0;
|
||||
else
|
||||
content_length = string.toInt(string.trim(x));
|
||||
|
||||
# TODO: Limit the content length
|
||||
|
||||
@@ -82,6 +98,6 @@ fun fromSocket(fd: int, max_head: int = 1024) {
|
||||
}
|
||||
}
|
||||
|
||||
request.body = body_bytes;
|
||||
request.body = bodyBytesToString(body_bytes);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ return {
|
||||
port : int = 8080,
|
||||
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:
|
||||
return none, err;
|
||||
|
||||
|
||||
@@ -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};
|
||||
@@ -39,12 +39,28 @@
|
||||
#include "files.h"
|
||||
#include "string.h"
|
||||
#include "buffer.h"
|
||||
#include "random.h"
|
||||
#include "../utils/defs.h"
|
||||
#include "../common/defs.h"
|
||||
#include "../objects/objects.h"
|
||||
#include "../runtime/runtime.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)
|
||||
{
|
||||
UNUSED(argc);
|
||||
@@ -335,6 +351,7 @@ StaticMapSlot bins_basic[] = {
|
||||
{ "files", SM_SMAP, .as_smap = bins_files, },
|
||||
{ "buffer", SM_SMAP, .as_smap = bins_buffer, },
|
||||
{ "string", SM_SMAP, .as_smap = bins_string, },
|
||||
{ "random", SM_SMAP, .as_smap = bins_random, },
|
||||
|
||||
{ "import", SM_FUNCT, .as_funct = bin_import, .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, },
|
||||
{ "typename", SM_FUNCT, .as_funct = bin_typename, .argc = 1, },
|
||||
{ "keysof", SM_FUNCT, .as_funct = bin_keysof, .argc = 1, },
|
||||
{ "getCurrentWorkingDirectory", SM_FUNCT, .as_funct = bin_getCurrentWorkingDirectory, .argc = 0 },
|
||||
{ NULL, SM_END, {}, {} },
|
||||
};
|
||||
|
||||
+12
-4
@@ -9,19 +9,27 @@
|
||||
|
||||
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"))
|
||||
ASSERT(argc == 4);
|
||||
ParsedArgument pargs[4];
|
||||
if (!parseArgs(error, argv, argc, pargs, "iii?b"))
|
||||
return -1;
|
||||
|
||||
int domain = pargs[0].as_int;
|
||||
int type = pargs[1].as_int;
|
||||
int proto = pargs[2].as_int;
|
||||
bool reuse = pargs[3].defined ? pargs[3].as_bool : false;
|
||||
|
||||
int fd = socket(domain, type, proto);
|
||||
|
||||
if (fd < 0)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -184,7 +192,7 @@ 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, },
|
||||
{ "socket", SM_FUNCT, .as_funct = bin_socket, .argc = 4, },
|
||||
{ "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, },
|
||||
|
||||
@@ -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, {}, {} },
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "../runtime/runtime.h"
|
||||
extern StaticMapSlot bins_random[];
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "utils.h"
|
||||
#include "string.h"
|
||||
#include "../utils/defs.h"
|
||||
#include "../utils/utf8.h"
|
||||
@@ -135,8 +137,138 @@ static int bin_cat(Runtime *runtime, Object **argv, unsigned int argc, Object *r
|
||||
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[] = {
|
||||
{ "ord", SM_FUNCT, .as_funct = bin_ord, .argc = 1 },
|
||||
{ "chr", SM_FUNCT, .as_funct = bin_chr, .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 },
|
||||
};
|
||||
|
||||
@@ -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 '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 'F': ret = Object_FromStream(va_arg(va, FILE*), heap, error); break;
|
||||
default:
|
||||
Error_Report(error, 1, "Invalid format specifier '%c'", fmt[i]);
|
||||
return -1;
|
||||
@@ -138,8 +139,17 @@ bool parseArgs(Error *error,
|
||||
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:
|
||||
Error_Report(error, 1, "Invalid argument parser format specifier");
|
||||
Error_Report(error, 1, "Invalid argument parser format specifier '%c'", fmt[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error)
|
||||
if(str[i] == '\\')
|
||||
{
|
||||
i += 1; // Consume the \.
|
||||
if(i < len && (str[i] == '\'' || str[i] == '"'))
|
||||
if(i < len && (str[i] == '\'' || str[i] == '"' || str[i] == '\\'))
|
||||
i += 1;
|
||||
}
|
||||
else break;
|
||||
|
||||
@@ -73,6 +73,7 @@ keysof(Object *self,
|
||||
Error *error)
|
||||
{
|
||||
MapObject *map = (MapObject*) self;
|
||||
#warning "This shouldn't return items that contain none"
|
||||
return Object_NewList2(map->count, map->keys, heap, error);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user