Added initial support for READ and DATA statements.
authorMichael Welch <michaelgwelch@gmail.com>
Thu, 28 Dec 2006 04:08:03 +0000 (04:08 +0000)
committerMichael Welch <michaelgwelch@gmail.com>
Thu, 28 Dec 2006 04:08:03 +0000 (04:08 +0000)
Lexer.cs:
Added support for reading Data token. Also we need to switch "modes" when reading a DATA statement. A DATA statement can have non-quoted strings. So I added a variable "readingData" which indicates if we are reading a DATA statement or not. Also needed to modify NextString to be able to read quoted and non-quoted strings.

mbasic.csproj:
Added Read.cs and Data.cs and data.mbas (to samples).

Parser.cs:
Added support for parsing DATA and READ statements. Also the constructor for Parser now takes a List<object>. This list is used to collect all the items found in DATA statements.

Program.cs:
Reserved the words READ and DATA. Also created a List<object> instance to pass into Parser constructor. Add a region to emit the code to generate the static list of DATA used by the READ statements.

Data.cs:
Added. A shell of a statement. It never will show up in an AST but is used to
make the parser be able to deal with DATA statements. It is a singleton class.

Read.cs:
Added. Used to represent a READ statement in an AST.

Token.cs
Add Read and Data.

BuiltIns.cs:
Fixed a problem in PrintString that resulted in zero length strings leaving a print serperator in effect.

Added AddData method for adding all of the static DATA to a list. Added ReadDouble and ReadString methods for reading from the list. Added Restore method to support RESTORE statement.

samples: Added data.mbas for testing the scenarios in DATA section of guide.
Also added testData.py for checking the results of data.mbas.

13 files changed:
TiBasicRuntime/BuiltIns.cs
mbasic/Lexer.cs
mbasic/Parser.cs
mbasic/Program.cs
mbasic/SyntaxTree/Data.cs [new file with mode: 0644]
mbasic/SyntaxTree/Read.cs [new file with mode: 0644]
mbasic/Token.cs
mbasic/mbasic.csproj
samples/data.mbas [new file with mode: 0644]
samples/helloworld.mbas
samples/print.mbas
samples/testData.py [new file with mode: 0644]
samples/testPrint.py

index a04c361..f127fb1 100644 (file)
@@ -181,7 +181,13 @@ namespace TiBasicRuntime
                 if (o is string)\r
                 {\r
                     s = o as string;\r
-                    if (s.Length == 0) continue;\r
+                    // handle zero length strings first.\r
+                    if (s.Length == 0)\r
+                    {\r
+                        printSepInEffect = false;\r
+                        continue;\r
+                    }\r
+\r
                     char ch = s[0];\r
                     switch (ch)\r
                     {\r
@@ -282,5 +288,45 @@ 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 int pos = 0;\r
+        public static void AddData(params object[] objects)\r
+        {\r
+            data.AddRange(objects);\r
+        }\r
+\r
+        public static void ReadDouble(out double d)\r
+        {\r
+            object o = data[pos];\r
+            if (o is double)\r
+            {\r
+                d = (double)o;\r
+                pos++;\r
+            }\r
+            else\r
+            {\r
+                throw new Exception("DATA ERROR");\r
+            }\r
+        }\r
+\r
+        public static void ReadString(out string s)\r
+        {\r
+            object o = data[pos];\r
+            if (o is string)\r
+            {\r
+                s = (string)o;\r
+            }\r
+            else\r
+            {\r
+                s = Radix100.ToString((double)o);\r
+            }\r
+            pos++;\r
+        }\r
+\r
+        public static void Restore()\r
+        {\r
+            pos = 0;\r
+        }\r
     }\r
 }\r
index 23c3502..86f939b 100644 (file)
@@ -37,6 +37,7 @@ namespace mbasic
         bool startOfLine;\r
         int index;\r
         string label;\r
+        bool readingData = false; // Unfortunately the grammar for BASIC is not a context free grammar, for example normally something like 'H' would be considered a variable, but after a DATA it is a string.\r
 \r
         public Lexer(Stream stream, SymbolTable symbols)\r
         {\r
@@ -76,6 +77,7 @@ namespace mbasic
                     else\r
                     {\r
                         startOfLine = true;\r
+                        readingData = false;\r
                         return Token.EndOfLine;\r
                     }\r
                 }\r
@@ -95,54 +97,65 @@ namespace mbasic
 \r
                 if (startOfLine) return Token.Error;\r
 \r
-                if (Char.IsDigit(ch) || ch == '.') return NextNumber();\r
-\r
-                if (Char.IsLetter(ch))\r
+                if (readingData) // special case, only happen if we have read Token.Data\r
                 {\r
-                    Token word = NextWord(); // keywords and variables, and remarks\r
-                    if (word != Token.Remark) return word;\r
-                    // Reset us back to start of line\r
-                    startOfLine = true; // after reading a REM we are now at start of line.\r
-                    continue;\r
+                    if (Char.IsLetter(ch)) return NextString(false);\r
+                    else if (Char.IsDigit(ch)) return NextNumber();\r
+                    else if (ch == '\"') return NextString();\r
+                    else if (ch == ',') { reader.Advance(); return Token.Comma; }\r
+                    else throw new Exception("Unexpected char" + ch.ToString());\r
                 }\r
+                else\r
+                {\r
+                    if (Char.IsDigit(ch) || ch == '.') return NextNumber();\r
 \r
-                if (ch == '\"') return NextString();\r
+                    if (Char.IsLetter(ch))\r
+                    {\r
+                        Token word = NextWord(); // keywords and variables, and remarks\r
+                        if (word == Token.Data) readingData = true;\r
+                        if (word != Token.Remark) return word;\r
+                        // Reset us back to start of line\r
+                        startOfLine = true; // after reading a REM we are now at start of line.\r
+                        continue;\r
+                    }\r
 \r
+                    if (ch == '\"') return NextString();\r
 \r
-                if (ch == '<')\r
-                {\r
-                    ch = reader.Read();\r
-                    if (ch == '>')\r
+\r
+                    if (ch == '<')\r
                     {\r
-                        reader.Advance();\r
-                        return Token.NotEquals;\r
+                        ch = reader.Read();\r
+                        if (ch == '>')\r
+                        {\r
+                            reader.Advance();\r
+                            return Token.NotEquals;\r
+                        }\r
+                        else return Token.LessThan;\r
                     }\r
-                    else return Token.LessThan;\r
-                }\r
 \r
-                switch (ch)\r
-                {\r
-                    case ',':\r
-                    case '*':\r
-                    case '/':\r
-                    case '+':\r
-                    case '-':\r
-                    case '>':\r
-                    case '=':\r
-                    case '&':\r
-                    case '^':\r
-                    case '(':\r
-                    case ')':\r
-                    case ';':\r
-                    case ':':\r
-                        reader.Advance();\r
-                        return (Token) ch;\r
-                    default:\r
-                        throw new Exception("Unexpected character: " + ch.ToString());\r
+                    switch (ch)\r
+                    {\r
+                        case ',':\r
+                        case '*':\r
+                        case '/':\r
+                        case '+':\r
+                        case '-':\r
+                        case '>':\r
+                        case '=':\r
+                        case '&':\r
+                        case '^':\r
+                        case '(':\r
+                        case ')':\r
+                        case ';':\r
+                        case ':':\r
+                            reader.Advance();\r
+                            return (Token)ch;\r
+                        default:\r
+                            throw new Exception("Unexpected character: " + ch.ToString());\r
+                    }\r
                 }\r
 \r
 \r
-\r
             }\r
         }\r
 \r
@@ -256,16 +269,24 @@ namespace mbasic
 \r
         }\r
 \r
-        private Token NextString()\r
+        private Token NextString(bool quoted)\r
         {\r
+            Char endChar = quoted ? '\"' : ',';\r
             StringBuilder bldr = new StringBuilder();\r
-            for (char ch = reader.Read(); ch != '\"'; ch = reader.Read())\r
+            if (!quoted) bldr.Append(reader.Current);\r
+            for (char ch = reader.Read(); ch != endChar; ch = reader.Read())\r
             {\r
                 bldr.Append(ch);\r
             }\r
-            reader.Advance(); // to consume the quotation mark\r
+            if (quoted) reader.Advance(); // to consume the quotation mark\r
             value = bldr.ToString();\r
             return Token.String;\r
+\r
+        }\r
+\r
+        private Token NextString()\r
+        {\r
+            return NextString(true);\r
         }\r
 \r
         public LineId LineId { get { return new LineId(reader.LineNumber, label); } }\r
index 0f557c0..c6f7497 100644 (file)
@@ -33,10 +33,12 @@ namespace mbasic
     internal class Parser\r
     {\r
         public Lexer lexer;\r
+        List<object> data;\r
         Token lookahead;\r
-        public Parser(Stream stream, SymbolTable symbols)\r
+        public Parser(Stream stream, SymbolTable symbols, List<object> data)\r
         {\r
             lexer = new Lexer(stream, symbols);\r
+            this.data = data;\r
         }\r
 \r
         public Statement Parse()\r
@@ -91,12 +93,40 @@ namespace mbasic
                 case Token.End:\r
                     retVal = EndStatement();\r
                     break;\r
+                case Token.Data:\r
+                    retVal = DataStatement();\r
+                    break;\r
+                case Token.Read:\r
+                    retVal = ReadStatement();\r
+                    break;\r
 \r
             }\r
             Match(Token.EndOfLine);\r
             return retVal;\r
         }\r
 \r
+        private Statement ReadStatement()\r
+        {\r
+            Match(Token.Read);\r
+            LineId line = lexer.LineId;\r
+            List<int> indexes = new List<int>();\r
+            while (lookahead != Token.EOF && lookahead != Token.EndOfLine)\r
+            {\r
+                indexes.Add(lexer.SymbolIndex); // The variable to read into.\r
+                Match(Token.Variable);\r
+                if (lookahead == Token.Comma) Match(Token.Comma);\r
+            }\r
+            Read read = new Read(indexes.ToArray(), line);\r
+            return read;\r
+        }\r
+\r
+        private Statement DataStatement()\r
+        {\r
+            Match(Token.Data);\r
+            data.AddRange(DataList());\r
+            return Data.Instance;\r
+        }\r
+\r
         private Statement EndStatement()\r
         {\r
             LineId line = lexer.LineId;\r
@@ -331,6 +361,34 @@ namespace mbasic
             }\r
         }\r
 \r
+        object[] DataList()\r
+        {\r
+            if (lookahead == Token.EndOfLine || lookahead == Token.EOF) return new object[0];\r
+            List<object> items = new List<object>();\r
+            while (lookahead != Token.EndOfLine && lookahead != Token.EOF)\r
+            {\r
+                switch(lookahead)\r
+                {\r
+                    case Token.Number:\r
+                        items.Add(lexer.NumericValue);\r
+                        Match(Token.Number);\r
+                        break;\r
+                    case Token.String:\r
+                        items.Add(lexer.Value);\r
+                        Match(Token.String);\r
+                        break;\r
+                    case Token.Comma:\r
+                        items.Add(String.Empty);\r
+                        break;\r
+                    default:\r
+                        throw new Exception(String.Format(\r
+                            "DATA ERROR ON {0}", lexer.LineId.Label));\r
+                }\r
+                if (lookahead == Token.Comma) Match(Token.Comma);\r
+            }\r
+            return items.ToArray();\r
+        }\r
+\r
         Expression[] ArgList()\r
         {\r
             if (lookahead == Token.EndOfLine || lookahead == Token.EOF\r
index f659e8e..a1e969e 100644 (file)
@@ -32,6 +32,7 @@ using System.Diagnostics;
 namespace mbasic\r
 {\r
     using LabelList = System.Collections.Generic.SortedList<string, Label>;\r
+    using TiBasicRuntime;\r
 \r
     class Program\r
     {\r
@@ -48,7 +49,8 @@ namespace mbasic
             Stream stream = File.OpenRead(fileName);\r
             SymbolTable symbols = new SymbolTable();\r
             InitializeReservedWords(symbols);\r
-            Parser parser = new Parser(stream, symbols);\r
+            List<object> data = new List<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
             Statement n = parser.Parse();\r
@@ -69,7 +71,7 @@ namespace mbasic
             }\r
             ModuleBuilder mbldr = bldr.DefineDynamicModule(moduleName, exeName, debug);\r
 \r
-            TypeBuilder typeBuilder = mbldr.DefineType("TiBasic.Program", TypeAttributes.BeforeFieldInit \r
+            TypeBuilder typeBuilder = mbldr.DefineType(assemblyName + ".Program", TypeAttributes.BeforeFieldInit \r
                 | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.Abstract);\r
 \r
 \r
@@ -97,6 +99,33 @@ namespace mbasic
 \r
             n.CheckTypes();\r
             n.RecordLabels(gen);\r
+\r
+            #region Create Static DATA data\r
+            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
+                {\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
+                    {\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
+\r
+                gen.Emit(OpCodes.Call, addDataMethod);\r
+            }\r
+            #endregion\r
+\r
             // Emit try\r
             Label end = gen.BeginExceptionBlock();\r
             Node.endLabel = end;\r
@@ -146,6 +175,8 @@ namespace mbasic
             symbols.ReserveWord("END", Token.End);\r
             symbols.ReserveWord("STOP", Token.End);\r
             symbols.ReserveWord("TAB", Token.Tab);\r
+            symbols.ReserveWord("DATA", Token.Data);\r
+            symbols.ReserveWord("READ", Token.Read);\r
 \r
             // String Functionis\r
             symbols.ReserveWord("ASC", Token.Function);\r
diff --git a/mbasic/SyntaxTree/Data.cs b/mbasic/SyntaxTree/Data.cs
new file mode 100644 (file)
index 0000000..bc3e8b9
--- /dev/null
@@ -0,0 +1,28 @@
+using System;\r
+using System.Collections.Generic;\r
+using System.Text;\r
+\r
+namespace mbasic.SyntaxTree\r
+{\r
+    /// <summary>\r
+    /// Represents a DATA statement. \r
+    /// </summary>\r
+    /// <remarks>\r
+    /// While this is categorized as a Statement, it is never actually stored \r
+    /// in an abstract syntax tree. It is defined as a statement only for ease of \r
+    /// parsing. The data pulled out of a DATA statement contains numeric and string literals\r
+    /// which are used to construct static data needed by the program.\r
+    /// </remarks>\r
+    class Data : Statement\r
+    {\r
+        private Data() : base(LineId.None) { }\r
+        public static readonly Data Instance = new Data();\r
+        public override void CheckTypes()\r
+        {\r
+        }\r
+\r
+        public override void Emit(System.Reflection.Emit.ILGenerator gen, bool labelSetAlready)\r
+        {\r
+        }\r
+    }\r
+}\r
diff --git a/mbasic/SyntaxTree/Read.cs b/mbasic/SyntaxTree/Read.cs
new file mode 100644 (file)
index 0000000..4b7337f
--- /dev/null
@@ -0,0 +1,59 @@
+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 Read : Statement\r
+    {\r
+        int[] indexes;\r
+        BasicType[] varType;\r
+\r
+        private static readonly MethodInfo readNumberMethod =\r
+            typeof(BuiltIns).GetMethod("ReadDouble");\r
+\r
+        private static readonly MethodInfo readStringMethod =\r
+            typeof(BuiltIns).GetMethod("ReadString");\r
+\r
+        public Read(int[] indexes, LineId line)\r
+            : base(line)\r
+        {\r
+            this.indexes = indexes;\r
+            varType = new BasicType[indexes.Length];\r
+        }\r
+\r
+        public override void CheckTypes()\r
+        {\r
+            for (int i = 0; i < indexes.Length; i++)\r
+            {\r
+                Type t = locals[indexes[i]].LocalType;\r
+\r
+                if (t == typeof(string)) varType[i] = BasicType.String;\r
+                else varType[i] = BasicType.Number;\r
+            }\r
+        }\r
+\r
+        public override void Emit(ILGenerator gen, bool labelSetAlready)\r
+        {\r
+            if (!labelSetAlready) MarkLabel(gen);\r
+            MarkSequencePoint(gen);\r
+            for (int i = 0; i < indexes.Length; i++)\r
+            {\r
+                gen.Emit(OpCodes.Ldloca, locals[indexes[i]]);\r
+\r
+                if (varType[i] == BasicType.Number)\r
+                {\r
+                    gen.Emit(OpCodes.Call, readNumberMethod);\r
+                }\r
+                else if (varType[i] == BasicType.String)\r
+                {\r
+                    gen.Emit(OpCodes.Call, readStringMethod);\r
+                }\r
+            }\r
+                \r
+        }\r
+    }\r
+}\r
index 84138a6..be0432c 100644 (file)
@@ -37,6 +37,7 @@ internal enum Token
     Semicolon   = ';',\r
     And         = 256,\r
     Call,\r
+    Data,\r
     Else,\r
     End, // used for keywords END and STOP\r
     EndOfLine,\r
@@ -58,6 +59,7 @@ internal enum Token
     Or,\r
     Print,\r
     Randomize,\r
+    Read,\r
     Remark, // technically not a token, and should never be returned. Used internally by lexer only.\r
     String,\r
     Subroutine,\r
index 4f62ee7..d2ff9e7 100644 (file)
@@ -44,6 +44,7 @@
     <Compile Include="SyntaxTree\BasicType.cs" />\r
     <Compile Include="SyntaxTree\Block.cs" />\r
     <Compile Include="SyntaxTree\Concatenate.cs" />\r
+    <Compile Include="SyntaxTree\Data.cs" />\r
     <Compile Include="SyntaxTree\Division.cs" />\r
     <Compile Include="SyntaxTree\End.cs" />\r
     <Compile Include="SyntaxTree\Equals.cs" />\r
@@ -62,6 +63,7 @@
     <Compile Include="SyntaxTree\Node.cs" />\r
     <Compile Include="SyntaxTree\Power.cs" />\r
     <Compile Include="SyntaxTree\Randomize.cs" />\r
+    <Compile Include="SyntaxTree\Read.cs" />\r
     <Compile Include="SyntaxTree\RelationalExpression.cs" />\r
     <Compile Include="SyntaxTree\Statement.cs" />\r
     <Compile Include="SyntaxTree\Subroutine.cs" />\r
@@ -80,6 +82,9 @@
     </ProjectReference>\r
   </ItemGroup>\r
   <ItemGroup>\r
+    <None Include="..\samples\data.mbas">\r
+      <Link>samples\data.mbas</Link>\r
+    </None>\r
     <None Include="..\samples\ForStatementTest.mbas">\r
       <Link>samples\ForStatementTest.mbas</Link>\r
     </None>\r
diff --git a/samples/data.mbas b/samples/data.mbas
new file mode 100644 (file)
index 0000000..8f19fd6
--- /dev/null
@@ -0,0 +1,25 @@
+99 REM Scenarios from page II-63 of User's Reference Guide (DATA statement)\r
+100 for i = 1 to 5\r
+110 read a,b\r
+120 print a;b\r
+130 next i\r
+140 data 2,4,6,7,8\r
+150 data 1,2,3,4,5\r
+\r
+200 read A$,B$,C,D\r
+210 print a$:B$:C:d\r
+220 data HELLO, "JONES, MARY",28,3.1416\r
+\r
+300 read A$,B$,C\r
+310 data HI,,2\r
+320 print "A$ IS ";A$\r
+330 print "B$ IS ";B$\r
+340 print "C IS ";C\r
+\r
+400 REM Some scenarios of my own \r
+410 read A$,B$,C$,D\r
+420 print "A$ IS ";A$;"."\r
+430 print "B$ IS ";B$;"."\r
+440 print "C$ IS ";C$;"."\r
+450 print "D IS ";d;"."\r
+460 data this is the first string,,,5\r
index 2b823c4..7c42000 100644 (file)
@@ -1,5 +1,15 @@
 50 REM PRINT Statements\r
+75 data 33, 21.45,  "hell0"\r
+80 Read X\r
+81 Read Y\r
+82 Read Z$\r
+83 print X;Y;Z$\r
 \r
+84 READ X\r
+85 READ Y\r
+86 READ Z$\r
+87 print X;Y;Z$\r
+88 DATA 19, 22, ""\r
 100 print -10\r
 105 print 7.1\r
 106 print "abcdefghijklmnopqrstuvwxyzabcdefghijk"\r
index 58334e2..ed4d291 100644 (file)
 1130 PRINT A,B\r
 1140 PRINT A;TAB(15),B\r
 1150 PRINT TAB(C2);A;TAB(C2+1);B\r
-1160 PRINT A;TAB(3+2*20);B
\ No newline at end of file
+1160 PRINT A;TAB(3+2*20);B\r
+\r
+\r
+2000 REM Scenarios to test problems I've found along the way\r
+2005 REM The next two lines should print on two different lines.\r
+2010 PRINT 75;""\r
+2020 PRINT "HELLO"\r
diff --git a/samples/testData.py b/samples/testData.py
new file mode 100644 (file)
index 0000000..1f5521f
--- /dev/null
@@ -0,0 +1,28 @@
+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-63
+assertEquals(" 2  4 ")
+assertEquals(" 6  7 ")
+assertEquals(" 8  1 ")
+assertEquals(" 2  3 ")
+assertEquals(" 4  5 ")
+assertEquals("HELLO")
+assertEquals("JONES, MARY")
+assertEquals(" 28 ")
+assertEquals(" 3.1416 ")
+assertEquals("A$ IS HI")
+assertEquals("B$ IS ")
+assertEquals("C IS  2 ")
+assertEquals("A$ IS ")
+assertEquals("this is the first string.")
+assertEquals("B$ IS .")
+assertEquals("C$ IS .")
+assertEquals("D IS  5 .")
index bd15980..7b466e2 100755 (executable)
@@ -69,4 +69,8 @@ assertEquals("     326 ")
 assertEquals("      79 ")
 assertEquals(" 326           79 ")
 
+
+# Other tests
+assertEquals(" 75 ");
+assertEquals("HELLO");
 Console.WriteLine("done")