]> git.mdlowis.com Git - proto/sclpl.git/commitdiff
Finished first pass at test suite for grammar rules
authorMichael D. Lowis <mike@mdlowis.com>
Fri, 10 Oct 2014 18:31:26 +0000 (14:31 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Fri, 10 Oct 2014 18:31:26 +0000 (14:31 -0400)
source/sclpl/grammar.c
source/sclpl/grammar.h
source/sclpl/lexer.c
source/sclpl/main.c
source/sclpl/pprint.c
source/sclpl/pprint.h
source/sclpl/scanner.c
spec/parser_spec.rb

index a36d394fdbc45cfe1eb6ab42ddfffe0ea2e0f92e..9083d2f597fdf47f3d2fdc9cc2b1d821074f6dcb 100644 (file)
@@ -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);
 }
index 9c9749bd77a31163ae78025e95b4dd340c466169..08f4954e97e37e8be57cb4aa3c8bbd8d78990a73 100644 (file)
@@ -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);
 
index 3881279e8ee782ca89b1b8355d5914650e9af5e1..686a928460d324d532b2c60a64e452ddf695bf77 100644 (file)
@@ -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]))) {
index b8ec5abe6f27c32245fd23d5fdbc01bf902562dc..dd2174cee1132036bbd2b9472b754e3e10368e12 100644 (file)
@@ -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.");
index 2e1b6b831bdae697cec1b38fc3023aeb1278004d..34085ac203689dd850a7d476ee7c298b5accb7c4 100644 (file)
@@ -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(")");
+    }
 }
 
index a669982cf3efbf26138c9b427343b25247a8c012..9d54159e1f2d0dc4463ab97595a04cf999d4cb37 100644 (file)
@@ -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 */
index 0dc278547d9aae437da2e033bc962e9ef6ac19f3..32f75b01c8e482c0d3f8bce96e4e7a82875429ef 100644 (file)
@@ -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);
index 9539ef52fae9d587b154bdc168a38a30219d33e6..1bda7f6bc3fb408d2837e13e9423e44069e81255 100644 (file)
@@ -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!(/<tok (T_[A-Z]+)>/,'\1')
+  #out.gsub!(/<tok (T_[A-Z]+)>/,'\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