From d502c497f1d9d5347cdbdf7297360ef5d0f220ca Mon Sep 17 00:00:00 2001 From: Ludovic 'Archivist' Lagouardette Date: Tue, 7 Sep 2021 23:52:19 +0200 Subject: [PATCH] IF statements --- BasicNumber.cs | 4 +- Functions/Goto.cs | 26 ++++++++ Parser.cs | 163 ++++++++++++++++++++++++++++++++++++---------- Program.cs | 2 + Runtime.cs | 9 +-- Test.basic | 9 ++- 6 files changed, 172 insertions(+), 41 deletions(-) create mode 100644 Functions/Goto.cs diff --git a/BasicNumber.cs b/BasicNumber.cs index bb777ba..4523990 100644 --- a/BasicNumber.cs +++ b/BasicNumber.cs @@ -15,7 +15,7 @@ namespace SuperBASIC { internal NumberType type; - readonly Runtime runtime; + internal Runtime runtime; readonly private float number; readonly private int operand; @@ -90,6 +90,6 @@ namespace SuperBASIC } public static implicit operator float(BasicNumber v) => v.GetValue(); - public override string ToString() => $"{number}"; + public override string ToString() => $"{GetValue()}"; } } diff --git a/Functions/Goto.cs b/Functions/Goto.cs new file mode 100644 index 0000000..117d6c0 --- /dev/null +++ b/Functions/Goto.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SuperBASIC.Functions +{ + class Goto : IFunction + { + public float Apply(List arguments) + { + // Substract its own arity +1 + arguments[0].runtime.pc = (int)arguments[0].GetOperand() - 2; + return 0f; + } + } + class JumpZero : IFunction + { + public float Apply(List arguments) + { + if(arguments[0] == 0) + // Substract its own arity +1 + arguments[1].runtime.pc = (int)arguments[1].GetOperand() - 3; + return 0f; + } + } +} diff --git a/Parser.cs b/Parser.cs index 327ea76..5c7e092 100644 --- a/Parser.cs +++ b/Parser.cs @@ -28,43 +28,25 @@ namespace SuperBASIC library = rt.lib; } - public Bytecode ParseFile(string filepath) + internal enum Controls { - Bytecode c = new Bytecode(); - string[] sourceLines = File.ReadAllLines(filepath); - Regex lws = new Regex(@"\s+"); - Regex leadings = new Regex(@"^\s+"); - Regex trailings = new Regex(@"\s+$"); - List codeLines = new List(); - List lineSpans = new List(); - - int a = 0; - foreach (string line in sourceLines) - { - a++; - string l = lws.Replace(line, " "); - l = leadings.Replace(l, ""); - l = trailings.Replace(l, ""); + If, Then, Else, EndIf, + For, To, Step, Next, + While, Wend, + Repeat, Until, + Do, LoopCond + } - if (l != String.Empty) - { - lineSpans.Add(a); - a = 0; - codeLines.Add(l); - } - } + internal struct FlowControlTag + { + public Controls ctrl; + public int index; + } - for (int idx = 0; idx < codeLines.Count; idx++) + private void ParseExpression(Bytecode c, string[] components, int idx, List lineSpans) + { + if (library.nameResolution.ContainsKey(components[0])) { - string line = codeLines[idx]; - var components = line.Split(' '); - - if (!library.nameResolution.ContainsKey(components[0])) - { - int lineIndex = 0; - foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt; - throw new ParseException($"Unknown operation \"{components[0]}\"\n\tat line {lineIndex}"); - } int opcode = library.nameResolution[components[0]]; int arity = library.arities[opcode]; @@ -117,7 +99,122 @@ namespace SuperBASIC } } } + else + { + int lineIndex = 0; + foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt; + throw new ParseException($"Unknown operation \"{components[0]}\"\n\tat line {lineIndex}"); + } + } + + public Bytecode ParseFile(string filepath) + { + Bytecode c = new Bytecode(); + string[] sourceLines = File.ReadAllLines(filepath); + Regex lws = new Regex(@"\s+"); + Regex leadings = new Regex(@"^\s+"); + Regex trailings = new Regex(@"\s+$"); + List codeLines = new List(); + List lineSpans = new List(); + Stack labelStack = new Stack(); + + int a = 0; + foreach (string line in sourceLines) + { + a++; + string l = lws.Replace(line, " "); + l = leadings.Replace(l, ""); + l = trailings.Replace(l, ""); + + if (l != String.Empty) + { + lineSpans.Add(a); + a = 0; + codeLines.Add(l); + } + } + + for (int idx = 0; idx < codeLines.Count; idx++) + { + string line = codeLines[idx]; + var components = line.Split(' '); + var label = new FlowControlTag(); + switch (components[0]) + { + case "IF": + // Role: manages the expression + label.ctrl = Controls.If; + label.index = c.bytecode.Count; + labelStack.Push(label); + ParseExpression(c, components[1..], idx, lineSpans); + break; + case "THEN": + // Role: Jumps to the far block if false + // 1. Adds the label to update the destination + label.ctrl = Controls.Then; + label.index = c.bytecode.Count; + labelStack.Push(label); + // 2. Adds the jumper + c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["JZ"])); + c.bytecode.Add(new BasicNumber(runtime)); + c.bytecode.Add(new BasicNumber(runtime, 0f)); + break; + case "ELSE": + //Role: skips its section if encountered, marks the THEN for JZ + //1. Gets the THEN label + label = labelStack.Pop(); + if(label.ctrl != Controls.Then) + { + int lineIndex = 0; + foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt; + throw new ParseException($"Orphaned ELSE\n\tat line {lineIndex}"); + } + //2. Self tags for the ENDIF to complete the GOTO + var label_self = new FlowControlTag + { + ctrl = Controls.Else, + index = c.bytecode.Count + }; + labelStack.Push(label_self); + //3. Pushes the GOTO + c.bytecode.Add(new BasicNumber(runtime, library.nameResolution["GOTO"])); + c.bytecode.Add(new BasicNumber(runtime, 0f)); + //4. Place the THEN destination after the GOTO + c.bytecode[label.index+2] = new BasicNumber(runtime, c.bytecode.Count); + break; + case "ENDIF": + //Role: Destination if depending on what is skipped + label = labelStack.Pop(); + if (label.ctrl == Controls.Else) + { + //Case 1: update the GOTO + c.bytecode[label.index + 1] = new BasicNumber(runtime, c.bytecode.Count); + } + else if (label.ctrl == Controls.Then) + { + //Case 2: update the JZ + c.bytecode[label.index + 2] = new BasicNumber(runtime, c.bytecode.Count); + } + else + { + int lineIndex = 0; + foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt; + throw new ParseException($"Orphaned ENDIF\n\tat line {lineIndex}"); + } + // Controls the syntax + if (labelStack.Pop().ctrl != Controls.If) + { + int lineIndex = 0; + foreach (int cnt in lineSpans.GetRange(0, idx + 1)) lineIndex += cnt; + throw new ParseException($"Orphaned ENDIF despite THEN\n\tat line {lineIndex}"); + } + break; + default: + ParseExpression(c, components, idx, lineSpans); + break; + } + } return c; } } diff --git a/Program.cs b/Program.cs index 8007d3f..cda8602 100644 --- a/Program.cs +++ b/Program.cs @@ -17,6 +17,8 @@ namespace SuperBASIC lib.AddFunction(new Functions.Print(), 1, "PRINT"); lib.AddFunction(new Functions.Multiply(), 2, "MULTIPLY"); lib.AddFunction(new Functions.Compare(), 2, "COMPARE"); + lib.AddFunction(new Functions.JumpZero(), 2, "JZ"); + lib.AddFunction(new Functions.Goto(), 1, "GOTO"); lib.AddFunction(new Functions.Pi(), 0, "PI"); lib.AddFunction(new Functions.Euler(), 0, "EULER"); Runtime r = new Runtime(lib); diff --git a/Runtime.cs b/Runtime.cs index 65f5f63..adc79a3 100644 --- a/Runtime.cs +++ b/Runtime.cs @@ -9,6 +9,7 @@ namespace SuperBASIC float register; internal Library lib; Bytecode code; + internal int pc = 0; public Runtime(Library library) { @@ -22,14 +23,14 @@ namespace SuperBASIC public void Run() { - for(int idx = 0; idx < code.bytecode.Count;) + for(pc = 0; pc < code.bytecode.Count;) { - int opcode = code.bytecode[idx].GetOperand(); + int opcode = code.bytecode[pc].GetOperand(); int arity = lib.arities[opcode]; IFunction op = lib.functions[opcode]; - var args = code.bytecode.GetRange(idx + 1, arity); + var args = code.bytecode.GetRange(pc + 1, arity); SetRegister(op.Apply(args)); - idx += arity + 1; + pc += arity + 1; } } diff --git a/Test.basic b/Test.basic index d4bff56..5c9f468 100644 --- a/Test.basic +++ b/Test.basic @@ -1,4 +1,9 @@ EULER MULTIPLY $ $ -COMPARE $ 7.3890557 -PRINT $ \ No newline at end of file + +IF COMPARE $ 7.3890557 +THEN +PRINT 25 +ELSE +PRINT 10 +ENDIF \ No newline at end of file