diff --git a/.vscode/settings.json b/.vscode/settings.json index 0185ccb..a5d9762 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "dotnetCoreExplorer.logpanel": true, "dotnetCoreExplorer.searchpatterns": "test/**/bin/**/*.{dll,exe}", "terminal.integrated.fontFamily": "monospace, fixed", - "terminal.integrated.fontWeight": "normal" + "terminal.integrated.fontWeight": "normal", + "cSpell.words": [ + "irules" + ] } \ No newline at end of file diff --git a/irules.core/Clause.cs b/irules.core/Clause.cs index 551cfb2..55437e9 100644 --- a/irules.core/Clause.cs +++ b/irules.core/Clause.cs @@ -4,32 +4,42 @@ using System.Data; using System.Drawing; using System.Linq; using System.Text; +using Irony.Interpreter.Ast; using Irony.Parsing; namespace irules.core { - public class Clause : Grammar - { - public Clause() - { - var comment = new CommentTerminal("comment", "#", "\n"); - this.NonGrammarTerminals.Add(comment); - NonTerminal E = new NonTerminal("E"); - NonTerminal T = new NonTerminal("T"); - NonTerminal F = new NonTerminal("F"); - NonTerminal L = new NonTerminal("L"); - NonTerminal S = new NonTerminal("S"); - IdentifierTerminal id = new IdentifierTerminal("Id"); - - E.Rule = "(" + E + ")" | E + "|" + T | T; - T.Rule = T + "&" + F | F; - F.Rule = "^" + E | S; - S.Rule = id | id + "(" + L + ")"; - L.Rule = id | L + "," + id; - this.RegisterBracePair("(",")"); - this.RegisterOperators(30,"&|^"); - - this.Root = E; - } - } + [Language("IRule", "0.1", "Clauses grammar.")] + public class ClauseGrammar : Grammar + { + public ClauseGrammar() + { + var comment = new CommentTerminal("comment", "#", "\n"); + NonGrammarTerminals.Add(comment); + NonTerminal expr = new NonTerminal("expr"); + NonTerminal singleByOr = new NonTerminal("singleByOr"); + NonTerminal singleByAnd = new NonTerminal("singleByAnd"); + NonTerminal idList = new NonTerminal("idList"); + NonTerminal idOrRelation = new NonTerminal("idOrRelation"); + NonTerminal SOrOr = new NonTerminal("SOrOr"); + IdentifierTerminal id = new IdentifierTerminal("Id"); + var orExpr = new NonTerminal("OrExpr", typeof(BinaryOperationNode)); + var andExpr = new NonTerminal("AndExpr", typeof(BinaryOperationNode)); + var neg = new NonTerminal("Negation", typeof(UnaryOperationNode)) + { + Rule = "^" + andExpr + ReduceHere() + }; + expr.Rule = "(" + expr + ")" | SOrOr ; + SOrOr.Rule = orExpr | singleByOr; + orExpr.Rule = expr + "|" + expr; + singleByOr.Rule = andExpr | singleByAnd; + andExpr.Rule = expr + "&" + expr; + singleByAnd.Rule = neg | idOrRelation; + idOrRelation.Rule = id | id + "(" + idList + ")"; + idList.Rule = id | idList + "," + id; + RegisterBracePair("(", ")"); + RegisterOperators(30, "&|^"); + Root = expr; + } + } } diff --git a/irules.core/ClauseChecker.cs b/irules.core/ClauseChecker.cs new file mode 100644 index 0000000..224cd00 --- /dev/null +++ b/irules.core/ClauseChecker.cs @@ -0,0 +1,37 @@ +using Irony.Interpreter; +using Irony.Parsing; + +namespace irules.core +{ + public class ClauseChecker + { + ScriptApp app; + public ClauseChecker() + { + ClauseGrammar g = new ClauseGrammar(); + LanguageData d = new LanguageData(g); + LanguageRuntime r = new LanguageRuntime(d); + app = new ScriptApp(r); + } + + public virtual AppStatus Parse(string problem) + { + app.Evaluate(problem); + try { + switch (app.Status) + { + case AppStatus.Ready: + var pm = app.GetParserMessages(); + var output = app.GetOutput(); + break; + } + } + catch (ScriptException ex) + { + // TODO display App Status + } + return app.Status; + } + + } +} \ No newline at end of file diff --git a/samples/red/App.axaml b/samples/red/App.axaml index f07e6e2..28d80f0 100644 --- a/samples/red/App.axaml +++ b/samples/red/App.axaml @@ -1,7 +1,7 @@ + RequestedThemeVariant="Dark"> diff --git a/samples/red/App.axaml.cs b/samples/red/App.axaml.cs index 12ea896..227b0f9 100644 --- a/samples/red/App.axaml.cs +++ b/samples/red/App.axaml.cs @@ -10,11 +10,14 @@ using System.Security; using Irony.Parsing; using Irony.Interpreter.Ast; using Irony; +using irules.core; namespace red; public partial class App : Application { + private readonly ClauseChecker checker = new ClauseChecker(); + public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -30,236 +33,3 @@ public partial class App : Application base.OnFrameworkInitializationCompleted(); } } - -#region License -/* ********************************************************************************** - * Copyright (c) Roman Ivantsov - * This source code is subject to terms and conditions of the MIT License - * for Irony. A copy of the license can be found in the License.txt file - * at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the - * MIT License. - * You must not remove this notice from this software. - * **********************************************************************************/ -#endregion - - - -public enum AppStatus -{ - Ready, - Evaluating, - WaitingMoreInput, //command line only - SyntaxError, - RuntimeError, - Crash, //interpreter crash - Aborted -} - -/// Represents a running instance of a script application. -public sealed class ScriptApp -{ - public readonly LanguageData Language; - public readonly RedLanguageRuntime Runtime; - public Parser Parser { get; private set; } - - public AppDataMap DataMap; - - public Scope[] StaticScopes; - public Scope MainScope; - public IDictionary Globals { get; private set; } - private IList ImportedAssemblies = new List(); - - // Current mode/status variables - public AppStatus Status; - public long EvaluationTime; - public Exception LastException; - public bool RethrowExceptions = true; - private ParseTree parsedScript; - - public ParseTree LastScript { get; private set; } //the root node of the last executed script - - - #region Constructors - public ScriptApp(LanguageData language) - { - Language = language; - var grammar = language.Grammar as InterpretedLanguageGrammar; - Runtime = new RedLanguageRuntime(language); - DataMap = new AppDataMap(Language.Grammar.CaseSensitive); - Init(); - } - - public ScriptApp(RedLanguageRuntime runtime) - { - Runtime = runtime; - Language = Runtime.Language; - DataMap = new AppDataMap(Language.Grammar.CaseSensitive); - Init(); - } - - public ScriptApp(AppDataMap dataMap) - { - DataMap = dataMap; - Init(); - } - - [SecuritySafeCritical] - private void Init() - { - Parser = new Parser(Language); - //Create static scopes - MainScope = new Scope(DataMap.MainModule.ScopeInfo, null, null, null); - StaticScopes = new Scope[DataMap.StaticScopeInfos.Count]; - StaticScopes[0] = MainScope; - Globals = MainScope.AsDictionary(); - } - - #endregion - - public LogMessageList GetParserMessages() - { - return Parser.Context.CurrentParseTree.ParserMessages; - } - // Utilities - public IEnumerable GetImportAssemblies() - { - //simple default case - return all assemblies loaded in domain - return AppDomain.CurrentDomain.GetAssemblies(); - } - - public ParseMode ParserMode - { - get { return Parser.Context.Mode; } - set { Parser.Context.Mode = value; } - } - - #region Evaluation - public object Evaluate(string script) - { - try - { - parsedScript = Parser.Parse(script); - if (parsedScript.HasErrors()) - { - Status = AppStatus.SyntaxError; - if (RethrowExceptions) - throw new ScriptException("Syntax errors found."); - return null; - } - - if (ParserMode == ParseMode.CommandLine && Parser.Context.Status == ParserStatus.AcceptedPartial) - { - Status = AppStatus.WaitingMoreInput; - return null; - } - LastScript = parsedScript; - var result = EvaluateParsedScript(); - return result; - } - catch (ScriptException) - { - throw; - } - catch (Exception ex) - { - this.LastException = ex; - this.Status = AppStatus.Crash; - return null; - } - } - - // Irony interpreter requires that once a script is executed in a ScriptApp, it is bound to AppDataMap object, - // and all later script executions should be performed only in the context of the same app (or at least by an App with the same DataMap). - // The reason is because the first execution sets up a data-binding fields, like slots, scopes, etc, which are bound to ScopeInfo objects, - // which in turn is part of DataMap. - public object Evaluate(ParseTree parsedScript) - { - Util.Check(parsedScript.Root.AstNode != null, "Root AST node is null, cannot evaluate script. Create AST tree first."); - var root = parsedScript.Root.AstNode as AstNode; - Util.Check(root != null, - "Root AST node {0} is not a subclass of Irony.Interpreter.AstNode. ScriptApp cannot evaluate this script.", root.GetType()); - Util.Check(root.Parent == null || root.Parent == DataMap.ProgramRoot, - "Cannot evaluate parsed script. It had been already evaluated in a different application."); - LastScript = parsedScript; - return EvaluateParsedScript(); - } - - public object Evaluate() - { - Util.Check(LastScript != null, "No previously parsed/evaluated script."); - return EvaluateParsedScript(); - } - - private object EvaluateParsedScript() - { - return EvaluateParsedScript(new ScriptThread(this)); - } - - //Actual implementation - private object EvaluateParsedScript(ScriptThread thread) - { - LastScript.Tag = DataMap; - var root = LastScript.Root.AstNode as AstNode; - root.DependentScopeInfo = MainScope.Info; - - Status = AppStatus.Evaluating; - try - { - var result = EvaluateRedScript(root, thread); - if (result != null) - thread.App.WriteLine(result.ToString()); - Status = AppStatus.Ready; - return result; - } - catch (ScriptException se) - { - Status = AppStatus.RuntimeError; - se.Location = thread.CurrentNode.Location; - se.ScriptStackTrace = thread.GetStackTrace(); - LastException = se; - if (RethrowExceptions) - throw; - return null; - } - catch (Exception ex) - { - Status = AppStatus.RuntimeError; - var se = new ScriptException(ex.Message, ex, thread.CurrentNode.Location, thread.GetStackTrace()); - LastException = se; - if (RethrowExceptions) - throw se; - return null; - - }//catch - - } - - private object EvaluateRedScript(AstNode root, ScriptThread thread) - { - throw new NotImplementedException(); - } - - private void WriteLine(string v) - { - throw new NotImplementedException(); - } - #endregion - - #region Output writing - - #region ConsoleWrite event - public event EventHandler ConsoleWrite; - private void OnConsoleWrite(string text) - { - if (ConsoleWrite != null) - { - ConsoleWriteEventArgs args = new ConsoleWriteEventArgs(text); - ConsoleWrite(this, args); - } - } - #endregion - - #endregion - -}//class \ No newline at end of file diff --git a/samples/red/MainWindow.axaml b/samples/red/MainWindow.axaml index 1c411a4..c8cb4e7 100644 --- a/samples/red/MainWindow.axaml +++ b/samples/red/MainWindow.axaml @@ -11,15 +11,20 @@ - + - + + + + + + + { + FilePickerOpenOptions options = new FilePickerOpenOptions + { + AllowMultiple = true, + Title = "Red" + }; + options.FileTypeFilter = new FilePickerFileType[] + { new("red"){ Patterns = new string[] { "*.red" } } }; + + var result = await StorageProvider.OpenFilePickerAsync(options); + + if (result != null) + { + foreach (var item in result) + { + System.Diagnostics.Debug.WriteLine($"Opened: {item.Path}"); + } + } + }); + } } \ No newline at end of file diff --git a/samples/red/RedBindingRequest.cs b/samples/red/RedBindingRequest.cs deleted file mode 100644 index 359fa9d..0000000 --- a/samples/red/RedBindingRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Irony.Interpreter; -using Irony.Interpreter.Ast; - -namespace red -{ - public class RedBindingRequest - { - private ScriptThread scriptThread; - private AstNode currentNode; - - public string Symbol { get; } - - public BindingRequestFlags Options { get; } - - public ModuleInfo FromModule { get; } - public ScopeInfo FromScopeInfo { get; } - public bool IgnoreCase { get; } - - public RedBindingRequest(ScriptThread thread, AstNode fromNode, string symbol, BindingRequestFlags flags) - - { - this.scriptThread = thread; - this.currentNode = fromNode; - Symbol = symbol; - this.Options = flags; - FromModule = thread.App.DataMap.GetModule(fromNode.ModuleNode); - FromScopeInfo = thread.CurrentScope.Info; - IgnoreCase = !thread.Runtime.Language.Grammar.CaseSensitive; - } - } -} \ No newline at end of file diff --git a/samples/red/RedLanguageRuntime.cs b/samples/red/RedLanguageRuntime.cs deleted file mode 100644 index ce43abc..0000000 --- a/samples/red/RedLanguageRuntime.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Irony.Interpreter; -using Irony.Parsing; - -namespace red -{ - public class RedLanguageRuntime - { - - public RedLanguageRuntime(LanguageData language) - { - this.Language = language; - } - - public LanguageData Language { get; internal set; } - - internal Binding Bind(RedBindingRequest request) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/samples/red/ScriptThread.cs b/samples/red/ScriptThread.cs deleted file mode 100644 index 1ab757d..0000000 --- a/samples/red/ScriptThread.cs +++ /dev/null @@ -1,105 +0,0 @@ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using Irony.Parsing; -using Irony.Interpreter.Ast; -using Irony.Interpreter; - -namespace red -{ - #region License -/* ********************************************************************************** - * Copyright (c) Roman Ivantsov - * This source code is subject to terms and conditions of the MIT License - * for Irony. A copy of the license can be found in the License.txt file - * at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the - * MIT License. - * You must not remove this notice from this software. - * **********************************************************************************/ -#endregion - - - /// Represents a running thread in script application. - public class ScriptThread { - public readonly ScriptApp App; - public readonly RedLanguageRuntime Runtime; - - public Scope CurrentScope; - public AstNode CurrentNode; - - // Tail call parameters - public ICallTarget Tail; - public object[] TailArgs; - - public ScriptThread(ScriptApp app) { - App = app; - Runtime = App.Runtime; - CurrentScope = app.MainScope; - } - - public void PushScope(ScopeInfo scopeInfo, object[] parameters) { - CurrentScope = new Scope(scopeInfo, CurrentScope, CurrentScope, parameters); - } - - - - public void PushClosureScope(ScopeInfo scopeInfo, Scope closureParent, object[] parameters) { - CurrentScope = new Scope(scopeInfo, CurrentScope, closureParent, parameters); - } - - public void PopScope() { - CurrentScope = CurrentScope.Caller; - } - - public Binding Bind(string symbol, BindingRequestFlags options) { - var request = new RedBindingRequest(this, CurrentNode, symbol, options); - var binding = Bind(request); - if (binding == null) - ThrowScriptError("Unknown symbol '{0}'.", symbol); - return binding; - } - - #region Exception handling - public object HandleError(Exception exception) { - if (exception is ScriptException) - throw exception; - var stack = GetStackTrace(); - var rex = new ScriptException(exception.Message, exception, CurrentNode.ErrorAnchor, stack); - throw rex; - } - - // Throws ScriptException exception. - public void ThrowScriptError(string message, params object[] args) { - if (args != null && args.Length > 0) - message = string.Format(message, args); - var loc = GetCurrentLocation(); - var stack = GetStackTrace(); - throw new ScriptException(message, null, loc, stack); - } - - //TODO: add construction of Script Call stack - public ScriptStackTrace GetStackTrace() { - return new ScriptStackTrace(); - } - - private SourceLocation GetCurrentLocation() { - return this.CurrentNode == null ? new SourceLocation() : CurrentNode.Location; - } - - #endregion - - - #region IBindingSource Members - - public Binding Bind(RedBindingRequest request) { - return Runtime.Bind(request); - } - - #endregion - }//class - -} \ No newline at end of file diff --git a/samples/red/red.csproj b/samples/red/red.csproj index f183c7f..3cd0787 100644 --- a/samples/red/red.csproj +++ b/samples/red/red.csproj @@ -3,7 +3,7 @@ WinExe net6.0 win7-x64;linux-x64;osx-x64 - true + false @@ -28,9 +28,16 @@ - - + + + + + + + + + diff --git a/test/test.core/ClauseTests.cs b/test/test.core/ClauseTests.cs index 2c733b8..d070242 100644 --- a/test/test.core/ClauseTests.cs +++ b/test/test.core/ClauseTests.cs @@ -5,45 +5,46 @@ namespace test.core; public class ClauseTests : GrammarTester { - public ClauseTests() : base(new Clause()) + public ClauseTests() : base() { } [Fact] public void TestA() { - Parse("A"); + AssertParsed("A"); } + [Fact] public void TestNotA() { - Parse("^A"); + AssertParsed("^A"); } [Fact] public void TestAandB() { - Parse("A&B"); + AssertParsed("A&B"); } [Fact] public void TestAorB() { - Parse("A|B"); + AssertParsed("A|B"); } [Fact] public void TestAorNotB() { - Parse("A|^B"); + AssertParsed("A|^B"); } [Fact] public void TestAandNotB() { - Parse("A&^B"); + AssertParsed("A&^B"); } [Fact] public void TestNotAandNotB() { - Parse("^A&^B"); + AssertParsed("^A&^B"); } diff --git a/test/test.core/GrammarTester.cs b/test/test.core/GrammarTester.cs index 531f4a3..930cf56 100644 --- a/test/test.core/GrammarTester.cs +++ b/test/test.core/GrammarTester.cs @@ -1,54 +1,18 @@ using Irony.Interpreter; using Irony.Parsing; +using irules.core; using Xunit.Sdk; namespace test.core; -public class GrammarTester +public class GrammarTester : ClauseChecker { - private Grammar grammar; - private LanguageData language; - private Parser parser; - - public GrammarTester(Grammar grammar) - { - this.grammar = grammar; - - language = new LanguageData(grammar); - - parser = new Parser(language); - } - - public Exception LastException { get; private set; } - public AppStatus Status { get; private set; } - public ParseTree LastScript { get; private set; } - - public object Parse(string script) + public GrammarTester() : base() { - try - { - var parsedScript = parser.Parse(script); - if (parsedScript.HasErrors()) - { - Status = AppStatus.SyntaxError; - var ex = new ScriptException("Syntax errors found: " - + string.Join(", ", parsedScript.ParserMessages.Select(m => $"{m.Message} at line {m.Location.Line}, column {m.Location.Column}" ))); - ex.Location = parsedScript.ParserMessages.First().Location; - throw ex; - } - - if (parser.Context.Status == ParserStatus.AcceptedPartial) - { - throw new Exception("AcceptedPartial"); - } - LastScript = parsedScript; - return parsedScript; - } - catch (Exception ex) - { - this.LastException = ex; - this.Status = AppStatus.Crash; - throw; - } } + + public void AssertParsed(string problem) + { + Assert.Equal( AppStatus.Ready,this.Parse(problem)); + } }