using System;
using System.Text.RegularExpressions;
namespace WandEnhancer.Core.Js
{
/// A named function or class method located in a bundle, addressed by delimiter position.
internal sealed class JsFunction
{
private static readonly Regex ReturnKeyword = new Regex(@"(? _source.Substring(BodyOpen + 1, BodyClose - BodyOpen - 1);
private JsCursor BodyCursor => _body ?? (_body = new JsCursor(Body));
/// Captures a group from a pattern matched against this body only, not the whole bundle.
public string Resolve(string pattern, string group)
{
var match = Regex.Match(Body, pattern, RegexOptions.Singleline);
if (!match.Success || string.IsNullOrEmpty(match.Groups[group].Value))
{
throw new Exception($"Could not resolve '{group}' inside {Name}()");
}
return match.Groups[group].Value;
}
/// Rewrites the first match of a pattern scoped to this body; ${group} back-references work.
public JsEdit ReplaceInBody(string pattern, string replacement)
{
var match = Regex.Match(Body, pattern, RegexOptions.Singleline);
if (!match.Success)
{
throw new Exception($"Pattern '{pattern}' not found inside {Name}()");
}
int start = BodyOpen + 1 + match.Index;
return new JsEdit(start, start + match.Length, match.Result(replacement));
}
public JsEdit InsertAtStart(string code) => new JsEdit(BodyOpen + 1, BodyOpen + 1, code);
public JsEdit InsertAtEnd(string code) => new JsEdit(BodyClose, BodyClose, code);
public JsEdit ReplaceBody(string code) => new JsEdit(BodyOpen + 1, BodyClose, code);
///
/// Rewrites the last top-level return X as return WRAPPER, where the wrapper's
/// $0 placeholder receives the original expression.
///
public JsEdit WrapReturn(string wrapper)
{
var body = BodyCursor;
int keywordEnd = -1;
for (var match = ReturnKeyword.Match(body.Text); match.Success; match = match.NextMatch())
{
if (body.OpenerStack(match.Index).Count == 0)
{
keywordEnd = match.Index + match.Length;
}
}
if (keywordEnd < 0)
{
throw new Exception($"No top-level return statement in {Name}()");
}
int expressionStart = body.SkipWhitespaceForward(keywordEnd);
int expressionEnd = FindStatementEnd(body, expressionStart);
string expression = body.Text.Substring(expressionStart, expressionEnd - expressionStart);
return new JsEdit(
BodyOpen + 1 + expressionStart,
BodyOpen + 1 + expressionEnd,
wrapper.Replace("$0", $"({expression})"));
}
private static int FindStatementEnd(JsCursor body, int start)
{
for (int cursor = start; cursor < body.Text.Length; cursor++)
{
if (body.Text[cursor] == ';' && body.OpenerStack(cursor).Count == 0)
{
return cursor;
}
}
return body.Text.Length;
}
}
/// A splice: replace [Start, End) of the bundle with .
internal sealed class JsEdit
{
public JsEdit(int start, int end, string text)
{
Start = start;
End = end;
Text = text;
}
/// An insertion at , replacing nothing.
public JsEdit(int at, string text) : this(at, at, text)
{
}
public int Start { get; }
public int End { get; }
public string Text { get; }
public string ApplyTo(string source) => source.Substring(0, Start) + Text + source.Substring(End);
}
}