Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Component/SimpleGlobalContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*----------------------------------------------------------
This Source Code Form is subject to the terms of the
Mozilla Public License, v.2.0. If a copy of the MPL
was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using OneScript.Contexts;
using ScriptEngine.Machine.Contexts;

namespace Component
{
// Компонента добавляет свой глобальный контекст, как в #344 и #911
[GlobalContext(Category = "Тестовая компонента")]
public class SimpleGlobalContext : GlobalContextBase<SimpleGlobalContext>
{
[ContextMethod("ГлобальныйМетодКомпоненты")]
public string GlobalMethod() => "метод компоненты";

[ContextProperty("ГлобальноеСвойствоКомпоненты")]
public string GlobalProperty => "свойство компоненты";

public static IAttachableContext CreateInstance() => new SimpleGlobalContext();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private ScopeBindingDescriptor(ScopeBindingKind kind, IAttachableContext target,
public IAttachableContext Target { get; }

/// <summary>
/// Индекс области видимости в списке ExecutionFrame.Scopes. Используется для ScopeBindingKind.FrameScope.
/// Номер области видимости с конца списка ExecutionFrame.Scopes (0 - самая внутренняя). Используется для ScopeBindingKind.FrameScope.
/// </summary>
public int ScopeIndex { get; }

Expand Down
2 changes: 1 addition & 1 deletion src/ScriptEngine/Compiler/ModuleDumpWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private void WriteSymbolMap(TextWriter output, IList<ModuleSymbolBinding> map)
{
ScopeBindingKind.Static => item.Target?.GetType().Name ?? "null",
ScopeBindingKind.ThisScope => "this",
ScopeBindingKind.FrameScope => $"frame[{item.ScopeIndex}]",
ScopeBindingKind.FrameScope => $"frame[^{item.ScopeIndex + 1}]",
_ => "unknown"
};
output.Write(string.Format("{0,-3}:({1},{2},{3})\n", i, item.Kind, targetInfo, item.MemberNumber));
Expand Down
42 changes: 42 additions & 0 deletions src/ScriptEngine/Machine/JoinedScopes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,53 @@ public class JoinedScopes : IReadOnlyList<IAttachableContext>
{
private readonly IReadOnlyList<IAttachableContext> _outerScopes;
private readonly IAttachableContext _innerScope;
private readonly IReadOnlyList<IAttachableContext> _rootScopes;
private readonly int _innerScopesCount;

public JoinedScopes(IReadOnlyList<IAttachableContext> outerScopes, IAttachableContext innerScope)
{
_outerScopes = outerScopes ?? throw new ArgumentNullException(nameof(outerScopes));
_innerScope = innerScope ?? throw new ArgumentNullException(nameof(innerScope));

if (_outerScopes is JoinedScopes outer)
{
_rootScopes = outer._rootScopes;
_innerScopesCount = outer._innerScopesCount + 1;
}
else
{
_rootScopes = _outerScopes;
_innerScopesCount = 1;
}
}

/// <summary>
/// Исходный список, к которому добавлены внутренние области (глобальные контексты окружения).
/// Может расти, пока выполняется код.
/// </summary>
public IReadOnlyList<IAttachableContext> RootScopes => _rootScopes;

/// <summary>
/// Число внутренних областей, добавленных к исходному списку.
/// </summary>
public int InnerScopesCount => _innerScopesCount;

/// <summary>
/// Внутренняя область по номеру с конца: 0 - самая внутренняя.
/// В отличие от индексатора не зависит от размера исходного списка.
/// </summary>
public IAttachableContext FromEnd(int index)
{
if (index < 0 || index >= _innerScopesCount)
throw new ArgumentOutOfRangeException(nameof(index));

var scopes = this;
for (; index > 0; index--)
{
scopes = (JoinedScopes)scopes._outerScopes;
}

return scopes._innerScope;
}

public IEnumerator<IAttachableContext> GetEnumerator()
Expand Down
102 changes: 58 additions & 44 deletions src/ScriptEngine/Machine/MachineInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2443,53 +2443,24 @@ private SymbolTable ExtractCompilerContext()
{
var ctx = new SymbolTable();
var scopes = _currentFrame.Scopes ?? Array.Empty<IAttachableContext>();
var scopeCount = scopes.Count;
var thisScope = _currentFrame.ThisScope;

// Добавляем все контексты из scopes (глобальные + локальные из предыдущих кадров)
for (int index = 0; index < scopeCount; index++)
// В начале scopes - глобальные контексты окружения, дальше области модуля и вложенных вычислений.
// Выражение кэшируется, а глобальных контекстов может стать больше (ПодключитьВнешнююКомпоненту),
// поэтому глобальные связываются напрямую, а остальные области - по номеру с конца списка
var joinedScopes = scopes as JoinedScopes;
var globals = joinedScopes?.RootScopes ?? scopes;
var globalsCount = globals.Count;
for (int index = 0; index < globalsCount; index++)
{
var scope = scopes[index];

var symbolScope = new SymbolScope();

// Добавляем методы
for (int i = 0; i < scope.MethodsCount; i++)
{
var methodInfo = scope.GetMethod(i);
symbolScope.DefineMethod(methodInfo.ToSymbol());
}

// Добавляем переменные
for (int i = 0; i < scope.VariablesCount; i++)
{
var variable = scope.GetVariable(i);

string alias = null;
if (scope is IRuntimeContextInstance runtimeContext)
{
try
{
var propInfo = runtimeContext.GetPropertyInfo(i);
alias = propInfo.Alias;
}
catch
{
// Алиас остается пустым
}
}

if (alias != null)
{
symbolScope.DefineVariable(new AliasedVariableSymbol(variable.Name, alias));
}
else
{
symbolScope.DefineVariable(new LocalVariableSymbol(variable.Name));
}
}
var scope = globals[index];
ctx.PushScope(CreateSymbolScope(scope), ScopeBindingDescriptor.Static(scope));
}

ctx.PushScope(symbolScope, ScopeBindingDescriptor.FrameScope(index));
var innerScopesCount = joinedScopes?.InnerScopesCount ?? 0;
for (int index = innerScopesCount - 1; index >= 0; index--)
{
// В кадре выражения самой внутренней будет его собственная локальная область
ctx.PushScope(CreateSymbolScope(joinedScopes.FromEnd(index)), ScopeBindingDescriptor.FrameScope(index + 1));
}

// Локальные переменные текущего фрейма
Expand All @@ -2503,6 +2474,49 @@ private SymbolTable ExtractCompilerContext()
return ctx;
}

private static SymbolScope CreateSymbolScope(IAttachableContext scope)
{
var symbolScope = new SymbolScope();

// Добавляем методы
for (int i = 0; i < scope.MethodsCount; i++)
{
var methodInfo = scope.GetMethod(i);
symbolScope.DefineMethod(methodInfo.ToSymbol());
}

// Добавляем переменные
for (int i = 0; i < scope.VariablesCount; i++)
{
var variable = scope.GetVariable(i);

string alias = null;
if (scope is IRuntimeContextInstance runtimeContext)
{
try
{
var propInfo = runtimeContext.GetPropertyInfo(i);
alias = propInfo.Alias;
}
catch
{
// Алиас остается пустым
}
}

if (alias != null)
{
symbolScope.DefineVariable(new AliasedVariableSymbol(variable.Name, alias));
}
else
{
symbolScope.DefineVariable(new LocalVariableSymbol(variable.Name));
}
}

return symbolScope;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IAttachableContext ResolveBindingTarget(ModuleSymbolBinding binding)
{
Expand Down
7 changes: 4 additions & 3 deletions src/ScriptEngine/Machine/ModuleSymbolBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ internal IAttachableContext ResolveTarget(ExecutionFrame frame)

private IAttachableContext ResolveFrameScope(ExecutionFrame frame)
{
if (frame?.Scopes == null)
if (frame?.Scopes is not JoinedScopes scopes)
throw new InvalidOperationException("Frame scopes are not available");
if (ScopeIndex < 0 || ScopeIndex >= frame.Scopes.Count)
if (ScopeIndex < 0 || ScopeIndex >= scopes.InnerScopesCount)
throw new InvalidOperationException($"Invalid scope index {ScopeIndex}");

return frame.Scopes[ScopeIndex];
// Номер с конца: глобальных контекстов в начале списка может стать больше
return scopes.FromEnd(ScopeIndex);
}

public bool Equals(ModuleSymbolBinding other)
Expand Down
78 changes: 78 additions & 0 deletions src/Tests/OneScript.Core.Tests/EvalScopesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*----------------------------------------------------------
This Source Code Form is subject to the terms of the
Mozilla Public License, v.2.0. If a copy of the MPL
was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using FluentAssertions;
using OneScript.Contexts;
using ScriptEngine.Hosting;
using ScriptEngine.Machine.Contexts;
using Xunit;

namespace OneScript.Core.Tests
{
[GlobalContext(ManualRegistration = true)]
public class EvalScopesTestGlobals : GlobalContextBase<EvalScopesTestGlobals>
{
[ContextProperty("СвойствоТестовогоКонтекста")]
public string Property => "свойство контекста";

[ContextMethod("МетодТестовогоКонтекста")]
public string Method() => "метод контекста";
}

[GlobalContext(ManualRegistration = true)]
public class EvalScopesTestHost : GlobalContextBase<EvalScopesTestHost>
{
private readonly IRuntimeEnvironment _environment;

public EvalScopesTestHost(IRuntimeEnvironment environment)
{
_environment = environment;
}

// Как ПодключитьВнешнююКомпоненту со своим глобальным контекстом
[ContextMethod("ПодключитьТестовыйКонтекст")]
public void Attach() => _environment.InjectObject(new EvalScopesTestGlobals());
}

public class EvalScopesTests
{
private const string Script =
"Перем МояПеременная;\n" +
"Перем Итог Экспорт;\n" +
"\n" +
"Функция МояФункция()\n" +
" Возврат \"функция модуля\";\n" +
"КонецФункции\n" +
"\n" +
"МояПеременная = \"переменная модуля\";\n" +
"Итог = \"\";\n" +
"Значение = Неопределено;\n" +
"Для Номер = 1 По 2 Цикл\n" +
" Итог = Итог + Вычислить(\"МояПеременная\") + \"; \" + Вычислить(\"МояФункция()\") + \"; \";\n" +
" Выполнить(\"Значение = МояПеременная\");\n" +
" Итог = Итог + Значение + \"; \";\n" +
" Если Номер = 1 Тогда\n" +
" ПодключитьТестовыйКонтекст();\n" +
" КонецЕсли;\n" +
"КонецЦикла;\n";

[Fact]
public void CachedExpressionsSeeModuleAfterGlobalContextIsAttached()
{
var engine = DefaultEngineBuilder.Create().SetDefaultOptions().Build();
engine.Initialize();
engine.Environment.InjectObject(new EvalScopesTestHost(engine.Environment));

// Вычислить и Выполнить кэшируют выражения у машины процесса, поэтому все в одном запуске
var instance = engine.AttachedScriptsFactory.LoadFromString(engine.GetCompilerService(), Script, engine.NewProcess());

instance.GetPropValue(instance.GetPropertyNumber("Итог")).ToString().Should().Be(
"переменная модуля; функция модуля; переменная модуля; " +
"переменная модуля; функция модуля; переменная модуля; ");
}
}
}
Loading
Loading