blob: f2674fa17aa0e99388edce089f06e48dc8a7f68e [file]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Antlr4.Runtime;
using org.apache.plc4net.tools.codegen.grammar;
using org.apache.plc4net.tools.codegen.model.terms;
namespace org.apache.plc4net.tools.codegen;
/// <summary>Converts an expression parsed by the shared grammar into its semantic tree.</summary>
public static class MspecExpressionParser
{
public static Term Parse(string expression)
{
ArgumentNullException.ThrowIfNull(expression);
var lexer = new ExpressionLexer(CharStreams.fromString(expression));
var lexerErrors = new ErrorCollector<int>();
lexer.RemoveErrorListeners();
lexer.AddErrorListener(lexerErrors);
var parser = new ExpressionParser(new CommonTokenStream(lexer));
var parserErrors = new ErrorCollector<IToken>();
parser.RemoveErrorListeners();
parser.AddErrorListener(parserErrors);
ExpressionParser.ExpressionStringContext parsed = parser.expressionString();
string[] errors = lexerErrors.Errors.Concat(parserErrors.Errors).ToArray();
if (errors.Length != 0)
{
throw new MspecParseException(
$"Failed to parse expression '{expression}': {string.Join("; ", errors)}");
}
try
{
return Convert(parsed.expression());
}
catch (Exception exception) when (exception is FormatException or OverflowException)
{
throw new MspecParseException(
$"Failed to parse expression '{expression}': numeric literal is outside the supported range.",
exception);
}
}
private static Term Convert(ExpressionParser.ExpressionContext context) => context switch
{
ExpressionParser.NumberExpressionContext value => Number(value.Number().GetText()),
ExpressionParser.HexExpressionContext value =>
new HexadecimalLiteral(value.HexExpression().GetText()),
ExpressionParser.BoolExpressionContext value =>
new BooleanLiteral(bool.Parse(value.Bool().GetText())),
ExpressionParser.NullExpressionContext => new NullLiteral(),
ExpressionParser.StringExpressionContext value => WithIndexes(
new StringLiteral(Unquote(value.String().GetText())), value.indexes()),
ExpressionParser.IdentifierExpressionContext value => Identifier(value.identifierSegment()),
ExpressionParser.UnaryMinusExpressionContext value =>
new UnaryExpression("-", Convert(value.expression())),
ExpressionParser.NotExpressionContext value =>
new UnaryExpression("!", Convert(value.expression())),
ExpressionParser.ExpressionExpressionContext value =>
WithIndexes(Convert(value.expression()), value.indexes()),
ExpressionParser.PowerExpressionContext value => Binary(value, "^"),
ExpressionParser.MultExpressionContext value => Binary(value, value.op.Text),
ExpressionParser.AddExpressionContext value => Binary(value, value.op.Text),
ExpressionParser.BitShiftExpressionContext value => Binary(value, value.op.Text),
ExpressionParser.CompExpressionContext value => Binary(value, value.op.Text),
ExpressionParser.EqExpressionContext value => Binary(value, value.op.Text),
ExpressionParser.AndExpressionContext value => Binary(value, "&&"),
ExpressionParser.BitAndExpressionContext value => Binary(value, "&"),
ExpressionParser.OrExpressionContext value => Binary(value, "||"),
ExpressionParser.BitOrExpressionContext value => Binary(value, "|"),
ExpressionParser.IfExpressionContext value => new TernaryExpression(
Convert(value.expression(0)), Convert(value.expression(1)), Convert(value.expression(2))),
_ => throw new MspecParseException($"Unsupported expression: {context.GetText()}")
};
private static Term Number(string text)
{
if (!text.Contains('.'))
{
return new IntegerLiteral(long.Parse(text, CultureInfo.InvariantCulture), text);
}
double value = double.Parse(text, CultureInfo.InvariantCulture);
return double.IsFinite(value)
? new FloatLiteral(value)
: throw new OverflowException();
}
private static BinaryExpression Binary(ParserRuleContext context, string op)
{
ExpressionParser.ExpressionContext[] operands =
context.GetRuleContexts<ExpressionParser.ExpressionContext>();
return new BinaryExpression(Convert(operands[0]), op, Convert(operands[1]));
}
private static Term WithIndexes(Term target, ExpressionParser.IndexesContext? indexes) =>
indexes == null
? target
: new IndexExpression(target, indexes.expression().Select(Convert).ToArray());
private static VariableLiteral Identifier(ExpressionParser.IdentifierSegmentContext context)
{
ExpressionParser.IdentifierSegmentArgumentsContext? arguments =
context.identifierSegmentArguments();
ExpressionParser.IdentifierSegmentIndexesContext? indexes =
context.identifierSegmentIndexes();
ExpressionParser.IdentifierSegmentRestContext? rest = context.identifierSegmentRest();
return new VariableLiteral(
context.Identifier().GetText(),
arguments?.arguments().expression().Select(Convert).ToArray(),
indexes?.indexes().expression().Select(Convert).ToArray(),
rest == null ? null : Identifier(rest.identifierSegment()));
}
private static string Unquote(string text)
{
char delimiter = text[0];
var value = new System.Text.StringBuilder(text.Length - 2);
for (int i = 1; i < text.Length - 1; i++)
{
if (text[i] == '\\' && i + 1 < text.Length - 1 &&
(text[i + 1] == delimiter || text[i + 1] == '\\'))
{
i++;
}
value.Append(text[i]);
}
return value.ToString();
}
private sealed class ErrorCollector<TSymbol> : IAntlrErrorListener<TSymbol>
{
public List<string> Errors { get; } = [];
public void SyntaxError(TextWriter output, IRecognizer recognizer, TSymbol offendingSymbol,
int line, int charPositionInLine, string msg, RecognitionException e) =>
Errors.Add($"line {line}:{charPositionInLine} {msg}");
}
}
public sealed class MspecParseException : Exception
{
public MspecParseException(string message) : base(message)
{
}
public MspecParseException(string message, Exception innerException) : base(message, innerException)
{
}
}