Last active
July 4, 2020 22:11
-
-
Save Gargaj/4a70352921b0ed9dd3730dac4d401ce8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Uses https://github.com/datalust/superpower | |
Usage: | |
var text = System.IO.File.ReadAllText("visuals.comp"); | |
var parsed = FusionCompParser.ParseComp(text); | |
var tools = parsed.Get<FusionCompParser.FusionArray>("Tools"); | |
*/ | |
using Superpower; | |
using Superpower.Display; | |
using Superpower.Model; | |
using Superpower.Parsers; | |
using Superpower.Tokenizers; | |
using System; | |
using System.Collections.Generic; | |
using System.Dynamic; | |
using System.Linq; | |
namespace FusionCompParser | |
{ | |
public class FusionCompParser | |
{ | |
public class FusionArray | |
{ | |
public string Type { get; set; } | |
public string Modifier { get; set; } | |
public List<object> Array { get; set; } | |
public bool Contains(string key) | |
{ | |
return Array | |
?.Where(s => s is KeyValuePair<string, object>) | |
?.Select(s => (KeyValuePair<string, object>)s) | |
.Any(s => s.Key == key) ?? false; | |
} | |
public T Get<T>(string key) | |
{ | |
return (T)Array | |
?.Where(s => s is KeyValuePair<string, object>) | |
?.Select(s => (KeyValuePair<string, object>)s) | |
.FirstOrDefault(s => s.Key == key) | |
.Value; | |
} | |
public T GetOrDefault<T>(T defaultValue, string key) | |
{ | |
var l = Array | |
.Where(s => s is KeyValuePair<string, object>) | |
.Select(s => (KeyValuePair<string, object>)s); | |
if (!l.Any(s => s.Key == key)) | |
{ | |
return defaultValue; | |
} | |
return (T)l.FirstOrDefault(s => s.Key == key).Value; | |
} | |
public bool Contains(params string[] keys) | |
{ | |
var array = this; | |
for (int i = 0; i < keys.Length - 1; i++) | |
{ | |
var key = keys[i]; | |
array = array.Get<FusionArray>(key); | |
if (array == null) | |
{ | |
return false; | |
} | |
} | |
return array.Contains(keys[keys.Length - 1]); | |
} | |
public T Get<T>(params string[] keys) | |
{ | |
var array = this; | |
for (int i = 0; i < keys.Length - 1; i++) | |
{ | |
var key = keys[i]; | |
array = array.Get<FusionArray>(key); | |
if (array == null) | |
{ | |
return default(T); | |
} | |
} | |
return array.Get<T>(keys[keys.Length - 1]); | |
} | |
public T GetOrDefault<T>(T defaultValue, params string[] keys) | |
{ | |
var array = this; | |
for (int i = 0; i < keys.Length - 1; i++) | |
{ | |
var key = keys[i]; | |
array = array.Get<FusionArray>(key); | |
if (array == null) | |
{ | |
return defaultValue; | |
} | |
} | |
return array.GetOrDefault<T>(defaultValue, keys[keys.Length - 1]); | |
} | |
} | |
enum FusionToken | |
{ | |
[Token(Example = "{")] | |
LBracket, | |
[Token(Example = "}")] | |
RBracket, | |
[Token(Example = "}")] | |
RBracketArrayTerminator, | |
[Token(Example = "=")] | |
Equals, | |
[Token(Example = ",")] | |
Comma, | |
Symbol, | |
String, | |
Number, | |
Literal, | |
Modifier, | |
Identifier, | |
Terminator, | |
} | |
static class FusionTokenizer | |
{ | |
static TextParser<Unit> FusionStringToken { get; } = | |
from open in Character.EqualTo('"') | |
from content in Span.EqualTo("\\\"").Value(Unit.Value).Try() | |
.Or(Character.Except('"').Value(Unit.Value)) | |
.IgnoreMany() | |
from close in Character.EqualTo('"') | |
select Unit.Value; | |
static TextParser<Unit> FusionLiteral { get; } = | |
from open1 in Character.EqualTo('[').OptionalOrDefault() | |
from open2 in Character.EqualTo('"').OptionalOrDefault() | |
from content in Character.LetterOrDigit.Or(Character.In('.', '-', '_')).IgnoreMany() | |
from close2 in Character.EqualTo('"').OptionalOrDefault() | |
from close1 in Character.EqualTo(']').OptionalOrDefault() | |
select Unit.Value; | |
static TextParser<Unit> FusionArrayTerminator { get; } = | |
from open1 in Character.EqualTo(',').OptionalOrDefault() | |
from _ in Span.WhiteSpace.Many().OptionalOrDefault() | |
from open2 in Character.EqualTo('}') | |
select Unit.Value; | |
static TextParser<Unit> FusionNumberToken { get; } = | |
from sign in Character.EqualTo('-').OptionalOrDefault() | |
from first in Character.Digit | |
from rest in Character.Digit.Or(Character.In('.', 'e', 'E', '+', '-')).IgnoreMany() | |
select Unit.Value; | |
static TextParser<Unit> FusionModifier { get; } = | |
from keyword in Character.Lower.IgnoreMany() | |
from parentheses1 in Character.EqualTo('(') | |
from parentheses2 in Character.EqualTo(')') | |
select Unit.Value; | |
public static Tokenizer<FusionToken> Instance { get; } = | |
new TokenizerBuilder<FusionToken>() | |
.Ignore(Span.WhiteSpace) | |
.Match(FusionArrayTerminator, FusionToken.RBracketArrayTerminator) | |
.Match(Character.EqualTo('{'), FusionToken.LBracket) | |
.Match(Character.EqualTo('}'), FusionToken.RBracket) | |
.Match(Character.EqualTo('='), FusionToken.Equals) | |
.Match(Character.EqualTo(','), FusionToken.Comma) | |
.Match(Character.EqualTo('\0'), FusionToken.Terminator) | |
.Match(FusionStringToken, FusionToken.String) | |
.Match(FusionNumberToken, FusionToken.Number, requireDelimiters: true) | |
.Match(FusionModifier, FusionToken.Modifier) | |
.Match(FusionLiteral, FusionToken.Literal) | |
.Build(); | |
} | |
static class FusionTextParsers | |
{ | |
public static TextParser<string> String { get; } = | |
from open in Character.EqualTo('"') | |
from chars in Character.ExceptIn('"', '\\') | |
.Or(Character.EqualTo('\\') | |
.IgnoreThen( | |
Character.EqualTo('\\') | |
.Or(Character.EqualTo('"')) | |
.Or(Character.EqualTo('/')) | |
.Or(Character.EqualTo('b').Value('\b')) | |
.Or(Character.EqualTo('f').Value('\f')) | |
.Or(Character.EqualTo('n').Value('\n')) | |
.Or(Character.EqualTo('r').Value('\r')) | |
.Or(Character.EqualTo('t').Value('\t')) | |
.Or(Character.EqualTo('u').IgnoreThen( | |
Span.MatchedBy(Character.HexDigit.Repeat(4)) | |
.Apply(Numerics.HexDigitsUInt32) | |
.Select(cc => (char)cc))) | |
.Named("escape sequence"))) | |
.Many() | |
from close in Character.EqualTo('"') | |
select new string(chars); | |
public static TextParser<double> Number { get; } = | |
from sign in Character.EqualTo('-').Value(-1.0).OptionalOrDefault(1.0) | |
from whole in Numerics.Natural.Select(n => double.Parse(n.ToStringValue())) | |
from frac in Character.EqualTo('.') | |
.IgnoreThen(Numerics.Natural) | |
.Select(n => double.Parse(n.ToStringValue()) * Math.Pow(10, -n.Length)) | |
.OptionalOrDefault() | |
from exp in Character.EqualToIgnoreCase('e') | |
.IgnoreThen(Character.EqualTo('+').Value(1.0) | |
.Or(Character.EqualTo('-').Value(-1.0)) | |
.OptionalOrDefault(1.0)) | |
.Then(expsign => Numerics.Natural.Select(n => double.Parse(n.ToStringValue()) * expsign)) | |
.OptionalOrDefault() | |
select (whole + frac) * sign * Math.Pow(10, exp); | |
public static TextParser<string> Literal { get; } = | |
from open1 in Character.EqualTo('[').Optional() | |
from open2 in Character.EqualTo('"').Optional() | |
from chars in Character.ExceptIn('"', '\\') | |
.Or(Character.EqualTo('\\') | |
.IgnoreThen( | |
Character.EqualTo('\\') | |
.Or(Character.EqualTo('"')) | |
.Or(Character.EqualTo('/')) | |
.Or(Character.EqualTo('b').Value('\b')) | |
.Or(Character.EqualTo('f').Value('\f')) | |
.Or(Character.EqualTo('n').Value('\n')) | |
.Or(Character.EqualTo('r').Value('\r')) | |
.Or(Character.EqualTo('t').Value('\t')) | |
.Or(Character.EqualTo('u').IgnoreThen( | |
Span.MatchedBy(Character.HexDigit.Repeat(4)) | |
.Apply(Numerics.HexDigitsUInt32) | |
.Select(cc => (char)cc))) | |
.Named("escape sequence"))) | |
.Many() | |
from close2 in Character.EqualTo('"').Optional() | |
from close1 in Character.EqualTo(']').Optional() | |
select new string(chars); | |
} | |
private static TokenListParser<FusionToken, object> FusionParserString { get; } = | |
Token.EqualTo(FusionToken.String) | |
.Apply(FusionTextParsers.String) | |
.Select(s => (object)s); | |
private static TokenListParser<FusionToken, object> FusionParserNumber { get; } = | |
Token.EqualTo(FusionToken.Number) | |
.Apply(FusionTextParsers.Number) | |
.Select(n => (object)n); | |
private static TokenListParser<FusionToken, object> FusionParserKeyValuePair { get; } = | |
from key in Token.EqualTo(FusionToken.Literal).Try() | |
.Named("kvp key") | |
.Apply(FusionTextParsers.Literal) | |
.Select(s => s) | |
from eq in Token.EqualTo(FusionToken.Equals).Try() | |
from value in Parse.Ref(() => FusionParserValue) | |
.Named("kvp value") | |
select (object)KeyValuePair.Create(key, value); | |
private static TokenListParser<FusionToken, object> FusionParserArray { get; } = | |
from type in Token.EqualTo(FusionToken.Literal) | |
.Named("array/object type") | |
.Apply(FusionTextParsers.Literal) | |
.Select(s => s) | |
.OptionalOrDefault("") | |
from modifier in Token.EqualTo(FusionToken.Modifier) | |
.Named("modifier") | |
.Select(s => s.ToString()) | |
.OptionalOrDefault("") | |
from begin in Token.EqualTo(FusionToken.LBracket) | |
from properties in FusionParserValue | |
.Select(value => value) | |
.ManyDelimitedBy( | |
Token.EqualTo(FusionToken.Comma), | |
Token.EqualTo(FusionToken.RBracketArrayTerminator) | |
) | |
select (object)new FusionArray() { Type = type, Modifier = modifier, Array = properties.ToList() }; | |
private static TokenListParser<FusionToken, object> FusionParserTrue { get; } = | |
Token.EqualToValue(FusionToken.Literal, "true").Value((object)true); | |
private static TokenListParser<FusionToken, object> FusionParserFalse { get; } = | |
Token.EqualToValue(FusionToken.Literal, "false").Value((object)false); | |
private static TokenListParser<FusionToken, object> FusionParserValue { get; } = | |
FusionParserString | |
.Or(FusionParserNumber) | |
.Or(FusionParserTrue) | |
.Or(FusionParserFalse) | |
.Or(FusionParserKeyValuePair) | |
.Or(FusionParserArray) | |
.Named("Fusion value"); | |
private static TokenListParser<FusionToken, object> FusionParserDocument { get; } = | |
from docu in FusionParserValue.Select(s => s) | |
from term in Token.EqualTo(FusionToken.Terminator).Optional() | |
select docu; | |
public static FusionArray ParseComp(string data) | |
{ | |
var tokens = FusionTokenizer.Instance.TryTokenize(data); | |
if (!tokens.HasValue) | |
{ | |
return null; | |
} | |
var parsed = FusionParserDocument.TryParse(tokens.Value); | |
if (!parsed.HasValue) | |
{ | |
return null; | |
} | |
return parsed.Value as FusionArray; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment