using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static GASi.Lexer; namespace GASi { class Parser { public enum kControl { FUNC, IF, FOR, WHILE, SWITCH, CASE } private ScopeTable Globals = new ScopeTable(); private MethodTable Methods = new MethodTable(); private Stack ControlStack = new Stack(); private MethodTable.Method CurrentMethod = null; private List Tokens; private int TokenIterator; private static Dictionary> Patterns = new Dictionary>() { { "pre_inc", new List { new Pattern(Token.kType.PREPROC, "#include"), new Pattern(Token.kType.STRING) } }, { "fndecl", new List { new Pattern(Token.kType.TYPE), new Pattern(Token.kType.IDENTIFIER), new Pattern(Token.kType.LPAREN) } }, { "fnparams", new List { new Pattern(Token.kType.TYPE), new Pattern(Token.kType.IDENTIFIER), new Pattern(Token.kType.COMMA), } }, { "ctrlstart", new List { new Pattern(Token.kType.RPAREN), new Pattern(Token.kType.LBRACE) } } }; public Parser(List Tokens) { this.Tokens = Tokens; } public List Parse() { var ret = new List(); TokenIterator = 0; while(TokenIterator < Tokens.Count) { var expr = new Expression(); var token = Tokens[TokenIterator]; if(CurrentMethod == null) { // if we're in the global scope switch(token.Type) { case Token.kType.PREPROC: // preprocessor definition // TODO add more preproc defs if(!CheckPattern(Patterns["pre_inc"])) Transpiler.Exception("Invalid preprocessor directive '" + token.Value.Substring(1) + "'", 0, 0); expr.Tokens = Iterate(2); expr.Type = Expression.kType.PREPROC; break; case Token.kType.TYPE: // function declaration expr = ParseFunctionDeclaration(); break; default: Transpiler.Exception("Unexpected token '" + token.Value + "' of type '" + token.Type.ToString() + "' in global scope", 0, 0); break; } } else { // if we're in a local scope } ret.Add(expr); } return null; } public Expression ParseFunctionDeclaration() { var expr = new Expression(); if(CheckPattern(Patterns["fndecl"])) { expr.Tokens = Iterate(Patterns["fndecl"].Count); var functionName = expr.Tokens[1].Value; expr.ControlName = functionName; expr.ControlType = kControl.FUNC; expr.Type = Expression.kType.CTRLSTART; var method = new MethodTable.Method() { Name = functionName, ReturnType = Conversion.StringToType(expr.Tokens[0].Value) }; int argTokenCount; if((argTokenCount = CheckPatternLoop(Patterns["fnparams"], Token.kType.RPAREN)) == -1) Transpiler.Exception("Invalid token found in argument list of function '"+ functionName +"'", 0, 0); // Now that we know that it follows the pattern, we must check for early truncation. // Note the standard form for an argument list: // TOKENS: [TYPE IDENTIFIER COMMA]*(n-1) + [TYPE IDENTIFIER] // Where n is the function's number of arguments. // The last argument should not be followed by a comma, nor should be incomplete // (that is to say, only a type identifier by itself), so we can check the // length of the argument list passed and treat it as such: // TOKEN COUNT = 3*(n-1) + 2 // This follows from the token formula state above. // If you add one to this token count, you end up with this: // TOKEN COUNT = 3*(n-1) + 3 = 3n - 3 + 3 = 3n // Which can then be confirmed using the remainder of dividing the argument list // by three and determining if it is zero. This will only be true for a well- // formed argument list. if((argTokenCount + 1) % 3 != 0) Transpiler.Exception("Malformed final argument in argument list of function '"+ functionName +"'", 0, 0); var args = Iterate(argTokenCount); var arg = new MethodTable.Method.Argument(); for(int i = 0; i < args.Count; ++i) { var token = args[i]; switch(i%3) { case 0: arg = new MethodTable.Method.Argument { Type = Conversion.StringToType(token.Value) }; if(arg.Type == kType.VOID) Transpiler.Exception("Function '" + functionName + "' cannot have arguments with a void type", 0, 0); break; case 1: arg.Name = token.Value; method.Arguments.Add(arg); break; } } expr.Tokens.AddRange(args); if(!CheckPattern(Patterns["ctrlstart"])) Transpiler.Exception("Malformed tokens after argument list in function declaration '"+ functionName +"'", 0, 0); expr.Tokens.AddRange(Iterate(Patterns["ctrlstart"].Count)); Methods.AddMethod(method); } else Transpiler.Exception("Invalid token found during function declaration", 0, 0); return expr; } private bool CheckPattern(List pattern) { for(var i = TokenIterator; i < TokenIterator + pattern.Count; ++i) { if(i >= Tokens.Count) Transpiler.Exception("Unexpected EOF when checking for token pattern", 0, 0); var j = i - TokenIterator; if(Tokens[i].Type != pattern[j].Type || (pattern[j].Value != null && Tokens[i].Value != pattern[j].Value)) return false; } return true; } private int CheckPatternLoop(List pattern, Token.kType terminator) { var i = TokenIterator; for(; ; ++i) { if(i >= Tokens.Count) Transpiler.Exception("Unexpected EOF when checking for repeated token pattern", 0, 0); var j = (i - TokenIterator) % pattern.Count; if(Tokens[i].Type == terminator) break; if(Tokens[i].Type != pattern[j].Type || (pattern[j].Value != null && Tokens[i].Value != pattern[j].Value)) return -1; } return i - TokenIterator; } private List Extract(int length) { if(TokenIterator + length > Tokens.Count) Transpiler.Exception("Attempted to read expected tokens past EOF", 0, 0); return Tokens.GetRange(TokenIterator, length); } private void Skip(int count) { TokenIterator += count; } private List Iterate(int count) { var tmp = Extract(count); Skip(count); return tmp; } private int CountUntilNext(Token.kType type, Token.kType? nester = null, bool allowNesting = true) { int depth = 1, count = 0; for(; depth > 0; ++count) { if(TokenIterator + count >= Tokens.Count) Transpiler.Exception("Closing token '"+ type.ToString() +"' for appropriate scope not found", 0, 0); var token = Tokens[TokenIterator + count]; if(token.Type == type) --depth; else if(token.Type == nester) { if(allowNesting) ++depth; else Transpiler.Exception("Invalid token of type '" + token.Type + "' found when nesting is not allowed", 0, 0); } } return count + 1; } public class Pattern { public Token.kType Type { get; set; } public string Value { get; set; } public Pattern(Token.kType type, string value = null) { Type = type; Value = value; } } public class Expression { public enum kType { PREPROC, CTRLSTART, BREAK, CTRLEND, LVAL, ASSIGN, RVAL } public kType Type { get; set; } public kControl ControlType { get; set; } public string ControlName { get; set; } public List Tokens { get; set; } } } }