Added support for RESTORE statement. Also modified Lexer to be able to parse strings...
authorMichael Welch <michaelgwelch@gmail.com>
Thu, 28 Dec 2006 19:38:28 +0000 (19:38 +0000)
committerMichael Welch <michaelgwelch@gmail.com>
Thu, 28 Dec 2006 19:38:28 +0000 (19:38 +0000)
Lexer.cs:
Added support for double quotes in a string.

mbasic.csproj:
Added Restore.cs and read.mbas

Parser.cs:
Added support for parsing RESTORE. Needed to modify the structure used to collect the DATA elements to allow for RESTORING'ing back to any label. Fixed MorePrintList to make sure a print seperator is used between print items.

Program.cs:
Modified the structure used for collection DATA elements to support RESTORE'ing to a label number. Reserved the word RESTORE.

For.cs:
Added a LineId to the constructor so that a label could be associated with the FOR statement (which is needed if we want to GOTO a for statement).

Statement.cs:
Added lineLabelAssigned field to make sure that MarkLabel doesn't throw an exception if lineLabel has not been assigned.

Token.cs:
Added Restore

print.mbas:
Added tests for double quotes in a string.

testPrint.py:
Added check for test added in print.mbas.

BuiltIns.cs:
Minor fix to PrintNumber to make sure a leading space is printed only after any NewLine is printed. And fixed the calculation.

Changed the structure used to hold DATA elements. As a result I needed one common Read method to be shared by ReadDouble and ReadStrinng which would keep the pointers to the current location up to date in one place. (Probably should be its own class.)

Restore.cs
Added. Represents a Restore statement in an AST.

read.mbas:
Added tests from II-61 and II-62.

testRead.py:
Checks the results of read.mbas.

13 files changed:
TiBasicRuntime/BuiltIns.cs
mbasic/Lexer.cs
mbasic/Parser.cs
mbasic/Program.cs
mbasic/SyntaxTree/For.cs
mbasic/SyntaxTree/Restore.cs [new file with mode: 0644]
mbasic/SyntaxTree/Statement.cs
mbasic/Token.cs
mbasic/mbasic.csproj
samples/print.mbas
samples/read.mbas [new file with mode: 0644]
samples/testPrint.py
samples/testRead.py [new file with mode: 0644]

index f127fb1..f5f18c0 100644 (file)
@@ -244,11 +244,11 @@ namespace TiBasicRuntime
 \r
         private static void PrintNumber(double d)\r
         {\r
-            if (d >= 0) PrintSpace(); // Positive numbers are printed with leading space \r
             string s = Radix100.ToString(d);\r
             if (s.Length > RemainingPrintColumns) PrintNewLine();\r
+            if (d >= 0) PrintSpace(); // Positive numbers are printed with leading space \r
             PrintItem(s);\r
-            if (printCol < 28) PrintSpace();\r
+            if (printCol < 29) PrintSpace();\r
         }\r
 \r
         private static void PrintItem(string s)\r
@@ -288,21 +288,20 @@ namespace TiBasicRuntime
         private static void ResetColumnPos() { printCol = 1; }\r
         private static int RemainingPrintColumns { get { return 28 - printCol + 1; } }\r
 \r
-\r
-        private static List<object> data = new List<object>();\r
+        private static SortedList<string, object[]> data = new SortedList<string, object[]>();\r
+        private static int labelIndex = 0;\r
         private static int pos = 0;\r
-        public static void AddData(params object[] objects)\r
+        public static void AddData(string label, params object[] objects)\r
         {\r
-            data.AddRange(objects);\r
+            data.Add(label, objects);\r
         }\r
 \r
         public static void ReadDouble(out double d)\r
         {\r
-            object o = data[pos];\r
+            object o = Read();\r
             if (o is double)\r
             {\r
                 d = (double)o;\r
-                pos++;\r
             }\r
             else\r
             {\r
@@ -312,7 +311,7 @@ namespace TiBasicRuntime
 \r
         public static void ReadString(out string s)\r
         {\r
-            object o = data[pos];\r
+            object o = Read();\r
             if (o is string)\r
             {\r
                 s = (string)o;\r
@@ -321,11 +320,35 @@ namespace TiBasicRuntime
             {\r
                 s = Radix100.ToString((double)o);\r
             }\r
+        }\r
+\r
+        private static object Read()\r
+        {\r
+            if (labelIndex == data.Count) throw new Exception("DATA ERROR");\r
+            object[] dataList = data.Values[labelIndex];\r
+\r
+            if (pos == dataList.Length) throw new Exception("DATA ERROR");\r
+            object o = dataList[pos];\r
+\r
             pos++;\r
+            if (pos == dataList.Length)\r
+            {\r
+                labelIndex++;\r
+                pos = 0;\r
+            }\r
+            return o;\r
+        }\r
+\r
+        public static void RestoreToBeginning()\r
+        {\r
+            labelIndex = 0;\r
+            pos = 0;\r
         }\r
 \r
-        public static void Restore()\r
+        public static void RestoreToLabel(string label)\r
         {\r
+            // TODO: What if labelIndex is not found? Need to throw data error\r
+            labelIndex = data.IndexOfKey(label);\r
             pos = 0;\r
         }\r
     }\r
index 86f939b..97d847d 100644 (file)
@@ -273,12 +273,26 @@ namespace mbasic
         {\r
             Char endChar = quoted ? '\"' : ',';\r
             StringBuilder bldr = new StringBuilder();\r
-            if (!quoted) bldr.Append(reader.Current);\r
-            for (char ch = reader.Read(); ch != endChar; ch = reader.Read())\r
+            if (!quoted) bldr.Append(reader.Current); // If not quoted then append current char, otherwise skip it\r
+\r
+\r
+            do\r
             {\r
-                bldr.Append(ch);\r
-            }\r
-            if (quoted) reader.Advance(); // to consume the quotation mark\r
+                for (char ch = reader.Read(); ch != endChar; ch = reader.Read())\r
+                {\r
+                    bldr.Append(ch);\r
+                }\r
+\r
+                if (quoted) reader.Advance(); // to consume the quotation mark\r
+\r
+                // if the next character is a quote then we haven't reached the end of\r
+                // the string. We just read a double quote "" in the string which\r
+                // should be replaced with a "\r
+\r
+                if (reader.Current == '\"') bldr.Append('\"');\r
+                \r
+            } while (quoted && reader.Current == '\"');\r
+\r
             value = bldr.ToString();\r
             return Token.String;\r
 \r
index c6f7497..87ece79 100644 (file)
@@ -33,9 +33,9 @@ namespace mbasic
     internal class Parser\r
     {\r
         public Lexer lexer;\r
-        List<object> data;\r
+        SortedList<string, object[]> data;\r
         Token lookahead;\r
-        public Parser(Stream stream, SymbolTable symbols, List<object> data)\r
+        public Parser(Stream stream, SymbolTable symbols, SortedList<string, object[]> data)\r
         {\r
             lexer = new Lexer(stream, symbols);\r
             this.data = data;\r
@@ -99,12 +99,32 @@ namespace mbasic
                 case Token.Read:\r
                     retVal = ReadStatement();\r
                     break;\r
+                case Token.Restore:\r
+                    retVal = RestoreStatement();\r
+                    break;\r
 \r
             }\r
             Match(Token.EndOfLine);\r
             return retVal;\r
         }\r
 \r
+        private Statement RestoreStatement()\r
+        {\r
+            LineId line = lexer.LineId;\r
+            Restore restore;\r
+            Match(Token.Restore);\r
+            if (lookahead == Token.Number)\r
+            {\r
+                restore = new Restore(lexer.Value, line);\r
+                Match(Token.Number);\r
+            }\r
+            else\r
+            {\r
+                restore = new Restore(line);\r
+            }\r
+            return restore;\r
+        }\r
+\r
         private Statement ReadStatement()\r
         {\r
             Match(Token.Read);\r
@@ -123,7 +143,8 @@ namespace mbasic
         private Statement DataStatement()\r
         {\r
             Match(Token.Data);\r
-            data.AddRange(DataList());\r
+            KeyValuePair<string, object[]> list = DataList();\r
+            data.Add(list.Key, list.Value);\r
             return Data.Instance;\r
         }\r
 \r
@@ -361,9 +382,13 @@ namespace mbasic
             }\r
         }\r
 \r
-        object[] DataList()\r
+        \r
+        KeyValuePair<string, object[]> DataList()\r
         {\r
-            if (lookahead == Token.EndOfLine || lookahead == Token.EOF) return new object[0];\r
+            string label = lexer.LineId.Label;\r
+\r
+            if (lookahead == Token.EndOfLine || lookahead == Token.EOF)\r
+                new KeyValuePair<string, object[]>(label, new object[0]);\r
             List<object> items = new List<object>();\r
             while (lookahead != Token.EndOfLine && lookahead != Token.EOF)\r
             {\r
@@ -386,7 +411,7 @@ namespace mbasic
                 }\r
                 if (lookahead == Token.Comma) Match(Token.Comma);\r
             }\r
-            return items.ToArray();\r
+            return new KeyValuePair<string,object[]>(label, items.ToArray());\r
         }\r
 \r
         Expression[] ArgList()\r
@@ -510,6 +535,12 @@ namespace mbasic
                     list.Add(new StringLiteral("\t", lexer.LineId));\r
                     Match(Token.Comma);\r
                     break;\r
+                case Token.EndOfLine:\r
+                case Token.EOF:\r
+                    return new Expression[0];\r
+                default:\r
+                    throw new Exception(String.Format(\r
+                        "Missing print seperator on {0}", lexer.LineId.Label));\r
             }\r
             list.AddRange(PrintList());\r
             return list.ToArray();\r
@@ -522,33 +553,33 @@ namespace mbasic
 \r
         private Statement ForStatement()\r
         {\r
-            LineId line = lexer.LineId;\r
+            LineId line = lexer.LineId; // This is the line that the FOR key word is used on\r
             Match(Token.For);\r
 \r
             int index = lexer.SymbolIndex;\r
-            Match(Token.Variable);\r
+            Match(Token.Variable);      // This is the counting variable \r
 \r
             Match(Token.Equals);\r
 \r
-            Expression startVal = Expression();\r
+            Expression startVal = Expression(); // This is the starting value\r
 \r
             Match(Token.To);\r
 \r
-            Expression endVal = Expression();\r
+            Expression endVal = Expression(); // this is the ending value\r
 \r
             Match(Token.EndOfLine);\r
 \r
             LineId blockStart = lexer.LineId;\r
             Block block = Block(Token.Next);\r
 \r
-            LineId endLine = lexer.LineId;\r
+            LineId endLine = lexer.LineId;  // This is the line that the NEXT keyword is used on\r
             Match(Token.Next);\r
             Match(Token.Variable);\r
 \r
             Assign init = new Assign(index, startVal, line);\r
             Assign update = new Assign(index, new Increment(index, endLine), endLine);\r
             Expression comparison = RelationalExpression.CompareLessThanEquals(new VariableReference(index, line), endVal, line);\r
-            return new For(init, comparison, update, block);\r
+            return new For(init, comparison, update, block, line);\r
         }\r
 \r
         public Block Block(Token endToken)\r
index a1e969e..92e47f2 100644 (file)
@@ -49,7 +49,7 @@ namespace mbasic
             Stream stream = File.OpenRead(fileName);\r
             SymbolTable symbols = new SymbolTable();\r
             InitializeReservedWords(symbols);\r
-            List<object> data = new List<object>(); // a list used to contain all the statid DATA data.\r
+            SortedList<string, object[]> data = new SortedList<string, object[]>(); // a list used to contain all the statid DATA data.\r
             Parser parser = new Parser(stream, symbols, data);\r
             Node.lexer = parser.lexer;\r
 \r
@@ -104,25 +104,33 @@ namespace mbasic
             if (data.Count > 0)\r
             {\r
                 MethodInfo addDataMethod = typeof(BuiltIns).GetMethod("AddData");\r
-                gen.Emit(OpCodes.Ldc_I4, data.Count);\r
-                gen.Emit(OpCodes.Newarr, typeof(object));\r
 \r
-                for (int i = 0; i < data.Count; i++)\r
+                for (int labelIndex = 0; labelIndex < data.Count; labelIndex++)\r
                 {\r
-                    gen.Emit(OpCodes.Dup); // duplicate array reference, it will be consumed on store element\r
-                    gen.Emit(OpCodes.Ldc_I4, i);\r
-                    object o = data[i];\r
-                    if (o is string) gen.Emit(OpCodes.Ldstr, (string)o);\r
-                    else\r
+                    object[] dataList = data.Values[labelIndex];\r
+                    string label = data.Keys[labelIndex];\r
+\r
+                    gen.Emit(OpCodes.Ldstr, label);\r
+                    gen.Emit(OpCodes.Ldc_I4, dataList.Length);\r
+                    gen.Emit(OpCodes.Newarr, typeof(object));\r
+\r
+                    for (int i = 0; i < dataList.Length; i++)\r
                     {\r
-                        double d = (double)o;\r
-                        gen.Emit(OpCodes.Ldc_R8, d);\r
-                        gen.Emit(OpCodes.Box, typeof(double));\r
+                        gen.Emit(OpCodes.Dup); // duplicate array reference, it will be consumed on store element\r
+                        gen.Emit(OpCodes.Ldc_I4, i);\r
+                        object o = dataList[i];\r
+                        if (o is string) gen.Emit(OpCodes.Ldstr, (string)o);\r
+                        else\r
+                        {\r
+                            double d = (double)o;\r
+                            gen.Emit(OpCodes.Ldc_R8, d);\r
+                            gen.Emit(OpCodes.Box, typeof(double));\r
+                        }\r
+                        gen.Emit(OpCodes.Stelem_Ref);\r
                     }\r
-                    gen.Emit(OpCodes.Stelem_Ref);\r
-                }\r
 \r
-                gen.Emit(OpCodes.Call, addDataMethod);\r
+                    gen.Emit(OpCodes.Call, addDataMethod);\r
+                }\r
             }\r
             #endregion\r
 \r
@@ -177,6 +185,7 @@ namespace mbasic
             symbols.ReserveWord("TAB", Token.Tab);\r
             symbols.ReserveWord("DATA", Token.Data);\r
             symbols.ReserveWord("READ", Token.Read);\r
+            symbols.ReserveWord("RESTORE", Token.Restore);\r
 \r
             // String Functionis\r
             symbols.ReserveWord("ASC", Token.Function);\r
index 7c4ffbd..3e901e0 100644 (file)
@@ -36,8 +36,8 @@ namespace mbasic.SyntaxTree
         Expression comparison;\r
         Statement stmt;\r
 \r
-        public For(Assign init, Expression comparison, Assign update, Statement stmt)\r
-            : base(LineId.None)\r
+        public For(Assign init, Expression comparison, Assign update, Statement stmt, LineId line)\r
+            : base(line)\r
         {\r
             this.init = init;\r
             this.comparison = comparison;\r
@@ -77,7 +77,7 @@ namespace mbasic.SyntaxTree
             stmt.Emit(gen);\r
 \r
             // Update counter\r
-            update.Emit(gen, false);\r
+            update.Emit(gen);\r
 \r
             gen.MarkLabel(condition);\r
             comparison.Emit(gen);\r
diff --git a/mbasic/SyntaxTree/Restore.cs b/mbasic/SyntaxTree/Restore.cs
new file mode 100644 (file)
index 0000000..87f27cf
--- /dev/null
@@ -0,0 +1,62 @@
+/*******************************************************************************\r
+    Copyright 2006 Michael Welch\r
+    \r
+    This file is part of MBasic99.\r
+\r
+    MBasic99 is free software; you can redistribute it and/or modify\r
+    it under the terms of the GNU General Public License as published by\r
+    the Free Software Foundation; either version 2 of the License, or\r
+    (at your option) any later version.\r
+\r
+    MBasic99 is distributed in the hope that it will be useful,\r
+    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+    GNU General Public License for more details.\r
+\r
+    You should have received a copy of the GNU General Public License\r
+    along with MBasic99; if not, write to the Free Software\r
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r
+*******************************************************************************/\r
+\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Text;\r
+using System.Reflection.Emit;\r
+using System.Reflection;\r
+using TiBasicRuntime;\r
+\r
+namespace mbasic.SyntaxTree\r
+{\r
+    class Restore : Statement\r
+    {\r
+        private static readonly MethodInfo restoreMethod =\r
+            typeof(BuiltIns).GetMethod("RestoreToBeginning");\r
+        private static readonly MethodInfo restoreToLabelMethod =\r
+            typeof(BuiltIns).GetMethod("RestoreToLabel");\r
+\r
+        string label;\r
+        public Restore(LineId line) : base(line) { }\r
+        public Restore(string label, LineId line)\r
+            : base(line)\r
+        {\r
+            this.label = label;\r
+        }\r
+        public override void CheckTypes()\r
+        {\r
+            //TODO: Check label number\r
+        }\r
+\r
+        public override void Emit(ILGenerator gen, bool labelSetAlready)\r
+        {\r
+            if (!labelSetAlready) MarkLabel(gen);\r
+            MarkSequencePoint(gen);\r
+            if (label == null) gen.Emit(OpCodes.Call, restoreMethod);\r
+            else\r
+            {\r
+                gen.Emit(OpCodes.Ldstr, label);\r
+                gen.Emit(OpCodes.Call, restoreToLabelMethod);\r
+            }\r
+        }\r
+    }\r
+}\r
index 06a9eea..25dc6e3 100644 (file)
@@ -30,6 +30,7 @@ namespace mbasic.SyntaxTree
     abstract class Statement : Node\r
     {\r
         Label lineLabel;        // A .NET label used to mark this location.\r
+        bool lineLabelAssigned = false;\r
 \r
         protected Statement(LineId line) : base(line)\r
         {\r
@@ -37,8 +38,9 @@ namespace mbasic.SyntaxTree
         public abstract void CheckTypes();\r
         public virtual void RecordLabels(ILGenerator gen)\r
         {\r
-            if (labels.ContainsKey(line.Label)) return;\r
+            if (labels.ContainsKey(line.Label) || line.Label == String.Empty) return;\r
             this.lineLabel = gen.DefineLabel();\r
+            this.lineLabelAssigned = true;\r
 \r
             labels.Add(line.Label, lineLabel);\r
         }\r
@@ -55,7 +57,8 @@ namespace mbasic.SyntaxTree
 \r
         public void MarkLabel(ILGenerator gen)\r
         {\r
-            gen.MarkLabel(lineLabel);\r
+            if (lineLabelAssigned)\r
+                gen.MarkLabel(lineLabel);\r
         }\r
 \r
         public abstract void Emit(ILGenerator gen, bool labelSetAlready);\r
index be0432c..349bd6f 100644 (file)
@@ -61,6 +61,7 @@ internal enum Token
     Randomize,\r
     Read,\r
     Remark, // technically not a token, and should never be returned. Used internally by lexer only.\r
+    Restore,\r
     String,\r
     Subroutine,\r
     Tab,\r
index d2ff9e7..1d67229 100644 (file)
@@ -65,6 +65,7 @@
     <Compile Include="SyntaxTree\Randomize.cs" />\r
     <Compile Include="SyntaxTree\Read.cs" />\r
     <Compile Include="SyntaxTree\RelationalExpression.cs" />\r
+    <Compile Include="SyntaxTree\Restore.cs" />\r
     <Compile Include="SyntaxTree\Statement.cs" />\r
     <Compile Include="SyntaxTree\Subroutine.cs" />\r
     <Compile Include="SyntaxTree\Subtract.cs" />\r
@@ -94,6 +95,9 @@
     <None Include="..\samples\print.mbas">\r
       <Link>samples\print.mbas</Link>\r
     </None>\r
+    <None Include="..\samples\read.mbas">\r
+      <Link>samples\read.mbas</Link>\r
+    </None>\r
     <None Include="..\samples\secretnum.mbas">\r
       <Link>samples\secretnum.mbas</Link>\r
     </None>\r
index ed4d291..d40fe50 100644 (file)
@@ -79,3 +79,6 @@
 2005 REM The next two lines should print on two different lines.\r
 2010 PRINT 75;""\r
 2020 PRINT "HELLO"\r
+\r
+2100 PRINT "TO PRINT ""QUOTE MARKS"" YOU MUST USE DOUBLE QUOTES."\r
+\r
diff --git a/samples/read.mbas b/samples/read.mbas
new file mode 100644 (file)
index 0000000..dfc2694
--- /dev/null
@@ -0,0 +1,19 @@
+99 REM Page II-61 of User's Reference Guide
+100 FOR I = 1 TO 3
+110 READ X,Y
+120 PRINT X;Y
+130 NEXT I
+140 DATA 22,15,36,52,48,96.5
+
+200 FOR I = 1 TO 2
+210 FOR J = 1 TO 4
+220 READ A,B
+230 PRINT A;B;
+240 NEXT J
+250 PRINT
+260 RESTORE 290
+270 NEXT I
+280 DATA 2,4,6,8,10
+290 DATA 12,14,16,18
+300 DATA 20,22,24,26
+310 END
index 7b466e2..cf9d054 100755 (executable)
@@ -73,4 +73,6 @@ assertEquals(" 326           79 ")
 # Other tests
 assertEquals(" 75 ");
 assertEquals("HELLO");
+assertEquals("TO PRINT \"QUOTE MARKS\" YOU M")
+assertEquals("UST USE DOUBLE QUOTES.")
 Console.WriteLine("done")
diff --git a/samples/testRead.py b/samples/testRead.py
new file mode 100644 (file)
index 0000000..94f16c4
--- /dev/null
@@ -0,0 +1,20 @@
+import clr
+from System import *
+
+lineNumber = 0
+def assertEquals(x):
+       global lineNumber
+       lineNumber = lineNumber + 1
+       input = Console.ReadLine()
+       if x.Equals(input): return
+       Console.WriteLine("Line {0}: '{1}' != '{2}'",lineNumber,x,input)
+# Page II-61
+assertEquals(" 22  15 ")
+assertEquals(" 36  52 ")
+assertEquals(" 48  96.5 ")
+
+assertEquals(" 2  4  6  8  10  12  14  16 ")
+assertEquals(" 12  14  16  18  20  22  24 ")
+assertEquals(" 26 ")
+Console.WriteLine("done")
+