Fixes recursion bug in disambiguate_in().
[ohcount] / src / parsers / haml.rl
1 // haml.rl written by Fedor Korsakov. spiritusobscurus at gmail dot com
2
3 /************************* Required for every parser *************************/
4 #ifndef OHCOUNT_HAML_PARSER_H
5 #define OHCOUNT_HAML_PARSER_H
6
7 #include "../parser_macros.h"
8
9 // the name of the language
10 const char *HAML_LANG = LANG_HAML;
11
12 // the languages entities
13 const char *haml_entities[] = {
14   "space", "comment", "string", "element", "element_class",
15   "element_id", "evaluator", "any"
16 };
17
18 // constants associated with the entities
19 enum {
20   HAML_SPACE = 0, HAML_COMMENT, HAML_STRING, HAML_ELEMENT, HAML_ELEMENT_CLASS,
21   HAML_ELEMENT_ID, HAML_EVALUATOR, HAML_ANY
22 };
23
24 /*****************************************************************************/
25
26 #include "ruby.h"
27
28 %%{
29   machine haml;
30   write data;
31   include common "common.rl";
32   #EMBED(ruby)
33
34   # Line counting machine
35
36   action haml_ccallback {
37     switch(entity) {
38     case HAML_SPACE:
39       ls
40       break;
41     case HAML_ANY:
42       code
43       break;
44     case INTERNAL_NL:
45       emb_internal_newline(HAML_LANG)
46       break;
47     case NEWLINE:
48       emb_newline(HAML_LANG)
49       break;
50     case CHECK_BLANK_ENTRY:
51       check_blank_entry(HAML_LANG)
52     }
53   }
54
55         action haml_indent_level_inc { current_indent_level++; }
56         action haml_indent_level_res { current_indent_level = 0; }
57   action haml_indent_level_set { prior_indent_level = current_indent_level; }
58   action bracket_inc { bracket_level++; }
59   action bracket_dec { bracket_level--; }
60   action bracket_level_res { bracket_level = 0; }
61
62         haml_indent = ([ ]{2}) @haml_indent_level_inc;
63   haml_indent_init = ([ ]{2} >haml_indent_level_res @haml_indent_level_inc)? haml_indent*;
64   haml_eol = newline >haml_indent_level_res;
65   haml_special_char = [\.%#];
66   haml_ruby_evaluator = "==" | ([&!]? "=") | "-"  | "~";
67   haml_comment_delimiter = ("-#" | "/");
68
69   haml_xhtml_tag_modifier =
70     ('{' >bracket_level_res @code (
71       newline %{ entity = INTERNAL_NL; } %haml_ccallback
72       |
73       ws
74       |
75       (nonnewline - ws - [{}]) @code
76       |
77       '{' @bracket_inc
78       |
79       '}' @bracket_dec
80     )* :>> ('}' when { bracket_level == 0 }) @code)
81     |
82     ('[' >bracket_level_res @code (
83       newline %{ entity = INTERNAL_NL; } %haml_ccallback
84       |
85       ws
86       |
87       '[' @bracket_inc
88       |
89       ']' @bracket_dec
90       |
91       (nonnewline - ws - '[' - ']') @code
92     )* :>> (']' when { bracket_level == 0 }) @code);
93
94   haml_xhtml_tag = "%" ((nonnewline-ws-'['-'{')+ - haml_ruby_evaluator) haml_xhtml_tag_modifier? '//'?;
95
96   haml_block_line_transition =
97     haml_eol %{ entity = INTERNAL_NL;} %haml_ccallback
98     ( newline %{ entity = INTERNAL_NL; } %haml_ccallback )*
99     ( [ ]{2} when {current_indent_level < prior_indent_level} @haml_indent_level_inc )*
100     ( [ ]{2} when {current_indent_level >= prior_indent_level} @haml_indent_level_inc)+;
101
102   haml_block_comment =
103     haml_indent_init haml_comment_delimiter >haml_indent_level_set @comment (
104       haml_block_line_transition
105       |
106       ws
107       |
108       (nonnewline-ws) @comment
109     )*;
110
111   haml_comment = haml_block_comment;
112
113   haml_string =
114     haml_indent*
115     (nonnewline
116       - haml_comment_delimiter
117       - haml_ruby_evaluator
118       - "."
119       - "#"
120       - "%"
121       - [ ]) @code
122     (nonnewline @code | ("|" @code newline %{ entity = INTERNAL_NL;}))*;
123
124   haml_ruby_entry =
125     ([ ]{2})*
126     haml_xhtml_tag{,1}
127     haml_ruby_evaluator ws @code;
128
129   haml_ruby_outry =
130     newline %{ entity = INTERNAL_NL;} %haml_ccallback
131     any @{fhold;};
132
133   haml_ruby_line := |*
134     haml_ruby_outry @{ p = ts; fret; };
135     spaces        ${ entity = RUBY_SPACE; } => ruby_ccallback;
136     ruby_comment;
137     ruby_string;
138     newline       ${ entity = NEWLINE;    } => ruby_ccallback;
139     ^space        ${ entity = RUBY_ANY;   } => ruby_ccallback;
140   *|;
141
142   haml_line := |*
143     haml_ruby_entry @{entity = CHECK_BLANK_ENTRY; } @{saw(RUBY_LANG)} => {fcall haml_ruby_line; };
144     spaces          ${ entity = HAML_SPACE; } => haml_ccallback;
145     haml_comment;
146     haml_string;
147     newline         ${ entity = NEWLINE;    } => haml_ccallback;
148     ^space          ${ entity = HAML_ANY;   } => haml_ccallback;
149   *|;
150
151   # Entity machine
152
153   action haml_ecallback {
154     callback(HAML_LANG, haml_entities[entity], cint(ts), cint(te), userdata);
155   }
156
157   haml_element_entity = '%' alnum+;
158   haml_element_class_entity = '.' alnum+;
159   haml_element_id_entity = '#' alnum+;
160
161   haml_string_entity = (nonnewline
162       - haml_comment_delimiter
163       - haml_ruby_evaluator
164       - "."
165       - "#"
166       - "%"
167       - [ ])
168     (nonnewline | ("|" newline))*;
169
170   haml_evaluator_entity = haml_ruby_evaluator;
171
172   # TODO: modifier and comment entity machines
173
174   haml_entity := |*
175     space+                     ${ entity = HAML_SPACE;          }  => haml_ecallback;
176     haml_element_entity        ${ entity = HAML_ELEMENT;        }  => haml_ecallback;
177     haml_element_class_entity  ${ entity = HAML_ELEMENT_CLASS;  }  => haml_ecallback;
178     haml_element_id_entity     ${ entity = HAML_ELEMENT_ID;     }  => haml_ecallback;
179     haml_evaluator_entity      ${ entity = HAML_EVALUATOR;      }  => haml_ecallback;
180     haml_string_entity         ${ entity = HAML_STRING;         }  => haml_ecallback;
181     ^space                     ${ entity = HAML_ANY;            }  => haml_ecallback;
182   *|;
183 }%%
184
185 /************************* Required for every parser *************************/
186
187 /* Parses a string buffer with Haml markup.
188  *
189  * @param *buffer The string to parse.
190  * @param length The length of the string to parse.
191  * @param count Integer flag specifying whether or not to count lines. If yes,
192  *   uses the Ragel machine optimized for counting. Otherwise uses the Ragel
193  *   machine optimized for returning entity positions.
194  * @param *callback Callback function. If count is set, callback is called for
195  *   every line of code, comment, or blank with 'lcode', 'lcomment', and
196  *   'lblank' respectively. Otherwise callback is called for each entity found.
197  */
198 void parse_haml(char *buffer, int length, int count,
199                 void (*callback) (const char *lang, const char *entity, int s,
200                                   int e, void *udata),
201                 void *userdata
202   ) {
203   init
204
205   int prior_indent_level = 0;
206   int current_indent_level = 0;
207   int bracket_level = 0;
208
209   %% write init;
210   cs = (count) ? haml_en_haml_line : haml_en_haml_entity;
211   %% write exec;
212
213   // if no newline at EOF; callback contents of last line
214   if (count) { process_last_line(HAML_LANG) }
215 }
216
217 #endif
218
219 /*****************************************************************************/