main
Paul Schneider 10 months ago
parent 96804f8531
commit 3ced6adbd4
13 changed files with 146 additions and 474 deletions

@ -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"
]
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -1,7 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="red.App"
RequestedThemeVariant="Default">
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>

@ -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
}
/// <summary> Represents a running instance of a script application. </summary>
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<string, object> Globals { get; private set; }
private IList<Assembly> ImportedAssemblies = new List<Assembly>();
// 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<Assembly> 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<ConsoleWriteEventArgs> ConsoleWrite;
private void OnConsoleWrite(string text)
{
if (ConsoleWrite != null)
{
ConsoleWriteEventArgs args = new ConsoleWriteEventArgs(text);
ConsoleWrite(this, args);
}
}
#endregion
#endregion
}//class

@ -11,15 +11,20 @@
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open..." />
<MenuItem Header="_Open..." Click="OnOpen"/>
<Separator />
<MenuItem Header="_Exit" />
<MenuItem Header="_Exit" Click="OnExit"/>
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="Copy" />
<MenuItem Header="Paste" />
</MenuItem>
<MenuItem Header="_Run">
</MenuItem>
</Menu>
<StackPanel DockPanel.Dock="Bottom">
<TextBlock Text="Status" x:Name="statusMessage" />
</StackPanel>
<AvaloniaEdit:TextEditor x:Name="src"
Name="Editor"
Text="Hello AvaloniaEdit!"

@ -1,4 +1,9 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace red;
@ -9,4 +14,32 @@ public partial class MainWindow : Window
InitializeComponent();
src.Text = "Hello !";
}
public void OnExit(object sender, RoutedEventArgs e)
{
Close();
}
public void OnOpen(object sender, RoutedEventArgs e)
{
Task.Run(async () =>
{
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}");
}
}
});
}
}

@ -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;
}
}
}

@ -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();
}
}
}

@ -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
/// <summary> Represents a running thread in script application. </summary>
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
}

@ -3,7 +3,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win7-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
@ -28,9 +28,16 @@
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.1" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.1" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.5" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.5" />
<ProjectReference Include="../../irules.core/irules.core.csproj" />
<PackageReference Include="ReactiveUI" Version="19.5.1" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="System.Interactive" Version="6.0.1" />
<PackageReference Include="System.Interactive.Async" Version="6.0.1" />
</ItemGroup>
</Project>

@ -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");
}

@ -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)
public GrammarTester() : base()
{
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)
{
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));
}
}

Loading…