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;
|
||||||
|
}
|
||||||
|
};
|
||||||
+217
-56
@@ -1,7 +1,36 @@
|
|||||||
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) {
|
||||||
|
|
||||||
@@ -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) {
|
||||||
|
|
||||||
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(401, "You're not logged in");
|
||||||
'/logout': generateLogout
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
else {
|
||||||
|
file = joinPath(getCurrentWorkingDirectory(), method_item);
|
||||||
|
data, error = loadFile(file);
|
||||||
if error == none:
|
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
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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,
|
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
|
||||||
|
|
||||||
@@ -82,6 +98,6 @@ fun fromSocket(fd: int, max_head: int = 1024) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.body = body_bytes;
|
request.body = bodyBytesToString(body_bytes);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 "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, {}, {} },
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-4
@@ -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, },
|
||||||
|
|||||||
@@ -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[];
|
||||||
+135
-3
@@ -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 },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user