A little while back, Joel posted the source to the Good For Nothing (GFN) Compiler and wondered what people can do with it. As a little side project, I added some features to the GFN Compiler which I will post in two parts.
Part 1: Making BCL calls from the GFN Compiler
Part 2: Making Function calls from the GFN Compiler
And without further ado.. here is part 1...
Here is the grammar modification I did to accomodate making BCL calls:
<stmt> := var <ident> = <expr> | <ident> = <expr> | <ident> = <func_stmt> | for <ident> = <expr> to <expr> do <stmt> end | read_int <ident> | print <expr> | <stmt> ; <stmt> | <func_stmt>
<func_stmt> := <scope><func_call><scope> := (<namespace>)? <scope>*<namespace> := <ident>.<func_call> := <ident> (<args>)
Additons to scanner.cs for class Scanner:
First, I need to scan the new objects for a function call into the tokens.
// Constants to represent arithmitic tokens. This could// be alternatively written as an enum.// ....public static readonly object Comma = new object(); public static readonly object LeftBracket = new object(); // "(" public static readonly object RightBracket = new object(); // ")"
// Switch statment for character// ....else switch (ch) { case ',': input.Read(); result.Add(Scanner.Comma); break; case '(': input.Read(); result.Add(Scanner.LeftBracket); break;case ')': input.Read(); result.Add(Scanner.RightBracket); break;}
Additons to Ast.cs:
Then, I created a class to hold a function call.
public class FunctionCall : Stmt { public List<string> DotedScopes; public string FunctionName; public List<Expr> Args; public FunctionCall() { DotedScopes = new List<string>(); Args = new List<Expr>(); } }
Additons to Parser.cs for class Paser:
I need to make additions to the Parser so that it can parse the function call into the the class I created in Ast.cs.
private FunctionCall ParseFunc() { //Function calls FunctionCall func = new FunctionCall(); // function scope while (this.index + 1 < this.tokens.Count && this.tokens[this.index + 1] == Scanner.Dot) { func.DotedScopes.Add((string)this.tokens[this.index++]); // Skips the dot this.index++; } // Function Name if (this.index == this.tokens.Count || !(this.tokens[this.index] is string)) { throw new System.Exception("expected function name after scope"); } func.FunctionName = (string)this.tokens[this.index++]; // Arguments if (this.index == this.tokens.Count || this.tokens[this.index] != Scanner.LeftBracket) { throw new System.Exception("expect open bracket after function name"); } this.index++; // Skip Left Bracket while ((this.tokens[this.index] != Scanner.RightBracket) && (this.index < this.tokens.Count)) { func.Args.Add(this.ParseExpr()); if (this.tokens[this.index] == Scanner.Comma) this.index++; // Skip comma else if (this.tokens[this.index] == Scanner.RightBracket) break; else throw new System.Exception("unexpected character in arg list"); } if (this.index == this.tokens.Count || this.tokens[this.index] != Scanner.RightBracket) { throw new System.Exception("expect close bracket after open bracket/args"); } this.index++; // Skip RightBracket return func; } }
Additons to CodeGen.cs for class CodeGen
Now, the best part of writing a compiler... the actual code generation.
private void CallFunction(FunctionCall func, out System.Type returnType) { System.Type[] typeArray = new System.Type[func.Args.Count]; for (int i = 0; i < func.Args.Count; i++) { typeArray[i] = this.TypeOfExpr(func.Args[i]); this.GenExpr(func.Args[i], typeof(object)); } Reflect.MethodInfo mi = null; // BCL calls if (func.DotedScopes.Count > 0) { Text.StringBuilder scope = new Text.StringBuilder(); foreach (string str in func.DotedScopes) { scope.Append(str + "."); } // Remove the last dot scope.Remove(scope.Length - 1, 1); System.Type type = System.Type.GetType(scope.ToString()); mi = type.GetMethod(func.FunctionName, typeArray); } else // Local Function calls { if (!this.symbolTable.FunctionTable.ContainsKey(func.FunctionName)) { throw new System.Exception("function \"" + func.FunctionName + "\" not declared"); } mi = this.symbolTable.FunctionTable[func.FunctionName]; } returnType = mi.ReturnType; this.il.Emit(Emit.OpCodes.Call, mi); }