├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Cargo.toml ├── LICENSE ├── README.md ├── csharp-expr-rs.sln ├── rustfmt.toml ├── src-csharp └── csharp-expr-rs │ ├── .editorconfig │ ├── Expression.cs │ ├── ExpressionInvokeException.cs │ ├── ExpressionParsingException.cs │ ├── Native.cs │ └── csharp-expr-rs.csproj ├── src ├── expressions.rs ├── ffi.rs ├── functions.rs ├── lib.rs └── parsing.rs └── tests-csharp ├── csharp-expr-rs.Benchmarks ├── CompilationBenchmark.cs ├── Program.cs ├── TinyExpressionBenchmark.cs └── csharp-expr-rs.Benchmarks.csproj ├── csharp-expr-rs.ConsoleTests ├── NativePassStringTests.cs ├── Program.cs ├── Properties │ └── launchSettings.json └── csharp-expr-rs.ConsoleTests.csproj └── csharp-expr-rs.Tests ├── CsharpExprLibTests.cs ├── DatetimeBehaviourTests.cs └── csharp-expr-rs.Tests.csproj /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | obj 12 | bin 13 | /.vs 14 | /tests-csharp/csharp-expr-rs.Benchmarks/bz-expressions 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "(Windows) Launch", 6 | "type": "cppvsdbg", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/target/debug/csharp-expr.exe", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceRoot}", 12 | "environment": [], 13 | "externalConsole": true 14 | }, 15 | { 16 | "name": "(OSX) Launch", 17 | "type": "lldb", 18 | "request": "launch", 19 | "program": "${workspaceRoot}/target/debug/csharp-expr", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug.allowBreakpointsEverywhere": true, 3 | "editor.formatOnSave": true 4 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "cargo", 8 | "subcommand": "build", 9 | "problemMatcher": [ 10 | "$rustc" 11 | ], 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "csharp-expr" 3 | version = "0.1.0" 4 | authors = ["Jêrome Rx "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name="csharp_expr" 9 | crate-type = ["dylib"] 10 | 11 | [dependencies] 12 | nom="5.1.2" 13 | lazy_static = "1.4.0" 14 | unescape = "0.1.0" 15 | encoding_rs = "0.8.24" 16 | once_cell = "1.4.1" 17 | regex = "1" 18 | num-format = "0.4.0" # when floating point formating will be here : https://github.com/bcmyers/num-format/issues/1 19 | chrono = "0.4" 20 | chrono-tz = "0.5.3" 21 | unicase = "2.6.0" 22 | rust_decimal = "1.8.1" 23 | rust_decimal_macros = "1.8.1" 24 | # cached = "0.18.0" 25 | 26 | [dev-dependencies] 27 | test-case = "1.0.0" 28 | # flame = "0.2.2" 29 | # flamer = "0.4.0" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jérôme Rx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csharp-expr-rs 2 | 3 | c# expression parser in Rust 4 | 5 | 6 | # Todo 7 | - [x] Parse expressions with nom parser 8 | - [x] Execute expressions 9 | - [x] FFI create an expression from .net 10 | - [x] Execute this expression from .net 11 | - [x] Benchmark execution time against DynamicExpresso (.net expression interpreter) => .net version x2 better 12 | - [x] Optimize so function are ready to call after first parsing => Rust now competes with DynamicExpresso 13 | - [x] Benchmark "compilation" time => Rust x40 better 14 | - [x] Debug trait for Expr 15 | - [x] PartialEq trait for Expr 16 | - [x] Parse '"str\"ing"' as 'str"ing' value for Exp::Str (tried escaped_transfom but it changes the return types, perhaps find an other way, when creating the Expr::Str) 17 | - [x] More unit tests (Rust side) 18 | - [x] Handle identifiers 19 | - [x] Handle identifier value and usage 20 | - [x] Handle passing values arguments for identifiers from C# side 21 | - [x] Benchmarks & Optimisations => cloning Expressions was a baaaaaad idea ! /10 perfs drop 22 | - [x] Could we try some better syntax with type aliasing Rc, perhaps some macros ? okay, not bad, not great 23 | - [x] Optimisation : Do not pass parameters throught FFI if they are not used by the expression ! 24 | - [x] More tests about identifiers list 25 | - [x] Couldn't prepare a function inside an unknown function => OK, expected behavior 26 | - [x] Handle `null` value (just in case) 27 | - [x] Debug ` ` white space and tabs in strings are still strings 28 | - [x] Segregate Expr & ExprResult enum types 29 | - [x] Implement Sql Like function 30 | - [x] Implement existing functions in BZP system 31 | - [x] Case insensitive function names ... and identifier names 32 | - [x] Result/Error handling on FFI for expressions execution 33 | - [x] Debug strings should be able to contain any character ! see also : https://github.com/Geal/nom/issues/1118 34 | - [x] Handle (good enough) non ascii strings 35 | - [x] Handle numeric types as Decimal/Money with 4 digits precision (have to find the right crate : https://crates.io/crates/rust_decimal ?) 36 | - [x] Debug empty parameters list parsing (it's parsed as identifier right now) 37 | - [x] Handle functions determinism 38 | - [x] Debug empty string parsing 39 | - [x] refacto : remove all usage of `opt(sp)`, the take_while in sp() should be enough do the optional trick 40 | - [x] refacto : replace some preceded/terminated by 'delimited' 41 | - [x] Handle C# Assoc/Binary operators parsing (only delimited by parenthesis) 42 | - [x] Handle C# Assoc/Binary operators execution 43 | - [x] Handle C# Assoc/Binary operators parsing with no parenthesis delimiter 44 | - [x] Make FFI parse fail to return an error and raise a nice exception AND not panicking ! 45 | - [x] Handle timezones & day light save hours (thank you chrono-tz !) 46 | - [x] sp() should get the new lines and allow the parsing to continue 47 | - [x] Make C# FFI 1 char strings to Rust 48 | - [x] Replace &Vec usage by slices (have a look at f_operators) 49 | - [x] Handle non usefull parenthesis (some tests to uncomment) 50 | - [ ] Turn all possible overflow problems in casts (isize as u32, i32 ....) to nice errors 51 | - [x] Rc strings to avoid cloning them 52 | - [x] Rc and lazy load variable values and avoid read them multiple times when used multiple times 53 | - [x] Optimisation : lazy evaluation for identifier value getters ? https://docs.rs/once_cell/1.2.0/once_cell/ 54 | - [ ] comment out or remove dbg!() 55 | - [ ] Handle operators precedence ! 56 | - [ ] Debug snake case identifiers parsing 57 | - [ ] Debug Identifiers (Some tests are not passing) 58 | - [ ] More perf benchmarks with arguments passing 59 | - [ ] Return the right result type throught FFI 60 | - [ ] Error handling on expressions parsing 61 | - [ ] Modularisation, so anyone can implement their own functions list 62 | - [ ] Allow passing functions from dotnet side to be called from rust expression ? // see System.Runtime.InteropServices.AllowReversePInvokeCallsAttribute 63 | - [ ] Publish on crates.io 64 | 65 | 66 | # Super helpfull resources 67 | - http://jakegoulding.com/rust-ffi-omnibus/ 68 | - https://dev.to/living_syn/calling-rust-from-c-6hk 69 | - https://dev.to/luzero/building-crates-so-they-look-like-c-abi-libraries-1ibn 70 | - https://ndportmann.com/system-runtime-compilerservices-unsafe/ 71 | 72 | # more reading ? 73 | - https://bodil.lol/parser-combinators/ 74 | -------------------------------------------------------------------------------- /csharp-expr-rs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29021.104 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-expr-rs", "src-csharp\csharp-expr-rs\csharp-expr-rs.csproj", "{9768A265-4904-49AF-99C5-42C3608E39F6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-expr-rs.Tests", "tests-csharp\csharp-expr-rs.Tests\csharp-expr-rs.Tests.csproj", "{7E32CF99-BF0A-4792-A132-C4CEC478AAFF}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-expr-rs.ConsoleTests", "tests-csharp\csharp-expr-rs.ConsoleTests\csharp-expr-rs.ConsoleTests.csproj", "{E3147DED-5924-4142-A772-4A6C401C96F4}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-expr-rs.Benchmarks", "tests-csharp\csharp-expr-rs.Benchmarks\csharp-expr-rs.Benchmarks.csproj", "{F8610EA1-931D-4FB0-98C9-7658E9193D39}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {9768A265-4904-49AF-99C5-42C3608E39F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9768A265-4904-49AF-99C5-42C3608E39F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9768A265-4904-49AF-99C5-42C3608E39F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9768A265-4904-49AF-99C5-42C3608E39F6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {7E32CF99-BF0A-4792-A132-C4CEC478AAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {7E32CF99-BF0A-4792-A132-C4CEC478AAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {7E32CF99-BF0A-4792-A132-C4CEC478AAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {7E32CF99-BF0A-4792-A132-C4CEC478AAFF}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {E3147DED-5924-4142-A772-4A6C401C96F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {E3147DED-5924-4142-A772-4A6C401C96F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {E3147DED-5924-4142-A772-4A6C401C96F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {E3147DED-5924-4142-A772-4A6C401C96F4}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {F8610EA1-931D-4FB0-98C9-7658E9193D39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {F8610EA1-931D-4FB0-98C9-7658E9193D39}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {F8610EA1-931D-4FB0-98C9-7658E9193D39}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {F8610EA1-931D-4FB0-98C9-7658E9193D39}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {664C54B0-C705-4950-A113-42B63FCDEAAA} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 220 2 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # S101: Types should be named in PascalCase 4 | dotnet_diagnostic.S101.severity = silent 5 | 6 | # S3881: "IDisposable" should be implemented correctly 7 | dotnet_diagnostic.S3881.severity = suggestion 8 | 9 | # IDE1006: Naming Styles 10 | dotnet_diagnostic.IDE1006.severity = silent 11 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/Expression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Runtime.ExceptionServices; 6 | using System.Text; 7 | 8 | namespace csharp_expr_rs 9 | { 10 | static class StringExtensions 11 | { 12 | static Encoding unicode = new UnicodeEncoding(); 13 | 14 | public static FFICSharpStringHolder MakeFFICSharpStringHolder(this string str) 15 | { 16 | if (str == null || str.Length == 0) 17 | { 18 | return new FFICSharpStringHolder 19 | { 20 | ffiStr = new FFICSharpString { len = (UIntPtr)0 } 21 | }; 22 | } 23 | 24 | var result = new FFICSharpStringHolder 25 | { 26 | dotnetStr = str 27 | }; 28 | 29 | unsafe 30 | { 31 | var len = (UIntPtr)(str.Length * sizeof(char)); 32 | 33 | if (str.Length < 2) 34 | { 35 | result.shortStr = unicode.GetBytes(str); 36 | fixed (byte* ptr = result.shortStr) 37 | { 38 | var sharpString = new FFICSharpString { ptr = ptr, len = len }; 39 | result.ffiStr = sharpString; 40 | return result; 41 | } 42 | } 43 | else 44 | { 45 | fixed (char* ptr = result.dotnetStr) 46 | { 47 | var sharpString = new FFICSharpString { ptr = (byte*)ptr, len = len }; 48 | result.ffiStr = sharpString; 49 | return result; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | /// 57 | /// If non sealed, implement the proper disposable pattern ! 58 | /// 59 | public sealed class Expression : IDisposable 60 | { 61 | private readonly FFIExpressionHandle _expressionHandle; 62 | private readonly HashSet _identifiers; 63 | 64 | public Expression(string expression) 65 | : this(PrepareExpression(expression)) 66 | { } 67 | 68 | [HandleProcessCorruptedStateExceptions] 69 | static (FFIExpressionHandle _expressionHandle, HashSet _identifiers, bool isDeterministic) PrepareExpression(string expression) 70 | { 71 | try 72 | { 73 | var FFIResultPointer = Native.ffi_parse_and_prepare_expr(expression); 74 | if (FFIResultPointer.is_error) 75 | { 76 | var errorMsg = FFIResultPointer.GetError().AsStringAndDispose(); 77 | throw new ExpressionParsingException(errorMsg); 78 | } 79 | 80 | var expressionHandle = FFIResultPointer.GetContent(); 81 | var identifiers = new HashSet( 82 | Native.ffi_get_identifiers(expressionHandle) 83 | .AsStringAndDispose() 84 | .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) 85 | ); 86 | var isDeterministic = Native.ffi_is_deterministic(expressionHandle); 87 | 88 | return (expressionHandle, identifiers, isDeterministic); 89 | } 90 | catch (Exception ex) 91 | { 92 | throw new ExpressionParsingException(ex.Message, ex); 93 | } 94 | } 95 | 96 | internal Expression((FFIExpressionHandle expressionHandle, HashSet identifiers, bool isDeterministic) preparedExpression) 97 | { 98 | _expressionHandle = preparedExpression.expressionHandle; 99 | _identifiers = preparedExpression.identifiers; 100 | Identifiers = _identifiers.ToArray(); 101 | IsDeterministic = preparedExpression.isDeterministic; 102 | } 103 | 104 | public string[] Identifiers { get; } 105 | public bool IsDeterministic { get; } 106 | 107 | readonly FFIIdentifierKeyValue[] _emptyValues = new FFIIdentifierKeyValue[0]; 108 | 109 | public (bool is_error, string content) Execute(IReadOnlyDictionary identifierValues) 110 | => Execute((IEnumerable>)identifierValues); 111 | 112 | [HandleProcessCorruptedStateExceptions] 113 | public (bool is_error, string content) Execute(IEnumerable> identifierValues) 114 | { 115 | try 116 | { 117 | var idValues = _emptyValues; 118 | 119 | if (identifierValues != null) 120 | { 121 | idValues = identifierValues 122 | .Select(kv => new FFIIdentifierKeyValue { key = kv.Key, value = kv.Value ?? string.Empty }) 123 | .ToArray(); 124 | } 125 | 126 | var result = Native.ffi_exec_expr(_expressionHandle, idValues, (UIntPtr)idValues.Length); 127 | var stringResult = result.GetContent().AsStringAndDispose(); 128 | return (result.is_error, stringResult); 129 | } 130 | catch (Exception ex) 131 | { 132 | throw new ExpressionInvokeException(ex.Message, ex); 133 | } 134 | } 135 | 136 | public void Dispose() 137 | { 138 | _expressionHandle.Dispose(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/ExpressionInvokeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace csharp_expr_rs 6 | { 7 | [Serializable] 8 | public class ExpressionInvokeException : Exception 9 | { 10 | public ExpressionInvokeException(string message) : base(message) { } 11 | public ExpressionInvokeException(string message, Exception inner) : base(message, inner) { } 12 | protected ExpressionInvokeException( 13 | System.Runtime.Serialization.SerializationInfo info, 14 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/ExpressionParsingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace csharp_expr_rs 6 | { 7 | [Serializable] 8 | public class ExpressionParsingException : Exception 9 | { 10 | public ExpressionParsingException(string message) : base(message) { } 11 | public ExpressionParsingException(string message, Exception inner) : base(message, inner) { } 12 | protected ExpressionParsingException( 13 | System.Runtime.Serialization.SerializationInfo info, 14 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/Native.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | [assembly: InternalsVisibleTo("csharp-expr-rs.Tests")] 7 | 8 | namespace csharp_expr_rs 9 | { 10 | internal static class Native 11 | { 12 | public const string LIB_NAME = "csharp_expr.dll"; 13 | 14 | // https://docs.microsoft.com/fr-fr/dotnet/framework/interop/default-marshaling-for-strings 15 | 16 | [DllImport(LIB_NAME)] 17 | public static extern FFIParseResult ffi_parse_and_prepare_expr([MarshalAs(UnmanagedType.LPUTF8Str)] string expression); 18 | [DllImport(LIB_NAME)] 19 | public static extern void ffi_free_expr(IntPtr ptr); 20 | 21 | [DllImport(LIB_NAME)] 22 | public static extern FFIStringHandle ffi_get_identifiers(FFIExpressionHandle ptr); 23 | [DllImport(LIB_NAME)] 24 | [return: MarshalAs(UnmanagedType.I1)] 25 | public static extern bool ffi_is_deterministic(FFIExpressionHandle ptr); 26 | 27 | [DllImport(LIB_NAME, CharSet = CharSet.Ansi)] 28 | public static extern FFIExecResult ffi_exec_expr(FFIExpressionHandle ptr, FFIIdentifierKeyValue[] identifier_values, UIntPtr identifier_values_len); 29 | [DllImport(LIB_NAME)] 30 | public static extern void ffi_free_cstring(IntPtr ptr); 31 | } 32 | 33 | [StructLayout(LayoutKind.Sequential)] 34 | internal struct FFIIdentifierKeyValue 35 | { 36 | [MarshalAs(UnmanagedType.LPUTF8Str)] 37 | public string key; 38 | [MarshalAs(UnmanagedType.LPUTF8Str)] 39 | public string value; 40 | } 41 | 42 | [StructLayout(LayoutKind.Sequential)] 43 | internal unsafe struct FFICSharpString 44 | { 45 | public byte* ptr; 46 | public UIntPtr len; 47 | } 48 | 49 | internal unsafe class FFICSharpStringHolder 50 | { 51 | public byte[] shortStr; 52 | public string dotnetStr; 53 | public FFICSharpString ffiStr; 54 | } 55 | 56 | [StructLayout(LayoutKind.Sequential)] 57 | internal unsafe struct FFIExecResult 58 | { 59 | [MarshalAs(UnmanagedType.I1)] 60 | public bool is_error; 61 | public IntPtr content; 62 | 63 | public FFIStringHandle GetContent() => new FFIStringHandle(content); 64 | } 65 | 66 | internal class FFIStringHandle : SafeHandle 67 | { 68 | public FFIStringHandle() : base(IntPtr.Zero, true) { } 69 | public FFIStringHandle(IntPtr intPtr) : base(intPtr, true) { } 70 | 71 | public override bool IsInvalid => false; 72 | 73 | public string AsString() 74 | { 75 | int len = 0; 76 | while (Marshal.ReadByte(handle, len) != 0) 77 | { ++len; } 78 | byte[] buffer = new byte[len]; 79 | Marshal.Copy(handle, buffer, 0, buffer.Length); 80 | return Encoding.UTF8.GetString(buffer); 81 | } 82 | 83 | public string AsStringAndDispose() 84 | { 85 | try 86 | { 87 | return AsString(); 88 | } 89 | finally 90 | { 91 | Dispose(); 92 | } 93 | } 94 | 95 | protected override bool ReleaseHandle() 96 | { 97 | Native.ffi_free_cstring(handle); 98 | return true; 99 | } 100 | } 101 | 102 | [StructLayout(LayoutKind.Sequential)] 103 | internal unsafe struct FFIParseResult 104 | { 105 | [MarshalAs(UnmanagedType.I1)] 106 | public bool is_error; 107 | public IntPtr error; 108 | public IntPtr content; 109 | 110 | public FFIExpressionHandle GetContent() 111 | { 112 | if (is_error) 113 | throw new InvalidOperationException("Cannot get the content, it's an error"); 114 | return new FFIExpressionHandle(content); 115 | } 116 | 117 | public FFIStringHandle GetError() 118 | { 119 | if (!is_error) 120 | throw new InvalidOperationException("Cannot get the content as string, it's not an error"); 121 | return new FFIStringHandle(error); 122 | } 123 | } 124 | 125 | internal class FFIExpressionHandle : SafeHandle 126 | { 127 | public FFIExpressionHandle() : base(IntPtr.Zero, true) { } 128 | public FFIExpressionHandle(IntPtr intPtr) : base(intPtr, true) { } 129 | 130 | public override bool IsInvalid => false; 131 | 132 | public IntPtr RawPtr => handle; 133 | 134 | protected override bool ReleaseHandle() 135 | { 136 | Native.ffi_free_expr(handle); 137 | return true; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src-csharp/csharp-expr-rs/csharp-expr-rs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | csharp_expr_rs 6 | true 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/expressions.rs: -------------------------------------------------------------------------------- 1 | use crate::parsing::*; 2 | use std::fmt::Display; 3 | 4 | use chrono::prelude::*; 5 | use chrono::Duration; 6 | use nom::error::ErrorKind; 7 | use rust_decimal::prelude::*; 8 | use std::cmp; 9 | use std::collections::{HashMap, HashSet}; 10 | use std::fmt; 11 | use std::ops::Add; 12 | use std::ops::AddAssign; 13 | use std::rc::Rc; 14 | use std::sync::Arc; 15 | use unicase::UniCase; 16 | 17 | pub type RcExpr = Rc; 18 | pub type VecRcExpr = Vec; 19 | pub type SliceRcExpr = [RcExpr]; 20 | pub type ExprFuncResult = Result; 21 | pub type FunctionImpl = dyn Fn(&SliceRcExpr, &IdentifierValues) -> ExprFuncResult; 22 | pub type FunctionImplList = HashMap, (FunctionDeterminism, Rc)>; 23 | pub type IdentifierValueGetter = dyn Fn() -> Rc; 24 | pub type IdentifierValues = HashMap>; 25 | pub type ExprDecimal = Decimal; 26 | 27 | pub trait BinaryOperatorsImpl: Fn(RcExpr, RcExpr, AssocOp, &IdentifierValues) -> ExprFuncResult {} 28 | impl BinaryOperatorsImpl for T where T: Fn(RcExpr, RcExpr, AssocOp, &IdentifierValues) -> ExprFuncResult {} 29 | pub type BinaryOperatorsImplRc = Rc; 30 | 31 | #[repr(C)] 32 | #[derive(Copy, Clone, Debug, PartialEq)] 33 | pub enum FunctionDeterminism { 34 | Deterministic, 35 | NonDeterministic, 36 | } 37 | 38 | impl Default for FunctionDeterminism { 39 | fn default() -> Self { 40 | FunctionDeterminism::Deterministic 41 | } 42 | } 43 | 44 | impl Add for FunctionDeterminism { 45 | type Output = Self; 46 | 47 | fn add(self, other: Self) -> Self { 48 | match (self, other) { 49 | (FunctionDeterminism::Deterministic, FunctionDeterminism::Deterministic) => FunctionDeterminism::Deterministic, 50 | _ => FunctionDeterminism::NonDeterministic, 51 | } 52 | } 53 | } 54 | 55 | impl AddAssign for FunctionDeterminism { 56 | fn add_assign(&mut self, other: Self) { 57 | *self = *self + other; 58 | } 59 | } 60 | 61 | // got this list from rust : https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/arithmetic-operators 62 | #[derive(PartialEq, Copy, Clone)] 63 | pub enum AssocOp { 64 | Add, 65 | Subtract, 66 | Multiply, 67 | Divide, 68 | Modulus, 69 | LAnd, 70 | LOr, 71 | Equal, 72 | Less, 73 | LessEqual, 74 | NotEqual, 75 | Greater, 76 | GreaterEqual, 77 | } 78 | 79 | impl fmt::Display for AssocOp { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 81 | match self { 82 | AssocOp::Add => write!(f, "+"), 83 | AssocOp::Subtract => write!(f, "-"), 84 | AssocOp::Multiply => write!(f, "*"), 85 | AssocOp::Divide => write!(f, "/"), 86 | AssocOp::Modulus => write!(f, "%"), 87 | AssocOp::LAnd => write!(f, "&&"), 88 | AssocOp::LOr => write!(f, "||"), 89 | AssocOp::Equal => write!(f, "=="), 90 | AssocOp::Less => write!(f, "<"), 91 | AssocOp::LessEqual => write!(f, "<="), 92 | AssocOp::NotEqual => write!(f, "!="), 93 | AssocOp::Greater => write!(f, ">"), 94 | AssocOp::GreaterEqual => write!(f, ">="), 95 | } 96 | } 97 | } 98 | 99 | impl fmt::Debug for AssocOp { 100 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 101 | self::Display::fmt(self, f) 102 | } 103 | } 104 | 105 | #[repr(C)] 106 | #[derive(Clone)] 107 | pub enum Expr { 108 | Str(String), // "text" 109 | Boolean(bool), // true | false 110 | Num(ExprDecimal), // 123.45 111 | Null, // null 112 | Identifier(String), // varToto 113 | FunctionCall(UniCase, VecRcExpr), // func(42, "text") 114 | PreparedFunctionCall(UniCase, VecRcExpr, Rc), // func(42, "text") + *func() 115 | BinaryOperator(RcExpr, RcExpr, AssocOp), // 32 + 10 116 | PreparedBinaryOperator(RcExpr, RcExpr, AssocOp, Rc), // 32 + 10 + *operators() 117 | } 118 | 119 | #[derive(Clone, Debug)] 120 | pub enum ExprResult { 121 | Str(Rc), 122 | Boolean(bool), 123 | Num(ExprDecimal), 124 | Date(NaiveDateTime), 125 | TimeSpan(Duration), 126 | Null, 127 | 128 | NonExecuted(RcExpr), 129 | } 130 | 131 | #[repr(C)] 132 | #[derive(Debug)] 133 | pub struct ExprAndIdentifiers { 134 | pub expr: RcExpr, 135 | pub identifiers_names: HashSet, 136 | pub determinism: FunctionDeterminism, 137 | } 138 | 139 | impl fmt::Debug for Expr { 140 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 141 | match self { 142 | Expr::Str(x) => write!(f, "Str({:?})", x), 143 | Expr::Boolean(x) => write!(f, "Boolean({:?})", x), 144 | Expr::Num(x) => write!(f, "Num({:?})", x), 145 | Expr::Null => write!(f, "Null"), 146 | // Expr::Array(x) => write!(f, "Array({:?})", x), 147 | Expr::Identifier(x) => write!(f, "Identifier({:?})", x), 148 | Expr::FunctionCall(s, x) => write!(f, "FunctionCall({:?},{:?})", s, x), 149 | Expr::PreparedFunctionCall(s, x, _) => write!(f, "PreparedFunctionCall({:?},{:?})", s, x), 150 | Expr::BinaryOperator(l, r, o) => write!(f, "{:?} {:?} {:?}", l, o, r), 151 | Expr::PreparedBinaryOperator(l, r, o, _) => write!(f, "{:?} {:?} {:?}", l, o, r), 152 | } 153 | } 154 | } 155 | 156 | impl cmp::PartialEq for Expr { 157 | fn eq(&self, other: &Self) -> bool { 158 | match (self, other) { 159 | (Expr::Str(x_a), Expr::Str(x_b)) => x_a == x_b, 160 | (Expr::Boolean(x_a), Expr::Boolean(x_b)) => x_a == x_b, 161 | (Expr::Num(x_a), Expr::Num(x_b)) => x_a == x_b, 162 | // (Expr::Array(x_a), Expr::Array(x_b)) => x_a == x_b, 163 | (Expr::Identifier(x_a), Expr::Identifier(x_b)) => x_a == x_b, 164 | (Expr::BinaryOperator(left_a, right_a, op_a), Expr::BinaryOperator(left_b, right_b, op_b)) => left_a == left_b && right_a == right_b && op_a == op_b, 165 | (Expr::PreparedBinaryOperator(left_a, right_a, op_a, _), Expr::PreparedBinaryOperator(left_b, right_b, op_b, _)) => left_a == left_b && right_a == right_b && op_a == op_b, 166 | (Expr::FunctionCall(n_a, p_a), Expr::FunctionCall(n_b, p_b)) => n_a == n_b && p_a == p_b, 167 | (Expr::PreparedFunctionCall(n_a, p_a, _), Expr::PreparedFunctionCall(n_b, p_b, _)) => n_a == n_b && p_a == p_b, 168 | (Expr::Null, Expr::Null) => true, 169 | _ => false, 170 | } 171 | } 172 | } 173 | 174 | impl cmp::PartialEq for ExprResult { 175 | fn eq(&self, other: &Self) -> bool { 176 | match (self, other) { 177 | (ExprResult::Str(x_a), ExprResult::Str(x_b)) => x_a == x_b, 178 | (ExprResult::Boolean(x_a), ExprResult::Boolean(x_b)) => x_a == x_b, 179 | (ExprResult::Num(x_a), ExprResult::Num(x_b)) => x_a == x_b, 180 | (ExprResult::Date(x_a), ExprResult::Date(x_b)) => x_a == x_b, 181 | (ExprResult::TimeSpan(x_a), ExprResult::TimeSpan(x_b)) => x_a == x_b, 182 | (ExprResult::Null, ExprResult::Null) => true, // should be false ? => implemented in the `f_are_equals` function 183 | _ => false, 184 | } 185 | } 186 | } 187 | 188 | impl Display for Expr { 189 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 190 | match self { 191 | Expr::Str(s) => write!(f, "{}", s), 192 | Expr::Boolean(b) => write!(f, "{}", b), 193 | Expr::Num(n) => write!(f, "{}", n), 194 | Expr::Null => write!(f, ""), 195 | // Expr::Array(_) => write!(f, "Array"), 196 | Expr::Identifier(i) => write!(f, "@{}", i), 197 | Expr::FunctionCall(_, _) => write!(f, "FunctionCall"), 198 | Expr::PreparedFunctionCall(_, _, _) => write!(f, "PreparedFunctionCall"), 199 | Expr::BinaryOperator(l, r, o) => write!(f, "{} {} {}", l, o, r), 200 | Expr::PreparedBinaryOperator(l, r, o, _) => write!(f, "{} {} {}", l, o, r), 201 | } 202 | } 203 | } 204 | 205 | const SECONDS_IN_MIN: i64 = 60; 206 | const SECONDS_IN_HOURS: i64 = SECONDS_IN_MIN * 60; 207 | const SECONDS_IN_DAYS: i64 = SECONDS_IN_HOURS * 24; 208 | 209 | impl Display for ExprResult { 210 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 211 | match self { 212 | ExprResult::Str(s) => write!(f, "{}", s), 213 | ExprResult::Boolean(b) => write!(f, "{}", b), 214 | ExprResult::Num(n) => write!(f, "{}", n), 215 | ExprResult::Date(d) => write!(f, "{:02}/{:02}/{:02} {:02}:{:02}:{:02}", d.month(), d.day(), d.year(), d.hour(), d.minute(), d.second()), 216 | ExprResult::TimeSpan(d) => { 217 | let sign = if *d < Duration::zero() { "-" } else { "" }; 218 | let mut secs = d.num_seconds().abs(); 219 | let days = secs / SECONDS_IN_DAYS; 220 | secs = secs - days * SECONDS_IN_DAYS; 221 | let hours = secs / SECONDS_IN_HOURS; 222 | secs = secs - hours * SECONDS_IN_HOURS; 223 | let mins = secs / SECONDS_IN_MIN; 224 | secs = secs - mins * SECONDS_IN_MIN; 225 | if days > 0 { 226 | write!(f, "{}{}.{:02}:{:02}:{:02}", sign, days, hours, mins, secs) 227 | } else { 228 | write!(f, "{}{:02}:{:02}:{:02}", sign, hours, mins, secs) 229 | } 230 | } 231 | ExprResult::Null => write!(f, ""), 232 | ExprResult::NonExecuted(rc_expr) => write!(f, "{:?}", rc_expr), 233 | } 234 | } 235 | } 236 | 237 | impl ExprResult { 238 | pub fn is_final(&self) -> bool { 239 | match self { 240 | ExprResult::NonExecuted(_) => false, 241 | _ => true, 242 | } 243 | } 244 | 245 | pub fn to_rc_string(&self) -> Rc { 246 | match self { 247 | ExprResult::Str(s) => s.clone(), 248 | ExprResult::Boolean(b) => Rc::new(b.to_string()), 249 | _ => Rc::new(self.to_string()), 250 | } 251 | } 252 | } 253 | 254 | pub fn parse_expr(expression: &str) -> Result { 255 | let expr = expr::<(&str, ErrorKind)>(expression); 256 | match expr { 257 | Ok((rest, expr)) => match rest.len() { 258 | 0 => Ok(expr), 259 | _ => Err(format!("Unable to parse the rest of the expression '{:?}'", rest)), 260 | }, 261 | Err(err_kind) => Err(format!("{:?}", err_kind)), 262 | } 263 | } 264 | 265 | pub fn prepare_expr_and_identifiers(expr: Expr, funcs: &FunctionImplList, operators: BinaryOperatorsImplRc) -> ExprAndIdentifiers { 266 | let mut identifiers = HashSet::::new(); 267 | let (determinism, expr) = prepare_expr(Rc::new(expr), funcs, &mut identifiers, operators); 268 | ExprAndIdentifiers { 269 | expr, 270 | identifiers_names: identifiers, 271 | determinism, 272 | } 273 | } 274 | 275 | pub fn prepare_expr_list(exprs: &SliceRcExpr, funcs: &FunctionImplList, identifiers: &mut HashSet, operators: BinaryOperatorsImplRc) -> (FunctionDeterminism, VecRcExpr) { 276 | let mut list = VecRcExpr::with_capacity(exprs.len()); 277 | let mut total_determinist = FunctionDeterminism::default(); 278 | for p in exprs.iter() { 279 | let (determinism, prepared) = prepare_expr(Rc::clone(p), funcs, identifiers, Rc::clone(&operators)); 280 | list.push(prepared); 281 | total_determinist += determinism; 282 | } 283 | (total_determinist, list) 284 | } 285 | 286 | pub fn prepare_expr(expr: RcExpr, funcs: &FunctionImplList, identifiers: &mut HashSet, operators: BinaryOperatorsImplRc) -> (FunctionDeterminism, RcExpr) { 287 | match expr.as_ref() { 288 | Expr::Identifier(name) => { 289 | identifiers.insert(name.clone()); 290 | (FunctionDeterminism::Deterministic, expr) 291 | } 292 | Expr::FunctionCall(name, parameters) => match &funcs.get(&name) { 293 | Some(fnc) => { 294 | let (params_determinism, prepared_list) = prepare_expr_list(parameters, funcs, identifiers, operators); 295 | (fnc.0 + params_determinism, Rc::new(Expr::PreparedFunctionCall(name.clone(), prepared_list, Rc::clone(&fnc.1)))) 296 | } 297 | None => (FunctionDeterminism::default(), expr), 298 | }, 299 | Expr::BinaryOperator(left, right, op) => { 300 | let left_prepared = prepare_expr(Rc::clone(left), funcs, identifiers, Rc::clone(&operators)); 301 | let right_prepared = prepare_expr(Rc::clone(right), funcs, identifiers, Rc::clone(&operators)); 302 | ( 303 | (left_prepared.0 + right_prepared.0), 304 | RcExpr::new(Expr::PreparedBinaryOperator(left_prepared.1, right_prepared.1, *op, Rc::clone(&operators))), 305 | ) 306 | } 307 | Expr::Str(_) => (FunctionDeterminism::Deterministic, expr), 308 | Expr::Boolean(_) => (FunctionDeterminism::Deterministic, expr), 309 | Expr::Num(_) => (FunctionDeterminism::Deterministic, expr), 310 | Expr::Null => (FunctionDeterminism::Deterministic, expr), 311 | Expr::PreparedFunctionCall(_, _, _) => unreachable!(), 312 | Expr::PreparedBinaryOperator(_, _, _, _) => unreachable!(), 313 | } 314 | } 315 | 316 | pub fn exec_expr<'a>(expr: &'a RcExpr, values: &'a IdentifierValues) -> Result { 317 | match expr.as_ref() { 318 | Expr::Str(s) => Ok(ExprResult::Str(Rc::new(s.clone()))), 319 | Expr::Boolean(b) => Ok(ExprResult::Boolean(*b)), 320 | Expr::Num(f) => Ok(ExprResult::Num(*f)), 321 | Expr::Null => Ok(ExprResult::Null), 322 | Expr::Identifier(name) => match &values.get(name) { 323 | Some(s) => Ok(ExprResult::Str(s())), 324 | None => Err(format!("Unable to find value for identifier named '{}'", name)), 325 | }, 326 | Expr::FunctionCall(name, _parameters) => Err(format!("Unable to find the function named '{}'", name)), 327 | Expr::PreparedFunctionCall(_, parameters, fnc) => { 328 | let call_result = fnc(¶meters, &values)?; 329 | if let ExprResult::NonExecuted(expr) = call_result { 330 | exec_expr(&expr, values) 331 | } else { 332 | Ok(call_result) 333 | } 334 | } 335 | Expr::BinaryOperator(_, _, _) => Err(format!("No operators implementation")), 336 | Expr::PreparedBinaryOperator(left, right, op, op_impl) => op_impl(Rc::clone(left), Rc::clone(right), *op, values), 337 | } 338 | } 339 | 340 | #[cfg(test)] 341 | mod tests { 342 | use super::FunctionDeterminism::*; 343 | use super::*; 344 | use crate::functions::*; 345 | use rust_decimal_macros::*; 346 | use std::time::Instant; 347 | use test_case::test_case; 348 | macro_rules! rc_expr_str { 349 | ( $x:expr ) => { 350 | Rc::new(Expr::Str($x.to_string())) 351 | }; 352 | } 353 | macro_rules! exprresult_str { 354 | ( $x:expr ) => { 355 | ExprResult::Str($x) 356 | }; 357 | } 358 | macro_rules! rc_expr_num { 359 | ( $x:expr ) => { 360 | Rc::new(Expr::Num(dec!($x))) 361 | }; 362 | } 363 | macro_rules! exprresult_num { 364 | ( $x:expr ) => { 365 | ExprResult::Num(dec!($x)) 366 | }; 367 | } 368 | macro_rules! rc_expr_null { 369 | () => { 370 | Rc::new(Expr::Null) 371 | }; 372 | } 373 | 374 | #[test_case("\"test\" + 2" => Vec::::new())] 375 | #[test_case("test" => vec!["test"])] 376 | #[test_case("knownFunc(test2)" => vec!["test2"])] 377 | #[test_case("knownFunc(test2, test3, test2, test3)" => vec!["test2", "test3"])] 378 | #[test_case("unknownFunc(test7)" => Vec::::new())] 379 | #[test_case("knownFunc(test2, test3, test3, knownFunc(test4, test5), test6, test5)" => vec!["test2", "test3", "test4", "test5", "test6"])] 380 | fn prepare_expr_and_identifiers_detection(expression: &str) -> Vec { 381 | let expr = parse_expr(expression).unwrap(); 382 | let mut funcs = FunctionImplList::new(); 383 | funcs.insert( 384 | UniCase::new("knownFunc".to_string()), 385 | (FunctionDeterminism::Deterministic, Rc::new(|_v: &SliceRcExpr, _: &IdentifierValues| Ok(exprresult_num!(42)))), 386 | ); 387 | let expr = prepare_expr_and_identifiers(expr, &funcs, Rc::new(null_op)); 388 | let mut result = expr.identifiers_names.iter().cloned().collect::>(); 389 | result.sort(); 390 | result 391 | } 392 | 393 | #[test] 394 | fn execute_one_expression() { 395 | let mut funcs = FunctionImplList::new(); 396 | funcs.insert( 397 | UniCase::new("first".to_string()), 398 | ( 399 | FunctionDeterminism::Deterministic, 400 | Rc::new(|v: &SliceRcExpr, _: &IdentifierValues| v.first().map_or_else(|| Err("There was no first value.".to_string()), |x| Ok(ExprResult::NonExecuted(x.clone())))), 401 | ), 402 | ); 403 | 404 | funcs.insert( 405 | UniCase::new("forty_two".to_string()), 406 | (FunctionDeterminism::Deterministic, Rc::new(|_v: &SliceRcExpr, _: &IdentifierValues| Ok(exprresult_num!(42)))), 407 | ); 408 | funcs.insert( 409 | UniCase::new("forty_two_str".to_string()), 410 | (FunctionDeterminism::Deterministic, Rc::new(|_v: &SliceRcExpr, _: &IdentifierValues| Ok(exprresult_str!(Rc::new("42".to_string()))))), 411 | ); 412 | 413 | let mut values = IdentifierValues::new(); 414 | values.insert("my".into(), Box::new(|| Rc::new("value".to_string()))); 415 | 416 | let expression = "first(fiRst(FIRST(my,2,3),2,3),2,3)"; 417 | let result = parse_exec_expr(expression, &funcs, &values, Rc::new(null_op)); 418 | assert_eq!(result, "value"); 419 | 420 | let expression = "fiRst(my,2,3) - 1"; 421 | let result = parse_exec_expr(expression, &funcs, &values, Rc::new(null_op)); 422 | assert_eq!(result, ""); 423 | } 424 | 425 | #[test_case("true && false" => "false")] 426 | #[test_case("false || true" => "true")] 427 | #[test_case("3" => "3")] 428 | #[test_case("1+2" => "3")] 429 | #[test_case("1-1" => "0")] 430 | #[test_case("1 == 1" => "true")] 431 | #[test_case("1 != 1" => "false")] 432 | #[test_case("1/2" => "0.5")] 433 | #[test_case("1-2/2" => "-0.5")] 434 | #[test_case("1-(3/3)" => "0")] 435 | #[test_case("1>42" => "false")] 436 | #[test_case("2 >= 2" => "true")] 437 | #[test_case("5>=2" => "true")] 438 | #[test_case("7<2" => "false")] 439 | #[test_case("9<=9" => "true")] 440 | #[test_case("42%3" => "0")] 441 | #[test_case("43 % 3" => "1")] 442 | #[test_case("NumberValue(\"3\")% 2" => "1")] 443 | #[test_case("3 % 2" => "1")] 444 | #[test_case("Exact(null, \"\")" => "true")] 445 | #[test_case("null" => "")] 446 | #[test_case("eXaCt(null, Concat(null, null))" => "true")] 447 | #[test_case("Exact(\"null\", \"null\")" => "true")] 448 | #[test_case(stringify!("test") => "test")] 449 | #[test_case("IIF(ISNULL(\"2345876\"), \"\", \"M\")" => "M")] 450 | #[test_case("IIF(ISNULL(76), \"\", \"M\")" => "M")] 451 | #[test_case("IsNull(null)" => "true")] 452 | #[test_case("IsNull(2)" => "false")] 453 | #[test_case("IsNull(\" \t \")" => "true")] 454 | #[test_case("IsNull(IsBlank(null))" => "false")] 455 | #[test_case("AreEquals(IsBlank(null), IsNull(null))" => "true")] 456 | #[test_case("AreEquals(IsBlank(42), IsNull(null))" => "false")] 457 | #[test_case("In(null, null)" => "false")] 458 | #[test_case("In(true, false, 42, false)" => "false")] 459 | #[test_case("In(true, 42, true, false)" => "true")] 460 | #[test_case("In(\"ok\", 42, true, \"ok\")" => "true")] 461 | #[test_case("In(42, 42, true, \"ok\")" => "true")] 462 | #[test_case("Like(42, 42)" => "true" )] 463 | #[test_case("ISLIKE(\"hello#world\", \"#\")" => "false" )] 464 | #[test_case("ISLIKE(\"hello#world\", \"%#%\")" => "true" )] 465 | #[test_case("Like(4242, \"4_42\")" => "true" )] 466 | #[test_case("Like(424, \"4_4%\")" => "true" )] 467 | #[test_case("Like(4242, \"%_42\")" => "true" )] 468 | #[test_case("Like(\"hooo%hooo_hoooo\", \"h%o%%h%o__%\")" => "true" )] 469 | #[test_case("InLike(42, 42)" => "true" )] 470 | #[test_case("InLike(42, 43, 42)" => "true" )] 471 | #[test_case("InLike(42, 43, 66)" => "false" )] 472 | #[test_case("InLike(\"event_ally%nt\", \"nope\", \"Eventually Consistant\")" => "true")] 473 | #[test_case("Concat(42, 42, true, \"ok\")" => "4242trueok")] 474 | #[test_case("Concatenate(null, \"42\", true, \"ok\", In(42, 3.14))" => "42trueokfalse")] 475 | #[test_case("Exact(null, Concat(null, null, null))" => "true")] 476 | #[test_case("Find(\"hello\", \"hello\")" => "1")] 477 | #[test_case("Find(\"world\", \"helloworld\")" => "6")] 478 | #[test_case("Find(\"not found\", \"helloworld\")" => "0")] 479 | #[test_case("Find(\"world\", \"Hello world\")" => "7")] 480 | #[test_case("Find(\"AAA\", \"AAA\")" => "1")] 481 | #[test_case("Find(\" BBB\", \" BBB\")" => "1")] 482 | #[test_case("Find(\"\\\"\", \"co\\\"co\")" => "3")] 483 | #[test_case("Find(\"\\t\", \"bo\\tbo\")" => "3")] 484 | #[test_case("Find(\"AbcD\", \"aBCd\")" => "1")] 485 | #[test_case("Find(\"C\", \"CCC\", 1)" => "1")] 486 | #[test_case("Find(\"C\", \"CCC\", 0)" => "1")] 487 | #[test_case("Find(\"C\", \"CCC\", 2)" => "2")] 488 | #[test_case("Find(\"C\", \"CCC\", 3)" => "3")] 489 | #[test_case("Find(\"Alpha\", \"Alphabet\")" => "1")] 490 | #[test_case("Substitute(\"abcEFG\", \"aBC\", \"A\")" => "AEFG")] 491 | #[test_case("Substitute(\"abcEFG\", \"CCC\", 3)" => "abcEFG")] 492 | #[test_case("Substitute(\"abababa\", \"a\", null)" => "bbb")] 493 | #[test_case("Substitute(\"abababa\", \"a\", \"0O\")" => "0Ob0Ob0Ob0O")] 494 | #[test_case("Fixed(2)" => "2.00")] 495 | #[test_case("Fixed(2, 2)" => "2.00")] 496 | #[test_case("Fixed(3.1416, 2)" => "3.14")] 497 | #[test_case("Fixed(3.1416, 3)" => "3.142")] 498 | #[test_case("Fixed(3.1416, 5)" => "3.14160")] 499 | #[test_case("Fixed(31415926.5359, 0, true)" => "31415927")] 500 | #[test_case("Fixed(31415926.5359, 0, false)" => "31,415,927")] 501 | #[test_case("Fixed(31415926.5359, 1, true)" => "31415926.5")] 502 | #[test_case("Fixed(31415926.5359, 1, false)" => "31,415,926.5")] 503 | #[test_case("Fixed(31415926.5359, 2, true)" => "31415926.54")] 504 | #[test_case("Fixed(31415926.5359, 2, false)" => "31,415,926.54")] 505 | #[test_case("Fixed(31415926.5359, 3, true)" => "31415926.536")] 506 | #[test_case("Fixed(31415926.5359, 3, false)" => "31,415,926.536")] 507 | #[test_case("Fixed(31415926.5359, 4, true)" => "31415926.5359")] 508 | #[test_case("Fixed(31415926.5359, 4, false)" => "31,415,926.5359")] 509 | #[test_case("Fixed(31415926.5359, 5, true)" => "31415926.53590")] 510 | #[test_case("Fixed(31415926.5359, 5, false)" => "31,415,926.53590")] 511 | #[test_case("Fixed(31416, 0, false)" => "31,416")] 512 | #[test_case("Fixed(31416, 0)" => "31416")] 513 | #[test_case("Fixed(0.42, 3, false)" => "0.420")] 514 | #[test_case("Left(\"Left\", 2)" => "Le")] 515 | #[test_case("Left(\"Left\", -2)" => "")] 516 | #[test_case("Left(\"Left\", 42)" => "Left")] 517 | #[test_case("Right(\"Right\", 3)" => "ght")] 518 | #[test_case("Right(\"Right\", -2)" => "")] 519 | #[test_case("Mid(\"Mid\", 1, 1)" => "M")] 520 | #[test_case("Mid(\"Mid\", 0, 2)" => "Mi")] 521 | #[test_case("Mid(\"Rose et blanc,Rose et gris,Rose et creme,LibertyRo\", 1, (50 - 1 + 1))" => "Rose et blanc,Rose et gris,Rose et creme,LibertyRo")] 522 | #[test_case("Mid(\"Mid\", 3, 15)" => "d")] 523 | #[test_case("Mid(\"bcatag\", 2, 3)" => "cat")] 524 | #[test_case("Mid(\"bcatag\", 6, 3)" => "g")] 525 | #[test_case("Mid(\"bcatag\", 7, 3)" => "")] 526 | #[test_case("Len(null)" => "0")] 527 | #[test_case("Len(\" \")" => "1")] 528 | #[test_case("Len(\"12\")" => "2")] 529 | #[test_case("Len(3.14)" => "4")] 530 | #[test_case("Lower(3.14)" => "3.14")] 531 | #[test_case("Lower(\"A\")" => "a")] 532 | #[test_case("Lower(\"aBc\")" => "abc")] 533 | #[test_case("Upper(3.14)" => "3.14")] 534 | #[test_case("Upper(\"a\")" => "A")] 535 | #[test_case("Upper(\"AbC\")" => "ABC")] 536 | #[test_case("Trim(\"AbC\")" => "AbC")] 537 | #[test_case("Trim(\" \t AbCd\")" => "AbCd")] 538 | #[test_case("Trim(\" \t AbCd eE \t \")" => "AbCd eE")] 539 | #[test_case("FirstWord(\"once\")" => "once")] 540 | #[test_case("FirstWord(\"once upon\")" => "once")] 541 | #[test_case("FirstWord(\"a!time\")" => "a")] 542 | #[test_case("FirstWord(\"w.at\")" => "w")] 543 | #[test_case("FirstWord(\"you\tou\")" => "you")] 544 | #[test_case("FirstSentence(\"once upon. a time\")" => "once upon.")] 545 | #[test_case("FirstSentence(\"toto\")" => "toto")] 546 | #[test_case("FirstSentence(\"toto. Wat ?\")" => "toto.")] 547 | #[test_case("FirstSentence(\"Wat ? toto.\")" => "Wat ?")] 548 | #[test_case("Split(\"a,b,c,d,e\", \",\", 0)" => "a")] 549 | #[test_case("Split(\"azbzczdze\", \"z\", 0)" => "a")] 550 | #[test_case("Split(\"a,b,c,d,e\", \",\", 2)" => "c")] 551 | #[test_case("Split(\"a,b,c,d,e\", \",\", 42)" => "")] 552 | #[test_case("Split(\"1,2,3,4,5,6,7\", \",\", 90)" => "")] 553 | #[test_case("Mid(\"abcdefghij\", 1, 2)" => "ab")] 554 | #[test_case("Mid(\"abcdefghij\", 2, 2)" => "bc")] 555 | #[test_case("Mid(\"abcdefghij\", 2, 3)" => "bcd")] 556 | #[test_case("Mid(\"abcdefghij\", -2, 3)" => "abc")] 557 | #[test_case("Mid(\"abcdefghij\", 0, 0)" => "")] 558 | #[test_case("Mid(\"abcdefghij\", 4, 42)" => "defghij")] 559 | #[test_case("Mid(\"abcdefghij\", 4, \"7\")" => "defghij")] 560 | #[test_case("Mid(\"abcdefghij\", -42, 42)" => "abcdefghij")] 561 | #[test_case("Mid(\"abcdefghij\", \"1\", 10)" => "abcdefghij")] 562 | #[test_case("Concat(Mid(Left(\"abcdefghij\", 3), \"2\", 1), NumberValue(\"3\"))" => "b3")] 563 | #[test_case("NumberValue(\"2\")" => "2")] 564 | #[test_case("NumberValue(\"2.3\")" => "2.3")] 565 | #[test_case("NumberValue(\"2z4\", \"z.\")" => "2.4")] 566 | #[test_case("Text(\"toto\")" => "toto")] 567 | #[test_case("Capitalize(\"\")" => "")] 568 | #[test_case("Capitalize(\"toto\")" => "Toto")] 569 | #[test_case("Capitalize(\" once Upon a Time. in ? america ? Already upper case ... y ! i i . \")" => " Once Upon a Time. In ? America ? Already upper case ... Y ! I i . ")] 570 | #[test_case("StartsWith(\"toto\", \"t\")" => "true")] 571 | #[test_case("StartsWith(null, \"t\")" => "false")] 572 | #[test_case("StartsWith(\"toto\", null)" => "true")] 573 | #[test_case("StartsWith(\"toto\", \"tota\")" => "false")] 574 | #[test_case("StartsWith(\"toto\", \"totoa\")" => "false")] 575 | #[test_case("StartsWith(\"abc\", \"aBC\")" => "true")] 576 | #[test_case("EndsWith(\"toto\", \"o\")" => "true")] 577 | #[test_case("EndsWith(null, \"t\")" => "false")] 578 | #[test_case("EndsWith(\"toto\", null)" => "true")] 579 | #[test_case("EndsWith(\"toto\", \"aoto\")" => "false")] 580 | #[test_case("EndsWith(\"toto\", \"atoto\")" => "false")] 581 | #[test_case("EndsWith(\"abc\", \"aBC\")" => "true")] 582 | #[test_case("ReplaceEquals(\"aBc\", null, \"aaa\", NumberValue(\"BREAK\"), \"abc\", 42, NumberValue(\"BREAK\"), NumberValue(\"BREAK\"))" => "42")] 583 | #[test_case("ReplaceLike(\"aBc\", null, \"aaa\", NumberValue(\"BREAK\"), \"%c\", 42, NumberValue(\"BREAK\"), NumberValue(\"BREAK\"))" => "42")] 584 | #[test_case("And(\"TrUe\", \"1\", 1, true, true)" => "true")] 585 | #[test_case("And(\"true\", 0, true, NumberValue(\"BREAK\"))" => "false")] 586 | #[test_case("Or(true, NumberValue(\"BREAK\"))" => "true")] 587 | #[test_case("Or(false, 0, \"anything\", Sum(2, -1), NumberValue(\"BREAK\"))" => "true")] 588 | #[test_case("Not(false)" => "true")] 589 | #[test_case("Not(1)" => "false")] 590 | #[test_case("Not(\"not\")" => "true")] 591 | #[test_case("Xor(true, true)" => "false")] 592 | #[test_case("Xor(true, false)" => "true")] 593 | #[test_case("Xor(false, true)" => "true")] 594 | #[test_case("Xor(false, false)" => "false")] 595 | #[test_case("Iif(true, 42, NumberValue(\"BREAK\"))" => "42")] 596 | #[test_case("Iif(false, NumberValue(\"BREAK\"), 42)" => "42")] 597 | // #[test_case("IIF(NUMBERVALUE(\"\") >= NUMBERVALUE(150), 0, 6.9)" => "6.9")] 598 | // #[test_case("IIF(NUMBERVALUE(\"\") < NUMBERVALUE(1), \"Hors Stock\", \"En stock\")" => "En stock")] 599 | #[test_case("Abs(2)" => "2")] 600 | #[test_case("Abs(-32)" => "32")] 601 | #[test_case("Abs(-0)" => "0")] 602 | #[test_case("Product(2, 3, 0)" => "0")] 603 | #[test_case("Product(1, 2, 3)" => "6")] 604 | #[test_case("Product(1, 2, 3, \"-1\")" => "-6")] 605 | #[test_case("Divide(2, 1)" => "2")] 606 | #[test_case("Divide(3, -2)" => "-1.5")] 607 | #[test_case("Subtract(3, 2)" => "1")] 608 | #[test_case("Subtract(-3, 5)" => "-8")] 609 | #[test_case("Modulo(3, 2)" => "1")] 610 | #[test_case("Mod(-5, 2)" => "-1")] 611 | #[test_case("Mod(-21, 4)" => "-1")] 612 | #[test_case("Mod(7.5, -3)" => "1.5")] 613 | #[test_case("Round(3.1416, -1)" => "3")] 614 | #[test_case("Round(3.1416, 0)" => "3")] 615 | #[test_case("Round(3.1416, 1)" => "3.1")] 616 | #[test_case("Round(3.1416, 2)" => "3.14")] 617 | #[test_case("Round(3.1416, 3)" => "3.142")] 618 | #[test_case("Round ( 3.1416, 4 )" => "3.1416")] 619 | #[test_case("Round(\"3.1416\", 5)" => "3.1416")] 620 | #[test_case("69.9 / NUMBERVALUE(1.2)" => "58.25")] 621 | #[test_case("NUMBERVALUE(69.9) / NUMBERVALUE(1.2)" => "58.25")] 622 | #[test_case("ROUND(NUMBERVALUE(69.9) / NUMBERVALUE(1.2), 2)" => "58.25")] 623 | #[test_case("GreaterThan(2, 3)" => "false")] 624 | #[test_case("Gt(3, 3)" => "false")] 625 | #[test_case("Gt(3, -1)" => "true")] 626 | #[test_case("LowerThan(2, 5)" => "true")] 627 | #[test_case("Lt(3, 3)" => "false")] 628 | #[test_case("Lt(3, -1)" => "false")] 629 | #[test_case("GreaterThanOrEqual(2, 3)" => "false")] 630 | #[test_case("Gtoe(3, 3)" => "true")] 631 | #[test_case("Gtoe(3, -1)" => "true")] 632 | #[test_case("LowerThanOrEqual(2, 5)" => "true")] 633 | #[test_case("Ltoe(3, 3)" => "true")] 634 | #[test_case("Ltoe(3, -1)" => "false")] 635 | #[test_case("Date(\"1996-12-19T16:39:57-08:00\")" => "12/20/1996 00:39:57")] 636 | #[test_case("Date(\"1996-12-07T16:39:57Z\")" => "12/07/1996 16:39:57")] 637 | #[test_case("Date(\"1996-12-07 16:39:57\")" => "12/07/1996 16:39:57")] 638 | #[test_case("Date(\" 1996/12/07 16:39:58 \")" => "12/07/1996 16:39:58")] 639 | #[test_case("Date(\"1996-12-07\")" => "12/07/1996 00:00:00")] 640 | #[test_case("Year(\"1996-12-19T16:39:57-08:00\")" => "1996")] 641 | #[test_case("Month(\"1996-12-19T16:39:57-08:00\")" => "12")] 642 | #[test_case("Day(\"1996-12-19T16:39:57-08:00\")" => "20")] 643 | #[test_case("Day(\"1996-12-07T16:39:57Z\")" => "7")] 644 | #[test_case("DateDiff(\"1996-12-07T16:39:58Z\", \"1996-12-07T16:39:57Z\")" => "00:00:01")] 645 | #[test_case("DateDiff(\"1996-12-07T16:39:57Z\", \"1996-12-02T16:40:52Z\")" => "4.23:59:05")] 646 | #[test_case("DateDiff(\"1996-12-07T16:39:57Z\", \"1996-12-09T16:39:57Z\")" => "-2.00:00:00")] 647 | #[test_case("DateAddHours(Date(\"1996-12-19T16:39:57-08:00\"), -8)" => "12/19/1996 16:39:57")] 648 | #[test_case("DateAddHours(\"1996-12-19T16:39:57-08:00\", -8.5)" => "12/19/1996 16:09:57")] 649 | #[test_case("DateAddDays(\"1996-12-19T16:39:57-08:00\", 1.5)" => "12/21/1996 12:39:57")] 650 | #[test_case("DateAddDays(\"1996-12-19T16:39:57-08:00\", -1.5)" => "12/18/1996 12:39:57")] 651 | #[test_case("DateAddMonths(\"1996-12-19T16:39:57-08:00\", 16)" => "04/20/1998 00:39:57")] 652 | #[test_case("DateAddMonths(\"1996-12-19T16:39:57-08:00\", -5)" => "07/20/1996 00:39:57")] 653 | #[test_case("DateAddMonths(\"1996-12-19T16:39:57-08:00\", -15)" => "09/20/1995 00:39:57")] 654 | #[test_case("LocalDate(\"1996-12-19T16:39:57Z\", \"Romance Standard Time\")" => "12/19/1996 17:39:57")] 655 | #[test_case("LocalDate(\"1996-07-23T16:39:57Z\", \"Romance Standard Time\")" => "07/23/1996 18:39:57")] 656 | #[test_case("DateFormat(\"1996-12-19T16:39:57Z\")" => "1996-12-19 16:39:57.000")] 657 | #[test_case("DateFormat(\"1996-12-19T16:39:57.123Z\")" => "1996-12-19 16:39:57.123")] 658 | #[test_case("DateFormat(\"2021-12-19T16:39:57.123Z\", \"yyyy-MMM-mm\")" => "2021-Dec-39")] 659 | #[test_case("DateFormat(\"2021-12-19T16:39:57.123Z\", \"yyyy-MMMM-dd\")" => "2021-December-19")] 660 | #[test_case("DateFormat(\"2021-12-19T16:39:57.123Z\", \"h:m:s\")" => " 4:39:57")] 661 | #[test_case("DateFormat(\"2021-12-19T16:39:57.123Z\", \"H:m:s\")" => "16:39:57")] 662 | #[test_case("SUBSTITUTE(null, \"\", \"hop\")" => "hop")] 663 | #[test_case("SUBSTITUTE(\"\", \"\", \"hip\")" => "hip")] 664 | #[test_case("SUBSTITUTE(\"ha\", \"\", \"hip\")" => "ha")] 665 | #[test_case("SUBSTITUTE(\"\", \"ho\", \"hip\")" => "")] 666 | #[test_case("IIF(AreEquals(NUMBERVALUE(LEN(\"123456789012\")), NUMBERVALUE(12)), CONCATENATE(\"123456789012\", \"0\"), \"nope\")" => "1234567890120")] 667 | #[test_case("IIF(NUMBERVALUE(LEN(\"123456789012\")) == NUMBERVALUE(12), CONCATENATE(\"123456789012\", \"0\"), \"nope\")" => "1234567890120")] 668 | #[test_case("Date(\"2020-07-14 13:00:00.000\")" => "07/14/2020 13:00:00")] 669 | #[test_case("Date(\"2020-07-14 13:00:00\")" => "07/14/2020 13:00:00")] 670 | #[test_case("Date(\"2020-07-14 08:18\")" => "07/14/2020 08:18:00")] 671 | #[test_case("DateFormat(LocalDate(\"2020-03-04\"), \"yyyy-MM-dd HH\")" => "2020-03-04 01")] 672 | // #[test_case("NowSpecificTimeZone(\"Saratov Standard Time\")" => "16:39:57")] 673 | // #[test_case("Today()" => "---")] 674 | // #[test_case("Time()" => "---")] 675 | fn execute_some_real_world_expression(expression: &str) -> String { 676 | let funcs = get_functions(); 677 | let op = f_operators; 678 | parse_exec_expr(expression, &funcs, &IdentifierValues::new(), Rc::new(op)) 679 | } 680 | 681 | #[test] 682 | fn non_ascii_tests() { 683 | assert_eq!(parse_exec_expr_with_defaults("Substitute(\"ta mère !\", \"mère !\", \"frêre ?\")"), "ta frêre ?"); 684 | assert_eq!(parse_exec_expr_with_defaults("Len(\"crème\")"), "5"); 685 | assert_eq!(parse_exec_expr_with_defaults("Mid(\"crème\", 3, 1)"), "è"); 686 | assert_eq!(parse_exec_expr_with_defaults("Mid(\"crème\", 1, 3)"), "crè"); 687 | assert_eq!(parse_exec_expr_with_defaults("Mid(\"crème\", 3, 2)"), "èm"); 688 | assert_eq!(parse_exec_expr_with_defaults("Left(\"crème\", 3)"), "crè"); 689 | assert_eq!(parse_exec_expr_with_defaults("Right(\"crème\", 3)"), "ème"); 690 | assert_eq!(parse_exec_expr_with_defaults("Right(\"crème\", 2)"), "me"); 691 | } 692 | 693 | fn parse_exec_expr_with_defaults<'a>(expression: &'a str) -> String { 694 | parse_exec_expr(expression, &get_functions(), &IdentifierValues::new(), Rc::new(f_operators)) 695 | } 696 | 697 | fn parse_exec_expr<'a>(expression: &'a str, funcs: &FunctionImplList, values: &IdentifierValues, operators: BinaryOperatorsImplRc) -> String { 698 | let expr = parse_expr(expression).unwrap(); 699 | let expr = prepare_expr_and_identifiers(expr, funcs, operators); 700 | let result = exec_expr(&expr.expr, values).unwrap(); 701 | result.to_string() 702 | } 703 | 704 | #[test] 705 | fn deterministic_operations() { 706 | assert_eq!(Deterministic + Deterministic, Deterministic); 707 | assert_eq!(NonDeterministic + Deterministic, NonDeterministic); 708 | assert_eq!(Deterministic + NonDeterministic, NonDeterministic); 709 | assert_eq!(NonDeterministic + NonDeterministic, NonDeterministic); 710 | let mut d = FunctionDeterminism::default(); 711 | assert_eq!(d, Deterministic); 712 | d += Deterministic; 713 | assert_eq!(d, Deterministic); 714 | d += NonDeterministic; 715 | assert_eq!(d, NonDeterministic); 716 | d += NonDeterministic; 717 | assert_eq!(d, NonDeterministic); 718 | d += Deterministic; 719 | assert_eq!(d, NonDeterministic); 720 | d += Deterministic; 721 | assert_eq!(d, NonDeterministic); 722 | } 723 | 724 | #[test_case("Substitute(\"ta mere !\", \"mere !\", \"frere ?\")" => true)] 725 | #[test_case("Substitute(\"ta mere !\", \"mere !\", Now(42))" => false)] 726 | #[test_case("Time(42)" => false)] 727 | #[test_case("Upper(Today(42))" => false)] 728 | #[test_case("Upper(\"\")" => true)] 729 | #[test_case("Upper(Sum(2, 4))" => true)] 730 | #[test_case("Upper(Sum(2, now(42)))" => false)] 731 | #[test_case("Upper(Unkkkkkknown(2, now(42)))" => true)] // this one will always fail before calling now(), so it's determinist 732 | #[test_case("now(42)" => false)] 733 | #[test_case("1 + now(42)" => false)] 734 | #[test_case("LocalDate(\"2019-08-06\")" => true)] 735 | #[test_case("DateFormat(LocalDate(\"2023-09-19\"), \"yyyy-MM-dd HH:mm:ss\")" => true)] 736 | #[test_case("Upper(\"\") + 2" => true)] 737 | fn deterministic_or_not(expression: &str) -> bool { 738 | let expr = parse_expr(expression).unwrap(); 739 | let expr = prepare_expr_and_identifiers(expr, &get_functions(), Rc::new(null_op)); 740 | expr.determinism == Deterministic 741 | } 742 | 743 | #[test] 744 | fn partial_eq_test() { 745 | assert_eq!(AssocOp::Multiply, AssocOp::Multiply); 746 | assert_eq!(Expr::Num(dec!(1)), Expr::Num(dec!(1))); 747 | assert_eq!( 748 | Expr::BinaryOperator(RcExpr::new(Expr::Num(dec!(3))), RcExpr::new(Expr::Num(dec!(5))), AssocOp::Divide), 749 | Expr::BinaryOperator(RcExpr::new(Expr::Num(dec!(3))), RcExpr::new(Expr::Num(dec!(5))), AssocOp::Divide) 750 | ); 751 | } 752 | 753 | fn null_op(l: RcExpr, r: RcExpr, op: AssocOp, _: &IdentifierValues) -> ExprFuncResult { 754 | dbg!(l, op, r, "RETURNS Null (null_op)"); 755 | Ok(ExprResult::Null) 756 | } 757 | 758 | // PLEASE TEST WITH: 759 | // cargo test --release -- --nocapture fast_try_thousands 760 | // PERFORMANCES ARE ABOUT 7 times better than : 761 | // cargo test -- --nocapture fast_try_thousands 762 | #[test] 763 | #[ignore] // remove that to execute 764 | fn fast_try_thousands() { 765 | let s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 766 | let expr = parse_expr("test").unwrap(); 767 | let expr = prepare_expr_and_identifiers(expr, &get_functions(), Rc::new(f_operators)); 768 | let now = Instant::now(); 769 | 770 | for i in 0..1000000 { 771 | let mut values = IdentifierValues::new(); 772 | let test_value = format!("{}{}", s, i); 773 | // let test_value2 = test_value.clone(); 774 | values.insert("test".into(), Box::new(move || Rc::new(format!("{}{}", s, i)))); 775 | let result = exec_expr(&expr.expr, &values).unwrap(); 776 | assert_eq!(result.to_string(), test_value); 777 | } 778 | 779 | dbg!(now.elapsed()); 780 | } 781 | } 782 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use crate::expressions::*; 2 | 3 | use once_cell::sync::Lazy; 4 | use std::cell::RefCell; 5 | use std::ffi::{CStr, CString}; 6 | use std::os::raw::c_char; 7 | use std::ptr; 8 | use std::rc::Rc; 9 | use std::slice; 10 | use std::vec::Vec; 11 | use unicase::UniCase; 12 | 13 | fn str_from_c_char_ptr<'a>(s: *const c_char) -> Result<&'a str, std::str::Utf8Error> { 14 | unsafe { 15 | assert!(!s.is_null()); 16 | CStr::from_ptr(s) 17 | } 18 | .to_str() 19 | } 20 | 21 | fn string_from_c_char_ptr(s: *const c_char) -> Result { 22 | Ok(str_from_c_char_ptr(s)?.to_string()) 23 | } 24 | 25 | static UTF16: Lazy<&'static encoding_rs::Encoding> = Lazy::new(|| { 26 | let encoding = encoding_rs::Encoding::for_label("UTF-16".as_bytes()).unwrap(); 27 | encoding 28 | }); 29 | 30 | fn string_from_csharp(s: *const c_char) -> String { 31 | let c_str = unsafe { 32 | assert!(!s.is_null()); 33 | CStr::from_ptr(s) 34 | }; 35 | let r_str = c_str.to_str().unwrap(); 36 | r_str.into() 37 | } 38 | 39 | #[repr(C)] 40 | #[derive(Debug, Copy, Clone)] 41 | pub struct FFIParseResult { 42 | is_error: bool, 43 | error: *mut c_char, 44 | content: *mut ExprAndIdentifiers, 45 | } 46 | 47 | #[no_mangle] 48 | extern "C" fn ffi_parse_and_prepare_expr(expression: *const c_char) -> FFIParseResult { 49 | let r_str = string_from_csharp(expression); 50 | let result = parse_expr(&r_str); 51 | match result { 52 | Err(err) => FFIParseResult { 53 | is_error: true, 54 | error: CString::new(err).unwrap().into_raw(), 55 | content: ptr::null_mut(), 56 | }, 57 | Ok(expr) => { 58 | let funcs = crate::functions::get_functions(); 59 | let expr = prepare_expr_and_identifiers(expr, &funcs, Rc::new(crate::functions::f_operators)); 60 | FFIParseResult { 61 | is_error: false, 62 | error: ptr::null_mut(), 63 | content: Box::into_raw(Box::new(expr)), 64 | } 65 | } 66 | } 67 | } 68 | 69 | #[no_mangle] 70 | extern "C" fn ffi_get_identifiers(ptr: *mut ExprAndIdentifiers) -> *mut c_char { 71 | let expr = unsafe { 72 | assert!(!ptr.is_null()); 73 | &mut *ptr 74 | }; 75 | 76 | let identifiers_separated = expr.identifiers_names.iter().cloned().collect::>().join("|"); 77 | let c_str_result = CString::new(identifiers_separated).unwrap(); 78 | c_str_result.into_raw() 79 | } 80 | 81 | #[no_mangle] 82 | extern "C" fn ffi_is_deterministic(ptr: *mut ExprAndIdentifiers) -> bool { 83 | let expr = unsafe { 84 | assert!(!ptr.is_null()); 85 | &mut *ptr 86 | }; 87 | 88 | match expr.determinism { 89 | FunctionDeterminism::Deterministic => true, 90 | FunctionDeterminism::NonDeterministic => false, 91 | } 92 | } 93 | 94 | #[repr(C)] 95 | #[derive(Debug)] 96 | pub struct IdentifierKeyValue { 97 | key: *const c_char, 98 | value: *const c_char, 99 | } 100 | 101 | #[repr(C)] 102 | #[derive(Debug, Copy, Clone)] 103 | pub struct FFIExecResult { 104 | is_error: bool, 105 | content: *mut c_char, 106 | } 107 | 108 | struct IdentifierStringValueLazyGetter { 109 | value_ptr: *const c_char, 110 | value: Option>, 111 | } 112 | 113 | impl IdentifierStringValueLazyGetter { 114 | fn new(value_ptr: *const c_char) -> IdentifierStringValueLazyGetter { 115 | IdentifierStringValueLazyGetter { value_ptr, value: None } 116 | } 117 | 118 | fn get_value(&mut self) -> Rc { 119 | match &self.value { 120 | Some(rc) => rc.clone(), 121 | None => { 122 | let s = string_from_csharp(self.value_ptr); 123 | let rc = Rc::new(s); 124 | self.value = Some(rc.clone()); 125 | rc 126 | } 127 | } 128 | } 129 | } 130 | 131 | #[no_mangle] 132 | extern "C" fn ffi_exec_expr(ptr: *mut ExprAndIdentifiers, identifier_values: *const IdentifierKeyValue, identifier_values_len: usize) -> FFIExecResult { 133 | let expr = unsafe { 134 | assert!(!ptr.is_null()); 135 | &mut *ptr 136 | }; 137 | 138 | let vals = unsafe { 139 | assert!(!identifier_values.is_null()); 140 | slice::from_raw_parts(identifier_values, identifier_values_len) 141 | }; 142 | 143 | let mut values = IdentifierValues::new(); 144 | for ikv in vals.iter() { 145 | let k = string_from_c_char_ptr(ikv.key).unwrap(); 146 | let lazy_getter = IdentifierStringValueLazyGetter::new(ikv.value); 147 | let lazy_refcell = RefCell::new(lazy_getter); 148 | let get_v = Box::new(move || lazy_refcell.borrow_mut().get_value()); 149 | values.insert(k, get_v); 150 | } 151 | 152 | let result = exec_expr(&expr.expr, &mut values); 153 | 154 | match result { 155 | Ok(r) => FFIExecResult { 156 | is_error: false, 157 | content: CString::new(r.to_string()).unwrap().into_raw(), 158 | }, 159 | Err(e) => FFIExecResult { 160 | is_error: true, 161 | content: CString::new(e).unwrap().into_raw(), 162 | }, 163 | } 164 | } 165 | 166 | #[no_mangle] 167 | extern "C" fn ffi_free_expr(ptr: *mut ExprAndIdentifiers) { 168 | if ptr.is_null() { 169 | return; 170 | } 171 | unsafe { 172 | Box::from_raw(ptr); 173 | } 174 | } 175 | 176 | #[no_mangle] 177 | extern "C" fn ffi_free_cstring(ptr: *mut c_char) { 178 | if ptr.is_null() { 179 | return; 180 | } 181 | unsafe { CString::from_raw(ptr) }; 182 | } 183 | 184 | // ========================================= 185 | // ========================================= 186 | // TESTING 187 | // ========================================= 188 | // ========================================= 189 | 190 | #[no_mangle] 191 | extern "C" fn PassLPStr(s: *const c_char) { 192 | let c_str = unsafe { 193 | assert!(!s.is_null()); 194 | CStr::from_ptr(s) 195 | }; 196 | 197 | let r_str = c_str.to_str().unwrap(); 198 | dbg!("PassLPStr", r_str); 199 | } 200 | #[no_mangle] 201 | extern "C" fn PassLPWStr(s: *const c_char) { 202 | let c_str = unsafe { 203 | assert!(!s.is_null()); 204 | CStr::from_ptr(s) 205 | }; 206 | 207 | let r_str = c_str.to_str().unwrap(); 208 | dbg!("PassLPWStr", r_str); 209 | } 210 | #[no_mangle] 211 | extern "C" fn PassLPTStr(s: *const c_char) { 212 | let c_str = unsafe { 213 | assert!(!s.is_null()); 214 | CStr::from_ptr(s) 215 | }; 216 | 217 | let r_str = c_str.to_str().unwrap(); 218 | dbg!("PassLPTStr", r_str); 219 | } 220 | #[no_mangle] 221 | extern "C" fn PassLPUTF8Str(s: *const c_char) { 222 | let c_str = unsafe { 223 | assert!(!s.is_null()); 224 | CStr::from_ptr(s) 225 | }; 226 | 227 | let r_str = c_str.to_str().unwrap(); 228 | dbg!("PassLPUTF8Str", r_str); 229 | } 230 | #[no_mangle] 231 | extern "C" fn PassBStr(s: *const c_char) { 232 | let c_str = unsafe { 233 | assert!(!s.is_null()); 234 | CStr::from_ptr(s) 235 | }; 236 | 237 | let r_str = c_str.to_str().unwrap(); 238 | dbg!("PassBStr", r_str); 239 | } 240 | -------------------------------------------------------------------------------- /src/functions.rs: -------------------------------------------------------------------------------- 1 | use crate::expressions::*; 2 | use chrono::{prelude::*, Duration, TimeZone}; 3 | use chrono_tz::Tz; 4 | use num_format::{Locale, ToFormattedString}; 5 | use regex::{Regex, RegexBuilder}; 6 | use rust_decimal::prelude::*; 7 | use rust_decimal_macros::*; 8 | use std::collections::HashMap; 9 | use std::rc::Rc; 10 | use unicase::UniCase; 11 | 12 | fn get_rc_empty_string() -> Rc { 13 | Rc::new(String::with_capacity(0)) 14 | } 15 | 16 | fn exec_vec_is_null(params: &SliceRcExpr, values: &IdentifierValues) -> Result { 17 | match params.len() { 18 | 0 => Ok(true), 19 | 1 => exec_expr_is_null(params.get(0).unwrap(), values), 20 | _ => Err("is_null only takes 0 or 1 parameter".to_string()), 21 | } 22 | } 23 | 24 | fn exec_expr_is_null(expr: &RcExpr, values: &IdentifierValues) -> Result { 25 | let res = exec_expr(expr, values)?; 26 | Ok(expr_result_is_null(&res)) 27 | } 28 | 29 | fn expr_result_is_null(result: &ExprResult) -> bool { 30 | match result { 31 | ExprResult::Null => true, 32 | ExprResult::Str(s) => s.is_empty() || s.chars().all(is_space), 33 | _ => false, 34 | } 35 | } 36 | 37 | fn results_are_equals(left: &ExprResult, right: &ExprResult) -> bool { 38 | match (left, right) { 39 | (ExprResult::Null, _) => false, 40 | (_, ExprResult::Null) => false, 41 | _ => left == right, 42 | } 43 | } 44 | 45 | fn result_to_string(expr: &ExprResult) -> Result, String> { 46 | if expr.is_final() { 47 | Ok(expr.to_rc_string()) 48 | } else { 49 | Err("Can't change this expression to string".to_string()) 50 | } 51 | } 52 | 53 | fn exec_expr_to_string(expr: &RcExpr, values: &IdentifierValues) -> Result, String> { 54 | let res = exec_expr(expr, values)?; 55 | result_to_string(&res) 56 | } 57 | 58 | fn exec_expr_to_num(expr: &RcExpr, values: &IdentifierValues, decimal_separator: Option) -> Result { 59 | let res = exec_expr(expr, values)?; 60 | if let ExprResult::Num(n) = res { 61 | Ok(n) 62 | } else { 63 | let mut s = exec_expr_to_string(expr, values)?.to_string(); 64 | // if s.is_empty() { 65 | // return Ok(dec!(0)); 66 | // } 67 | if let Some(c) = decimal_separator { 68 | s = s.replace(c, ".") 69 | } else { 70 | // ok it's dummy but works in our use cases 71 | if s.contains(",") && !s.contains(".") { 72 | s = s.replace(",", ".") 73 | } 74 | } 75 | let n: ExprDecimal = s.parse().or_else(|_| Err(format!("The value '{}' is not a number.", exec_expr_to_string(expr, values)?)))?; 76 | Ok(n) 77 | } 78 | } 79 | 80 | fn exec_expr_to_float(expr: &RcExpr, values: &IdentifierValues, decimal_separator: Option) -> Result { 81 | let num = exec_expr_to_num(expr, values, decimal_separator)?; 82 | num.to_f64().ok_or_else(|| "Error casting value to float.".to_string()) 83 | } 84 | 85 | // fn exec_expr_to_int(expr: &RcExpr, values: &IdentifierValues, decimal_separator: Option) -> Result { 86 | // let num = exec_expr_to_num(expr, values, decimal_separator)?; 87 | // num.to_i64().ok_or_else(|| "Error casting value to integer".to_string()) 88 | // } 89 | 90 | fn exec_expr_to_int(expr: &RcExpr, values: &IdentifierValues) -> Result { 91 | let res = exec_expr(expr, values)?; 92 | match &res { 93 | ExprResult::Num(n) => Ok(n.to_isize().ok_or_else(|| "Error casting value to integer".to_string())?), 94 | ExprResult::Str(s) => Ok(s.parse::().or_else(|_| Err(format!("The value '{}' is not a integer.", s)))?), 95 | expr => Err(format!("The value '{}' is not a number, nor a string.", expr)), 96 | } 97 | } 98 | 99 | fn exec_expr_to_bool(expr: &RcExpr, values: &IdentifierValues) -> Result { 100 | lazy_static! { 101 | static ref TRUE_STRING: Regex = RegexBuilder::new("^\\s*(true|1)\\s*$").case_insensitive(true).build().unwrap(); 102 | } 103 | let res = exec_expr(expr, values)?; 104 | match &res { 105 | ExprResult::Boolean(b) => Ok(*b), 106 | ExprResult::Num(n) => Ok(*n == dec!(1)), 107 | ExprResult::Str(s) => Ok(TRUE_STRING.is_match(&*s)), 108 | _ => Ok(false), 109 | } 110 | } 111 | 112 | fn exec_expr_to_date_no_defaults(expr: &RcExpr, values: &IdentifierValues) -> Result { 113 | exec_expr_to_date(expr, values, false, false, false, false, false, false) 114 | } 115 | 116 | fn exec_expr_to_date( 117 | expr: &RcExpr, 118 | values: &IdentifierValues, 119 | default_year: bool, 120 | default_month: bool, 121 | default_day: bool, 122 | default_hour: bool, 123 | default_minute: bool, 124 | default_second: bool, 125 | ) -> Result { 126 | let res = exec_expr(expr, values)?; 127 | let mut date_time = match &res { 128 | ExprResult::Date(d) => *d, 129 | e => { 130 | let text = result_to_string(&e)?.trim().to_string(); 131 | // text.parse::>().map_err(|e| format!("{}", e))?.naive_utc() 132 | match text.parse::>() { 133 | Ok(dt) => return Ok(dt.naive_utc()), 134 | Err(_err) => { 135 | //dbg!(&text, _err); 136 | } 137 | } 138 | match DateTime::parse_from_rfc2822(&text) { 139 | Ok(dt) => return Ok(dt.naive_utc()), 140 | Err(_err) => { 141 | //dbg!(&text, _err); 142 | } 143 | } 144 | match text.parse::() { 145 | Ok(dt) => return Ok(dt), 146 | Err(_err) => { 147 | //dbg!(&text, _err); 148 | } 149 | } 150 | match NaiveDateTime::parse_from_str(&text, "%Y-%m-%d %H:%M:%S") { 151 | Ok(dt) => return Ok(dt), 152 | Err(_err) => { 153 | //dbg!(&text, _err); 154 | } 155 | } 156 | match NaiveDateTime::parse_from_str(&text, "%Y/%m/%d %H:%M:%S") { 157 | Ok(dt) => return Ok(dt), 158 | Err(_err) => { 159 | //dbg!(&text, _err); 160 | } 161 | } 162 | match NaiveDateTime::parse_from_str(&text, "%Y-%m-%d %H:%M:%S%.f") { 163 | Ok(dt) => return Ok(dt), 164 | Err(_err) => { 165 | //dbg!(&text, _err); 166 | } 167 | } 168 | match NaiveDateTime::parse_from_str(&text, "%Y/%m/%d %H:%M:%S%.f") { 169 | Ok(dt) => return Ok(dt), 170 | Err(_err) => { 171 | //dbg!(&text, _err); 172 | } 173 | } 174 | match NaiveDateTime::parse_from_str(&text, "%Y-%m-%d %H:%M") { 175 | Ok(dt) => return Ok(dt), 176 | Err(_err) => { 177 | //dbg!(&text, _err); 178 | } 179 | } 180 | match NaiveDateTime::parse_from_str(&text, "%Y/%m/%d %H:%M") { 181 | Ok(dt) => return Ok(dt), 182 | Err(_err) => { 183 | //dbg!(&text, _err); 184 | } 185 | } 186 | match text.parse::() { 187 | Ok(dt) => return Ok(dt.and_hms(0, 0, 0)), 188 | Err(_err) => { 189 | //dbg!(&text, _err); 190 | } 191 | } 192 | Err(format!("The value '{}' is not a date.", text))? 193 | } 194 | }; 195 | 196 | if default_year { 197 | date_time = date_time.with_year(1).unwrap(); 198 | } 199 | if default_month { 200 | date_time = date_time.with_month(1).unwrap(); 201 | } 202 | if default_day { 203 | date_time = date_time.with_day(1).unwrap(); 204 | } 205 | if default_hour { 206 | date_time = date_time.with_hour(1).unwrap(); 207 | } 208 | if default_minute { 209 | date_time = date_time.with_minute(1).unwrap(); 210 | } 211 | if default_second { 212 | date_time = date_time.with_second(1).unwrap(); 213 | } 214 | Ok(date_time) 215 | } 216 | 217 | fn assert_exact_params_count(params: &SliceRcExpr, count: usize, f_name: &str) -> Result<(), String> { 218 | if params.len() == count { 219 | Ok(()) 220 | } else { 221 | Err(format!("Function {} should have exactly {} parameters", f_name, count).to_string()) 222 | } 223 | } 224 | 225 | fn assert_max_params_count(params: &SliceRcExpr, count: usize, f_name: &str) -> Result<(), String> { 226 | if params.len() <= count { 227 | Ok(()) 228 | } else { 229 | Err(format!("Function {} should have no more than {} parameters", f_name, count).to_string()) 230 | } 231 | } 232 | 233 | fn assert_min_params_count(params: &SliceRcExpr, count: usize, f_name: &str) -> Result<(), String> { 234 | if params.len() >= count { 235 | Ok(()) 236 | } else { 237 | Err(format!("Function {} should have {} parameters or more", f_name, count).to_string()) 238 | } 239 | } 240 | 241 | fn assert_between_params_count(params: &SliceRcExpr, count_min: usize, count_max: usize, f_name: &str) -> Result<(), String> { 242 | let len = params.len(); 243 | if len < count_min || len > count_max { 244 | Err(format!("Function {} should have between {} and {} parameters", f_name, count_min, count_max).to_string()) 245 | } else { 246 | Ok(()) 247 | } 248 | } 249 | 250 | /**********************************/ 251 | /* Regex helpers */ 252 | /**********************************/ 253 | 254 | // use cached::proc_macro::cached; 255 | // use cached::SizedCache; 256 | 257 | // #[cached(type = "SizedCache>", create = "{ SizedCache::with_size(1000) }", convert = r#"{ search_pattern.into() }"#)] 258 | fn make_case_insensitive_search_regex(search_pattern: &str) -> Result { 259 | let search_pattern = regex::escape(&search_pattern); 260 | let regex = RegexBuilder::new(&search_pattern).case_insensitive(true).build().map_err(|e| format!("{}", e))?; 261 | Ok(regex) 262 | } 263 | 264 | // #[cached(type = "SizedCache>", create = "{ SizedCache::with_size(1000) }", convert = r#"{ search_pattern.into() }"#)] 265 | fn make_case_insensitive_equals_regex(search_pattern: &str) -> Result { 266 | let search_pattern = regex::escape(&search_pattern); 267 | let search_pattern = format!("^{}$", search_pattern); 268 | let regex = RegexBuilder::new(&search_pattern).case_insensitive(true).build().map_err(|e| format!("{}", e))?; 269 | Ok(regex) 270 | } 271 | 272 | fn like_pattern_to_regex_pattern(like_pattern: &str) -> String { 273 | let mut result = String::new(); 274 | result.push('^'); 275 | 276 | const ANY_MANY: &str = ".*"; 277 | const ANY_ONE: &str = ".{1}"; 278 | 279 | let mut previous_char = None; 280 | for c in like_pattern.chars() { 281 | match (previous_char, c) { 282 | (None, '%') | (None, '_') => { 283 | previous_char = Some(c); 284 | } 285 | (None, _) => { 286 | result.push(c); 287 | previous_char = Some(c); 288 | } 289 | (Some('%'), '%') | (Some('_'), '_') => { 290 | result.push(c); 291 | previous_char = None; 292 | } 293 | (Some('%'), _) => { 294 | result.push_str(ANY_MANY); 295 | if c != '%' && c != '_' { 296 | result.push(c); 297 | } 298 | previous_char = Some(c); 299 | } 300 | (Some('_'), _) => { 301 | result.push_str(ANY_ONE); 302 | if c != '%' && c != '_' { 303 | result.push(c); 304 | } 305 | previous_char = Some(c); 306 | } 307 | (Some(_), '%') | (Some(_), '_') => { 308 | previous_char = Some(c); 309 | } 310 | (Some(_), _) => { 311 | result.push(c); 312 | previous_char = Some(c); 313 | } 314 | } 315 | // dbg!("{} {} => {}", c, previous_char.unwrap_or(' '), &result); 316 | } 317 | 318 | match previous_char { 319 | None => {} 320 | Some('%') => result.push_str(ANY_MANY), 321 | Some('_') => result.push_str(ANY_ONE), 322 | _ => {} 323 | } 324 | 325 | result.push('$'); 326 | result 327 | } 328 | 329 | // #[cached(type = "SizedCache>", create = "{ SizedCache::with_size(1000) }", convert = r#"{ search_pattern.into() }"#)] 330 | fn make_case_insensitive_like_regex(search_pattern: &str) -> Result { 331 | let search_pattern = regex::escape(&search_pattern); 332 | let regex_pattern = like_pattern_to_regex_pattern(&search_pattern); 333 | let regex = RegexBuilder::new(®ex_pattern).case_insensitive(true).build().map_err(|e| format!("{}", e))?; 334 | Ok(regex) 335 | } 336 | 337 | /**********************************/ 338 | /* Functions list */ 339 | /**********************************/ 340 | 341 | pub fn get_functions() -> FunctionImplList { 342 | let mut funcs = FunctionImplList::new(); 343 | funcs.insert(UniCase::new("IsNull".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_is_null))); 344 | funcs.insert(UniCase::new("IsBlank".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_is_null))); 345 | funcs.insert(UniCase::new("AreEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_are_equals))); 346 | funcs.insert(UniCase::new("In".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_in))); 347 | funcs.insert(UniCase::new("InLike".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_in_like))); 348 | funcs.insert(UniCase::new("IsLike".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_is_like))); 349 | funcs.insert(UniCase::new("Like".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_is_like))); 350 | funcs.insert(UniCase::new("FirstNotNull".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_first_not_null))); 351 | funcs.insert(UniCase::new("FirstNotEmpty".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_first_not_null))); 352 | funcs.insert(UniCase::new("Concatenate".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_concat))); 353 | funcs.insert(UniCase::new("Concat".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_concat))); 354 | funcs.insert(UniCase::new("Exact".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_exact))); 355 | funcs.insert(UniCase::new("Find".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_find))); 356 | funcs.insert(UniCase::new("Substitute".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_substitute))); 357 | funcs.insert(UniCase::new("Fixed".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_fixed))); 358 | funcs.insert(UniCase::new("Left".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_left))); 359 | funcs.insert(UniCase::new("Right".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_right))); 360 | funcs.insert(UniCase::new("Mid".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_mid))); 361 | funcs.insert(UniCase::new("Len".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_len))); 362 | funcs.insert(UniCase::new("Lower".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_lower))); 363 | funcs.insert(UniCase::new("Upper".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_upper))); 364 | funcs.insert(UniCase::new("Trim".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_trim))); 365 | funcs.insert(UniCase::new("FirstWord".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_first_word))); 366 | funcs.insert(UniCase::new("FirstSentence".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_first_sentence))); 367 | funcs.insert(UniCase::new("Capitalize".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_capitalize))); 368 | funcs.insert(UniCase::new("Split".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_split))); 369 | funcs.insert(UniCase::new("NumberValue".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_number_value))); 370 | funcs.insert(UniCase::new("Text".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_text))); 371 | funcs.insert(UniCase::new("StartsWith".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_starts_with))); 372 | funcs.insert(UniCase::new("EndsWith".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_ends_with))); 373 | funcs.insert(UniCase::new("ReplaceEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_replace_equals))); 374 | funcs.insert(UniCase::new("ReplaceLike".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_replace_like))); 375 | funcs.insert(UniCase::new("And".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_and))); 376 | funcs.insert(UniCase::new("Or".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_or))); 377 | funcs.insert(UniCase::new("Not".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_not))); 378 | funcs.insert(UniCase::new("Xor".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_xor))); 379 | funcs.insert(UniCase::new("Iif".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_iif))); 380 | funcs.insert(UniCase::new("If".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_iif))); 381 | funcs.insert(UniCase::new("Abs".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_abs))); 382 | funcs.insert(UniCase::new("Product".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_product))); 383 | funcs.insert(UniCase::new("Sum".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_sum))); 384 | funcs.insert(UniCase::new("Divide".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_divide))); 385 | funcs.insert(UniCase::new("Subtract".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_subtract))); 386 | funcs.insert(UniCase::new("Mod".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_mod))); 387 | funcs.insert(UniCase::new("Modulo".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_mod))); 388 | funcs.insert(UniCase::new("Round".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_round))); 389 | funcs.insert(UniCase::new("GreaterThan".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_greater_than))); 390 | funcs.insert(UniCase::new("Gt".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_greater_than))); 391 | funcs.insert(UniCase::new("LowerThan".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_lower_than))); 392 | funcs.insert(UniCase::new("Lt".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_lower_than))); 393 | funcs.insert(UniCase::new("GreaterThanOrEqual".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_greater_than_or_equal))); 394 | funcs.insert(UniCase::new("Gtoe".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_greater_than_or_equal))); 395 | funcs.insert(UniCase::new("LowerThanOrEqual".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_lower_than_or_equal))); 396 | funcs.insert(UniCase::new("Ltoe".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_lower_than_or_equal))); 397 | funcs.insert(UniCase::new("Date".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date))); 398 | funcs.insert(UniCase::new("Now".to_string()), (FunctionDeterminism::NonDeterministic, Rc::new(f_now))); 399 | funcs.insert(UniCase::new("Year".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_year))); 400 | funcs.insert(UniCase::new("Month".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_month))); 401 | funcs.insert(UniCase::new("Day".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_day))); 402 | funcs.insert(UniCase::new("DateDiff".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_diff))); 403 | funcs.insert(UniCase::new("DateDiffHours".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_diff_hours))); 404 | funcs.insert(UniCase::new("DateDiffDays".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_diff_days))); 405 | funcs.insert(UniCase::new("DateDiffMonths".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_diff_months))); 406 | funcs.insert(UniCase::new("DateEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_equals))); 407 | funcs.insert(UniCase::new("DateNotEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_not_equals))); 408 | funcs.insert(UniCase::new("DateLower".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_lower))); 409 | funcs.insert(UniCase::new("DateLowerOrEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_lower_or_equals))); 410 | funcs.insert(UniCase::new("DateGreater".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_greater))); 411 | funcs.insert(UniCase::new("DateGreaterOrEquals".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_greater_or_equals))); 412 | funcs.insert(UniCase::new("DateAddHours".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_add_hours))); 413 | funcs.insert(UniCase::new("DateAddDays".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_add_days))); 414 | funcs.insert(UniCase::new("DateAddMonths".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_add_months))); 415 | funcs.insert(UniCase::new("DateAddYears".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_add_years))); 416 | funcs.insert(UniCase::new("LocalDate".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_local_date))); 417 | funcs.insert(UniCase::new("DateFormat".to_string()), (FunctionDeterminism::Deterministic, Rc::new(f_date_format))); 418 | funcs.insert(UniCase::new("NowSpecificTimeZone".to_string()), (FunctionDeterminism::NonDeterministic, Rc::new(f_now_specific_timezone))); 419 | funcs.insert(UniCase::new("Today".to_string()), (FunctionDeterminism::NonDeterministic, Rc::new(f_today))); 420 | funcs.insert(UniCase::new("Time".to_string()), (FunctionDeterminism::NonDeterministic, Rc::new(f_time))); 421 | funcs 422 | } 423 | 424 | pub fn f_operators(left: RcExpr, right: RcExpr, op: AssocOp, values: &IdentifierValues) -> ExprFuncResult { 425 | match (op, left, right) { 426 | (AssocOp::Add, l, r) => f_sum(&vec![l, r], values), 427 | (AssocOp::Divide, l, r) => f_divide(&vec![l, r], values), 428 | (AssocOp::Equal, l, r) => f_are_equals(&vec![l, r], values), 429 | (AssocOp::Greater, l, r) => f_greater_than(&vec![l, r], values), 430 | (AssocOp::GreaterEqual, l, r) => f_greater_than_or_equal(&vec![l, r], values), 431 | (AssocOp::LAnd, l, r) => f_and(&vec![l, r], values), 432 | (AssocOp::Less, l, r) => f_lower_than(&vec![l, r], values), 433 | (AssocOp::LessEqual, l, r) => f_lower_than_or_equal(&vec![l, r], values), 434 | (AssocOp::LOr, l, r) => f_or(&vec![l, r], values), 435 | (AssocOp::Modulus, l, r) => f_mod(&vec![l, r], values), 436 | (AssocOp::Multiply, l, r) => f_product(&vec![l, r], values), 437 | (AssocOp::NotEqual, l, r) => f_are_not_equals(&vec![l, r], values), 438 | (AssocOp::Subtract, l, r) => f_subtract(&vec![l, r], values), 439 | } 440 | } 441 | 442 | // #region Category names 443 | 444 | // private const string MiscCatName = "Misc"; 445 | // private const string StringsCatName = "Strings"; 446 | // private const string LogicalCatName = "Logical"; 447 | // private const string MathCatName = "Math"; 448 | // private const string DateCatName = "DateTime"; 449 | 450 | // #endregion 451 | 452 | /**********************************/ 453 | /* Miscellaneous */ 454 | /**********************************/ 455 | 456 | // IsNull, IsBlank 457 | fn f_is_null(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 458 | let res = exec_vec_is_null(params, values)?; 459 | Ok(ExprResult::Boolean(res)) 460 | } 461 | 462 | // AreEquals 463 | fn f_are_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 464 | assert_exact_params_count(params, 2, "AreEquals")?; 465 | let equals = are_equals_internal(params, values)?; 466 | Ok(ExprResult::Boolean(equals)) 467 | } 468 | 469 | // AreNotEquals 470 | fn f_are_not_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 471 | assert_exact_params_count(params, 2, "AreNotEquals")?; 472 | let equals = are_equals_internal(params, values)?; 473 | Ok(ExprResult::Boolean(!equals)) 474 | } 475 | 476 | fn are_equals_internal(params: &SliceRcExpr, values: &IdentifierValues) -> Result { 477 | let left = exec_expr_to_string(params.get(0).unwrap(), values)?; 478 | let right = exec_expr_to_string(params.get(1).unwrap(), values)?; 479 | Ok(left == right) 480 | } 481 | 482 | // In 483 | fn f_in(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 484 | assert_min_params_count(params, 2, "In")?; 485 | let search = exec_expr(params.get(0).unwrap(), values)?; 486 | for p in params.iter().skip(1) { 487 | let p_result = exec_expr(p, values)?; 488 | if results_are_equals(&search, &p_result) { 489 | return Ok(ExprResult::Boolean(true)); 490 | } 491 | } 492 | return Ok(ExprResult::Boolean(false)); 493 | } 494 | 495 | // InLike 496 | fn f_in_like(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 497 | assert_min_params_count(params, 2, "InLike")?; 498 | let search = exec_expr_to_string(params.get(0).unwrap(), values)?; 499 | let regex = make_case_insensitive_like_regex(&search)?; 500 | for p in params.iter().skip(1) { 501 | let text = exec_expr_to_string(p, values)?; 502 | if regex.is_match(&text) { 503 | return Ok(ExprResult::Boolean(true)); 504 | } 505 | } 506 | Ok(ExprResult::Boolean(false)) 507 | } 508 | 509 | // IsLike, Like 510 | fn f_is_like(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 511 | assert_exact_params_count(params, 2, "IsLike")?; 512 | let text = exec_expr_to_string(params.get(0).unwrap(), values)?; 513 | let search = exec_expr_to_string(params.get(1).unwrap(), values)?; 514 | let regex = make_case_insensitive_like_regex(&search)?; 515 | Ok(ExprResult::Boolean(regex.is_match(&text))) 516 | } 517 | 518 | fn f_first_not_null(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 519 | for p in params.iter() { 520 | let p_result = exec_expr(p, values)?; 521 | if !expr_result_is_null(&p_result) { 522 | return Ok(p_result); 523 | } 524 | } 525 | Ok(ExprResult::Null) 526 | } 527 | 528 | /**********************************/ 529 | /* Strings */ 530 | /**********************************/ 531 | 532 | // Concatenate, Concat 533 | fn f_concat(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 534 | let mut result = String::new(); 535 | for p in params.iter() { 536 | let s = exec_expr_to_string(p, values)?; 537 | result.push_str(&s); 538 | } 539 | Ok(ExprResult::Str(Rc::new(result))) 540 | } 541 | 542 | // Exact 543 | fn f_exact(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 544 | assert_exact_params_count(params, 2, "Exact")?; 545 | let left = exec_expr_to_string(params.get(0).unwrap(), values)?; 546 | let right = exec_expr_to_string(params.get(1).unwrap(), values)?; 547 | Ok(ExprResult::Boolean(left == right)) 548 | } 549 | 550 | // Find 551 | fn f_find(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 552 | assert_between_params_count(params, 2, 3, "Find")?; 553 | let start_num: usize = match params.get(2) { 554 | None => 0, 555 | Some(epxr) => (exec_expr_to_int(epxr, values)? - 1).max(0) as usize, 556 | }; 557 | 558 | let find_text = exec_expr_to_string(params.get(0).unwrap(), values)?; 559 | let regex = make_case_insensitive_search_regex(&find_text)?; 560 | 561 | let within_text = exec_expr_to_string(params.get(1).unwrap(), values)?; 562 | let position = match regex.find_at(&within_text, start_num) { 563 | None => 0, // 0 for not found 564 | Some(m) => m.start() + 1, // because it's a Excel function and 1 based enumeration 565 | }; 566 | Ok(ExprResult::Num(ExprDecimal::from(position))) 567 | } 568 | 569 | // Substitute 570 | fn f_substitute(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 571 | assert_exact_params_count(params, 3, "Substitute")?; 572 | 573 | let within_text = exec_expr_to_string(params.get(0).unwrap(), values)?; 574 | let find_text = exec_expr_to_string(params.get(1).unwrap(), values)?; 575 | let replace_text = exec_expr_to_string(params.get(2).unwrap(), values)?; 576 | 577 | if within_text.is_empty() && find_text.is_empty() { 578 | return Ok(ExprResult::Str(replace_text)); 579 | } 580 | 581 | if within_text.is_empty() || find_text.is_empty() { 582 | return Ok(ExprResult::Str(within_text)); 583 | } 584 | 585 | let regex = make_case_insensitive_search_regex(&find_text)?; 586 | let replaced = regex.replace_all(&within_text, move |_c: ®ex::Captures| replace_text.to_string()); 587 | 588 | Ok(ExprResult::Str(Rc::new(replaced.into()))) 589 | } 590 | 591 | // Fixed 592 | fn f_fixed(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 593 | assert_between_params_count(params, 1, 3, "Fixed")?; 594 | 595 | let number = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 596 | 597 | let decimals = match params.get(1) { 598 | None => 2, 599 | Some(epxr) => exec_expr_to_int(epxr, values)?.max(0) as u32, 600 | }; 601 | 602 | let number = number.round_dp_with_strategy(decimals, RoundingStrategy::RoundHalfDown); 603 | 604 | let no_commas = match params.get(2) { 605 | None => true, 606 | Some(epxr) => exec_expr_to_bool(epxr, values)?, 607 | }; 608 | 609 | let result = if no_commas { 610 | format!("{num:.prec$}", num = number, prec = decimals as usize) 611 | } else { 612 | let int = number.trunc().to_isize().ok_or_else(|| "integer cast error".to_string())?.to_formatted_string(&Locale::en); 613 | let fract = format!("{num:.prec$}", num = number.fract(), prec = decimals as usize); 614 | let fract: Vec<&str> = fract.split(".").collect(); 615 | let result = match fract.get(1) { 616 | Some(s) => format!("{}.{}", int, s), 617 | None => int, 618 | }; 619 | result 620 | }; 621 | Ok(ExprResult::Str(Rc::new(result))) 622 | } 623 | 624 | // Left 625 | fn f_left(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 626 | assert_exact_params_count(params, 2, "Left")?; 627 | let size = exec_expr_to_int(params.get(1).unwrap(), values)?.max(0) as usize; 628 | if size == 0 { 629 | return Ok(ExprResult::Str(get_rc_empty_string())); 630 | } 631 | let s = exec_expr_to_string(params.get(0).unwrap(), values)?; 632 | let len = get_human_string_length(&s); 633 | if size >= len { 634 | Ok(ExprResult::Str(s)) 635 | } else { 636 | Ok(ExprResult::Str(Rc::new(format!("{}", s.chars().take(size).collect::())))) 637 | } 638 | } 639 | 640 | // Right 641 | fn f_right(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 642 | assert_exact_params_count(params, 2, "Right")?; 643 | let size = exec_expr_to_int(params.get(1).unwrap(), values)?.max(0) as usize; 644 | if size == 0 { 645 | return Ok(ExprResult::Str(get_rc_empty_string())); 646 | } 647 | let s = exec_expr_to_string(params.get(0).unwrap(), values)?; 648 | let len = get_human_string_length(&s); 649 | if size >= len { 650 | Ok(ExprResult::Str(s)) 651 | } else { 652 | Ok(ExprResult::Str(Rc::new(format!("{}", s.chars().skip(len - size).collect::())))) 653 | } 654 | } 655 | 656 | // Mid 657 | fn f_mid(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 658 | assert_exact_params_count(params, 3, "Mid")?; 659 | let size = exec_expr_to_int(params.get(2).unwrap(), values)?.max(0) as usize; 660 | if size == 0 { 661 | return Ok(ExprResult::Str(get_rc_empty_string())); 662 | } 663 | let s = exec_expr_to_string(params.get(0).unwrap(), values)?; 664 | let false_position = exec_expr_to_int(params.get(1).unwrap(), values)?.max(1); 665 | let position = (false_position - 1) as usize; 666 | let len = get_human_string_length(&s); 667 | if position >= len { 668 | return Ok(ExprResult::Str(get_rc_empty_string())); 669 | } 670 | if position == 0 && size >= len { 671 | Ok(ExprResult::Str(s)) 672 | } else { 673 | Ok(ExprResult::Str(Rc::new(format!("{}", s.chars().skip(position).take(size).collect::())))) 674 | } 675 | } 676 | 677 | fn single_string_func) -> ExprFuncResult>(params: &SliceRcExpr, values: &IdentifierValues, f_name: &str, func: F) -> ExprFuncResult { 678 | assert_exact_params_count(params, 1, f_name)?; 679 | let s = exec_expr_to_string(params.get(0).unwrap(), values)?; 680 | func(s) 681 | } 682 | 683 | fn get_human_string_length(s: &str) -> usize { 684 | s.chars().count() 685 | } 686 | 687 | // Len 688 | fn f_len(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 689 | single_string_func(params, values, "Len", |s| Ok(ExprResult::Num(ExprDecimal::from(get_human_string_length(&s))))) 690 | } 691 | 692 | // Lower 693 | fn f_lower(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 694 | single_string_func(params, values, "Lower", |s| Ok(ExprResult::Str(Rc::new(s.to_lowercase())))) 695 | } 696 | 697 | // Upper 698 | fn f_upper(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 699 | single_string_func(params, values, "Upper", |s| Ok(ExprResult::Str(Rc::new(s.to_uppercase())))) 700 | } 701 | 702 | // Trim 703 | fn f_trim(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 704 | single_string_func(params, values, "Trim", |s| Ok(ExprResult::Str(Rc::new(s.trim().to_string())))) 705 | } 706 | 707 | fn is_punctuation(c: char) -> bool { 708 | c == '.' || c == ',' || c == '!' || c == '?' || c == '¿' 709 | } 710 | fn is_space(c: char) -> bool { 711 | c == ' ' || c == '\t' || c == '\r' || c == '\n' 712 | } 713 | fn is_sentence_punctuation(c: char) -> bool { 714 | c == '.' || c == '!' || c == '?' 715 | } 716 | 717 | // FirstWord 718 | fn f_first_word(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 719 | single_string_func(params, values, "FirstWord", |s| { 720 | let position = s.chars().position(|c| is_space(c) || is_punctuation(c)); 721 | match position { 722 | None => Ok(ExprResult::Str(s)), 723 | Some(i) => Ok(ExprResult::Str(Rc::new(format!("{}", &s[..i])))), 724 | } 725 | }) 726 | } 727 | 728 | // Text 729 | fn f_text(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 730 | single_string_func(params, values, "Text", |s| Ok(ExprResult::Str(s))) 731 | } 732 | 733 | // FirstSentence 734 | fn f_first_sentence(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 735 | single_string_func(params, values, "FirstSentence", |s| { 736 | let position = s.chars().position(|c| is_sentence_punctuation(c)); 737 | match position { 738 | None => Ok(ExprResult::Str(s)), 739 | Some(i) => Ok(ExprResult::Str(Rc::new(format!("{}", &s[..i + 1])))), 740 | } 741 | }) 742 | } 743 | 744 | // Capitalize 745 | fn f_capitalize(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 746 | single_string_func(params, values, "Capitalize", |s| { 747 | let (_, result) = s.chars().into_iter().fold((true, String::with_capacity(s.capacity())), |state, c| { 748 | let (should_capitalize, mut s) = state; 749 | match (should_capitalize, is_sentence_punctuation(c), is_space(c)) { 750 | (false, end_sentence, _) => { 751 | s.push(c); 752 | (should_capitalize || end_sentence, s) 753 | } 754 | (true, _, true) => { 755 | s.push(c); 756 | (should_capitalize, s) 757 | } 758 | (true, _, false) => { 759 | for u in c.to_uppercase() { 760 | s.push(u); 761 | } 762 | (false, s) 763 | } 764 | } 765 | }); 766 | 767 | Ok(ExprResult::Str(Rc::new(result))) 768 | }) 769 | } 770 | 771 | // Split 772 | fn f_split(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 773 | assert_exact_params_count(params, 3, "Split")?; 774 | let s = exec_expr_to_string(params.get(0).unwrap(), values)?.to_string(); 775 | let separator = exec_expr_to_string(params.get(1).unwrap(), values)?.to_string(); 776 | let index = exec_expr_to_int(params.get(2).unwrap(), values)?.max(0) as usize; 777 | let parts: Vec<&str> = s.split(&separator).collect(); 778 | let result = match parts.get(index) { 779 | None => ExprResult::Null, 780 | Some(p) => ExprResult::Str(Rc::new(p.to_string())), 781 | }; 782 | Ok(result) 783 | } 784 | 785 | // NumberValue 786 | fn f_number_value(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 787 | assert_between_params_count(params, 1, 2, "NumberValue")?; 788 | let separator = match params.get(1) { 789 | None => None, 790 | Some(expr) => exec_expr_to_string(expr, values)?.chars().next(), 791 | }; 792 | 793 | let number = exec_expr_to_num(params.get(0).unwrap(), values, separator)?; 794 | Ok(ExprResult::Num(number)) 795 | } 796 | 797 | // StartsWith 798 | fn f_starts_with(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 799 | assert_exact_params_count(params, 2, "StartsWith")?; 800 | let text = exec_expr_to_string(params.get(0).unwrap(), values)?; 801 | let search = exec_expr_to_string(params.get(1).unwrap(), values)?; 802 | 803 | let mut t_iter = text.chars().into_iter(); 804 | let mut s_iter = search.chars().into_iter(); 805 | 806 | loop { 807 | let t = t_iter.next(); 808 | let s = s_iter.next(); 809 | match (s, t) { 810 | (None, None) => return Ok(ExprResult::Boolean(true)), 811 | (None, Some(_)) => return Ok(ExprResult::Boolean(true)), 812 | (Some(_), None) => return Ok(ExprResult::Boolean(false)), 813 | (Some(vs), Some(vt)) => { 814 | if !vs.to_lowercase().eq(vt.to_lowercase()) { 815 | return Ok(ExprResult::Boolean(false)); 816 | } 817 | } 818 | } 819 | } 820 | unreachable!(); 821 | } 822 | 823 | // EndsWith 824 | fn f_ends_with(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 825 | assert_exact_params_count(params, 2, "EndsWith")?; 826 | let text = exec_expr_to_string(params.get(0).unwrap(), values)?; 827 | let search = exec_expr_to_string(params.get(1).unwrap(), values)?; 828 | 829 | let mut t_iter = text.chars().rev().into_iter(); 830 | let mut s_iter = search.chars().rev().into_iter(); 831 | 832 | loop { 833 | let t = t_iter.next(); 834 | let s = s_iter.next(); 835 | match (s, t) { 836 | (None, None) => return Ok(ExprResult::Boolean(true)), 837 | (None, Some(_)) => return Ok(ExprResult::Boolean(true)), 838 | (Some(_), None) => return Ok(ExprResult::Boolean(false)), 839 | (Some(vs), Some(vt)) => { 840 | if !vs.to_lowercase().eq(vt.to_lowercase()) { 841 | return Ok(ExprResult::Boolean(false)); 842 | } 843 | } 844 | } 845 | } 846 | unreachable!(); 847 | } 848 | 849 | // ReplaceEquals 850 | fn f_replace_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 851 | assert_min_params_count(params, 4, "ReplaceEquals")?; 852 | if params.len() % 2 == 1 { 853 | return Err("Remplacement key/value parameters must come 2 by 2".to_string()); 854 | } 855 | 856 | let text = exec_expr_to_string(params.get(0).unwrap(), values)?; 857 | let mut p_iter = params.iter().skip(2); 858 | loop { 859 | match (p_iter.next(), p_iter.next()) { 860 | (Some(pattern_expr), Some(replacement_expr)) => { 861 | let pattern = exec_expr_to_string(pattern_expr, values)?; 862 | let regex = make_case_insensitive_equals_regex(&pattern)?; 863 | 864 | if regex.is_match(&text) { 865 | let replacement = exec_expr(replacement_expr, values); 866 | return replacement; 867 | } 868 | } 869 | _ => break, 870 | } 871 | } 872 | 873 | let default = exec_expr(params.get(1).unwrap(), values); 874 | default 875 | } 876 | 877 | // ReplaceLike 878 | fn f_replace_like(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 879 | assert_min_params_count(params, 4, "ReplaceLike")?; 880 | if params.len() % 2 == 1 { 881 | return Err("Remplacement key/value parameters must come 2 by 2".to_string()); 882 | } 883 | 884 | let text = exec_expr_to_string(params.get(0).unwrap(), values)?; 885 | let mut p_iter = params.iter().skip(2); 886 | loop { 887 | match (p_iter.next(), p_iter.next()) { 888 | (Some(pattern_expr), Some(replacement_expr)) => { 889 | let pattern = exec_expr_to_string(pattern_expr, values)?; 890 | let regex = make_case_insensitive_like_regex(&pattern)?; 891 | 892 | if regex.is_match(&text) { 893 | let replacement = exec_expr(replacement_expr, values); 894 | return replacement; 895 | } 896 | } 897 | _ => break, 898 | } 899 | } 900 | 901 | let default = exec_expr(params.get(1).unwrap(), values); 902 | default 903 | } 904 | 905 | /**********************************/ 906 | /* Logical */ 907 | /**********************************/ 908 | 909 | // And 910 | fn f_and(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 911 | for expr in params { 912 | let b = exec_expr_to_bool(expr, values)?; 913 | if !b { 914 | return Ok(ExprResult::Boolean(false)); 915 | } 916 | } 917 | Ok(ExprResult::Boolean(true)) 918 | } 919 | 920 | // Or 921 | fn f_or(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 922 | for expr in params { 923 | let b = exec_expr_to_bool(expr, values)?; 924 | if b { 925 | return Ok(ExprResult::Boolean(true)); 926 | } 927 | } 928 | Ok(ExprResult::Boolean(false)) 929 | } 930 | 931 | // Not 932 | fn f_not(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 933 | assert_exact_params_count(params, 1, "Not")?; 934 | Ok(ExprResult::Boolean(!exec_expr_to_bool(params.get(0).unwrap(), values)?)) 935 | } 936 | 937 | // Xor 938 | fn f_xor(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 939 | assert_exact_params_count(params, 2, "Xor")?; 940 | let p0 = exec_expr_to_bool(params.get(0).unwrap(), values)?; 941 | let p1 = exec_expr_to_bool(params.get(1).unwrap(), values)?; 942 | Ok(ExprResult::Boolean(p0 ^ p1)) 943 | } 944 | 945 | // Iif, If 946 | fn f_iif(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 947 | assert_exact_params_count(params, 3, "Iif")?; 948 | let test = exec_expr_to_bool(params.get(0).unwrap(), values)?; 949 | exec_expr(params.get(if test { 1 } else { 2 }).unwrap(), values) 950 | } 951 | 952 | /**********************************/ 953 | /* Math */ 954 | /**********************************/ 955 | 956 | // Abs 957 | fn f_abs(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 958 | assert_exact_params_count(params, 1, "Abs")?; 959 | let num = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 960 | Ok(ExprResult::Num(num.abs())) 961 | } 962 | 963 | // Product 964 | fn f_product(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 965 | let mut result = ExprDecimal::from(1); 966 | for expr in params.iter() { 967 | let i = exec_expr_to_num(expr, values, None)?; 968 | let intermediate_result = result; 969 | result = std::panic::catch_unwind(|| intermediate_result * i).map_err(|_| format!("Couldn't multiply {} by {} : overflow", result, i).to_string())?; 970 | } 971 | Ok(ExprResult::Num(result)) 972 | } 973 | 974 | // Sum 975 | fn f_sum(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 976 | let mut result = ExprDecimal::from(0); 977 | for expr in params.iter() { 978 | let i = exec_expr_to_num(expr, values, None)?; 979 | let intermediate_result = result; 980 | result = std::panic::catch_unwind(|| intermediate_result + i).map_err(|_| format!("Couldn't add {} to {} : overflow", i, result).to_string())?; 981 | } 982 | Ok(ExprResult::Num(result)) 983 | } 984 | 985 | // Divide 986 | fn f_divide(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 987 | assert_exact_params_count(params, 2, "Divide")?; 988 | let num = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 989 | let divisor = exec_expr_to_num(params.get(1).unwrap(), values, None)?; 990 | let result = std::panic::catch_unwind(|| num / divisor).map_err(|_| format!("Couldn't divide {} by {}", num, divisor).to_string())?; 991 | Ok(ExprResult::Num(result)) 992 | } 993 | 994 | // Subtract 995 | fn f_subtract(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 996 | assert_exact_params_count(params, 2, "Subtract")?; 997 | let num = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 998 | let sub = exec_expr_to_num(params.get(1).unwrap(), values, None)?; 999 | let result = std::panic::catch_unwind(|| num - sub).map_err(|_| format!("Couldn't remove {} from {}", sub, num).to_string())?; 1000 | Ok(ExprResult::Num(result)) 1001 | } 1002 | 1003 | // Mod, Modulo 1004 | fn f_mod(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1005 | assert_exact_params_count(params, 2, "Mod")?; 1006 | let num = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 1007 | let divisor = exec_expr_to_num(params.get(1).unwrap(), values, None)?; 1008 | let result = std::panic::catch_unwind(|| num % divisor).map_err(|_| format!("Couldn't module {} by {}", num, divisor).to_string())?; 1009 | Ok(ExprResult::Num(result)) 1010 | } 1011 | 1012 | // Round 1013 | fn f_round(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1014 | assert_exact_params_count(params, 2, "Round")?; 1015 | let num = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 1016 | let digits = exec_expr_to_int(params.get(1).unwrap(), values)?.max(0) as u32; 1017 | let mult_div = ExprDecimal::from((10 as u32).pow(digits)); 1018 | let result = std::panic::catch_unwind(|| (num * mult_div).round() / mult_div).map_err(|_| format!("Couldn't round {} to {} digits", num, digits).to_string())?; 1019 | Ok(ExprResult::Num(result)) 1020 | } 1021 | 1022 | fn simple_operator ExprFuncResult>(params: &SliceRcExpr, values: &IdentifierValues, f_name: &str, func: F) -> ExprFuncResult { 1023 | assert_exact_params_count(params, 2, f_name)?; 1024 | let num_a = exec_expr_to_num(params.get(0).unwrap(), values, None)?; 1025 | let num_b = exec_expr_to_num(params.get(1).unwrap(), values, None)?; 1026 | func(num_a, num_b) 1027 | } 1028 | 1029 | // GreaterThan, Gt 1030 | fn f_greater_than(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1031 | simple_operator(params, values, "GreaterThan", |a, b| Ok(ExprResult::Boolean(a > b))) 1032 | } 1033 | 1034 | // LowerThan, Lt 1035 | fn f_lower_than(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1036 | simple_operator(params, values, "LowerThan", |a, b| Ok(ExprResult::Boolean(a < b))) 1037 | } 1038 | 1039 | // GreaterThanOrEqual, Gtoe 1040 | fn f_greater_than_or_equal(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1041 | simple_operator(params, values, "GreaterThanOrEqual", |a, b| Ok(ExprResult::Boolean(a >= b))) 1042 | } 1043 | 1044 | // LowerThanOrEqual, Ltoe 1045 | fn f_lower_than_or_equal(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1046 | simple_operator(params, values, "LowerThanOrEqual", |a, b| Ok(ExprResult::Boolean(a <= b))) 1047 | } 1048 | 1049 | /**********************************/ 1050 | /* DateTime */ 1051 | /**********************************/ 1052 | 1053 | // Now 1054 | fn f_now(params: &SliceRcExpr, _values: &IdentifierValues) -> ExprFuncResult { 1055 | assert_exact_params_count(params, 0, "Now")?; 1056 | Ok(ExprResult::Date(Utc::now().naive_utc())) 1057 | } 1058 | 1059 | // Today 1060 | fn f_today(params: &SliceRcExpr, _values: &IdentifierValues) -> ExprFuncResult { 1061 | assert_exact_params_count(params, 0, "Today")?; 1062 | let date = NaiveDateTime::new(Utc::now().date().naive_utc(), NaiveTime::from_hms(0, 0, 0)); 1063 | Ok(ExprResult::Date(date)) 1064 | } 1065 | 1066 | // Time 1067 | fn f_time(params: &SliceRcExpr, _values: &IdentifierValues) -> ExprFuncResult { 1068 | assert_exact_params_count(params, 0, "Time")?; 1069 | let duration = Utc::now().time().signed_duration_since(NaiveTime::from_hms(0, 0, 0)); 1070 | Ok(ExprResult::TimeSpan(duration)) 1071 | } 1072 | 1073 | // NowSpecificTimeZone 1074 | fn f_now_specific_timezone(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1075 | assert_between_params_count(params, 0, 1, "NowSpecificTimeZone")?; 1076 | 1077 | let now = Utc::now().naive_utc(); 1078 | 1079 | match params.get(0) { 1080 | None => Ok(ExprResult::Date(now)), 1081 | Some(expr) => { 1082 | let time_zone_name = exec_expr_to_string(expr, values)?; 1083 | naive_datetime_to_timezone(&now, &time_zone_name) 1084 | } 1085 | } 1086 | } 1087 | 1088 | fn single_date_func ExprFuncResult>(params: &SliceRcExpr, values: &IdentifierValues, f_name: &str, func: F) -> ExprFuncResult { 1089 | assert_exact_params_count(params, 1, f_name)?; 1090 | let date = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1091 | func(date) 1092 | } 1093 | 1094 | // Date 1095 | fn f_date(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1096 | single_date_func(params, values, "Date", |d| Ok(ExprResult::Date(d))) 1097 | } 1098 | 1099 | // Year 1100 | fn f_year(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1101 | single_date_func(params, values, "Year", |d| Ok(ExprResult::Num(ExprDecimal::from(d.year())))) 1102 | } 1103 | 1104 | // Month 1105 | fn f_month(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1106 | single_date_func(params, values, "Month", |d| Ok(ExprResult::Num(ExprDecimal::from(d.month())))) 1107 | } 1108 | 1109 | // Day 1110 | fn f_day(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1111 | single_date_func(params, values, "Day", |d| Ok(ExprResult::Num(ExprDecimal::from(d.day())))) 1112 | } 1113 | 1114 | fn two_dates_func_no_defaults ExprFuncResult>(params: &SliceRcExpr, values: &IdentifierValues, f_name: &str, func: F) -> ExprFuncResult { 1115 | assert_exact_params_count(params, 2, f_name)?; 1116 | let date_left = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1117 | let date_right = exec_expr_to_date_no_defaults(params.get(1).unwrap(), values)?; 1118 | func(date_left, date_right) 1119 | } 1120 | 1121 | fn two_dates_func ExprFuncResult>(params: &SliceRcExpr, values: &IdentifierValues, f_name: &str, func: F) -> ExprFuncResult { 1122 | assert_between_params_count(params, 2, 8, f_name)?; 1123 | 1124 | let default_year = params.get(2).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1125 | let default_month = params.get(3).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1126 | let default_day = params.get(4).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1127 | let default_hour = params.get(5).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1128 | let default_minute = params.get(6).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1129 | let default_second = params.get(7).map_or(Ok(false), |expr| exec_expr_to_bool(expr, values))?; 1130 | 1131 | let date_left = exec_expr_to_date(params.get(0).unwrap(), values, default_year, default_month, default_day, default_hour, default_minute, default_second)?; 1132 | let date_right = exec_expr_to_date(params.get(1).unwrap(), values, default_year, default_month, default_day, default_hour, default_minute, default_second)?; 1133 | func(date_left, date_right) 1134 | } 1135 | 1136 | // DateDiff 1137 | fn f_date_diff(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1138 | two_dates_func_no_defaults(params, values, "DateDiff", |d1, d2| Ok(ExprResult::TimeSpan(d1 - d2))) 1139 | } 1140 | 1141 | pub const SECONDS_IN_MIN: i64 = 60; 1142 | pub const SECONDS_IN_HOURS: i64 = SECONDS_IN_MIN * 60; 1143 | pub const SECONDS_IN_DAYS: i64 = SECONDS_IN_HOURS * 24; 1144 | // pub const SECONDS_IN_MONTHS_size: f64 = SECONDS_IN_DAYS as f64 * 30.5_f64; 1145 | 1146 | //DateDiffHours 1147 | fn f_date_diff_hours(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1148 | two_dates_func_no_defaults(params, values, "DateDiffHours", |d1, d2| { 1149 | let hours = ((d1 - d2).num_seconds() / SECONDS_IN_HOURS).abs(); 1150 | Ok(ExprResult::Num(ExprDecimal::from(hours))) 1151 | }) 1152 | } 1153 | 1154 | // DateDiffDays 1155 | fn f_date_diff_days(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1156 | two_dates_func_no_defaults(params, values, "DateDiffDays", |d1, d2| { 1157 | let days = ((d1 - d2).num_seconds() / SECONDS_IN_DAYS).abs(); 1158 | Ok(ExprResult::Num(ExprDecimal::from(days))) 1159 | }) 1160 | } 1161 | 1162 | // DateDiffMonths 1163 | fn f_date_diff_months(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1164 | two_dates_func_no_defaults(params, values, "DateDiffMonths", |d1, d2| { 1165 | let months = ((d1.month() as i32 - d2.month() as i32) + 12 * (d1.year() - d2.year())).abs(); 1166 | Ok(ExprResult::Num(ExprDecimal::from(months))) 1167 | }) 1168 | } 1169 | 1170 | // DateEquals 1171 | fn f_date_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1172 | two_dates_func(params, values, "DateEquals", |d1, d2| Ok(ExprResult::Boolean(d1 == d2))) 1173 | } 1174 | 1175 | // DateNotEquals 1176 | fn f_date_not_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1177 | two_dates_func(params, values, "DateNotEquals", |d1, d2| Ok(ExprResult::Boolean(d1 != d2))) 1178 | } 1179 | 1180 | // DateLower 1181 | fn f_date_lower(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1182 | two_dates_func(params, values, "DateLower", |d1, d2| Ok(ExprResult::Boolean(d1 < d2))) 1183 | } 1184 | 1185 | // DateLowerOrEquals 1186 | fn f_date_lower_or_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1187 | two_dates_func(params, values, "DateLowerOrEquals", |d1, d2| Ok(ExprResult::Boolean(d1 <= d2))) 1188 | } 1189 | 1190 | // DateGreater 1191 | fn f_date_greater(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1192 | two_dates_func(params, values, "DateGreater", |d1, d2| Ok(ExprResult::Boolean(d1 > d2))) 1193 | } 1194 | 1195 | // DateGreaterOrEquals 1196 | fn f_date_greater_or_equals(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1197 | two_dates_func(params, values, "DateGreaterOrEquals", |d1, d2| Ok(ExprResult::Boolean(d1 >= d2))) 1198 | } 1199 | 1200 | // DateAddHours 1201 | fn f_date_add_hours(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1202 | assert_exact_params_count(params, 2, "DateAddHours")?; 1203 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1204 | let hours = exec_expr_to_float(params.get(1).unwrap(), values, None)?; 1205 | let date_time = date_time + Duration::seconds((hours * SECONDS_IN_HOURS as f64) as i64); 1206 | Ok(ExprResult::Date(date_time)) 1207 | } 1208 | 1209 | // DateAddDays 1210 | fn f_date_add_days(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1211 | assert_exact_params_count(params, 2, "DateAddDays")?; 1212 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1213 | let days = exec_expr_to_float(params.get(1).unwrap(), values, None)?; 1214 | let date_time = date_time + Duration::seconds((days * SECONDS_IN_DAYS as f64) as i64); 1215 | Ok(ExprResult::Date(date_time)) 1216 | } 1217 | 1218 | // DateAddMonths 1219 | fn f_date_add_months(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1220 | assert_exact_params_count(params, 2, "DateAddMonths")?; 1221 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1222 | 1223 | let months = exec_expr_to_int(params.get(1).unwrap(), values)?; 1224 | let month0 = date_time.month0() as i32 + (months as i32); 1225 | let mut years_to_add = month0 / 12; 1226 | let mut new_month0 = month0 % 12; 1227 | if new_month0 < 0 { 1228 | new_month0 = new_month0 + 12; 1229 | years_to_add = years_to_add - 1; 1230 | } 1231 | 1232 | let mut new_date_time = date_time 1233 | .with_year(date_time.year() + years_to_add) 1234 | .ok_or(format!("Couldn't add {} years to the date {}", years_to_add, date_time))?; 1235 | 1236 | new_date_time = new_date_time 1237 | .with_month0(new_month0 as u32) 1238 | .ok_or(format!("Couldn't set {} as month to the date {}", new_month0 + 1, new_date_time))?; 1239 | 1240 | Ok(ExprResult::Date(new_date_time)) 1241 | } 1242 | 1243 | // DateAddYears 1244 | fn f_date_add_years(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1245 | assert_exact_params_count(params, 2, "DateAddYears")?; 1246 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1247 | let years = exec_expr_to_int(params.get(1).unwrap(), values)? as i32; 1248 | 1249 | let new_date_time = date_time.with_year(date_time.year() + years).ok_or(format!("Couldn't add {} years to the date {}", years, date_time))?; 1250 | 1251 | Ok(ExprResult::Date(new_date_time)) 1252 | } 1253 | 1254 | fn get_rc_default_timezone_name() -> Rc { 1255 | Rc::new("Romance Standard Time".into()) 1256 | } 1257 | // LocalDate 1258 | fn f_local_date(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1259 | assert_between_params_count(params, 1, 2, "LocalDate")?; 1260 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1261 | let time_zone_name = params.get(1).map_or(Ok(get_rc_default_timezone_name()), |expr| exec_expr_to_string(expr, values))?; 1262 | naive_datetime_to_timezone(&date_time, &time_zone_name) 1263 | } 1264 | 1265 | fn get_rc_default_date_format() -> Rc { 1266 | Rc::new("yyyy-MM-dd HH:mm:ss.fff".into()) 1267 | } 1268 | 1269 | // DateFormat 1270 | fn f_date_format(params: &SliceRcExpr, values: &IdentifierValues) -> ExprFuncResult { 1271 | assert_between_params_count(params, 1, 2, "DateFormat")?; 1272 | let date_time = exec_expr_to_date_no_defaults(params.get(0).unwrap(), values)?; 1273 | let format = params.get(1).map_or(Ok(get_rc_default_date_format()), |expr| exec_expr_to_string(expr, values))?; 1274 | 1275 | let format = dotnet_format_to_strptime_format(&format); 1276 | let result = date_time.format(&format); 1277 | 1278 | Ok(ExprResult::Str(Rc::new(result.to_string()))) 1279 | } 1280 | 1281 | #[cfg(test)] 1282 | mod tests { 1283 | use super::*; 1284 | use test_case::test_case; 1285 | 1286 | #[test_case("yyyy-MM-dd HH:mm:ss.fff" => "%Y-%m-%d %H:%M:%S.%3f")] 1287 | fn test_dotnet_format_to_strptime_format(dotnet_format: &str) -> String { 1288 | dotnet_format_to_strptime_format(dotnet_format) 1289 | } 1290 | 1291 | #[test_case("abcd" => "^abcd$")] 1292 | #[test_case("a_cd" => "^a.{1}cd$")] 1293 | #[test_case("ab%d" => "^ab.*d$")] 1294 | #[test_case("ab%%cd" => "^ab%cd$")] 1295 | #[test_case("_abc" => "^.{1}abc$")] 1296 | #[test_case("%abc" => "^.*abc$")] 1297 | #[test_case("def_" => "^def.{1}$")] 1298 | #[test_case("def%" => "^def.*$")] 1299 | #[test_case("_O__%%___%%%O%" => "^.{1}O_%_.{1}%.*O.*$")] 1300 | fn test_like_pattern_to_regex_pattern(like_pattern: &str) -> String { 1301 | like_pattern_to_regex_pattern(like_pattern) 1302 | } 1303 | } 1304 | 1305 | fn dotnet_format_to_strptime_format(dotnet_format: &str) -> String { 1306 | lazy_static! { 1307 | static ref REPLACEMENTS: [(Regex, &'static str); 46] = [ 1308 | (Regex::new("dddd").unwrap(), "%A"), 1309 | (Regex::new("ddd").unwrap(), "%a"), 1310 | (Regex::new("dd").unwrap(), "%DAY"), 1311 | (Regex::new("d").unwrap(), "%e"), 1312 | (Regex::new("%DAY").unwrap(), "%d"), 1313 | // Ok it's scrappy but (? Error 1314 | // look-around, including look-ahead and look-behind, is not supported 1315 | (Regex::new("fffffff").unwrap(), "%7f"), 1316 | (Regex::new("ffffff").unwrap(), "%6f"), 1317 | (Regex::new("fffff").unwrap(), "%5f"), 1318 | (Regex::new("ffff").unwrap(), "%4f"), 1319 | (Regex::new("fff").unwrap(), "%3f"), 1320 | (Regex::new("ff").unwrap(), "%2f"), 1321 | // (Regex::new("f").unwrap(), "%1f"), // Not supporting this one, no one uses it anyway 1322 | (Regex::new("FFFFFFF").unwrap(), "%7f"), 1323 | (Regex::new("FFFFFF").unwrap(), "%6f"), 1324 | (Regex::new("FFFFF").unwrap(), "%5f"), 1325 | (Regex::new("FFFF").unwrap(), "%4f"), 1326 | (Regex::new("FFF").unwrap(), "%3f"), 1327 | (Regex::new("FF").unwrap(), "%2f"), 1328 | (Regex::new("F").unwrap(), "%1f"), 1329 | (Regex::new("hh").unwrap(), "%I"), 1330 | (Regex::new("h").unwrap(), "%l"), 1331 | (Regex::new("HH").unwrap(), "%_OURS"), 1332 | (Regex::new("H").unwrap(), "%k"), 1333 | (Regex::new("%_OURS").unwrap(), "%H"), 1334 | (Regex::new("mm").unwrap(), "%_INUTE"), // same, kind of unsupported 1335 | (Regex::new("m").unwrap(), "%_INUTE"), // same, kind of unsupported 1336 | (Regex::new("MMMM").unwrap(), "%B"), 1337 | (Regex::new("MMM").unwrap(), "%b"), 1338 | (Regex::new("MM").unwrap(), "%m"), 1339 | (Regex::new("M").unwrap(), "%m"), 1340 | (Regex::new("%_INUTE").unwrap(), "%M"), 1341 | (Regex::new("%_INUTE").unwrap(), "%M"), 1342 | (Regex::new("ss").unwrap(), "%S"), 1343 | (Regex::new("s").unwrap(), "%S"), 1344 | (Regex::new("tt").unwrap(), "%P"), 1345 | (Regex::new("t").unwrap(), "%P"), 1346 | (Regex::new("yyyyy").unwrap(), "%Y"), 1347 | (Regex::new("yyyy").unwrap(), "%Y"), 1348 | (Regex::new("yyy").unwrap(), "%Y"), 1349 | (Regex::new("yy").unwrap(), "%YEAR"), 1350 | (Regex::new("y").unwrap(), "%y"), 1351 | (Regex::new("%YEAR").unwrap(), "%y"), 1352 | (Regex::new("zzz").unwrap(), "%:_one"), 1353 | (Regex::new("zz").unwrap(), "%_one"), 1354 | (Regex::new("z").unwrap(), "%z"), 1355 | (Regex::new("%_one").unwrap(), "%z"), 1356 | (Regex::new("%:_one").unwrap(), "%:z"), 1357 | ]; 1358 | } 1359 | 1360 | let result = REPLACEMENTS.iter().fold(dotnet_format.to_string(), |acc, replacer| { 1361 | // let res = replacer.0.replace(&acc, replacer.1).to_string(); 1362 | // println!("{}", res); 1363 | // res 1364 | replacer.0.replace(&acc, replacer.1).to_string() 1365 | }); 1366 | 1367 | result 1368 | } 1369 | 1370 | // Could be replaced by ? https://github.com/chronotope/chrono-tz/ 1371 | fn get_utc_offset(time_zone_name: &str) -> Result<&'static Tz, String> { 1372 | lazy_static! { 1373 | static ref TIME_ZONES: HashMap<&'static str, Tz> = { 1374 | let mut m = HashMap::new(); 1375 | m.insert("Dateline Standard Time", chrono_tz::Etc::GMTPlus12); 1376 | m.insert("UTC-11", chrono_tz::Etc::GMTPlus11); 1377 | m.insert("Aleutian Standard Time", chrono_tz::America::Adak); 1378 | m.insert("Hawaiian Standard Time", chrono_tz::Pacific::Honolulu); 1379 | m.insert("Marquesas Standard Time", chrono_tz::Pacific::Marquesas); 1380 | m.insert("Alaskan Standard Time", chrono_tz::America::Anchorage); 1381 | m.insert("UTC-09", chrono_tz::Etc::GMTPlus9); 1382 | m.insert("Pacific Standard Time (Mexico)", chrono_tz::America::Tijuana); 1383 | m.insert("UTC-08", chrono_tz::Etc::GMTPlus8); 1384 | m.insert("Pacific Standard Time", chrono_tz::America::Los_Angeles); 1385 | m.insert("US Mountain Standard Time", chrono_tz::America::Phoenix); 1386 | m.insert("Mountain Standard Time (Mexico)", chrono_tz::America::Chihuahua); 1387 | m.insert("Mountain Standard Time", chrono_tz::America::Denver); 1388 | m.insert("Central America Standard Time", chrono_tz::America::Guatemala); 1389 | m.insert("Central Standard Time", chrono_tz::America::Chicago); 1390 | m.insert("Easter Island Standard Time", chrono_tz::Pacific::Easter); 1391 | m.insert("Central Standard Time (Mexico)", chrono_tz::America::Mexico_City); 1392 | m.insert("Canada Central Standard Time", chrono_tz::America::Regina); 1393 | m.insert("SA Pacific Standard Time", chrono_tz::America::Bogota); 1394 | m.insert("Eastern Standard Time (Mexico)", chrono_tz::America::Cancun); 1395 | m.insert("Eastern Standard Time", chrono_tz::America::New_York); 1396 | m.insert("Haiti Standard Time", chrono_tz::America::PortauPrince); 1397 | m.insert("Cuba Standard Time", chrono_tz::America::Havana); 1398 | m.insert("US Eastern Standard Time", chrono_tz::America::Indiana::Indianapolis); 1399 | m.insert("Turks And Caicos Standard Time", chrono_tz::America::Grand_Turk); 1400 | m.insert("Paraguay Standard Time", chrono_tz::America::Asuncion); 1401 | m.insert("Atlantic Standard Time", chrono_tz::America::Halifax); 1402 | m.insert("Venezuela Standard Time", chrono_tz::America::Caracas); 1403 | m.insert("Central Brazilian Standard Time", chrono_tz::America::Cuiaba); 1404 | m.insert("SA Western Standard Time", chrono_tz::America::La_Paz); 1405 | m.insert("Pacific SA Standard Time", chrono_tz::America::Santiago); 1406 | m.insert("Newfoundland Standard Time", chrono_tz::America::St_Johns); 1407 | m.insert("Tocantins Standard Time", chrono_tz::America::Araguaina); 1408 | m.insert("E. South America Standard Time", chrono_tz::America::Sao_Paulo); 1409 | m.insert("SA Eastern Standard Time", chrono_tz::America::Cayenne); 1410 | m.insert("Argentina Standard Time", chrono_tz::America::Argentina::Buenos_Aires); 1411 | m.insert("Greenland Standard Time", chrono_tz::America::Godthab); 1412 | m.insert("Montevideo Standard Time", chrono_tz::America::Montevideo); 1413 | m.insert("Magallanes Standard Time", chrono_tz::America::Punta_Arenas); 1414 | m.insert("Saint Pierre Standard Time", chrono_tz::America::Miquelon); 1415 | m.insert("Bahia Standard Time", chrono_tz::America::Bahia); 1416 | m.insert("UTC-02", chrono_tz::Etc::GMTPlus2); 1417 | m.insert("Mid-Atlantic Standard Time", chrono_tz::Etc::GMTPlus2); 1418 | m.insert("Azores Standard Time", chrono_tz::Atlantic::Azores); 1419 | m.insert("Cape Verde Standard Time", chrono_tz::Atlantic::Cape_Verde); 1420 | m.insert("UTC", chrono_tz::Etc::UTC); 1421 | m.insert("GMT Standard Time", chrono_tz::Europe::London); 1422 | m.insert("Greenwich Standard Time", chrono_tz::Atlantic::Reykjavik); 1423 | m.insert("Sao Tome Standard Time", chrono_tz::Africa::Sao_Tome); 1424 | m.insert("Morocco Standard Time", chrono_tz::Africa::Casablanca); 1425 | m.insert("W. Europe Standard Time", chrono_tz::Europe::Berlin); 1426 | m.insert("Central Europe Standard Time", chrono_tz::Europe::Budapest); 1427 | m.insert("Romance Standard Time", chrono_tz::Europe::Paris); 1428 | m.insert("Central European Standard Time", chrono_tz::Europe::Warsaw); 1429 | m.insert("W. Central Africa Standard Time", chrono_tz::Africa::Lagos); 1430 | m.insert("Jordan Standard Time", chrono_tz::Asia::Amman); 1431 | m.insert("GTB Standard Time", chrono_tz::Europe::Bucharest); 1432 | m.insert("Middle East Standard Time", chrono_tz::Asia::Beirut); 1433 | m.insert("Egypt Standard Time", chrono_tz::Africa::Cairo); 1434 | m.insert("E. Europe Standard Time", chrono_tz::Europe::Chisinau); 1435 | m.insert("Syria Standard Time", chrono_tz::Asia::Damascus); 1436 | m.insert("West Bank Standard Time", chrono_tz::Asia::Hebron); 1437 | m.insert("South Africa Standard Time", chrono_tz::Africa::Johannesburg); 1438 | m.insert("FLE Standard Time", chrono_tz::Europe::Kiev); 1439 | m.insert("Israel Standard Time", chrono_tz::Asia::Jerusalem); 1440 | m.insert("Kaliningrad Standard Time", chrono_tz::Europe::Kaliningrad); 1441 | m.insert("Sudan Standard Time", chrono_tz::Africa::Khartoum); 1442 | m.insert("Libya Standard Time", chrono_tz::Africa::Tripoli); 1443 | m.insert("Namibia Standard Time", chrono_tz::Africa::Windhoek); 1444 | m.insert("Arabic Standard Time", chrono_tz::Asia::Baghdad); 1445 | m.insert("Turkey Standard Time", chrono_tz::Europe::Istanbul); 1446 | m.insert("Arab Standard Time", chrono_tz::Asia::Riyadh); 1447 | m.insert("Belarus Standard Time", chrono_tz::Europe::Minsk); 1448 | m.insert("Russian Standard Time", chrono_tz::Europe::Moscow); 1449 | m.insert("E. Africa Standard Time", chrono_tz::Africa::Nairobi); 1450 | m.insert("Iran Standard Time", chrono_tz::Asia::Tehran); 1451 | m.insert("Arabian Standard Time", chrono_tz::Asia::Dubai); 1452 | m.insert("Astrakhan Standard Time", chrono_tz::Europe::Astrakhan); 1453 | m.insert("Azerbaijan Standard Time", chrono_tz::Asia::Baku); 1454 | m.insert("Russia Time Zone 3", chrono_tz::Europe::Samara); 1455 | m.insert("Mauritius Standard Time", chrono_tz::Indian::Mauritius); 1456 | m.insert("Saratov Standard Time", chrono_tz::Europe::Saratov); 1457 | m.insert("Georgian Standard Time", chrono_tz::Asia::Tbilisi); 1458 | m.insert("Volgograd Standard Time", chrono_tz::Europe::Volgograd); 1459 | m.insert("Caucasus Standard Time", chrono_tz::Asia::Yerevan); 1460 | m.insert("Afghanistan Standard Time", chrono_tz::Asia::Kabul); 1461 | m.insert("West Asia Standard Time", chrono_tz::Asia::Tashkent); 1462 | m.insert("Ekaterinburg Standard Time", chrono_tz::Asia::Yekaterinburg); 1463 | m.insert("Pakistan Standard Time", chrono_tz::Asia::Karachi); 1464 | m.insert("Qyzylorda Standard Time", chrono_tz::Asia::Qyzylorda); 1465 | m.insert("India Standard Time", chrono_tz::Asia::Kolkata); 1466 | m.insert("Sri Lanka Standard Time", chrono_tz::Asia::Colombo); 1467 | m.insert("Nepal Standard Time", chrono_tz::Asia::Kathmandu); 1468 | m.insert("Central Asia Standard Time", chrono_tz::Asia::Almaty); 1469 | m.insert("Bangladesh Standard Time", chrono_tz::Asia::Dhaka); 1470 | m.insert("Omsk Standard Time", chrono_tz::Asia::Omsk); 1471 | m.insert("Myanmar Standard Time", chrono_tz::Asia::Yangon); 1472 | m.insert("SE Asia Standard Time", chrono_tz::Asia::Bangkok); 1473 | m.insert("Altai Standard Time", chrono_tz::Asia::Barnaul); 1474 | m.insert("W. Mongolia Standard Time", chrono_tz::Asia::Hovd); 1475 | m.insert("North Asia Standard Time", chrono_tz::Asia::Krasnoyarsk); 1476 | m.insert("N. Central Asia Standard Time", chrono_tz::Asia::Novosibirsk); 1477 | m.insert("Tomsk Standard Time", chrono_tz::Asia::Tomsk); 1478 | m.insert("China Standard Time", chrono_tz::Asia::Shanghai); 1479 | m.insert("North Asia East Standard Time", chrono_tz::Asia::Irkutsk); 1480 | m.insert("Singapore Standard Time", chrono_tz::Asia::Singapore); 1481 | m.insert("W. Australia Standard Time", chrono_tz::Australia::Perth); 1482 | m.insert("Taipei Standard Time", chrono_tz::Asia::Taipei); 1483 | m.insert("Ulaanbaatar Standard Time", chrono_tz::Asia::Ulaanbaatar); 1484 | m.insert("Aus Central W. Standard Time", chrono_tz::Australia::Eucla); 1485 | m.insert("Transbaikal Standard Time", chrono_tz::Asia::Chita); 1486 | m.insert("Tokyo Standard Time", chrono_tz::Asia::Tokyo); 1487 | m.insert("North Korea Standard Time", chrono_tz::Asia::Pyongyang); 1488 | m.insert("Korea Standard Time", chrono_tz::Asia::Seoul); 1489 | m.insert("Yakutsk Standard Time", chrono_tz::Asia::Yakutsk); 1490 | m.insert("Cen. Australia Standard Time", chrono_tz::Australia::Adelaide); 1491 | m.insert("AUS Central Standard Time", chrono_tz::Australia::Darwin); 1492 | m.insert("E. Australia Standard Time", chrono_tz::Australia::Brisbane); 1493 | m.insert("AUS Eastern Standard Time", chrono_tz::Australia::Sydney); 1494 | m.insert("West Pacific Standard Time", chrono_tz::Pacific::Port_Moresby); 1495 | m.insert("Tasmania Standard Time", chrono_tz::Australia::Hobart); 1496 | m.insert("Vladivostok Standard Time", chrono_tz::Asia::Vladivostok); 1497 | m.insert("Lord Howe Standard Time", chrono_tz::Australia::Lord_Howe); 1498 | m.insert("Bougainville Standard Time", chrono_tz::Pacific::Bougainville); 1499 | m.insert("Russia Time Zone 10", chrono_tz::Asia::Srednekolymsk); 1500 | m.insert("Magadan Standard Time", chrono_tz::Asia::Magadan); 1501 | m.insert("Norfolk Standard Time", chrono_tz::Pacific::Norfolk); 1502 | m.insert("Sakhalin Standard Time", chrono_tz::Asia::Sakhalin); 1503 | m.insert("Central Pacific Standard Time", chrono_tz::Pacific::Guadalcanal); 1504 | m.insert("Russia Time Zone 11", chrono_tz::Asia::Kamchatka); 1505 | m.insert("New Zealand Standard Time", chrono_tz::Pacific::Auckland); 1506 | m.insert("UTC+12", chrono_tz::Etc::GMTMinus12); 1507 | m.insert("Fiji Standard Time", chrono_tz::Pacific::Fiji); 1508 | m.insert("Kamchatka Standard Time", chrono_tz::Asia::Kamchatka); 1509 | m.insert("Chatham Islands Standard Time", chrono_tz::Pacific::Chatham); 1510 | m.insert("UTC+13", chrono_tz::Etc::GMTMinus13); 1511 | m.insert("Tonga Standard Time", chrono_tz::Pacific::Tongatapu); 1512 | m.insert("Samoa Standard Time", chrono_tz::Pacific::Apia); 1513 | m.insert("Line Islands Standard Time", chrono_tz::Pacific::Kiritimati); 1514 | m 1515 | }; 1516 | }; 1517 | 1518 | if let Some(time_zone) = TIME_ZONES.get(time_zone_name) { 1519 | Ok(time_zone) 1520 | } else { 1521 | Err(format!("Unable to find a time zone named '{}'", time_zone_name)) 1522 | } 1523 | } 1524 | 1525 | fn naive_datetime_to_timezone(datetime: &NaiveDateTime, time_zone_name: &str) -> ExprFuncResult { 1526 | let timezone = get_utc_offset(time_zone_name)?; 1527 | let local = timezone.from_utc_datetime(datetime); 1528 | // dbg!(datetime, timezone, local); 1529 | Ok(ExprResult::Date(local.naive_local())) 1530 | } 1531 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![cfg(feature = "alloc")] 2 | // #[global_allocator] 3 | // static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; 4 | 5 | // #![feature(result_map_or_else)] 6 | #![deny(bare_trait_objects)] 7 | 8 | #[macro_use] 9 | extern crate lazy_static; 10 | 11 | pub mod expressions; 12 | pub mod ffi; 13 | mod functions; 14 | mod parsing; 15 | -------------------------------------------------------------------------------- /src/parsing.rs: -------------------------------------------------------------------------------- 1 | use crate::expressions::*; 2 | 3 | use nom::{ 4 | branch::alt, 5 | bytes::complete::{escaped, tag, take_while1}, // escaped_transform 6 | character::complete::{alphanumeric1, char, multispace0, one_of}, 7 | combinator::{map, opt, recognize}, 8 | error::{context, ParseError}, 9 | number::complete::double, 10 | sequence::{delimited, preceded, tuple}, 11 | IResult, 12 | }; 13 | use rust_decimal::prelude::FromPrimitive; 14 | use std::cell::RefCell; 15 | use unescape::unescape; 16 | use unicase::UniCase; 17 | 18 | #[derive(Debug)] 19 | enum Lex { 20 | ParenthesisOpen, 21 | ParenthesisClose, 22 | Comma, 23 | FunctionOpen(String), 24 | Expr(crate::expressions::Expr), 25 | Op(crate::expressions::AssocOp), 26 | } 27 | 28 | /// A nom parser has the following signature: 29 | /// `Input -> IResult`, with `IResult` defined as: 30 | /// `type IResult = Result<(I, O), Err>;` 31 | 32 | // string parser from here : https://github.com/Geal/nom/issues/1075 33 | fn string<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { 34 | delimited( 35 | char('\"'), 36 | map( 37 | opt(escaped( 38 | take_while1(|c| c != '\\' && c != '"'), 39 | '\\', 40 | one_of("\"\\/bfnrtu"), // Note, this will not detect invalid unicode escapes. 41 | )), 42 | |o| o.unwrap_or_default(), 43 | ), 44 | char('\"'), 45 | )(input) 46 | } 47 | 48 | fn boolean<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> { 49 | context("boolean", alt((map(tag("false"), |_| false), map(tag("true"), |_| true))))(input) 50 | } 51 | 52 | fn null<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Expr, E> { 53 | let (input, _) = tag("null")(input)?; 54 | Ok((input, Expr::Null)) 55 | } 56 | 57 | fn identifier<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { 58 | preceded(opt(tag("@")), recognize(tuple((opt(tag("_")), alphanumeric1))))(input) 59 | } 60 | 61 | fn binary_operator<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, AssocOp, E> { 62 | alt(( 63 | map(tag("+"), |_| AssocOp::Add), 64 | map(tag("-"), |_| AssocOp::Subtract), 65 | map(tag("*"), |_| AssocOp::Multiply), 66 | map(tag("<="), |_| AssocOp::LessEqual), 67 | map(tag("<"), |_| AssocOp::Less), 68 | map(tag(">="), |_| AssocOp::GreaterEqual), 69 | map(tag(">"), |_| AssocOp::Greater), 70 | map(tag("/"), |_| AssocOp::Divide), 71 | map(tag("=="), |_| AssocOp::Equal), 72 | map(tag("!="), |_| AssocOp::NotEqual), 73 | map(tag("%"), |_| AssocOp::Modulus), 74 | map(tag("&&"), |_| AssocOp::LAnd), 75 | map(tag("||"), |_| AssocOp::LOr), 76 | ))(input) 77 | } 78 | 79 | fn open_parenthesis<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Lex, E> { 80 | let (input, _) = char('(')(input)?; 81 | Ok((input, Lex::ParenthesisOpen)) 82 | } 83 | fn close_parenthesis<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Lex, E> { 84 | let (input, _) = char(')')(input)?; 85 | Ok((input, Lex::ParenthesisClose)) 86 | } 87 | fn comma<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Lex, E> { 88 | let (input, _) = char(',')(input)?; 89 | Ok((input, Lex::Comma)) 90 | } 91 | 92 | fn open_function<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { 93 | let (input, name) = identifier(input)?; 94 | let (input, _) = multispace0(input)?; 95 | let (input, _) = open_parenthesis(input)?; 96 | Ok((input, name)) 97 | } 98 | 99 | fn full_lexer<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Lex, E> { 100 | alt(( 101 | open_parenthesis, 102 | close_parenthesis, 103 | comma, 104 | map(binary_operator, |op| Lex::Op(op)), 105 | map(string, |s| Lex::Expr(Expr::Str(unescape(s).unwrap()))), 106 | map(null, |_| Lex::Expr(Expr::Null)), 107 | map(boolean, |b| Lex::Expr(Expr::Boolean(b))), 108 | map(double, |d| Lex::Expr(Expr::Num(FromPrimitive::from_f64(d).unwrap()))), 109 | map(open_function, |id| Lex::FunctionOpen(id.into())), 110 | map(identifier, |id| Lex::Expr(Expr::Identifier(id.into()))), 111 | ))(input) 112 | } 113 | 114 | fn second_chance_lexer<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Expr, E> { 115 | map(double, |d| Expr::Num(FromPrimitive::from_f64(d).unwrap()))(input) 116 | } 117 | 118 | #[derive(Debug)] 119 | struct ParserMachine { 120 | parsers: Vec, 121 | } 122 | 123 | enum OperatorParseTryResult { 124 | Ok, 125 | ShouldBeANumber, 126 | } 127 | 128 | impl ParserMachine { 129 | fn new() -> ParserMachine { 130 | // let mut machine = ParserMachine { parsers: vec![] }; 131 | // machine.push_parser(ParsingState::Started); 132 | // machine 133 | ParserMachine { parsers: vec![] } 134 | } 135 | 136 | fn push_parser(&mut self, state: ParsingState) { 137 | self.parsers.push(Parser { state: state }); 138 | } 139 | 140 | fn ensure_one_parser(&mut self) { 141 | if self.parsers.is_empty() { 142 | self.push_parser(ParsingState::Started); 143 | } 144 | } 145 | 146 | fn current_parser_state<'a>(&'a mut self) -> &'a ParsingState { 147 | self.ensure_one_parser(); 148 | &(self.parsers.last_mut().unwrap().state) 149 | } 150 | 151 | fn current_parser_mut<'a>(&'a mut self) -> &'a mut Parser { 152 | self.ensure_one_parser(); 153 | self.parsers.last_mut().unwrap() 154 | } 155 | 156 | fn current_parser_read<'a>(&'a mut self) -> &'a Parser { 157 | self.ensure_one_parser(); 158 | self.parsers.last().unwrap() 159 | } 160 | 161 | fn open_parenthesis(&mut self) { 162 | let current = self.current_parser_mut(); 163 | let new_state = ParsingState::JustParenthesis(None); 164 | match ¤t.state { 165 | ParsingState::Started => { 166 | current.state = new_state; 167 | } 168 | _ => self.push_parser(new_state), 169 | }; 170 | } 171 | 172 | fn open_function(&mut self, name: String) { 173 | let next_state = ParsingState::Function(UniCase::new(name.clone()), RefCell::new(vec![]), false); 174 | self.push_parser(next_state); 175 | } 176 | 177 | fn comma(&mut self) { 178 | let current = self.current_parser_mut(); 179 | match ¤t.state { 180 | ParsingState::Function(s, p, false) => { 181 | current.state = ParsingState::Function(s.clone(), p.clone(), true); 182 | return; 183 | } 184 | _ => { 185 | dbg!(current); 186 | todo!("Unable to handle a comma ',' here"); 187 | } 188 | }; 189 | } 190 | 191 | fn expression(&mut self, expr: RcExpr) { 192 | let current = self.current_parser_mut(); 193 | match ¤t.state { 194 | ParsingState::Function(s, p, has_comma) => { 195 | let parameters = p.clone().into_inner(); 196 | if !has_comma && parameters.len() != 0 { 197 | dbg!(&self); 198 | todo!("There should be a comma to separate arguments") 199 | } 200 | p.borrow_mut().push(expr); 201 | current.state = ParsingState::Function(s.clone(), p.clone(), false); 202 | } 203 | ParsingState::AwaitingNextOperand(e, op) => { 204 | current.state = ParsingState::Expr(RcExpr::new(Expr::BinaryOperator(e.clone(), expr, op.clone()))); 205 | } 206 | ParsingState::Started => { 207 | current.state = ParsingState::Expr(expr); 208 | } 209 | ParsingState::JustParenthesis(None) => { 210 | current.state = ParsingState::JustParenthesis(Some(expr)); 211 | } 212 | _ => { 213 | dbg!(current); 214 | todo!("Unable to handle an expression here") 215 | } 216 | }; 217 | } 218 | 219 | fn operator(&mut self, op: AssocOp) -> OperatorParseTryResult { 220 | let current = self.current_parser_mut(); 221 | let (current_state_to_change, next_state_to_push, result) = match ¤t.state { 222 | ParsingState::Expr(e) => (Some(ParsingState::AwaitingNextOperand(e.clone(), op)), None, OperatorParseTryResult::Ok), 223 | ParsingState::Function(n, p, false) => { 224 | let mut parameters = p.borrow_mut(); 225 | if parameters.len() == 0 { 226 | (None, None, OperatorParseTryResult::ShouldBeANumber) 227 | } else { 228 | let expr = parameters.pop().unwrap(); 229 | drop(parameters); 230 | let expr = expr.clone(); 231 | ( 232 | Some(ParsingState::Function(n.clone(), p.clone(), true)), 233 | Some(ParsingState::AwaitingNextOperand(expr, op)), 234 | OperatorParseTryResult::Ok, 235 | ) 236 | } 237 | } 238 | ParsingState::JustParenthesis(Some(expr)) => (Some(ParsingState::JustParenthesis(None)), Some(ParsingState::AwaitingNextOperand(expr.clone(), op)), OperatorParseTryResult::Ok), 239 | _ => (None, None, OperatorParseTryResult::ShouldBeANumber), 240 | }; 241 | 242 | if let Some(s) = current_state_to_change { 243 | current.state = s; 244 | } 245 | if let Some(s) = next_state_to_push { 246 | self.push_parser(s); 247 | } 248 | 249 | result 250 | } 251 | 252 | fn close_parenthesis(&mut self) { 253 | let current = self.current_parser_mut(); 254 | match ¤t.state { 255 | ParsingState::Function(s, p, false) => { 256 | let parameters = p.clone().into_inner(); 257 | let expr = RcExpr::new(Expr::FunctionCall(s.clone(), parameters)); 258 | current.state = ParsingState::Expr(expr); 259 | } 260 | ParsingState::JustParenthesis(Some(expr)) => { 261 | current.state = ParsingState::Expr(expr.clone()); 262 | } 263 | _ => { 264 | dbg!(current); 265 | todo!("Unable to close any parenthesis here") 266 | } 267 | } 268 | } 269 | 270 | fn finalize(mut self) -> Expr { 271 | self.reduce(); 272 | 273 | if self.parsers.len() != 1 { 274 | dbg!(&self); 275 | todo!("There should be nothing else than one expression."); 276 | } 277 | 278 | RcExpr::try_unwrap(self.parsers.pop().unwrap().finalize()).unwrap() 279 | } 280 | 281 | fn reduce(&mut self) { 282 | while (self.parsers.len() > 1) && self.parsers.last().unwrap().is_final() { 283 | let expr = self.parsers.pop().unwrap().finalize(); 284 | self.expression(expr); 285 | } 286 | } 287 | } 288 | 289 | #[derive(Debug)] 290 | struct Parser { 291 | state: ParsingState, 292 | } 293 | 294 | #[derive(Debug)] 295 | enum ParsingState { 296 | Started, 297 | JustParenthesis(Option), 298 | Expr(RcExpr), 299 | AwaitingNextOperand(RcExpr, AssocOp), 300 | Function(UniCase, RefCell, bool), 301 | } 302 | 303 | impl Parser { 304 | fn finalize(self) -> RcExpr { 305 | match self.try_finalize() { 306 | Some(e) => e, 307 | None => todo!("Cannot finalize parser in this state"), 308 | } 309 | } 310 | fn try_finalize(self) -> Option { 311 | match self.state { 312 | ParsingState::Expr(e) => Some(e), 313 | _ => None, 314 | } 315 | } 316 | fn is_final(&self) -> bool { 317 | match self.state { 318 | ParsingState::Expr(_) => true, 319 | _ => false, 320 | } 321 | } 322 | } 323 | 324 | fn parser<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Expr, E> { 325 | let mut machine = ParserMachine::new(); 326 | 327 | let (input, _) = multispace0(input)?; 328 | let mut input = input; 329 | while input.len() != 0 { 330 | let (i, lex) = full_lexer(input)?; 331 | // dbg!(&machine, &input, &lex); 332 | let mut i = i; 333 | match lex { 334 | Lex::ParenthesisOpen => machine.open_parenthesis(), 335 | Lex::ParenthesisClose => machine.close_parenthesis(), 336 | Lex::Expr(e) => machine.expression(RcExpr::new(e)), 337 | Lex::Op(op) => { 338 | if let OperatorParseTryResult::ShouldBeANumber = machine.operator(op) { 339 | let (i2, expr) = second_chance_lexer(input)?; 340 | machine.expression(RcExpr::new(expr)); 341 | i = i2; 342 | } 343 | } 344 | Lex::Comma => machine.comma(), 345 | Lex::FunctionOpen(s) => machine.open_function(s), 346 | } 347 | machine.reduce(); 348 | let (i, _) = multispace0(i)?; 349 | input = i; 350 | } 351 | 352 | Ok((input, machine.finalize())) 353 | } 354 | 355 | pub fn expr<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Expr, E> { 356 | parser(input) 357 | } 358 | 359 | #[cfg(test)] 360 | mod tests { 361 | use super::*; 362 | use nom::error::ErrorKind; 363 | use rust_decimal_macros::*; 364 | use std::rc::Rc; 365 | use std::time::Instant; 366 | use test_case::test_case; 367 | 368 | macro_rules! unicase { 369 | ( $x:expr ) => { 370 | UniCase::new($x.to_string()) 371 | }; 372 | } 373 | macro_rules! rc_expr_str { 374 | ( $x:expr ) => { 375 | Rc::new(Expr::Str($x.into())) 376 | }; 377 | } 378 | macro_rules! rc_expr_id { 379 | ( $x:expr ) => { 380 | Rc::new(Expr::Identifier($x.to_string())) 381 | }; 382 | } 383 | macro_rules! rc_expr_num { 384 | ( $x:expr ) => { 385 | Rc::new(Expr::Num(dec!($x))) 386 | }; 387 | } 388 | 389 | #[test_case("+", AssocOp::Add)] 390 | #[test_case("-", AssocOp::Subtract)] 391 | #[test_case("/", AssocOp::Divide)] 392 | #[test_case("&&", AssocOp::LAnd)] 393 | fn binary_operator_test(text: &str, expected: AssocOp) { 394 | let result = binary_operator::<(&str, ErrorKind)>(text); 395 | assert_eq!(result, Ok(("", expected))); 396 | } 397 | 398 | #[test_case(stringify!("null") => "null")] 399 | #[test_case(stringify!("test") => "test")] 400 | #[test_case(stringify!("t") => "t")] 401 | #[test_case("\"test escape\"" => "test escape")] 402 | #[test_case(stringify!("test escape") => "test escape")] 403 | #[test_case(stringify!("test\"doublequote") => "test\"doublequote")] 404 | #[test_case(stringify!("test\\slash") => "test\\slash")] 405 | #[test_case("\"test\nnewline\"" => "test\nnewline")] 406 | #[test_case("\"test\ttab\"" => "test\ttab")] 407 | #[test_case("\"test\rreturn\"" => "test\rreturn")] 408 | #[test_case("\"\\t\"" => "\t")] 409 | fn string_parser_tests(text: &str) -> String { 410 | let (i, result) = full_lexer::<(&str, ErrorKind)>(text).unwrap(); 411 | if i.len() != 0 { 412 | dbg!(i, &result); 413 | assert_eq!(i.len(), 0); 414 | } 415 | if let Lex::Expr(Expr::Str(s)) = result { 416 | s.clone() 417 | } else { 418 | dbg!(&result); 419 | panic!("Should be a Lex::Expr(Expr::Str())"); 420 | } 421 | } 422 | 423 | #[test_case("1+2")] 424 | fn parser_machine_test(expression: &str) { 425 | super_show(expression); 426 | } 427 | 428 | fn super_show(expression: &str) { 429 | let (input, lexed) = get_lexed::<(&str, ErrorKind)>(expression).unwrap(); 430 | dbg!(&lexed); 431 | // assert_eq!(input.len(), 0); 432 | 433 | // let mut machine = ParserMachine::new(); 434 | // for lex in lexed { 435 | // dbg!(&lex); 436 | // machine.lex(lex); 437 | // dbg!(&machine); 438 | // } 439 | 440 | // machine.finalize(); 441 | } 442 | 443 | fn get_lexed<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Vec, E> { 444 | let mut lexed = vec![]; 445 | let (input, _) = multispace0(input)?; 446 | let mut input = input; 447 | while input.len() != 0 { 448 | let (i, lex) = full_lexer(input)?; 449 | dbg!(&lex); 450 | lexed.push(lex); 451 | let (i, _) = multispace0(i)?; 452 | input = i; 453 | } 454 | Ok((input, lexed)) 455 | } 456 | 457 | #[test_case("1+2", (Expr::Num(dec!(1)), Expr::Num(dec!(2)), AssocOp::Add))] 458 | #[test_case("(3)+(4)", (Expr::Num(dec!(3)), Expr::Num(dec!(4)), AssocOp::Add))] 459 | #[test_case(" 3- 2 ", (Expr::Num(dec!(3)), Expr::Num(dec!(2)), AssocOp::Subtract))] 460 | #[test_case(" 3 /2", (Expr::Num(dec!(3)), Expr::Num(dec!(2)), AssocOp::Divide))] 461 | #[test_case("3|| 5 ", (Expr::Num(dec!(3)), Expr::Num(dec!(5)), AssocOp::LOr))] 462 | #[test_case("5 * 5", (Expr::Num(dec!(5)), Expr::Num(dec!(5)), AssocOp::Multiply))] 463 | #[test_case(" 42 % \"2\"", (Expr::Num(dec!(42)), Expr::Str("2".to_string()), AssocOp::Modulus))] 464 | #[test_case("2 == 2", (Expr::Num(dec!(2)), Expr::Num(dec!(2)), AssocOp::Equal))] 465 | #[test_case("2 > 2", (Expr::Num(dec!(2)), Expr::Num(dec!(2)), AssocOp::Greater))] 466 | #[test_case("2 < 2", (Expr::Num(dec!(2)), Expr::Num(dec!(2)), AssocOp::Less))] 467 | #[test_case("2 >= 2", (Expr::Num(dec!(2)), Expr::Num(dec!(2)), AssocOp::GreaterEqual))] 468 | #[test_case("2 <= 2", (Expr::Num(dec!(2)), Expr::Num(dec!(2)), AssocOp::LessEqual))] 469 | #[test_case("true && false", (Expr::Boolean(true), Expr::Boolean(false), AssocOp::LAnd))] 470 | #[test_case("false || true", (Expr::Boolean(false), Expr::Boolean(true), AssocOp::LOr))] 471 | fn binary_operations_test(text: &str, expected: (Expr, Expr, AssocOp)) { 472 | let result = parser::<(&str, ErrorKind)>(text); 473 | let (left, right, op) = expected; 474 | assert_eq!(result, Ok(("", Expr::BinaryOperator(RcExpr::new(left), RcExpr::new(right), op)))); 475 | } 476 | 477 | #[test_case("test()", Expr::FunctionCall(unicase!("test"), VecRcExpr::new()))] 478 | #[test_case(" toto () ", Expr::FunctionCall(unicase!("toto"), VecRcExpr::new()))] 479 | #[test_case(" toto (toto()) ", Expr::FunctionCall(unicase!("toto"), vec![RcExpr::new(Expr::FunctionCall(unicase!("toto"), VecRcExpr::new()))]))] 480 | #[test_case("toto((null - null)) ", Expr::FunctionCall(unicase!("toto"), vec![RcExpr::new(Expr::BinaryOperator( RcExpr::new(Expr::Null),RcExpr::new(Expr::Null), AssocOp::Subtract))]))] 481 | #[test_case("(null - null) ", Expr::BinaryOperator(RcExpr::new(Expr::Null), RcExpr::new(Expr::Null), AssocOp::Subtract))] 482 | #[test_case("2 - null ", Expr::BinaryOperator(RcExpr::new(Expr::Num(dec!(2))), RcExpr::new(Expr::Null), AssocOp::Subtract))] 483 | #[test_case("tata(null - null) ", Expr::FunctionCall(unicase!("tata"), vec![RcExpr::new(Expr::BinaryOperator( RcExpr::new(Expr::Null),RcExpr::new(Expr::Null), AssocOp::Subtract))]))] 484 | #[test_case("Find(\"\\t\", \"bo\\tbo\")", Expr::FunctionCall(unicase!("Find"), vec![rc_expr_str!("\t"), rc_expr_str!("bo\tbo")]))] 485 | fn parse_some_expr(text: &str, expected: Expr) { 486 | let result = parse_expr(text).unwrap(); 487 | assert_eq!(result, expected); 488 | } 489 | 490 | #[test] 491 | fn binary_operation_parenthesis_test() { 492 | let expected = Ok(( 493 | "", 494 | Expr::BinaryOperator( 495 | RcExpr::new(Expr::BinaryOperator(RcExpr::new(Expr::Num(dec!(3))), RcExpr::new(Expr::Num(dec!(5))), AssocOp::Divide)), 496 | RcExpr::new(Expr::Str("2".to_string())), 497 | AssocOp::Subtract, 498 | ), 499 | )); 500 | assert_eq!(parser::<(&str, ErrorKind)>("3 / 5-\"2\""), expected); 501 | assert_eq!(parser::<(&str, ErrorKind)>("(3) / (5) -(\"2\")"), expected); 502 | assert_eq!(parser::<(&str, ErrorKind)>("(3 / 5-\"2\")"), expected); 503 | } 504 | 505 | #[test_case("true" => Expr::Boolean(true))] 506 | #[test_case("false" => Expr::Boolean(false))] 507 | fn parse_boolean(expression: &str) -> Expr { 508 | parse_expr(expression).unwrap() 509 | } 510 | 511 | #[test_case("1 + 2" => Expr::BinaryOperator(rc_expr_num!(1), rc_expr_num!(2), AssocOp::Add))] 512 | #[test_case("(4) + (2)" => Expr::BinaryOperator(rc_expr_num!(4), rc_expr_num!(2), AssocOp::Add))] 513 | #[test_case("(1 * 3)" => Expr::BinaryOperator(rc_expr_num!(1), rc_expr_num!(3), AssocOp::Multiply))] 514 | fn parse_binary_operator(expression: &str) -> Expr { 515 | parse_expr(expression).unwrap() 516 | } 517 | 518 | #[test] 519 | fn parse_empty_string() { 520 | let expr = parse_expr("\"\"").unwrap(); 521 | assert_eq!(expr, Expr::Str("".to_string())) 522 | } 523 | 524 | #[test] 525 | fn parse_simple() { 526 | assert_eq!(parser::<(&str, ErrorKind)>("true"), Ok(("", Expr::Boolean(true)))); 527 | assert_eq!(parser::<(&str, ErrorKind)>("2"), Ok(("", Expr::Num(dec!(2))))); 528 | assert_eq!(parser::<(&str, ErrorKind)>("(true)"), Ok(("", Expr::Boolean(true)))); 529 | assert_eq!(parser::<(&str, ErrorKind)>("(-2)"), Ok(("", Expr::Num(dec!(-2))))); 530 | // assert_eq!(parser::<(&str, ErrorKind)>("true)"), Ok((")", Expr::Boolean(true)))); 531 | // assert_eq!(parser::<(&str, ErrorKind)>("(true"), Err(nom::Err::Error(("", ErrorKind::Char)))); 532 | // assert_eq!(parser::<(&str, ErrorKind)>("2 ,"), Ok((",", Expr::Num(dec!(2))))); 533 | // assert_eq!(parser::<(&str, ErrorKind)>("(2) ,"), Ok((" ,", Expr::Num(dec!(2))))); 534 | } 535 | 536 | #[test] 537 | fn parse_identifier_or_function_call() { 538 | assert_eq!(parser::<(&str, ErrorKind)>("true"), Ok(("", Expr::Boolean(true)))); 539 | assert_eq!(parser::<(&str, ErrorKind)>("(true)"), Ok(("", Expr::Boolean(true)))); 540 | assert_eq!(parser::<(&str, ErrorKind)>("( true )"), Ok(("", Expr::Boolean(true)))); 541 | assert_eq!(parser::<(&str, ErrorKind)>("( _id )"), Ok(("", Expr::Identifier("_id".into())))); 542 | assert_eq!(parser::<(&str, ErrorKind)>("( id ( ) )"), Ok(("", Expr::FunctionCall(unicase!("id"), vec!())))); 543 | assert_eq!(parser::<(&str, ErrorKind)>("_id()"), Ok(("", Expr::FunctionCall(unicase!("_id"), vec!())))); 544 | // assert_eq!(parser::<(&str, ErrorKind)>("_id()toto"), Ok(("toto", Expr::FunctionCall(unicase!("_id"), vec!())))); 545 | assert_eq!(parser::<(&str, ErrorKind)>("_id(1,2)"), Ok(("", Expr::FunctionCall(unicase!("_id"), vec![rc_expr_num!(1), rc_expr_num!(2)])))); 546 | // assert_eq!(parser::<(&str, ErrorKind)>("_id( a , b"), Err(nom::Err::Error(("", ErrorKind::Char)))); 547 | // assert_eq!(parser::<(&str, ErrorKind)>("id("), Err(nom::Err::Error(("", ErrorKind::Char)))); 548 | } 549 | 550 | #[test_case(stringify!("null") => "null")] 551 | #[test_case(stringify!("test") => "test")] 552 | #[test_case(stringify!("€") => "€")] 553 | #[test_case(stringify!("t") => "t")] 554 | #[test_case("\"test escape\"" => "test escape")] 555 | #[test_case("\"test\ttab\"" => "test\ttab")] 556 | #[test_case(stringify!("test escape") => "test escape")] 557 | // #[test_case(stringify!("test\"doublequote") => "test\"doublequote")] 558 | #[test_case(stringify!("test\\slash") => "test\\slash")] 559 | // #[test_case(stringify!("test\newline") => "test\newline")] 560 | // #[test_case(stringify!("test\ttab") => "test\ttab")] 561 | // #[test_case(stringify!("test\rreturn") => "test\rreturn")] 562 | fn parse_str(expression: &str) -> String { 563 | let result = parse_expr(expression); 564 | println!("{:?}", result); 565 | let expr = result.unwrap(); 566 | if let Expr::Str(result) = expr { 567 | result 568 | } else { 569 | panic!("{:?}", expr) 570 | } 571 | } 572 | 573 | #[test] 574 | fn parse_complex_str() { 575 | let result = parse_expr("\" te sΓé¼t\tt ab ./\""); 576 | assert_eq!(result, Ok(Expr::Str(" te sΓé¼t\tt ab ./".to_string()))); 577 | } 578 | 579 | #[test_case("1" => Expr::Num(dec!(1)))] 580 | #[test_case("1.2" => Expr::Num(dec!(1.2)))] 581 | #[test_case("-0.42" => Expr::Num(dec!(-0.42)))] 582 | #[test_case("(3.14)" => Expr::Num(dec!(3.14)))] 583 | #[test_case("(((42)))" => Expr::Num(dec!(42)))] 584 | fn parse_num(expression: &str) -> Expr { 585 | parse_expr(expression).unwrap() 586 | } 587 | 588 | #[test_case("null" => Expr::Null)] 589 | fn parse_null(expression: &str) -> Expr { 590 | parse_expr(expression).unwrap() 591 | } 592 | 593 | #[test_case("a" => Expr::Identifier("a".to_string()))] 594 | #[test_case("(b)" => Expr::Identifier("b".to_string()))] 595 | #[test_case("id" => Expr::Identifier("id".to_string()))] 596 | #[test_case(" hello " => Expr::Identifier("hello".to_string()))] 597 | #[test_case("@idarobase" => Expr::Identifier("idarobase".to_string()))] 598 | // #[test_case("id_id" => Expr::Identifier("id_id".to_string()))] // to debug 599 | #[test_case("id42" => Expr::Identifier("id42".to_string()))] 600 | #[test_case("_id0" => Expr::Identifier("_id0".to_string()))] 601 | #[test_case("_id1" => Expr::Identifier("_id1".to_string()))] 602 | fn parse_identifier(expression: &str) -> Expr { 603 | parse_expr(expression).unwrap() 604 | } 605 | 606 | #[test_case("test(1,2)" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_num!(1), rc_expr_num!(2)]))] 607 | #[test_case("(test( ( 3 ), (4)))" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_num!(3), rc_expr_num!(4)]))] 608 | #[test_case("test ( 1 , 42 )" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_num!(1), rc_expr_num!(42)]))] 609 | #[test_case("test()" => Expr::FunctionCall(unicase!("test"), vec!()))] 610 | #[test_case("test((test()))" => Expr::FunctionCall(unicase!("test"), vec![Rc::new(Expr::FunctionCall(unicase!("test"), vec![]))]))] 611 | #[test_case("test(aa)" => Expr::FunctionCall(unicase!("test"), vec![Rc::new(Expr::Identifier("aa".to_string()))]))] 612 | #[test_case("Test(42)" => Expr::FunctionCall(unicase!("Test"), vec![rc_expr_num!(42)]))] 613 | #[test_case("Test(1 / 2)" => Expr::FunctionCall(unicase!("Test"), vec![RcExpr::new(Expr::BinaryOperator(rc_expr_num!(1), rc_expr_num!(2), AssocOp::Divide))]))] 614 | fn parse_function_call(expression: &str) -> Expr { 615 | parse_expr(expression).unwrap() 616 | } 617 | 618 | #[test_case("test(\"value\" , 2 , \"null\")" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_str!("value"), rc_expr_num!(2), rc_expr_str!("null")]))] 619 | #[test_case("hello" => Expr::Identifier("hello".into()))] 620 | #[test_case("\"€\"" => Expr::Str("€".into()))] 621 | #[test_case(" _hella " => Expr::Identifier("_hella".into()))] 622 | #[test_case(" helloworld " => Expr::Identifier("helloworld".into()))] 623 | #[test_case("test(\"value\")" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_str!("value")]))] 624 | #[test_case("test(\"va lue\")" => Expr::FunctionCall(unicase!("test"), vec![rc_expr_str!("va lue")]))] 625 | #[test_case("test(\"va lue\") - 3" => Expr::BinaryOperator(RcExpr::new( Expr::FunctionCall(unicase!("test"), vec![rc_expr_str!("va lue")])), rc_expr_num!(3), AssocOp::Subtract))] 626 | #[test_case("42 / test(\"va lue\")" => Expr::BinaryOperator(rc_expr_num!(42), RcExpr::new(Expr::FunctionCall(unicase!("test"), vec![rc_expr_str!("va lue")])), AssocOp::Divide))] 627 | #[test_case("42 \r\n \t / func()" => Expr::BinaryOperator(rc_expr_num!(42), RcExpr::new(Expr::FunctionCall(unicase!("func"), vec![])), AssocOp::Divide))] 628 | #[test_case("(43 \r\n \t / ( func() ) )" => Expr::BinaryOperator(rc_expr_num!(43), RcExpr::new(Expr::FunctionCall(unicase!("func"), vec![])), AssocOp::Divide))] 629 | #[test_case("Func(2 + 1, 42)" => Expr::FunctionCall(unicase!("Func"), vec![RcExpr::new(Expr::BinaryOperator(rc_expr_num!(2), rc_expr_num!(1), AssocOp::Add)), rc_expr_num!(42)]))] 630 | #[test_case(" IIF ( ISLIKE(@var0, \"hello\" ), NUMBERVALUE( @var1 ) * NUMBERVALUE( 1.5), @var2 )" => Expr::FunctionCall(unicase!("IIF"), vec![RcExpr::new(Expr::FunctionCall(unicase!("ISLIKE"), vec![rc_expr_id!("var0"), rc_expr_str!("hello")])), RcExpr::new(Expr::BinaryOperator(RcExpr::new(Expr::FunctionCall(unicase!("NUMBERVALUE"), vec![rc_expr_id!("var1")])), RcExpr::new(Expr::FunctionCall(unicase!("NUMBERVALUE"), vec![rc_expr_num!(1.5)])), AssocOp::Multiply)), rc_expr_id!("var2") ]))] 631 | fn parse_complexe_expressions(expression: &'static str) -> Expr { 632 | let expr = expr::<(&str, ErrorKind)>(expression); 633 | match expr { 634 | Ok((rest, expr)) => match rest.len() { 635 | 0 => expr, 636 | _ => { 637 | dbg!(expr); 638 | panic!(rest) 639 | } 640 | }, 641 | Err(err_kind) => panic!(format!("{:?}", err_kind)), 642 | } 643 | } 644 | 645 | #[test] 646 | fn parse_insane_recursive_expressions() { 647 | for complexity in 1..100 { 648 | let mut expression = String::new(); 649 | for i in 0..complexity { 650 | expression.push_str("Funk(true, 0, "); 651 | if i == complexity - 1 { 652 | expression.push_str("42"); 653 | } 654 | } 655 | for _ in 0..complexity { 656 | expression.push_str(")"); 657 | } 658 | // dbg!(complexity, &expression); 659 | let now = Instant::now(); 660 | let expr = expr::<(&str, ErrorKind)>(&expression); 661 | let (rest, _) = expr.unwrap(); 662 | assert_eq!(rest.len(), 0); 663 | dbg!(now.elapsed()); 664 | } 665 | } 666 | 667 | #[test] 668 | fn parse_insane_long_expressions() { 669 | for complexity in 1..100 { 670 | let mut expression = String::new(); 671 | for i in 0..complexity { 672 | if i != 0 { 673 | expression.push_str(" + "); 674 | } 675 | expression.push_str("Funk(true, 0, \"42\")"); 676 | } 677 | // dbg!(complexity, &expression); 678 | let _now = Instant::now(); 679 | let expr = expr::<(&str, ErrorKind)>(&expression); 680 | let (rest, _) = expr.unwrap(); 681 | assert_eq!(rest.len(), 0); 682 | dbg!(_now.elapsed()); 683 | } 684 | } 685 | } 686 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Benchmarks/CompilationBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using DynamicExpresso; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace csharp_expr_rs.Benchmarks 9 | { 10 | [MemoryDiagnoser] 11 | public class CompilationBenchmark 12 | { 13 | const string expression = "first(first(first(first(first(first(first(first(first(first(first(1,2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3)"; 14 | 15 | [Benchmark(Baseline = true)] 16 | public void DynamicExpresso() 17 | { 18 | // DynamicExpresso 19 | var interpreter = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); 20 | interpreter.SetFunction("first", (Func)((a, b, c) => new[] { a, b, c }.First())); 21 | var dynamicExpression = interpreter.Parse(expression); 22 | } 23 | 24 | [Benchmark] 25 | public void Rust() 26 | { 27 | Expression rustExpression = null; 28 | try 29 | { 30 | rustExpression = new Expression(expression); 31 | } 32 | finally 33 | { 34 | rustExpression?.Dispose(); 35 | } 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace csharp_expr_rs.Benchmarks 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | BenchmarkRunner.Run(); 10 | //BenchmarkRunner.Run(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Benchmarks/TinyExpressionBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BeezUP2.Framework.Expressions; 2 | using BenchmarkDotNet.Attributes; 3 | using BenchmarkDotNet.Engines; 4 | using DynamicExpresso; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace csharp_expr_rs.Benchmarks 11 | { 12 | [SimpleJob(RunStrategy.Throughput, targetCount: 50)] 13 | [MemoryDiagnoser] 14 | //[NativeMemoryDiagnoser] // Use it when available : https://github.com/dotnet/BenchmarkDotNet/pull/1131 / https://github.com/dotnet/BenchmarkDotNet/releases -> 0.11.6 15 | public class TinyExpressionBenchmark 16 | { 17 | private Lambda _dynamicExpression; 18 | private Expression _rustExpression; 19 | private CSharpExpressionDynamicExpresso _bzExpression; 20 | private Dictionary _rustParameters; 21 | 22 | [Params(1, 5, 10, 50, 200)] public int IdentifiersCount { get; set; } 23 | [Params(10, 100, 1000)] public int IdentifiersValueSize { get; set; } 24 | //[Params(10, 50, 100)] public int ExpressionSize { get; set; } 25 | 26 | [IterationSetup] 27 | public void GlobalSetup() 28 | { 29 | var randy = new Random(); 30 | var chars = "abcdefghijklmnopqrsrtwxyz"; 31 | 32 | _rustParameters = Enumerable.Range(0, IdentifiersCount) 33 | .ToDictionary(i => $"a{i}", i => new String(chars[randy.Next(chars.Length)], IdentifiersValueSize)); 34 | 35 | var expression = "first(first(first(first(first(first(first(first(first(first(first(a0,a1,a2),a3,a4),a5,a6),a7,a8),a9,a10),2,3),2,3),2,3),2,3),2,3),2,3)"; 36 | var expressionForDExp = "first(first(first(first(first(first(first(first(first(first(first(1,2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3)"; 37 | 38 | var firstFunction = (Func)((a, b, c) => new[] { a, b, c }.First()); 39 | 40 | // DynamicExpresso 41 | var interpreter = new Interpreter(InterpreterOptions.DefaultCaseInsensitive); 42 | interpreter.SetFunction("first", firstFunction); 43 | _dynamicExpression = interpreter.Parse(expressionForDExp); 44 | 45 | //bz 46 | var expression2 = "first(first(first(first(first(first(first(first(first(first(first([a0],2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3),2,3)"; 47 | _bzExpression = new CSharpExpressionDynamicExpresso(expression2, null, new Dictionary { { "first", firstFunction } }); 48 | 49 | //Rust 50 | _rustExpression = new Expression(expression); 51 | } 52 | 53 | [IterationCleanup] 54 | public void GlobalCleanup() 55 | { 56 | _rustExpression.Dispose(); 57 | } 58 | 59 | //[Benchmark] 60 | //public object DynamicExpresso() => _dynamicExpression.Invoke(_rustParameters.Select(kv => new Parameter(kv.Key, kv.Value)).ToArray()); 61 | 62 | [Benchmark(Baseline = true)] 63 | public object BzExpression() => _bzExpression.Invoke(_rustParameters); 64 | 65 | [Benchmark] 66 | public object Rust() => _rustExpression.Execute(_rustParameters); 67 | 68 | 69 | //private readonly Dictionary _noParams = new Dictionary(); 70 | //[Benchmark] 71 | //public object RustNoParams() => _rustExpression.Execute(_noParams); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Benchmarks/csharp-expr-rs.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | csharp_expr_rs.Benchmarks 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.ConsoleTests/NativePassStringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace csharp_expr_rs.ConsoleTests 7 | { 8 | public class NativePassStringTests 9 | { 10 | public void Test() 11 | { 12 | var str = "€"; 13 | //NativePassString.PassLPStr(str); // exception when € 14 | //NativePassString.PassLPWStr(str); 15 | //NativePassString.PassLPTStr(str); 16 | NativePassString.PassLPUTF8Str(str); 17 | //NativePassString.PassBStr(str); 18 | } 19 | } 20 | 21 | /// 22 | /// https://docs.microsoft.com/fr-fr/dotnet/framework/interop/default-marshaling-for-strings 23 | /// 24 | internal static class NativePassString 25 | { 26 | public const string LIB_NAME = "csharp_expr.dll"; 27 | 28 | [DllImport(LIB_NAME)] 29 | public static extern void PassLPStr([MarshalAs(UnmanagedType.LPStr)] string s); 30 | [DllImport(LIB_NAME)] 31 | public static extern void PassLPWStr([MarshalAs(UnmanagedType.LPWStr)] string s); 32 | [DllImport(LIB_NAME)] 33 | public static extern void PassLPTStr([MarshalAs(UnmanagedType.LPTStr)] string s); 34 | [DllImport(LIB_NAME)] 35 | public static extern void PassLPUTF8Str([MarshalAs(UnmanagedType.LPUTF8Str)] string s); 36 | [DllImport(LIB_NAME)] 37 | public static extern void PassBStr([MarshalAs(UnmanagedType.BStr)] string s); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.ConsoleTests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace csharp_expr_rs.ConsoleTests 5 | { 6 | class Program 7 | { 8 | //static void Main(string[] args) 9 | //{ 10 | // var shoulbe = Guid.NewGuid().ToString(); 11 | // Console.WriteLine($"Should be : {shoulbe}."); 12 | // using var expression = new Expression("concat(aa,\"---------OK\")"); 13 | // var result = expression.Execute(new Dictionary() { { "aa", shoulbe } }); 14 | // Console.WriteLine($"RESULT : {result}"); 15 | //} 16 | 17 | static void Main(string[] args) 18 | { 19 | new NativePassStringTests().Test(); 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.ConsoleTests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "csharp-expr-rs.ConsoleTests": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "RUST_BACKTRACE": "1" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.ConsoleTests/csharp-expr-rs.ConsoleTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | csharp_expr_rs.ConsoleTests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Tests/CsharpExprLibTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrouaix/csharp-expr-rs/b8967b706045cfcd906b5eb03a44d67c6bc05ae5/tests-csharp/csharp-expr-rs.Tests/CsharpExprLibTests.cs -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Tests/DatetimeBehaviourTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using TimeZoneConverter; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace csharp_expr_rs.Tests 10 | { 11 | public class DatetimeBehaviourTests 12 | { 13 | private readonly ITestOutputHelper _output; 14 | 15 | public DatetimeBehaviourTests(ITestOutputHelper output) 16 | { 17 | _output = output; 18 | } 19 | 20 | [Fact] 21 | public void Tests() 22 | { 23 | new DateTime(2020, 1, 1).AddYears(1).ShouldBe(new DateTime(2021, 1, 1)); // Leap year yet it's on time 24 | //new DateTime(2020, 1, 1).AddYears(0.5).ShouldBe(new DateTime(2021, 1, 1); 25 | } 26 | 27 | 28 | [Fact] 29 | public void PrintWindowsTimezonesToIanaTz() 30 | { 31 | var sb = new StringBuilder(); 32 | foreach (var z in TimeZoneInfo.GetSystemTimeZones()) 33 | { 34 | var iana = TZConvert.WindowsToIana(z.Id); 35 | var chonotz = iana 36 | .Replace("Port-au-Prince", "PortauPrince") 37 | .Replace("/", "::").Replace("+", "Plus").Replace("-", "Minus"); 38 | 39 | string line = $"m.insert(\"{z.Id}\", chrono_tz::{chonotz});"; 40 | sb.AppendLine(line); 41 | _output.WriteLine(line); 42 | } 43 | } 44 | 45 | [Fact] 46 | public void PrintWindowsTimezonesToOffsets() 47 | { 48 | var sb = new StringBuilder(); 49 | foreach (var z in TimeZoneInfo.GetSystemTimeZones()) 50 | { 51 | var offset = z.BaseUtcOffset; 52 | var direction = (offset >= TimeSpan.Zero) ? "east" : "west"; 53 | var seconds = (int)Math.Abs(offset.TotalSeconds); 54 | 55 | string line = $"m.insert(\"{z.Id}\", FixedOffset::{direction}({seconds}));"; 56 | sb.AppendLine(line); 57 | _output.WriteLine(line); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests-csharp/csharp-expr-rs.Tests/csharp-expr-rs.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | csharp_expr_rs.Tests 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------