[LG] Robust Template CRUD with Two-phase parsing of LG (#3731)

* init

* init

* init

* init

* init

* init

* init

* fix error

* remove unused code

* remove unused file

* remove unused code

* fix build error

* fix error

* refine

* only allow define option/import at the top of lg file.

* refine import recognization

* add more comments and remove unused code

* remove unused antlr file

* remove antlr related file

* fix build warning

* refine

* fix error

* fix error

* fix error

* add context for Template and TemplateImport

* fix comments

* fix comments

* fix comments

* rename templatebody to body to avoid the same context name
This commit is contained in:
Hongyang Du (hond) 2020-04-20 23:44:32 +08:00 коммит произвёл GitHub
Родитель ef46dd1815
Коммит abc14c6a52
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
93 изменённых файлов: 1080 добавлений и 1121 удалений

Просмотреть файл

@ -77,6 +77,8 @@
<Source>.*\\ExpressionAntlrParser.cs</Source>
<Source>.*\\LGFileLexer.cs</Source>
<Source>.*\\LGFileParser.cs</Source>
<Source>.*\\LGTemplateLexer.cs</Source>
<Source>.*\\LGTemplateParser.cs</Source>
</Exclude>
</Sources>
</CodeCoverage>

Просмотреть файл

@ -1,5 +1,8 @@
lexer grammar ExpressionAntlrLexer;
@parser::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::members {
bool ignoreWS = true; // usually we ignore whitespace, but inside stringInterpolation, whitespace is significant
}

Просмотреть файл

@ -1,5 +1,8 @@
parser grammar ExpressionAntlrParser;
@parser::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
options { tokenVocab=ExpressionAntlrLexer; }
file: expression EOF;

Просмотреть файл

@ -13,8 +13,6 @@ using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;
using Newtonsoft.Json.Linq;
[assembly: CLSCompliant(false)]
namespace AdaptiveExpressions
{
/// <summary>

Просмотреть файл

@ -12,7 +12,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// LG template analyzer.
/// </summary>
public class Analyzer : LGFileParserBaseVisitor<AnalyzerResult>
public class Analyzer : LGTemplateParserBaseVisitor<AnalyzerResult>
{
private readonly Dictionary<string, Template> templateMap;
@ -23,8 +23,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="Analyzer"/> class.
/// </summary>
/// <param name="templates">template list.</param>
/// <param name="expressionParser">expression parser.</param>
/// <param name="templates">Template list.</param>
/// <param name="expressionParser">Expression parser.</param>
public Analyzer(List<Template> templates, ExpressionParser expressionParser)
{
Templates = templates;
@ -47,7 +47,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Analyzer a template to get the static analyzer results.
/// </summary>
/// <param name="templateName">Template name.</param>
/// <returns>analyze result including variables and template references.</returns>
/// <returns>Analyze result including variables and template references.</returns>
public AnalyzerResult AnalyzeTemplate(string templateName)
{
if (!templateMap.ContainsKey(templateName))
@ -67,29 +67,15 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
// because given we don't track down for templates have parameters
// the only scenario that we are still analyzing an parameterized template is
// this template is root template to analyze, in this we also don't have exclude parameters
var dependencies = Visit(templateMap[templateName].ParseTree);
var dependencies = Visit(templateMap[templateName].TemplateBodyParseTree);
evaluationTargetStack.Pop();
return dependencies;
}
public override AnalyzerResult VisitTemplateDefinition([NotNull] LGFileParser.TemplateDefinitionContext context)
{
var templateNameContext = context.templateNameLine();
if (templateNameContext.templateName().GetText().Equals(CurrentTarget().TemplateName))
{
if (context.templateBody() != null)
{
return Visit(context.templateBody());
}
}
public override AnalyzerResult VisitNormalBody([NotNull] LGTemplateParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
return new AnalyzerResult();
}
public override AnalyzerResult VisitNormalBody([NotNull] LGFileParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
public override AnalyzerResult VisitNormalTemplateBody([NotNull] LGFileParser.NormalTemplateBodyContext context)
public override AnalyzerResult VisitNormalTemplateBody([NotNull] LGTemplateParser.NormalTemplateBodyContext context)
{
var result = new AnalyzerResult();
@ -102,7 +88,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override AnalyzerResult VisitStructuredTemplateBody([NotNull] LGFileParser.StructuredTemplateBodyContext context)
public override AnalyzerResult VisitStructuredTemplateBody([NotNull] LGTemplateParser.StructuredTemplateBodyContext context)
{
var result = new AnalyzerResult();
@ -123,7 +109,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override AnalyzerResult VisitIfElseBody([NotNull] LGFileParser.IfElseBodyContext context)
public override AnalyzerResult VisitIfElseBody([NotNull] LGTemplateParser.IfElseBodyContext context)
{
var result = new AnalyzerResult();
@ -145,7 +131,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override AnalyzerResult VisitSwitchCaseBody([NotNull] LGFileParser.SwitchCaseBodyContext context)
public override AnalyzerResult VisitSwitchCaseBody([NotNull] LGTemplateParser.SwitchCaseBodyContext context)
{
var result = new AnalyzerResult();
var switchCaseNodes = context.switchCaseTemplateBody().switchCaseRule();
@ -166,7 +152,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override AnalyzerResult VisitNormalTemplateString([NotNull] LGFileParser.NormalTemplateStringContext context)
public override AnalyzerResult VisitNormalTemplateString([NotNull] LGTemplateParser.NormalTemplateStringContext context)
{
var result = new AnalyzerResult();
foreach (var expression in context.EXPRESSION())
@ -177,7 +163,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
private AnalyzerResult VisitStructureValue(LGFileParser.KeyValueStructureLineContext context)
private AnalyzerResult VisitStructureValue(LGTemplateParser.KeyValueStructureLineContext context)
{
var values = context.keyValueStructureValue();
@ -211,7 +197,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// return only those without parameters.
/// </summary>
/// <param name="exp">Expression.</param>
/// <returns>template refs.</returns>
/// <returns>Template refs.</returns>
private AnalyzerResult AnalyzeExpressionDirectly(Expression exp)
{
var result = new AnalyzerResult();

Просмотреть файл

@ -35,7 +35,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets template references that this template contains.
/// </summary>
/// <value>
/// template references that this template contains.
/// Template references that this template contains.
/// </value>
public List<string> TemplateReferences { get; set; }

Просмотреть файл

@ -17,7 +17,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="CustomizedMemory"/> class.
/// </summary>
/// <param name="scope">scope.</param>
/// <param name="scope">Scope.</param>
public CustomizedMemory(object scope)
{
this.GlobalMemory = scope == null ? null : MemoryFactory.Create(scope);
@ -27,8 +27,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="CustomizedMemory"/> class.
/// </summary>
/// <param name="globalMemory">global memory.</param>
/// <param name="localMemory">local memory.</param>
/// <param name="globalMemory">Global memory.</param>
/// <param name="localMemory">Local memory.</param>
public CustomizedMemory(IMemory globalMemory, IMemory localMemory = null)
{
this.GlobalMemory = globalMemory;
@ -60,9 +60,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Try to get the value from a given path. Firstly, get result from global memory,
/// if global memory does not contain, get from local memory.
/// </summary>
/// <param name="path">memory path.</param>
/// <param name="value">resolved value.</param>
/// <returns> true if the memory contains an element with the specified key; otherwise, false.</returns>
/// <param name="path">Memory path.</param>
/// <param name="value">Resolved value.</param>
/// <returns>True if the memory contains an element with the specified key; otherwise, false.</returns>
public bool TryGetValue(string path, out object value)
{
value = null;

Просмотреть файл

@ -14,16 +14,18 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
public class ErrorListener : BaseErrorListener
{
private readonly string source;
private readonly int lineOffset;
public ErrorListener(string errorSource)
public ErrorListener(string errorSource, int lineOffset = 0)
{
source = errorSource;
this.lineOffset = lineOffset;
}
public override void SyntaxError([NotNull] IRecognizer recognizer, [Nullable] IToken offendingSymbol, int line, int charPositionInLine, [NotNull] string msg, [Nullable] RecognitionException e)
{
var startPosition = new Position(line, charPositionInLine);
var stopPosition = new Position(line, charPositionInLine + offendingSymbol.StopIndex - offendingSymbol.StartIndex + 1);
var startPosition = new Position(lineOffset + line, charPositionInLine);
var stopPosition = new Position(lineOffset + line, charPositionInLine + offendingSymbol.StopIndex - offendingSymbol.StartIndex + 1);
var range = new Range(startPosition, stopPosition);
var diagnostic = new Diagnostic(range, TemplateErrors.SyntaxError, DiagnosticSeverity.Error, source);
throw new TemplateException(diagnostic.ToString(), new List<Diagnostic>() { diagnostic });

Просмотреть файл

@ -13,8 +13,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="EvaluationTarget"/> class.
/// </summary>
/// <param name="templateName">template name.</param>
/// <param name="scope">template scope.</param>
/// <param name="templateName">Template name.</param>
/// <param name="scope">Template scope.</param>
public EvaluationTarget(string templateName, object scope)
{
TemplateName = templateName;
@ -33,7 +33,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets template name.
/// </summary>
/// <value>
/// template name.
/// Template name.
/// </value>
public string TemplateName { get; set; }
@ -49,7 +49,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Get current instance id. If two target has the same Id,
/// we can say they have the same template evaluation.
/// </summary>
/// <returns>id.</returns>
/// <returns>Id.</returns>
public string GetId()
{
var memory = (CustomizedMemory)Scope;

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// LG template Evaluator.
/// </summary>
public class Evaluator : LGFileParserBaseVisitor<object>
public class Evaluator : LGTemplateParserBaseVisitor<object>
{
public const string LGType = "lgType";
@ -34,8 +34,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Initializes a new instance of the <see cref="Evaluator"/> class.
/// </summary>
/// <param name="templates">Template list.</param>
/// <param name="expressionParser">expression parser.</param>
/// <param name="strictMode">strict mode. If strictMode == true, exception in expression would throw outside.</param>
/// <param name="expressionParser">Expression parser.</param>
/// <param name="strictMode">Strict mode. If strictMode == true, exception in expression would throw outside.</param>
public Evaluator(List<Template> templates, ExpressionParser expressionParser, bool strictMode = false)
{
Templates = templates;
@ -73,8 +73,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Evaluate a template with given name and scope.
/// </summary>
/// <param name="inputTemplateName">template name.</param>
/// <param name="scope">scope.</param>
/// <param name="inputTemplateName">Template name.</param>
/// <param name="scope">Scope.</param>
/// <returns>Evaluate result.</returns>
public object EvaluateTemplate(string inputTemplateName, object scope)
{
@ -112,7 +112,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
// Using a stack to track the evaluation trace
evaluationTargetStack.Push(templateTarget);
var result = Visit(TemplateMap[templateName].ParseTree);
var result = Visit(TemplateMap[templateName].TemplateBodyParseTree);
if (previousEvaluateTarget != null)
{
previousEvaluateTarget.EvaluatedChildren[currentEvaluateId] = result;
@ -123,18 +123,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override object VisitTemplateDefinition([NotNull] LGFileParser.TemplateDefinitionContext context)
{
var templateNameContext = context.templateNameLine();
if (templateNameContext.templateName().GetText().Equals(CurrentTarget().TemplateName))
{
return Visit(context.templateBody());
}
return null;
}
public override object VisitStructuredTemplateBody([NotNull] LGFileParser.StructuredTemplateBodyContext context)
public override object VisitStructuredTemplateBody([NotNull] LGTemplateParser.StructuredTemplateBodyContext context)
{
var result = new JObject();
var typeName = context.structuredBodyNameLine().STRUCTURE_NAME().GetText();
@ -173,16 +162,16 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override object VisitNormalBody([NotNull] LGFileParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
public override object VisitNormalBody([NotNull] LGTemplateParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
public override object VisitNormalTemplateBody([NotNull] LGFileParser.NormalTemplateBodyContext context)
public override object VisitNormalTemplateBody([NotNull] LGTemplateParser.NormalTemplateBodyContext context)
{
var normalTemplateStrs = context.templateString();
var rd = new Random();
return Visit(normalTemplateStrs[rd.Next(normalTemplateStrs.Length)].normalTemplateString());
}
public override object VisitIfElseBody([NotNull] LGFileParser.IfElseBodyContext context)
public override object VisitIfElseBody([NotNull] LGTemplateParser.IfElseBodyContext context)
{
var ifRules = context.ifElseTemplateBody().ifConditionRule();
foreach (var ifRule in ifRules)
@ -196,7 +185,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return null;
}
public override object VisitSwitchCaseBody([NotNull] LGFileParser.SwitchCaseBodyContext context)
public override object VisitSwitchCaseBody([NotNull] LGTemplateParser.SwitchCaseBodyContext context)
{
var switchCaseNodes = context.switchCaseTemplateBody().switchCaseRule();
var length = switchCaseNodes.Length;
@ -240,7 +229,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return null;
}
public override object VisitNormalTemplateString([NotNull] LGFileParser.NormalTemplateStringContext context)
public override object VisitNormalTemplateString([NotNull] LGTemplateParser.NormalTemplateStringContext context)
{
var prefixErrorMsg = context.GetPrefixErrorMessage();
@ -249,14 +238,14 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
{
switch (node.Symbol.Type)
{
case LGFileParser.DASH:
case LGFileParser.MULTILINE_PREFIX:
case LGFileParser.MULTILINE_SUFFIX:
case LGTemplateParser.DASH:
case LGTemplateParser.MULTILINE_PREFIX:
case LGTemplateParser.MULTILINE_SUFFIX:
break;
case LGFileParser.ESCAPE_CHARACTER:
case LGTemplateParser.ESCAPE_CHARACTER:
result.Add(node.GetText().Escape());
break;
case LGFileParser.EXPRESSION:
case LGTemplateParser.EXPRESSION:
result.Add(EvalExpression(node.GetText(), context, prefixErrorMsg));
break;
default:
@ -345,7 +334,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
throw new Exception(ConcatErrorMsg(childErrorMsg, errorMsg));
}
private object VisitStructureValue(LGFileParser.KeyValueStructureLineContext context)
private object VisitStructureValue(LGTemplateParser.KeyValueStructureLineContext context)
{
var values = context.keyValueStructureValue();
@ -363,10 +352,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
{
switch (node.Symbol.Type)
{
case LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
case LGTemplateParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
itemStringResult.Append(node.GetText().Replace(@"\|", "|").Escape());
break;
case LGFileParser.EXPRESSION_IN_STRUCTURE_BODY:
case LGTemplateParser.EXPRESSION_IN_STRUCTURE_BODY:
var errorPrefix = "Property '" + context.STRUCTURE_IDENTIFIER().GetText() + "':";
itemStringResult.Append(EvalExpression(node.GetText(), context, errorPrefix));
break;
@ -383,7 +372,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result.Count == 1 ? result[0] : result;
}
private bool EvalCondition(LGFileParser.IfConditionContext condition)
private bool EvalCondition(LGTemplateParser.IfConditionContext condition)
{
var expression = condition.EXPRESSION(0);
if (expression == null || // no expression means it's else
@ -452,7 +441,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
private (object value, string error) EvalByAdaptiveExpression(string exp, object scope)
{
var parse = this.ExpressionParser.Parse(exp);
return parse.TryEvaluate(scope);
var result = parse.TryEvaluate(scope);
return result;
}
// Generate a new lookup function based on one lookup function
@ -555,7 +545,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
else
{
var template = TemplateMap[CurrentTarget().TemplateName];
var sourcePath = template.Source.NormalizePath();
var sourcePath = template.SourceRange.Source.NormalizePath();
var baseFolder = Environment.CurrentDirectory;
if (Path.IsPathRooted(sourcePath))
{
@ -587,7 +577,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
if ((children0.ReturnType & ReturnType.Object) == 0 && (children0.ReturnType & ReturnType.String) == 0)
{
throw new Exception(TemplateErrors.ErrorTemplateNameformat(children0.ToString()));
throw new Exception(TemplateErrors.InvalidTemplateName);
}
// Validate more if the name is string constant

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// LG template expander.
/// </summary>
public class Expander : LGFileParserBaseVisitor<List<object>>
public class Expander : LGTemplateParserBaseVisitor<List<object>>
{
public const string LGType = "lgType";
public static readonly string RegexString = @"(?<!\\)\${(('(\\('|\\)|[^'])*?')|(""(\\(""|\\)|[^""])*?"")|(`(\\(`|\\)|[^`])*?`)|([^\r\n{}'""`])|({\s*}))+}?";
@ -30,9 +30,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="Expander"/> class.
/// </summary>
/// <param name="templates">template list.</param>
/// <param name="templates">Template list.</param>
/// <param name="expressionParser">Given expression parser.</param>
/// <param name="strictMode">strict mode. If strictMode == true, exception in expression would throw outside.</param>
/// <param name="strictMode">Strict mode. If strictMode == true, exception in expression would throw outside.</param>
public Expander(List<Template> templates, ExpressionParser expressionParser, bool strictMode = false)
{
Templates = templates;
@ -101,26 +101,15 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
// Using a stack to track the evaluation trace
evaluationTargetStack.Push(new EvaluationTarget(templateName, scope));
var result = Visit(TemplateMap[templateName].ParseTree);
var result = Visit(TemplateMap[templateName].TemplateBodyParseTree);
evaluationTargetStack.Pop();
return result;
}
public override List<object> VisitTemplateDefinition([NotNull] LGFileParser.TemplateDefinitionContext context)
{
var templateNameContext = context.templateNameLine();
if (templateNameContext.templateName().GetText().Equals(CurrentTarget().TemplateName))
{
return Visit(context.templateBody());
}
public override List<object> VisitNormalBody([NotNull] LGTemplateParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
return null;
}
public override List<object> VisitNormalBody([NotNull] LGFileParser.NormalBodyContext context) => Visit(context.normalTemplateBody());
public override List<object> VisitNormalTemplateBody([NotNull] LGFileParser.NormalTemplateBodyContext context)
public override List<object> VisitNormalTemplateBody([NotNull] LGTemplateParser.NormalTemplateBodyContext context)
{
var normalTemplateStrs = context.templateString();
var result = new List<object>();
@ -133,7 +122,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override List<object> VisitIfElseBody([NotNull] LGFileParser.IfElseBodyContext context)
public override List<object> VisitIfElseBody([NotNull] LGTemplateParser.IfElseBodyContext context)
{
var ifRules = context.ifElseTemplateBody().ifConditionRule();
foreach (var ifRule in ifRules)
@ -147,7 +136,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return null;
}
public override List<object> VisitSwitchCaseBody([NotNull] LGFileParser.SwitchCaseBodyContext context)
public override List<object> VisitSwitchCaseBody([NotNull] LGTemplateParser.SwitchCaseBodyContext context)
{
var switchCaseNodes = context.switchCaseTemplateBody().switchCaseRule();
var length = switchCaseNodes.Length;
@ -190,7 +179,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return null;
}
public override List<object> VisitStructuredBody([NotNull] LGFileParser.StructuredBodyContext context)
public override List<object> VisitStructuredBody([NotNull] LGTemplateParser.StructuredBodyContext context)
{
var templateRefValues = new Dictionary<string, List<object>>();
var stb = context.structuredTemplateBody();
@ -278,7 +267,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return finalResult;
}
public override List<object> VisitNormalTemplateString([NotNull] LGFileParser.NormalTemplateStringContext context)
public override List<object> VisitNormalTemplateString([NotNull] LGTemplateParser.NormalTemplateStringContext context)
{
var prefixErrorMsg = context.GetPrefixErrorMessage();
var result = new List<string>() { string.Empty };
@ -286,14 +275,14 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
{
switch (node.Symbol.Type)
{
case LGFileParser.DASH:
case LGFileParser.MULTILINE_PREFIX:
case LGFileParser.MULTILINE_SUFFIX:
case LGTemplateParser.DASH:
case LGTemplateParser.MULTILINE_PREFIX:
case LGTemplateParser.MULTILINE_SUFFIX:
break;
case LGFileParser.ESCAPE_CHARACTER:
case LGTemplateParser.ESCAPE_CHARACTER:
result = StringListConcat(result, new List<string>() { node.GetText().Escape() });
break;
case LGFileParser.EXPRESSION:
case LGTemplateParser.EXPRESSION:
result = StringListConcat(result, EvalExpression(node.GetText(), context, prefixErrorMsg));
break;
default:
@ -320,7 +309,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return newScope;
}
private bool EvalCondition(LGFileParser.IfConditionContext condition)
private bool EvalCondition(LGTemplateParser.IfConditionContext condition)
{
var expression = condition.EXPRESSION(0);
if (expression == null || // no expression means it's else
@ -332,7 +321,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return false;
}
private List<List<object>> VisitStructureValue(LGFileParser.KeyValueStructureLineContext context)
private List<List<object>> VisitStructureValue(LGTemplateParser.KeyValueStructureLineContext context)
{
var values = context.keyValueStructureValue();
@ -350,10 +339,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
{
switch (node.Symbol.Type)
{
case LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
case LGTemplateParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
itemStringResult = StringListConcat(itemStringResult, new List<string>() { node.GetText().Replace(@"\|", "|").Escape() });
break;
case LGFileParser.EXPRESSION_IN_STRUCTURE_BODY:
case LGTemplateParser.EXPRESSION_IN_STRUCTURE_BODY:
var errorPrefix = "Property '" + context.STRUCTURE_IDENTIFIER().GetText() + "':";
itemStringResult = StringListConcat(itemStringResult, EvalExpression(node.GetText(), item, errorPrefix));
break;
@ -556,7 +545,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
if ((children0.ReturnType & ReturnType.Object) == 0 && (children0.ReturnType & ReturnType.String) == 0)
{
throw new Exception(TemplateErrors.ErrorTemplateNameformat(children0.ToString()));
throw new Exception(TemplateErrors.InvalidTemplateName);
}
// Validate more if the name is string constant
@ -658,7 +647,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
else
{
var template = TemplateMap[CurrentTarget().TemplateName];
var sourcePath = template.Source.NormalizePath();
var sourcePath = template.SourceRange.Source.NormalizePath();
var baseFolder = Environment.CurrentDirectory;
if (Path.IsPathRooted(sourcePath))
{

Просмотреть файл

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
namespace Microsoft.Bot.Builder.LanguageGeneration
@ -19,9 +20,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// If a value is pure Expression.
/// </summary>
/// <param name="context">Key value structure value context.</param>
/// <param name="expression">string expression.</param>
/// <returns>is pure expression or not.</returns>
public static bool IsPureExpression(this LGFileParser.KeyValueStructureValueContext context, out string expression)
/// <param name="expression">String expression.</param>
/// <returns>Is pure expression or not.</returns>
public static bool IsPureExpression(this LGTemplateParser.KeyValueStructureValueContext context, out string expression)
{
expression = context.GetText();
@ -30,9 +31,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
{
switch (node.Symbol.Type)
{
case LGFileParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
case LGTemplateParser.ESCAPE_CHARACTER_IN_STRUCTURE_BODY:
return false;
case LGFileParser.EXPRESSION_IN_STRUCTURE_BODY:
case LGTemplateParser.EXPRESSION_IN_STRUCTURE_BODY:
if (hasExpression)
{
return false;
@ -57,8 +58,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Escape \ from text.
/// </summary>
/// <param name="text">input text.</param>
/// <returns>escaped text.</returns>
/// <param name="text">Input text.</param>
/// <returns>Escaped text.</returns>
public static string Escape(this string text)
{
if (text == null)
@ -88,8 +89,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// trim expression. ${abc} => abc, ${a == {}} => a == {}.
/// </summary>
/// <param name="expression">input expression string.</param>
/// <returns>pure expression string.</returns>
/// <param name="expression">Input expression string.</param>
/// <returns>Pure expression string.</returns>
public static string TrimExpression(this string expression)
{
var result = expression.Trim().TrimStart('$').Trim();
@ -110,8 +111,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// This method treats / and \ both as separators regardless of OS, for Windows that means / -> \ and for Linux/Mac \ -> /.
/// This allows author to use ../foo.lg or ..\foo.lg as equivalents for importing.
/// </remarks>
/// <param name="ambigiousPath">authoredPath.</param>
/// <returns>path expressed as OS path.</returns>
/// <param name="ambigiousPath">Authored path.</param>
/// <returns>Path expressed as OS path.</returns>
public static string NormalizePath(this string ambigiousPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -129,18 +130,18 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Get prefix error message from normal template sting context.
/// </summary>
/// <param name="context">normal template sting context.</param>
/// <returns>prefix error message.</returns>
public static string GetPrefixErrorMessage(this LGFileParser.NormalTemplateStringContext context)
/// <param name="context">Normal template sting context.</param>
/// <returns>Prefix error message.</returns>
public static string GetPrefixErrorMessage(this LGTemplateParser.NormalTemplateStringContext context)
{
var errorPrefix = string.Empty;
if (context.Parent?.Parent?.Parent is LGFileParser.IfConditionRuleContext conditionContext)
if (context.Parent?.Parent?.Parent is LGTemplateParser.IfConditionRuleContext conditionContext)
{
errorPrefix = "Condition '" + conditionContext.ifCondition()?.EXPRESSION(0)?.GetText() + "': ";
}
else
{
if (context.Parent?.Parent?.Parent is LGFileParser.SwitchCaseRuleContext switchCaseContext)
if (context.Parent?.Parent?.Parent is LGTemplateParser.SwitchCaseRuleContext switchCaseContext)
{
var state = switchCaseContext.switchCaseStat();
if (state?.DEFAULT() != null)
@ -160,5 +161,23 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return errorPrefix;
}
/// <summary>
/// Convert antlr parser into Range.
/// </summary>
/// <param name="context">Antlr parse context.</param>
/// <param name="lineOffset">Line offset.</param>
/// <returns>Range object.</returns>
public static Range ConvertToRange(this ParserRuleContext context, int lineOffset = 0)
{
if (context == null)
{
return Range.DefaultRange;
}
var startPosition = new Position(lineOffset + context.Start.Line, context.Start.Column);
var stopPosition = new Position(lineOffset + context.Stop.Line, context.Stop.Column + context.Stop.Text.Length);
return new Range(startPosition, stopPosition);
}
}
}

Просмотреть файл

@ -1,259 +1,24 @@
lexer grammar LGFileLexer;
// From a multiple-lanague perpective, it's not recommended to use members, predicates and actions to
// put target-language-specific code in lexer rules
// the reason we use it here is that
// 1. it greatly simplify the lexer rules, can avoid unnecessary lexer modes
// 2. it helps us to output token more precisely
// (for example, 'CASE:' not followed right after '-' will not be treated as a CASE token)
// 3. we only use very basic boolen variables, and basic predidates
// so it would be very little effort to translate to other languages
@parser::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::members {
bool ignoreWS = true; // usually we ignore whitespace, but inside template, whitespace is significant
bool inTemplate = false; // whether we are in the template
bool beginOfTemplateBody = false; // whether we are at the begining of template body
bool inMultiline = false; // whether we are in multiline
bool beginOfTemplateLine = false;// weather we are at the begining of template string
bool inStructuredValue = false; // weather we are in the structure value
bool beginOfStructureProperty = false; // weather we are at the begining of structure property
bool startTemplate = false;
}
// fragments
fragment A: 'a' | 'A';
fragment C: 'c' | 'C';
fragment D: 'd' | 'D';
fragment E: 'e' | 'E';
fragment F: 'f' | 'F';
fragment H: 'h' | 'H';
fragment I: 'i' | 'I';
fragment L: 'l' | 'L';
fragment S: 's' | 'S';
fragment T: 't' | 'T';
fragment U: 'u' | 'U';
fragment W: 'w' | 'W';
fragment LETTER: 'a'..'z' | 'A'..'Z';
fragment NUMBER: '0'..'9';
fragment WHITESPACE : ' '|'\t'|'\ufeff'|'\u00a0';
fragment STRING_LITERAL : ('\'' (('\\'('\''|'\\'))|(~'\''))*? '\'') | ('"' (('\\'('"'|'\\'))|(~'"'))*? '"');
NEWLINE : '\r'? '\n';
fragment STRING_INTERPOLATION : '`' (('\\'('`'|'\\'))|(~'`'))*? '`';
OPTION : WHITESPACE* '>' WHITESPACE* '!#' ~('\r'|'\n')+ { !startTemplate }?;
fragment ESCAPE_CHARACTER_FRAGMENT : '\\' ~[\r\n]?;
COMMENT : WHITESPACE* '>' ~('\r'|'\n')* { !startTemplate }?;
IMPORT : WHITESPACE* '[' ~[\r\n[\]]*? ']' '(' ~[\r\n()]*? ')' WHITESPACE* { !startTemplate }?;
// top level elements
OPTIONS
: '>' WHITESPACE* '!#' ~('\r'|'\n')+
;
TEMPLATE_NAME_LINE : WHITESPACE* '#' ~('\r'|'\n')* { startTemplate = true; };
COMMENTS
: '>' ~('\r'|'\n')* -> skip
;
WS
: WHITESPACE+ -> skip
;
NEWLINE
: '\r'? '\n' -> skip
;
HASH
: '#' { inTemplate = true; beginOfTemplateBody = false; } -> pushMode(TEMPLATE_NAME_MODE)
;
DASH
: '-' { inTemplate }? { beginOfTemplateLine = true; beginOfTemplateBody = false; } -> pushMode(TEMPLATE_BODY_MODE)
;
OBJECT_DEFINITION
: '{' ((WHITESPACE) | ((IDENTIFIER | STRING_LITERAL) ':' ( STRING_LITERAL | ~[{}\r\n'"`] | OBJECT_DEFINITION)+))* '}'
;
EXPRESSION_FRAGMENT
: '$' '{' (STRING_LITERAL | STRING_INTERPOLATION | OBJECT_DEFINITION | ~[}'"`])+ '}'?
;
LEFT_SQUARE_BRACKET
: '[' { inTemplate && beginOfTemplateBody }? -> pushMode(STRUCTURE_NAME_MODE)
;
IMPORT
: '[' ~[\r\n[\]]*? ']' '(' ~[\r\n()]*? ')' { inTemplate = false;}
;
INVALID_TOKEN
: . { inTemplate = false; beginOfTemplateBody = false; }
;
mode TEMPLATE_NAME_MODE;
WS_IN_NAME
: WHITESPACE+ -> skip
;
NEWLINE_IN_NAME
: '\r'? '\n' { beginOfTemplateBody = true;}-> skip, popMode
;
IDENTIFIER
: (LETTER | NUMBER | '_') (LETTER | NUMBER | '_')*
;
DOT
: '.'
;
OPEN_PARENTHESIS
: '('
;
CLOSE_PARENTHESIS
: ')'
;
COMMA
: ','
;
TEXT_IN_NAME
: ~[\r\n]+?
;
mode TEMPLATE_BODY_MODE;
WS_IN_BODY
: WHITESPACE+ {ignoreWS}? -> skip
;
MULTILINE_PREFIX
: '```' { !inMultiline && beginOfTemplateLine }? { inMultiline = true; beginOfTemplateLine = false;}-> pushMode(MULTILINE_MODE)
;
NEWLINE_IN_BODY
: '\r'? '\n' { ignoreWS = true;} -> skip, popMode
;
IF
: I F WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ELSEIF
: E L S E WHITESPACE* I F WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ELSE
: E L S E WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
SWITCH
: S W I T C H WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
CASE
: C A S E WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
DEFAULT
: D E F A U L T WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ESCAPE_CHARACTER
: ESCAPE_CHARACTER_FRAGMENT { ignoreWS = false; beginOfTemplateLine = false;}
;
EXPRESSION
: EXPRESSION_FRAGMENT { ignoreWS = false; beginOfTemplateLine = false;}
;
TEXT
: ~[\r\n]+? { ignoreWS = false; beginOfTemplateLine = false;}
;
mode MULTILINE_MODE;
MULTILINE_SUFFIX
: '```' { inMultiline = false; } -> popMode
;
MULTILINE_ESCAPE_CHARACTER
: ESCAPE_CHARACTER_FRAGMENT -> type(ESCAPE_CHARACTER)
;
MULTILINE_EXPRESSION
: EXPRESSION_FRAGMENT -> type(EXPRESSION)
;
MULTILINE_TEXT
: (('\r'? '\n') | ~[\r\n])+? -> type(TEXT)
;
mode STRUCTURE_NAME_MODE;
WS_IN_STRUCTURE_NAME
: WHITESPACE+ -> skip
;
NEWLINE_IN_STRUCTURE_NAME
: '\r'? '\n' { ignoreWS = true;} {beginOfStructureProperty = true;}-> skip, pushMode(STRUCTURE_BODY_MODE)
;
STRUCTURE_NAME
: (LETTER | NUMBER | '_') (LETTER | NUMBER | '-' | '_' | '.')*
;
TEXT_IN_STRUCTURE_NAME
: ~[\r\n]+?
;
mode STRUCTURE_BODY_MODE;
STRUCTURED_COMMENTS
: '>' ~[\r\n]* '\r'?'\n' { !inStructuredValue && beginOfStructureProperty}? -> skip
;
WS_IN_STRUCTURE_BODY
: WHITESPACE+ {ignoreWS}? -> skip
;
STRUCTURED_NEWLINE
: '\r'? '\n' { ignoreWS = true; inStructuredValue = false; beginOfStructureProperty = true;}
;
STRUCTURED_BODY_END
: ']' {!inStructuredValue}? { inTemplate = false; beginOfTemplateBody = false;} -> popMode, popMode
;
STRUCTURE_IDENTIFIER
: (LETTER | NUMBER | '_') (LETTER | NUMBER | '-' | '_' | '.')* { !inStructuredValue && beginOfStructureProperty}? {beginOfStructureProperty = false;}
;
STRUCTURE_EQUALS
: '=' {!inStructuredValue}? {inStructuredValue = true;}
;
STRUCTURE_OR_MARK
: '|' { ignoreWS = true; }
;
ESCAPE_CHARACTER_IN_STRUCTURE_BODY
: ESCAPE_CHARACTER_FRAGMENT { ignoreWS = false; }
;
EXPRESSION_IN_STRUCTURE_BODY
: EXPRESSION_FRAGMENT { ignoreWS = false; }
;
TEXT_IN_STRUCTURE_BODY
: ~[\r\n]+? { ignoreWS = false; beginOfStructureProperty = false;}
;
TEMPLATE_BODY_LINE : ~('\r'|'\n')+ { startTemplate }?;
INVALID_LINE : ~('\r'|'\n')+ { !startTemplate }?;

Просмотреть файл

@ -6,130 +6,47 @@ parser grammar LGFileParser;
options { tokenVocab=LGFileLexer; }
file
: paragraph+? EOF
;
: paragraph+? EOF
;
paragraph
: templateDefinition
| importDefinition
| optionsDefinition
| optionDefinition
| errorDefinition
| commentDefinition
| NEWLINE
| EOF
| errorTemplate
;
errorTemplate
: INVALID_TOKEN+
;
templateDefinition
: templateNameLine templateBody?
;
templateNameLine
: HASH ((templateName parameters?) | errorTemplateName)
;
errorTemplateName
: (IDENTIFIER|TEXT_IN_NAME|OPEN_PARENTHESIS|COMMA|CLOSE_PARENTHESIS|DOT)*
;
templateName
: IDENTIFIER (DOT IDENTIFIER)*
;
parameters
: OPEN_PARENTHESIS (IDENTIFIER (COMMA IDENTIFIER)*)? CLOSE_PARENTHESIS
;
templateBody
: normalTemplateBody #normalBody
| ifElseTemplateBody #ifElseBody
| switchCaseTemplateBody #switchCaseBody
| structuredTemplateBody #structuredBody
;
structuredTemplateBody
: structuredBodyNameLine (((structuredBodyContentLine? STRUCTURED_NEWLINE) | errorStructureLine)+)? structuredBodyEndLine?
;
structuredBodyNameLine
: LEFT_SQUARE_BRACKET (STRUCTURE_NAME | errorStructuredName)
;
errorStructuredName
: (STRUCTURE_NAME|TEXT_IN_STRUCTURE_NAME)*
;
structuredBodyContentLine
: keyValueStructureLine
| objectStructureLine
;
errorStructureLine
: (STRUCTURE_IDENTIFIER|STRUCTURE_EQUALS|STRUCTURE_OR_MARK|TEXT_IN_STRUCTURE_BODY|EXPRESSION_IN_STRUCTURE_BODY|ESCAPE_CHARACTER_IN_STRUCTURE_BODY)+
;
keyValueStructureLine
: STRUCTURE_IDENTIFIER STRUCTURE_EQUALS keyValueStructureValue (STRUCTURE_OR_MARK keyValueStructureValue)*
;
keyValueStructureValue
: (TEXT_IN_STRUCTURE_BODY|EXPRESSION_IN_STRUCTURE_BODY|ESCAPE_CHARACTER_IN_STRUCTURE_BODY)+
;
objectStructureLine
: EXPRESSION_IN_STRUCTURE_BODY
;
structuredBodyEndLine
: STRUCTURED_BODY_END
;
normalTemplateBody
: templateString+
;
templateString
: normalTemplateString
| errorTemplateString
;
normalTemplateString
: DASH MULTILINE_PREFIX? (TEXT|EXPRESSION|ESCAPE_CHARACTER)* MULTILINE_SUFFIX?
;
errorTemplateString
: INVALID_TOKEN+
;
ifElseTemplateBody
: ifConditionRule+
;
ifConditionRule
: ifCondition normalTemplateBody?
;
ifCondition
: DASH (IF|ELSE|ELSEIF) (WS|TEXT|EXPRESSION)*
;
switchCaseTemplateBody
: switchCaseRule+
;
switchCaseRule
: switchCaseStat normalTemplateBody?
;
switchCaseStat
: DASH (SWITCH|CASE|DEFAULT) (WS|TEXT|EXPRESSION)*
commentDefinition
: COMMENT NEWLINE?
;
importDefinition
: IMPORT
: IMPORT NEWLINE?
;
optionsDefinition
: OPTIONS
optionDefinition
: OPTION NEWLINE?
;
errorDefinition
: INVALID_LINE NEWLINE?
;
templateDefinition
: templateNameLine templateBody
;
templateNameLine
: TEMPLATE_NAME_LINE NEWLINE?
;
templateBody
: templateBodyLine*
;
templateBodyLine
: (TEMPLATE_BODY_LINE NEWLINE?) | NEWLINE
;

Просмотреть файл

@ -0,0 +1,202 @@
lexer grammar LGTemplateLexer;
@parser::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::members {
bool ignoreWS = true; // usually we ignore whitespace, but inside template, whitespace is significant
bool beginOfTemplateBody = true; // whether we are at the begining of template body
bool inMultiline = false; // whether we are in multiline
bool beginOfTemplateLine = false;// weather we are at the begining of template string
bool inStructuredValue = false; // weather we are in the structure value
bool beginOfStructureProperty = false; // weather we are at the begining of structure property
}
// fragments
fragment A: 'a' | 'A';
fragment C: 'c' | 'C';
fragment D: 'd' | 'D';
fragment E: 'e' | 'E';
fragment F: 'f' | 'F';
fragment H: 'h' | 'H';
fragment I: 'i' | 'I';
fragment L: 'l' | 'L';
fragment S: 's' | 'S';
fragment T: 't' | 'T';
fragment U: 'u' | 'U';
fragment W: 'w' | 'W';
fragment LETTER: 'a'..'z' | 'A'..'Z';
fragment NUMBER: '0'..'9';
fragment WHITESPACE : ' '|'\t'|'\ufeff'|'\u00a0';
fragment STRING_LITERAL : ('\'' (('\\'('\''|'\\'))|(~'\''))*? '\'') | ('"' (('\\'('"'|'\\'))|(~'"'))*? '"');
fragment STRING_INTERPOLATION : '`' (('\\'('`'|'\\'))|(~'`'))*? '`';
fragment ESCAPE_CHARACTER_FRAGMENT : '\\' ~[\r\n]?;
fragment IDENTIFIER : (LETTER | NUMBER | '_') (LETTER | NUMBER | '_')*;
WS
: WHITESPACE+ -> skip
;
NEWLINE
: '\r'? '\n' -> skip
;
COMMENTS
: '>' ~('\r'|'\n')* -> skip
;
DASH
: '-' { beginOfTemplateLine = true; beginOfTemplateBody = false; } -> pushMode(NORMAL_TEMPLATE_BODY_MODE)
;
OBJECT_DEFINITION
: '{' ((WHITESPACE) | ((IDENTIFIER | STRING_LITERAL) ':' ( STRING_LITERAL | ~[{}\r\n'"`] | OBJECT_DEFINITION)+))* '}'
;
EXPRESSION_FRAGMENT
: '$' '{' (STRING_LITERAL | STRING_INTERPOLATION | OBJECT_DEFINITION | ~[}'"`])+ '}'?
;
LEFT_SQUARE_BRACKET
: '[' { beginOfTemplateBody }? {beginOfTemplateBody = false;} -> pushMode(STRUCTURE_NAME_MODE)
;
INVALID_TOKEN
: . { beginOfTemplateBody = false; }
;
mode NORMAL_TEMPLATE_BODY_MODE;
WS_IN_BODY
: WHITESPACE+ {ignoreWS}? -> skip
;
MULTILINE_PREFIX
: '```' { !inMultiline && beginOfTemplateLine }? { inMultiline = true; beginOfTemplateLine = false;}-> pushMode(MULTILINE_MODE)
;
NEWLINE_IN_BODY
: '\r'? '\n' { ignoreWS = true;} -> skip, popMode
;
IF
: I F WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ELSEIF
: E L S E WHITESPACE* I F WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ELSE
: E L S E WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
SWITCH
: S W I T C H WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
CASE
: C A S E WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
DEFAULT
: D E F A U L T WHITESPACE* ':' {beginOfTemplateLine}? { ignoreWS = true; beginOfTemplateLine = false;}
;
ESCAPE_CHARACTER
: ESCAPE_CHARACTER_FRAGMENT { ignoreWS = false; beginOfTemplateLine = false;}
;
EXPRESSION
: EXPRESSION_FRAGMENT { ignoreWS = false; beginOfTemplateLine = false;}
;
TEXT
: ~[\r\n]+? { ignoreWS = false; beginOfTemplateLine = false;}
;
mode MULTILINE_MODE;
MULTILINE_SUFFIX
: '```' { inMultiline = false; } -> popMode
;
MULTILINE_ESCAPE_CHARACTER
: ESCAPE_CHARACTER_FRAGMENT -> type(ESCAPE_CHARACTER)
;
MULTILINE_EXPRESSION
: EXPRESSION_FRAGMENT -> type(EXPRESSION)
;
MULTILINE_TEXT
: (('\r'? '\n') | ~[\r\n])+? -> type(TEXT)
;
mode STRUCTURE_NAME_MODE;
WS_IN_STRUCTURE_NAME
: WHITESPACE+ -> skip
;
NEWLINE_IN_STRUCTURE_NAME
: '\r'? '\n' { ignoreWS = true;} {beginOfStructureProperty = true;}-> skip, pushMode(STRUCTURE_BODY_MODE)
;
STRUCTURE_NAME
: (LETTER | NUMBER | '_') (LETTER | NUMBER | '-' | '_' | '.')*
;
TEXT_IN_STRUCTURE_NAME
: ~[\r\n]+?
;
mode STRUCTURE_BODY_MODE;
STRUCTURED_COMMENTS
: '>' ~[\r\n]* '\r'?'\n' { !inStructuredValue && beginOfStructureProperty}? -> skip
;
WS_IN_STRUCTURE_BODY
: WHITESPACE+ {ignoreWS}? -> skip
;
STRUCTURED_NEWLINE
: '\r'? '\n' { ignoreWS = true; inStructuredValue = false; beginOfStructureProperty = true;}
;
STRUCTURED_BODY_END
: ']' {!inStructuredValue}? -> popMode, popMode
;
STRUCTURE_IDENTIFIER
: (LETTER | NUMBER | '_') (LETTER | NUMBER | '-' | '_' | '.')* { !inStructuredValue && beginOfStructureProperty}? {beginOfStructureProperty = false;}
;
STRUCTURE_EQUALS
: '=' {!inStructuredValue}? {inStructuredValue = true;}
;
STRUCTURE_OR_MARK
: '|' { ignoreWS = true; }
;
ESCAPE_CHARACTER_IN_STRUCTURE_BODY
: ESCAPE_CHARACTER_FRAGMENT { ignoreWS = false; }
;
EXPRESSION_IN_STRUCTURE_BODY
: EXPRESSION_FRAGMENT { ignoreWS = false; }
;
TEXT_IN_STRUCTURE_BODY
: ~[\r\n]+? { ignoreWS = false; beginOfStructureProperty = false;}
;

Просмотреть файл

@ -0,0 +1,93 @@
parser grammar LGTemplateParser;
@parser::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
@lexer::header {#pragma warning disable 3021} // Disable StyleCop warning CS3021 re CLSCompliant attribute in generated files.
options { tokenVocab=LGTemplateLexer; }
context: body EOF;
body
: normalTemplateBody #normalBody
| ifElseTemplateBody #ifElseBody
| switchCaseTemplateBody #switchCaseBody
| structuredTemplateBody #structuredBody
;
structuredTemplateBody
: structuredBodyNameLine (((structuredBodyContentLine? STRUCTURED_NEWLINE) | errorStructureLine)+)? structuredBodyEndLine?
;
structuredBodyNameLine
: LEFT_SQUARE_BRACKET (STRUCTURE_NAME | errorStructuredName)
;
errorStructuredName
: (STRUCTURE_NAME|TEXT_IN_STRUCTURE_NAME)*
;
structuredBodyContentLine
: keyValueStructureLine
| objectStructureLine
;
errorStructureLine
: (STRUCTURE_IDENTIFIER|STRUCTURE_EQUALS|STRUCTURE_OR_MARK|TEXT_IN_STRUCTURE_BODY|EXPRESSION_IN_STRUCTURE_BODY|ESCAPE_CHARACTER_IN_STRUCTURE_BODY)+
;
keyValueStructureLine
: STRUCTURE_IDENTIFIER STRUCTURE_EQUALS keyValueStructureValue (STRUCTURE_OR_MARK keyValueStructureValue)*
;
keyValueStructureValue
: (TEXT_IN_STRUCTURE_BODY|EXPRESSION_IN_STRUCTURE_BODY|ESCAPE_CHARACTER_IN_STRUCTURE_BODY)+
;
objectStructureLine
: EXPRESSION_IN_STRUCTURE_BODY
;
structuredBodyEndLine
: STRUCTURED_BODY_END
;
normalTemplateBody
: templateString+
;
templateString
: normalTemplateString
| errorTemplateString
;
normalTemplateString
: DASH MULTILINE_PREFIX? (TEXT|EXPRESSION|ESCAPE_CHARACTER)* MULTILINE_SUFFIX?
;
errorTemplateString
: INVALID_TOKEN+
;
ifElseTemplateBody
: ifConditionRule+
;
ifConditionRule
: ifCondition normalTemplateBody?
;
ifCondition
: DASH (IF|ELSE|ELSEIF) (WS|TEXT|EXPRESSION)*
;
switchCaseTemplateBody
: switchCaseRule+
;
switchCaseRule
: switchCaseStat normalTemplateBody?
;
switchCaseStat
: DASH (SWITCH|CASE|DEFAULT) (WS|TEXT|EXPRESSION)*
;

Просмотреть файл

@ -8,12 +8,20 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// </summary>
public class Range
{
public static readonly Range DefaultRange = new Range(1, 0, 1, 0);
public Range(Position start, Position end)
{
Start = start;
End = end;
}
public Range(int startLine, int startChar, int endLine, int endChar)
{
Start = new Position(startLine, startChar);
End = new Position(endLine, endChar);
}
/// <summary>
/// Gets or sets the start position. It is before or equal to <see cref="End"/>.
/// </summary>
@ -35,8 +43,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
var result = Start.ToString();
if (Start.Line <= End.Line && Start.Character < End.Character)
{
result += " - ";
result += End.ToString();
result += $" - {End}";
}
return result;

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Antlr4.Runtime;
namespace Microsoft.Bot.Builder.LanguageGeneration
{
/// <summary>
/// Source range of the context. Including parse tree, source id and the context range.
/// </summary>
public class SourceRange
{
public SourceRange(ParserRuleContext parseTree, string source = "", int offset = 0)
{
this.Source = source ?? string.Empty;
this.ParseTree = parseTree;
this.Range = parseTree.ConvertToRange(offset);
}
public SourceRange(Range range, string source = "")
{
this.Range = range;
this.Source = source ?? string.Empty;
}
/// <summary>
/// Gets or sets range of the block.
/// </summary>
/// <value>
/// range of the block.
/// </value>
public Range Range { get; set; }
/// <summary>
/// Gets or sets code source, used as the lg file path.
/// </summary>
/// <value>
/// Code source, used as the lg file path.
/// </value>
public string Source { get; set; }
/// <summary>
/// Gets or sets content parse tree form LGFileParser.g4.
/// </summary>
/// <value>
/// Content parse tree form LGFileParser.g4.
/// </value>
public ParserRuleContext ParseTree { get; set; }
}
}

Просмотреть файл

@ -13,22 +13,22 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// LG managed code checker.
/// </summary>
internal class StaticChecker : LGFileParserBaseVisitor<List<Diagnostic>>
internal class StaticChecker : LGTemplateParserBaseVisitor<List<Diagnostic>>
{
private readonly ExpressionParser baseExpressionParser;
private readonly Templates templates;
private IList<string> visitedTemplateNames;
private Template currentTemplate;
private IExpressionParser _expressionParser;
/// <summary>
/// Initializes a new instance of the <see cref="StaticChecker"/> class.
/// </summary>
/// <param name="lg">the lg wihch would be checked.</param>
public StaticChecker(Templates lg)
/// <param name="templates">The templates wihch would be checked.</param>
public StaticChecker(Templates templates)
{
this.templates = lg;
baseExpressionParser = lg.ExpressionParser;
this.templates = templates;
baseExpressionParser = templates.ExpressionParser;
}
// Create a property because we want this to be lazy loaded
@ -50,81 +50,48 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Return error messages list.
/// </summary>
/// <returns>report result.</returns>
/// <returns>Report result.</returns>
public List<Diagnostic> Check()
{
visitedTemplateNames = new List<string>();
var result = new List<Diagnostic>();
if (templates.AllTemplates.Count == 0)
{
result.Add(BuildLGDiagnostic(
TemplateErrors.NoTemplate,
DiagnosticSeverity.Warning,
includeTemplateNameInfo: false));
var diagnostic = new Diagnostic(Range.DefaultRange, TemplateErrors.NoTemplate, DiagnosticSeverity.Warning, templates.Id);
result.Add(diagnostic);
return result;
}
templates.ToList().ForEach(t =>
foreach (var template in templates)
{
result.AddRange(Visit(t.ParseTree));
});
currentTemplate = template;
var templateDiagnostics = new List<Diagnostic>();
return result;
}
public override List<Diagnostic> VisitTemplateDefinition([NotNull] LGFileParser.TemplateDefinitionContext context)
{
var result = new List<Diagnostic>();
var templateNameLine = context.templateNameLine();
var errorTemplateName = templateNameLine.errorTemplateName();
if (errorTemplateName != null)
{
result.Add(BuildLGDiagnostic(TemplateErrors.InvalidTemplateName, context: errorTemplateName, includeTemplateNameInfo: false));
}
else
{
var templateName = context.templateNameLine().templateName().GetText();
if (visitedTemplateNames.Contains(templateName))
// checker duplicated in different files
foreach (var reference in templates.References)
{
result.Add(BuildLGDiagnostic(TemplateErrors.DuplicatedTemplateInSameTemplate(templateName), context: templateNameLine));
}
else
{
visitedTemplateNames.Add(templateName);
foreach (var reference in templates.References)
var sameTemplates = reference.Where(u => u.Name == template.Name);
foreach (var sameTemplate in sameTemplates)
{
var sameTemplates = reference.Where(u => u.Name == templateName);
foreach (var sameTemplate in sameTemplates)
{
result.Add(BuildLGDiagnostic(TemplateErrors.DuplicatedTemplateInDiffTemplate(sameTemplate.Name, sameTemplate.Source), context: templateNameLine));
}
}
if (result.Count > 0)
{
return result;
}
else
{
if (context.templateBody() == null)
{
result.Add(BuildLGDiagnostic(TemplateErrors.NoTemplateBody(templateName), DiagnosticSeverity.Warning, context.templateNameLine()));
}
else
{
result.AddRange(Visit(context.templateBody()));
}
var startLine = template.SourceRange.Range.Start.Line;
var range = new Range(startLine, 0, startLine, template.Name.Length + 1);
var diagnostic = new Diagnostic(range, TemplateErrors.DuplicatedTemplateInDiffTemplate(sameTemplate.Name, sameTemplate.SourceRange.Source), source: templates.Id);
templateDiagnostics.Add(diagnostic);
}
}
if (templateDiagnostics.Count == 0 && template.TemplateBodyParseTree != null)
{
templateDiagnostics.AddRange(Visit(template.TemplateBodyParseTree));
}
result.AddRange(templateDiagnostics);
}
return result;
}
public override List<Diagnostic> VisitNormalTemplateBody([NotNull] LGFileParser.NormalTemplateBodyContext context)
public override List<Diagnostic> VisitNormalTemplateBody([NotNull] LGTemplateParser.NormalTemplateBodyContext context)
{
var result = new List<Diagnostic>();
@ -144,7 +111,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override List<Diagnostic> VisitStructuredTemplateBody([NotNull] LGFileParser.StructuredTemplateBodyContext context)
public override List<Diagnostic> VisitStructuredTemplateBody([NotNull] LGTemplateParser.StructuredTemplateBodyContext context)
{
var result = new List<Diagnostic>();
@ -202,7 +169,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override List<Diagnostic> VisitIfElseBody([NotNull] LGFileParser.IfElseBodyContext context)
public override List<Diagnostic> VisitIfElseBody([NotNull] LGTemplateParser.IfElseBodyContext context)
{
var result = new List<Diagnostic>();
@ -277,7 +244,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override List<Diagnostic> VisitSwitchCaseBody([NotNull] LGFileParser.SwitchCaseBodyContext context)
public override List<Diagnostic> VisitSwitchCaseBody([NotNull] LGTemplateParser.SwitchCaseBodyContext context)
{
var result = new List<Diagnostic>();
var switchCaseRules = context.switchCaseTemplateBody().switchCaseRule();
@ -364,7 +331,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
public override List<Diagnostic> VisitNormalTemplateString([NotNull] LGFileParser.NormalTemplateStringContext context)
public override List<Diagnostic> VisitNormalTemplateString([NotNull] LGTemplateParser.NormalTemplateStringContext context)
{
var prefixErrorMsg = context.GetPrefixErrorMessage();
var result = new List<Diagnostic>();
@ -413,23 +380,14 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return result;
}
/// <summary>
/// Build LG diagnostic with ANTLR tree node context.
/// </summary>
/// <param name="message">error/warning message. <see cref="Diagnostic.Message"/>.</param>
/// <param name="severity">diagnostic Severity <see cref="DiagnosticSeverity"/> to get more info.</param>
/// <param name="context">the parsed tree node context of the diagnostic.</param>
/// <returns>new Diagnostic object.</returns>
private Diagnostic BuildLGDiagnostic(
string message,
DiagnosticSeverity severity = DiagnosticSeverity.Error,
ParserRuleContext context = null,
bool includeTemplateNameInfo = true)
ParserRuleContext context = null)
{
message = visitedTemplateNames.Count > 0 && includeTemplateNameInfo ? $"[{visitedTemplateNames.LastOrDefault()}]" + message : message;
var startPosition = context == null ? new Position(0, 0) : new Position(context.Start.Line, context.Start.Column);
var stopPosition = context == null ? new Position(0, 0) : new Position(context.Stop.Line, context.Stop.Column + context.Stop.Text.Length);
var range = new Range(startPosition, stopPosition);
var lineOffset = this.currentTemplate != null ? this.currentTemplate.SourceRange.Range.Start.Line : 0;
message = this.currentTemplate != null ? $"[{this.currentTemplate.Name}]" + message : message;
var range = context == null ? new Range(1 + lineOffset, 0, 1 + lineOffset, 0) : context.ConvertToRange(lineOffset);
return new Diagnostic(range, message, severity, templates.Id);
}
}

Просмотреть файл

@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Microsoft.Bot.Builder.LanguageGeneration
{
@ -20,34 +17,45 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="Template"/> class.
/// </summary>
/// <param name="parseTree">The parse tree of this template.</param>
/// <param name="lgfileContent">lg file content.</param>
/// <param name="source">Source of this template.</param>
internal Template(LGFileParser.TemplateDefinitionContext parseTree, string lgfileContent, string source = "")
/// <param name="templateName">Template name without parameters.</param>
/// <param name="parameters">Parameter list.</param>
/// <param name="templateBody">Template content.</param>
/// <param name="sourceRange">Source range of template.</param>
internal Template(
string templateName,
List<string> parameters,
string templateBody,
SourceRange sourceRange)
{
ParseTree = parseTree;
Source = source;
Name = ExtractName();
Parameters = ExtractParameters();
Body = ExtractBody(lgfileContent);
this.Name = templateName ?? string.Empty;
this.Parameters = parameters ?? new List<string>();
this.Body = templateBody ?? string.Empty;
this.SourceRange = sourceRange;
}
/// <summary>
/// Gets name of the template, what's followed by '#' in a LG file.
/// Gets or sets source range.
/// </summary>
/// <value>
/// Name of the template, what's followed by '#' in a LG file.
/// Start line of the template in LG file.
/// </value>
public string Name { get; }
public SourceRange SourceRange { get; set; }
/// <summary>
/// Gets parameter list of this template.
/// Gets or sets name of the template, which follows '#' in a LG file.
/// </summary>
/// <value>
/// Name of the template, which follows '#' in a LG file.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets parameter list of this template.
/// </summary>
/// <value>
/// Parameter list of this template.
/// </value>
public List<string> Parameters { get; }
public List<string> Parameters { get; set; }
/// <summary>
/// Gets or sets text format of Body of this template. All content except Name and Parameters.
@ -58,102 +66,13 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
public string Body { get; set; }
/// <summary>
/// Gets source of this template, source file path if it's from a certain file.
/// </summary>
/// <value>
/// Source of this template, source file path if it's from a certain file.
/// </value>
public string Source { get; }
/// <summary>
/// Gets the parse tree of this template.
/// Gets or sets the parse tree of this template.
/// </summary>
/// <value>
/// The parse tree of this template.
/// </value>
public LGFileParser.TemplateDefinitionContext ParseTree { get; }
public LGTemplateParser.BodyContext TemplateBodyParseTree { get; set; }
public override string ToString() => $"[{Name}({string.Join(", ", Parameters)})]\"{Body}\"";
/// <summary>
/// Get the startLine and stopLine of template.
/// </summary>
/// <returns>template content range.</returns>
public (int startLine, int stopLine) GetTemplateRange()
{
var startLine = ParseTree.Start.Line - 1;
var stopLine = ParseTree.Stop.Line - 1;
if (ParseTree?.Parent?.Parent is LGFileParser.FileContext fileContext)
{
var templateDefinitions = fileContext
.paragraph()
.Select(u => u.templateDefinition())
.Where(u => u != null)
.ToList();
var currentIndex = -1;
for (var i = 0; i < templateDefinitions.Count; i++)
{
if (templateDefinitions[i] == ParseTree)
{
currentIndex = i;
break;
}
}
if (currentIndex >= 0 && currentIndex < templateDefinitions.Count - 1)
{
// in the middle of templates
stopLine = templateDefinitions[currentIndex + 1].Start.Line - 2;
}
else
{
// last item
stopLine = fileContext.Stop.Line - 1;
}
}
if (stopLine <= startLine)
{
stopLine = startLine;
}
return (startLine, stopLine);
}
private string ExtractBody(string lgfileContent)
{
var (startLine, stopLine) = GetTemplateRange();
return startLine >= stopLine ? string.Empty : GetRangeContent(lgfileContent, startLine + 1, stopLine);
}
private string ExtractName()
{
var name = ParseTree.templateNameLine().templateName()?.GetText();
return name ?? string.Empty;
}
private List<string> ExtractParameters()
{
var parameters = ParseTree.templateNameLine().parameters();
if (parameters != null)
{
return parameters.IDENTIFIER().Select(param => param.GetText()).ToList();
}
return new List<string>();
}
private string GetRangeContent(string originString, int startLine, int stopLine)
{
var originList = originString.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
if (startLine < 0 || startLine > stopLine || stopLine >= originList.Length)
{
throw new Exception("index out of range.");
}
var destList = originList.Skip(startLine).Take(stopLine - startLine + 1);
return string.Join("\r\n", destList);
}
}
}

Просмотреть файл

@ -62,7 +62,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
public const string LoopDetected = "Loop detected:";
public const string SyntaxError = "Unexpected content. Expecting either a comment or a template definition or an import statement.";
public const string SyntaxError = "Unexpected content. Expecting a comment, template definition, import statement or option definition.";
public const string InvalidMemory = "Scope is not a LG customized memory.";
@ -82,8 +82,6 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
public static string ArgumentMismatch(string templateName, int expectedCount, int actualCount) => $"arguments mismatch for template '{templateName}'. Expecting '{expectedCount}' arguments, actual '{actualCount}'.";
public static string ErrorTemplateNameformat(string templateName) => $"'{templateName}' cannot be used as a template name. Template names must be avalid string.";
public static string TemplateExist(string templateName) => $"template '{templateName}' already exists.";
public static string ExpressionParseError(string exp) => $"Error occurred when parsing expression '{exp}'.";

Просмотреть файл

@ -13,63 +13,43 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Initializes a new instance of the <see cref="TemplateImport"/> class.
/// </summary>
/// <param name="parseTree">The parse tree of this template.</param>
/// <param name="source">Source of this import.</param>
internal TemplateImport(LGFileParser.ImportDefinitionContext parseTree, string source = "")
/// <param name="description">Import description, which is in [].</param>
/// <param name="id">Import id, which is a path, in ().</param>
/// <param name="sourceRange">Source range of template.</param>
internal TemplateImport(string description, string id, SourceRange sourceRange)
{
ParseTree = parseTree;
Source = source;
Description = ExtractDescription(parseTree);
Id = ExtractId(parseTree);
this.SourceRange = sourceRange;
this.Description = description;
this.Id = id;
}
/// <summary>
/// Gets description of the import, what's included by '[]' in a lg file.
/// Gets or sets description of the import, included by '[]' in a lg file.
/// </summary>
/// <value>
/// Description of the import, what's included by '[]' in a lg file.
/// Description of the import, included by '[]' in a lg file.
/// </value>
public string Description { get; }
public string Description { get; set; }
/// <summary>
/// Gets id of this import, what's included by '()' in a lg file.
/// Gets or sets id of this import, included by '()' in a lg file.
/// </summary>
/// <value>
/// Id of this import.
/// </value>
public string Id { get; }
public string Id { get; set; }
/// <summary>
/// Gets origin root source of the import.
/// Gets or sets original root source of the import.
/// </summary>
/// <value>
/// origin root source of the import.
/// Original root source of the import.
/// </value>
public string Source { get; }
public SourceRange SourceRange { get; set; }
/// <summary>
/// Gets the parse tree of this lg file.
/// </summary>
/// <value>
/// The parse tree of this lg file.
/// </value>
public LGFileParser.ImportDefinitionContext ParseTree { get; }
private string ExtractDescription(LGFileParser.ImportDefinitionContext parseTree)
public override string ToString()
{
// content: [xxx](yyy)
var content = parseTree.GetText();
var closeSquareBracketIndex = content.IndexOf(']');
return content.Substring(1, closeSquareBracketIndex - 1);
}
private string ExtractId(LGFileParser.ImportDefinitionContext parseTree)
{
// content: [xxx](yyy)
var content = parseTree.GetText();
var lastOpenBracketIndex = content.LastIndexOf('(');
return content.Substring(lastOpenBracketIndex + 1, content.Length - lastOpenBracketIndex - 2);
return $"[{Description}]({Id})";
}
}
}

Просмотреть файл

@ -77,7 +77,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets expression parser.
/// </summary>
/// <value>
/// expression parser.
/// Expression parser.
/// </value>
public ExpressionParser ExpressionParser { get; set; }
@ -85,7 +85,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets import elements that this LG file contains directly.
/// </summary>
/// <value>
/// import elements that this LG file contains directly.
/// Import elements that this LG file contains directly.
/// </value>
public IList<TemplateImport> Imports { get; set; }
@ -96,7 +96,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// so, reference count may >= imports count.
/// </summary>
/// <value>
/// all references that this LG file has from <see cref="Imports"/>.
/// All references that this LG file has from <see cref="Imports"/>.
/// </value>
public IList<Templates> References { get; set; }
@ -104,7 +104,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets diagnostics.
/// </summary>
/// <value>
/// diagnostics.
/// Diagnostics.
/// </value>
public IList<Diagnostic> Diagnostics { get; set; }
@ -120,7 +120,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Gets or sets id of this LG file.
/// </summary>
/// <value>
/// id of this lg source. For file, is full path.
/// Id of this lg source. For file, is full path.
/// </value>
public string Id { get; set; }
@ -147,7 +147,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Parser to turn lg content into a <see cref="LanguageGeneration.Templates"/>.
/// </summary>
/// <param name="filePath"> absolut path of a LG file.</param>
/// <param name="filePath">Absolute path of a LG file.</param>
/// <param name="importResolver">resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">expressionEngine Expression engine for evaluating expressions.</param>
/// <returns>new <see cref="LanguageGeneration.Templates"/> entity.</returns>
@ -160,10 +160,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Parser to turn lg content into a <see cref="LanguageGeneration.Templates"/>.
/// </summary>
/// <param name="content">Text content contains lg templates.</param>
/// <param name="id">id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">expressionEngine parser engine for parsing expressions.</param>
/// <returns>new <see cref="LanguageGeneration.Templates"/> entity.</returns>
/// <param name="id">Id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">Resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">Expression parser engine for parsing expressions.</param>
/// <returns>new <see cref="Templates"/> entity.</returns>
public static Templates ParseText(
string content,
string id = "",
@ -187,8 +187,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Use to evaluate an inline template str.
/// </summary>
/// <param name="text">inline string which will be evaluated.</param>
/// <param name="scope">scope object or JToken.</param>
/// <param name="text">Inline string which will be evaluated.</param>
/// <param name="scope">Scope object or JToken.</param>
/// <returns>Evaluate result.</returns>
public object EvaluateText(string text, object scope = null)
{
@ -229,10 +229,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// (experimental)
/// Analyzer a template to get the static analyzer results including variables and template references.
/// Analyze a template to get the static analyzer results including variables and template references.
/// </summary>
/// <param name="templateName">Template name to be evaluated.</param>
/// <returns>analyzer result.</returns>
/// <returns>Analyzer result.</returns>
public AnalyzerResult AnalyzeTemplate(string templateName)
{
CheckErrors();
@ -241,13 +241,13 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
}
/// <summary>
/// update an exist template.
/// Update an existing template.
/// </summary>
/// <param name="templateName">origin template name. the only id of a template.</param>
/// <param name="newTemplateName">new template Name.</param>
/// <param name="parameters">new params.</param>
/// <param name="templateBody">new template body.</param>
/// <returns>updated LG file.</returns>
/// <param name="templateName">Original template name. The only id of a template.</param>
/// <param name="newTemplateName">New template Name.</param>
/// <param name="parameters">New params.</param>
/// <param name="templateBody">New template body.</param>
/// <returns>Updated LG file.</returns>
public Templates UpdateTemplate(string templateName, string newTemplateName, List<string> parameters, string templateBody)
{
var template = this.FirstOrDefault(u => u.Name == templateName);
@ -256,7 +256,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
var templateNameLine = BuildTemplateNameLine(newTemplateName, parameters);
var newTemplateBody = ConvertTemplateBody(templateBody);
var content = $"{templateNameLine}{newLine}{newTemplateBody}";
var (startLine, stopLine) = template.GetTemplateRange();
var startLine = template.SourceRange.Range.Start.Line - 1;
var stopLine = template.SourceRange.Range.End.Line - 1;
var newContent = ReplaceRangeContent(Content, startLine, stopLine, content);
Initialize(ParseText(newContent, Id, ImportResolver));
@ -268,10 +270,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Add a new template and return LG File.
/// </summary>
/// <param name="templateName">new template name.</param>
/// <param name="parameters">new params.</param>
/// <param name="templateBody">new template body.</param>
/// <returns>updated LG file.</returns>
/// <param name="templateName">New template name.</param>
/// <param name="parameters">New params.</param>
/// <param name="templateBody">New template body.</param>
/// <returns>Updated LG file.</returns>
public Templates AddTemplate(string templateName, List<string> parameters, string templateBody)
{
var template = this.FirstOrDefault(u => u.Name == templateName);
@ -291,15 +293,15 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Delete an exist template.
/// </summary>
/// <param name="templateName">which template should delete.</param>
/// <returns>updated LG file.</returns>
/// <param name="templateName">Which template should delete.</param>
/// <returns>Updated LG file.</returns>
public Templates DeleteTemplate(string templateName)
{
var template = this.FirstOrDefault(u => u.Name == templateName);
if (template != null)
{
var (startLine, stopLine) = template.GetTemplateRange();
var startLine = template.SourceRange.Range.Start.Line - 1;
var stopLine = template.SourceRange.Range.End.Line - 1;
var newContent = ReplaceRangeContent(Content, startLine, stopLine, null);
Initialize(ParseText(newContent, Id, ImportResolver));
}
@ -363,7 +365,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
}
/// <summary>
/// use an existing LG file to override current object.
/// Use an existing LG file to override current object.
/// </summary>
/// <param name="templates">Existing LG file.</param>
private void Initialize(Templates templates)

Просмотреть файл

@ -8,6 +8,8 @@ using System.Linq;
using System.Text.RegularExpressions;
using AdaptiveExpressions;
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;
namespace Microsoft.Bot.Builder.LanguageGeneration
{
@ -27,14 +29,19 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// option regex.
/// </summary>
private static readonly Regex OptionRegex = new Regex(@"^> *!#(.*)$");
public static readonly Regex OptionRegex = new Regex(@">\s*!#(.*)");
/// <summary>
/// Import regex.
/// </summary>
public static readonly Regex ImportRegex = new Regex(@"\[([^]]*)\]\(([^)]*)\)");
/// <summary>
/// Parser to turn lg content into a <see cref="Templates"/>.
/// </summary>
/// <param name="filePath"> absolut path of a LG file.</param>
/// <param name="importResolver">resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">expressionEngine Expression engine for evaluating expressions.</param>
/// <param name="filePath">Absolut path of a LG file.</param>
/// <param name="importResolver">Resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">Expression parser for parsing expressions.</param>
/// <returns>new <see cref="Templates"/> entity.</returns>
public static Templates ParseFile(
string filePath,
@ -51,10 +58,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Parser to turn lg content into a <see cref="Templates"/>.
/// </summary>
/// <param name="content">Text content contains lg templates.</param>
/// <param name="id">id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">expressionEngine parser engine for parsing expressions.</param>
/// <returns>new <see cref="Templates"/> entity.</returns>
/// <param name="id">Id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">Resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">Expression parser for parsing expressions.</param>
/// <returns>New <see cref="Templates"/> entity.</returns>
public static Templates ParseText(
string content,
string id = "",
@ -68,8 +75,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Parser to turn lg content into a <see cref="Templates"/> based on the original LGFile.
/// </summary>
/// <param name="content">Text content contains lg templates.</param>
/// <param name="lg">original LGFile.</param>
/// <returns>new <see cref="Templates"/> entity.</returns>
/// <param name="lg">Original LGFile.</param>
/// <returns>New <see cref="Templates"/> entity.</returns>
public static Templates ParseTextWithRef(string content, Templates lg)
{
if (lg == null)
@ -79,33 +86,20 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
var id = "inline content";
var newLG = new Templates(content: content, id: id, importResolver: lg.ImportResolver, options: lg.Options);
var diagnostics = new List<Diagnostic>();
try
{
var (templates, imports, invalidTemplateErrors, options) = AntlrParse(content, id);
newLG.AddRange(templates);
newLG.Imports = imports;
newLG.Options = options;
diagnostics.AddRange(invalidTemplateErrors);
newLG = new TemplatesTransformer(newLG).Transform(AntlrParseTemplates(content, id));
newLG.References = GetReferences(newLG)
.Union(lg.References)
.Union(new List<Templates> { lg })
.ToList();
var semanticErrors = new StaticChecker(newLG).Check();
diagnostics.AddRange(semanticErrors);
new StaticChecker(newLG).Check().ForEach(u => newLG.Diagnostics.Add(u));
}
catch (TemplateException ex)
{
diagnostics.AddRange(ex.Diagnostics);
ex.Diagnostics.ToList().ForEach(u => newLG.Diagnostics.Add(u));
}
catch (Exception err)
{
diagnostics.Add(BuildDiagnostic(err.Message, source: id));
}
newLG.Diagnostics = diagnostics;
return newLG;
}
@ -114,10 +108,10 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// Parser to turn lg content into a <see cref="Templates"/>.
/// </summary>
/// <param name="content">Text content contains lg templates.</param>
/// <param name="id">id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">expressionEngine parser engine for parsing expressions.</param>
/// <param name="cachedTemplates">give the file path and templates to avoid parsing and to improve performance.</param>
/// <param name="id">Id is the identifier of content. If importResolver is null, id must be a full path string. </param>
/// <param name="importResolver">Resolver to resolve LG import id to template text.</param>
/// <param name="expressionParser">Expression parser for parsing expressions.</param>
/// <param name="cachedTemplates">Give the file path and templates to avoid parsing and to improve performance.</param>
/// <returns>new <see cref="Templates"/> entity.</returns>
private static Templates InnerParseText(
string content,
@ -135,29 +129,16 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
importResolver = importResolver ?? DefaultFileResolver;
var lg = new Templates(content: content, id: id, importResolver: importResolver, expressionParser: expressionParser);
var diagnostics = new List<Diagnostic>();
try
{
var (templates, imports, invalidTemplateErrors, options) = AntlrParse(content, id);
lg.AddRange(templates);
lg.Imports = imports;
lg.Options = options;
diagnostics.AddRange(invalidTemplateErrors);
lg = new TemplatesTransformer(lg).Transform(AntlrParseTemplates(content, id));
lg.References = GetReferences(lg, cachedTemplates);
var semanticErrors = new StaticChecker(lg).Check();
diagnostics.AddRange(semanticErrors);
new StaticChecker(lg).Check().ForEach(u => lg.Diagnostics.Add(u));
}
catch (TemplateException ex)
{
diagnostics.AddRange(ex.Diagnostics);
ex.Diagnostics.ToList().ForEach(u => lg.Diagnostics.Add(u));
}
catch (Exception err)
{
diagnostics.Add(BuildDiagnostic(err.Message, source: id));
}
lg.Diagnostics = diagnostics;
return lg;
}
@ -165,9 +146,9 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// <summary>
/// Default import resolver, using relative/absolute file path to access the file content.
/// </summary>
/// <param name="sourceId">default is file path.</param>
/// <param name="resourceId">import path.</param>
/// <returns>target content id.</returns>
/// <param name="sourceId">Default is file path.</param>
/// <param name="resourceId">Import path.</param>
/// <returns>Target content id.</returns>
private static (string content, string id) DefaultFileResolver(string sourceId, string resourceId)
{
// import paths are in resource files which can be executed on multiple OS environments
@ -183,41 +164,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return (File.ReadAllText(importPath), importPath);
}
private static (IList<Template> templates, IList<TemplateImport> imports, IList<Diagnostic> diagnostics, IList<string> options) AntlrParse(string content, string id = "")
{
var fileContext = GetFileContentContext(content, id);
var templates = ExtractLGTemplates(fileContext, content, id);
var imports = ExtractLGImports(fileContext, id);
var options = ExtractLGOptions(fileContext);
var diagnostics = GetInvalidTemplateErrors(fileContext, id);
return (templates, imports, diagnostics, options);
}
private static IList<Diagnostic> GetInvalidTemplateErrors(LGFileParser.FileContext fileContext, string id)
{
var errorTemplates = fileContext == null ? new List<LGFileParser.ErrorTemplateContext>() :
fileContext.paragraph()
.Select(x => x.errorTemplate())
.Where(x => x != null);
return errorTemplates.Select(u => BuildDiagnostic("error context.", u, id)).ToList();
}
private static Diagnostic BuildDiagnostic(string errorMessage, ParserRuleContext context = null, string source = null)
{
errorMessage = TemplateErrors.StaticFailure + "- " + errorMessage;
var startPosition = context == null ? new Position(0, 0) : new Position(context.Start.Line, context.Start.Column);
var stopPosition = context == null ? new Position(0, 0) : new Position(context.Stop.Line, context.Stop.Column + context.Stop.Text.Length);
return new Diagnostic(new Range(startPosition, stopPosition), errorMessage, source: source);
}
/// <summary>
/// Get parsed tree nodes from text by antlr4 engine.
/// </summary>
/// <param name="text">Original text which will be parsed.</param>
/// <returns>Parsed tree nodes.</returns>
private static LGFileParser.FileContext GetFileContentContext(string text, string id)
private static IParseTree AntlrParseTemplates(string text, string id)
{
if (string.IsNullOrEmpty(text))
{
@ -239,72 +186,6 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
return parser.file();
}
/// <summary>
/// Extract LG templates from the parse tree of a file.
/// </summary>
/// <param name="file">LG file context from ANTLR parser.</param>
/// <param name="lgfileContent">LG file content.</param>
/// <param name="source">text source.</param>
/// <returns>LG template list.</returns>
private static IList<Template> ExtractLGTemplates(LGFileParser.FileContext file, string lgfileContent, string source = "")
{
return file == null ? new List<Template>() :
file.paragraph()
.Select(x => x.templateDefinition())
.Where(x => x != null)
.Select(t => new Template(t, lgfileContent, source))
.ToList();
}
/// <summary>
/// Extract LG options from the parse tree of a file.
/// </summary>
/// <param name="file">LG file context from ANTLR parser.</param>
/// <returns>Option list.</returns>
private static IList<string> ExtractLGOptions(LGFileParser.FileContext file)
{
return file == null ? new List<string>() :
file.paragraph()
.Select(x => x.optionsDefinition())
.Where(x => x != null)
.Select(t => ExtractOption(t.GetText()))
.Where(t => !string.IsNullOrEmpty(t))
.ToList();
}
private static string ExtractOption(string originalText)
{
var result = string.Empty;
if (string.IsNullOrWhiteSpace(originalText))
{
return result;
}
var matchResult = OptionRegex.Match(originalText);
if (matchResult.Success && matchResult.Groups.Count == 2)
{
result = matchResult.Groups[1].Value?.Trim();
}
return result;
}
/// <summary>
/// Extract LG imports from a file parse tree.
/// </summary>
/// <param name="file">LG file context from ANTLR parser.</param>
/// <param name="source">text source.</param>
/// <returns>lg template list.</returns>
private static IList<TemplateImport> ExtractLGImports(LGFileParser.FileContext file, string source = "")
{
return file == null ? new List<TemplateImport>() :
file.paragraph()
.Select(x => x.importDefinition())
.Where(x => x != null)
.Select(t => new TemplateImport(t, source))
.ToList();
}
private static IList<Templates> GetReferences(Templates file, Dictionary<string, Templates> cachedTemplates = null)
{
var resourcesFound = new HashSet<Templates>();
@ -316,38 +197,248 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
private static void ResolveImportResources(Templates start, HashSet<Templates> resourcesFound, Dictionary<string, Templates> cachedTemplates)
{
var resourceIds = start.Imports.Select(lg => lg.Id);
resourcesFound.Add(start);
foreach (var id in resourceIds)
foreach (var import in start.Imports)
{
string content;
string path;
try
{
var (content, path) = start.ImportResolver(start.Id, id);
if (resourcesFound.All(u => u.Id != path))
{
Templates childResource;
if (cachedTemplates.ContainsKey(path))
{
childResource = cachedTemplates[path];
}
else
{
childResource = InnerParseText(content, path, start.ImportResolver, start.ExpressionParser, cachedTemplates);
cachedTemplates.Add(path, childResource);
}
(content, path) = start.ImportResolver(start.Id, import.Id);
}
catch (Exception e)
{
var diagnostic = new Diagnostic(import.SourceRange.ParseTree.ConvertToRange(), e.Message, DiagnosticSeverity.Error, start.Id);
throw new TemplateException(e.Message, new List<Diagnostic>() { diagnostic });
}
ResolveImportResources(childResource, resourcesFound, cachedTemplates);
if (resourcesFound.All(u => u.Id != path))
{
Templates childResource;
if (cachedTemplates.ContainsKey(path))
{
childResource = cachedTemplates[path];
}
else
{
childResource = InnerParseText(content, path, start.ImportResolver, start.ExpressionParser, cachedTemplates);
cachedTemplates.Add(path, childResource);
}
ResolveImportResources(childResource, resourcesFound, cachedTemplates);
}
}
}
private class TemplatesTransformer : LGFileParserBaseVisitor<object>
{
private static readonly Regex IdentifierRegex = new Regex(@"^[0-9a-zA-Z_]+$");
private readonly Templates templates;
public TemplatesTransformer(Templates templates)
{
this.templates = templates;
}
public Templates Transform(IParseTree parseTree)
{
Visit(parseTree);
return this.templates;
}
public override object VisitErrorDefinition([NotNull] LGFileParser.ErrorDefinitionContext context)
{
var lineContent = context.INVALID_LINE().GetText();
if (!string.IsNullOrWhiteSpace(lineContent))
{
this.templates.Diagnostics.Add(BuildTemplatesDiagnostic(TemplateErrors.SyntaxError, context));
}
return null;
}
public override object VisitImportDefinition([NotNull] LGFileParser.ImportDefinitionContext context)
{
var importStr = context.IMPORT().GetText();
var matchResult = ImportRegex.Match(importStr);
if (matchResult.Success && matchResult.Groups.Count == 3)
{
var description = matchResult.Groups[1].Value?.Trim();
var id = matchResult.Groups[2].Value?.Trim();
var sourceRange = new SourceRange(context, this.templates.Id);
var import = new TemplateImport(description, id, sourceRange);
this.templates.Imports.Add(import);
}
return null;
}
public override object VisitOptionDefinition([NotNull] LGFileParser.OptionDefinitionContext context)
{
var originalText = context.OPTION().GetText();
var result = string.Empty;
if (!string.IsNullOrWhiteSpace(originalText))
{
var matchResult = OptionRegex.Match(originalText);
if (matchResult.Success && matchResult.Groups.Count == 2)
{
result = matchResult.Groups[1].Value?.Trim();
}
}
catch (TemplateException err)
if (!string.IsNullOrWhiteSpace(result))
{
throw err;
this.templates.Options.Add(result);
}
catch (Exception err)
return null;
}
public override object VisitTemplateDefinition([NotNull] LGFileParser.TemplateDefinitionContext context)
{
var startLine = context.Start.Line;
var stopLine = context.Stop.Line;
var templateNameLine = context.templateNameLine().TEMPLATE_NAME_LINE().GetText();
var (templateName, parameters) = ExtractTemplateNameLine(templateNameLine);
if (this.templates.Any(u => u.Name == templateName))
{
throw new TemplateException(err.Message, new List<Diagnostic> { BuildDiagnostic(err.Message, source: start.Id) });
var diagnostic = BuildTemplatesDiagnostic(TemplateErrors.DuplicatedTemplateInSameTemplate(templateName), context.templateNameLine());
this.templates.Diagnostics.Add(diagnostic);
}
else
{
var templateBody = context.templateBody().GetText();
var file = context.Parent.Parent as LGFileParser.FileContext;
var isLastTemplate = file.paragraph().Select(u => u.templateDefinition()).Where(u => u != null).Last() == context;
if (!isLastTemplate)
{
templateBody = RemoveTrailingNewline(templateBody);
}
var sourceRange = new SourceRange(context, this.templates.Id);
var template = new Template(templateName, parameters, templateBody, sourceRange);
CheckTemplateName(templateName, context.templateNameLine());
CheckTemplateParameters(parameters, context.templateNameLine());
template.TemplateBodyParseTree = CheckTemplateBody(templateName, templateBody, context.templateBody(), startLine);
this.templates.Add(template);
}
return null;
}
private LGTemplateParser.BodyContext CheckTemplateBody(string templateName, string templateBody, LGFileParser.TemplateBodyContext context, int startLine)
{
if (string.IsNullOrWhiteSpace(templateBody))
{
var diagnostic = BuildTemplatesDiagnostic(TemplateErrors.NoTemplateBody(templateName), context, DiagnosticSeverity.Warning);
this.templates.Diagnostics.Add(diagnostic);
}
else
{
try
{
return AntlrParseTemplate(templateBody, startLine);
}
catch (TemplateException e)
{
e.Diagnostics.ToList().ForEach(u => this.templates.Diagnostics.Add(u));
}
}
return null;
}
private void CheckTemplateParameters(List<string> parameters, LGFileParser.TemplateNameLineContext context)
{
foreach (var parameter in parameters)
{
if (!IdentifierRegex.IsMatch(parameter))
{
var diagnostic = BuildTemplatesDiagnostic(TemplateErrors.InvalidTemplateName, context);
this.templates.Diagnostics.Add(diagnostic);
}
}
}
private void CheckTemplateName(string templateName, ParserRuleContext context)
{
var functionNameSplitDot = templateName.Split('.');
foreach (var id in functionNameSplitDot)
{
if (!IdentifierRegex.IsMatch(id))
{
var diagnostic = BuildTemplatesDiagnostic(TemplateErrors.InvalidTemplateName, context);
this.templates.Diagnostics.Add(diagnostic);
}
}
}
private (string templateName, List<string> parameters) ExtractTemplateNameLine(string templateNameLine)
{
var hashIndex = templateNameLine.IndexOf('#');
templateNameLine = templateNameLine.Substring(hashIndex + 1).Trim();
var templateName = templateNameLine;
var parameters = new List<string>();
var leftBracketIndex = templateNameLine.IndexOf("(");
if (leftBracketIndex >= 0 && templateNameLine.EndsWith(")"))
{
templateName = templateNameLine.Substring(0, leftBracketIndex).Trim();
var paramString = templateNameLine.Substring(leftBracketIndex + 1, templateNameLine.Length - leftBracketIndex - 2);
if (!string.IsNullOrWhiteSpace(paramString))
{
parameters = paramString.Split(',').Select(u => u.Trim()).ToList();
}
}
return (templateName, parameters);
}
private string RemoveTrailingNewline(string line)
{
// remove the end newline of middle template.
var result = line;
if (result.EndsWith("\n", StringComparison.Ordinal))
{
result = result.Substring(0, result.Length - 1);
if (result.EndsWith("\r", StringComparison.Ordinal))
{
result = result.Substring(0, result.Length - 1);
}
}
return result;
}
private LGTemplateParser.BodyContext AntlrParseTemplate(string templateBody, int lineOffset)
{
var input = new AntlrInputStream(templateBody);
var lexer = new LGTemplateLexer(input);
lexer.RemoveErrorListeners();
var tokens = new CommonTokenStream(lexer);
var parser = new LGTemplateParser(tokens);
parser.RemoveErrorListeners();
var listener = new ErrorListener(this.templates.Id, lineOffset);
parser.AddErrorListener(listener);
parser.BuildParseTree = true;
return parser.context().body();
}
private Diagnostic BuildTemplatesDiagnostic(string errorMessage, ParserRuleContext context, DiagnosticSeverity severity = DiagnosticSeverity.Error)
{
return new Diagnostic(context.ConvertToRange(), errorMessage, severity, this.templates.Id);
}
}
}

Просмотреть файл

@ -1,4 +1,6 @@
[sandwichTest-BreadEntity.lg](sandwichTest-BreadEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskBread
- ${askEnum('Bread')}
@ -10,6 +12,3 @@
# Bread(val)
- ${BreadEntity(val)}
[sandwichTest-BreadEntity.lg](sandwichTest-BreadEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)
# BreadEntity(value)
- SWITCH: ${value}
@ -16,5 +17,3 @@
# chooseBreadEntity
- ${chooseEnumEntity('Bread')}
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwichTest-CheeseEntity.lg](sandwichTest-CheeseEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskCheese
- ${askEnum('Cheese')}
@ -10,6 +12,3 @@
# Cheese(val)
- ${CheeseEntity(val)}
[sandwichTest-CheeseEntity.lg](sandwichTest-CheeseEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)
# CheeseEntity(value)
- SWITCH: ${value}
@ -24,5 +25,3 @@
# chooseCheeseEntity
- ${chooseEnumEntity('Cheese')}
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskLength
- ${askString('Length')}
@ -6,5 +7,3 @@
# LengthName
- length
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwichTest-MeatEntity.lg](sandwichTest-MeatEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskMeat
- ${askEnum('Meat')}
@ -10,6 +12,3 @@
# Meat(val)
- ${MeatEntity(val)}
[sandwichTest-MeatEntity.lg](sandwichTest-MeatEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)
# MeatEntity(value)
- SWITCH: ${value}
@ -24,5 +25,3 @@
# chooseMeatEntity
- ${chooseEnumEntity('Meat')}
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Choose.lg](sandwichTest-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwichTest-NameEntity.lg](sandwichTest-NameEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskName
- ${askString('Name')}
@ -10,6 +12,3 @@
# Name(val)
- ${NameEntity(val)}
[sandwichTest-NameEntity.lg](sandwichTest-NameEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskPrice
- ${askString('Price')}
@ -6,5 +7,3 @@
# PriceName
- price
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwichTest-QuantityEntity.lg](sandwichTest-QuantityEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)
# AskQuantity
- ${askNumber('Quantity')}
@ -10,6 +12,3 @@
# Quantity(val)
- ${QuantityEntity(val)}
[sandwichTest-QuantityEntity.lg](sandwichTest-QuantityEntity.lg)
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Missing.lg](sandwichTest-library-Missing.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-ReadForm.lg](sandwichTest-library-ReadForm.lg)
# numberValidation(property, number)
- IF: ${less(number, dialogClass.schema.properties[property].minimum)}
- ${number} is less than the minimum value ${dialogClass.schema.properties[property].minimum}.
@ -35,5 +38,3 @@
# unexpectedPropertyChange(property, val, oldVal)
- ${name(property)} is changed from ${value(property, oldVal)} to ${value(property, val)}.
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-ReadForm.lg](sandwichTest-library-ReadForm.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Help.lg](sandwichTest-library-Help.lg)
# chooseEnumEntity(property)
- ```
${askHelp()}
@ -9,5 +12,3 @@ Please choose a value for ${name(property)} from [${join(foreach(turn.dialogEven
# choosePropertyEntity(property)
- "${property.entity.text}" as ${name(property.property)}
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Help.lg](sandwichTest-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
# askHelp
- IF: ${$retries > 0 && $lastIntent != 'Help'}
- ${join(foreach($expectedProperties, expected, help1(expected)), '\n')}
@ -25,4 +27,3 @@ You can find out about a specific property by doing 'help <property>'.
- Enter any string for ${name(property)}
- ELSE:
- No help available.
[sandwichTest-library.lg](sandwichTest-library.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Help.lg](sandwichTest-library-Help.lg)
# askEnum(property)
- ```
${askHelp()}
@ -25,5 +28,3 @@ Enter a value for ${name(property)}
${askHelp()}
Enter a value for ${name(property)}: true or false
```
[sandwichTest-library.lg](sandwichTest-library.lg)
[sandwichTest-library-Help.lg](sandwichTest-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[sandwichTest-library.lg](sandwichTest-library.lg)
# readForm(name)
[Activity
Attachments=${json(formCard(name))}
@ -49,4 +51,3 @@
# subFact(property, subproperty)
- ${dialog[property][subproperty]}
[sandwichTest-library.lg](sandwichTest-library.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[sandwichTest-library-PROPERTYName.lg](sandwichTest-library-PROPERTYName.lg)
> This file contains the generic templates to use for properties of particular types.
# welcome
@ -46,4 +48,3 @@
# notUnderstood
- Sorry, I do not understand ${join(foreach(turn.unrecognizedtext, chunk, concat("'", chunk, "'")), ' or ')}
[sandwichTest-library-PROPERTYName.lg](sandwichTest-library-PROPERTYName.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskAge
- ${askString('Age')}
@ -6,5 +7,3 @@
# AgeName
- age
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskDatetimev2
- ${askString('Datetimev2')}
@ -6,5 +7,3 @@
# Datetimev2Name
- datetimev2
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskDimension
- ${askString('Dimension')}
@ -6,5 +7,3 @@
# DimensionName
- dimension
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-EmailEntity.lg](unittests-EmailEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskEmail
- ${askString('Email')}
@ -10,6 +12,3 @@
# Email(val)
- ${EmailEntity(val)}
[unittests-EmailEntity.lg](unittests-EmailEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-Enum1Entity.lg](unittests-Enum1Entity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskEnum1
- ${askEnum('Enum1')}
@ -10,6 +12,3 @@
# Enum1(val)
- ${Enum1Entity(val)}
[unittests-Enum1Entity.lg](unittests-Enum1Entity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Choose.lg](unittests-library-Choose.lg)
# Enum1Entity(value)
- SWITCH: ${value}
@ -16,5 +17,3 @@
# chooseEnum1Entity
- ${chooseEnumEntity('Enum1')}
[unittests-library.lg](unittests-library.lg)
[unittests-library-Choose.lg](unittests-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-Enum2Entity.lg](unittests-Enum2Entity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskEnum2
- ${askEnum('Enum2')}
@ -10,6 +12,3 @@
# Enum2(val)
- ${Enum2Entity(val)}
[unittests-Enum2Entity.lg](unittests-Enum2Entity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Choose.lg](unittests-library-Choose.lg)
# Enum2Entity(value)
- SWITCH: ${value}
@ -24,5 +25,3 @@
# chooseEnum2Entity
- ${chooseEnumEntity('Enum2')}
[unittests-library.lg](unittests-library.lg)
[unittests-library-Choose.lg](unittests-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskGeographyv2
- ${askString('Geographyv2')}
@ -6,5 +7,3 @@
# Geographyv2Name
- geographyv2
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-KeyphrasepropertyEntity.lg](unittests-KeyphrasepropertyEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskKeyphraseproperty
- ${askString('Keyphraseproperty')}
@ -10,6 +12,3 @@
# Keyphraseproperty(val)
- ${KeyphrasepropertyEntity(val)}
[unittests-KeyphrasepropertyEntity.lg](unittests-KeyphrasepropertyEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskMoney
- ${askString('Money')}
@ -6,5 +7,3 @@
# MoneyName
- money
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskOrdinalv2
- ${askString('Ordinalv2')}
@ -6,5 +7,3 @@
# Ordinalv2Name
- ordinalv2
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-PercentageEntity.lg](unittests-PercentageEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskPercentage
- ${askString('Percentage')}
@ -10,6 +12,3 @@
# Percentage(val)
- ${PercentageEntity(val)}
[unittests-PercentageEntity.lg](unittests-PercentageEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-PersonnameEntity.lg](unittests-PersonnameEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskPersonname
- ${askString('Personname')}
@ -10,6 +12,3 @@
# Personname(val)
- ${PersonnameEntity(val)}
[unittests-PersonnameEntity.lg](unittests-PersonnameEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-PhonenumberEntity.lg](unittests-PhonenumberEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskPhonenumber
- ${askString('Phonenumber')}
@ -10,6 +12,3 @@
# Phonenumber(val)
- ${PhonenumberEntity(val)}
[unittests-PhonenumberEntity.lg](unittests-PhonenumberEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-QuantityEntity.lg](unittests-QuantityEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskQuantity
- ${askNumber('Quantity')}
@ -10,6 +12,3 @@
# Quantity(val)
- ${QuantityEntity(val)}
[unittests-QuantityEntity.lg](unittests-QuantityEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskTemperature
- ${askString('Temperature')}
@ -6,5 +7,3 @@
# TemperatureName
- temperature
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-UrlEntity.lg](unittests-UrlEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskUrl
- ${askString('Url')}
@ -10,6 +12,3 @@
# Url(val)
- ${UrlEntity(val)}
[unittests-UrlEntity.lg](unittests-UrlEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-UtterancepropertyEntity.lg](unittests-UtterancepropertyEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskUtteranceproperty
- ${askString('Utteranceproperty')}
@ -10,6 +12,3 @@
# Utteranceproperty(val)
- ${UtterancepropertyEntity(val)}
[unittests-UtterancepropertyEntity.lg](unittests-UtterancepropertyEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[unittests-ZipcodepatternEntity.lg](unittests-ZipcodepatternEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)
# AskZipcodepattern
- ${askString('Zipcodepattern')}
@ -10,6 +12,3 @@
# Zipcodepattern(val)
- ${ZipcodepatternEntity(val)}
[unittests-ZipcodepatternEntity.lg](unittests-ZipcodepatternEntity.lg)
[unittests-library.lg](unittests-library.lg)
[unittests-library-Missing.lg](unittests-library-Missing.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-ReadForm.lg](unittests-library-ReadForm.lg)
# numberValidation(property, number)
- IF: ${less(number, dialogClass.schema.properties[property].minimum)}
- ${number} is less than the minimum value ${dialogClass.schema.properties[property].minimum}.
@ -35,5 +38,3 @@
# unexpectedPropertyChange(property, val, oldVal)
- ${name(property)} is changed from ${value(property, oldVal)} to ${value(property, val)}.
[unittests-library.lg](unittests-library.lg)
[unittests-library-ReadForm.lg](unittests-library-ReadForm.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Help.lg](unittests-library-Help.lg)
# chooseEnumEntity(property)
- ```
${askHelp()}
@ -9,5 +12,3 @@ Please choose a value for ${name(property)} from [${join(foreach(turn.dialogEven
# choosePropertyEntity(property)
- "${property.entity.text}" as ${name(property.property)}
[unittests-library.lg](unittests-library.lg)
[unittests-library-Help.lg](unittests-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[unittests-library.lg](unittests-library.lg)
# askHelp
- IF: ${$retries > 0 && $lastIntent != 'Help'}
- ${join(foreach($expectedProperties, expected, help1(expected)), '\n')}
@ -25,4 +27,3 @@ You can find out about a specific property by doing 'help <property>'.
- Enter any string for ${name(property)}
- ELSE:
- No help available.
[unittests-library.lg](unittests-library.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[unittests-library.lg](unittests-library.lg)
[unittests-library-Help.lg](unittests-library-Help.lg)
# askEnum(property)
- ```
${askHelp()}
@ -25,5 +28,3 @@ Enter a value for ${name(property)}
${askHelp()}
Enter a value for ${name(property)}: true or false
```
[unittests-library.lg](unittests-library.lg)
[unittests-library-Help.lg](unittests-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[unittests-library.lg](unittests-library.lg)
# readForm(name)
[Activity
Attachments=${json(formCard(name))}
@ -49,4 +51,3 @@
# subFact(property, subproperty)
- ${dialog[property][subproperty]}
[unittests-library.lg](unittests-library.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[unittests-library-PROPERTYName.lg](unittests-library-PROPERTYName.lg)
> This file contains the generic templates to use for properties of particular types.
# welcome
@ -46,4 +48,3 @@
# notUnderstood
- Sorry, I do not understand ${join(foreach(turn.unrecognizedtext, chunk, concat("'", chunk, "'")), ' or ')}
[unittests-library-PROPERTYName.lg](unittests-library-PROPERTYName.lg)

Просмотреть файл

@ -1,7 +1,6 @@
# template
[import](./CustomFunctionSub.lg)
# template
- ${custom(1, 2)}
# callRef
- ${refSub()}
[import](./CustomFunctionSub.lg)

Просмотреть файл

@ -1,7 +1,3 @@
# basicTemplate
- Hi
- Hello
[import](./6.lg)
[import](6.lg)
[import](././6.lg)
@ -9,6 +5,11 @@
[import](import.lg)
[import](import/import3.lg)
# basicTemplate
- Hi
- Hello
# basicTemplate2
- Hi 2
- Hello 2

Просмотреть файл

@ -1,12 +1,12 @@
# basicTemplate3
- Hi
- Hello
[import](./6.lg)
[import](6.lg)
[import](././6.lg)
[import](8.lg)
# basicTemplate3
- Hi
- Hello
# basicTemplate4
- Hi 2
- Hello 2

Просмотреть файл

@ -10,7 +10,7 @@
- IF: ${false}
- hi
> else should not follewed by any expressions
> else should not be followed by any expressions
# template3
- IF: ${true}
- hello

Просмотреть файл

@ -1,9 +1,8 @@
# basicTemplate
[import](ImportFile.lg)
# basicTemplate
- Hi
- Hello
[import](ImportFile.lg)
# basicTemplate2
- Hi 2
- Hello 2

Просмотреть файл

@ -0,0 +1,9 @@
# hi
[]
- hi
# foo.bar
[
]
-hi
[import]

Просмотреть файл

@ -1,4 +1,3 @@
# template
[No such file Non.lg](./Non.lg)
# template
- Hello
[No such file Non.lg](./Non.lg)

Просмотреть файл

@ -1,19 +1,4 @@
#Demo
- ${createArray(1,2
2,3)
[import](5.lg)
#Demo2
- ${createArray(1,
2,3)
#Demo3
- IF ${32.5 > 14.1 ||
userName == 'doskey' ||
day = 'Monday'
- good day
#Demo4
- ${createArray(1,
2,3)
> this is a comment

Просмотреть файл

@ -63,6 +63,7 @@
<None Remove="ExceptionExamples\DuplicatedTemplatesInImportFiles.lg" />
<None Remove="ExceptionExamples\RunTimeErrors.lg" />
<None Remove="ExceptionExamples\ExpressionFormatError.lg" />
<None Remove="ExceptionExamples\ErrorLine.lg" />
<None Remove="MultiLanguage\a.lg" />
<None Remove="MultiLanguage\a.en.lg" />
</ItemGroup>
@ -227,6 +228,9 @@
<Content Include="ExceptionExamples\ExpressionFormatError.lg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="ExceptionExamples\ErrorLine.lg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="MultiLanguage\a.lg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Просмотреть файл

@ -137,11 +137,6 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
Assert.AreEqual(1, diagnostics.Count);
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[0].Severity);
Assert.IsTrue(diagnostics[0].Message.Contains("Close } is missing in Expression"));
diagnostics = Templates.ParseText("#Demo4\r\n- ${createArray(1,\r\n2,3)\r\n#AnotherTemplate").Diagnostics;
Assert.AreEqual(1, diagnostics.Count);
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[0].Severity);
Assert.IsTrue(diagnostics[0].Message.Contains("Close } is missing in Expression"));
}
[TestMethod]
@ -317,6 +312,23 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
Assert.AreEqual("'dialog.abc' evaluated to null. [switchcase2] Case 'Default': Error occurred when evaluating '-I want ${dialog.abc}'.", exception.Message);
}
[TestMethod]
public void TestErrorLine()
{
var diagnostics = GetDiagnostics("ErrorLine.lg");
Assert.AreEqual(4, diagnostics.Count);
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[0].Severity);
Assert.IsTrue(diagnostics[0].Message.Contains(TemplateErrors.SyntaxError));
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[1].Severity);
Assert.IsTrue(diagnostics[1].Message.Contains(TemplateErrors.InvalidStrucName));
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[2].Severity);
Assert.IsTrue(diagnostics[2].Message.Contains(TemplateErrors.MissingStrucEnd));
Assert.AreEqual(DiagnosticSeverity.Error, diagnostics[3].Severity);
Assert.IsTrue(diagnostics[3].Message.Contains(TemplateErrors.InvalidStrucBody));
}
[TestMethod]
public void TestExpressionFormatError()
{

Просмотреть файл

@ -425,7 +425,7 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
Assert.IsTrue(evaled == "Hi 2" || evaled == "Hello 2");
// Assert 6.lg of relative path is imported from text.
templates = Templates.ParseText("# basicTemplate\r\n- Hi\r\n- Hello\r\n[import](./6.lg)", GetExampleFilePath("xx.lg"));
templates = Templates.ParseText("[import](./6.lg)\r\n# basicTemplate\r\n- Hi\r\n- Hello\r\n", GetExampleFilePath("xx.lg"));
Assert.AreEqual(8, templates.AllTemplates.Count());
evaled = templates.Evaluate("basicTemplate", null).ToString();

Просмотреть файл

@ -1,4 +1,6 @@
[sandwich-BreadEntity.lg](sandwich-BreadEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskBread
- ${askEnum('Bread')}
@ -10,6 +12,3 @@
# Bread(val)
- ${BreadEntity(val)}
[sandwich-BreadEntity.lg](sandwich-BreadEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)
# BreadEntity(value)
- SWITCH: ${value}
@ -16,5 +17,3 @@
# chooseBreadEntity
- ${chooseEnumEntity('Bread')}
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwich-CheeseEntity.lg](sandwich-CheeseEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskCheese
- ${askEnum('Cheese')}
@ -10,6 +12,3 @@
# Cheese(val)
- ${CheeseEntity(val)}
[sandwich-CheeseEntity.lg](sandwich-CheeseEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)
# CheeseEntity(value)
- SWITCH: ${value}
@ -24,5 +25,3 @@
# chooseCheeseEntity
- ${chooseEnumEntity('Cheese')}
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskLength
- ${askString('Length')}
@ -6,5 +7,3 @@
# LengthName
- length
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwich-MeatEntity.lg](sandwich-MeatEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskMeat
- ${askEnum('Meat')}
@ -10,6 +12,3 @@
# Meat(val)
- ${MeatEntity(val)}
[sandwich-MeatEntity.lg](sandwich-MeatEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)
# MeatEntity(value)
- SWITCH: ${value}
@ -24,5 +25,3 @@
# chooseMeatEntity
- ${chooseEnumEntity('Meat')}
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Choose.lg](sandwich-library-Choose.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwich-NameEntity.lg](sandwich-NameEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskName
- ${askString('Name')}
@ -10,6 +12,3 @@
# Name(val)
- ${NameEntity(val)}
[sandwich-NameEntity.lg](sandwich-NameEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskPrice
- ${askString('Price')}
@ -6,5 +7,3 @@
# PriceName
- price
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,4 +1,6 @@
[sandwich-QuantityEntity.lg](sandwich-QuantityEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)
# AskQuantity
- ${askNumber('Quantity')}
@ -10,6 +12,3 @@
# Quantity(val)
- ${QuantityEntity(val)}
[sandwich-QuantityEntity.lg](sandwich-QuantityEntity.lg)
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Missing.lg](sandwich-library-Missing.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-ReadForm.lg](sandwich-library-ReadForm.lg)
# numberValidation(property, number)
- IF: ${less(number, dialogClass.schema.properties[property].minimum)}
- ${number} is less than the minimum value ${dialogClass.schema.properties[property].minimum}.
@ -35,5 +38,3 @@
# unexpectedPropertyChange(property, val, oldVal)
- ${name(property)} is changed from ${value(property, oldVal)} to ${value(property, val)}.
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-ReadForm.lg](sandwich-library-ReadForm.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Help.lg](sandwich-library-Help.lg)
# chooseEnumEntity(property)
- ```
${askHelp()}
@ -9,5 +12,3 @@ Please choose a value for ${name(property)} from [${join(foreach(turn.dialogEven
# choosePropertyEntity(property)
- "${property.entity.text}" as ${name(property.property)}
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Help.lg](sandwich-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
# askHelp
- IF: ${$retries > 0 && $lastIntent != 'Help'}
- ${join(foreach($expectedProperties, expected, help1(expected)), '\n')}
@ -25,4 +27,3 @@ You can find out about a specific property by doing 'help <property>'.
- Enter any string for ${name(property)}
- ELSE:
- No help available.
[sandwich-library.lg](sandwich-library.lg)

Просмотреть файл

@ -1,3 +1,6 @@
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Help.lg](sandwich-library-Help.lg)
# askEnum(property)
- ```
${askHelp()}
@ -25,5 +28,3 @@ Enter a value for ${name(property)}
${askHelp()}
Enter a value for ${name(property)}: true or false
```
[sandwich-library.lg](sandwich-library.lg)
[sandwich-library-Help.lg](sandwich-library-Help.lg)

Просмотреть файл

@ -1,3 +1,5 @@
[sandwich-library.lg](sandwich-library.lg)
# readForm(name)
[Activity
Attachments=${json(formCard(name))}
@ -49,4 +51,3 @@
# subFact(property, subproperty)
- ${dialog[property][subproperty]}
[sandwich-library.lg](sandwich-library.lg)

Просмотреть файл

@ -1,4 +1,5 @@
> This file contains the generic templates to use for properties of particular types.
[sandwich-library-PROPERTYName.lg](sandwich-library-PROPERTYName.lg)
# welcome
- Welcome!
@ -46,4 +47,3 @@
# notUnderstood
- Sorry, I do not understand ${join(foreach(turn.unrecognizedtext, chunk, concat("'", chunk, "'")), ' or ')}
[sandwich-library-PROPERTYName.lg](sandwich-library-PROPERTYName.lg)