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 if (_LexString == "static" || _LexString == "new")
206 throw new ParseException("Unrecognized token: " + tok + " " + _LexString);
213 private void PARSE_Public(ParseWriter w)
215 MatchToken(LexToken.TOKEN_CONST);
217 /* Eat up an optional tag */
218 MatchToken(LexToken.TOKEN_LABEL);
221 NeedToken(LexToken.TOKEN_SYMBOL);
223 while (MatchChar('['))
225 DiscardUntilChar(']');
233 else if (MatchChar('(') && MatchChar(')'))
242 private void PARSE_Struct(ParseWriter w)
244 /* For now, we completely ignore these (they're not written to the output) */
245 NeedToken(LexToken.TOKEN_SYMBOL);
246 w.structList.Add(LexString);
249 bool need_closebrace = true;
250 bool more_entries = true;
255 need_closebrace = false;
258 MatchToken(LexToken.TOKEN_CONST);
259 MatchToken(LexToken.TOKEN_LABEL);
260 NeedToken(LexToken.TOKEN_SYMBOL);
261 while (MatchChar('['))
263 DiscardUntilChar(']');
266 more_entries = MatchChar(',');
267 MatchToken(LexToken.TOKEN_DOCBLOCK);
280 private void PARSE_Include(ParseWriter w)
282 /* read until the end of the line */
283 int index = Contents.ToString().IndexOf('\n');
287 value = Contents.ToString();
288 Contents.Remove(0, value.Length);
296 value = Contents.ToString().Substring(0, index);
297 Contents.Remove(0, index);
300 /* Strip whitespace */
301 value = value.Trim('\r', '\t', ' ');
304 w.BeginSection("include");
305 w.WritePair("name", value);
309 private void PARSE_FuncEnum(ParseWriter w)
311 w.BeginSection("funcenum");
313 /* Get the functag name */
314 NeedToken(LexToken.TOKEN_SYMBOL);
315 w.WritePair("name", LexString);
316 w.funcenumList.Add(LexString);
320 bool need_closebrace = true;
326 need_closebrace = false;
330 /* Start function section */
331 w.BeginSection("function");
333 if (MatchToken(LexToken.TOKEN_DOCBLOCK))
335 w.WritePair("doc", LexString);
338 /* Get the return tag */
339 if (MatchToken(LexToken.TOKEN_LABEL))
341 w.WritePair("return", LexString);
344 /* Get function type */
345 if (MatchToken(LexToken.TOKEN_PUBLIC))
347 w.WritePair("type", "public");
349 else if (MatchToken(LexToken.TOKEN_STOCK))
351 w.WritePair("type", "stock");
354 /* Parse the parameters */
357 /* End the section */
375 private void PARSE_FuncTag(ParseWriter w)
377 w.BeginSection("functag");
379 if (MatchToken(LexToken.TOKEN_PUBLIC))
381 w.WritePair("type", "public");
383 else if (MatchToken(LexToken.TOKEN_STOCK))
385 w.WritePair("type", "stock");
388 if (MatchToken(LexToken.TOKEN_LABEL))
390 w.WritePair("return", LexString);
393 /* Get the functag name */
394 NeedToken(LexToken.TOKEN_SYMBOL);
395 w.WritePair("name", LexString);
396 w.functagList.Add(LexString);
404 private void PARSE_Function(LexToken tok, ParseWriter w)
408 /* Get the return value */
409 if (MatchToken(LexToken.TOKEN_LABEL))
414 if (MatchToken(LexToken.TOKEN_OPERATOR))
416 /* Check if we're some sort of invalid function */
417 DiscardUntilCharOrComment('(', false);
418 ParseParameters(null);
419 if (tok == LexToken.TOKEN_STOCK)
423 else if (tok == LexToken.TOKEN_NATIVE || tok == LexToken.TOKEN_FORWARD)
427 DiscardUntilCharOrComment(';', false);
435 NeedToken(LexToken.TOKEN_SYMBOL);
438 PARSE_Function(tok, w, tag, name);
441 private void PARSE_Function(LexToken tok, ParseWriter w, string tag, string name)
443 if (tok == LexToken.TOKEN_FORWARD)
445 w.BeginSection("forward");
446 w.forwardList.Add(name);
448 else if (tok == LexToken.TOKEN_NATIVE)
450 w.BeginSection("native");
451 w.nativeList.Add(name);
453 else if (tok == LexToken.TOKEN_STOCK)
455 w.BeginSection("stock");
456 w.stockList.Add(name);
459 w.WritePair("name", name);
463 w.WritePair("return", tag);
468 if (tok == LexToken.TOKEN_STOCK)
474 /* Make sure there's a semicolon */
477 DiscardUntilCharOrComment(';', false);
485 private void ParseParameters(ParseWriter w)
496 /* Start the parameter */
499 w.BeginSection("parameter");
502 /* Check for a 'const' token */
503 if (MatchToken(LexToken.TOKEN_CONST) && w != null)
505 w.WritePair("const", "true");
508 /* Check if it's by reference */
509 if (MatchChar('&') && w != null)
511 w.WritePair("byref", "true");
514 /* Check for a tag */
515 if (MatchToken(LexToken.TOKEN_LABEL) && w != null)
517 w.WritePair("tag", LexString);
520 /* Get the symbol and write it */
521 if (MatchString(0, "...") && w != null)
523 w.WritePair("name", "...");
527 NeedToken(LexToken.TOKEN_SYMBOL);
530 w.WritePair("name", LexString);
534 /* Check if we have a default value yet */
535 bool default_value = false;
538 default_value = true;
540 else if (MatchChar('['))
547 temp = ConsumeUntilChar(']');
554 } while (MatchChar('['));
555 w.WritePair("decoration", extra);
558 /* If we have a default value, get it */
559 if (default_value || MatchChar('='))
561 if ((extra = ConsumeParamDefValue()) == null)
563 throw new ParseException("Expected a default value; found none");
567 w.WritePair("defval", extra);
571 /* End the parameter */
586 private void PARSE_Enum(ParseWriter w)
589 if (MatchToken(LexToken.TOKEN_SYMBOL))
594 w.BeginSection("enum");
595 w.WritePair("name", name);
596 w.BeginSection("properties");
598 w.enumList.Add(name);
601 bool more_entries = true;
602 bool need_closebrace = true;
604 if (MatchToken(LexToken.TOKEN_DOCBLOCK))
606 w.WritePair("doc", LexString);
613 need_closebrace = false;
616 NeedToken(LexToken.TOKEN_SYMBOL);
621 DiscardUntilCharOrComment(",\n}", true);
623 more_entries = MatchChar(',');
624 w.WritePair("name", name);
625 w.enumTypeList.Add(name);
626 if (MatchToken(LexToken.TOKEN_DOCBLOCK))
628 w.WritePair("doc", LexString);
630 } while (more_entries);
640 private void PARSE_Define(ParseWriter w)
642 /* first, get the symbol name */
643 NeedToken(LexToken.TOKEN_SYMBOL);
644 string name = LexString;
647 /* read until we hit the end of a line OR a comment */
649 for (int i = 0; i < Contents.Length; i++)
651 if (Contents[i] == '\n')
656 else if (CheckString(i, "/**"))
669 value = Contents.ToString().Substring(0, endpoint);
670 Contents.Remove(0, endpoint);
674 w.defineList.Add(name);
676 w.BeginSection("define");
677 w.WritePair("name", name);
678 w.WritePair("linetext", value);
682 /* Reads up to a character and discards all text before it.
684 private void DiscardUntilChar(char c)
686 int eol = Contents.ToString().IndexOf(c);
693 Contents.Remove(0, eol);
696 /* Same as DiscardUntilChar, except the discarded
697 * text is returned first.
699 private string ConsumeUntilChar(char c)
701 int eol = Contents.ToString().IndexOf(c);
708 string rest = Contents.ToString().Substring(0, eol);
709 Contents.Remove(0, eol);
714 * Returns a parameter's default value.
716 private string ConsumeParamDefValue()
718 int eol = FindEndOfExprLine(0);
724 string rest = Contents.ToString().Substring(0, eol);
725 Contents.Remove(0, eol);
730 * Discards all data up to a certain character, ignoring
731 * the contents, unless specified to stop at a doc block comment.
733 private void DiscardUntilCharOrComment(char c, bool stop_at_comment)
735 for (int i = 0; i < Contents.Length; i++)
737 if (Contents[i] == '\n')
741 else if (Contents[i] == c)
745 Contents.Remove(0, i);
749 else if (stop_at_comment && CheckString(i, "/**"))
753 Contents.Remove(0, i);
761 * Discards all data up to a certain character, ignoring
762 * the contents, unless specified to stop at a doc block comment.
764 private void DiscardUntilCharOrComment(string s, bool stop_at_comment)
766 for (int i = 0; i < Contents.Length; i++)
768 if (Contents[i] == '\n')
772 else if (s.Contains(Contents[i].ToString()))
776 Contents.Remove(0, i);
780 else if (stop_at_comment && CheckString(i, "/**"))
784 Contents.Remove(0, i);
792 * Finds the end of an expression
794 private int FindEndOfExprLine(int start)
796 for (int i = start; i < Contents.Length; i++)
798 if (Contents[i] == '{')
800 if ((i = FindEndOfConsumptionLine('}', start+1)) == -1)
802 throw new ParseException("Expected token: } (found none)");
805 else if (Contents[i] == ')' || Contents[i] == ',')
814 private int FindEndOfConsumptionLine(char c, int start)
816 for (int i = start; i < Contents.Length; i++)
818 if (Contents[i] == '{')
820 if ((i = FindEndOfConsumptionLine('}', i + 1)) == -1)
822 throw new ParseException("Expected token: } (found none)");
825 else if (Contents[i] == c)
834 private void Initialize()
836 /* Open file, read it, and close it */
837 StreamReader sr = File.OpenText(FileName);
838 Contents = new StringBuilder(sr.ReadToEnd());
841 /* Strip comments (may throw an exception) */
844 /* Lastly, initialize the tokenizer */
845 Tokenizer.Initialize();
848 /* Consumes a token iff it matches */
849 private bool MatchToken(LexToken token)
851 LexToken tok = lex();
860 /* Consumes a token and aborts the parser if there is no match */
861 private void NeedToken(LexToken token)
863 if (!MatchToken(token))
865 throw new ParseException("Expected token: " + token);
869 /* Consumes a character iff it matches */
870 private bool MatchChar(char c)
872 if (lex() != LexToken.TOKEN_CHARACTER)
885 /* Consumes a character or aborts the parser if there is no match */
886 private void NeedChar(char c)
890 throw new ParseException("Expected character: " + c);
894 /* Saves the lexer state so we don't reparse unnecessarily */
895 private void lexpush()
899 throw new ParseException("Lexer cannot be pushed twice");
904 /* Attempts to match a string against the next stream tokens.
905 * On success, the token stream is advanced.
907 private bool MatchString(int startPos, string str)
909 if (str.Length > Contents.Length - startPos)
913 for (int i = 0; i < str.Length; i++)
915 if (str[i] != Contents[startPos + i])
920 Contents.Remove(startPos, str.Length);
924 /* Attempts to match a string against the next stream tokens */
925 private bool CheckString(int startPos, string str)
927 if (str.Length > Contents.Length - startPos)
931 for (int i = 0; i < str.Length; i++)
933 if (str[i] != Contents[startPos + i])
941 /* Ignores a block starting and ending with { and } respectively */
942 private void ignore_block()
944 /* Hrm, this is tricky. First, expect an open brace. */
946 /* Consume the entire function by keeping track of braces */
948 bool in_string = false;
949 bool found_end = false;
950 for (int i = 0; i < Contents.Length; i++)
952 if (Contents[i] == '\n')
958 if (Contents[i] == '"')
962 else if (Contents[i] == '{')
966 else if (Contents[i] == '}')
970 throw new ParseException("Unable to find a matching open brace");
972 if (--numBraces == 0)
974 Contents.Remove(0, i + 1);
982 /* It is possible to be in a string and at the first character */
983 if (Contents[i] == '"' && Contents[i - 1] != '\\')
991 throw new ParseException("Unable to find a matching close brace");
995 /* Strips all non-doc C and C++ style comments. as well as unused pragmas */
996 private void preprocess()
998 if (Contents.Length == 1)
1003 bool in_comment = false;
1005 bool is_doc = false;
1006 bool is_newline = true;
1008 for (int i=0; i<Contents.Length; i++)
1010 if (Contents[i] == '\n')
1016 if (Contents[i] == '/')
1018 if (Contents[i+1] == '/')
1024 } else if (Contents[i+1] == '*') {
1025 /* Detect whether this is a doc comment or not */
1026 if (Contents.Length - i >= 3
1027 && Contents[i+2] == '*')
1036 if (in_comment && !is_doc)
1039 Contents[i+1] = ' '; /* Just for safety */
1042 else if (is_newline)
1044 if (Contents[i] == '#')
1046 if (!CheckString(i, "#define")
1047 && !CheckString(i, "#include"))
1057 else if (!Char.IsWhiteSpace(Contents[i]))
1065 if (Contents[i] == '*' && Contents[i+1] == '/')
1070 Contents[i+1] = ' ';
1073 i++; /* Don't look at the next character */
1075 else if (!is_doc && Contents[i] != '\n')
1096 throw new ParseException("Multi-line comment has no terminator");
1100 /* Strips whitespace */
1101 private void trim_whitespace_pre()
1104 while (spaces < Contents.Length
1105 && Contents[spaces] != '\0'
1106 && Char.IsWhiteSpace(Contents[spaces]))
1108 if (Contents[spaces] == '\n')
1116 Contents.Remove(0, spaces);
1120 private void preprocess_line()
1122 trim_whitespace_pre();
1125 private bool IsSymbolChar(char c, bool first)
1127 bool valid = (first ? Char.IsLetter(c) : Char.IsLetterOrDigit(c));
1128 return (valid || (c == '_'));
1131 private bool IsEscapedChar(char c)
1141 private bool IsNotKeywordChar(char c)
1143 return (Char.IsWhiteSpace(c)
1144 || (c == '-' || c == '+')
1145 || (c == '/' || c == '*')
1146 || (c == '(' || c == ')')
1147 || (c == '=' || c == '%')
1148 || (c == '>' || c == '<')
1149 || (c == '!' || c == '~')
1153 /* Consumes one lexical token and gathers information about it */
1154 private LexToken lex()
1162 /* Number of chars we will be deleting from the input */
1165 /* Clear our state */
1166 _LastToken = LexToken.TOKEN_NONE;
1168 /* Remove stuff we don't want from the line */
1171 if (Contents.Length < 1)
1173 _LastToken = LexToken.TOKEN_EOF;
1177 /* Get the token list */
1178 KeywordToken[] tokens = Tokenizer.Tokens;
1179 for (int i = 0; i < tokens.Length; i++)
1181 if (!CheckString(0, tokens[i].keyword))
1185 int len = tokens[i].keyword.Length;
1186 /* Now check to see what the next token is */
1187 if (Contents.Length == len
1188 || IsNotKeywordChar(Contents[len]))
1190 /* We have a token match! */
1191 _LastToken = tokens[i].token;
1199 if (_LastToken == LexToken.TOKEN_NONE)
1201 /* See if we can try to read a symbol */
1202 if (IsSymbolChar(Contents[0], true))
1205 while (Contents[characters] != '\0'
1206 && IsSymbolChar(Contents[characters], false))
1210 stripchars = characters;
1211 /* We're done! See what's next.. */
1212 if (Contents[characters] == ':')
1214 _LastToken = LexToken.TOKEN_LABEL;
1219 _LastToken = LexToken.TOKEN_SYMBOL;
1221 _LexString = Contents.ToString().Substring(0, characters);
1222 _LexChar = _LexString[0];
1224 else if (Contents[0] == '"')
1226 bool reached_end = false;
1227 StringBuilder builder = new StringBuilder();
1228 for (int i = 1; i < Contents.Length; i++)
1230 if (i < Contents.Length - 1
1231 && Contents[i] == '\\'
1232 && IsEscapedChar(Contents[i+1]))
1236 else if (Contents[i] == '"')
1241 builder.Append(Contents[i]);
1245 throw new ParseException("Expected end of string; none found");
1247 _LexString = builder.ToString();
1248 _LexChar = _LexString.Length > 0 ? _LexString[0] : '\0';
1250 else if (Contents[0] == '\'')
1253 if (Contents.Length < 3)
1255 throw new ParseException("Expected end of character; none found");
1257 else if (Contents[1] == '\\')
1259 if (Contents.Length < 4 || !IsEscapedChar(Contents[2]))
1261 throw new ParseException("Expected end of character; none found");
1263 if (Contents[2] == 'r')
1267 else if (Contents[2] == 'n')
1271 else if (Contents[2] == 't')
1275 else if (Contents[2] == 'v')
1279 else if (Contents[2] == '\\')
1283 else if (Contents[2] == '"')
1294 _LexString = c.ToString();
1296 _LastToken = LexToken.TOKEN_QUOTCHAR;
1298 else if (CheckString(0, "/**"))
1301 for (int i=3; i<Contents.Length-1; i++)
1303 if (Contents[i] == '\n')
1307 else if (Contents[i] == '*' && Contents[i+1] == '/')
1315 throw new ParseException("Expected end of multi-line comment; found end of file");
1317 /* We have to factor one more character in because we're zero based */
1319 _LastToken = LexToken.TOKEN_DOCBLOCK;
1320 _LexString = Contents.ToString().Substring(0, endpoint);
1321 _LexChar = _LexString[0];
1322 stripchars = endpoint;
1326 _LastToken = LexToken.TOKEN_CHARACTER;
1327 _LexString = Contents[0].ToString();
1328 _LexChar = Contents[0];
1336 Contents.Remove(0, stripchars);