| // | |
| // 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) | |
| { | |
| } | |
| } |