public EarleFunction(EarleFile file, string name, string[] parameters, byte[] pCode, Dictionary<int, int> callLines) { if (name == null) throw new ArgumentNullException(nameof(name)); File = file; Parameters = parameters; PCode = pCode; Name = name.ToLower(); CallLines = callLines == null ? null : new Dictionary<int, int>(callLines); }
public EarleFunction CompileFunction(ILexer lexer, EarleFile file) { if (lexer == null) throw new ArgumentNullException(nameof(lexer)); var name = lexer.Current.Value.ToLower(); var parameters = new List<string>(); lexer.AssertMoveNext(); lexer.SkipToken(TokenType.Token, "("); while (!lexer.Current.Is(TokenType.Token, ")")) { lexer.AssertToken(TokenType.Identifier); if (parameters.Contains(lexer.Current.Value)) throw new ParseException(lexer.Current, $"Duplicate parameter name in function \"{name}\""); parameters.Add(lexer.Current.Value); lexer.AssertMoveNext(); if (lexer.Current.Is(TokenType.Token, ")")) break; lexer.SkipToken(TokenType.Token, ","); } lexer.SkipToken(TokenType.Token, ")"); var lastLine = lexer.Current.Line; var block = Compile(lexer, file, EarleCompileOptions.Method); file.AddReferencedFiles(block.ReferencedFiles); return new EarleFunction(file, name, parameters.ToArray(), block.PCode, block.CallLines); }
public CompiledBlock Compile(ILexer lexer, EarleFile file, EarleCompileOptions options) { var pCode = new List<byte>(); var callLines = new Dictionary<int, int>(); var breaks = new List<int>(); var continues = new List<int>(); var usedFiles = new List<string>(); var didReturnAnyValue = false; var multiLine = false; var startLine = lexer.Current.Line; if (lexer.Current.Is(TokenType.Token, "{")) { multiLine = true; lexer.AssertMoveNext(); } var requiresScope = false; do { if (multiLine && lexer.Current.Is(TokenType.Token, "}")) break; var parserName = SyntaxGrammarProcessor.GetMatch(lexer, true); if (parserName == null) throw new ParseException(lexer.Current, $"Expected statement, found token `{lexer.Current.Value}`"); if (parserName == "STATEMENT_BREAK" && options.HasFlag(EarleCompileOptions.CanBreak)) { lexer.SkipToken(TokenType.Identifier, "break"); lexer.SkipToken(TokenType.Token, ";"); breaks.Add(pCode.Count); pCode.Add((byte) OpCode.Jump); pCode.AddRange(ArrayUtility.Repeat((byte) 0xaa, 4)); didReturnAnyValue = false; if (options.HasFlag(EarleCompileOptions.EnforceMultiline)) break; } else if (parserName == "STATEMENT_CONTINUE" && options.HasFlag(EarleCompileOptions.CanContinue)) { lexer.SkipToken(TokenType.Identifier, "continue"); lexer.SkipToken(TokenType.Token, ";"); continues.Add(pCode.Count); pCode.Add((byte) OpCode.Jump); pCode.AddRange(ArrayUtility.Repeat((byte) 0xaa, 4)); didReturnAnyValue = false; if (options.HasFlag(EarleCompileOptions.EnforceMultiline)) break; } else { IParser parser; if (!_parsers.TryGetValue(parserName, out parser)) throw new ParseException(lexer.Current, $"Expected statement, found {parserName.ToLower()} `{lexer.Current.Value}`"); var parseOptions = options.HasFlag(EarleCompileOptions.MustReturn) ? options ^ EarleCompileOptions.MustReturn : options; var block = parser.Parse(_runtime, file, lexer, parseOptions); var startIndex = pCode.Count; breaks.AddRange(block.Breaks.Select(b => b + pCode.Count)); foreach (var p in block.CallLines) callLines.Add(p.Key + startIndex, p.Value); continues.AddRange(block.Continues.Select(c => c + pCode.Count)); usedFiles.AddRange(block.ReferencedFiles.Where(f => !usedFiles.Contains(f))); requiresScope = requiresScope || block.RequiresScope; pCode.AddRange(block.PCode); if (parser is ISimpleStatement) lexer.SkipToken(TokenType.Token, ";"); didReturnAnyValue = parser is StatementReturnParser; if (didReturnAnyValue && options.HasFlag(EarleCompileOptions.EnforceMultiline)) break; } if (options.HasFlag(EarleCompileOptions.EnforceMultiline) && SyntaxGrammarProcessor.MatchStartsWith(lexer, "LABEL_")) break; } while (options.HasFlag(EarleCompileOptions.EnforceMultiline) || (multiLine && !lexer.Current.Is(TokenType.Token, "}"))); if (multiLine) { lexer.AssertToken(TokenType.Token, "}"); lexer.MoveNext(); } if (!didReturnAnyValue && options.HasFlag(EarleCompileOptions.MustReturn)) { pCode.Add((byte) OpCode.PushUndefined); } if (requiresScope) { pCode.Insert(0, (byte) OpCode.PushScope); pCode.Add((byte) OpCode.PopScope); // Increase keys because of the inserted PUSH.S instruction callLines = new Dictionary<int, int>(callLines.ToDictionary(kv => kv.Key + 1, kv => kv.Value)); } return new CompiledBlock(pCode.ToArray(), callLines, usedFiles.ToArray(), breaks.ToArray(), continues.ToArray(), false); }
public virtual EarleFile CompileFile(string fileName, string script) { if (fileName == null) throw new ArgumentNullException(nameof(fileName)); if (script == null) throw new ArgumentNullException(nameof(script)); fileName = fileName.ToLower(); var lexer = new Lexer(fileName, script); var file = new EarleFile(_runtime, fileName); lexer.MoveNext(); // Recursively look foor function declarations while (lexer.Current != null) { var match = _fileGrammarProcessor.GetMatch(lexer, true); switch (match) { case "FUNCTION_DECLARATION": // Compile the function and add it to the file file.AddFunction(CompileFunction(lexer, file)); break; case "INCLUDE": lexer.SkipToken(TokenType.Token, "#"); lexer.SkipToken(TokenType.Identifier, "include"); var identifier = !lexer.Current.Is(TokenType.Token, "\\"); var path = identifier ? "\\" : string.Empty; do { // check syntax if (identifier) lexer.AssertToken(TokenType.Identifier); else lexer.AssertToken(TokenType.Token, "\\"); identifier = !identifier; path += lexer.Current.Value; lexer.AssertMoveNext(); } while (!lexer.Current.Is(TokenType.Token, ";")); lexer.SkipToken(TokenType.Token, ";"); file.IncludeFile(path); break; default: throw new ParseException(lexer.Current, $"Expected function, found {match} `{lexer.Current.Value}`"); } } return file; }