example http parser
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
fun compareLists(a: List, b: List) {
|
||||||
|
len = count(a);
|
||||||
|
if len != count(b):
|
||||||
|
return false;
|
||||||
|
i = 0;
|
||||||
|
while i < len: {
|
||||||
|
if a[i] != b[i]:
|
||||||
|
return false;
|
||||||
|
i = i+1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compareMaps(a: Map, b: Map) {
|
||||||
|
keys = keysof(a);
|
||||||
|
len = count(keys);
|
||||||
|
if len != count(b):
|
||||||
|
return false;
|
||||||
|
i = 0;
|
||||||
|
while i < len: {
|
||||||
|
k = keys[i];
|
||||||
|
if a[k] != b[k]:
|
||||||
|
return false;
|
||||||
|
i = i+1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compareAny(a, b) {
|
||||||
|
Ta = type(a);
|
||||||
|
Tb = type(b);
|
||||||
|
|
||||||
|
if Ta != Tb:
|
||||||
|
return false;
|
||||||
|
if Ta == List:
|
||||||
|
return compareLists(a, b);
|
||||||
|
if Ta == Map:
|
||||||
|
return compareMaps(a, b);
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
scan = import("scan.noja");
|
||||||
|
|
||||||
|
newScanner = scan.newScanner;
|
||||||
|
Scanner = scan.Scanner;
|
||||||
|
hint = scan.hint;
|
||||||
|
consume = scan.consume;
|
||||||
|
current = scan.current;
|
||||||
|
isDigit = scan.isDigit;
|
||||||
|
consumeSpaces = scan.consumeSpaces;
|
||||||
|
|
||||||
|
fun isAlpha(char: String)
|
||||||
|
return (string.ord(char) >= string.ord('a') and string.ord(char) <= string.ord('z'))
|
||||||
|
or (string.ord(char) >= string.ord('A') and string.ord(char) <= string.ord('Z'));
|
||||||
|
|
||||||
|
fun isUpper(char: String)
|
||||||
|
return string.ord(char) >= string.ord('A')
|
||||||
|
and string.ord(char) <= string.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 = current(scan);
|
||||||
|
if char == none or not isUpper(char):
|
||||||
|
return none, "Missing method";
|
||||||
|
|
||||||
|
method = "";
|
||||||
|
do {
|
||||||
|
method = string.cat(method, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != none and isUpper(char);
|
||||||
|
|
||||||
|
return method, none;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parsePort(scan: Scanner) {
|
||||||
|
|
||||||
|
port = none;
|
||||||
|
char = current(scan);
|
||||||
|
if char == ":" and isDigit(hint(scan, 1)): {
|
||||||
|
|
||||||
|
consume(scan); # Skip the ":"
|
||||||
|
char = current(scan);
|
||||||
|
|
||||||
|
port = 0;
|
||||||
|
do {
|
||||||
|
digit = string.ord(char)
|
||||||
|
- string.ord("0");
|
||||||
|
port = port * 10 + digit;
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != none and isDigit(char);
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isHostname(char: String)
|
||||||
|
return isUnreserved(char)
|
||||||
|
or isSubdelim(char);
|
||||||
|
|
||||||
|
fun parseHost(scan: Scanner) {
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
if char == none:
|
||||||
|
return none, "Missing host";
|
||||||
|
|
||||||
|
if not isHostname(char):
|
||||||
|
return none, "Invalid character in place of host";
|
||||||
|
|
||||||
|
name = "";
|
||||||
|
do {
|
||||||
|
name = string.cat(name, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != none and isHostname(char);
|
||||||
|
|
||||||
|
port = parsePort(scan);
|
||||||
|
return {name: name, port: port};
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parsePath(scan: Scanner) {
|
||||||
|
|
||||||
|
path = "";
|
||||||
|
char = current(scan);
|
||||||
|
if char != none and char == "/": {
|
||||||
|
path = string.cat(path, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} else {
|
||||||
|
if char == none or not isPChar(char):
|
||||||
|
return none, "Invalid character in place of path";
|
||||||
|
}
|
||||||
|
|
||||||
|
while char != none and isPChar(char): {
|
||||||
|
do {
|
||||||
|
path = string.cat(path, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != none and isPChar(char);
|
||||||
|
if char == none or char != "/":
|
||||||
|
break;
|
||||||
|
path = string.cat(path, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isQuery(char: ?String) {
|
||||||
|
if char == none:
|
||||||
|
return false;
|
||||||
|
return isPChar(char)
|
||||||
|
or char == "/"
|
||||||
|
or char == "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseQuery(scan: Scanner) {
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
if char != none and char == "?": {
|
||||||
|
consume(scan); # Consume the "?"
|
||||||
|
query = "";
|
||||||
|
while isQuery(char = current(scan)): {
|
||||||
|
query = string.cat(query, char);
|
||||||
|
consume(scan);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
query = none;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isFragment(char: ?String) {
|
||||||
|
if char == none:
|
||||||
|
return false;
|
||||||
|
return isPChar(char) or char == "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseFragment(scan: Scanner) {
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
if char != none and char == "#": {
|
||||||
|
consume(scan); # Consume the "#"
|
||||||
|
fragment = "";
|
||||||
|
while isFragment(char = current(scan)): {
|
||||||
|
fragment = string.cat(fragment, char);
|
||||||
|
consume(scan);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
fragment = none;
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
Host = { name: String, port: ?int };
|
||||||
|
|
||||||
|
URL = {
|
||||||
|
schema: ?String,
|
||||||
|
host: ?Host,
|
||||||
|
path: ?String,
|
||||||
|
query: ?String,
|
||||||
|
fragment: ?String
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = current(scan);
|
||||||
|
schema = none;
|
||||||
|
if isSchemaFirst(char): {
|
||||||
|
start = scan.i;
|
||||||
|
schema = "";
|
||||||
|
do {
|
||||||
|
schema = string.cat(schema, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while isSchema(char);
|
||||||
|
|
||||||
|
if char == ":":
|
||||||
|
consume(scan);
|
||||||
|
else {
|
||||||
|
schema = none;
|
||||||
|
scan.i = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun followsAuthority(scan: Scanner)
|
||||||
|
return current(scan) == "/" and hint(scan, 1) == "/";
|
||||||
|
|
||||||
|
fun parseURL(scan: Scanner) {
|
||||||
|
|
||||||
|
schema = parseSchema(scan);
|
||||||
|
|
||||||
|
if followsAuthority(scan): {
|
||||||
|
consume(scan, 2);
|
||||||
|
|
||||||
|
host, error = parseHost(scan);
|
||||||
|
if error != none:
|
||||||
|
return none, error;
|
||||||
|
|
||||||
|
path = none;
|
||||||
|
if current(scan) == "/":
|
||||||
|
path = parsePath(scan);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
host = none;
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
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);
|
||||||
|
print(scan);
|
||||||
|
|
||||||
|
return {
|
||||||
|
host : host,
|
||||||
|
path : path,
|
||||||
|
query : query,
|
||||||
|
schema : schema,
|
||||||
|
fragment: fragment
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseVersion(scan: Scanner) {
|
||||||
|
|
||||||
|
if hint(scan, 0) != "H" or hint(scan, 1) != "T"
|
||||||
|
or hint(scan, 2) != "T" or hint(scan, 3) != "P"
|
||||||
|
or hint(scan, 4) != "/" or not isDigit(hint(scan, 5)):
|
||||||
|
return none, "Invalid version token";
|
||||||
|
|
||||||
|
consume(scan, 5);
|
||||||
|
major = string.ord(current(scan))
|
||||||
|
- string.ord("0");
|
||||||
|
|
||||||
|
if hint(scan, 1) != "." and isDigit(hint(scan, 2)): {
|
||||||
|
consume(scan, 2);
|
||||||
|
minor = string.ord(current(scan))
|
||||||
|
- string.ord("0");
|
||||||
|
} else
|
||||||
|
minor = 0;
|
||||||
|
consume(scan);
|
||||||
|
|
||||||
|
return {major: major, minor: minor};
|
||||||
|
}
|
||||||
|
|
||||||
|
Version = {
|
||||||
|
major: int,
|
||||||
|
minor: int
|
||||||
|
};
|
||||||
|
|
||||||
|
Request = {
|
||||||
|
method : String,
|
||||||
|
url : URL,
|
||||||
|
version: Version
|
||||||
|
};
|
||||||
|
|
||||||
|
fun newRequest(method: String, url: URL,
|
||||||
|
version: Version, headers: Map = {})
|
||||||
|
return {
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
version: version,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
|
fun parse(src: String) {
|
||||||
|
|
||||||
|
scan = newScanner(src);
|
||||||
|
|
||||||
|
method, error = parseMethod(scan);
|
||||||
|
if error != none:
|
||||||
|
return none, error;
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
if char == none or char != " ":
|
||||||
|
return none, "Missing space after method";
|
||||||
|
consume(scan);
|
||||||
|
|
||||||
|
url, error = parseURL(scan);
|
||||||
|
if error != none:
|
||||||
|
return none, error;
|
||||||
|
|
||||||
|
char = current(scan);
|
||||||
|
if char != " ":
|
||||||
|
return none, "Missing space after URL";
|
||||||
|
consume(scan);
|
||||||
|
|
||||||
|
version, error = parseVersion(scan);
|
||||||
|
if error != none:
|
||||||
|
return none, error;
|
||||||
|
|
||||||
|
if hint(scan, 0) != "\r" or hint(scan, 1) != "\n":
|
||||||
|
# Request with no headers or body
|
||||||
|
return newRequest(method, url, version);
|
||||||
|
consume(scan, 2); # Consume the "\r\n"
|
||||||
|
|
||||||
|
headers = {};
|
||||||
|
while (char = hint(scan, 0)) != none and (char != "\r" or hint(scan, 1) != "\n"): {
|
||||||
|
|
||||||
|
# Header name until the next ":"
|
||||||
|
name = "";
|
||||||
|
do {
|
||||||
|
name = string.cat(name, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != ":" and char != none;
|
||||||
|
|
||||||
|
if char == none:
|
||||||
|
return none, "Invalid header (missing \":\" after name)";
|
||||||
|
consume(scan);
|
||||||
|
consumeSpaces(scan);
|
||||||
|
char = current(scan);
|
||||||
|
|
||||||
|
# Header body until \r
|
||||||
|
body = "";
|
||||||
|
do {
|
||||||
|
body = string.cat(body, char);
|
||||||
|
consume(scan);
|
||||||
|
char = current(scan);
|
||||||
|
} while char != "\r" and char != none;
|
||||||
|
|
||||||
|
headers[name] = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if char != none:
|
||||||
|
consume(scan, 2); # Consume the final "\r\n"
|
||||||
|
|
||||||
|
return newRequest(method, url, version, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
sample = string.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 = parse(sample);
|
||||||
|
print("request=", request, "\n");
|
||||||
|
print("error=", error, "\n");
|
||||||
@@ -1,49 +1,12 @@
|
|||||||
|
scan = import("scan.noja");
|
||||||
|
|
||||||
Scanner = {src: String, i: int};
|
newScanner = scan.newScanner;
|
||||||
|
Scanner = scan.Scanner;
|
||||||
fun newScanner(src: String) {
|
hint = scan.hint;
|
||||||
scan = {src: src, i: 0};
|
consume = scan.consume;
|
||||||
assert(istypeof(Scanner, scan));
|
current = scan.current;
|
||||||
return scan;
|
consumeSpaces = scan.consumeSpaces;
|
||||||
}
|
isDigit = scan.isDigit;
|
||||||
|
|
||||||
fun isDigit(char: ?String) {
|
|
||||||
if char == none:
|
|
||||||
return false;
|
|
||||||
return string.ord(char) >= string.ord('0')
|
|
||||||
and string.ord(char) <= string.ord('9');
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSpace(c: String)
|
|
||||||
return c == ' '
|
|
||||||
or c == '\t'
|
|
||||||
or c == '\n';
|
|
||||||
|
|
||||||
fun min(x, y) {
|
|
||||||
if x < y:
|
|
||||||
return x;
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 hint(scan, 0);
|
|
||||||
|
|
||||||
fun consume(scan: Scanner, n: int = 1) {
|
|
||||||
assert(n > 0);
|
|
||||||
limit = count(scan.src);
|
|
||||||
scan.i = min(scan.i + n, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
fun consumeSpaces(scan: Scanner)
|
|
||||||
while isSpace(current(scan)):
|
|
||||||
consume(scan);
|
|
||||||
|
|
||||||
fun parseString(scan: Scanner) {
|
fun parseString(scan: Scanner) {
|
||||||
|
|
||||||
@@ -302,3 +265,82 @@ fun parse(src: String) {
|
|||||||
val, err = parseValue(scan);
|
val, err = parseValue(scan);
|
||||||
return val, err;
|
return val, err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
|
||||||
|
compareAny = import("compare.noja").compareAny;
|
||||||
|
|
||||||
|
Error = String;
|
||||||
|
JSONValueType = (bool | float | String | Map | List | None);
|
||||||
|
TestCase = [String, ?JSONValueType, ?Error];
|
||||||
|
|
||||||
|
fun runAllTestCases(test_cases: List) {
|
||||||
|
total = count(test_cases);
|
||||||
|
passed = 0;
|
||||||
|
evaluated = 0;
|
||||||
|
while evaluated < total: {
|
||||||
|
|
||||||
|
test_case = test_cases[evaluated];
|
||||||
|
input = test_case[0];
|
||||||
|
expres = test_case[1]; # Expected result
|
||||||
|
experr = test_case[2]; # Expected error
|
||||||
|
|
||||||
|
res, err = parse(input);
|
||||||
|
|
||||||
|
#print("res=", res, ", err=", err, "\n");
|
||||||
|
#print("expres=", expres, ", experr=", experr, "\n");
|
||||||
|
|
||||||
|
result = compareAny(res, expres)
|
||||||
|
and compareAny(err, experr);
|
||||||
|
|
||||||
|
if result: {
|
||||||
|
print("Test ", evaluated, ": PASSED\n");
|
||||||
|
passed = passed+1;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
print("Test ", evaluated, ": FAILED\n");
|
||||||
|
print("\tInput:\n");
|
||||||
|
print("\t\t", input, "\n");
|
||||||
|
|
||||||
|
if experr == none: {
|
||||||
|
if err == none:
|
||||||
|
print("\tGot result ", res, " but was expected ", expres, "\n");
|
||||||
|
else
|
||||||
|
print("\tFailed unexpectedly [", err, "]\n");
|
||||||
|
} else if err == none:
|
||||||
|
print("\tSucceded unexpectedly. Was expected error [", experr, "]\n");
|
||||||
|
else
|
||||||
|
print("\tWas expected error [", experr, "], but got [", err, "]\n");
|
||||||
|
}
|
||||||
|
evaluated = evaluated+1;
|
||||||
|
}
|
||||||
|
return passed, (total-passed), total;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newPassingTestCase(src: String, res: JSONValueType)
|
||||||
|
return [src, res, none];
|
||||||
|
|
||||||
|
fun newFailingTestCase(src: String, err: String)
|
||||||
|
return [src, none, err];
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
newPassingTestCase("null", none),
|
||||||
|
newPassingTestCase("true", true),
|
||||||
|
newPassingTestCase("false", false),
|
||||||
|
newPassingTestCase("\"Hello, world!\"", "Hello, world!"),
|
||||||
|
newPassingTestCase("1", 1.0),
|
||||||
|
newPassingTestCase("2.3", 2.3),
|
||||||
|
newPassingTestCase("[]", []),
|
||||||
|
newPassingTestCase("[1]", [1.0]),
|
||||||
|
newPassingTestCase("[1, 2]", [1.0, 2.0]),
|
||||||
|
newPassingTestCase("[1, 2, 3]", [1.0, 2.0, 3.0]),
|
||||||
|
newPassingTestCase("{}", {}),
|
||||||
|
newPassingTestCase("{\"k1\": true}", {k1: true}),
|
||||||
|
newPassingTestCase("{\"k1\": true, \"k2\": false}", {k1: true, k2: false})
|
||||||
|
];
|
||||||
|
|
||||||
|
passed, failed, total = runAllTestCases(test_cases);
|
||||||
|
print("passed: ", passed, "\n");
|
||||||
|
print("failed: ", failed, "\n");
|
||||||
|
print(" total: ", total, "\n");
|
||||||
|
}
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
json = import("json.noja");
|
|
||||||
|
|
||||||
Error = String;
|
|
||||||
JSONValueType = (bool | float | String | Map | List | None);
|
|
||||||
TestCase = [String, ?JSONValueType, ?Error];
|
|
||||||
|
|
||||||
fun compareLists(a: List, b: List) {
|
|
||||||
len = count(a);
|
|
||||||
if len != count(b):
|
|
||||||
return false;
|
|
||||||
i = 0;
|
|
||||||
while i < len: {
|
|
||||||
if a[i] != b[i]:
|
|
||||||
return false;
|
|
||||||
i = i+1;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compareMaps(a: Map, b: Map) {
|
|
||||||
keys = keysof(a);
|
|
||||||
len = count(keys);
|
|
||||||
if len != count(b):
|
|
||||||
return false;
|
|
||||||
i = 0;
|
|
||||||
while i < len: {
|
|
||||||
k = keys[i];
|
|
||||||
if a[k] != b[k]:
|
|
||||||
return false;
|
|
||||||
i = i+1;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compareAny(a, b) {
|
|
||||||
Ta = type(a);
|
|
||||||
Tb = type(b);
|
|
||||||
|
|
||||||
if Ta != Tb:
|
|
||||||
return false;
|
|
||||||
if Ta == List:
|
|
||||||
return compareLists(a, b);
|
|
||||||
if Ta == Map:
|
|
||||||
return compareMaps(a, b);
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runTestCase(test_case: TestCase) {
|
|
||||||
res, err = json.parse(test_case[0]);
|
|
||||||
return compareAny(res, test_case[1])
|
|
||||||
and compareAny(err, test_case[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fun runAllTestCases(test_cases: List) {
|
|
||||||
total = count(test_cases);
|
|
||||||
passed = 0;
|
|
||||||
evaluated = 0;
|
|
||||||
while evaluated < total: {
|
|
||||||
|
|
||||||
test_case = test_cases[evaluated];
|
|
||||||
input = test_case[0];
|
|
||||||
expres = test_case[1]; # Expected result
|
|
||||||
experr = test_case[2]; # Expected error
|
|
||||||
|
|
||||||
res, err = json.parse(input);
|
|
||||||
|
|
||||||
#print("res=", res, ", err=", err, "\n");
|
|
||||||
#print("expres=", expres, ", experr=", experr, "\n");
|
|
||||||
|
|
||||||
result = compareAny(res, expres)
|
|
||||||
and compareAny(err, experr);
|
|
||||||
|
|
||||||
if result: {
|
|
||||||
print("Test ", evaluated, ": PASSED\n");
|
|
||||||
passed = passed+1;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
print("Test ", evaluated, ": FAILED\n");
|
|
||||||
print("\tInput:\n");
|
|
||||||
print("\t\t", input, "\n");
|
|
||||||
|
|
||||||
if experr == none: {
|
|
||||||
if err == none:
|
|
||||||
print("\tGot result ", res, " but was expected ", expres, "\n");
|
|
||||||
else
|
|
||||||
print("\tFailed unexpectedly [", err, "]\n");
|
|
||||||
} else if err == none:
|
|
||||||
print("\tSucceded unexpectedly. Was expected error [", experr, "]\n");
|
|
||||||
else
|
|
||||||
print("\tWas expected error [", experr, "], but got [", err, "]\n");
|
|
||||||
}
|
|
||||||
evaluated = evaluated+1;
|
|
||||||
}
|
|
||||||
return passed, (total-passed), total;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newPassingTestCase(src: String, res: JSONValueType)
|
|
||||||
return [src, res, none];
|
|
||||||
|
|
||||||
fun newFailingTestCase(src: String, err: String)
|
|
||||||
return [src, none, err];
|
|
||||||
|
|
||||||
test_cases = [
|
|
||||||
newPassingTestCase("null", none),
|
|
||||||
newPassingTestCase("true", true),
|
|
||||||
newPassingTestCase("false", false),
|
|
||||||
newPassingTestCase("\"Hello, world!\"", "Hello, world!"),
|
|
||||||
newPassingTestCase("1", 1.0),
|
|
||||||
newPassingTestCase("2.3", 2.3),
|
|
||||||
newPassingTestCase("[]", []),
|
|
||||||
newPassingTestCase("[1]", [1.0]),
|
|
||||||
newPassingTestCase("[1, 2]", [1.0, 2.0]),
|
|
||||||
newPassingTestCase("[1, 2, 3]", [1.0, 2.0, 3.0]),
|
|
||||||
newPassingTestCase("{}", {}),
|
|
||||||
newPassingTestCase("{\"k1\": true}", {k1: true}),
|
|
||||||
newPassingTestCase("{\"k1\": true, \"k2\": false}", {k1: true, k2: false})
|
|
||||||
];
|
|
||||||
|
|
||||||
passed, failed, total = runAllTestCases(test_cases);
|
|
||||||
print("passed: ", passed, "\n");
|
|
||||||
print("failed: ", failed, "\n");
|
|
||||||
print(" total: ", total, "\n");
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
Scanner = {src: String, i: int};
|
||||||
|
|
||||||
|
fun newScanner(src: String) {
|
||||||
|
scan = {src: src, i: 0};
|
||||||
|
assert(istypeof(Scanner, scan));
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDigit(char: ?String) {
|
||||||
|
if char == none:
|
||||||
|
return false;
|
||||||
|
return string.ord(char) >= string.ord('0')
|
||||||
|
and string.ord(char) <= string.ord('9');
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSpace(c: String)
|
||||||
|
return c == ' '
|
||||||
|
or c == '\t'
|
||||||
|
or c == '\n';
|
||||||
|
|
||||||
|
fun min(x, y) {
|
||||||
|
if x < y:
|
||||||
|
return x;
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 hint(scan, 0);
|
||||||
|
|
||||||
|
fun consume(scan: Scanner, n: int = 1) {
|
||||||
|
assert(n > 0);
|
||||||
|
limit = count(scan.src);
|
||||||
|
scan.i = min(scan.i + n, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeSpaces(scan: Scanner)
|
||||||
|
while isSpace(current(scan)):
|
||||||
|
consume(scan);
|
||||||
@@ -931,7 +931,7 @@ static _Bool step(Runtime *runtime, Error *error)
|
|||||||
Object *callable = Stack_Top(runtime->stack, 0);
|
Object *callable = Stack_Top(runtime->stack, 0);
|
||||||
ASSERT(callable != NULL);
|
ASSERT(callable != NULL);
|
||||||
|
|
||||||
Object *argv[8];
|
Object *argv[32];
|
||||||
|
|
||||||
int max_argc = sizeof(argv) / sizeof(argv[0]);
|
int max_argc = sizeof(argv) / sizeof(argv[0]);
|
||||||
if(argc > max_argc)
|
if(argc > max_argc)
|
||||||
|
|||||||
Reference in New Issue
Block a user