// //You can use the following additional attributes as you write your tests: // //Use ClassInitialize to run code before running the first test in the class //[ClassInitialize()] //public static void MyClassInitialize(TestContext testContext) //{ //} // //Use ClassCleanup to run code after all tests in a class have run //[ClassCleanup()] //public static void MyClassCleanup() //{ //} // //Use TestInitialize to run code before running each test //[TestInitialize()] //public void MyTestInitialize() //{ //} // //Use TestCleanup to run code after each test has run //[TestCleanup()] //public void MyTestCleanup() //{ //} // #endregion void ValidateDebug(Token debug, string prefix, string value, long startLine, long startPos, long endLine, long endPos) { Assert.AreEqual(value, debug.Value, prefix + ".Debug.Value"); Assert.AreEqual(startLine, debug.StartLine, prefix + ".Debug.StartLine"); Assert.AreEqual(startPos, debug.StartPos, prefix + ".Debug.StartPos"); Assert.AreEqual(endLine, debug.EndLine, prefix + ".Debug.StartLine"); Assert.AreEqual(endPos, debug.EndPos, prefix + ".Debug.EndPos"); }
/// <summary> /// Reads a table from the input. Input must be either on the starting '{'. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="token">The token to append the read Tokenm to.</param> /// <returns>The table that was read.</returns> protected virtual TableItem ReadTable(ITokenizer input, ref Token token) { Token debug = input.Read(); if (debug.Value != "{") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, debug.Value, "table", "{"), input.Name, debug); TableItem ret = new TableItem(); Token last = input.Peek(); while (last.Value != "}") { if (last.Value == "[") { Read(input, ref debug); // read the "[" var temp = ReadExp(input, ref debug); if (temp == null) throw new SyntaxException(string.Format(Resources.InvalidDefinition, "table"), input.Name, debug); // read ']' last = Read(input, ref debug); if (last.Value != "]") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "table", "]"), input.Name, last); // read '=' last = Read(input, ref debug); if (last.Value != "=") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "table", "="), input.Name, last); // read the expression var val = ReadExp(input, ref debug); if (val == null) throw new SyntaxException(string.Format(Resources.InvalidDefinition, "table"), input.Name, debug); ret.AddItem(temp, val); } else { var val = ReadExp(input, ref debug); if (input.Peek().Value == "=") { Read(input, ref debug); // read '=' NameItem name = val as NameItem; if (name == null) throw new SyntaxException(string.Format(Resources.InvalidDefinition, "table"), input.Name, debug); // read the expression var exp = ReadExp(input, ref debug); ret.AddItem(new LiteralItem(name.Name), exp); } else { ret.AddItem(null, val); } } if (input.Peek().Value != "," && input.Peek().Value != ";") break; else Read(input, ref debug); last = input.Peek(); } // end While Token end = Read(input, ref debug); // read the "}" if (end.Value != "}") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, end.Value, "table", "}"), input.Name, end); ret.Debug = debug; token.Append(debug); return ret; }
/// <summary> /// Creates a new SyntaxException with the given message. /// </summary> /// <param name="message">The cause of the error.</param> /// <param name="source">The source token that caused the error.</param> /// <param name="inner">The inner exception.</param> public SyntaxException(string message, Token source, Exception inner) : this(message, null, source, inner) { }
/// <summary> /// Reads a single token from the input stream. /// </summary> /// <returns>The token that was read or a null string token.</returns> /// <remarks> /// If it is at the end of the enumeration, it will return a token with /// a null string, the values of the other members are unspecified. /// </remarks> /// <exception cref="ModMaker.Lua.Parser.SyntaxException">If there is /// an error in the syntax of the input.</exception> protected virtual Token InternalRead() { start: ReadWhitespace(); Token ret = new Token(); ret.StartPos = Position; ret.StartLine = Line; string last = ReadElement(); if (last == null) return new Token(); // goto the start if this is a comment if (last == "-") { last = PeekElement(); if (last == "-") { ReadElement(); ReadComment(); goto start; } else last = "-"; } // read an identifier (e.g. 'foo' or '_cat'). else if (char.IsLetter(last, 0) || last == "_") { StringBuilder str = new StringBuilder(); bool over = false; str.Append(last); last = PeekElement(); while (last != null && (char.IsLetterOrDigit(last, 0) || last == "_" || last == "`")) { if (over) { if (last == "`") throw new SyntaxException(Resources.OverloadOneGrave, Name, ret); if (!char.IsDigit(last, 0)) throw new SyntaxException(Resources.OnlyNumbersInOverload, Name, ret); } else if (last == "`") { over = true; } ReadElement(); str.Append(last); last = PeekElement(); } last = str.ToString(); } // read indexer, concat, and ... else if (last == ".") { if (PeekElement() == ".") { ReadElement(); // read "." if (PeekElement() == ".") { ReadElement(); // read "." last = "..."; } else last = ".."; } } // read a number else if (char.IsNumber(last, 0) || last == CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) { return ReadNumber(last); } // read a literal string else if (last == "\"" || last == "'") { return ReadString(last == "'" ? -1 : -2, ret.StartPos, ret.StartLine); } // handle "[" else if (last == "[") { last = PeekElement(); if (last == "[" || last == "=") { int dep = 0; while (last == "=") { ReadElement(); // read "=" dep++; last = PeekElement(); } if (last != "[") throw new SyntaxException(string.Format(Resources.InvalidDefinition, "long string"), Name, ret); ReadElement(); // read "[" if (PeekElement() == "\n") ReadElement(); return ReadString(dep, ret.StartPos, ret.StartLine); } else last = "["; } // read :: else if (last == ":") { if (PeekElement() == ":") { ReadElement(); // read ":" last = "::"; } } // read comparison operatos else if (last == ">" || last == "<" || last == "~" || last == "=") { if (PeekElement() == "=") { last += "="; ReadElement(); // read "=" } else if (last == "~") throw new SyntaxException("Invalid token '~'.", Name, ret); } // otherwise simply return the read text-element ret.EndPos = Position; ret.EndLine = Line; ret.Value = last; return ret; }
/// <summary> /// Helper function that reads a number from the input. The resulting /// token should start with '&' if the number is in hex format, /// otherwise the number should be in a parseable double format. /// </summary> /// <param name="last">The first character of the number.</param> /// <returns>The token that was read. It should start with '&' if /// the number is hex.</returns> protected virtual Token ReadNumber(string last) { Token ret = new Token(); ret.StartPos = Position - (last == null ? 0 : last.Length); ret.StartLine = Line; // this version does nothing to check for a valid number, that is done in the parser. // this only supports 0xNNN notation for hexadecimal numbers (where NNN is a char.IsNumber char or a-f or A-F). StringBuilder str = new StringBuilder(); CultureInfo ci = CultureInfo.CurrentCulture; string l = last; bool hex = false; if (last == "0" && PeekElement().ToLowerInvariant() == "x") { hex = true; str.Append("&"); ReadElement(); // read the 'x' last = PeekElement(); } else { str.Append(last); last = PeekElement(); } while (last != null && (char.IsNumber(last, 0) || (hex && ((last[0] >= 'a' && last[0] <= 'f') || (last[0] >= 'A' && last[0] <= 'F'))) || (!hex && (last == ci.NumberFormat.NumberDecimalSeparator || last == "-" || (l != "." && last == "e"))))) { ReadElement(); str.Append(last); l = last; last = PeekElement(); } ret.EndLine = Line; ret.EndPos = Position; ret.Value = str.ToString(); return ret; }
/// <summary> /// Helper function that reads a comment from the input, it assumes that /// the first two chars '--' have been already been read and the input /// is on the next char. /// </summary> /// <returns>The token that holds the comments, this is unlikely to be /// used except for debugging.</returns> /// <exception cref="ModMaker.Lua.Parser.SyntaxException">If there is /// an error in the syntax of the input.</exception> protected virtual Token ReadComment() { Token ret = new Token(); StringBuilder build = new StringBuilder(); build.Append("--"); ret.StartLine = Line; ret.StartPos = Position; int depth = -1; string temp; if (PeekElement() == "[") { depth = 0; build.Append(ReadElement()); while ((temp = ReadElement()) != null) { build.Append(temp); if (temp == "=") depth++; else if (temp == "\n") { ret.EndLine = Line; ret.EndPos = Position; ret.Value = build.ToString(); return ret; } else { if (temp != "[") depth = -1; break; } } } int curDepth = -1; while ((temp = ReadElement()) != null) { build.Append(temp); if (depth == -1) { if (temp == "\n") break; } else { if (curDepth != -1) { if (temp == "]") { if (curDepth == depth) break; else curDepth = -1; } else if (temp == "=") curDepth++; else curDepth = -1; } else if (temp == "]") curDepth = 0; } } ret.EndLine = Line; ret.EndPos = Position; ret.Value = build.ToString(); if (PeekElement() == null && depth != curDepth) throw new SyntaxException(string.Format(Resources.MissingEnd, "long comment"), ret); return ret; }
/// <summary> /// Reads a prefix-expression from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="token">The token to append the total token onto.</param> /// <returns>The expression that was read.</returns> protected virtual IParseExp ReadPrefixExp(ITokenizer input, ref Token token) { Stack<UnaryInfo> ex = new Stack<UnaryInfo>(); IParseExp o = null; Token last, debug = input.Peek(); debug.Value = ""; // check for unary operators last = input.Peek(); while (last.Value == "-" || last.Value == "not" || last.Value == "#") { Read(input, ref debug); if (last.Value == "-") { ex.Push(new UnaryInfo(1, last.StartPos, last.StartLine)); } else if (last.Value == "not") { ex.Push(new UnaryInfo(2, last.StartPos, last.StartLine)); } else { Contract.Assert(last.Value == "#"); ex.Push(new UnaryInfo(3, last.StartPos, last.StartLine)); } last = input.Peek(); } // check for literals last = input.Peek(); int over = -1; if (last.Value != null) { NumberFormatInfo ni = CultureInfo.CurrentCulture.NumberFormat; if (last.Value != "..." && (char.IsNumber(last.Value, 0) || last.Value.StartsWith(ni.NumberDecimalSeparator, StringComparison.CurrentCulture))) { Read(input, ref debug); // read the number. try { o = new LiteralItem(double.Parse(last.Value, CultureInfo.CurrentCulture)) { Debug = last }; } catch (FormatException e) { throw new SyntaxException(Resources.BadNumberFormat, input.Name, last, e); } } else if (last.Value.StartsWith("&", StringComparison.Ordinal)) { Read(input, ref debug); try { o = new LiteralItem(Convert.ToDouble(long.Parse(last.Value.Substring(1), NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture))) { Debug = last }; } catch (FormatException e) { throw new SyntaxException(Resources.BadNumberFormat, input.Name, last, e); } } else if (last.Value.StartsWith("\"", StringComparison.Ordinal)) { Read(input, ref debug); o = new LiteralItem(last.Value.Substring(1)) { Debug = last }; } else if (last.Value.StartsWith("{", StringComparison.Ordinal)) { o = ReadTable(input, ref debug); } else if (last.Value == "(") { Read(input, ref debug); o = ReadExp(input, ref debug); last = Read(input, ref debug); if (last.Value != ")") throw new SyntaxException(string.Format(Resources.TokenInvalidExpecting, last.Value, "expression", ")"), input.Name, last); } else if (last.Value == "true") { Read(input, ref debug); o = new LiteralItem(true) { Debug = last }; } else if (last.Value == "false") { Read(input, ref debug); o = new LiteralItem(false) { Debug = last }; } else if (last.Value == "nil") { Read(input, ref debug); o = new LiteralItem(null) { Debug = last }; } else if (last.Value == "function") { o = ReadFunctionHelper(input, ref debug, false, false); } else { // allow for specifying overloads on global variables if (last.Value.IndexOf('`') != -1) { if (!int.TryParse(last.Value.Substring(last.Value.IndexOf('`') + 1), out over)) throw new InvalidOperationException(Resources.OnlyNumbersInOverload); last.Value = last.Value.Substring(0, last.Value.IndexOf('`')); } Read(input, ref debug); o = new NameItem(last.Value) { Debug = last }; } } // read function calls and indexers { string inst = null; bool cont = true; while (cont) { last = input.Peek(); last.Value = last.Value ?? ""; if (last.Value == ".") { Read(input, ref debug); if (over != -1) throw new SyntaxException(Resources.FunctionCallAfterOverload, input.Name, last); if (inst != null) throw new SyntaxException(Resources.IndexerAfterInstance, input.Name, last); last = Read(input, ref debug); // allow for specifying an overload if (last.Value.IndexOf('`') != -1) { if (!int.TryParse(last.Value.Substring(last.Value.IndexOf('`') + 1), out over)) throw new InvalidOperationException(Resources.OnlyNumbersInOverload); last.Value = last.Value.Substring(0, last.Value.IndexOf('`')); } if (!IsName(last.Value)) throw new SyntaxException(string.Format(Resources.TokenNotAName, "indexer", last.Value), input.Name, last); if (!(o is IParsePrefixExp)) throw new SyntaxException(Resources.IndexAfterExpression, input.Name, last); o = new IndexerItem(o, new LiteralItem(last.Value) { Debug = last }) { Debug = debug }; } else if (last.Value == ":") { Read(input, ref debug); if (over != -1) throw new SyntaxException(Resources.FunctionCallAfterOverload, input.Name, last); if (inst != null) throw new SyntaxException(Resources.OneInstanceCall, input.Name, last); inst = Read(input, ref debug).Value; if (!IsName(inst)) throw new SyntaxException(string.Format(Resources.TokenNotAName, "indexer", last.Value), input.Name, last); } else if (last.Value == "[") { Read(input, ref debug); if (over != -1) throw new SyntaxException(Resources.FunctionCallAfterOverload, input.Name, last); if (inst != null) throw new SyntaxException(Resources.IndexerAfterInstance, input.Name, last); var temp = ReadExp(input, ref debug); last = Read(input, ref debug); o = new IndexerItem(o, temp) { Debug = debug }; if (last.Value != "]") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "indexer", "]"), input.Name, last); } else if (last.Value.StartsWith("\"", StringComparison.Ordinal)) { Read(input, ref debug); FuncCallItem temp = new FuncCallItem(o, inst, over) { Debug = debug }; o = temp; temp.AddItem(new LiteralItem(last.Value.Substring(1)), false); inst = null; over = -1; } else if (last.Value == "{") { var temp = ReadTable(input, ref debug); FuncCallItem func = new FuncCallItem(o, inst, over) { Debug = debug }; o = func; func.AddItem(temp, false); inst = null; over = -1; } else if (last.Value == "(") { Read(input, ref debug); FuncCallItem func = new FuncCallItem(o, inst, over); o = func; inst = null; over = -1; while (input.Peek().Value != ")" && input.Peek().Value != null) { bool? byRef = null; if (input.Peek().Value == "@") { byRef = false; Read(input, ref debug); } else if (input.Peek().Value == "ref") { Read(input, ref debug); if (input.Peek().Value == "(") { Read(input, ref debug); byRef = true; } else byRef = false; } var temp = ReadExp(input, ref debug); if (byRef != null && !(temp is NameItem) && !(temp is IndexerItem)) throw new SyntaxException(Resources.OnlyVarByReference, input.Name, last); if (temp == null) throw new SyntaxException(string.Format(Resources.InvalidDefinition, "function call"), input.Name, last); func.AddItem(temp, byRef != null); if (byRef == true && (last = input.Read()).Value != ")") throw new SyntaxException(Resources.RefOneArgument, input.Name, last); if (input.Peek().Value == ",") Read(input, ref debug); else if (input.Peek().Value == ")") break; else throw new SyntaxException(string.Format(Resources.TokenInvalidExpecting2, input.Peek().Value, "function call", ",", ")"), input.Name, last); } if (input.Peek() == null) throw new SyntaxException(string.Format(Resources.UnexpectedEOF, "function call"), input.Name, last); Read(input, ref debug); func.Debug = debug; } else { if (inst != null) throw new SyntaxException(Resources.InstanceMissingArgs, input.Name, last); if (over != -1) throw new SyntaxException(Resources.OverloadMissingArgs, input.Name, last); cont = false; } } } // read exponents // HACK: This is needed here because the power operator has // higher precedence than the unary operators. Rather than // have unary operators handled in ReadExp, they are handled // so exponents need to be handled before we apply the // unary operators. if (input.Peek().Value == "^") { Read(input, ref debug); var temp = ReadPrefixExp(input, ref debug); BinOpItem item = new BinOpItem(o, BinaryOperationType.Power, temp) { Debug = debug }; o = item; } // now apply the unary operators while (ex.Count > 0) { var loc = ex.Pop(); Token tok = new Token(debug.Value, loc.StartPos, debug.EndPos, loc.StartLine, debug.EndLine); switch (loc.Version) { case 1: // neg if (o is LiteralItem) { object oo = (o as LiteralItem).Value; if (!(oo is double)) throw new SyntaxException(Resources.InvalidUnary, input.Name, debug); o = new LiteralItem(-(double)oo) { Debug = tok }; } else o = new UnOpItem(o, UnaryOperationType.Minus) { Debug = tok }; break; case 2: // not o = new UnOpItem(o, UnaryOperationType.Not) { Debug = tok }; break; case 3: // len o = new UnOpItem(o, UnaryOperationType.Length) { Debug = tok }; break; } } // finaly return token.Append(debug); return o; }
/// <summary> /// Parses the given Lua code into a IParseItem tree. /// </summary> /// <param name="input">The Lua code to parse.</param> /// <param name="name">The name of the chunk, used for exceptions.</param> /// <param name="hash">The hash of the Lua code, can be null.</param> /// <returns>The code as an IParseItem tree.</returns> /// <remarks>Simply calls Parse(Tokenizer, string, bool) with force:false.</remarks> public IParseItem Parse(ITokenizer input, string name, string hash) { if (input == null) throw new ArgumentNullException("input"); // check if the chunk is already loaded if (UseCache) { lock (_lock) { if (_cache != null && hash != null && _cache.ContainsKey(hash)) return _cache[hash]; } } // parse the chunk Token temp = new Token(); IParseItem read = ReadBlock(input, ref temp); Token end = Read(input, ref temp); if (end.Value != null) throw new SyntaxException(string.Format(Resources.TokenEOF, end.Value), input.Name, end); // store the loaded chunk in the cache lock (_lock) { if (_cache != null && hash != null) _cache[hash] = read; } return read; }
/// <summary> /// Reads a for statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadFor(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'for' if (debug.Value != "for") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "for", "ReadFor")); // read a name var name = Read(input, ref debug); if (!IsName(name.Value)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "for", name.Value), input.Name, name); if (_reserved.Contains(name.Value)) throw new SyntaxException( string.Format(Resources.TokenReserved, name.Value), input.Name, name); // numeric for if (input.Peek().Value == "=") { var ret = ReadNumberFor(input, ref debug, name); prev.Append(debug); return ret; } // generic for statement else { var ret = ReadGenericFor(input, ref debug, name); prev.Append(debug); return ret; } }
/// <summary> /// Reads a normal function statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadFunction(ITokenizer input, ref Token prev) { return ReadFunctionHelper(input, ref prev, true, false); }
/// <summary> /// Reads a return statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual ReturnItem ReadReturn(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'return' ReturnItem r = new ReturnItem(); if (debug.Value != "return") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "return", "ReadReturn")); var name = input.Peek(); if (name.Value != "end" && name.Value != "until" && name.Value != "elseif" && name.Value != "else") { r.AddExpression(ReadExp(input, ref debug)); while (input.Peek().Value == ",") { Read(input, ref debug); // read ',' r.AddExpression(ReadExp(input, ref debug)); } if (input.Peek().Value == ";") { Read(input, ref debug); // read ';' } // look at the next token for validation but keep it in the // reader for the parrent. name = input.Peek(); if (name.Value != "end" && name.Value != "until" && name.Value != "elseif" && name.Value != "else" && !IsNullOrWhiteSpace(name.Value)) throw new SyntaxException( Resources.ReturnAtEnd, input.Name, debug); } prev.Append(debug); r.Debug = debug; return r; }
/// <summary> /// Reads a class statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadClass(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'class' string sname = null; List<string> imp = new List<string>(); if (debug.Value != "class") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "class", "ReadClass")); if (input.Peek().Value.StartsWith("'", StringComparison.Ordinal) || input.Peek().Value.StartsWith("\"", StringComparison.Ordinal)) { var name = Read(input, ref debug); sname = name.Value.Substring(1); if (input.Peek().Value == "(") { Read(input, ref debug); // read '(' while (input.Peek().Value != ")") { // read the name name = Read(input, ref debug); if (!IsName(name.Value)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "class", name.Value), input.Name, name); imp.Add(name.Value); // read ',' name = Read(input, ref debug); if (name.Value != ",") throw new SyntaxException( string.Format(Resources.TokenInvalid, name.Value, "class"), input.Name, name); } Read(input, ref debug); // read ')' } } else { var name = Read(input, ref debug); sname = name.Value; if (!IsName(sname)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "class", name.Value), input.Name, name); if (input.Peek().Value == ":") { do { // simply include the '.' in the name. string n = ""; do { Read(input, ref debug); // read ':' or ',' n += (n == "" ? "" : ".") + Read(input, ref debug).Value; } while (input.Peek().Value == "."); imp.Add(n); } while (input.Peek().Value == ","); } } prev.Append(debug); return new ClassDefItem(sname, imp.ToArray()) { Debug = debug }; }
/// <summary> /// Reads a local statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadLocal(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'local' if (debug.Value != "local") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "local", "ReadLocal")); var name = input.Peek(); if (name.Value == "function") { prev.Append(debug); return ReadFunctionHelper(input, ref prev, true, true); } else { Read(input, ref debug); // read name if (!IsName(name.Value)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "local", name.Value), input.Name, name); if (_reserved.Contains(name.Value)) throw new SyntaxException( string.Format(Resources.TokenReserved, name.Value), input.Name, name); var i = ReadAssignment(input, ref debug, true, new NameItem(name.Value) { Debug = name }); prev.Append(debug); return i; } }
/// <summary> /// Reads a token from the tokenizer and appends the read /// value to the given token. /// </summary> /// <param name="input">Where to get the input from.</param> /// <param name="token">A token to append the read token to.</param> /// <returns>The token that was read.</returns> protected static Token Read(ITokenizer input, ref Token token) { Token ret = input.Read(); token.Append(ret); return ret; }
/// <summary> /// Reads a block of code from the input. Any end tokens /// should not be read and are handled by the parrent call /// (e.g. 'end' or 'until'). /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append the total token onto.</param> /// <returns>The item that was read.</returns> protected virtual BlockItem ReadBlock(ITokenizer input, ref Token prev) { BlockItem ret = new BlockItem(); Token total = input.Peek(); total.Value = ""; Token name; while ((name = input.Peek()).Value != null) { if (Functions.ContainsKey(name.Value)) { var temp = Functions[name.Value](input, ref total); ret.AddItem(temp); } else if (name.Value == "return") { ret.Return = ReadReturn(input, ref total); return ret; } else if (name.Value == ";") { Read(input, ref total); // read ';' } else if (name.Value == "end" || name.Value == "else" || name.Value == "elseif" || name.Value == "until") { // don'type read as it will be handled by the parrent prev.Append(total); // don't add 'end' to the prev ret.Debug = total; // or the current block, this return ret; // end belongs to the parrent. } else { Token debug = name; debug.Value = ""; var exp = ReadExp(input, ref debug); if (exp is FuncCallItem) { (exp as FuncCallItem).Statement = true; ret.AddItem((FuncCallItem)exp); } else if (exp is LiteralItem) { throw new SyntaxException( "A literal is not a variable.", input.Name, debug); } else if (exp is NameItem || exp is IndexerItem) { var i = ReadAssignment(input, ref debug, false, (IParseVariable)exp); ret.AddItem(i); } else throw new SyntaxException( string.Format(Resources.TokenStatement, name.Value), input.Name, debug); total.Append(debug); } } // end While // only gets here if this is the global function ret.Debug = total; ret.Return = ret.Return ?? new ReturnItem(); return ret; }
/// <summary> /// Reads an assignment statement from the input. The input is currently /// after the first name, on the comma or equal sign. The debug token /// contains the name and should contain the entire statement. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="debug">Currently contains the first name, and after /// should contain the entire statement.</param> /// <param name="local">True if this is a local definition, otherwise false.</param> /// <param name="variable">The first variable that was read.</param> /// <returns>The statement that was read.</returns> protected virtual AssignmentItem ReadAssignment(ITokenizer input, ref Token debug, bool local, IParseVariable variable) { // read each of the variable names AssignmentItem assign = new AssignmentItem(local); assign.AddName(variable); while (input.Peek().Value == ",") { Read(input, ref debug); // read ',' // read the left-hand-expression var exp = ReadExp(input, ref debug); if ((local && !(exp is NameItem)) || (!local && !(exp is IParseVariable))) throw new SyntaxException(Resources.NameOrExpForVar, input.Name, debug); assign.AddName((IParseVariable)exp); } // read the initial values if (input.Peek().Value == "=") { Read(input, ref debug); // read '=' assign.AddItem(ReadExp(input, ref debug)); while (input.Peek().Value == ",") { Read(input, ref debug); // read ',' assign.AddItem(ReadExp(input, ref debug)); } } else if (!local) throw new SyntaxException( string.Format(Resources.InvalidDefinition, "assignment"), input.Name, debug); assign.Debug = debug; return assign; }
/// <summary> /// Reads part of a generic for loop from the input. The input is /// currently on the token after the first name and debug contains /// the parts read for the 'for' loop. 'name' contains the name of /// the first variable. /// </summary> /// <param name="input">Where to read input from/</param> /// <param name="debug">The token that currently holds what was read /// so far in the for statement and should after contain the entire loop.</param> /// <param name="name">The token that contains the name of the variable.</param> /// <returns>The loop object that was read.</returns> protected virtual ForGenItem ReadGenericFor(ITokenizer input, ref Token debug, Token name) { // read the variables List<NameItem> names = new List<NameItem>(); names.Add(new NameItem(name.Value) { Debug = name }); while (input.Peek().Value == ",") { Read(input, ref debug); // read ',' // read the name name = Read(input, ref debug); if (!IsName(name.Value)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "for", name.Value), input.Name, name); if (_reserved.Contains(name.Value)) throw new SyntaxException( string.Format(Resources.TokenReserved, name.Value), input.Name, name); names.Add(new NameItem(name.Value) { Debug = name }); } // check for 'in' name = Read(input, ref debug); if (name.Value != "in") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "for", "in"), input.Name, name); // read the expression-list ForGenItem f = new ForGenItem(names); f.AddExpression(ReadExp(input, ref debug)); while (input.Peek().Value == ",") { Read(input, ref debug); // read "," f.AddExpression(ReadExp(input, ref debug)); } // check for 'do' name = Read(input, ref debug); if (name.Value != "do") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "for", "do"), input.Name, name); // read the chunk f.Block = ReadBlock(input, ref debug); // read 'end' name = Read(input, ref debug); if (name.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "for", "end"), input.Name, name); f.Debug = debug; return f; }
/// <summary> /// Reads an if statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadIf(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'if' IfItem i = new IfItem(); if (debug.Value != "if") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "if", "ReadIf")); // read the initial expression i.Exp = ReadExp(input, ref debug); // read 'then' var name = Read(input, ref debug); if (name.Value != "then") throw new SyntaxException( string.Format(Resources.TokenInvalid, name.Value, "if"), input.Name, debug); // read the block var readBlock = ReadBlock(input, ref debug); i.Block = readBlock; // handle elseif(s) while ((name = input.Peek()).Value == "elseif") { Read(input, ref debug); // read 'elseif' // read the expression var readExp = ReadExp(input, ref debug); // read 'then' name = Read(input, ref debug); if (name.Value != "then") throw new SyntaxException( string.Format(Resources.TokenInvalid, name.Value, "elseif"), input.Name, debug); // read the block readBlock = ReadBlock(input, ref debug); i.AddElse(readExp, readBlock); } // handle else if (name.Value != "else" && name.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalid, name.Value, "if"), input.Name, debug); if (name.Value == "else") { Read(input, ref debug); // read 'else' // read the block readBlock = ReadBlock(input, ref debug); i.ElseBlock = readBlock; } // read 'end' name = Read(input, ref debug); if (name.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalid, name.Value, "if"), input.Name, debug); prev.Append(debug); i.Debug = debug; return i; }
/// <summary> /// Reads part of a numerical for loop from the input. The input is /// currently on the equals sign '=' and the debug token currently /// contains the parts read for the 'for' loop. 'name' contains the /// name of the variable. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="debug">The token that currently holds what was read /// so far in the for statement and should after contain the entire loop.</param> /// <param name="name">The token that contains the name of the variable.</param> /// <returns>The loop object that was read.</returns> protected virtual ForNumItem ReadNumberFor(ITokenizer input, ref Token debug, Token name) { // read "=" var temp = Read(input, ref debug); if (temp.Value != "=") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "=", "ReadNumberFor")); // get the 'start' value var start = ReadExp(input, ref debug); // read ',' temp = Read(input, ref debug); if (temp.Value != ",") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, temp.Value, "for", ","), input.Name, temp); // get the 'limit' var limit = ReadExp(input, ref debug); // read ',' IParseExp step = null; if (input.Peek().Value == ",") { Read(input, ref debug); // read the 'step' step = ReadExp(input, ref debug); } ForNumItem i = new ForNumItem(new NameItem(name.Value) { Debug = name }, start, limit, step); // check for 'do' name = Read(input, ref debug); if (name.Value != "do") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "for", "do"), input.Name, name); // read the block i.Block = ReadBlock(input, ref debug); // read 'end' name = Read(input, ref debug); if (name.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "for", "end"), input.Name, name); i.Debug = debug; return i; }
/// <summary> /// Reads a repeat statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadRepeat(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'repeat' RepeatItem repeat = new RepeatItem(); if (debug.Value != "repeat") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "repeat", "ReadRepeat")); // read the block repeat.Block = ReadBlock(input, ref debug); // read 'until' var name = Read(input, ref debug); if (name.Value != "until") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "repeat", "until"), input.Name, name); // read the expression repeat.Expression = ReadExp(input, ref debug); prev.Append(debug); repeat.Debug = debug; return repeat; }
/// <summary> /// Reads a label statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadLabel(ITokenizer input, ref Token prev) { var debug = input.Read(); // read '::' if (debug.Value != "::") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "::", "ReadLabel")); // read the label Token label = Read(input, ref debug); // read '::' var name = Read(input, ref debug); if (name.Value != "::") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "label", "::"), input.Name, debug); prev.Append(debug); return new LabelItem(label.Value) { Debug = debug }; }
/// <summary> /// Reads a break statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadBreak(ITokenizer input, ref Token prev) { var ret = input.Read(); if (ret.Value != "break") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "break", "ReadBreak")); prev.Append(ret); return new GotoItem("<break>") { Debug = ret }; }
/// <summary> /// Helper function that reads a string from the input, it assumes that /// it is on the first character in the string. /// </summary> /// <param name="depth">The depth of the long-string or -1 for ' or /// -2 for ".</param> /// <param name="line">The starting line of the string.</param> /// <param name="pos">The starting position of the string.</param> /// <returns>The token that represents the string read. The token should /// start with a ".</returns> /// <exception cref="ModMaker.Lua.Parser.SyntaxException">If there is /// an error in the syntax of the input.</exception> protected virtual Token ReadString(int depth, long pos, long line) { StringBuilder str = new StringBuilder(); str.Append("\""); Token ret = new Token(); ret.StartPos = pos; ret.StartLine = line; while (PeekElement() != null) { string temp = ReadElement(); if (temp == "'" && depth == -1) break; else if (temp == "\"" && depth == -2) break; else if (temp == "\n" && depth < 0) throw new SyntaxException(string.Format(Resources.MissingEnd, "string literal"), Name, ret); else if (temp == "]" && depth >= 0) { int j = 0; while (PeekElement() == "=") { j++; ReadElement(); } if (PeekElement() != "]" || j != depth) { // if this isn't the end of the string, // append the parts read already. str.Append(']'); str.Append('=', j); } else { ReadElement(); break; } } else if (temp == "\\") { if (depth >= 0) { str.Append("\\"); continue; } temp = ReadElement(); switch (temp) { case "'": case "\"": case "\\": case "\n": str.Append(temp); break; case "z": ReadWhitespace(); break; case "n": str.Append("\n"); break; case "a": str.Append('\a'); break; case "b": str.Append('\b'); break; case "f": str.Append('\f'); break; case "repeat": str.Append('\r'); break; case "t": str.Append('\t'); break; case "v": str.Append('\v'); break; case "x": { int ii = 0; temp = ReadElement(); if (!"0123456789ABCDEFabcdef".Contains(temp)) throw new SyntaxException(string.Format(Resources.InvalidEscape, "x" + temp), Name, ret); ii = int.Parse(temp, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); temp = ReadElement(); if (!"0123456789ABCDEFabcdef".Contains(temp)) throw new SyntaxException(string.Format(Resources.InvalidEscape, "x" + ii.ToString("x", CultureInfo.CurrentCulture) + temp), Name, ret); ii = (ii >> 16) + int.Parse(temp, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); str.Append((char)ii); break; } case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": { int ii = 0; if (!"0123456789".Contains(PeekElement())) continue; temp = ReadElement(); ii = int.Parse(temp, CultureInfo.InvariantCulture); if ("0123456789".Contains(PeekElement())) { temp = ReadElement(); ii = (ii * 10) + int.Parse(temp, CultureInfo.InvariantCulture); if ("0123456789".Contains(PeekElement())) { temp = ReadElement(); ii = (ii * 10) + int.Parse(temp, CultureInfo.InvariantCulture); } } str.Append((char)ii); break; } default: throw new SyntaxException(string.Format(Resources.InvalidEscape, temp), Name, ret); } } else str.Append(temp); } ret.EndPos = Position; ret.EndLine = Line; ret.Value = str.ToString(); return ret; }
/// <summary> /// Reads a goto statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadGoto(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'goto' if (debug.Value != "goto") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "goto", "ReadGoto")); // read the target var name = Read(input, ref debug); if (!IsName(name.Value)) throw new SyntaxException( string.Format(Resources.TokenNotAName, "goto", name.Value), input.Name, debug); if (_reserved.Contains(name.Value)) throw new SyntaxException( string.Format(Resources.TokenReserved, name.Value), input.Name, name); prev.Append(debug); return new GotoItem(name.Value) { Debug = debug }; }
/// <summary> /// Pushes a token back onto the tokenizer. This will allow to reverse /// a read. /// </summary> /// <param name="token">The token to push-back.</param> public void PushBack(Token token) { peek.Push(token); }
/// <summary> /// Reads a do statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadDo(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'do' if (debug.Value != "do") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "do", "ReadDo")); // read the block var ret = ReadBlock(input, ref debug); // ensure that it ends with 'end' Token end = Read(input, ref debug); if (end.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, end.Value, "do", "end"), input.Name, end); prev.Append(debug); return ret; }
/// <summary> /// Creates a new SyntaxException with the given message. /// </summary> /// <param name="message">The cause of the exception.</param> /// <param name="file">The source file that caused the exception.</param> /// <param name="source">The source token that caused the exception.</param> public SyntaxException(string message, string file, Token source) : this(message, file, source, null) { }
/// <summary> /// Reads a while statement from the input. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="prev">The token to append what is read into.</param> /// <returns>The object that was read.</returns> protected virtual IParseStatement ReadWhile(ITokenizer input, ref Token prev) { var debug = input.Read(); // read 'while' WhileItem w = new WhileItem(); if (debug.Value != "while") throw new InvalidOperationException(string.Format(Resources.MustBeOn, "while", "ReadWhile")); // read the expression w.Exp = ReadExp(input, ref debug); // read 'do' var name = Read(input, ref debug); if (name.Value != "do") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "while", "do"), input.Name, name); // read the block w.Block = ReadBlock(input, ref debug); // read 'end' name = Read(input, ref debug); if (name.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, name.Value, "while", "end"), input.Name, name); prev.Append(debug); w.Debug = debug; return w; }
/// <summary> /// Creates a new SyntaxException with the given message. /// </summary> /// <param name="file">The file that caused the exception.</param> /// <param name="message">The cause of the exception.</param> /// <param name="source">The source token that caused the exception.</param> /// <param name="inner">The inner exception.</param> public SyntaxException(string message, string file, Token source, Exception inner) : base("Error in the syntax of the file.\nMessage: " + message, inner) { this.SourceToken = source; this.SourceFile = file; }
/// <summary> /// Reads a function from the input. Input must either be on the word 'function' or /// on the next token. If it is on 'function' and canName is true, it will give /// the function the read name; otherwise it will give it a null name. /// </summary> /// <param name="input">Where to read input from.</param> /// <param name="token">The token to append the read Token to.</param> /// <param name="canName">True if the function can have a name, otherwise false.</param> /// <param name="local">True if this function is a local definition, otherwise false.</param> /// <returns>The function definition that was read.</returns> protected virtual FuncDefItem ReadFunctionHelper(ITokenizer input, ref Token token, bool canName, bool local) { IParseVariable name = null; string inst = null; Token last = input.Peek(), debug = last; if (last.Value == "function") { input.Read(); // read 'function' last = input.Peek(); if (IsName(last.Value)) { Token nameTok = input.Read(); // read name name = new NameItem(last.Value) { Debug = last }; // handle indexers last = input.Peek(); while (last.Value == ".") { Read(input, ref nameTok); // read '.' last = input.Peek(); if (!IsName(last.Value)) break; name = new IndexerItem(name, new LiteralItem(last.Value) { Debug = last }) { Debug = nameTok }; Read(input, ref nameTok); } if (input.Peek().Value == ":") { Read(input, ref nameTok); inst = Read(input, ref nameTok).Value; if (!IsName(inst)) throw new SyntaxException(string.Format(Resources.TokenInvalid, last.Value, "function"), input.Name, last); } debug.Append(nameTok); } } if (name != null && !canName) throw new SyntaxException(Resources.FunctionCantHaveName, input.Name, debug); FuncDefItem ret = new FuncDefItem(name, local); ret.InstanceName = inst; last = Read(input, ref debug); if (last.Value != "(") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "function", "("), input.Name, last); last = input.Peek(); while (last.Value != ")") { Token temp = Read(input, ref debug); // read the name if (!IsName(last.Value) && last.Value != "...") throw new SyntaxException(string.Format(Resources.TokenInvalid, last.Value, "function"), input.Name, temp); ret.AddArgument(new NameItem(last.Value) { Debug = last }); last = input.Peek(); if (last.Value == ",") Read(input, ref debug); else if (last.Value != ")") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting2, last.Value, "function", ",", ")"), input.Name, last); last = input.Peek(); } if (last.Value != ")") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "function", ")"), input.Name, last); Read(input, ref debug); // read ')' BlockItem chunk = ReadBlock(input, ref debug); chunk.Return = chunk.Return ?? new ReturnItem(); ret.Block = chunk; last = Read(input, ref debug); if (last.Value != "end") throw new SyntaxException( string.Format(Resources.TokenInvalidExpecting, last.Value, "function", "end"), input.Name, last); token.Append(debug); ret.Debug = debug; return ret; }