Added support for Gosub/Return.
authorMichael Welch <michaelgwelch@gmail.com>
Sat, 6 Jan 2007 21:12:18 +0000 (21:12 +0000)
committerMichael Welch <michaelgwelch@gmail.com>
Sat, 6 Jan 2007 21:12:18 +0000 (21:12 +0000)
Lexer.cs:
Modified lexer to return Token.Remark. It turns out that comments are significant in one respect: They have line numbers and can therefore be the target of a Goto or Gosub. Therefore we need to return this info to the Parser.cs

mbasic.csproj:
Added Gosub.cs, Remark.cs and Return.cs.

Parser.cs:
Added parsing for Remarks, Go Sub, Gosub, and Return statements. Note Go Sub and Gosub are equivalent statements but parse differently.

Program.cs:
Added the keywords Go, Sub, Gosub, and Return. Also, the main program generates a a block of code that includes a switch statement at the end of the program that is used to implement RETURN. During CheckTypes an index number is assigned to each GOSUB location. That index number is assigned a label that corresponds to the line of code immediately after the GOSUB. When a GOSUB is encountered the index number of the GOSUB is pushed onto a stack using BuiltIns.PushReturnAddress. A RETURN statement branches to the block of code discussed above. It calls BuiltIns.PopReturnAddress to get the index of the calling GOSUB. Then it calls the switch statement which uses the index to branch to the correct return location.

Gosub.cs, Return.cs, Remarks.cs: Added nodes for Gosub, Return and Remark.cs

Node.cs:
added fields returnLabels and returnSwitch.

Token.cs:
Added Go, Sub, Gosub, and Return.

BuiltIns.cs:
Added a field to hold a stack, and added PushReturnAddress and PopReturnAddress.

gosub.mbas:
Added a test program for testing gosubs.

TiBasicRuntime/BuiltIns.cs
mbasic/Lexer.cs
mbasic/Parser.cs
mbasic/Program.cs
mbasic/SyntaxTree/Gosub.cs [new file with mode: 0644]
mbasic/SyntaxTree/Node.cs
mbasic/SyntaxTree/Remark.cs [new file with mode: 0644]
mbasic/SyntaxTree/Return.cs [new file with mode: 0644]
mbasic/Token.cs
mbasic/mbasic.csproj
samples/gosub.mbas [new file with mode: 0644]

index f7a43f0..96ead9b 100644 (file)
@@ -354,5 +354,18 @@ namespace TiBasicRuntime
             labelIndex = data.IndexOfKey(label);
             pos = 0;
         }
+
+        #region GOSUB/RETURN Helpers
+        private static readonly Stack<int> gosubs = new Stack<int>();
+        public static void PushReturnAddress(int index)
+        {
+            gosubs.Push(index);
+        }
+
+        public static int PopReturnAddress()
+        {
+            return gosubs.Pop();
+        }
+        #endregion
     }
 }
index 7ae9d05..a56fc4a 100644 (file)
@@ -113,10 +113,7 @@ namespace mbasic
                     {
                         Token word = NextWord(); // keywords and variables, and remarks
                         if (word == Token.Data) readingData = true;
-                        if (word != Token.Remark) return word;
-                        // Reset us back to start of line
-                        startOfLine = true; // after reading a REM we are now at start of line.
-                        continue;
+                        return word;
                     }
 
                     if (ch == '\"') return NextString();
@@ -265,11 +262,10 @@ namespace mbasic
                     ch = reader.Read();
                     if (ch == '\n')
                     {
-                        // consume the line feed 
-                        reader.Advance();
                         return Token.Remark;
                     }
                 }
+                if (reader.EndOfStream) return Token.Remark;
             }
 
             index = symbols.Lookup(value);
index f843765..78565a2 100644 (file)
@@ -64,8 +64,11 @@ namespace mbasic
             Statement retVal = null;
             switch (lookahead)
             {
+                case Token.Remark:
+                    retVal = RemarkStatement();
+                    break;
                 case Token.Print:
-                    retVal = Print();
+                    retVal = PrintStatement();
                     break;
                 case Token.For:
                     retVal = ForStatement();
@@ -74,19 +77,19 @@ namespace mbasic
                     Match(Token.Let);
                     goto case Token.Variable;
                 case Token.Variable:
-                    retVal = Assign();
+                    retVal = AssignStatement();
                     break;
                 case Token.Input:
-                    retVal = Input();
+                    retVal = InputStatement();
                     break;
                 case Token.If:
                     retVal = IfStatement();
                     break;
                 case Token.Goto:
-                    retVal = Goto();
+                    retVal = GotoStatement();
                     break;
                 case Token.Randomize:
-                    retVal = Randomize();
+                    retVal = RandomizeStatement();
                     break;
                 case Token.Call:
                     retVal = CallSubroutine();
@@ -103,12 +106,59 @@ namespace mbasic
                 case Token.Restore:
                     retVal = RestoreStatement();
                     break;
+                case Token.Go:
+                    retVal = GoStatement();
+                    break;
+                case Token.Gosub:
+                    retVal = GosubStatement();
+                    break;
+                case Token.Return:
+                    retVal = ReturnStatement();
+                    break;
 
             }
             Match(Token.EndOfLine);
             return retVal;
         }
 
+        private Statement RemarkStatement()
+        {
+            LineId line = lexer.LineId;
+            Statement remark = new Remark(line);
+            Match(Token.Remark);
+            return remark;
+        }
+
+        private Statement ReturnStatement()
+        {
+            LineId line = lexer.LineId;
+            Statement returnStatement = new Return(line);
+            Match(Token.Return);
+            return returnStatement;
+        }
+
+        private Statement GoStatement()
+        {
+            Match(Token.Go);
+            Match(Token.Sub);
+            return GosubStatementInternal();
+        }
+
+        private Statement GosubStatement()
+        {
+            Match(Token.Gosub);
+            return GosubStatementInternal();
+        }
+
+        private Statement GosubStatementInternal()
+        {
+            string destLabel = lexer.Value;
+            LineId line = lexer.LineId;
+            Statement gosub = new Gosub(destLabel, line);
+            Match(Token.Number);
+            return gosub;
+        }
+
         private Statement RestoreStatement()
         {
             LineId line = lexer.LineId;
@@ -166,7 +216,7 @@ namespace mbasic
 
         }
 
-        private Statement Randomize()
+        private Statement RandomizeStatement()
         {
             LineId line = lexer.LineId;
             Match(Token.Randomize);
@@ -194,7 +244,7 @@ namespace mbasic
             return new If(conditional, label, line);
         }
 
-        private Statement Goto()
+        private Statement GotoStatement()
         {
             LineId line = lexer.LineId;
             Match(Token.Goto);
@@ -203,7 +253,7 @@ namespace mbasic
             return new Goto(label, line);
         }
 
-        private Statement Input()
+        private Statement InputStatement()
         {
             LineId line = lexer.LineId;
             Match(Token.Input);
@@ -212,7 +262,7 @@ namespace mbasic
             return new Input(index, line);
         }
 
-        private Statement Assign()
+        private Statement AssignStatement()
         {
             LineId line = lexer.LineId;
             int index = lexer.SymbolIndex;
@@ -472,7 +522,7 @@ namespace mbasic
 
         #endregion Expression Handling
 
-        private Statement Print()
+        private Statement PrintStatement()
         {
             LineId line = lexer.LineId;
             Match(Token.Print);
index f56df50..66c3952 100644 (file)
@@ -36,6 +36,9 @@ namespace mbasic
 
     class Program
     {
+        static readonly MethodInfo popMethod =
+            typeof(BuiltIns).GetMethod("PopReturnAddress");
+
         static void Main(string[] args)
         {
             bool debug = true;
@@ -134,12 +137,27 @@ namespace mbasic
             }
             #endregion
 
+            Node.returnSwitch = gen.DefineLabel();
             // Emit try
             Label end = gen.BeginExceptionBlock();
             Node.endLabel = end;
 
             n.Emit(gen);
 
+            #region Emit Return Switch for GOSUB/RETURN
+            gen.MarkSequencePoint(Node.writer, Int32.MaxValue, -1, Int32.MaxValue, -1);
+
+            Label exitLabel = gen.DefineLabel();
+            gen.Emit(OpCodes.Br, exitLabel);
+
+            gen.MarkLabel(Node.returnSwitch);
+
+            gen.Emit(OpCodes.Call, popMethod);
+            gen.Emit(OpCodes.Switch, Node.returnLabels.ToArray());
+
+            gen.MarkLabel(exitLabel);
+            #endregion
+
             // Emit catch
             gen.BeginCatchBlock(typeof(Exception));
             MethodInfo getMessageMethod = typeof(Exception).GetMethod("get_Message");
@@ -147,8 +165,8 @@ namespace mbasic
             MethodInfo writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
             gen.Emit(OpCodes.Call, writeLineMethod);
             gen.EndExceptionBlock();
-            
 
+            gen.MarkSequencePoint(Node.writer, Int32.MaxValue, -1, Int32.MaxValue, -1);
             gen.Emit(OpCodes.Ret);
 
 
@@ -166,28 +184,33 @@ namespace mbasic
 
         static private void InitializeReservedWords(SymbolTable symbols)
         {
+            // Keywords for statements
+            symbols.ReserveWord("CALL", Token.Call);
+            symbols.ReserveWord("CLEAR", Token.Subroutine);
+            symbols.ReserveWord("DATA", Token.Data);
+            symbols.ReserveWord("DISPLAY", Token.Print);
             symbols.ReserveWord("ELSE", Token.Else);
-            symbols.ReserveWord("PRINT", Token.Print);
-            symbols.ReserveWord("INPUT", Token.Input);
+            symbols.ReserveWord("END", Token.End);
+            symbols.ReserveWord("FOR", Token.For);
+            symbols.ReserveWord("GO", Token.Go);
+            symbols.ReserveWord("GOSUB", Token.Gosub);
             symbols.ReserveWord("GOTO", Token.Goto);
             symbols.ReserveWord("IF", Token.If);
-            symbols.ReserveWord("FOR", Token.For);
-            symbols.ReserveWord("TO", Token.To);
+            symbols.ReserveWord("INPUT", Token.Input);
+            symbols.ReserveWord("LET", Token.Let);
             symbols.ReserveWord("NEXT", Token.Next);
-            symbols.ReserveWord("THEN", Token.Then);
-            symbols.ReserveWord("CALL", Token.Call);
-            symbols.ReserveWord("CLEAR", Token.Subroutine);
-            symbols.ReserveWord("DISPLAY", Token.Print);
+            symbols.ReserveWord("PRINT", Token.Print);
+            symbols.ReserveWord("READ", Token.Read);
             symbols.ReserveWord("REM", Token.Remark);
-            symbols.ReserveWord("LET", Token.Let);
-            symbols.ReserveWord("END", Token.End);
+            symbols.ReserveWord("RESTORE", Token.Restore);
+            symbols.ReserveWord("RETURN", Token.Return);
             symbols.ReserveWord("STOP", Token.End);
+            symbols.ReserveWord("SUB", Token.Sub);
             symbols.ReserveWord("TAB", Token.Tab);
-            symbols.ReserveWord("DATA", Token.Data);
-            symbols.ReserveWord("READ", Token.Read);
-            symbols.ReserveWord("RESTORE", Token.Restore);
+            symbols.ReserveWord("THEN", Token.Then);
+            symbols.ReserveWord("TO", Token.To);
 
-            // String Functionis
+            // String Functions
             symbols.ReserveWord("ASC", Token.Function);
             symbols.ReserveWord("CHR$", Token.Function);
             symbols.ReserveWord("LEN", Token.Function);
diff --git a/mbasic/SyntaxTree/Gosub.cs b/mbasic/SyntaxTree/Gosub.cs
new file mode 100644 (file)
index 0000000..094df49
--- /dev/null
@@ -0,0 +1,69 @@
+/*******************************************************************************
+    Copyright 2006 Michael Welch
+    
+    This file is part of MBasic99.
+
+    MBasic99 is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    MBasic99 is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MBasic99; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*******************************************************************************/
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Reflection.Emit;
+using System.Reflection;
+using TiBasicRuntime;
+namespace mbasic.SyntaxTree
+{
+    class Gosub : Statement
+    {
+        private static readonly MethodInfo pushMethod =
+            typeof(BuiltIns).GetMethod("PushReturnAddress");
+
+        string destLabel; // This is the label identifier we jump to
+        Label returnLabel; // This is the Label the subroutine should return to.
+        int gosubIndex;
+        public Gosub(string destLabel, LineId line)
+            : base(line)
+        {
+            this.destLabel = destLabel;
+        }
+
+        public override void CheckTypes()
+        {
+            // TODO: Check destination label
+        }
+
+        public override void Emit(ILGenerator gen, bool labelSetAlready)
+        {
+            if (!labelSetAlready) MarkLabel(gen);
+            MarkSequencePoint(gen);
+            gen.Emit(OpCodes.Ldc_I4, gosubIndex);
+            gen.Emit(OpCodes.Call, pushMethod);
+            Label dest = labels[destLabel];
+            MarkSequencePoint(gen);
+            gen.Emit(OpCodes.Br, dest);
+            gen.MarkLabel(returnLabel);// Mark the target to return to.
+        }
+
+        public override void RecordLabels(ILGenerator gen)
+        {
+            base.RecordLabels(gen);
+            returnLabel = gen.DefineLabel();
+            returnLabels.Add(returnLabel);
+            gosubIndex = returnLabels.Count - 1;
+        }
+    }
+}
index 8a7040c..3e4b5c4 100644 (file)
@@ -36,7 +36,9 @@ using System.Reflection;
         public static List<LocalBuilder> locals;
         public static Lexer lexer;
         public static LabelList labels;
+        public static List<Label> returnLabels = new List<Label>();// these are the labels that mark lines after gosub statements. 
         public static Label endLabel;
+        public static Label returnSwitch;
 
         protected Node(LineId line) { this.line = line; }
 
diff --git a/mbasic/SyntaxTree/Remark.cs b/mbasic/SyntaxTree/Remark.cs
new file mode 100644 (file)
index 0000000..06cf5cf
--- /dev/null
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace mbasic.SyntaxTree
+{
+    /// <summary>
+    /// Represents a remark. A remark has no code associated with
+    /// it, but unlike comments in other languages, a remark
+    /// has one significant detail. It has a line number and
+    /// can therefore be the destination of a GOSUB or GOTO.
+    /// </summary>
+    class Remark : Statement
+    {
+        public Remark(LineId line)
+            : base(line)
+        {
+        }
+
+        public override void CheckTypes()
+        {
+        }
+
+        public override void Emit(System.Reflection.Emit.ILGenerator gen, bool labelSetAlready)
+        {
+            if (!labelSetAlready) MarkLabel(gen);
+            // no code to emit.
+        }
+    }
+}
diff --git a/mbasic/SyntaxTree/Return.cs b/mbasic/SyntaxTree/Return.cs
new file mode 100644 (file)
index 0000000..7362bc9
--- /dev/null
@@ -0,0 +1,49 @@
+/*******************************************************************************
+    Copyright 2006 Michael Welch
+    
+    This file is part of MBasic99.
+
+    MBasic99 is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    MBasic99 is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with MBasic99; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*******************************************************************************/
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Reflection.Emit;
+using System.Reflection;
+
+namespace mbasic.SyntaxTree
+{
+    class Return : Statement
+    {
+        public Return(LineId line)
+            : base(line)
+        {
+        }
+
+        public override void CheckTypes()
+        {
+            // nothing to do.
+        }
+
+        public override void Emit(ILGenerator gen, bool labelSetAlready)
+        {
+            if (!labelSetAlready) MarkLabel(gen);
+            MarkSequencePoint(gen);
+            gen.Emit(OpCodes.Br, returnSwitch);
+        }
+    }
+}
index ca373aa..47bb6f1 100644 (file)
@@ -46,7 +46,9 @@ internal enum Token
     Float,
     For,
     Function,
+    Go, // As part of GO SUB 
     Goto,
+    Gosub,
     GreaterThanEqual,
     If,
     Input,
@@ -62,8 +64,10 @@ internal enum Token
     Read,
     Remark, // technically not a token, and should never be returned. Used internally by lexer only.
     Restore,
+    Return,
     String,
-    Subroutine,
+    Sub,        // The key word "SUB"
+    Subroutine, // A built in subroutine like clear, or print.
     Tab,
     Then,
     To,
index c1175df..d444e50 100644 (file)
@@ -52,6 +52,7 @@
     <Compile Include="SyntaxTree\Expression.cs" />\r
     <Compile Include="SyntaxTree\For.cs" />\r
     <Compile Include="SyntaxTree\Function.cs" />\r
+    <Compile Include="SyntaxTree\Gosub.cs" />\r
     <Compile Include="SyntaxTree\Goto.cs" />\r
     <Compile Include="SyntaxTree\If.cs" />\r
     <Compile Include="SyntaxTree\Increment.cs" />\r
@@ -66,7 +67,9 @@
     <Compile Include="SyntaxTree\Randomize.cs" />\r
     <Compile Include="SyntaxTree\Read.cs" />\r
     <Compile Include="SyntaxTree\RelationalExpression.cs" />\r
+    <Compile Include="SyntaxTree\Remark.cs" />\r
     <Compile Include="SyntaxTree\Restore.cs" />\r
+    <Compile Include="SyntaxTree\Return.cs" />\r
     <Compile Include="SyntaxTree\Statement.cs" />\r
     <Compile Include="SyntaxTree\Subroutine.cs" />\r
     <Compile Include="SyntaxTree\Subtract.cs" />\r
diff --git a/samples/gosub.mbas b/samples/gosub.mbas
new file mode 100644 (file)
index 0000000..3846ba0
--- /dev/null
@@ -0,0 +1,15 @@
+90 REM gosub example
+100 for i = 1 to 3
+110 gosub 150
+120 print "i=";i
+130 next i
+131 gosub 1000
+140 stop
+150 rem subroutine
+160 for x = 1 to 2
+170 print "x=";x
+180 next x
+190 return
+
+1000 print "subroutine 1000 here."
+1010 return
\ No newline at end of file