Updated PARSER_DOC to include information on embedded languages.
[ohcount] / PARSER_DOC
1 PARSER_DOC written by Mitchell Foral
2
3 Overview:
4   I will assume the reader has a decent knowledge of how Ragel works and the
5   Ragel syntax. If not, please review the Ragel manual found at:
6     http://research.cs.queensu.ca/~thurston/ragel/
7
8   All parsers must at least:
9     * Call a callback function when a line of code is parsed.
10     * Call a callback function when a line of comment is parsed.
11     * Call a callback function when a blank line is parsed.
12   Additionally a parser can call the callback function for each position of
13   entities parsed.
14
15   Take a look at c.rl and even keep it open for reference when reading this
16   document to better understand how parsers work and how to write one.
17
18 Writing a Parser:
19   First create your parser in ext/ohcount_native/ragel_parsers/. Its name
20   should be the language you are parsing with a '.rl' extension. You will not
21   have to manually compile any parsers, as the Rakefile does this automatically
22   for you. Every parser must have the following at the top:
23
24 /************************* Required for every parser *************************/
25 #ifndef RAGEL_C_PARSER
26 #define RAGEL_C_PARSER
27
28 #include "ragel_parser_macros.h"
29
30 // the name of the language
31 const char *C_LANG = "c";
32
33 // the languages entities
34 const char *c_entities[] = {
35   "space", "comment", "string", "number", "preproc",
36   "keyword", "identifier", "operator", "any"
37 };
38
39 // constants associated with the entities
40 enum {
41   C_SPACE = 0, C_COMMENT, C_STRING, C_NUMBER, C_PREPROC,
42   C_KEYWORD, C_IDENTIFIER, C_OPERATOR, C_ANY
43 };
44
45 /*****************************************************************************/
46
47   And the following at the bottom:
48
49 /************************* Required for every parser *************************/
50
51 /* Parses a string buffer with C/C++ code.
52  *
53  * @param *buffer The string to parse.
54  * @param length The length of the string to parse.
55  * @param count Integer flag specifying whether or not to count lines. If yes,
56  *   uses the Ragel machine optimized for counting. Otherwise uses the Ragel
57  *   machine optimized for returning entity positions.
58  * @param *callback Callback function. If count is set, callback is called for
59  *   every line of code, comment, or blank with 'lcode', 'lcomment', and
60  *   'lblank' respectively. Otherwise callback is called for each entity found.
61  */
62 void parse_c(char *buffer, int length, int count,
63   void (*callback) (const char *lang, const char *entity, int start, int end)
64   ) {
65   p = buffer;
66   pe = buffer + length;
67   eof = pe;
68
69   buffer_start = buffer;
70   whole_line_comment = 0;
71   line_contains_code = 0;
72   line_start = 0;
73   entity = 0;
74
75   %% write init;
76   cs = (count) ? c_en_c_line : c_en_c_entity;
77   %% write exec;
78
79   // if no newline at EOF; callback contents of last line
80   if (count) { process_last_line(C_LANG) }
81 }
82
83 #endif
84
85 /*****************************************************************************/
86
87   (Your parser will go between these two blocks.)
88
89   The code can be found in the existing c.rl parser. You will need to change:
90     * RAGEL_[lang]_PARSER - Replace [lang] with your language name. So if you
91       are writing a C parser, it would be RAGEL_C_PARSER.
92     * [lang]_LANG - Set the variable name to be [lang]_LANG and its value to be
93       the name of your language to parse. [lang] is your language name. For C it
94       would be C_LANG.
95     * [lang]_entities - Set the variable name to be [lang]_entities (e.g.
96       c_entries) The value is an array of string entities your language has.
97       For example C has comment, string, number, etc. entities. You should
98       definately have "space", and "any" entities. "any" entities are typically
99       used for entity machines (discussed later) and match any character that
100       is not recognized so the parser does not do something unpredictable.
101     * enum - Change the value of the enum to correspond with your entities. So
102       if in your parser you look up [lang]_entities[ENTITY], you will get the
103       associated entity's string name.
104     * parse_[lang] - Set the function name to parse_[lang] where again, [lang]
105       is the name of your language. In the case of C, it is parse_c.
106     * [lang]_en_[lang]_line - The line counting machine.
107     * [lang]_en_[lang]_entity - The entity machine.
108
109     You may be asking why you have to rename variables and functions. Well if
110     variables have the same name in header files (which is what parsers are),
111     the compiler complains. Also, when you have languages embedded inside each
112     other, any identifiers with the same name can easily be mixed up. It is also
113     important to prefix your Ragel definitions with your language to avoid
114     conflicts with other parsers.
115
116   Additional variables available to parsers are in the "ragel_parser_macros.h"
117   file. Take a look at it and try to understand what the variables are used for.
118   They will make more sense later on.
119
120   Now you can define your Ragel parser. Name your machine after your language,
121   'write data', and include 'common.rl', a file with common Ragel definitions,
122   actions, etc. For example:
123     %%{
124       machine c;
125       write data;
126       include "common.rl";
127
128       ...
129     }%%
130
131   Before you begin to write patterns for each entity in your language, you need
132   to understand how the parser should work.
133
134   Each parser has two machines: one optimized for counting lines of code,
135   comments, and blanks; the other for identifying entity positions in the
136   buffer.
137
138   Line Counting Machine:
139     This machine should be written as a line-by-line parser for multiple lines.
140     This means you match any combination of entities except a newline up until
141     you do reach a newline. If the line contains only spaces, or nothing at all,
142     it is blank. If the line contains spaces at first, but then a comment, or
143     just simply a comment, the line is a comment. If the line contains anything
144     but a comment after spaces (if there are any), it is a line of code. You
145     will do this using a Ragel scanner.
146     The callback function will be called for each line parsed.
147
148     Scanner Parser Structure:
149       A scanner parser will look like this:
150         [lang]_line := |*
151           entity1 ${ entity = ENTITY1; } => [lang]_ccallback;
152           entity2 ${ entity = ENTITY2; } => [lang]_ccallback;
153           ...
154           entityn ${ entity = ENTITYN; } => [lang]_ccallback;
155         *|;
156       (As usual, replace [lang] with your language name.)
157       Each entity is the pattern for an entity to match, the last one typically
158       being the newline entity. For each match, the variable is set to a
159       constant defined in the enum, and the main action is called (you will need
160       to create this action above the scanner).
161
162       When you detect whether or not a line is code or comment, you should call
163       the appropriate 'code' or 'comment' action defined in common.rl as soon
164       as possible. It is not necessary to worry about whether or not these
165       actions are called more than once for a given line; the first call to
166       either sets the status of the line permanently. Sometimes you cannot call
167       'code' or 'comment' for one reason or another. Do not worry, as this is
168       discussed later.
169
170       When you reach a newline, you will need to decide whether the current line
171       is a line of code, comment, or blank. This is easy. Simply check if the
172       line_contains_code or whole_line_comment variables are set to 1. If
173       neither of them are, the line is blank. Then call the callback function
174       (not action) with an "lcode", "lcomment", or "lblank" string, and the
175       start and end positions of that line (including the newline). The start
176       position of the line is in the line_start variable. It should be set at
177       the beginning of every line either through the 'code' or 'comment'
178       actions, or manually in the main action. Finally the line_contains_code,
179       whole_line_comment, and line_start state variables must be reset. All this
180       should be done within the main action shown below.
181       Note: For most parsers, the std_newline(lang) macro is sufficient and does
182       everything in the main action mentioned above. The lang parameter is the
183       [lang]_LANG string.
184
185     Main Action Structure:
186       The main action looks like this:
187         action [lang]_ccallback {
188           switch(entity) {
189           when ENTITY1:
190             ...
191             break;
192           when ENTITY2:
193             ...
194             break;
195           ...
196           when ENTITYN:
197             ...
198             break;
199           }
200         }
201
202     Defining Patterns for Entities:
203       Now it is time to write patterns for each entity in your language. That
204       does not seem very hard, except when your entity can cover multiple lines.
205       Comments and strings in particular can do this. To make an accurate line
206       counter, you will need to count the lines covered by multi-line entities.
207       When you detect a newline inside your multi-line entity, you should set
208       the entity variable to be INTERNAL_NL (-2) and call the main action. The
209       main action should have a case for INTERNAL_NL separate from the newline
210       entity. In it, you will check if the current line is code or comment and
211       call the callback function with the appropriate string ("lcode" or
212       "lcomment") and beginning and end of the line (including the newline).
213       Afterwards, you will reset the line_contains_code and whole_line_comment
214       state variables. Then set the line_start variable to be p, the current
215       Ragel buffer position. Because line_contains_code and whole_line_comment
216       have been reset, any non-newline and non-space character in the multi-line
217       pattern should set line_contains_code or whole_line_comment back to 1.
218       Otherwise you would count the line as blank.
219       Note: For most parsers, the std_internal_newline(lang) macro is sufficient
220       and does everything in the main action mentioned above. The lang parameter
221       is the [lang]_LANG string.
222
223       For multi-line matches, it is important to call the 'code' or 'comment'
224       actions (mentioned earlier) before an internal newline is detected so the
225       line_contains_code and whole_line_comment variables are properly set. For
226       other entities, you can use the 'code' macro inside the main action which
227       executes the same code as the Ragel 'code' action. Other C macros are
228       'comment' and 'ls', the latter is typically used for the SPACE entity when
229       defining line_start.
230
231     Notes:
232       * You can be a bit sloppy with the line counting machine. For example the
233         only C entities that can contain newlines are strings and comments, so
234         INTERNAL_NL would only be necessary inside them. Other than those,
235         anything other than spaces is considered code, so do not waste your time
236         defining specific patterns for other entities.
237
238     Parsers with Embedded Languages:
239       Notation: [lang] is the parent language, [elang] is the embedded language.
240
241       To write a parser with embedded languages (such as HTML with embedded CSS
242       and Javascript), you should first #include the parser(s) above your Ragel
243       code. The header file is "[elang]_parser.h".
244
245       Next, after the inclusion of 'common.rl', add '#EMBED([elang])' on
246       separate lines for each embedded language. The Rakefile looks for these
247       special comments to embed the language for you automatically.
248
249       In your main action, you need to add another entity CHECK_BLANK_ENTRY. It
250       should call the 'check_blank_entry([lang]_LANG)' macro. Blank entries are
251       an entry into an embedded language, but the rest of the line is blank
252       before a newline. For example, a CSS entry in HTML is something like:
253         <style type="text/css">
254       If there is no CSS code after the entry (a blank entry), the line should
255       be counted as HTML code, and the 'check_blank_entry' macro handles this.
256       But you may be asking, "how do I get to the CHECK_BLANK_ENTRY entity?".
257       This will be discussed in just a bit.
258
259       For each embedded language you will have to define an entry and outry. An
260       entry is the pattern that transitions from the parent language into the
261       child language. An outry is the pattern from child to parent. You will
262       need to put your entries in your [lang]_line machine. You will also need
263       to re-create each embedded language's line machine (define as
264       [lang]_[elang]_line; e.g. html_css_line) and put outry patterns in those.
265       Entries typically would be defined as [lang]_[elang]_entry, and outries
266       as [lang]_[elang]_outry.
267
268       Entry pattern actions should be:
269         [lang]_[elang]_entry @{ entity = CHECK_BLANK_ENTRY; } @[lang]_callback
270           @{ fgoto [lang]_[elang]_line; };
271       What this does is checks for a blank entry, and if it is, counts the line
272       as a line of parent language code. If it is not, the macro will not do
273       anything. The machine then transitions into the child language.
274
275       Outry pattern actions should be:
276         @{ p = ts; fgoto [lang]_line };
277       What this does is sets the current Ragel parser position to the beginning
278       of the outry so the line is counted as a line of parent language code. The
279       machine then transitions into the parent language.
280
281   Entity Identifying Machine:
282     This machine does not have to be written as a line-by-line parser. It only
283     has to identify the positions of language entities, such as whitespace,
284     comments, strings, etc. in sequence. As a result they can be written much
285     faster and more easily with less thought than a line counter. Using a
286     scanner is most efficient.
287     The callback function will be called for each entity parsed.
288
289     Scanner Structure:
290       [lang]_entity := |*
291         entity1 ${ entity = ENTITY1; } => [lang]_ecallback;
292         entity2 ${ entity = ENTITY2; } => [lang]_ecallback;
293         ...
294         entityn ${ entity = ENTITYN; } => [lang]_ecallback;
295       *|;
296
297     Main Action Structure:
298       action [lang]_ecallback {
299         callback([lang]_LANG, entity, cint(ts), cint(te));
300       }
301
302     Parsers for Embedded Languages:
303       TODO: