From 0a0db31c066fa1bfc64f355a74928574b39f9fea Mon Sep 17 00:00:00 2001 From: Trey Tomes Date: Fri, 31 Dec 2021 16:12:31 -0600 Subject: [PATCH] SLEEP statement, LIST bug fix, and PLOT sample. --- src/ECMABasic.Core/ComplexTokenReader.cs | 34 ++++--- src/ECMABasic.Core/EnvironmentBase.cs | 11 ++- src/ECMABasic.Core/IEnvironment.cs | 11 +++ src/ECMABasic.Core/Interpreter.cs | 35 +++++--- src/ECMABasic.Core/NumericExpressionParser.cs | 18 ++++ .../Parsers/PrintStatementParser.cs | 9 +- src/ECMABasic55/ConsoleEnvironment.cs | 9 +- src/ECMABasic55/PLOT.BAS | 8 ++ .../Parsers/ListStatementParser.cs | 88 ++++++++++++++----- .../Parsers/SleepStatementParser.cs | 21 +++++ src/ECMABasic55/Program.cs | 31 ++++--- src/ECMABasic55/RuntimeInterpreter.cs | 10 +-- src/ECMABasic55/Statements/ListStatement.cs | 19 +++- src/ECMABasic55/Statements/LoadStatement.cs | 3 +- src/ECMABasic55/Statements/SleepStatement.cs | 26 ++++++ 15 files changed, 251 insertions(+), 82 deletions(-) create mode 100644 src/ECMABasic55/PLOT.BAS create mode 100644 src/ECMABasic55/Parsers/SleepStatementParser.cs create mode 100644 src/ECMABasic55/Statements/SleepStatement.cs diff --git a/src/ECMABasic.Core/ComplexTokenReader.cs b/src/ECMABasic.Core/ComplexTokenReader.cs index 1db02ca..e35efa6 100644 --- a/src/ECMABasic.Core/ComplexTokenReader.cs +++ b/src/ECMABasic.Core/ComplexTokenReader.cs @@ -16,10 +16,6 @@ namespace ECMABasic.Core /// public class ComplexTokenReader { - /// - /// The index into _tokens. - /// - private int _tokenIndex; private readonly List _tokens; private ComplexTokenReader(SimpleTokenReader reader) @@ -33,7 +29,12 @@ private ComplexTokenReader(SimpleTokenReader reader) reader.Dispose(); } - public bool IsAtEnd => _tokenIndex >= _tokens.Count; + /// + /// The index into _tokens. + /// + public int TokenIndex { get; private set; } + + public bool IsAtEnd => TokenIndex >= _tokens.Count; public static ComplexTokenReader FromFile(string filename) { @@ -158,7 +159,7 @@ public static ComplexTokenReader FromText(string text) /// Throws an exception if the token type doesn't match what was expected. public Token Next(TokenType type, bool throwOnError = true, string pattern = null) { - var startPosition = _tokenIndex; + var startPosition = TokenIndex; var token = Next(); if ((token == null) || (token.Type != type) || ((pattern != null) && !Regex.IsMatch(token.Text, @"^" + pattern + @"$", RegexOptions.Singleline))) { @@ -168,7 +169,7 @@ public Token Next(TokenType type, bool throwOnError = true, string pattern = nul } else { - _tokenIndex = startPosition; + TokenIndex = startPosition; return null; } } @@ -274,7 +275,7 @@ public Token Next() private Token ReadRestOfString(Token start) { - var startIndex = _tokenIndex; + var startIndex = TokenIndex; List stringTokens = new() { start }; while (true) @@ -283,7 +284,7 @@ private Token ReadRestOfString(Token start) if ((token == null) || (token.Type == TokenType.EndOfLine)) { // Rewind. - _tokenIndex = startIndex; + TokenIndex = startIndex; return null; } @@ -307,8 +308,8 @@ private Token Read() return null; } - var token = _tokens[_tokenIndex]; - _tokenIndex++; + var token = _tokens[TokenIndex]; + TokenIndex++; return token; } @@ -318,16 +319,21 @@ public Token Peek() { return null; } - return _tokens[_tokenIndex]; + return _tokens[TokenIndex]; + } + + public void Seek(int index) + { + TokenIndex = index; } public void Rewind() { - if (_tokenIndex == 0) + if (TokenIndex == 0) { return; } - _tokenIndex--; + TokenIndex--; } } } diff --git a/src/ECMABasic.Core/EnvironmentBase.cs b/src/ECMABasic.Core/EnvironmentBase.cs index 6fbd5b1..44100e1 100644 --- a/src/ECMABasic.Core/EnvironmentBase.cs +++ b/src/ECMABasic.Core/EnvironmentBase.cs @@ -13,12 +13,15 @@ public abstract class EnvironmentBase : IEnvironment private readonly IBasicConfiguration _config; - public EnvironmentBase(IBasicConfiguration config = null) + public EnvironmentBase(Interpreter interpreter = null, IBasicConfiguration config = null) { + Interpreter = interpreter ?? new Interpreter(); _config = config ?? MinimalBasicConfiguration.Instance; Program = new Program(); } + public Interpreter Interpreter { get; } + /// /// The line number currently being executed. /// @@ -114,5 +117,11 @@ public ICallStackContext PopCallStack() } public abstract void CheckForStopRequest(); + + public bool LoadFile(string filename) + { + Program.Clear(); + return Interpreter.InterpretProgramFromFile(this, filename); + } } } diff --git a/src/ECMABasic.Core/IEnvironment.cs b/src/ECMABasic.Core/IEnvironment.cs index 540fe99..1cd8bb5 100644 --- a/src/ECMABasic.Core/IEnvironment.cs +++ b/src/ECMABasic.Core/IEnvironment.cs @@ -5,6 +5,11 @@ /// public interface IEnvironment : IErrorReporter { + /// + /// The active interpreter. + /// + public Interpreter Interpreter { get; } + /// /// The full program, ready to execute. /// @@ -95,5 +100,11 @@ public interface IEnvironment : IErrorReporter /// Used by the Program evaluator to see if a stop has been requested. /// public void CheckForStopRequest(); + + /// + /// Load and interpret a file. + /// + /// The file to load. + public bool LoadFile(string filename); } } diff --git a/src/ECMABasic.Core/Interpreter.cs b/src/ECMABasic.Core/Interpreter.cs index b1abc76..1019177 100644 --- a/src/ECMABasic.Core/Interpreter.cs +++ b/src/ECMABasic.Core/Interpreter.cs @@ -17,11 +17,9 @@ public class Interpreter protected ComplexTokenReader _reader; private readonly IBasicConfiguration _config; - protected readonly IEnvironment _env; - public Interpreter(IEnvironment env, IBasicConfiguration config = null) + public Interpreter(IBasicConfiguration config = null) { - _env = env; _config = config ?? MinimalBasicConfiguration.Instance; _lineStatements = new List() @@ -49,8 +47,8 @@ public Interpreter(IEnvironment env, IBasicConfiguration config = null) /// Was the input interpreted successfully? public static bool FromText(string text, IEnvironment env, IBasicConfiguration config = null) { - var interpreter = new Interpreter(env, config); - return interpreter.InterpretProgramFromText(text); + var interpreter = new Interpreter(config); + return interpreter.InterpretProgramFromText(env, text); } /// @@ -61,8 +59,17 @@ public static bool FromText(string text, IEnvironment env, IBasicConfiguration c /// Was the input interpreted successfully? public static bool FromFile(string path, IEnvironment env, IBasicConfiguration config = null) { - var interpreter = new Interpreter(env, config); - return interpreter.InterpretProgramFromFile(path); + var interpreter = new Interpreter(config); + return interpreter.InterpretProgramFromFile(env, path); + } + + /// + /// Allow interpretation of additional statements. + /// + /// The statements to add to the interpreter. + public void InjectStatements(IEnumerable additionalStatements) + { + _lineStatements.AddRange(additionalStatements); } /// @@ -71,10 +78,10 @@ public static bool FromFile(string path, IEnvironment env, IBasicConfiguration c /// The path to the file to interpret. /// A receiver for error messages. /// Was the input interpreted successfully? - public bool InterpretProgramFromFile(string path) + public bool InterpretProgramFromFile(IEnvironment env, string path) { _reader = ComplexTokenReader.FromFile(path); - return InterpretProgram(); + return InterpretProgram(env); } /// @@ -83,13 +90,13 @@ public bool InterpretProgramFromFile(string path) /// The text to interpret. /// A receiver for error messages. /// Was the input interpreted successfully? - public bool InterpretProgramFromText(string text) + public bool InterpretProgramFromText(IEnvironment env, string text) { _reader = ComplexTokenReader.FromText(text); - return InterpretProgram(); + return InterpretProgram(env); } - private bool InterpretProgram() + private bool InterpretProgram(IEnvironment env) { try { @@ -102,7 +109,7 @@ private bool InterpretProgram() } else { - _env.Program.Insert(line); + env.Program.Insert(line); } } @@ -115,7 +122,7 @@ private bool InterpretProgram() } catch (SyntaxException e) { - _env.ReportError(e.Message); + env.ReportError(e.Message); return false; } } diff --git a/src/ECMABasic.Core/NumericExpressionParser.cs b/src/ECMABasic.Core/NumericExpressionParser.cs index 09089f3..4b83c41 100644 --- a/src/ECMABasic.Core/NumericExpressionParser.cs +++ b/src/ECMABasic.Core/NumericExpressionParser.cs @@ -130,6 +130,7 @@ private IExpression ParseSums() { var space = _reader.Next(TokenType.Space, false); + var preSymbolIndex = _reader.TokenIndex; var symbol = _reader.Next(TokenType.Symbol, false, @"\+|\-"); if (symbol == null) { @@ -143,6 +144,11 @@ private IExpression ParseSums() _reader.Next(TokenType.Space, false); var right = ParseProducts(); + if (right == null) + { + _reader.Seek(preSymbolIndex); + return expr; + } expr = symbol.Text switch { @@ -165,6 +171,7 @@ private IExpression ParseProducts() { var space = _reader.Next(TokenType.Space, false); + var preSymbolIndex = _reader.TokenIndex; var symbol = _reader.Next(TokenType.Symbol, false, @"\*|\/"); if (symbol == null) { @@ -178,6 +185,11 @@ private IExpression ParseProducts() _reader.Next(TokenType.Space, false); var right = ParseUnary(); + if (right == null) + { + _reader.Seek(preSymbolIndex); + return expr; + } expr = symbol.Text switch { @@ -220,6 +232,7 @@ private IExpression ParseInvolution() { var space = _reader.Next(TokenType.Space, false); + var preSymbolIndex = _reader.TokenIndex; var symbol = _reader.Next(TokenType.Symbol, false, @"\^"); if (symbol == null) { @@ -233,6 +246,11 @@ private IExpression ParseInvolution() _reader.Next(TokenType.Space, false); var right = ParseAtomic(true); + if (right == null) + { + _reader.Seek(preSymbolIndex); + return expr; + } expr = new InvolutionExpression(expr, right); } diff --git a/src/ECMABasic.Core/Parsers/PrintStatementParser.cs b/src/ECMABasic.Core/Parsers/PrintStatementParser.cs index 2114e11..c0bd4f0 100644 --- a/src/ECMABasic.Core/Parsers/PrintStatementParser.cs +++ b/src/ECMABasic.Core/Parsers/PrintStatementParser.cs @@ -76,18 +76,13 @@ private static IPrintItemSeparator ProcessPrintSeparator(ComplexTokenReader read private static IPrintItem ProcessPrintItem(ComplexTokenReader reader, int? lineNumber = null) { - //if (lineNumber == 680) - //{ - // var a = 0; - //} - - IPrintItem expr = ParseExpression(reader, lineNumber, false); + var expr = ProcessTabExpression(reader, lineNumber); if (expr != null) { return expr; } - expr = ProcessTabExpression(reader, lineNumber); + expr = ParseExpression(reader, lineNumber, false); if (expr != null) { return expr; diff --git a/src/ECMABasic55/ConsoleEnvironment.cs b/src/ECMABasic55/ConsoleEnvironment.cs index 99233d2..ec82899 100644 --- a/src/ECMABasic55/ConsoleEnvironment.cs +++ b/src/ECMABasic55/ConsoleEnvironment.cs @@ -1,12 +1,17 @@ using ECMABasic.Core; +using ECMABasic.Core.Configuration; using ECMABasic.Core.Exceptions; -using ECMABasic.Core.Statements; using System; namespace ECMABasic55 { - class ConsoleEnvironment : EnvironmentBase + public class ConsoleEnvironment : EnvironmentBase { + public ConsoleEnvironment(Interpreter interpreter = null, IBasicConfiguration config = null) + : base(interpreter, config) + { + } + public override int TerminalRow { get diff --git a/src/ECMABasic55/PLOT.BAS b/src/ECMABasic55/PLOT.BAS new file mode 100644 index 0000000..d2ec20c --- /dev/null +++ b/src/ECMABasic55/PLOT.BAS @@ -0,0 +1,8 @@ +10 LET P=3.14159265359 +20 LET A=0 +30 LET R=((COS(A*(P/180))+1)*16)+8 +50 SLEEP 100 +40 PRINT TAB(R);"*" +80 LET A=A+16 +90 GOTO 30 +100 END diff --git a/src/ECMABasic55/Parsers/ListStatementParser.cs b/src/ECMABasic55/Parsers/ListStatementParser.cs index 395e5b3..8ddebab 100644 --- a/src/ECMABasic55/Parsers/ListStatementParser.cs +++ b/src/ECMABasic55/Parsers/ListStatementParser.cs @@ -1,5 +1,6 @@ using ECMABasic.Core; using ECMABasic.Core.Configuration; +using ECMABasic.Core.Exceptions; using ECMABasic.Core.Expressions; using ECMABasic55.Statements; using System; @@ -28,39 +29,80 @@ public override IStatement Parse(ComplexTokenReader reader, int? lineNumber = nu return new ListStatement(null, null); } - var endToken = reader.Next(TokenType.Symbol, false, @"\-"); - if (endToken != null) - { - var onlyToExpr = ParseNumericExpression(reader, lineNumber, true) as NumberExpression; - if (onlyToExpr.Value < 0) - { - throw new Exception("LINE NUMBER MUST BE > 0"); - } - return new ListStatement(null, new IntegerExpression((int)onlyToExpr.Value)); - } + var expr = ParseNumericExpression(reader, lineNumber, true); - var fromExpr = ParseNumericExpression(reader, lineNumber, false) as NumberExpression; - if (fromExpr.Value < 0) + if (expr is SubtractionExpression) { - throw new Exception("LINE NUMBER MUST BE > 0"); + var from = (expr as SubtractionExpression).Left; + var to = (expr as SubtractionExpression).Right; + return new ListStatement(from, to); } - - endToken = reader.Next(TokenType.Symbol, false, @"\-"); - if (endToken == null) + else if (expr is NegationExpression) { - return new ListStatement(new IntegerExpression((int)fromExpr.Value), null); + var to = (expr as NegationExpression).Root; + return new ListStatement(null, to); } - - if (ParseNumericExpression(reader, lineNumber, false) is not NumberExpression toExpr) + else if (expr is NumberExpression) { - toExpr = new NumberExpression(_config.MaxLineNumber); + var val = (expr as NumberExpression).Value; + if (val < 0) + { + return new ListStatement(null, (expr as NumberExpression).Negate()); + } + else + { + var from = expr; + var minus = reader.Next(TokenType.Symbol, false, @"\-"); + if (minus != null) + { + var to = new NumberExpression(_config.MaxLineNumber); + return new ListStatement(from, to); + } + else + { + return new ListStatement(from, null); + } + } } - else if (toExpr.Value < 0) + else { - throw new Exception("LINE NUMBER MUST BE > 0"); + throw new SyntaxException("EXPECTED A LINE NUMBER"); } - return new ListStatement(new IntegerExpression((int)fromExpr.Value), new IntegerExpression((int)toExpr.Value)); + //var endToken = reader.Next(TokenType.Symbol, false, @"\-"); + //if (endToken != null) + //{ + // var onlyToExpr = ParseNumericExpression(reader, lineNumber, true) as NumberExpression; + // if (onlyToExpr.Value < 0) + // { + // throw new Exception("LINE NUMBER MUST BE > 0"); + // } + // return new ListStatement(null, new IntegerExpression((int)onlyToExpr.Value)); + //} + + ////var fromExpr = ParseNumericExpression(reader, lineNumber, false) as NumberExpression; + //var fromExpr = new NumericExpressionParser(reader, lineNumber, false).ParseLiteral(); + //if (fromExpr.Text) < 0) + //{ + // throw new Exception("LINE NUMBER MUST BE > 0"); + //} + + //endToken = reader.Next(TokenType.Symbol, false, @"\-"); + //if (endToken == null) + //{ + // return new ListStatement(new IntegerExpression((int)fromExpr.Value), null); + //} + + //if (ParseNumericExpression(reader, lineNumber, false) is not NumberExpression toExpr) + //{ + // toExpr = new NumberExpression(_config.MaxLineNumber); + //} + //else if (toExpr.Value < 0) + //{ + // throw new Exception("LINE NUMBER MUST BE > 0"); + //} + + //return new ListStatement(new IntegerExpression((int)fromExpr.Value), new IntegerExpression((int)toExpr.Value)); } } } diff --git a/src/ECMABasic55/Parsers/SleepStatementParser.cs b/src/ECMABasic55/Parsers/SleepStatementParser.cs new file mode 100644 index 0000000..a0693a1 --- /dev/null +++ b/src/ECMABasic55/Parsers/SleepStatementParser.cs @@ -0,0 +1,21 @@ +using ECMABasic.Core; +using ECMABasic55.Statements; + +namespace ECMABasic55.Parsers +{ + public class SleepStatementParser : StatementParser + { + public override IStatement Parse(ComplexTokenReader reader, int? lineNumber = null) + { + if (reader.Next(TokenType.Word, false, @"SLEEP") == null) + { + return null; + } + + ProcessSpace(reader, true); + + var milliseconds = ParseNumericExpression(reader, lineNumber, true); + return new SleepStatement(milliseconds); + } + } +} diff --git a/src/ECMABasic55/Program.cs b/src/ECMABasic55/Program.cs index 7379204..37adc95 100644 --- a/src/ECMABasic55/Program.cs +++ b/src/ECMABasic55/Program.cs @@ -1,20 +1,20 @@ using ECMABasic.Core; +using ECMABasic55.Parsers; using System; +using System.Collections.Generic; using System.IO; namespace ECMABasic55 { - // TODO: Implement SAVE. - public static class Program { - private static readonly IEnvironment _env = new ConsoleEnvironment(); + private static readonly List _additionalStatements = new() + { + new SleepStatementParser(), + }; public static int Main(string[] args) { - _env.PrintLine(RuntimeConfiguration.Instance.Preamble); - _env.PrintLine(); - if (args.Length == 1) { return RunBatch(args[0]); @@ -27,14 +27,19 @@ public static int Main(string[] args) private static int RunBatch(string path) { + IEnvironment env = new ConsoleEnvironment(); + env.Interpreter.InjectStatements(_additionalStatements); + env.PrintLine(RuntimeConfiguration.Instance.Preamble); + env.PrintLine(); + if (!File.Exists(path)) { throw new FileNotFoundException(null, path); } - if (Interpreter.FromFile(path, _env)) + if (env.LoadFile(path)) { - _env.Program.Execute(_env); + env.Program.Execute(env); return 0; } else @@ -45,7 +50,11 @@ private static int RunBatch(string path) private static int RunREPL() { - var interpreter = new RuntimeInterpreter(_env); + IEnvironment env = new ConsoleEnvironment(new RuntimeInterpreter()); + env.Interpreter.InjectStatements(_additionalStatements); + env.PrintLine(RuntimeConfiguration.Instance.Preamble); + env.PrintLine(); + var isRunning = true; Console.WriteLine("OK"); @@ -55,10 +64,10 @@ private static int RunREPL() try { - var statement = interpreter.ProcessImmediate(line); + var statement = (env.Interpreter as RuntimeInterpreter).ProcessImmediate(env, line); if (statement != null) { - statement.Execute(_env, true); + statement.Execute(env, true); Console.WriteLine(); Console.WriteLine("OK"); } diff --git a/src/ECMABasic55/RuntimeInterpreter.cs b/src/ECMABasic55/RuntimeInterpreter.cs index 268e483..f682734 100644 --- a/src/ECMABasic55/RuntimeInterpreter.cs +++ b/src/ECMABasic55/RuntimeInterpreter.cs @@ -14,8 +14,8 @@ public class RuntimeInterpreter : Interpreter { private readonly List _immediateStatements; - public RuntimeInterpreter(IEnvironment env, IBasicConfiguration config = null) - : base(env, config) + public RuntimeInterpreter(IBasicConfiguration config = null) + : base(config) { _immediateStatements = new List() { @@ -28,7 +28,7 @@ public RuntimeInterpreter(IEnvironment env, IBasicConfiguration config = null) }; } - public IStatement ProcessImmediate(string text) + public IStatement ProcessImmediate(IEnvironment env, string text) { try { @@ -39,11 +39,11 @@ public IStatement ProcessImmediate(string text) { if (line.Statement != null) { - _env.Program.Insert(line); + env.Program.Insert(line); } else { - _env.Program.Delete(line.LineNumber); + env.Program.Delete(line.LineNumber); } return null; } diff --git a/src/ECMABasic55/Statements/ListStatement.cs b/src/ECMABasic55/Statements/ListStatement.cs index ed6b1ee..b7cab89 100644 --- a/src/ECMABasic55/Statements/ListStatement.cs +++ b/src/ECMABasic55/Statements/ListStatement.cs @@ -1,5 +1,7 @@ using ECMABasic.Core; +using ECMABasic.Core.Configuration; using ECMABasic.Core.Exceptions; +using System; using System.Linq; using System.Text; @@ -7,8 +9,11 @@ namespace ECMABasic55.Statements { public class ListStatement : IStatement { - public ListStatement(IExpression from, IExpression to) + private readonly IBasicConfiguration _config; + + public ListStatement(IExpression from, IExpression to, IBasicConfiguration config = null) { + _config = config ?? MinimalBasicConfiguration.Instance; From = from; To = to; } @@ -28,7 +33,11 @@ public void Execute(IEnvironment env, bool isImmediate) return; } - var fromLineNumber = (int)((From == null) ? env.Program.First().LineNumber : From.Evaluate(env)); + var fromLineNumber = (int)((From == null) ? env.Program.First().LineNumber : Convert.ToInt32(From.Evaluate(env))); + if (fromLineNumber < 0) + { + throw new RuntimeException("LINE NUMBER OUT OF RANGE"); + } if ((From != null) && (To == null)) { @@ -37,7 +46,11 @@ public void Execute(IEnvironment env, bool isImmediate) return; } - var toLineNumber = (int)((To == null) ? env.Program.Last().LineNumber : To.Evaluate(env)); + var toLineNumber = (To == null) ? env.Program.Last().LineNumber : Convert.ToInt32(To.Evaluate(env)); + if ((toLineNumber < fromLineNumber) || (toLineNumber > _config.MaxLineNumber)) + { + throw new RuntimeException("LINE NUMBER OUT OF RANGE"); + } foreach (var line in env.Program) { diff --git a/src/ECMABasic55/Statements/LoadStatement.cs b/src/ECMABasic55/Statements/LoadStatement.cs index 98f0f19..0567115 100644 --- a/src/ECMABasic55/Statements/LoadStatement.cs +++ b/src/ECMABasic55/Statements/LoadStatement.cs @@ -28,8 +28,7 @@ public void Execute(IEnvironment env, bool isImmediate) return; } - env.Program.Clear(); - Interpreter.FromFile(path, env); + env.LoadFile(path); } public string ToListing() diff --git a/src/ECMABasic55/Statements/SleepStatement.cs b/src/ECMABasic55/Statements/SleepStatement.cs new file mode 100644 index 0000000..2097da4 --- /dev/null +++ b/src/ECMABasic55/Statements/SleepStatement.cs @@ -0,0 +1,26 @@ +using ECMABasic.Core; +using System; +using System.Threading; + +namespace ECMABasic55.Statements +{ + public class SleepStatement : IStatement + { + public SleepStatement(IExpression milliseconds) + { + Milliseconds = milliseconds; + } + + public IExpression Milliseconds { get; } + + public void Execute(IEnvironment env, bool isImmediate) + { + Thread.Sleep(Convert.ToInt32(Milliseconds.Evaluate(env))); + } + + public string ToListing() + { + return string.Concat("SLEEP ", Milliseconds.ToListing()); + } + } +}