2 using System.Collections.Generic;
32 class ParseException : System.Exception
34 public ParseException(string message) : base(message)
41 public KeywordToken(string k, LexToken t)
46 public string keyword;
47 public LexToken token;
52 private static bool s_initialized = false;
53 private static int s_position = 0;
54 private static KeywordToken [] s_tokens = null;
55 public static KeywordToken[] Tokens = null;
57 public static void Initialize()
63 s_tokens = new KeywordToken[12];
67 AddToken("#include", LexToken.TOKEN_INCLUDE);
68 AddToken("#define", LexToken.TOKEN_DEFINE);
69 AddToken("native", LexToken.TOKEN_NATIVE);
70 AddToken("functag", LexToken.TOKEN_FUNCTAG);
71 AddToken("funcenum", LexToken.TOKEN_FUNCENUM);
72 AddToken("enum", LexToken.TOKEN_ENUM);
73 AddToken("stock", LexToken.TOKEN_STOCK);
74 AddToken("public", LexToken.TOKEN_PUBLIC);
75 AddToken("forward", LexToken.TOKEN_FORWARD);
76 AddToken("const", LexToken.TOKEN_CONST);
77 AddToken("struct", LexToken.TOKEN_STRUCT);
78 AddToken("operator", LexToken.TOKEN_OPERATOR);
83 private static void AddToken(string lex, LexToken tok)
85 s_tokens[s_position] = new KeywordToken(lex, tok);
92 private string FileName;
93 private StringBuilder Contents;
94 private bool LexPushed;
95 private LexToken _LastToken;
96 private string _LexString;
97 private char _LexChar;
100 public string LexString
116 public uint GetLineNumber()
121 public IncParser(string file)
125 /* Clear out lexer stuff */
127 _LastToken = LexToken.TOKEN_NONE;
132 /* Initialize; this can throw an exception */
136 public void Parse(ParseWriter w)
138 w.BeginSection(System.IO.Path.GetFileName(FileName));
139 LexToken tok = LexToken.TOKEN_NONE;
140 while ((tok = lex()) != LexToken.TOKEN_EOF)
144 case LexToken.TOKEN_DOCBLOCK:
146 w.WritePair("doc", LexString);
149 case LexToken.TOKEN_DEFINE:
154 case LexToken.TOKEN_ENUM:
159 case LexToken.TOKEN_FORWARD:
161 PARSE_Function(tok, w);
164 case LexToken.TOKEN_NATIVE:
166 PARSE_Function(tok, w);
169 case LexToken.TOKEN_STOCK:
171 PARSE_Function(tok, w);
174 case LexToken.TOKEN_FUNCTAG:
179 case LexToken.TOKEN_FUNCENUM:
184 case LexToken.TOKEN_INCLUDE:
189 case LexToken.TOKEN_STRUCT:
194 case LexToken.TOKEN_PUBLIC:
201 throw new ParseException("Unrecognized token: " + tok);
208 private void PARSE_Public(ParseWriter w)
210 /* Eat up an optional tag */
211 MatchToken(LexToken.TOKEN_LABEL);
214 NeedToken(LexToken.TOKEN_SYMBOL);
224 private void PARSE_Struct(ParseWriter w)
226 /* For now, we completely ignore these (they're not written to the output) */
227 NeedToken(LexToken.TOKEN_SYMBOL);
230 bool need_closebrace = true;
231 bool more_entries = true;
236 need_closebrace = false;
239 MatchToken(LexToken.TOKEN_CONST);
240 MatchToken(LexToken.TOKEN_LABEL);
241 NeedToken(LexToken.TOKEN_SYMBOL);
242 while (MatchChar('['))
244 DiscardUntilChar(']');
247 more_entries = MatchChar(',');
248 MatchToken(LexToken.TOKEN_DOCBLOCK);
261 private void PARSE_Include(ParseWriter w)
263 /* read until the end of the line */
264 int index = Contents.ToString().IndexOf('\n');
268 value = Contents.ToString();
269 Contents.Remove(0, value.Length);
277 value = Contents.ToString().Substring(0, index);
278 Contents.Remove(0, index);
281 /* Strip whitespace */
282 value = value.Trim('\r', '\t', ' ');
285 w.BeginSection("include");
286 w.WritePair("name", value);
290 private void PARSE_FuncEnum(ParseWriter w)
292 w.BeginSection("funcenum");
294 /* Get the functag name */
295 NeedToken(LexToken.TOKEN_SYMBOL);
296 w.WritePair("name", LexString);
300 bool need_closebrace = true;
306 need_closebrace = false;
310 /* Start function section */
311 w.BeginSection("function");
313 if (MatchToken(LexToken.TOKEN_DOCBLOCK))
315 w.WritePair("doc", LexString);
318 /* Get the return tag */
319 if (MatchToken(LexToken.TOKEN_LABEL))
321 w.WritePair("return", LexString);
324 /* Get function type */
325 if (MatchToken(LexToken.TOKEN_PUBLIC))
327 w.WritePair("type", "public");
329 else if (MatchToken(LexToken.TOKEN_STOCK))
331 w.WritePair("type", "stock");
334 /* Parse the parameters */
337 /* End the section */
355 private void PARSE_FuncTag(ParseWriter w)
357 w.BeginSection("functag");
359 /* Get the functag name */
360 NeedToken(LexToken.TOKEN_SYMBOL);
361 w.WritePair("name", LexString);
363 if (MatchToken(LexToken.TOKEN_LABEL))
365 w.WritePair("return", LexString);
368 if (MatchToken(LexToken.TOKEN_PUBLIC))
370 w.WritePair("type", "public");
372 else if (MatchToken(LexToken.TOKEN_STOCK))
374 w.WritePair("type", "stock");
383 private void PARSE_Function(LexToken tok, ParseWriter w)
387 /* Get the return value */
388 if (MatchToken(LexToken.TOKEN_LABEL))
393 if (MatchToken(LexToken.TOKEN_OPERATOR))
395 /* Check if we're some sort of invalid function */
396 DiscardUntilCharOrComment('(', false);
397 ParseParameters(null);
398 if (tok == LexToken.TOKEN_STOCK)
402 else if (tok == LexToken.TOKEN_NATIVE || tok == LexToken.TOKEN_FORWARD)
406 DiscardUntilCharOrComment(';', false);
414 NeedToken(LexToken.TOKEN_SYMBOL);
417 PARSE_Function(tok, w, tag, name);
420 private void PARSE_Function(LexToken tok, ParseWriter w, string tag, string name)
422 if (tok == LexToken.TOKEN_FORWARD)
424 w.BeginSection("forward");
426 else if (tok == LexToken.TOKEN_NATIVE)
428 w.BeginSection("native");
430 else if (tok == LexToken.TOKEN_STOCK)
432 w.BeginSection("stock");
435 w.WritePair("name", name);
439 w.WritePair("return", tag);
444 if (tok == LexToken.TOKEN_STOCK)
450 /* Make sure there's a semicolon */
453 DiscardUntilCharOrComment(';', false);
461 private void ParseParameters(ParseWriter w)
472 /* Start the parameter */
475 w.BeginSection("parameter");
478 /* Check for a 'const' token */
479 if (MatchToken(LexToken.TOKEN_CONST) && w != null)
481 w.WritePair("const", "true");
484 /* Check if it's by reference */
485 if (MatchChar('&') && w != null)
487 w.WritePair("byref", "true");
490 /* Check for a tag */
491 if (MatchToken(LexToken.TOKEN_LABEL) && w != null)
493 w.WritePair("tag", LexString);
496 /* Get the symbol and write it */
497 if (MatchString(0, "...") && w != null)
499 w.WritePair("name", "...");
503 NeedToken(LexToken.TOKEN_SYMBOL);
506 w.WritePair("name", LexString);
510 /* Check if we have a default value yet */
511 bool default_value = false;
514 default_value = true;
516 else if (MatchChar('['))
523 temp = ConsumeUntilChar(']');
530 } while (MatchChar('['));
531 w.WritePair("decoration", extra);
534 /* If we have a default value, get it */
535 if (default_value || MatchChar('='))
537 if ((extra = ConsumeParamDefValue()) == null)
539 throw new ParseException("Expected a default value; found none");
543 w.WritePair("defval", extra);
547 /* End the parameter */
562 private void PARSE_Enum(ParseWriter w)
565 if (MatchToken(LexToken.TOKEN_SYMBOL))
570 w.BeginSection("enum");
571 w.WritePair("name", name);
572 w.BeginSection("properties");
575 bool more_entries = true;
576 bool need_closebrace = true;
581 need_closebrace = false;
584 NeedToken(LexToken.TOKEN_SYMBOL);
588 DiscardUntilCharOrComment(',', true);
590 more_entries = MatchChar(',');
591 w.WritePair("name", name);
592 if (MatchToken(LexToken.TOKEN_DOCBLOCK))
594 w.WritePair("doc", LexString);
596 } while (more_entries);
606 private void PARSE_Define(ParseWriter w)
608 /* first, get the symbol name */
609 NeedToken(LexToken.TOKEN_SYMBOL);
610 string name = LexString;
613 /* read until we hit the end of a line OR a comment */
615 for (int i = 0; i < Contents.Length; i++)
617 if (Contents[i] == '\n')
622 else if (CheckString(i, "/**"))
635 value = Contents.ToString().Substring(0, endpoint);
636 Contents.Remove(0, endpoint);
640 w.BeginSection("define");
641 w.WritePair("name", name);
642 w.WritePair("linetext", value);
646 /* Reads up to a character and discards all text before it.
648 private void DiscardUntilChar(char c)
650 int eol = Contents.ToString().IndexOf(c);
657 Contents.Remove(0, eol);
660 /* Same as DiscardUntilChar, except the discarded
661 * text is returned first.
663 private string ConsumeUntilChar(char c)
665 int eol = Contents.ToString().IndexOf(c);
672 string rest = Contents.ToString().Substring(0, eol);
673 Contents.Remove(0, eol);
678 * Returns a parameter's default value.
680 private string ConsumeParamDefValue()
682 int eol = FindEndOfExprLine(0);
688 string rest = Contents.ToString().Substring(0, eol);
689 Contents.Remove(0, eol);
694 * Discards all data up to a certain character, ignoring
695 * the contents, unless specified to stop at a doc block comment.
697 private void DiscardUntilCharOrComment(char c, bool stop_at_comment)
699 for (int i = 0; i < Contents.Length; i++)
701 if (Contents[i] == '\n')
705 else if (Contents[i] == c)
709 Contents.Remove(0, i);
713 else if (stop_at_comment && CheckString(i, "/**"))
717 Contents.Remove(0, i);
725 * Finds the end of an expression
727 private int FindEndOfExprLine(int start)
729 for (int i = start; i < Contents.Length; i++)
731 if (Contents[i] == '{')
733 if ((i = FindEndOfConsumptionLine('}', start+1)) == -1)
735 throw new ParseException("Expected token: } (found none)");
738 else if (Contents[i] == ')' || Contents[i] == ',')
747 private int FindEndOfConsumptionLine(char c, int start)
749 for (int i = start; i < Contents.Length; i++)
751 if (Contents[i] == '{')
753 if ((i = FindEndOfConsumptionLine('}', i + 1)) == -1)
755 throw new ParseException("Expected token: } (found none)");
758 else if (Contents[i] == c)
767 private void Initialize()
769 /* Open file, read it, and close it */
770 StreamReader sr = File.OpenText(FileName);
771 Contents = new StringBuilder(sr.ReadToEnd());
774 /* Strip comments (may throw an exception) */
777 /* Lastly, initialize the tokenizer */
778 Tokenizer.Initialize();
781 /* Consumes a token iff it matches */
782 private bool MatchToken(LexToken token)
784 LexToken tok = lex();
793 /* Consumes a token and aborts the parser if there is no match */
794 private void NeedToken(LexToken token)
796 if (!MatchToken(token))
798 throw new ParseException("Expected token: " + token);
802 /* Consumes a character iff it matches */
803 private bool MatchChar(char c)
805 if (lex() != LexToken.TOKEN_CHARACTER)
818 /* Consumes a character or aborts the parser if there is no match */
819 private void NeedChar(char c)
823 throw new ParseException("Expected character: " + c);
827 /* Saves the lexer state so we don't reparse unnecessarily */
828 private void lexpush()
832 throw new ParseException("Lexer cannot be pushed twice");
837 /* Attempts to match a string against the next stream tokens.
838 * On success, the token stream is advanced.
840 private bool MatchString(int startPos, string str)
842 if (str.Length > Contents.Length - startPos)
846 for (int i = 0; i < str.Length; i++)
848 if (str[i] != Contents[startPos + i])
853 Contents.Remove(startPos, str.Length);
857 /* Attempts to match a string against the next stream tokens */
858 private bool CheckString(int startPos, string str)
860 if (str.Length > Contents.Length - startPos)
864 for (int i = 0; i < str.Length; i++)
866 if (str[i] != Contents[startPos + i])
874 /* Ignores a block starting and ending with { and } respectively */
875 private void ignore_block()
877 /* Hrm, this is tricky. First, expect an open brace. */
879 /* Consume the entire function by keeping track of braces */
881 bool in_string = false;
882 bool found_end = false;
883 for (int i = 0; i < Contents.Length; i++)
885 if (Contents[i] == '\n')
891 if (Contents[i] == '"')
895 else if (Contents[i] == '{')
899 else if (Contents[i] == '}')
903 throw new ParseException("Unable to find a matching open brace");
905 if (--numBraces == 0)
907 Contents.Remove(0, i + 1);
915 /* It is possible to be in a string and at the first character */
916 if (Contents[i] == '"' && Contents[i - 1] != '\\')
924 throw new ParseException("Unable to find a matching close brace");
928 /* Strips all non-doc C and C++ style comments. as well as unused pragmas */
929 private void preprocess()
931 if (Contents.Length == 1)
936 bool in_comment = false;
939 bool is_newline = true;
941 for (int i=0; i<Contents.Length-1; i++)
943 if (Contents[i] == '\n')
949 if (Contents[i] == '/')
951 if (Contents[i+1] == '/')
956 } else if (Contents[i+1] == '*') {
957 /* Detect whether this is a doc comment or not */
958 if (Contents.Length - i >= 3
959 && Contents[i+2] == '*')
968 if (in_comment && !is_doc)
971 Contents[i+1] = ' '; /* Just for safety */
976 if (Contents[i] == '#')
978 if (!CheckString(i, "#define")
979 && !CheckString(i, "#include"))
989 else if (!Char.IsWhiteSpace(Contents[i]))
997 if (Contents[i] == '*' && Contents[i+1] == '/')
1002 Contents[i+1] = ' ';
1005 i++; /* Don't look at the next character */
1007 else if (!is_doc && Contents[i] != '\n')
1028 throw new ParseException("Multi-line comment has no terminator");
1032 /* Strips whitespace */
1033 private void trim_whitespace_pre()
1036 while (spaces < Contents.Length
1037 && Contents[spaces] != '\0'
1038 && Char.IsWhiteSpace(Contents[spaces]))
1040 if (Contents[spaces] == '\n')
1048 Contents.Remove(0, spaces);
1052 private void preprocess_line()
1054 trim_whitespace_pre();
1057 private bool IsSymbolChar(char c, bool first)
1059 bool valid = (first ? Char.IsLetter(c) : Char.IsLetterOrDigit(c));
1060 return (valid || (c == '_'));
1063 private bool IsEscapedChar(char c)
1073 private bool IsNotKeywordChar(char c)
1075 return (Char.IsWhiteSpace(c)
1076 || (c == '-' || c == '+')
1077 || (c == '/' || c == '*')
1078 || (c == '(' || c == ')')
1079 || (c == '=' || c == '%')
1080 || (c == '>' || c == '<')
1081 || (c == '!' || c == '~')
1085 /* Consumes one lexical token and gathers information about it */
1086 private LexToken lex()
1094 /* Number of chars we will be deleting from the input */
1097 /* Clear our state */
1098 _LastToken = LexToken.TOKEN_NONE;
1100 /* Remove stuff we don't want from the line */
1103 if (Contents.Length < 1)
1105 _LastToken = LexToken.TOKEN_EOF;
1109 /* Get the token list */
1110 KeywordToken[] tokens = Tokenizer.Tokens;
1111 for (int i = 0; i < tokens.Length; i++)
1113 if (!CheckString(0, tokens[i].keyword))
1117 int len = tokens[i].keyword.Length;
1118 /* Now check to see what the next token is */
1119 if (Contents.Length == len
1120 || IsNotKeywordChar(Contents[len]))
1122 /* We have a token match! */
1123 _LastToken = tokens[i].token;
1131 if (_LastToken == LexToken.TOKEN_NONE)
1133 /* See if we can try to read a symbol */
1134 if (IsSymbolChar(Contents[0], true))
1137 while (Contents[characters] != '\0'
1138 && IsSymbolChar(Contents[characters], false))
1142 stripchars = characters;
1143 /* We're done! See what's next.. */
1144 if (Contents[characters] == ':')
1146 _LastToken = LexToken.TOKEN_LABEL;
1151 _LastToken = LexToken.TOKEN_SYMBOL;
1153 _LexString = Contents.ToString().Substring(0, characters);
1154 _LexChar = _LexString[0];
1156 else if (Contents[0] == '"')
1158 bool reached_end = false;
1159 StringBuilder builder = new StringBuilder();
1160 for (int i = 1; i < Contents.Length; i++)
1162 if (i < Contents.Length - 1
1163 && Contents[i] == '\\'
1164 && IsEscapedChar(Contents[i+1]))
1168 else if (Contents[i] == '"')
1173 builder.Append(Contents[i]);
1177 throw new ParseException("Expected end of string; none found");
1179 _LexString = builder.ToString();
1180 _LexChar = _LexString.Length > 0 ? _LexString[0] : '\0';
1182 else if (Contents[0] == '\'')
1185 if (Contents.Length < 3)
1187 throw new ParseException("Expected end of character; none found");
1189 else if (Contents[1] == '\\')
1191 if (Contents.Length < 4 || !IsEscapedChar(Contents[2]))
1193 throw new ParseException("Expected end of character; none found");
1195 if (Contents[2] == 'r')
1199 else if (Contents[2] == 'n')
1203 else if (Contents[2] == 't')
1207 else if (Contents[2] == 'v')
1211 else if (Contents[2] == '\\')
1215 else if (Contents[2] == '"')
1226 _LexString = c.ToString();
1228 _LastToken = LexToken.TOKEN_QUOTCHAR;
1230 else if (CheckString(0, "/**"))
1233 for (int i=3; i<Contents.Length-1; i++)
1235 if (Contents[i] == '\n')
1239 else if (Contents[i] == '*' && Contents[i+1] == '/')
1247 throw new ParseException("Expected end of multi-line comment; found end of file");
1249 /* We have to factor one more character in because we're zero based */
1251 _LastToken = LexToken.TOKEN_DOCBLOCK;
1252 _LexString = Contents.ToString().Substring(0, endpoint);
1253 _LexChar = _LexString[0];
1254 stripchars = endpoint;
1258 _LastToken = LexToken.TOKEN_CHARACTER;
1259 _LexString = Contents[0].ToString();
1260 _LexChar = Contents[0];
1268 Contents.Remove(0, stripchars);