Skip to content

Commit

Permalink
[whamm] Fix parser and test more thoroughly
Browse files Browse the repository at this point in the history
  • Loading branch information
titzer committed Aug 12, 2024
1 parent bb113f2 commit 2e7fae7
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 9 deletions.
22 changes: 16 additions & 6 deletions src/util/WhammUtil.v3
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ component WhammUtil {
def parseParams(r: TextReader) -> Array<WhammParam> {
var params = Vector<WhammParam>.new();
r.req1('(');
while (r.pos < r.limit) {
while (r.ok) {
if (r.char == ')') {
r.advance1();
break;
Expand Down Expand Up @@ -81,8 +81,13 @@ def parseParam0(r: TextReader) -> WhammParam {
if (Ranges.equal("local", id)) return parseUint(r, i, WhammParam.Local);

i = r.star_rel(i - r.pos, isIdentChar);
var token = r.readToken(i - r.pos);

if (i == r.pos) {
r.fail("expected identifier");
return WhammParam.DynamicLoc;
}

var token = r.readToken(i - r.pos);
if (r.char == '(') {
var params = WhammUtil.parseParams(r);
return WhammParam.Call(token, params);
Expand All @@ -92,14 +97,13 @@ def parseParam0(r: TextReader) -> WhammParam {
if (Strings.equal("func", token.image)) return WhammParam.Func;
if (Strings.equal("frame", token.image)) return WhammParam.FrameAccessor;

r.setFirstError(token.beginLine, token.beginColumn, Strings.format1("Unresolved identifier: \"%s\"", token.image));
r.setFirstError(token.beginLine, token.beginColumn, Strings.format1("unresolved identifier: \"%s\"", token.image));
return WhammParam.DynamicLoc;
}
def parseUint<T>(r: TextReader, i: int, f: (Token, u31) -> T) -> T {
var token = r.readToken(i);
var t = Ints.parsePosDecimal(r.data, r.pos + i);
var t = Ints.parsePosDecimal(r.data, i);
if (t.0 > 0) {
var token = r.readToken(i + t.0);
var token = r.readToken(i + t.0 - r.pos);
return f(token, u31.!(t.1));
}
r.failRel(i, "expected positive integer");
Expand All @@ -117,3 +121,9 @@ def isIdentChar(ch: byte) -> bool {
|| (ch >= '0' && ch <= '9')
|| ch == '_' || ch == '$';
}

def debug(r: TextReader, where: string) {
Trace.OUT.puts(where).ln();
r.renderCurrentLineWithCaret(Trace.OUT, r.pos);
Trace.OUT.ln();
}
107 changes: 104 additions & 3 deletions test/unittest/WhammTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,30 @@ def X_ = [
T("parse2", test_parse2),
T("call1", test_call1),
T("call2", test_call2),
// TODO: immN, localN, argN
T("call3", test_call3),
T("imm0", test_imm0),
T("imm1", test_imm1),
T("local0", test_local0),
T("local1", test_local1),
T("arg0", test_arg0),
T("arg1", test_arg1),
T("fail0", test_fail0),
T("fail1", test_fail1),
T("fail2", test_fail2),
T("fail3", test_fail3),
()
];

class WhammTester(t: Tester) {
def assert_params(expected: Array<WhammParam>, str: string) {
var r = TextReader.new("<input>", str);
var got = WhammUtil.parseParams(r);
if (!r.ok) t.fail1("failed to parse: %s", r.error_msg);
if (!r.ok) {
Trace.OUT.puts("failed to parse:").ln();
r.renderCurrentLineWithCaret(Trace.OUT, r.error_column);
Trace.OUT.ln();
t.fail1("failed to parse: %s", r.error_msg);
}
if (got.length != expected.length) return t.fail2("expected %d params, got %d", expected.length, got.length);
for (i < expected.length) {
if (!equal(expected[i], got[i])) {
Expand All @@ -35,6 +50,13 @@ class WhammTester(t: Tester) {
}
}
}
def assert_fail(expected_msg: string, str: string) {
var r = TextReader.new("<input>", str);
var got = WhammUtil.parseParams(r);
if (r.ok) return t.fail1("expected parse failure: \"%s\"", str);
if (!Strings.startsWith(r.error_msg, expected_msg)) t.fail2("expected parse failure {%s}, got {%s}", expected_msg, r.error_msg);

}
def equal(a: WhammParam, b: WhammParam) -> bool {
if (a == b) return true;
match (a) {
Expand Down Expand Up @@ -70,6 +92,10 @@ def CALL(str: string, params: Array<WhammParam>) -> WhammParam.Call {
return WhammParam.Call(Token.new("<file>", str, 0, 0), params);
}

def IMM = WhammParam.Imm(null, _);
def LOCAL = WhammParam.Local(null, _);
def ARG = WhammParam.Stack(null, _);

def test_parse1(t: WhammTester) {
t.assert_params([PC], "(pc)");
t.assert_params([FUNC], "(func)");
Expand Down Expand Up @@ -100,4 +126,79 @@ def test_call1(t: WhammTester) {

def test_call2(t: WhammTester) {
t.assert_params([CALL("foo", [FUNC]), CALL("bar", [])], "(foo(func), bar())");
}
}

def test_call3(t: WhammTester) {
t.assert_params([CALL("foo", [ARG(0)]), LOCAL(0), IMM(0)], "(foo(arg0), local0, imm0))");
t.assert_params([CALL("$BAZ", [LOCAL(1), LOCAL(2), ARG(0), ARG(1)])], "($BAZ(local1, local2, arg0, arg1))");
}

def test_imm0(t: WhammTester) {
t.assert_params([IMM(0)], "(imm0)");
}

def test_imm1(t: WhammTester) {
t.assert_params([IMM(1)], "(imm1)");
t.assert_params([IMM(7)], "(imm7)");
t.assert_params([IMM(22)], "(imm22)");
}

def test_local0(t: WhammTester) {
t.assert_params([LOCAL(0)], "(local0)");
}

def test_local1(t: WhammTester) {
t.assert_params([LOCAL(1)], "(local1)");
t.assert_params([LOCAL(6)], "(local6)");
t.assert_params([LOCAL(99)], "(local99)");
t.assert_params([LOCAL(1273)], "(local1273)");
}

def test_arg0(t: WhammTester) {
t.assert_params([ARG(0)], "(arg0)");
}

def test_arg1(t: WhammTester) {
t.assert_params([ARG(1)], "(arg1)");
t.assert_params([ARG(3)], "(arg3)");
t.assert_params([ARG(888)], "(arg888)");
t.assert_params([ARG(999)], "(arg999)");
}

def test_fail0(t: WhammTester) {
t.assert_fail("expected identifier", "(");
t.assert_fail("\"(\" expected", "arg");
}

def test_fail1(t: WhammTester) {
t.assert_fail("unresolved identifier", "(unknown)");
t.assert_fail("unresolved identifier", "(foo)");
t.assert_fail("unresolved identifier", "(pc, bar)");
t.assert_fail("unresolved identifier", "(pc, $NEN)");
}

def test_fail2(t: WhammTester) {
t.assert_fail("expected positive integer", "(arg)");
t.assert_fail("expected positive integer", "(imm)");
t.assert_fail("expected positive integer", "(local)");

t.assert_fail("unresolved", "(pc0)");
t.assert_fail("unresolved", "(func1)");
t.assert_fail("unresolved", "(frame2)");
}

def test_fail3(t: WhammTester) {
t.assert_fail("", "((");
t.assert_fail("", ")(");
t.assert_fail("", "(()");
t.assert_fail("", "(())");
t.assert_fail("", "[]");

t.assert_fail("", "(pc");
t.assert_fail("", "(pc, func");
t.assert_fail("", "(pc, func]");
t.assert_fail("", "(pc + func)");
t.assert_fail("", "(pc - func]");
t.assert_fail("", "(pc func]");
t.assert_fail("", "(pc(func)]");
}

0 comments on commit 2e7fae7

Please sign in to comment.