From: Michael D. Lowis Date: Fri, 10 Oct 2014 18:31:26 +0000 (-0400) Subject: Finished first pass at test suite for grammar rules X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=bdfc0af1cca4bb591535b5168ed320721bb0c33c;p=proto%2Fsclpl.git Finished first pass at test suite for grammar rules --- diff --git a/source/sclpl/grammar.c b/source/sclpl/grammar.c index a36d394..9083d2f 100644 --- a/source/sclpl/grammar.c +++ b/source/sclpl/grammar.c @@ -12,14 +12,12 @@ tree_t* grammar_toplevel(parser_t* p_parser) { tree_t* p_tree = NULL; try { - if (parser_accept_str(p_parser, T_VAR, "import")) - grammar_import(p_parser); + if (parser_accept_str(p_parser, T_VAR, "require")) + grammar_require(p_parser); else if (parser_accept_str(p_parser, T_VAR, "def")) grammar_definition(p_parser); - else if (p_parser->p_lexer->scanner->p_input == stdin) - grammar_expression(p_parser); else - parser_error(p_parser, "Unrecognized top-level form"); + grammar_expression(p_parser); p_tree = parser_get_tree(p_parser); } catch(ParseException) { fprintf(stderr, "Invalid Syntax\n"); @@ -27,7 +25,7 @@ tree_t* grammar_toplevel(parser_t* p_parser) return p_tree; } -void grammar_import(parser_t* p_parser) +void grammar_require(parser_t* p_parser) { size_t mark = parser_mark(p_parser); parser_expect(p_parser, T_VAR); @@ -83,7 +81,6 @@ void grammar_literal(parser_t* p_parser) default: parser_error(p_parser, "Not a valid expression"); - break; } } @@ -105,8 +102,9 @@ void grammar_if_stmnt(parser_t* p_parser) size_t mark = parser_mark(p_parser); grammar_expression(p_parser); grammar_expression(p_parser); - parser_expect_str(p_parser,T_VAR,"else"); - grammar_expression(p_parser); + if (parser_accept_str(p_parser, T_VAR, "else")) { + grammar_expression(p_parser); + } parser_expect(p_parser,T_END); parser_reduce(p_parser, mark); } diff --git a/source/sclpl/grammar.h b/source/sclpl/grammar.h index 9c9749b..08f4954 100644 --- a/source/sclpl/grammar.h +++ b/source/sclpl/grammar.h @@ -9,7 +9,7 @@ tree_t* grammar_toplevel(parser_t* p_parser); -void grammar_import(parser_t* p_parser); +void grammar_require(parser_t* p_parser); void grammar_definition(parser_t* p_parser); diff --git a/source/sclpl/lexer.c b/source/sclpl/lexer.c index 3881279..686a928 100644 --- a/source/sclpl/lexer.c +++ b/source/sclpl/lexer.c @@ -25,7 +25,7 @@ static int read_radix(char ch); static void lex_tok_free(void* p_obj) { lex_tok_t* p_tok = (lex_tok_t*)p_obj; - if (NULL != p_tok->value) + if ((p_tok->type != T_BOOL) && (p_tok->type != T_CHAR) && (NULL != p_tok->value)) mem_release(p_tok->value); } @@ -108,8 +108,8 @@ static lex_tok_t* lexer_char(char* text) "\t\0tab", "\v\0vtab" }; - if (strlen(text) == 1) { - p_tok = lex_tok_new(T_CHAR, (void*)((intptr_t)text[0])); + if (strlen(text) == 2) { + p_tok = lex_tok_new(T_CHAR, (void*)((intptr_t)text[1])); } else { for(int i = 0; i < 5; i++) { if (strcmp(text, &(lookup_table[i][2]))) { diff --git a/source/sclpl/main.c b/source/sclpl/main.c index b8ec5ab..dd2174c 100644 --- a/source/sclpl/main.c +++ b/source/sclpl/main.c @@ -5,6 +5,7 @@ #include "grammar.h" #include "parser.h" #include "lexer.h" +#include "pprint.h" /* Command Line Options *****************************************************************************/ @@ -19,28 +20,6 @@ opts_cfg_t Options_Config[] = { {NULL, false, NULL, NULL } }; -/* Tree Printing - *****************************************************************************/ -void print_indent(int depth) { - for(int i = 0; i < (2 * depth); i++) - printf("%c", ' '); -} - -void print_tree(tree_t* p_tree, int depth) { - print_indent(depth); - if (p_tree->tag == ATOM) { - pprint_token(stdout, p_tree->ptr.tok); - } else { - puts("(tree"); - vec_t* p_vec = p_tree->ptr.vec; - for(size_t idx = 0; idx < vec_size(p_vec); idx++) { - print_tree((tree_t*)vec_at(p_vec, idx), depth+1); - } - print_indent(depth); - puts(")"); - } -} - /* Tree Rewriting *****************************************************************************/ bool is_punctuation(lex_tok_t* p_tok) { @@ -55,6 +34,8 @@ bool is_punctuation(lex_tok_t* p_tok) { case T_RPAR: case T_COMMA: ret = true; + default: + break; } return ret; } @@ -97,7 +78,7 @@ static int emit_tree(void) { tree_t* p_tree = grammar_toplevel(p_parser); if (NULL != p_tree) { tree_t* p_ast = convert_to_ast(p_tree); - print_tree(p_ast, 0); + pprint_tree(stdout, p_ast, 0); mem_release(p_tree); mem_release(p_ast); } else { @@ -115,7 +96,7 @@ static int exec_repl(void) { tree_t* p_tree = grammar_toplevel(p_parser); if (NULL != p_tree) { tree_t* p_ast = convert_to_ast(p_tree); - print_tree(p_ast, 0); + pprint_tree(stdout, p_ast, 0); mem_release(p_tree); mem_release(p_ast); puts("OK."); diff --git a/source/sclpl/pprint.c b/source/sclpl/pprint.c index 2e1b6b8..34085ac 100644 --- a/source/sclpl/pprint.c +++ b/source/sclpl/pprint.c @@ -6,6 +6,11 @@ */ #include "pprint.h" +static void print_indent(FILE* file, int depth) { + for(int i = 0; i < (2 * depth); i++) + fprintf(file, "%c", ' '); +} + static const char* token_type_to_string(lex_tok_type_t type) { switch(type) { case T_STRING: return "T_STRING"; @@ -36,13 +41,13 @@ void pprint_token_type(FILE* file, lex_tok_t* token) { void pprint_token_value(FILE* file, lex_tok_t* token) { void* value = token->value; switch(token->type) { - case T_STRING: fprintf(file, "'%s'", ((char*)value)); break; - case T_CHAR: fprintf(file, "\\%c", ((char)value)); break; - case T_INT: fprintf(file, "%d", *((long int*)value)); break; - case T_FLOAT: fprintf(file, "%f", *((double*)value)); break; - case T_BOOL: fprintf(file, "%b", ((bool)value)); break; - case T_VAR: fprintf(file, "%s", ((char*)value)); break; - default: fprintf(file, "???"); break; + case T_STRING: fprintf(file, "'%s'", ((char*)value)); break; + case T_CHAR: fprintf(file, "\\%c", ((char)(int)value)); break; + case T_INT: fprintf(file, "%ld", *((long int*)value)); break; + case T_FLOAT: fprintf(file, "%f", *((double*)value)); break; + case T_BOOL: fprintf(file, "%s", ((int)value)?"true":"false"); break; + case T_VAR: fprintf(file, "%s", ((char*)value)); break; + default: fprintf(file, "???"); break; } } @@ -57,8 +62,19 @@ void pprint_token(FILE* file, lex_tok_t* token) } -void pprint_tree(FILE* file, tree_t* tree) +void pprint_tree(FILE* file, tree_t* tree, int depth) { - + print_indent(file, depth); + if (tree->tag == ATOM) { + pprint_token(file, tree->ptr.tok); + } else { + puts("(tree"); + vec_t* p_vec = tree->ptr.vec; + for(size_t idx = 0; idx < vec_size(p_vec); idx++) { + pprint_tree(file, (tree_t*)vec_at(p_vec, idx), depth+1); + } + print_indent(file, depth); + puts(")"); + } } diff --git a/source/sclpl/pprint.h b/source/sclpl/pprint.h index a669982..9d54159 100644 --- a/source/sclpl/pprint.h +++ b/source/sclpl/pprint.h @@ -16,6 +16,6 @@ void pprint_token_value(FILE* file, lex_tok_t* token); void pprint_token(FILE* file, lex_tok_t* token); -void pprint_tree(FILE* file, tree_t* tree); +void pprint_tree(FILE* file, tree_t* tree, int depth); #endif /* PPRINT_H */ diff --git a/source/sclpl/scanner.c b/source/sclpl/scanner.c index 0dc2785..32f75b0 100644 --- a/source/sclpl/scanner.c +++ b/source/sclpl/scanner.c @@ -30,7 +30,8 @@ char* scanner_read(scanner_t* p_scanner) { p_scanner->index++; } else { size_t start = p_scanner->index; - while(!scanner_oneof(p_scanner," \t\r\n()[];,'\"")) { + while(!scanner_oneof(p_scanner," \t\r\n()[];,'\"") && + (scanner_current(p_scanner) != '\0')) { p_scanner->index++; } p_tok = scanner_dup(p_scanner, start, p_scanner->index - start); diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 9539ef5..1bda7f6 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -17,19 +17,159 @@ def re_structure( token_array, offset = 0 ) return [offset, struct] end -def parser(input) +def ast(input) out, err, status = Open3.capture3('./build/bin/sclpl-test', '--ast', :stdin_data => input) + raise err unless err == "" raise "Parser command returned non-zero status" unless status.success? - raise "Parser produced error messages" unless err == "" - out.gsub!(//,'\1') + #out.gsub!(//,'\1') out.gsub!(/([()])|tree/,' \1 ') off, expr = re_structure(out.split) expr end -describe "parser" do - it "should parse a definition" do - expect(parser('def foo 123;')).to eq([ - ['T_VAR:def', 'T_VAR:foo', 'T_INT:123'] ]) +describe "sclpl grammar" do + context "requires" do + it "should parse a require statement" do + expect(ast('require foo;')).to eq([ ['T_VAR:require', 'T_VAR:foo'] ]) + end + + it "should parse a require statement using end keyword" do + expect(ast('require foo end')).to eq([ ['T_VAR:require', 'T_VAR:foo'] ]) + end + + it "should error on missing semicolon" do + expect{ast('require foo')}.to raise_error /Invalid Syntax/ + end + + it "should error on missing filename" do + expect{ast('require ;')}.to raise_error /Invalid Syntax/ + end + + it "should error on invalid filename type" do + expect{ast('require 123;')}.to raise_error /Invalid Syntax/ + end + + it "should error on too many parameters" do + expect{ast('require foo bar;')}.to raise_error /Invalid Syntax/ + end + end + + context "definitions" do + it "should parse a value definition" do + expect(ast('def foo 123;')).to eq([ ['T_VAR:def', 'T_VAR:foo', 'T_INT:123'] ]) + end + + it "should parse a function definition" do + expect(ast('def foo() 123;')).to eq([ + ['T_VAR:def', 'T_VAR:foo', ['T_VAR:fn', [], 'T_INT:123']] ]) + end + + it "should parse a function definition with multiple expressions in the body" do + expect(ast('def foo() 123 321;')).to eq([ + ['T_VAR:def', 'T_VAR:foo', ['T_VAR:fn', [], 'T_INT:123', 'T_INT:321']] ]) + end + + it "should parse a function definition with one argument" do + expect(ast('def foo(a) 123;')).to eq([ + ['T_VAR:def', 'T_VAR:foo', ['T_VAR:fn', ['T_VAR:a'], 'T_INT:123']] ]) + end + + it "should parse a function definition with two arguments" do + expect(ast('def foo(a,b) 123;')).to eq([ + ['T_VAR:def', 'T_VAR:foo', ['T_VAR:fn', ['T_VAR:a', 'T_VAR:b'], 'T_INT:123']] ]) + end + + it "should parse a function definition with three arguments" do + expect(ast('def foo(a,b,c) 123;')).to eq([ + ['T_VAR:def', 'T_VAR:foo', ['T_VAR:fn', ['T_VAR:a', 'T_VAR:b', 'T_VAR:c'], 'T_INT:123']] ]) + end + end + + context "expressions" do + context "parenthese grouping" do + it "should parse a parenthesized expression" do + expect(ast('(123)')).to eq([['T_INT:123']]) + end + + it "should parse a nested parenthesized expression" do + expect(ast('((123))')).to eq([[['T_INT:123']]]) + end + end + + context "if statements" do + it "should parse an if statement with no else clause" do + expect(ast('if 123 321 end')).to eq([["T_VAR:if", "T_INT:123", "T_INT:321"]]) + end + + it "should parse an if statement with an else clause " do + expect(ast('if 123 321 else 456 end')).to eq([ + ["T_VAR:if", "T_INT:123", "T_INT:321", "T_VAR:else", "T_INT:456"]]) + end + end + + context "function literals" do + it "should parse a function with no params" do + expect(ast('fn() 123;')).to eq([["T_VAR:fn", [], "T_INT:123"]]) + end + + it "should parse a function with one param" do + expect(ast('fn(a) 123;')).to eq([["T_VAR:fn", ["T_VAR:a"], "T_INT:123"]]) + end + + it "should parse a function with two params" do + expect(ast('fn(a,b) 123;')).to eq([["T_VAR:fn", ["T_VAR:a", "T_VAR:b"], "T_INT:123"]]) + end + end + + context "function application" do + it "should parse an application with no params " do + expect(ast('foo()')).to eq([["T_VAR:foo"]]) + end + + it "should parse an application with one param" do + expect(ast('foo(a)')).to eq([["T_VAR:foo", "T_VAR:a"]]) + end + + it "should parse an application with two params" do + expect(ast('foo(a,b)')).to eq([["T_VAR:foo", "T_VAR:a", "T_VAR:b"]]) + end + end + end + + context "literals" do + it "should parse a string" do + pending "Waiting for implementation of string literals" + expect(ast('"foo"')).to eq(['T_STRING:"foo"']) + end + + it "should parse a character" do + expect(ast('\\c')).to eq(['T_CHAR:\\c']) + end + + it "should parse an integer" do + expect(ast('123')).to eq(['T_INT:123']) + end + + it "should parse a float" do + expect(ast('123.0')).to eq(['T_FLOAT:123.000000']) + end + + it "should parse boolean" do + expect(ast('true')).to eq(['T_BOOL:true']) + end + + it "should parse an identifier" do + expect(ast('foo')).to eq(['T_VAR:foo']) + end + end + + context "corner cases" do + it "an unexpected terminator should error" do + expect{ast(';')}.to raise_error /Invalid Syntax/ + end + + it "an invalid literal should error" do + expect{ast('\'')}.to raise_error /Invalid Syntax/ + end end end