Skip to content

Commit 5cccbf0

Browse files
authored
Feature: Config & breaks (#21)
## Mellis - Added `CompilerSettings`, used in `ICompiler`, controlling: - Compile-time breakpoints (aka Breaks) - Performance optimization - Jump limits - Instruction limits - Added `BreakCause`, compile-time breakpoints, representing: - Loop was entered (for, while, repeat) - Loop inner block ended (for, while, repeat) - CLR function about to be called - User function about to be called - Jump limit was reached - Instruction limit was reached - Added `Walk` to `IProcessor`, that walks as much as possible until a break/yield/eof. - Added `WalkStatus`, a return value to `WalkLine` and the newly added `Walk`, representing: - Script ended, no more instructions - Yielded, a ClrYieldingFunction was entered - NewLine, source line switch, only used in `WalkLine` - Break, a compile-time breakpoint was triggered - Upped version to 0.2.1 ## Python3 module - Added compile-time breaks compilation according to Mellis specifications - Added jump & instruction limit using `CompilerSettings`. - Fully unit-tested the above mentioned implementations - Some refactoring - Upped version to 0.6.3
1 parent a6bab89 commit 5cccbf0

36 files changed

+1618
-166
lines changed

src/Directory.Build.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.1.7.3</Version>
4-
<AssemblyVersion>0.1.7.3</AssemblyVersion>
5-
<FileVersion>0.1.7.3</FileVersion>
3+
<Version>0.2.1.0</Version>
4+
<AssemblyVersion>0.2.1.0</AssemblyVersion>
5+
<FileVersion>0.2.1.0</FileVersion>
66

77
<Authors>Zifro AB</Authors>
88
<Company>Zifro AB</Company>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using Mellis.Core.Interfaces;
3+
4+
namespace Mellis.Core.Entities
5+
{
6+
/// <summary>
7+
/// Cause of compile-time defined breakpoints. (Flags)
8+
/// </summary>
9+
[Flags]
10+
public enum BreakCause
11+
{
12+
Nothing = 0,
13+
14+
/// <summary>
15+
/// A loop (for, repeat, while) was entered.
16+
/// <para>This only triggers once when the loop is first entered
17+
/// just before the condition is first checked.
18+
/// Only triggers once per loop.</para>
19+
/// </summary>
20+
LoopEnter = 0x1,
21+
/// <summary>
22+
/// A loop (for, repeat, while) has reached end of its code block.
23+
/// <para>This triggers just before the condition is checked again
24+
/// and can be triggered multiple times for the same loop.</para>
25+
/// </summary>
26+
LoopBlockEnd = 0x2,
27+
28+
/// <summary>
29+
/// A user-defined function call is about to be invoked.
30+
/// <para>This triggers just before the call stack is pushed.</para>
31+
/// </summary>
32+
FunctionUserCall = 0x10,
33+
/// <summary>
34+
/// A CLR-defined function call <see cref="IClrFunction"/> is about to be invoked.
35+
/// <para>This triggers just before the call stack is pushed.</para>
36+
/// </summary>
37+
FunctionClrCall = 0x20,
38+
/// <summary>
39+
/// Any function call (user-defined or CLR-defined) is about to be invoked
40+
/// <para>This triggers just before the call stack is pushed.</para>
41+
/// </summary>
42+
FunctionAnyCall = FunctionUserCall | FunctionClrCall,
43+
44+
/// <summary>
45+
/// Consecutive jumps count limit was reached.
46+
/// Is only enabled if compiler settings jump limit property
47+
/// <see cref="CompilerSettings.JumpLimit"/> is non-zero and positive.
48+
/// <para>This triggers just after the limited jump was jumped.</para>
49+
/// </summary>
50+
JumpLimitReached = 0x100,
51+
52+
/// <summary>
53+
/// Consecutive instructions count limit per walk was reached.
54+
/// Is only enabled if compiler settings instruction limit property
55+
/// <see cref="CompilerSettings.InstructionLimit"/> is non-zero and positive.
56+
/// <para>This triggers just after the limited instruction was walked.</para>
57+
/// </summary>
58+
InstructionLimitReached = 0x200,
59+
}
60+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Mellis.Core.Interfaces;
2+
3+
namespace Mellis.Core.Entities
4+
{
5+
/// <summary>
6+
/// Common compiler settings.
7+
/// </summary>
8+
public struct CompilerSettings
9+
{
10+
/// <summary>
11+
/// The setting for inserting breaks in the compiled code
12+
/// that when the processor walks over in <see cref="IProcessor.Walk"/>
13+
/// or walk line <see cref="IProcessor.WalkLine"/> shall break.
14+
/// <para>Default: <see cref="BreakCause.JumpLimitReached"/></para>
15+
/// </summary>
16+
public BreakCause BreakOn { get; set; }
17+
18+
/// <summary>
19+
/// Will try to optimize the produced code for speed and performance.
20+
/// Is not compatible with the processor walk line method <see cref="IProcessor.WalkLine"/>.
21+
/// <para>Default: <c>false</c>.</para>
22+
/// </summary>
23+
public bool Optimize { get; set; }
24+
25+
/// <summary>
26+
/// Specifies a jump limit for each call to the processors
27+
/// walk method <see cref="IProcessor.Walk"/>
28+
/// or walk line method <see cref="IProcessor.WalkLine"/>.
29+
/// Is only active if value is non-zero positive number and
30+
/// break setting <see cref="BreakOn"/> has jump limit flag
31+
/// <see cref="BreakCause.JumpLimitReached"/> set.
32+
/// <para>Default: <c>239</c>, a Oliver &amp; Fredrik approved ✔ limit</para>
33+
/// </summary>
34+
public int JumpLimit { get; set; }
35+
36+
/// <summary>
37+
/// Specifies an instruction (op-code) limit for each call to the processors
38+
/// walk method <see cref="IProcessor.Walk"/>
39+
/// or walk line method <see cref="IProcessor.WalkLine"/>.
40+
/// Is only active if value is non-zero positive number and
41+
/// break setting <see cref="BreakOn"/> has instruction limit flag
42+
/// <see cref="BreakCause.InstructionLimitReached"/> set.
43+
/// <para>Default: <c>0</c>, i.e. disabled</para>
44+
/// </summary>
45+
public int InstructionLimit { get; set; }
46+
47+
/// <summary>
48+
/// Gets the default settings used in compilation among the language modules.
49+
/// </summary>
50+
public static CompilerSettings DefaultSettings { get; } = new CompilerSettings {
51+
BreakOn = BreakCause.JumpLimitReached,
52+
Optimize = false,
53+
JumpLimit = 102 + 137, // Oliver & Fredrik approved ✔
54+
InstructionLimit = 0
55+
};
56+
}
57+
}

src/Mellis.Core/Entities/ProcessState.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Mellis.Core.Entities
1+
using Mellis.Core.Interfaces;
2+
3+
namespace Mellis.Core.Entities
24
{
35
public enum ProcessState
46
{
@@ -14,6 +16,8 @@ public enum ProcessState
1416

1517
/// <summary>
1618
/// Interpreter has paused in anticipation of a yielded function return.
19+
/// Use the resolve yield method <see cref="IProcessor.ResolveYield()"/>
20+
/// on processor <see cref="IProcessor"/> to release the yield lock.
1721
/// </summary>
1822
Yielded,
1923

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Mellis.Core.Interfaces;
2+
3+
namespace Mellis.Core.Entities
4+
{
5+
/// <summary>
6+
/// Returned status from the processor walk <see cref="IProcessor.Walk"/>
7+
/// or walk line <see cref="IProcessor.WalkLine"/> call.
8+
/// </summary>
9+
public enum WalkStatus
10+
{
11+
/// <summary>
12+
/// Script execution completed. No more instructions to walk.
13+
/// <para>Matches the process state <see cref="ProcessState"/>
14+
/// ended value <see cref="ProcessState.Ended"/>.</para>
15+
/// </summary>
16+
Ended,
17+
18+
/// <summary>
19+
/// A yielding function was called.
20+
/// Use the resolve yield method <see cref="IProcessor.ResolveYield()"/>
21+
/// on processor <see cref="IProcessor"/> to release the yield lock.
22+
/// <para>Matches the process state <see cref="ProcessState"/>
23+
/// yielded value <see cref="ProcessState.Yielded"/>.</para>
24+
/// </summary>
25+
Yielded,
26+
27+
/// <summary>
28+
/// Walker finished all instructions before line switches.
29+
/// <para>Note: Only applicable for processor <see cref="IProcessor"/>
30+
/// walk line method <see cref="IProcessor.WalkLine"/>.</para>
31+
/// </summary>
32+
NewLine,
33+
34+
/// <summary>
35+
/// A breakpoint defined at compile-time with <see cref="BreakCause"/> in the
36+
/// <see cref="ICompiler"/> compiler property <see cref="ICompiler.Settings"/>.
37+
/// </summary>
38+
Break
39+
}
40+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
namespace Mellis.Core.Interfaces
1+
using Mellis.Core.Entities;
2+
3+
namespace Mellis.Core.Interfaces
24
{
35
public interface ICompiler
46
{
7+
CompilerSettings Settings { get; set; }
8+
59
IProcessor Compile(string code);
610
}
711
}

src/Mellis.Core/Interfaces/IProcessor.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,50 @@
11
using Mellis.Core.Entities;
22
using Mellis.Core.Exceptions;
3+
using Mellis.Core.Resources;
34

45
namespace Mellis.Core.Interfaces
56
{
67
public interface IProcessor
78
{
9+
/// <summary>
10+
/// Value factory. Use this to create new values, bound to this
11+
/// processor.
12+
/// </summary>
813
IScriptTypeFactory Factory { get; }
14+
915
IScopeContext GlobalScope { get; }
16+
17+
/// <summary>
18+
/// Current scope for the processor.
19+
/// </summary>
1020
IScopeContext CurrentScope { get; }
21+
22+
/// <summary>
23+
/// Current state in the process.
24+
/// </summary>
1125
ProcessState State { get; }
26+
27+
/// <summary>
28+
/// Current source pointer of the execution.
29+
/// </summary>
1230
SourceReference CurrentSource { get; }
1331

32+
/// <summary>
33+
/// Last thrown error from inside the processor during a walk.
34+
/// Does not record "process ended" nor "already yielded" errors.
35+
/// </summary>
1436
InterpreterException LastError { get; }
1537

38+
/// <summary>
39+
/// Break cause from latest break after a walk.
40+
/// </summary>
41+
BreakCause LastBreakCause { get; }
42+
43+
/// <summary>
44+
/// Compiler settings used when compiling this processor.
45+
/// </summary>
46+
CompilerSettings CompilerSettings { get; }
47+
1648
/// <summary>
1749
/// Resolves a yielding function <see cref="IClrYieldingFunction"/>
1850
/// with specified return value and allows walking operations
@@ -34,8 +66,29 @@ public interface IProcessor
3466
/// </summary>
3567
void ResolveYield();
3668

37-
void WalkLine();
69+
/// <summary>
70+
/// Walks the instructions and stops if a new line was reached,
71+
/// a yielding function <seealso cref="IClrYieldingFunction"/> was called,
72+
/// a breakpoint defined in the compiler settings was reached,
73+
/// or the processor finishes all instructions.
74+
/// <para>See walk status enum <see cref="WalkStatus"/> for more info.</para>
75+
/// </summary>
76+
WalkStatus WalkLine();
77+
78+
/// <summary>
79+
/// Walks the instructions and stops if
80+
/// a yielding function <seealso cref="IClrYieldingFunction"/> was called,
81+
/// a breakpoint <see cref="BreakCause"/> in the compiler settings was reached,
82+
/// or the processor finishes all instructions.
83+
/// <para>See walk status enum <see cref="WalkStatus"/> for more info.</para>
84+
/// </summary>
85+
WalkStatus Walk();
3886

87+
/// <summary>
88+
/// Adds CLR functions to list of builtins.
89+
/// Functions added this way does not show up in the current scope
90+
/// but are still accessible in the code.
91+
/// </summary>
3992
void AddBuiltin(params IEmbeddedType[] builtinList);
4093
}
4194
}

src/Mellis.Lang.Python3.Tests/Compiler/CompileExpressionTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,87 @@ public void FunctionCallMultipleArgsTest()
322322
Assert.AreEqual(5, compiler.Count);
323323
}
324324

325+
[DataTestMethod]
326+
[DataRow(BreakCause.FunctionAnyCall)]
327+
[DataRow(BreakCause.FunctionClrCall)]
328+
[DataRow(BreakCause.FunctionUserCall)]
329+
public void BreakOn_FunctionCallEmptyArgs(BreakCause breakCause)
330+
{
331+
// Arrange
332+
var compiler = new PyCompiler {
333+
Settings = new CompilerSettings {
334+
BreakOn = breakCause
335+
}
336+
};
337+
338+
var args = new ExpressionNode[0];
339+
340+
var callNode = new FunctionCall(
341+
SourceReference.ClrSource,
342+
new Identifier(SourceReference.ClrSource, "foo"),
343+
new ArgumentsList(SourceReference.ClrSource, args)
344+
);
345+
346+
// Act
347+
callNode.Compile(compiler);
348+
349+
// Assert
350+
Assert.That.IsOpCode<VarGet>(compiler, 0);
351+
var breakpoint = Assert.That.IsOpCode<Breakpoint>(compiler, 1);
352+
Assert.That.IsOpCode<Call>(compiler, 2);
353+
354+
Assert.AreEqual(breakCause, breakpoint.BreakCause);
355+
Assert.AreEqual(3, compiler.Count);
356+
}
357+
358+
359+
[DataTestMethod]
360+
[DataRow(BreakCause.FunctionAnyCall)]
361+
[DataRow(BreakCause.FunctionClrCall)]
362+
[DataRow(BreakCause.FunctionUserCall)]
363+
public void BreakOn_FunctionCallMultipleArgs(BreakCause breakCause)
364+
{
365+
// Arrange
366+
var compiler = new PyCompiler {
367+
Settings = new CompilerSettings {
368+
BreakOn = breakCause
369+
}
370+
};
371+
372+
compiler.CreateAndSetup(out Mock<ExpressionNode> expr1Mock, out var nop1);
373+
compiler.CreateAndSetup(out Mock<ExpressionNode> expr2Mock, out var nop2);
374+
compiler.CreateAndSetup(out Mock<ExpressionNode> expr3Mock, out var nop3);
375+
376+
var args = new[] {
377+
expr1Mock.Object,
378+
expr2Mock.Object,
379+
expr3Mock.Object
380+
};
381+
382+
var callNode = new FunctionCall(
383+
SourceReference.ClrSource,
384+
new Identifier(SourceReference.ClrSource, "foo"),
385+
new ArgumentsList(SourceReference.ClrSource, args)
386+
);
387+
388+
// Act
389+
callNode.Compile(compiler);
390+
391+
// Assert
392+
Assert.That.IsOpCode<VarGet>(compiler, 0);
393+
394+
Assert.That.IsExpectedOpCode(compiler, 1, nop1);
395+
Assert.That.IsExpectedOpCode(compiler, 2, nop2);
396+
Assert.That.IsExpectedOpCode(compiler, 3, nop3);
397+
398+
var breakpoint = Assert.That.IsOpCode<Breakpoint>(compiler, 4);
399+
400+
Assert.That.IsOpCode<Call>(compiler, 5);
401+
402+
Assert.AreEqual(breakCause, breakpoint.BreakCause);
403+
Assert.AreEqual(6, compiler.Count);
404+
}
405+
325406
[TestMethod]
326407
public void ExpressionStatementTest()
327408
{

0 commit comments

Comments
 (0)