Ragel parsers will use 2 machines: one optimized for counting, one for entities.
[ohcount] / ext / ohcount_native / ragel_parser.c
1 #include "ruby.h"
2 #include "common.h"
3
4 // BEGIN parser includes
5 #include "c_parser.h"
6 #include "lua_parser.h"
7 // END parser includes
8
9 ParseResult *pr;
10 char *parse_buffer;
11 int parse_buffer_len;
12
13 struct language {
14   char name[MAX_LANGUAGE_NAME];
15   void (*parser) (char*, int, int, void*);
16 };
17
18 struct language languages[] = {
19 // BEGIN languages
20   { "c", parse_c },
21   { "cpp", parse_c },
22   { "lua", parse_lua },
23 // END languages
24   { "", NULL }
25 };
26
27 /* Returns a language_breakdown for a given language name. */
28 LanguageBreakdown *get_language_breakdown(char *name) {
29         int i;
30         for (i = 0; i < pr->language_breakdown_count; i++)
31                 if (strcmp(pr->language_breakdowns[i].name, name) == 0)
32                         return &pr->language_breakdowns[i]; // found one
33
34         language_breakdown_initialize(
35     &pr->language_breakdowns[pr->language_breakdown_count],
36     name, parse_buffer_len); // create one
37         return &pr->language_breakdowns[pr->language_breakdown_count++];
38 }
39
40 /* Yields a line's language, semantic, and text to an optional Ruby block. */
41 void ragel_parse_yield_line(const char *lang, const char *entity, int s, int e) {
42         if (rb_block_given_p()) {
43         VALUE ary;
44                 ary = rb_ary_new2(2);
45                 rb_ary_store(ary, 0, ID2SYM(rb_intern(lang)));
46                 if (strcmp(entity, "lcode") == 0)
47       rb_ary_store(ary, 1, ID2SYM(rb_intern("code")));
48     else if (strcmp(entity, "lcomment") == 0)
49       rb_ary_store(ary, 1, ID2SYM(rb_intern("comment")));
50     else if (strcmp(entity, "lblank") == 0)
51       rb_ary_store(ary, 1, ID2SYM(rb_intern("blank")));
52     rb_ary_store(ary, 2, rb_str_new(parse_buffer + s, e - s));
53                 rb_yield(ary);
54         }
55 }
56
57 /* Callback function called for every entity in the source file discovered.
58  *
59  * Entities are defined in the parser and are things like comments, strings,
60  * keywords, etc.
61  * This callback yields for a Ruby block if necessary:
62  *   |language, semantic, line|
63  * @param *lang The language associated with the entity.
64  * @param *entity The entity discovered. There are 3 additional entities used
65  *   by Ohcount for counting: lcode, lcomment, and lblank for a line of code,
66  *   a whole line comment, or a blank line respectively.
67  * @param s The start position of the entity relative to the start of the
68  *   buffer.
69  * @param e The end position of the entity relative to the start of the buffer
70  *   (non-inclusive).
71  */
72 void ragel_parser_callback(const char *lang, const char *entity, int s, int e) {
73   LanguageBreakdown *lb = get_language_breakdown((char *) lang);
74   if (strcmp(entity, "lcode") == 0) {
75     language_breakdown_copy_code(lb, parse_buffer + s, parse_buffer + e);
76     ragel_parse_yield_line(lang, entity, s, e);
77   } else if (strcmp(entity, "lcomment") == 0) {
78     language_breakdown_copy_comment(lb, parse_buffer + s, parse_buffer + e);
79     ragel_parse_yield_line(lang, entity, s, e);
80   } else if (strcmp(entity, "lblank") == 0) {
81     lb->blank_count++;
82     ragel_parse_yield_line(lang, entity, s, e);
83   }
84 }
85
86 /* Tries to use an existing Ragel parser for the given language.
87  *
88  * @param *parse_result An allocated, empty ParseResult to hold parse results.
89  * @param *buffer A pointer to the buffer or character in the buffer to start
90  *   parsing at.
91  * @param buffer_len The length of the buffer to parse.
92  * @param *lang The language name associated with the buffer to parse.
93  * @return 1 if a Ragel parser is found, 0 otherwise.
94  */
95 int ragel_parser_parse(ParseResult *parse_result,
96                        char *buffer, int buffer_len, char *lang) {
97   pr = parse_result;
98   pr->language_breakdown_count = 0;
99   parse_buffer = buffer;
100   parse_buffer_len = buffer_len;
101   int i;
102   for (i = 0; strlen(languages[i].name) != 0; i++)
103     if (strcmp(languages[i].name, lang) == 0) {
104       languages[i].parser(buffer, buffer_len, 1, ragel_parser_callback);
105       return 1;
106     }
107   return 0;
108 }