Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling scoping mechanism #35

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
47 changes: 47 additions & 0 deletions src/Parlot/Fluent/ParseContext.Scoped.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;

namespace Parlot.Fluent
{
public partial class ParseContext
{
IDictionary<string, object> scope ;
private ParseContext parent;

private bool HasValue(string name)
{
return scope!=null && scope.TryGetValue(name, out _) || parent != null && parent.HasValue(name);
}

public void Set(string name, object value)
{
if (parent != null && parent.HasValue(name))
parent.Set(name, value);
else
{
if(scope == null)
scope = new Dictionary<string,object>();
scope[name] = value;
}
}

public T Get<T>(string name)
{
if (scope != null && scope.TryGetValue(name, out var result))
return (T)result;
if (parent == null)
return default(T);
return parent.Get<T>(name);
}

public ParseContext(ParseContext context)
{

Scanner=context.Scanner;
UseNewLines=context.UseNewLines;
OnEnterParser=context.OnEnterParser;
WhiteSpaceParser=context.WhiteSpaceParser;
parent = context ;
}
}
}
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/ParseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Parlot.Fluent
{
public class ParseContext
public partial class ParseContext
{
/// <summary>
/// Whether new lines are treated as normal chars or white spaces.
Expand Down
5 changes: 5 additions & 0 deletions src/Parlot/Fluent/Parsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public static partial class Parsers
/// </summary>
public static Parser<T> ZeroOrOne<T>(Parser<T> parser) => new ZeroOrOne<T>(parser);

/// <summary>
/// Builds a parser that creates a scope usable in the specified parser.
/// </summary>
public static Parser<T> Scope<T>(Parser<T> parser) => new ScopedParser<T>(parser);

/// <summary>
/// Builds a parser that looks for zero or many times the specified parser.
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions src/Parlot/Fluent/ScopedParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Parlot.Fluent
{
/// <summary>
/// Returns a new <see cref="Parser{U}" /> converting the input value of
/// type T to the output value of type U using a custom function.
/// </summary>
/// <typeparam name="T">The input parser type.</typeparam>
public sealed class ScopedParser<T> : Parser<T>
{
private readonly Parser<T> _parser;

public ScopedParser(Parser<T> parser)
{
_parser = parser ?? throw new ArgumentNullException(nameof(parser));
}

public override bool Parse(ParseContext context, ref ParseResult<T> result)
{
context.EnterParser(this);

return _parser.Parse(new ParseContext(context), ref result);
}
}
}
54 changes: 54 additions & 0 deletions test/Parlot.Tests/FluentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,60 @@ public void ShouldReadStringsWithLineBreaks(string text, string expected)
Assert.Equal(expected, Literals.String(StringLiteralQuotes.SingleOrDouble).Parse(text).ToString());
}

[Fact]
public void ScopeShouldAllowScopedParserContext()
{
var a = Literals.Char('a');
var b = Literals.Char('b');
var c = Literals.Char('c');

var o2 = Scope(
a.Then((c, t)=>{c.Set("lorem", "ipsum"); return "lorem";})
.And(b).Then((c, t)=> c.Get<string>(t.Item1)));

Assert.IsType<ScopedParser<string>>(o2);
Assert.False(o2.TryParse("a", out _));
Assert.True(o2.TryParse("ab", out var result));
Assert.Equal("ipsum", result);

o2 = Scope(
a.Then((c, t)=> "lorem")
.And(b).Then((c, t)=> c.Get<string>(t.Item1)));

Assert.IsType<ScopedParser<string>>(o2);
Assert.True(o2.TryParse("ab", out result));
Assert.Null(result);


o2 = Scope(
a.Then((c, t)=>{c.Set("lorem", "ipsum"); return t;})
.SkipAnd(Scope(b.Then((c, t)=> c.Get<string>("lorem")))));

Assert.IsType<ScopedParser<string>>(o2);
Assert.True(o2.TryParse("ab", out result));
Assert.Equal("ipsum", result);

o2 = Scope(
a.Then((c, t)=>{c.Set("lorem", "ipsum"); return t;})
.SkipAnd(Scope(b.Then((c, t)=> {c.Set("lorem", "ipsumipsum"); return t;})))
.SkipAnd(c.Then((c,t)=>c.Get<string>("lorem")))
);

Assert.IsType<ScopedParser<string>>(o2);
Assert.True(o2.TryParse("abc", out result));
Assert.Equal("ipsumipsum", result);

o2 = Scope(
a.Then((c, t)=>{c.Set("lorem", "ipsum"); return t;})
.SkipAnd(Scope(b.Then((c, t)=> {c.Set("lorem2", "ipsum"); return t;})))
.SkipAnd(c.Then((c,t)=>c.Get<string>("lorem2")))
);

Assert.IsType<ScopedParser<string>>(o2);
Assert.True(o2.TryParse("abc", out result));
Assert.Null( result);
}

[Fact]
public void OrShouldReturnOneOf()
{
Expand Down