Compare commits

...
Author SHA1 Message Date
John Colvin ddd07464d4 check pt: Before going to Chi (11/07/23, random scripts, etc.) 2024-01-13 12:56:31 -05:00
John Colvin d7ba0b3215 check pt: Before going to Chi (11/07/23) 2024-01-13 12:56:31 -05:00
John Colvin 98b25ee17e add util: StringStreamEnumerable 2024-01-13 12:56:31 -05:00
John Colvin 99106a8a73 add websocket server config, README 2024-01-13 12:56:31 -05:00
John Colvin b56d0fb8f2 refactor: Change WebsocketServerConfig.port to uint32
It should always be a number between 1-65535
2024-01-13 12:56:31 -05:00
John Colvin f6f692f38e feat: Add Bts.DataStream.Websocket/Server 2024-01-13 12:56:30 -05:00
John Colvin 8009e61fc5 feat: Add JsonRpc types for websocket paradigm 2024-01-13 12:56:30 -05:00
John Colvin 4fa8a63c71 refactor: Add WebsocketGatewayObservable
An IObservable wrapper for IWebsocketConnectionGateway
2024-01-13 12:56:30 -05:00
John Colvin 8c8a8a9c22 refactor: Remove nullability constraint in GetValueOrDefault 2024-01-13 12:56:30 -05:00
John Colvin 2e12a37cb0 refactor: Use events to avoid circular deps for ws sync/gateway 2024-01-13 12:56:30 -05:00
John Colvin e97e674ca1 refactor: Let caller specify nullability of T in json parse fn 2024-01-13 12:56:30 -05:00
John Colvin 6c91cfcb66 refactor: Get rid of redundant UniversalNumericComparer 2024-01-13 12:56:30 -05:00
John Colvin 34cab94692 refactor: Reorganize NumericComparer, support all types 2024-01-13 12:56:30 -05:00
John Colvin 25117c5bb7 Append application name to "logging" dir
So we can run datastream procs alongside sparkd on the same machine without
log file conflicts
2024-01-12 14:22:02 -05:00
John Colvin 8c95b43524 devcontainer: Name mount dir w/ 'devcontainerId'
To ensure it is unique on the main host (i.e., outside the container). Get rid
of devcontainer_initialize.sh, all it did was run "mkdir". Something this
trivial can be inlined in the devcontainer.json (less indirection this way).
2024-01-12 11:41:40 -05:00
John Colvin 1324e2aad1 Gitignore the '.devcontainer' dir (not the json file)
We actually do track a 'devcontainer.json' file (to keep as a template). We
just don't keep it in the '.devcontainer' dir, for the same reason we don't
track the '.vscode' dir: so devs can tailor it to their specific needs.
2024-01-12 11:41:40 -05:00
John Colvin 6fad8b754b docker: Install kitware-archive-keyring to keep key in sync (for cmake) 2024-01-12 11:41:40 -05:00
Taha Afzal f872fe9d56 AsyncTCPConnection_APM.cs now send onData with Disconnection if it detects teh other side has ShutDown 2024-01-11 17:02:41 -05:00
Taha Afzal d50e8cda50 Adding AsyncTCPConnectionAcceptor_APM and AsyncTCPConnector_APM to create ITCPConnection 2024-01-11 16:10:51 -05:00
Taha Afzal 694f8b8d11 AsyncTCPConnection_APM works now 2024-01-09 14:34:15 -05:00
Taha Afzal 047267ad1b AsyncTCPConnection_APM completed (not hooked yet) 2024-01-06 11:34:46 -05:00
Taha Afzal 5b4b41d4eb Adding MongoDB astream integration 2024-01-02 00:44:52 -05:00
Taha 3e25b4918f Adding 3 AsyncTCPCOnnections for APM, TAP, and SocketAsyncEventArgs 2024-01-02 00:43:40 -05:00
Taha Afzal 1ceea4af5a Changing TCPUtils to use SocketAsyncEventArgs instead of TAP functions, simple client<->server works with echoing messages 2023-12-20 11:32:53 -05:00
Taha Afzal cdc7da8c59 Adding sec_name col to bbo 2023-12-18 12:57:04 -05:00
Taha Afzal f1e26b984a Merge branch 'development' of bitbucket.org:bluetradesys/spark into development 2023-12-18 12:42:35 -05:00
Taha Afzal a7189628b1 Adding EMA, SMA and RenkoStreams 2023-12-18 12:42:14 -05:00
John Colvin 32433577ec fix build (client): Move a few includes inside 'if' ppd 2023-12-14 11:31:31 -05:00
John Colvin ed5ef1b7c4 fix build: Remove a few RequiresPreviewFeatures attrs
Features which are now standard in .Net
2023-12-14 11:16:19 -05:00
Mohammad Arham ac7c38d0e2 Merge branch 'development' of bitbucket.org:bluetradesys/spark into development 2023-12-13 21:06:41 -08:00
Mohammad Arham b887f90b99 added book levels to mdrecorder 2023-12-13 21:06:19 -08:00
John Colvin 9a0ef90700 fix build (windows): Update fmt to v10.1.1
Manage this dep with vcpkg now (instead of externals)
2023-12-13 13:27:29 -05:00
John Colvin 5086e81578 build: Add vcpkg as submodule 2023-12-13 12:34:14 -05:00
Mohammad Arham 2230f8d748 emty string can also be default linfopath 2023-12-11 06:49:34 -08:00
Mohammad Arham 1fa66f4815 defaults updated to make glbx work on onix 2023-12-11 06:47:49 -08:00
Mohammad Arham e5d19ceb8d assert if channerls per thread < 1 2023-12-11 06:47:03 -08:00
Mohammad Arham edda571509 added glbx config just to enable glbx 2023-12-11 06:45:36 -08:00
Mohammad Arham 55f20e5a09 Added entry for cme/onix in mdgateway 2023-12-11 06:43:05 -08:00
Taha Afzal 619b49ee6c Changing the stream subscription structure 2023-11-15 12:30:43 +00:00
Taha Afzal ab32055416 Adding C++ stream tables (and tests) 2023-11-14 21:53:54 +00:00
Taha Afzal 2ae3ded65e src/DataStreamSuite/DataStream/Primitives/FileStreams/PlaybackStream.cs 2023-11-14 18:15:53 +00:00
Taha Afzal 4acb1e268a Merging 2023-11-14 18:14:18 +00:00
Mohammad Arham d45c79c44b Merge branch 'development' of bitbucket.org:bluetradesys/spark into development 2023-11-02 07:46:49 -07:00
John Colvin cba32be99d fix build: Use relative path in csproj references
The build was broken because Spark.csproj was looking for SharedStreams
at some hardcoded path on taha's comp
2023-10-26 14:37:56 -04:00
John Colvin 292fa54dda fix: Get rid of hardcoded path to dll
Use Assembly.Location instead

Bugs (fixed):
  * PubSubServer throws an exception on startup because it can't find
    dll at hardcoded path
2023-10-26 14:31:20 -04:00
John Colvin 3b059ebdfa fix: Format arguments properly in DataStream code gen
Previously, all arguments were formatted as "Bts.DataStream.Argument"
(from the default ToString)
2023-10-26 14:24:32 -04:00
John Colvin d0d3c9de4e feat: Allow line-comments in DataStream parser
Supports c#/c++ style line comments (i.e., that begin with '//')

TODO: block comments
2023-10-26 12:59:44 -04:00
John Colvin 633163518a fix: Fix divide token typo (should be fwd slash) 2023-10-26 12:59:44 -04:00
John Colvin 0a466052e0 cleanup: Remove old submodules (again)
* docker/linux
* docker/windows
2023-10-26 10:52:15 -04:00
John Colvin 7e139ddf0a cleanup: Remove old submodule docker\linux (again) 2023-10-26 10:48:47 -04:00
John Colvin dcac260efe fix (hack): Pass lines as array to cloud stream
Rather than a single multi-line string. This hack will have to do
until we implement multi-line strings in the parser.
2023-10-25 17:42:59 -04:00
Taha 81ea106ce3 Fixing the DSQA query box font color 2023-10-23 15:18:44 -04:00
Taha 88bf04e5f1 TimeSeries and TimeBucketStreams, Stream offloading etc 2023-10-23 14:18:31 -04:00
Taha 33030b1212 Market data streams work now 2023-10-16 00:01:29 -04:00
Taha 140c0c4152 MDStream uses MarketDataClient instead of MDConsumer now 2023-10-03 18:35:23 -04:00
Taha 7d63718602 MDStream uses MarketDataClient instead of MDConsumer now 2023-10-03 18:35:23 -04:00
John Colvin 9cb94fabdb fix: Add cross-type NumericComparer for BooleanExpressions
Bugs (fixed):
   * FilterStream throws exception when comparing numeric arguments
2023-10-02 22:02:35 -04:00
John Colvin e37a39f9fc chore: Add SharedStreams to default loaded assemblies cfg 2023-10-02 21:03:54 -04:00
John Colvin 7e307504cb chore: Remove redundant reference from csproj 2023-10-02 20:53:26 -04:00
John Colvin d01809e49c refactor: Move ProtoHelper to spark_api
So it can be used by other csprojs (was previously stuck in Spark)
2023-10-02 20:45:32 -04:00
John Colvin 292ed2dc0e refactor: Convert WebsocketServerParams to proto 2023-10-02 20:45:32 -04:00
John Colvin 623fce6e8f build: Enable nullable in proto generated cs 2023-10-02 20:45:32 -04:00
John Colvin 7d6d4c03b0 build: Update IronPython to v3.4.1 2023-10-02 20:45:32 -04:00
John Colvin 4962556a6c build: Update protobuf-net to v3.2.26
Also update System.Collections.Immutable to v7.0.0 to new deps
2023-10-02 20:45:32 -04:00
John Colvin 5ad9d9a0e6 build: Update System.Reactive to v6.0.0 2023-10-02 20:45:12 -04:00
John Colvin a091a95f50 build: Add .cmd wrapper script for build_data_stream.py 2023-10-02 19:38:38 -04:00
John Colvin 0bd06da929 build: Upgrade Google.Protobuf to 3.24.3
Now all csprojs are on the same Google.Protobuf version. Previously,
DataStreamSuite projs were on this (newer) version, while Spark projs
were on 3.21.12
2023-10-02 19:38:38 -04:00
Taha af9dd0c1fe PubSub Streams to get list of tags etc 2023-10-02 19:38:38 -04:00
John Colvin fd9eaa65ed build: Add script to build DataStreamSuite and cpp deps 2023-09-30 13:54:58 -04:00
John Colvin 1c744329e5 chore: Add SharedStreams.csproj bones 2023-09-30 13:08:03 -04:00
Taha f8c9148609 Adding tests 2023-09-30 12:59:02 -04:00
Taha fa4da63c24 Streams can be published across PubSub now 2023-09-30 12:59:02 -04:00
Taha 7bb7f1a5e2 PubSub is working now 2023-09-30 12:53:51 -04:00
Taha 3fa37397d9 AsyncTCPCOnnection2 done 2023-09-30 12:52:37 -04:00
Taha d75f97ba1f Both SImpleTCPClient and SimpleTCPServer use non async calls now 2023-09-30 12:52:37 -04:00
Taha 404698152e Merging, and adding test project 2023-09-30 12:52:37 -04:00
John Colvin 9311c27f47 feat: Resurrect DataStream primitives
They were originally commented out because the expression parsing utils
weren't ready (now they are).
2023-09-29 13:10:50 -04:00
John Colvin 1dfc028554 fix (temp): Comment out TestPublisher in Program.Main 2023-09-29 13:10:50 -04:00
John Colvin 931c681174 fix build (DataStreamSuite): Change path sep to '\' in Build.targets
Csharp protobuf generation was failing, albeit inconsistently, with a
mysterious error message about normalizing relative/absolute paths.
2023-09-29 13:10:50 -04:00
John Colvin f4568c3bb7 docker: Fix typo in README 2023-09-29 13:10:50 -04:00
John Colvin 126e3571df docker: Restructure/clean up Dockerfile
Break up large RUN steps (where many apt pkgs are installed at once) to
smaller incremental ones. If 'docker build' fails on a particular RUN
step, it can be re-run from the last successful step, so smaller steps
better preserve progress.
2023-09-29 13:10:49 -04:00
John Colvin 6759ce3a2d docker: Install latest cmake & node
The versions in the default apt repo are too old (need cmake >= 3.23,
node >= 16.0.0)
2023-09-29 13:10:49 -04:00
John Colvin 300a9b4eff docker: Use ubuntu:latest instead of transient 22.10
'latest' guarentees we're on an LTS release
2023-09-29 13:10:49 -04:00
John Colvin 52bb54252d gitignore: Add *.Generated.cs, *.lst
The Yacc code generator emits these files
2023-09-29 13:10:49 -04:00
John Colvin b416209ca7 refactor: Add DataStream cfg protos
Translated from Edge config
2023-09-29 13:10:49 -04:00
John Colvin bcd8aa3d80 refactor: Return fake data from myGetAllTagsStream
Until we port the actual impl from Edge
2023-09-29 13:10:49 -04:00
John Colvin ed0be9162b refactor: Move PrimitiveManager to DataStreamCore
So DSQA can reference it
2023-09-29 13:10:49 -04:00
John Colvin 54a6dca401 chore: Clean up leftover DataStreamGrammar dir 2023-09-29 13:10:49 -04:00
John Colvin 248e37577f feat: Add DataStreamExpressions
All lex/yacc scanners/parsers are now generated with a proper dotnet
nuget pkg, YaccLexTools.

* Implements BooleanExpressionFilter with the new Parser.
2023-09-29 13:10:49 -04:00
John Colvin ac1ede5986 refactor: Move a few low-level DataStream types to DataStreamCore
So the lex/yacc generators can reference them
2023-09-29 13:10:49 -04:00
John Colvin d1f5ae86fd chore: Get rid of BooleanExpressionsInterpreter csproj 2023-09-29 13:10:49 -04:00
John Colvin 094ea67e43 feat: Impl BooleanExpressions 2023-09-29 13:10:49 -04:00
John Colvin ca95ab7a24 feat: Add ArithmeticExpressions csproj 2023-09-29 12:54:14 -04:00
John Colvin e4f276be2a refactor: Consolidate redundant HighPerformanceTimer classes
Move the one in Spark csproj to spark_core, and reference spark_core
in all the DataStreamSuite csprojs that need it.
2023-09-21 23:48:57 -04:00
John Colvin a18521674e fix: Only init static DataStreamLoadFileConfig once 2023-09-21 23:48:57 -04:00
John Colvin d366ccc9c9 fix: Add simple null check 2023-09-21 23:48:57 -04:00
John Colvin 92e4b0a793 feat: Open gui when DSQA runs 2023-09-21 23:48:57 -04:00
John Colvin 56f7518186 chore: Uncomment DataStream Primitives code
It builds now that all the deps are in place
2023-09-21 23:48:57 -04:00
John Colvin 99ff8ac8dd chore: Comment out refs to PublishedTagsStream
For the DataStream port

TODO: Port PublishedTagsStream from edge
2023-09-21 23:48:57 -04:00
John Colvin 0754b48d44 chore: Comment out refs to SitesCfg
For the DataStream port

TODO: Translate SitesCfg to protobuf to integrate w/ modern config
2023-09-21 23:48:57 -04:00
John Colvin 081e96bca6 build: Add GuiUtilities csproj
For the DataStream port (DSQA depends on this proj)
2023-09-21 23:48:57 -04:00
John Colvin 8665bfc2d2 fix build (wip): Disambiguate HighPerformanceTimer ref 2023-09-21 23:48:57 -04:00
John Colvin 725471a238 fix build: Add using System.Runtime to fix TimeSpan ref
This Windows type must have moved namespaces since the Edge days
2023-09-21 23:48:57 -04:00
John Colvin d09c6ba543 refactor: Replace outdated DataGrid refs w/ DataGridView
Windows replaced the former with the latter in dotnet core 3.1
2023-09-21 23:48:57 -04:00
John Colvin 80c4542942 refactor: Replace old BlueEventLog w/ (Spark) Log 2023-09-21 23:48:57 -04:00
John Colvin 4446eb0412 chore: Get rid of redundant DataPacket.cs 2023-09-21 23:48:57 -04:00
John Colvin 71f642aef5 chore: Progress twds building DSQA 2023-09-21 23:48:57 -04:00
John Colvin 700bc45998 chore: Add DSQA (work-in-progress) 2023-09-21 23:48:57 -04:00
John Colvin 7cc7dd652f chore: Add missing DataStreamExpression.parser(.cs) 2023-09-21 23:48:57 -04:00
John Colvin 9fdffdc648 chore: Remove redundant init calls 2023-09-21 23:48:57 -04:00
John Colvin a3406279f9 fix build: Don't inherit build props/targets in WebApp build
The Directory.Build.props/targets files were recently moved up to the
'src' dir (from 'src/client') for the new DataStreamSuite sln. Evidently
this broke the WebApp build.
2023-09-18 05:03:11 +00:00
John Colvin 3ccda24e20 fix: Add missing venue cases in AdminWebApp
Coinbase and OKX
2023-09-18 05:02:12 +00:00
John Colvin 10085a4976 fix build: Don't copy 'sparkd.lic' in cmake install tgt
This was a license file for the emumba-deribit lib. We don't need it now
that the license check has been disabled.
2023-09-17 21:20:50 +00:00
John Colvin 78f41127fe chore: Remove artifact from old web build (added by accident) 2023-09-17 20:09:59 +00:00
John Colvin 8c35d63ab8 chore: Remove empty dirs (added by accident) 2023-09-06 18:24:04 +00:00
Taha 797816be7e Merging changes 2023-09-04 16:25:40 -04:00
John Colvin c4216d2272 build: Configure DataStreamSuite cpp to only build on Windows 2023-09-03 14:16:33 -04:00
John Colvin 431dad7551 fix: Init logging/config in DataStreamPublisher entry pt 2023-09-03 14:16:33 -04:00
John Colvin 7a5025d9d9 build: Add spark_core and spark_api to DataStreamSuite sln
Although outside of the DataStreamSuite dir, they should be included
because some csprojs in the sln depend on them.
2023-09-03 13:53:07 -04:00
John Colvin cc2959249e build: Add lexer/parser.cs files to src ctrl
Move all lexer/parser files into a single dir, DataStreamGrammar, for
organizational purposes and to avoid dependency conflicts when
generating code. Note that we'll only need to regen cs if we modify the
parser/lexer files, which seems unlikely.
2023-09-03 13:53:07 -04:00
John Colvin 86a1695286 Add update-synth-strategy fn to SyntheticSecurityClient 2023-09-01 23:24:35 -04:00
John Colvin 4c1cbbe8fe build: Remove unnecessary WorkingDirectory attrs
From the GenerateLexer/Parser targets
2023-09-01 12:48:18 -04:00
John Colvin 5081153db9 fix build: Don't explicitly include generated parser/lexer cs files
Since the files are generated in the source dir (not the build dir),
they are automatically included in the build. This removes a few
warnings abt duplicate symbols.
2023-09-01 12:46:21 -04:00
John Colvin 846ed05f76 chore: Remove redundant props in nested Directory.Build.props 2023-09-01 12:40:08 -04:00
John Colvin 3e9afea40a refactor: Rename DataStream ExpressionSyntax/Parser -> Boolean
The DataStream name was causing confusion. The lexer/parser are both
part of BooleanExpressions csproj, so the Boolean name is appropriate.
2023-09-01 12:38:27 -04:00
John Colvin cfa35fb5ce fix build: Add dummy classes for DataStreamSuite 2023-09-01 01:39:15 -04:00
John Colvin 9e221cabc3 build: Add BooleanExpressionsInterpreter csproj
For the DataStream port

* Add Lexer/Parser build targets
* Update externals w/ lexer/parser generator deps
2023-09-01 01:28:40 -04:00
John Colvin 23c83c0fb6 fix: Skip 1st arg (exe-path) when parsing cmd line
This only applies to Spark.exe, where the args are obtained via
Environment.GetCommandLineArgs, rather than directly from a 'main' fn.
The former prepends the [Spark] exe path to the argv array from 'main',
which the CommandLine parser does not expect.
2023-08-29 00:31:02 -04:00
John Colvin 5368f25ed8 feat: Generate rich version w/ GitVersion.MsBuild pkg
Run one of our dotnet exes w/ '--version' to print info to console
2023-08-29 00:21:54 -04:00
John Colvin bee76289ca chore: Format csproj & fsproj files 2023-08-28 12:25:47 -04:00
John Colvin 1fcfc2886c chore: Add displayName, description to cmake preset 2023-08-24 14:40:54 +00:00
John Colvin 83c35f55a3 build: Make DataStreamSuite build cross-platform 2023-08-24 14:40:15 +00:00
John Colvin 2f9f0108ef build: Rename cmake scripts by platform client/server spec
Now that we've added another cmake preset (data-stream-suite) some
scripts (GetLinuxClient*.cmake) are shared by multiple presets.
2023-08-24 14:32:44 +00:00
John Colvin 75a1165923 build: Remove dotnet build tgt from CMakeLists
Calling 'dotnet build' from CMakeLists doesn't feel natural; it's
simpler to just run it from the cmd line, or in a script.
2023-08-24 04:22:40 +00:00
John Colvin 2d394b77f4 chore: Remove redundant flags from CMakeLists
They're already defined in CMakePresets
2023-08-24 04:22:40 +00:00
John Colvin 59e5727f71 fix build (api serv linux): Use cmake shared lib suffix var
This Makes code platform independent. The build was broken because it
 was trying to copy a ".dll" file on Linux.
2023-08-24 04:22:40 +00:00
John Colvin f5d2e98d4f fix build (api serv linux): Fix cfg in CMakePresets
The api server linux config shouldn't inherit the spark (windows)
config; no reason to tether them together.
2023-08-24 04:11:47 +00:00
John Colvin 481f33c462 fix build (api serv linux): Fix link lib typo 'fmt' -> 'fmtd' 2023-08-24 04:04:39 +00:00
John Colvin 513bbeeaae fix build (api serv linux): Revert primary ctr syntax
This is a new feat in cs lang version 12, which is only in preview mode.
Reverting the syntax change bc I couldn't get it to build on Linux.
2023-08-24 04:04:39 +00:00
John Colvin 0cade21f1d build: Remove libspark from spark_client.sln
Libspark's inclusion in the sln file has become redundant now that
its bins are copied to the dotnet build dir as part of the CMake build
process. Unlike msbuild, dotnet doesn't integrate nicely with c++. It
seems better to keep c++ projects out of the sln.
2023-08-23 02:41:35 -04:00
John Colvin 7b284b40f7 fix build: Remove problematic generator expression
Just copy libspark.dll over to the dotnet build dir for now.
TODO: additionally copy pdb file for debug builds
2023-08-23 02:39:43 -04:00
John Colvin e1b6971667 build: Greatly simplify build_spark.py
Make it a thin wrapper around dotnet/cmake clis
2023-08-23 00:46:21 -04:00
John Colvin ecbae62d07 feat: Add bones for DataStreamSuite
TODO (Taha): Migrate source files from edge
2023-08-22 15:04:08 -04:00
John Colvin 8182b4b289 dev: Simplify sparkd build instructions
Restructure in style similar to Spark and Api Server build instructions
2023-08-22 14:38:39 -04:00
John Colvin a644174601 dev: Add instructions to build spark in README 2023-08-22 14:38:39 -04:00
John Colvin cf34c13f03 build: Replace build_api_server.py w/ cmake/dotnet clis
Add instructions to build the Api Server in README
2023-08-22 14:38:39 -04:00
John Colvin 126e84e4e8 build: Remove 'jobs' arg from CMake build presets
Modern CMake uses an appropriate number of jobs by default (I believe
the number of cores on the cpu)
2023-08-22 14:38:39 -04:00
John Colvin 5ffc83a237 build: Init SPARK_ENABLE_API_SERVER reguardless of platform 2023-08-22 14:38:39 -04:00
John Colvin 3082f5c8d9 build: Only build Admin Web App on Linux 2023-08-22 14:38:39 -04:00
John Colvin f1cde451d8 build: Copy libspark to dotnet bin dirs in CMakeLists 2023-08-22 14:38:39 -04:00
910 changed files with 159031 additions and 2688 deletions

No files matched your search

+3 -1
View File
@@ -54,4 +54,6 @@ trace_data/
/src/Spark.Admin.WebApp/src/bin/
**/CMakeDeps.cmake
**/CMakeDeps_win.cmake
**/devcontainer.json
**/.devcontainer
*.Generated.cs
*.lst
+3
View File
@@ -1,3 +1,6 @@
[submodule "externals"]
path = externals
url = git@bitbucket.org:bluetradesys/externals.git
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
+6 -62
View File
@@ -20,40 +20,22 @@ set(SPARK_LIB "${EXTERNALS_ROOT}/lib/$<CONFIG>")
set(BUILD_API_SERVER OFF CACHE BOOL "Build the api server")
set(BUILD_DOCS OFF CACHE BOOL "Build documentation")
set(BUILD_SDK OFF CACHE BOOL "Build the sdk")
set(BUILD_ADMIN_WEB_APP ON CACHE BOOL "Build the Admin WebApp")
if (WIN32)
set(BUILD_SPARKD OFF CACHE BOOL "Build sparkd")
set(BUILD_ADMIN_WEB_APP OFF CACHE BOOL "Build the Admin WebApp")
else()
set(BUILD_SPARKD ON CACHE BOOL "Build sparkd")
set(BUILD_ADMIN_WEB_APP ON CACHE BOOL "Build the Admin WebApp")
endif()
include_directories(BEFORE SYSTEM ${SPARK_INCLUDE} ${EXTERNALS_ROOT}/include/zlib)
link_directories(${SPARK_LIB})
if(WIN32)
# flags
set(
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} \
/MP \
/WX /wd4251 /wd4250 /wd4251 /wd4244 /wd4267 /wd4355 /wd4275 /wd4345 \
/bigobj \
/DCURL_STATICLIB \
/D_WINSOCK_DEPRECATED_NO_WARNINGS \
/D__TBB_NO_IMPLICIT_LINKAGE \
/DBOOST_ALL_NO_LIB \
/std:c++20 \
/D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} /MT")
set(
CMAKE_SHARED_LINKER_FLAGS_RELEASE
"${CMAKE_SHARED_LINKER_FLAGS_RELEASE} \
/DEBUG \
/OPT:REF \
/OPT:ICF \
/LTCG")
if(BUILD_API_SERVER)
add_compile_definitions(SPARK_ENABLE_API_SERVER)
endif()
if(WIN32)
add_compile_definitions(SHARED_MEMORY_PREFIX="spark")
add_compile_definitions(APP_DATA_DIR="\$\{APPDATA\}/bts/")
# subdirs
@@ -66,7 +48,6 @@ else()
set(SPARK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/$<CONFIG>")
if(BUILD_API_SERVER)
add_compile_definitions(SPARK_ENABLE_API_SERVER)
add_compile_definitions(APP_DATA_DIR="\$\{HOME\}/spark_api/")
add_compile_definitions(SHARED_MEMORY_PREFIX="spark_api")
elseif(BUILD_SPARKD)
@@ -138,42 +119,6 @@ else()
if (BUILD_ADMIN_WEB_APP)
add_subdirectory(src/Spark.Admin.WebApp)
endif()
if (BUILD_API_SERVER)
set(_cs_source_dirs
spark_api
spark_api_server
spark_core
spark_websocket)
foreach(cs_source_dir IN LISTS _cs_source_dirs)
file(GLOB cs_source_dir "${CMAKE_SOURCE_DIR}/src/client/${cs_source_dir}/*.cs")
list(APPEND _api_server_sources ${cs_source_dir})
endforeach()
list(APPEND _api_server_sources
${CMAKE_SOURCE_DIR}/src/client/spark_api/spark_api.csproj
${CMAKE_SOURCE_DIR}/src/client/spark_api_server/spark_api_server.csproj
${CMAKE_SOURCE_DIR}/src/client/spark_core/spark_core.csproj
${CMAKE_SOURCE_DIR}/src/client/spark_websocket/spark_websocket.csproj)
set(_out_dir ${CMAKE_BINARY_DIR}/$<CONFIG>)
set(_api_server_dlls
${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/spark_api.dll
${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/spark_core.dll
${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/spark_websocket.dll
${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/spark_api_server)
add_custom_command(
OUTPUT ${_api_server_dlls}
COMMAND dotnet build ${CMAKE_SOURCE_DIR}/Spark.Api.Server.sln --verbosity minimal --configuration $<CONFIG>
COMMENT "Building Spark.Api.Server.sln"
DEPENDS ${_api_server_sources})
add_custom_command(
OUTPUT
${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/libspark.so
COMMAND ln -sf ARGS ${CMAKE_BINARY_DIR}/src/lib/$<CONFIG>/libspark.so ${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0
COMMENT "Symlink libspark to api server bins"
DEPENDS ${_api_server_dlls})
add_custom_target(websocket_api_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/$<CONFIG>/bin/net7.0/libspark.so)
endif()
if(BUILD_DOCS)
add_subdirectory(doc/user)
endif()
@@ -206,7 +151,6 @@ else()
FILES
"${SPARK_LIB}/libACE-5.7.5.so"
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
"${CMAKE_SOURCE_DIR}/externals/src/emumba/sparkd.lic"
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
endif()
endif()
+81 -46
View File
@@ -26,8 +26,11 @@
"CMAKE_CXX_STANDARD": "20",
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"EXTERNALS_ROOT": "${sourceDir}/externals/windows",
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf"
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0",
"DOTNET_WINDOWS_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0-windows"
}
},
@@ -147,9 +150,21 @@
{
"name": "spark-api-server-base",
"inherits": "spark",
"hidden": true,
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"BUILD_API_SERVER": {
"type": "bool",
"value": "ON"
},
"CONFIG_DEFAULTS_PATH": "config/profiles/md_dev/*.conf;config/profiles/default/*.conf"
}
},
{
"name": "spark-api-server-windows",
"inherits": "spark",
"displayName": "Spark Api Server",
"cacheVariables": {
"BUILD_API_SERVER": {
"type": "bool",
@@ -158,33 +173,31 @@
}
},
{
"name": "spark-api-server-windows",
"inherits": "spark-api-server-base",
"displayName": "Spark Api Server",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"generator": "Visual Studio 17 2022"
},
{
"name": "spark-api-server-linux",
"inherits": "spark-api-server-base",
"inherits": "sparkd",
"displayName": "Spark Api Server",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"generator": "Ninja Multi-Config",
"cacheVariables": {
"BUILD_ADMIN_WEB_APP": {
"type": "BOOL",
"value": "OFF"
}
},
"BUILD_API_SERVER": {
"type": "bool",
"value": "ON"
},
"BUILD_SPARKD": {
"type": "BOOL",
"value": "OFF"
},
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_CXX_FLAGS_DEBUG": "-g -fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++20 -fdiagnostics-color -DDEBUG",
"CMAKE_CXX_FLAGS_RELEASE": "-fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++20 -fdiagnostics-color -fno-omit-frame-pointer -DNDEBUG -O3",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf",
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/net7.0"
}
},
@@ -204,6 +217,17 @@
"cacheVariables": {
"CCAPI_LOG_LEVEL": "Trace"
}
},
{
"name": "data-stream-suite",
"inherits": "spark",
"displayName": "Data Stream Suite",
"description": "Data Stream utils ported over from Edge",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CONFIG_DEFAULTS_PATH": "config/profiles/default/*.conf;config/profiles/data_stream/*.conf"
}
}
],
@@ -215,8 +239,7 @@
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"hidden": true,
"jobs": 4
"hidden": true
},
{
@@ -243,7 +266,6 @@
"rhs": "Linux"
},
"hidden": true,
"jobs": 8,
"targets": [
"data_files_dir",
"admin_web_app_bundle",
@@ -284,53 +306,52 @@
},
{
"name": "spark-api-server-debug-windows",
"name": "spark-api-server-windows",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"configurePreset": "spark-api-server-windows",
"hidden": true,
"configurePreset": "spark-api-server-windows"
},
{
"name": "spark-api-server-windows-debug",
"displayName": "Spark Api Server (Debug)",
"inherits": "spark",
"inherits": "spark-api-server-windows",
"configuration": "Debug"
},
{
"name": "spark-api-server-release-windows",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"configurePreset": "spark-api-server-windows",
"name": "spark-api-server-windows-release",
"displayName": "Spark Api Server (Debug)",
"inherits": "spark",
"inherits": "spark-api-server-windows",
"configuration": "Release"
},
{
"name": "spark-api-server-debug-linux",
"name": "spark-api-server-linux",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"configurePreset": "spark-api-server-linux",
"hidden": true,
"configurePreset": "spark-api-server-linux"
},
{
"name": "spark-api-server-linux-debug",
"inherits": "spark-api-server-linux",
"displayName": "Spark Api Server (Debug)",
"inherits": "spark",
"configuration": "Debug"
},
{
"name": "spark-api-server-release-linux",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"configurePreset": "spark-api-server-linux",
"displayName": "Spark Api Server (Debug)",
"name": "spark-api-server-linux-release",
"inherits": "spark-api-server-linux",
"displayName": "Spark Api Server (Release)",
"configuration": "Release"
},
@@ -347,6 +368,20 @@
"configurePreset": "sparkd-ccapi-diag",
"displayName": "Debug (ccapi diag)",
"inherits": "sparkd"
},
{
"name": "data-stream-suite-debug",
"displayName": "DataStreamSuite (Debug)",
"configurePreset": "data-stream-suite",
"configuration": "Debug"
},
{
"name": "data-stream-suite-release",
"displayName": "DataStreamSuite (Release)",
"configurePreset": "data-stream-suite",
"configuration": "Release"
}
]
}
+15
View File
@@ -0,0 +1,15 @@
#nusers:
#allow concurrent : true
#expires: 2024-Oct-06 00:00:00 638637696000000000
#generation_time: 2023-Oct-06 18:58:48.015723 5250008173707545134
EncryptedKeyCode {
value {
keycode: "KgoMeiVPzf/kv65GYGCyQQJVY/11sId6LYRwNNlNwA83+sISiSQnOZrziSgxjEAcaAZQn1nV4hY6MRi4XXznvxLYnDKf2bK0yktWWpSSqxqNxbDQ6AVSk5MGHptbsQSGYpYHlyCi0Niv8ES5NTj/FiBJXPrGEZABZBbNkqXIOa0HDdcxY+joeIf32ekX+5/LXN5XDTZBFT2A7q+IPDrFbKzSBvlzGOONSlq+XyKZZPqt0rXGO45V/GxFUTMYiEvn1/72iPGQ715tFXkr9ozGtitonnixzvW8Sfwmu7vM8VpWfoAc9D/1w5m5Ol5BS+Je8wigQysyrC8rud2KU+f7bQ=="
}
}
PublicKey {
value {
key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvvtV/IP1l/F85ME8N8H+\n8TCBOB68Lxd7sdR0geJGssLBq5F22IIlVKylNxiQV3TE6/hmL9dmr8FuUMrjPCal\nvma0axyAdyCMjFH3U0bYzzxnxSWuj5VJIgaZKOVQuX/3Hb+l4RZi7VWZgQFvEJ+F\ny66S2wDu4ey71Mt0yxkMsl5WMZBL3M1z+bt3pKzmBoyIMVLDcV1k1Yr2wspIRriZ\n95er3JWofK1P9s8B3OoqypEopLRW5hJaZN8YRpFNq5VwRUSj83KVobBAQTWDdOA5\nzOkixPVklZFaNyR06iT0gcgZUUM2bjyfQJGQ4GBCZpZb8KPPgtP+WhghoWby9OLv\nKQIDAQAB\n-----END PUBLIC KEY-----"
}
}
+33
View File
@@ -0,0 +1,33 @@
diff.astextplain.textconv=astextplain
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
http.sslbackend=openssl
http.sslcainfo=C:/Program Files/Git/mingw64/etc/ssl/certs/ca-bundle.crt
core.autocrlf=true
core.fscache=true
core.symlinks=false
pull.rebase=false
credential.helper=manager
credential.https://dev.azure.com.usehttppath=true
init.defaultbranch=master
status.short=true
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
submodule.active=.
remote.origin.url=git@bitbucket.org:bluetradesys/spark.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.spark_crypto.dev.remote=origin
branch.spark_crypto.dev.merge=refs/heads/spark_crypto.dev
submodule.docker/linux.url=git@bitbucket.org:bluetradesys/linux_docker.git
submodule.docker/windows.url=git@bitbucket.org:bluetradesys/windows_docker.git
submodule.externals.url=git@bitbucket.org:bluetradesys/externals.git
branch.development.remote=origin
branch.development.merge=refs/heads/development
user.name=John Colvin
user.email=john.colvin@bluetradesys.com
+189 -41
View File
@@ -1,8 +1,16 @@
[cmake]: https://cmake.org/
[cmake ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
[cmake presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[cpp ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools
[clangd ext]: https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd
[dotnet]: https://dotnet.microsoft.com/en-us/download/dotnet
[ninja build]: https://ninja-build.org/
[ninja generator]: https://cmake.org/cmake/help/latest/generator/Ninja.html
[ninja multi-config generator]: https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html
[python]: https://www.python.org/
[py venv]: https://docs.python.org/3/library/venv.html
[visual studio]: https://visualstudio.microsoft.com/
[visual studio generator]: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html
# C++ Intellisense for Spark on Windows (VS Code)
**Steps**
@@ -30,47 +38,187 @@
* *The **Clang** presets may work best with the **Clangd** extension*
* *Intellisense won't work for generated files/types until you build libspark*
# Build Sparkd on Linux
# Build Spark on Windows
### Dependencies
* **[CMake][cmake]** (version >= 3.23)
* **[Python][python]** (version >= 3.9)
* **[Visual Studio][visual studio]** (2022)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
**Notes**
* Sparkd is built using **[CMakePresets][cmake presets]**
### References
* **[CMakePresets][cmake presets]**
* **[Visual Studio 17 2022 (CMake generator)][visual studio generator]**
* **[Python Virtual Environment][py venv]**
### Notes
* *Run commands from the repo root dir unless otherwise noted*
**Steps**
1. Set up the **[Python virtual environment][py venv]**<br>
* *Run commands from the repo root dir unless otherwise noted*
1. Create the virtual environment
```
python3 -m venv .venv
```
2. Activate the virtual environment
```
. .venv/bin/activate
```
3. Verify `python` in the venv is first in the path<br>
* ***Expected:** output shows the absolute path to `.venv/bin/python`*
```
which python
```
4. Update `pip`<br>
* *The venv module initializes virtual environments with its own version
of pip, rather than the system's version. Even if the latter is up-to-date, the
former may not be.*
```
python -m pip install --upgrade pip
```
5. Install the packages listed `requirements.txt`
```
pip install -r requirements.txt
```
2. Build **Sparkd**<br>
* From the terminal
```
tools/build_sparkd.sh
```
* *Run with `--help` to see available options*
* From VS Code
1. Install the **[CMake extension][cmake ext]**
2. In the VS Code Command Palette, run **CMake: Select Configure
Preset**, then select the **[preset][cmake presets]** you wish to
build from the dropdown
3. Run **CMake: Build** in the command palette.
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
.venv\bin\activate.bat
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
* *Run commands in python virtual environment*
```
# Configure
cmake --preset spark
# Build Debug
cmake --build --preset spark-debug
dotnet build spark_client.sln --configuration Debug
# Build Release
cmake --build --preset spark-release
dotnet build spark_client.sln --configuration Release
```
# Build Sparkd on Linux
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Ninja Build][ninja build]**
* **[Python][python]** (version >= 3.9)
### **References**
* **[CMakePresets][cmake presets]**
* **[Ninja (CMake generator)][ninja generator]**
* **[Python Virtual Environment][py venv]**
* **[Devcontainer](devcontainer)**
### Prerequisites
* Open repository in a VS Code [Devcontainer](../docker/sparkd/dev/README.md), or
create your own [docker container](../docker/sparkd/dev/README.md) in which to build
### Notes
* *Run commands from the repo root dir unless otherwise noted*<br>
1. Set up the **[Python virtual environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
. .venv/bin/activate
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
* *Run commands in python virtual environment*
```
# Configure clang debug
cmake --preset sparkd-clang-debug
# Build clang debug
cmake --build --preset spark-clang-debug
# Configure gcc release
cmake --build --preset sparkd-gcc-release
# Build gcc release
cmake --build --preset sparkd-gcc-release
```
# Build Spark Api Server on Windows
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Python][python]** (version >= 3.9)
* **[Visual Studio][visual studio]** (version)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
### **References**
* **[CMakePresets][cmake presets]**
* **[Visual Studio 17 2022 (CMake generator)][visual studio generator]**
* **[Python Virtual Environment][py venv]**
### Notes
* *Run commands from the repo root dir unless otherwise noted*
1. Set up the **[Python Virtual Environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
.venv\bin\activate.bat
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
* *Run in python virtual environment*
```
# Configure
cmake --preset spark-api-server-windows
# Build debug
cmake --build --preset spark-api-server-windows-debug
dotnet build Spark.Api.Server.sln --configuration Debug
# Build release
cmake --build --preset spark-api-server-windows-release
dotnet build Spark.Api.Server.sln --configuration Release
```
# Build Spark Api Server on Linux
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Ninja Build][ninja build]**
* **[Python][python]** (version >= 3.9)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
### **References**
* **[CMakePresets][cmake presets]**
* **[Ninja Multi-Config (CMake generator)][ninja multi-config generator]**
* **[Python Virtual Environment][py venv]**
### Prerequisites
* Open repository in a VS Code [Devcontainer](../docker/sparkd/dev/README.md), or
create your own [docker container](../docker/sparkd/dev/README.md) in which to build
### Notes
* *Run commands from the repo root dir unless otherwise noted*
1. Set up the **[Python Virtual Environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
. .venv/bin/activate
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
* *Run in python virtual environment*
```
# Configure
cmake --preset spark-api-server-linux
# Build debug
cmake --build --preset spark-api-server-linux-debug
dotnet build Spark.Api.Server.sln --configuration Debug
# Build release
cmake --build --preset spark-api-server-linux-release
dotnet build Spark.Api.Server.sln --configuration Release
```
+80 -42
View File
@@ -1,66 +1,104 @@
FROM ubuntu:22.10
FROM ubuntu:latest
RUN \
apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=America/New_York
# light deps (should be quick to download/install)
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
gnupg \
liblttng-ust-dev \
libpcap0.8-dev \
locales \
ninja-build \
software-properties-common \
sqlite3 \
tzdata
# clang
RUN apt-get update && apt-get install -y \
clang-13 \
clang-format-13 \
clangd-13 \
clang-15 \
clang-format-15 \
clangd-15 \
cmake \
curl \
dotnet7 \
fish \
clangd-15
# g++
RUN apt-get update && apt-get install -y \
g++-11 \
g++-12 \
gnupg \
liblttng-ust-dev \
libssh2-1-dev \
libpcap0.8-dev \
locales \
ninja-build \
nodejs \
npm \
openssh-server \
g++-12
# python
RUN apt-get update && apt-get install -y \
python3-pip \
python3-venv \
rapidjson-dev \
software-properties-common \
sqlite3 \
sudo \
tar \
vim \
wget
&& python3 -m pip install --upgrade pip
# this is done here so that ccache sees clang++ installed, not clang-10
RUN \
apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y ccache
# dotnet
RUN apt-get update && apt-get install -y \
dotnet7
# emumba deps
# ssh
RUN apt-get update && apt-get install -y \
libssh2-1-dev \
openssh-server
# cmake
RUN \
apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
tzdata \
curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc 2> /dev/null \
| gpg --dearmor - \
| tee /usr/share/keyrings/kitware-archive-keyring.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \
| tee /etc/apt/sources.list.d/kitware.list > /dev/null \
&& apt-get update \
&& rm /usr/share/keyrings/kitware-archive-keyring.gpg \
&& apt-get install -y kitware-archive-keyring \
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs)-rc main" \
| tee -a /etc/apt/sources.list.d/kitware.list >/dev/null \
&& apt-get install -y cmake
# node
ARG node_major=18
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_major.x nodistro main" \
| tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y nodejs
# ccache is done here so that ccache sees clang++ installed, not clang-10
RUN apt-get update && apt-get install -y \
ccache
# emumba (light deps)
RUN apt-get update && apt-get install -y \
build-essential \
cmake-curses-gui \
git-all \
doxygen \
python3-sphinx \
graphviz \
libgtest-dev \
libssl-dev \
rapidjson-dev \
zlib1g-dev \
pkg-config
# emumba (boost)
RUN apt-get update && apt-get install -y \
libboost-filesystem-dev \
libboost-program-options-dev \
libboost-test-dev \
libboost-date-time-dev \
zlib1g-dev \
netcat-traditional \
net-tools \
gdb gdbserver \
pkg-config
libboost-date-time-dev
# dev utils
RUN apt-get update && apt-get install -y \
fish \
git-all \
sudo \
vim \
wget
# set up default executables (update-alternatives)
RUN \
+1 -1
View File
@@ -55,7 +55,7 @@ then select your container in the dropdown
3. Push the new image to Docker Hub
```
docker push johncolvin/bluetradingsystems:sparkd-dev
docker push bluetradingsystems/sparkd-dev:latest
```
***Note:** The image doesn't have to be on Docker Hub, we can simply build
it from scratch on our local machines (with the Dockerfile). However, this
+3 -2
View File
@@ -1,6 +1,7 @@
{
"image": "bluetradingsystems/sparkd-dev:latest",
"initializeCommand": "${localWorkspaceFolder}/docker/sparkd/dev/devcontainer_initialize.sh",
// Creates local dir to mount to HOME in docker
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
"hostRequirements": {
"memory": "4gb",
"storage": "6gb"
@@ -27,6 +28,6 @@
"8003:8003"
],
"mounts": [
"source=${localEnv:HOME}/.sparkd-dev/mounts/sparkd-dev,target=/home/bts,type=bind"
"source=${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId},target=/home/bts,type=bind"
]
}
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
mkdir -p "$HOME/.sparkd-dev/mounts/sparkd-dev"
+45
View File
@@ -0,0 +1,45 @@
# import json
# from rauth import OAuth2Service
# class ExampleOAuth2Client:
# def __init__(self, url, path, client_id, client_secret):
# self.access_token = None
# self.service = OAuth2Service(
# name="foo",
# client_id=client_id,
# client_secret=client_secret,
# access_token_url=f'{url}{path}',
# authorize_url=f'{url}{path}',
# base_url=url,
# )
# self.get_access_token()
# def get_access_token(self):
# data = {
# 'grant_type': 'client_credentials', # generally required!
# }
# session = self.service.get_auth_session(data=data, decoder=json.loads)
# self.access_token = session.access_token
# def _main():
# client_id = 'API_THINKALPHA123'
# client_secret = '$MM9Rn*#qYARY$$QTMA#AMn*'
# oath = ExampleOAuth2Client(
# url='https://authnr.cmegroup.com',
# path='/as/token.oath2',
# client_id=client_id,
# client_secret=client_secret,
# )
# tok = oath.get_access_token()
# print(tok)
# if __name__ == '__main__':
# try:
# _main()
# except json.JSONDecodeError as jde:
# print(f'get-token.py failed with json-decode-error: {jde}.\n{jde.doc}\n\nlineno: {jde.lineno}\npos: {jde.pos}')
# except Exception as e:
# print(f'get-token.py failed: {e}')
+30
View File
@@ -0,0 +1,30 @@
import subprocess
def _main():
host = "markets.api.uat.cmegroup.com"
port = "443"
# AUTH_ENDPOINT="$host/marketdatastream/v1"
AUTH_ENDPOINT = 'https://authnr.cmegroup.com/as/token.oauth2'
API_KEY = "API_THINKALPHA123"
API_SECRET = '$MM9Rn*#qYARY$$QTMA#AMn*'
# Define your API key and secret
# API_KEY="YOUR_API_KEY"
# API_SECRET="YOUR_API_SECRET"
# Define the authentication API endpoint URL
# AUTH_ENDPOINT="https://api.example.com/auth"
# Perform the POST request and capture the response
# response=$(curl -s -X POST -d "api_key=$API_KEY&api_secret=$API_SECRET&grant_type=password&username=API_THINKALPHA123&password=$API_SECRET" "$AUTH_ENDPOINT")
# subprocess.run('ls')
subprocess.run(
f'curl -s -X POST -d "grant_type=client_credentials&client_id={API_KEY}&client_secret={API_SECRET}" "{AUTH_ENDPOINT}"',
)
# curl --user 'API_THINKALPHA123:$MM9Rn*#qYARY$$QTMA#AMn*' 'https://query-api.datamine.cmegroup.com/v3/query/query_order_record?tradedate=2023-07-10&fields=symbol,transacttime,instrumentsequencenumber,tradeprice,tradequantity,tradeflag&q=asset.eq.TN,securitytype.eq.FUT,tradeflag.nin.OBS&fileFormat=csv&securityexchange=XCBT&underlyingproduct=Interest%20Rate'
if __name__ == '__main__':
_main()
View File
Whitespace-only changes.
+211
View File
@@ -0,0 +1,211 @@
[cmake]: https://cmake.org/
[cmake ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
[cmake presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[clangd ext]: https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd
[cpp ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools
[docker]: https://www.docker.com/
[dotnet]: https://dotnet.microsoft.com/en-us/download/dotnet
[ninja build]: https://ninja-build.org/
[ninja generator]: https://cmake.org/cmake/help/latest/generator/Ninja.html
[ninja multi-config generator]: https://cmake.org/cmake/help/latest/generator/Ninja%20Multi-Config.html
[python]: https://www.python.org/
[py venv]: https://docs.python.org/3/library/venv.html
[visual studio]: https://visualstudio.microsoft.com/
[visual studio generator]: https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html
[devcontainer]: https://code.visualstudio.com/docs/devcontainers/create-dev-container
# C++ Intellisense for Spark on Windows (VS Code)
**Steps**
1. Install the **[C++ extension][cpp ext]**
2. Install the **[CMake extension][cmake ext]**
3. Copy `c_cpp_properties.json` from this folder to `$workspaceFolder/.vscode/`
4. In the VS Code Command Palette, run `CMake: Select Configure Preset`, then
select '**spark**' from the dropdown
**Notes**
* Intellisense won't work for generated files/types until you build libspark
# C++ Intellisense for Sparkd on Linux (VS Code)
1. Install the **[CMake extension][cmake ext]**
2. Install the **[Clangd extension][clangd ext]**
3. (Optional) Install the **[C++ extension][cpp ext]** (*needed for debugging,
but intellisense should work without it*).
4. If the **[C++ extension][cpp ext]** is installed, disable its intellisense in
your workspace settings (*Clangd intellisense is better*)
```
"C_Cpp.intelliSenseEngine": "disabled"
```
5. In the VS Code Command Palette, run **CMake: Select Configure Preset**, then select the
**[preset][cmake presets]** you wish to build from the dropdown
* *The **Clang** presets may work best with the **Clangd** extension*
* *Intellisense won't work for generated files/types until you build libspark*
# Build Spark on Windows
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Python][python]** (version >= 3.9)
* **[Visual Studio][visual studio]** (2022)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
### **References**
* **[CMakePresets][cmake presets]**
* **[Visual Studio 17 2022 (CMake generator)][visual studio generator]**
* **[Python Virtual Environment][py venv]**
> *Run commands from the repo root dir unless otherwise noted*
1. Set up the **[Python virtual environment][py venv]**<br>
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
.venv\bin\activate.bat
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
> *Run commands in python virtual environment*
```
# Configure
cmake --preset spark
# Build Debug
cmake --build --preset spark-debug
dotnet build spark_client.sln --configuration Debug
# Build Release
cmake --build --preset spark-release
dotnet build spark_client.sln --configuration Release
```
# Build Sparkd on Linux
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Ninja Build][ninja build]**
* **[Python][python]** (version >= 3.9)
* **[Docker][docker]**
### **References**
* **[CMakePresets][cmake presets]**
* **[Ninja (CMake generator)][ninja generator]**
* **[Python Virtual Environment][py venv]**
* **[Devcontainer](devcontainer)**
### **Prerequisites**
* Open repository in a VS Code [Devcontainer](../docker/sparkd/dev/README.md), or
create your own [docker container](../docker/sparkd/dev/README.md) in which to build
> *Run commands from the repo root dir unless otherwise noted*<br>
1. Set up the **[Python virtual environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
. .venv/bin/activate
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
> *Run commands in python virtual environment*
```
# Configure clang debug
cmake --preset sparkd-clang-debug
# Build clang debug
cmake --build --preset spark-clang-debug
# Configure gcc release
cmake --build --preset sparkd-gcc-release
# Build gcc release
cmake --build --preset sparkd-gcc-release
```
# Build Spark Api Server on Windows
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Python][python]** (version >= 3.9)
* **[Visual Studio][visual studio]** (version)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
### **References**
* **[CMakePresets][cmake presets]**
* **[Visual Studio 17 2022 (CMake generator)][visual studio generator]**
* **[Python Virtual Environment][py venv]**
> *Run commands from the repo root dir unless otherwise noted*
1. Set up the **[Python Virtual Environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
.venv\bin\activate.bat
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
> *Run in python virtual environment*
```
# Configure
cmake --preset spark-api-server-windows
# Build debug
cmake --build --preset spark-api-server-windows-debug
dotnet build Spark.Api.Server.sln --configuration Debug
# Build release
cmake --build --preset spark-api-server-windows-release
dotnet build Spark.Api.Server.sln --configuration Release
```
## Linux
### **Dependencies**
* **[CMake][cmake]** (version >= 3.23)
* **[Python][python]** (version >= 3.9)
* **[Dotnet][dotnet]** (sdk version >= 7.0.0)
### **References**
* **[CMakePresets][cmake presets]**
* **[Ninja Multi-Config (CMake generator)][ninja multi-config generator]**
* **[Python Virtual Environment][py venv]**
> *Run commands from the repo root dir unless otherwise noted*
1. Set up the **[Python Virtual Environment][py venv]**
```
# Create the virtual environment
python3 -m venv .venv
# Activate the virtual environment
. .venv/bin/activate
# Update pip
python -m pip install --upgrade pip
# Install packages
pip install -r requirements.txt
```
2. Build
> *Run in python virtual environment*
```
# Configure
cmake --preset spark-api-server-linux
# Build debug
cmake --build --preset spark-api-server-linux-debug
dotnet build Spark.Api.Server.sln --configuration Debug
# Build release
cmake --build --preset spark-api-server-linux-release
dotnet build Spark.Api.Server.sln --configuration Release
```
+66
View File
@@ -0,0 +1,66 @@
diff --git a/src/DataStreamSuite/SharedStreams/Business/MarketData/MDStream.cs b/src/DataStreamSuite/SharedStreams/Business/MarketData/MDStream.cs
index 643146112..e334c3aba 100644
--- a/src/DataStreamSuite/SharedStreams/Business/MarketData/MDStream.cs
+++ b/src/DataStreamSuite/SharedStreams/Business/MarketData/MDStream.cs
@@ -1,7 +1,9 @@
namespace BCG {
using BCG.Utilities.General;
+ using bts;
using bts.api;
+ using bts.api.core;
using bts.api.domain;
using bts.api.synthetic;
using bts.md;
@@ -14,6 +16,7 @@ namespace BCG {
using mbts.spark;
using mbts.ui;
+ using Spark.Api.MarketData;
using Spark.Core.Data;
using Spark.Core.Disposables;
using Spark.Core.Reactive;
@@ -39,6 +42,8 @@ namespace BCG {
new ColumnInfo("sec", 1, typeof(string))}, "md");
private BlueTable _rows;
+ readonly MarketDataClient mdClient;
+ IDisposable? mdSub;
public MDStream()
: this(String.Empty, ViewStreamDatabase.instance) {
@@ -49,14 +54,27 @@ namespace BCG {
_symbol = symbol;
_rows = new BlueTable(_schema);
_md_source = new MDSource(TimeSpan.FromSeconds(0.5));
+ mdClient = new(_md_source);
}
protected override void myOnFirstSubscriber() {
_vsd.addWorker(this);
_md_source.subscribe_securities().Subscribe(c => on_md(c));
+ Log.isnull(mdSub);
+ mdSub = mdClient.books().Subscribe(onBooks);
//base.myOnFirstSubscriber();
}
+ void onBooks(TableUpdate<BookTuple, uint> update) {
+ foreach (var book in update.added) {
+ bts.Log.Gen.info($"MDStream.on_md sec: {book.security}");
+ var sec = book.security;
+ IBlueRow r = new BlueRow(_schema, _id++, sec.ToString());
+ _rows.addRow(r);
+ _is_dirty = true;
+ }
+ }
+
protected override void myOnLastSubscriber() {
//base.myOnLastSubscriber();
_vsd.removeWorker(this);
@@ -82,4 +100,4 @@ namespace BCG {
}
}
}
-}
\ No newline at end of file
+}
+112
View File
@@ -0,0 +1,112 @@
diff --git a/src/client/spark/AW2/VolSurfaceExtensions.cs b/src/client/spark/AW2/VolSurfaceExtensions.cs
index 6279d82a2..c143902a7 100644
--- a/src/client/spark/AW2/VolSurfaceExtensions.cs
+++ b/src/client/spark/AW2/VolSurfaceExtensions.cs
@@ -162,7 +162,8 @@ namespace mbts.spark {
api_sec_exp.Exchange.to_venue(),
api_sec_exp.SecurityType.contract_case(),
api_sec_exp.Symbol,
- api_sec_exp.Expiration.date_time());
+ api_sec_exp.HasExpiration ?
+ api_sec_exp.Expiration.date_time() : SecurityExpiration.empty.expiration);
public static SecurityExpiration to_sec_exp(this Security sec) =>
to_sec_exp(new SecurityImmutable(sec));
diff --git a/src/client/spark/Analytics/Core/Settings/SecurityExpiration.cs b/src/client/spark/Analytics/Core/Settings/SecurityExpiration.cs
index bf9f44455..b2ac80561 100644
--- a/src/client/spark/Analytics/Core/Settings/SecurityExpiration.cs
+++ b/src/client/spark/Analytics/Core/Settings/SecurityExpiration.cs
@@ -3,6 +3,7 @@ namespace mbts {
using bts.api;
using bts.utils;
+ using mbts.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -25,6 +26,9 @@ namespace mbts {
SecurityTuple.empty.security_name,
DateTime.MinValue);
+ public static bts.api.domain.SecurityExpirationImmutable empty_api_sec_expi =>
+ empty.to_api_sec_expi();
+
// for deserialization
SecurityExpiration() =>
security_tuple = SecurityTuple.empty;
diff --git a/src/client/spark/Analytics/SupportingData/AnalyticsExpirationParameters.cs b/src/client/spark/Analytics/SupportingData/AnalyticsExpirationParameters.cs
index f5e2a73d6..0ea7b34be 100644
--- a/src/client/spark/Analytics/SupportingData/AnalyticsExpirationParameters.cs
+++ b/src/client/spark/Analytics/SupportingData/AnalyticsExpirationParameters.cs
@@ -50,9 +50,7 @@ namespace mbts.analytics {
deliverable_security ??= SecurityExpiration.empty;
roll_schedule_expiration ??= SecurityExpiration.empty;
- this.driver = driver ?? new(new() {
- TimeWeightedInterpolated = new()
- });
+ this.driver = driver ?? _default_driver;
_deliverable = deliverable_security.to_api_sec_expi();
_roll_schedule = roll_schedule_expiration.to_api_sec_expi();
this.roll_target = roll_target;
@@ -63,10 +61,10 @@ namespace mbts.analytics {
}
public AnalyticsExpirationParameters(ExpirationParameterEntryImmutable db_i) {
- _security = db_i.SecurityExpiration;
- _deliverable = db_i.Deliverable;
- _roll_schedule = db_i.RollSchedule;
- driver = db_i.Driver;
+ _security = db_i.HasSecurityExpiration ? db_i.SecurityExpiration : _default_api_sec_expi;
+ _deliverable = db_i.HasDeliverable ? db_i.Deliverable : _default_api_sec_expi;
+ _roll_schedule = db_i.HasRollSchedule ? db_i.RollSchedule : _default_api_sec_expi;
+ driver = db_i.HasDriver ? db_i.Driver : _default_driver;
roll_target = db_i.RollTarget;
riskless_rate = db_i.RisklessRate;
settlement_price = db_i.SettlementPrice;
@@ -74,6 +72,12 @@ namespace mbts.analytics {
vega_beta = db_i.VegaBeta;
}
+ static ExpirationDriverImmutable _default_driver =>
+ new(new() { TimeWeightedInterpolated = new() });
+
+ static bts.api.domain.SecurityExpirationImmutable _default_api_sec_expi =>
+ SecurityExpiration.empty_api_sec_expi;
+
public SecurityExpiration security => _security.to_sec_exp();
public SecurityExpiration driver_security => driver.HasSecurity ?
driver.Security.to_sec_exp() : SecurityExpiration.empty;
diff --git a/src/client/spark/NMSheets/DataProviders/NMSheetSingleOptionMainDataSource.cs b/src/client/spark/NMSheets/DataProviders/NMSheetSingleOptionMainDataSource.cs
index 622121388..ec902f142 100644
--- a/src/client/spark/NMSheets/DataProviders/NMSheetSingleOptionMainDataSource.cs
+++ b/src/client/spark/NMSheets/DataProviders/NMSheetSingleOptionMainDataSource.cs
@@ -233,9 +233,18 @@ namespace BCG {
private IDictionary<DateTime, ATMData> _get_atm_data(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data) {
NMSheetSettings sheet_settings = _threaded_bb.read_state(_in_bb_sheet_settings_tag) ?? new NMSheetSettings();
VolSurfaceNonVolInput vs_nvinp = _threaded_bb.read_state(_in_exp_param_tag);
- IDictionary<DateTime, double> option_exp_roll_data = vs_nvinp != null ?
- vs_nvinp.get_roll_map((exp) => exp.symbol == _nmsheet_symbol.option_name && exp.security_type == SecurityType.Option).ToDictionary(kv => kv.Key.expiration, kv => kv.Value) :
- new Dictionary<DateTime, double>();
+ var option_sym_exp_groups = vs_nvinp
+ ?.get_roll_map(exp =>
+ exp.symbol == _nmsheet_symbol.option_name &&
+ exp.security_type == SecurityType.Option)
+ ?.GroupBy(kv => kv.Key.expiration)
+ ?.ToDictionary(kv => kv.Key, kv => kv.ToList());
+ var option_exp_roll_data = vs_nvinp
+ ?.get_roll_map(exp =>
+ exp.symbol == _nmsheet_symbol.option_name &&
+ exp.security_type == SecurityType.Option)
+ ?.ToDictionary(kv => kv.Key.expiration, kv => kv.Value)
+ ?? new();
IDictionary<DateTime, ATMData> atm_data = OptionATMDataSource.compute_atm_data(_nmsheet_symbol.option_name, _get_onefive(), theo_data, option_exp_roll_data);
if (sheet_settings.setting_atm.select_atm_nearest_strike_only)
foreach (ATMData exp_atm_data in atm_data.Values)
@@ -306,4 +315,4 @@ namespace BCG {
}
#endregion
}
-}
\ No newline at end of file
+}
+45
View File
@@ -0,0 +1,45 @@
/* global use, db */
// MongoDB Playground
// To disable this template go to Settings | MongoDB | Use Default Template For Playground.
// Make sure you are connected to enable completions and to be able to run a playground.
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.
// The result of the last command run in a playground is shown on the results panel.
// By default the first 20 documents will be returned with a cursor.
// Use 'console.log()' to print to the debug output.
// For more documentation on playgrounds please refer to
// https://www.mongodb.com/docs/mongodb-vscode/playgrounds/
// Select the database to use.
use('mongodbVSCodePlaygroundDB');
// Insert a few documents into the sales collection.
db.getCollection('sales').insertMany([
{ 'item': 'abc', 'price': 10, 'quantity': 2, 'date': new Date('2014-03-01T08:00:00Z') },
{ 'item': 'jkl', 'price': 20, 'quantity': 1, 'date': new Date('2014-03-01T09:00:00Z') },
{ 'item': 'xyz', 'price': 5, 'quantity': 10, 'date': new Date('2014-03-15T09:00:00Z') },
{ 'item': 'xyz', 'price': 5, 'quantity': 20, 'date': new Date('2014-04-04T11:21:39.736Z') },
{ 'item': 'abc', 'price': 10, 'quantity': 10, 'date': new Date('2014-04-04T21:23:13.331Z') },
{ 'item': 'def', 'price': 7.5, 'quantity': 5, 'date': new Date('2015-06-04T05:08:13Z') },
{ 'item': 'def', 'price': 7.5, 'quantity': 10, 'date': new Date('2015-09-10T08:43:00Z') },
{ 'item': 'abc', 'price': 10, 'quantity': 5, 'date': new Date('2016-02-06T20:20:13Z') },
]);
// Run a find command to view items sold on April 4th, 2014.
const salesOnApril4th = db.getCollection('sales').find({
date: { $gte: new Date('2014-04-04'), $lt: new Date('2014-04-05') }
}).count();
// Print a message to the output window.
console.log(`${salesOnApril4th} sales occurred in 2014.`);
// Here we run an aggregation and open a cursor to the results.
// Use '.toArray()' to exhaust the cursor to return the whole result set.
// You can use '.hasNext()/.next()' to iterate through the cursor page by page.
db.getCollection('sales').aggregate([
// Find all of the sales that occurred in 2014.
{ $match: { date: { $gte: new Date('2014-01-01'), $lt: new Date('2015-01-01') } } },
// Group the total sales for each product.
{ $group: { _id: '$item', totalSaleAmount: { $sum: { $multiply: ['$price', '$quantity'] } } } }
]);
+30
View File
@@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIgmHCnJpc1awCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECPGPVlg9BOO1BIIEyHTdfD5zGKyf
hHVm4So+G8TtIQvL+xu0oLNKQR/DVo3wo3iUD70h7oXQnLJ4CL5qo9nR75u2UghL
mg6xGFbmvpIX/2LAHsS3+MzmOYWKAr0sjmFgJ0YJ/Km6zL88wpk8Gxf3KW3PTZta
+STA1OVPzggSxbHNV16kD9mVUwzOGxD6+zmkRBrvVr3z7/Y8DO8102QrT13U7wy3
RWWo9cNFSS4UG1yeOy0IGRzvfPVf8si6xO2GXKJZX1W3pfpUihEVDynz9I7pkw0/
NF9CVROOgY351d0Vs7is+K9q1etpAHTRJl5phpQUXX6DTXlkjWInAXYcs+4z/kEK
icUa9fijxvz+rx67WkEn4TmaBCIkPVJukeJFh2NHKEj9nK88/okjDExes9TDC4HC
riGoT7honGjkmeIQd0EZo3YXzCCRcr5hCjx3z5CQThLY0MT3avNqYLs6qwaEL4ho
Mnkiiwm/d8n5AGpqm1dI8hmJMhlQzL5a6fpcgst6bqVc7MMRrTNnY/k8ptoXyHWI
XXkcf1SWBxwK5u14AoH5u40LORz0Shn9PKL05EeAqK5eh9TX+HmxCE7pES4uceRJ
nBYjPp8q/NkhqHhMlVZgNcZoY3edPY4We5nYKpW4uLFl9DPckFKlaQpN+dVcSY0I
vIsRaKXeju3YpzehRE80ztE4f8ZO8nyuGScx1PV0mYlXsjMJpAkV2J7Y3BVdFRsk
bvQ2zfhgX5LQeC9MU0jn5cZbBO67eFxmEvtZKRt0ooZSNR748NqbfO2wHOuxfLOe
UeZfKGvpJhHqH+nZQiEKIldf7A9ORQeVr/yhR+shv91GJtUE89cm725ODFe5NiHw
FWVgyBFuB4DN/XVvD8oS7S+Io/q+YAfbDzYqqYjlCuoQcrEfhkcWJOKZ7nEXqEdN
W/Ly6HAl2QddVK8xj93IGLYhzEZSU8ldw7KhRwXE2jAe9cB+tygOWygxfVpt6d3i
q0xBLvlUVWC2aviOtQ+xvNb54JPPRTnOZ2RsOUkTPDrTEFeEm0O0lbPI1PSIMlGH
BSQukR+n0vRfmRRs/OXipe2zJEDy6+BJ44uV+Pz5aARH4arvnfWnRKlx5+b58HEd
Six4y+Sz2i5k6MwRCQyuPTBJHpp6zvwFCX1doPrcCN6Q0GjtDWGklPyofmuLSM3d
8sHciqGLfv6+hSH/yY/SbyyU2MYx5WJoREYIfQ1M/V6ycppmIb7QDXrQ6nVd33FC
73OpKaB/q1tIgdImUT7J0PeM6EmjKC3g6y0iGp9adAF2BMe19rOQjrn9lHMbFb9w
/4uZDTLPmYZyONmMFnL47PoD3MUlJFvDPN//9g1DTk3lMLHETNzv7Lh6vR2h5Otl
x9SnakCahQDcLYvxdKNSKHfClwMuS3gjpoT45Qki0APH+dYwGYNXDLj41t61fYkl
v7IoGoHJ3Ym8ySWtS5MMt4kdb7eiVAzGla5TEa1RqOtU31A6W3GKSOzlY7znaBoi
Q/aGtZq/AhU4DOba5pT/ddB442fnG/8i2un6oSAthtK6VvHx4ebBDxTgV8Fp62le
dCLw93+lppVS8ChRH2MQDS2Mr/khQo5f+BybnY6zVBRJh7rvQ7g4U0qBzpoaj+nD
IiX/FNTzdAYjmFa+iHB2OQ==
-----END ENCRYPTED PRIVATE KEY-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvvtV/IP1l/F85ME8N8H+
8TCBOB68Lxd7sdR0geJGssLBq5F22IIlVKylNxiQV3TE6/hmL9dmr8FuUMrjPCal
vma0axyAdyCMjFH3U0bYzzxnxSWuj5VJIgaZKOVQuX/3Hb+l4RZi7VWZgQFvEJ+F
y66S2wDu4ey71Mt0yxkMsl5WMZBL3M1z+bt3pKzmBoyIMVLDcV1k1Yr2wspIRriZ
95er3JWofK1P9s8B3OoqypEopLRW5hJaZN8YRpFNq5VwRUSj83KVobBAQTWDdOA5
zOkixPVklZFaNyR06iT0gcgZUUM2bjyfQJGQ4GBCZpZb8KPPgtP+WhghoWby9OLv
KQIDAQAB
-----END PUBLIC KEY-----
+1 -14
View File
@@ -1,11 +1,9 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark.Console", "src\client\Spark.Console\Spark.Console.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
@@ -13,9 +11,6 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
ProjectSection(ProjectDependencies) = postProject
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25} = {FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
EndProject
@@ -39,14 +34,6 @@ Global
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Debug|x64.ActiveCfg = Debug|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Debug|x64.Build.0 = Debug|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Release|x64.ActiveCfg = Release|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Release|x64.Build.0 = Release|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
@@ -0,0 +1,37 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
class AdditiveInverse : IArithmeticExpression<double> {
readonly IArithmeticExpression expression;
public AdditiveInverse(IArithmeticExpression expression) =>
this.expression = expression;
public static AdditiveInverse<T> Create<T>(IArithmeticExpression<T> expression)
where T : INumber<T> =>
new(expression);
public double Evaluate(IDictionary<string, object?> env) =>
-evaluateArg(expression, nameof(expression), env);
static double evaluateArg(
IArithmeticExpression expr, string name, IDictionary<string, object?> env) {
var obj = expr.Evaluate(env);
return ConvertUtils.try_convert(expr.Evaluate(env), out double dbl) ? dbl :
throw new Exception(
$"Invalid AdditiveInverse. {name} is of type {obj?.GetType()} " +
$"(expected {typeof(double)})");
}
}
class AdditiveInverse<T> : IArithmeticExpression<T>
where T : INumber<T> {
readonly IArithmeticExpression<T> expression;
public AdditiveInverse(IArithmeticExpression<T> expression) =>
this.expression = expression;
public T Evaluate(IDictionary<string, object?> env) =>
-expression.Evaluate(env);
}
@@ -0,0 +1,45 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
public static class ArithmeticExpressionExtensions {
public static IArithmeticExpression<TResult> Add<TLeft, TRight, TResult>(
this IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right)
where TLeft :
INumber<TLeft>,
IAdditionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> =>
new Sum<TLeft, TRight, TResult>(left, right);
public static IArithmeticExpression<TResult> Subtract<TLeft, TRight, TResult>(
this IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right)
where TLeft :
INumber<TLeft>,
ISubtractionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> =>
new Difference<TLeft, TRight, TResult>(left, right);
public static IArithmeticExpression<TResult> Multiply<TLeft, TRight, TResult>(
this IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right)
where TLeft :
INumber<TLeft>,
IMultiplyOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> =>
new Product<TLeft, TRight, TResult>(left, right);
public static IArithmeticExpression<TResult> Divide<TLeft, TRight, TResult>(
this IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right)
where TLeft :
INumber<TLeft>,
IDivisionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> =>
new Quotient<TLeft, TRight, TResult>(left, right);
public static IArithmeticExpression<T> Negate<T>(this IArithmeticExpression<T> expression)
where T : INumber<T> =>
new AdditiveInverse<T>(expression);
}
@@ -0,0 +1,99 @@
%namespace Bts.ArithmeticExpressions
%partial
%visibility internal
%tokentype Token
%parsertype ArithmeticExpressionParserImpl
%union {
public int Integer;
public double Real;
public string Identifier;
public DateTime DateTime;
public ImmutableList<IArithmeticExpression> Arguments;
public ImmutableList<Assignment> Assignments;
public IArithmeticExpression Argument;
public Assignment Assignment;
public ArithmeticProgram Program;
}
/* Constants */
%token INTEGER_CONSTANT REAL_CONSTANT
/* Arithmetic Operators */
%token ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUALS
/* Syntax */
%token PARENTHESIS_OPEN, PARENTHESIS_CLOSE, COMMA, IDENTIFIER
%left ADD SUBTRACT
%left MULTIPLY DIVIDE
%nonassoc EQUALS
%type <Argument> Argument
%type <Arguments> Arguments
%type <Assignment> Assignment
%type <Assignments> Assignments
%type <Program> Program
%%
Program :
Assignments {
$$ = new($1);
}
;
Assignments :
Assignment {
$$ = ImmutableList<Assignment>.Empty.Add($1);
}
| Assignments Assignment {
$$ = $1.Add($2);
}
;
Assignment :
IDENTIFIER EQUALS Argument {
$$ = new($1.Identifier, $3);
}
;
Argument :
IDENTIFIER PARENTHESIS_OPEN Arguments PARENTHESIS_CLOSE {
$$ = new Function($1.Identifier, $3);
}
| INTEGER_CONSTANT {
$$ = new IntegerConstant($1.Integer);
}
| REAL_CONSTANT {
$$ = new RealConstant($1.Real);
}
| IDENTIFIER {
$$ = new Variable($1.Identifier);
}
| SUBTRACT Argument {
$$ = $2.Negate();
}
| PARENTHESIS_OPEN Argument PARENTHESIS_CLOSE {
$$ = $2;
}
| Argument MULTIPLY Argument {
$$ = $1.Multiply($3);
}
| Argument DIVIDE Argument {
$$ = $1.Divide($3);
}
| Argument ADD Argument {
$$ = $1.Add($3);
}
| Argument SUBTRACT Argument {
$$ = $1.Subtract($3);
}
;
Arguments :
Argument {
$$ = ImmutableList<IArithmeticExpression>.Empty.Add($1);
}
| Arguments COMMA Argument {
$$ = $1.Add($3);
}
;
@@ -0,0 +1,19 @@
namespace Bts.ArithmeticExpressions;
using System.Text;
partial class ArithmeticExpressionParserImpl {
public ArithmeticExpressionParserImpl() : base(null) { }
public ValueType Parse(Stream stream) {
Scanner = new ArithmeticExpressionScanner(stream);
Parse();
return CurrentSemanticValue;
}
public ValueType Parse(string str) {
var inputBuffer = Encoding.Default.GetBytes(str);
using var stream = new MemoryStream(inputBuffer);
return Parse(stream);
}
}
@@ -0,0 +1,11 @@
namespace Bts.ArithmeticExpressions;
using System;
partial class ArithmeticExpressionScanner {
public override void yyerror(string format, params object[] args) {
base.yyerror(format, args);
Console.WriteLine(format, args);
Console.WriteLine();
}
}
@@ -0,0 +1,70 @@
%namespace Bts.ArithmeticExpressions
%scannertype ArithmeticExpressionScanner
%visibility internal
%tokentype Token
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers
Letter [a-zA-Z]
Identifier {Letter}({Letter}*[0-9]*_*)*
IntegerConstant -?[0-9]+
RealConstant -?[0-9]+\.[0-9]+
ParenthesisOpen \(
ParenthesisClose \)
Add \+
Subtract \-
Multiply \*
Divide /
Equals =
Comma ,
%%
{IntegerConstant} {
yylval.Integer = int.Parse(yytext);
return (int)Token.INTEGER_CONSTANT;
}
{RealConstant} {
yylval.Real = double.Parse(yytext);
return (int)Token.REAL_CONSTANT;
}
{Identifier} {
yylval.Identifier = yytext;
return (int)Token.IDENTIFIER;
}
{ParenthesisOpen} {
return (int)Token.PARENTHESIS_OPEN;
}
{ParenthesisClose} {
return (int)Token.PARENTHESIS_CLOSE;
}
{Add} {
return (int)Token.ADD;
}
{Subtract} {
return (int)Token.SUBTRACT;
}
{Multiply} {
return (int)Token.MULTIPLY;
}
{Divide} {
return (int)Token.DIVIDE;
}
{Equals} {
return (int)Token.EQUALS;
}
{Comma} {
return (int)Token.COMMA;
}
%%
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExpressionCore\ExpressionCore.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
<ItemGroup Label="ArithmeticExpression">
<Scanner Include="ArithmeticExpressionTokens.lex" />
<Parser Include="ArithmeticExpressionGrammar.y" />
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
namespace Bts.ArithmeticExpressions;
using System.Collections.Immutable;
class ArithmeticProgram : IArithmeticProgram {
readonly ImmutableList<Assignment>? assignments;
public ArithmeticProgram(Assignment assignment) =>
assignments = ImmutableList<Assignment>.Empty.Add(assignment);
public ArithmeticProgram(ImmutableList<Assignment> assignments) =>
this.assignments = assignments;
IEnumerable<string> IArithmeticProgram.OutputIdentifiers =>
assignments.empty_if_null()
.Select(a => a.Identifier ?? throw new Exception("Identifier string is null"))
.Distinct();
IDictionary<string, object?> IArithmeticProgram.Evaluate(IDictionary<string, object?> env) =>
assignments.empty_if_null().Aggregate(env, (e, a) => a.Evaluate(e));
}
@@ -0,0 +1,16 @@
namespace Bts.ArithmeticExpressions;
using Bts.ExpressionCore;
public static class ArithmeticProgramParser {
public static IArithmeticProgram Parse(Stream expression) =>
new Parser().Parse(expression);
public static IArithmeticProgram Parse(string expression) =>
new Parser().Parse(expression);
class Parser : IParser<ArithmeticProgram> {
public ArithmeticProgram Parse(Stream expression) =>
new ArithmeticExpressionParserImpl().Parse(expression).Program;
}
}
@@ -0,0 +1,24 @@
namespace Bts.ArithmeticExpressions;
public class Assignment {
public Assignment(string? identifier, IArithmeticExpression? value) {
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier));
Value = value ?? throw new ArgumentNullException(nameof(identifier));
}
public string? Identifier { get; }
public IArithmeticExpression? Value { get; }
public IDictionary<string, object?> Evaluate(IDictionary<string, object?> env) {
if (Identifier is null) {
throw new InvalidOperationException("Identifier is null");
}
if (Value is null) {
throw new InvalidOperationException($"Value is null (Identifier '{Identifier}')");
}
env[Identifier] = Value.Evaluate(env) ?? throw new Exception(
$"{nameof(Assignment)}.{nameof(Value)} (of type '{Value.GetType()}') " +
$"evaluated to null (Identifier '{Identifier}')");
return env;
}
}
@@ -0,0 +1,45 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
public class Difference : IArithmeticExpression<double> {
readonly IArithmeticExpression lhs;
readonly IArithmeticExpression rhs;
public Difference(IArithmeticExpression lhs, IArithmeticExpression rhs) {
this.lhs = lhs;
this.rhs = rhs;
}
public static Difference<T, T, T> Create<T>(
IArithmeticExpression<T> left, IArithmeticExpression<T> right)
where T : INumber<T>, ISubtractionOperators<T, T, T> =>
new(left, right);
public double Evaluate(IDictionary<string, object?> env) =>
evaluateArg(lhs, nameof(lhs), env) - evaluateArg(rhs, nameof(rhs), env);
static double evaluateArg(
IArithmeticExpression expr, string name, IDictionary<string, object?> env) {
var obj = expr.Evaluate(env);
return ConvertUtils.try_convert(expr.Evaluate(env), out double dbl) ? dbl :
throw new Exception(
$"Invalid Difference. {name} is of type {obj?.GetType()} (expected {typeof(double)})");
}
}
public class Difference<TLeft, TRight, TResult> : IArithmeticExpression<TResult>
where TLeft : INumber<TLeft>, ISubtractionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> {
readonly IArithmeticExpression<TLeft> left;
readonly IArithmeticExpression<TRight> right;
public Difference(IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right) {
this.left = left;
this.right = right;
}
public TResult Evaluate(IDictionary<string, object?> env) =>
left.Evaluate(env) - right.Evaluate(env);
}
@@ -0,0 +1,208 @@
namespace Bts.ArithmeticExpressions;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
class EvaluationEnvironment : IDictionary<string, object?>, IReadOnlyDictionary<string, object?> {
IDictionary<string, object?> outerEnv;
IDictionary<string, object?> innerEnv = new Dictionary<string, object?>();
int? count;
public EvaluationEnvironment(IDictionary<string, object?> initial) =>
outerEnv = initial;
public object? this[string key] {
get {
if (innerEnv.TryGetValue(key, out var rv)) {
return rv;
}
if (outerEnv.TryGetValue(key, out rv)) {
return rv;
}
throw new Exception($"Undefined identifier '{key}'");
}
set {
if (count.HasValue && !ContainsKey(key)) {
count = count.Value + 1;
innerEnv.Add(key, value);
return;
}
innerEnv[key] = value;
}
}
public IDictionary<string, object?> Result =>
innerEnv;
public ICollection<string> Keys =>
new KeyCollection(outerEnv, innerEnv);
public ICollection<object?> Values =>
new ValueCollection(outerEnv, innerEnv);
public int Count =>
count ??= countKeys(outerEnv, innerEnv);
public bool IsReadOnly => throw new NotImplementedException();
public bool ContainsKey(string key) =>
innerEnv.ContainsKey(key) || outerEnv.ContainsKey(key);
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) =>
innerEnv.TryGetValue(key, out value) || outerEnv.TryGetValue(key, out value);
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() {
foreach (var kv in innerEnv) {
yield return kv;
}
foreach (var kv in outerEnv.Except()) {
if (!innerEnv.ContainsKey(kv.Key)) {
yield return kv;
}
}
}
public void Add(string key, object? value) {
if (ContainsKey(key)) {
throw new ArgumentException(
$"An element with the same key ({key}) already exists", nameof(key));
}
innerEnv.Add(key, value);
}
public void Add(KeyValuePair<string, object?> item) =>
Add(item.Key, item.Value);
public void Clear() =>
throw new NotSupportedException();
public bool Contains(KeyValuePair<string, object?> item) =>
TryGetValue(item.Key, out var v) && Equals(v, item.Value);
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) {
foreach (var kv in innerEnv) {
array[arrayIndex++] = kv;
}
foreach (var kv in outerEnv) {
if (!innerEnv.ContainsKey(kv.Key)) {
array[arrayIndex++] = kv;
}
}
}
public bool Remove(string key) =>
throw new NotSupportedException();
public bool Remove(KeyValuePair<string, object?> item) =>
throw new NotSupportedException();
static int countKeys(IDictionary<string, object?> d1, IDictionary<string, object?> d2) =>
d1.Count > d2.Count ?
d1.Count + d2.Keys.WhereNot(d1.ContainsKey).Count() :
d2.Count + d1.Keys.WhereNot(d2.ContainsKey).Count();
IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => Keys;
IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => Values;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
class KeyCollection : ICollection<string> {
readonly IDictionary<string, object?> outerEnv;
readonly IDictionary<string, object?> innerEnv;
public KeyCollection(
IDictionary<string, object?> outerEnv, IDictionary<string, object?> innerEnv) {
this.outerEnv = outerEnv;
this.innerEnv = innerEnv;
}
public int Count =>
countKeys(outerEnv, innerEnv);
public bool IsReadOnly => true;
public void Add(string item) =>
throw new NotSupportedException();
public void Clear() =>
throw new NotSupportedException();
public bool Contains(string item) =>
innerEnv.ContainsKey(item) || outerEnv.ContainsKey(item);
public void CopyTo(string[] array, int arrayIndex) {
foreach (var k in this) {
array[arrayIndex++] = k;
}
}
public IEnumerator<string> GetEnumerator() {
foreach (var k in innerEnv.Keys) {
yield return k;
}
foreach (var k in outerEnv.Keys) {
if (!innerEnv.ContainsKey(k)) {
yield return k;
}
}
}
public bool Remove(string item) =>
throw new NotSupportedException();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
class ValueCollection : ICollection<object?> {
readonly IDictionary<string, object?> outerEnv;
readonly IDictionary<string, object?> innerEnv;
public ValueCollection(
IDictionary<string, object?> outerEnv, IDictionary<string, object?> innerEnv) {
this.outerEnv = outerEnv;
this.innerEnv = innerEnv;
}
public int Count =>
// Not a typo (obv, numValues == numKeys)
countKeys(outerEnv, innerEnv);
public bool IsReadOnly => true;
public void Add(object? item) =>
throw new NotSupportedException();
public void Clear() =>
throw new NotSupportedException();
public bool Contains(object? item) =>
innerEnv.Values.Contains(item) ||
outerEnv
.Where(kv => !innerEnv.ContainsKey(kv.Key))
.Select(kv => kv.Value)
.Contains(item);
public void CopyTo(object?[] array, int arrayIndex) {
foreach (var k in this) {
array[arrayIndex++] = k;
}
}
public IEnumerator<object?> GetEnumerator() {
foreach (var k in innerEnv.Values) {
yield return k;
}
foreach (var kv in outerEnv) {
if (!innerEnv.ContainsKey(kv.Key)) {
yield return kv.Value;
}
}
}
public bool Remove(object? item) =>
throw new NotSupportedException();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
@@ -0,0 +1,34 @@
namespace Bts.ArithmeticExpressions;
using Spark.Core;
using System.Collections.Immutable;
using System.Data;
public class Function : IArithmeticExpression {
public Function(string? identifier, ImmutableList<IArithmeticExpression>? arguments) {
Identifier = identifier;
Arguments = arguments;
}
public string? Identifier { get; }
public ImmutableList<IArithmeticExpression>? Arguments { get; }
public object? Evaluate(IDictionary<string, object?> env) {
if (Identifier is null) {
throw new InvalidOperationException("Identifier is null");
}
if (Arguments is null) {
throw new InvalidOperationException("Arguments is null");
}
if (Identifier == "ifNull") {
throw new NotImplementedException("'ifNull' function not yet implemented");
}
var argVals = Arguments.Select(e => e.Evaluate(env)).ToArray();
var argTypes = Array.ConvertAll(argVals, v => v?.GetType() ?? typeof(object));
var method = typeof(Math).GetMethod(Identifier, argTypes) ??
throw new EvaluateException(
$"Failed to find method '{Identifier}({", ".join(argTypes)})' in {typeof(Math)}");
return method.Invoke(null, argVals);
}
}
@@ -0,0 +1,720 @@
// ==============================================================
// <auto-generated>
// This code automatically produced from an embedded resource.
// Do not edit this file, or it will become incompatible with
// the specification from which it was generated.
// </auto-generated>
// ==============================================================
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Globalization;
namespace QUT.GplexBuffers
{
// Code copied from GPLEX embedded resource
[Serializable]
internal class BufferException : Exception
{
public BufferException() { }
public BufferException(string message) : base(message) { }
public BufferException(string message, Exception innerException)
: base(message, innerException) { }
protected BufferException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
internal abstract class ScanBuff
{
private string fileNm;
public const int EndOfFile = -1;
public const int UnicodeReplacementChar = 0xFFFD;
public bool IsFile { get { return (fileNm != null); } }
public string FileName { get { return fileNm; } set { fileNm = value; } }
public abstract int Pos { get; set; }
public abstract int Read();
public virtual void Mark() { }
public abstract string GetString(int begin, int limit);
public static ScanBuff GetBuffer(string source)
{
return new StringBuffer(source);
}
public static ScanBuff GetBuffer(IList<string> source)
{
return new LineBuffer(source);
}
#if (!NOFILES)
public static ScanBuff GetBuffer(Stream source)
{
return new BuildBuffer(source);
}
#if (!BYTEMODE)
public static ScanBuff GetBuffer(Stream source, int fallbackCodePage)
{
return new BuildBuffer(source, fallbackCodePage);
}
#endif // !BYTEMODE
#endif // !NOFILES
}
#region Buffer classes
// ==============================================================
// ===== Definitions for various ScanBuff derived classes ====
// ==============================================================
// =============== String input ================
// ==============================================================
/// <summary>
/// This class reads characters from a single string as
/// required, for example, by Visual Studio language services
/// </summary>
sealed class StringBuffer : ScanBuff
{
string str; // input buffer
int bPos; // current position in buffer
int sLen;
public StringBuffer(string source)
{
this.str = source;
this.sLen = source.Length;
this.FileName = null;
}
public override int Read()
{
if (bPos < sLen) return str[bPos++];
else if (bPos == sLen) { bPos++; return '\n'; } // one strike, see new line
else { bPos++; return EndOfFile; } // two strikes and you're out!
}
public override string GetString(int begin, int limit)
{
// "limit" can be greater than sLen with the BABEL
// option set. Read returns a "virtual" EOL if
// an attempt is made to read past the end of the
// string buffer. Without the guard any attempt
// to fetch yytext for a token that includes the
// EOL will throw an index exception.
if (limit > sLen) limit = sLen;
if (limit <= begin) return "";
else return str.Substring(begin, limit - begin);
}
public override int Pos
{
get { return bPos; }
set { bPos = value; }
}
public override string ToString() { return "StringBuffer"; }
}
// ==============================================================
// The LineBuff class contributed by Nigel Horspool,
// nigelh@cs.uvic.cs
// ==============================================================
sealed class LineBuffer : ScanBuff
{
IList<string> line; // list of source lines from a file
int numLines; // number of strings in line list
string curLine; // current line in that list
int cLine; // index of current line in the list
int curLen; // length of current line
int curLineStart; // position of line start in whole file
int curLineEnd; // position of line end in whole file
int maxPos; // max position ever visited in whole file
int cPos; // ordinal number of code in source
// Constructed from a list of strings, one per source line.
// The lines have had trailing '\n' characters removed.
public LineBuffer(IList<string> lineList)
{
line = lineList;
numLines = line.Count;
cPos = curLineStart = 0;
curLine = (numLines > 0 ? line[0] : "");
maxPos = curLineEnd = curLen = curLine.Length;
cLine = 1;
FileName = null;
}
public override int Read()
{
if (cPos < curLineEnd)
return curLine[cPos++ - curLineStart];
if (cPos++ == curLineEnd)
return '\n';
if (cLine >= numLines)
return EndOfFile;
curLine = line[cLine];
curLen = curLine.Length;
curLineStart = curLineEnd + 1;
curLineEnd = curLineStart + curLen;
if (curLineEnd > maxPos)
maxPos = curLineEnd;
cLine++;
return curLen > 0 ? curLine[0] : '\n';
}
// To speed up searches for the line containing a position
private int cachedPosition;
private int cachedIxdex;
private int cachedLineStart;
// Given a position pos within the entire source, the results are
// ix -- the index of the containing line
// lstart -- the position of the first character on that line
private void findIndex(int pos, out int ix, out int lstart)
{
if (pos >= cachedPosition)
{
ix = cachedIxdex; lstart = cachedLineStart;
}
else
{
ix = lstart = 0;
}
while (ix < numLines)
{
int len = line[ix].Length + 1;
if (pos < lstart + len) break;
lstart += len;
ix++;
}
cachedPosition = pos;
cachedIxdex = ix;
cachedLineStart = lstart;
}
public override string GetString(int begin, int limit)
{
if (begin >= maxPos || limit <= begin) return "";
int endIx, begIx, endLineStart, begLineStart;
findIndex(begin, out begIx, out begLineStart);
int begCol = begin - begLineStart;
findIndex(limit, out endIx, out endLineStart);
int endCol = limit - endLineStart;
string s = line[begIx];
if (begIx == endIx)
{
// the usual case, substring all on one line
return (endCol <= s.Length) ?
s.Substring(begCol, endCol - begCol)
: s.Substring(begCol) + "\n";
}
// the string spans multiple lines, yuk!
StringBuilder sb = new StringBuilder();
if (begCol < s.Length)
sb.Append(s.Substring(begCol));
for (; ; )
{
sb.Append("\n");
s = line[++begIx];
if (begIx >= endIx) break;
sb.Append(s);
}
if (endCol <= s.Length)
{
sb.Append(s.Substring(0, endCol));
}
else
{
sb.Append(s);
sb.Append("\n");
}
return sb.ToString();
}
public override int Pos
{
get { return cPos; }
set
{
cPos = value;
findIndex(cPos, out cLine, out curLineStart);
// cLine should be the *next* line after curLine.
curLine = (cLine < numLines ? line[cLine++] : "");
curLineEnd = curLineStart + curLine.Length;
}
}
public override string ToString() { return "LineBuffer"; }
}
#if (!NOFILES)
// ==============================================================
// ===== class BuildBuff : for unicode text files ========
// ==============================================================
class BuildBuffer : ScanBuff
{
// Double buffer for char stream.
class BufferElement
{
StringBuilder bldr = new StringBuilder();
StringBuilder next = new StringBuilder();
int minIx;
int maxIx;
int brkIx;
bool appendToNext;
internal BufferElement() { }
internal int MaxIndex { get { return maxIx; } }
// internal int MinIndex { get { return minIx; } }
internal char this[int index]
{
get
{
if (index < minIx || index >= maxIx)
throw new BufferException("Index was outside data buffer");
else if (index < brkIx)
return bldr[index - minIx];
else
return next[index - brkIx];
}
}
internal void Append(char[] block, int count)
{
maxIx += count;
if (appendToNext)
this.next.Append(block, 0, count);
else
{
this.bldr.Append(block, 0, count);
brkIx = maxIx;
appendToNext = true;
}
}
internal string GetString(int start, int limit)
{
if (limit <= start)
return "";
if (start >= minIx && limit <= maxIx)
if (limit < brkIx) // String entirely in bldr builder
return bldr.ToString(start - minIx, limit - start);
else if (start >= brkIx) // String entirely in next builder
return next.ToString(start - brkIx, limit - start);
else // Must do a string-concatenation
return
bldr.ToString(start - minIx, brkIx - start) +
next.ToString(0, limit - brkIx);
else
throw new BufferException("String was outside data buffer");
}
internal void Mark(int limit)
{
if (limit > brkIx + 16) // Rotate blocks
{
StringBuilder temp = bldr;
bldr = next;
next = temp;
next.Length = 0;
minIx = brkIx;
brkIx = maxIx;
}
}
}
BufferElement data = new BufferElement();
int bPos; // Postion index in the StringBuilder
BlockReader NextBlk; // Delegate that serves char-arrays;
private string EncodingName
{
get
{
StreamReader rdr = NextBlk.Target as StreamReader;
return (rdr == null ? "raw-bytes" : rdr.CurrentEncoding.BodyName);
}
}
public BuildBuffer(Stream stream)
{
FileStream fStrm = (stream as FileStream);
if (fStrm != null) FileName = fStrm.Name;
NextBlk = BlockReaderFactory.Raw(stream);
}
#if (!BYTEMODE)
public BuildBuffer(Stream stream, int fallbackCodePage)
{
FileStream fStrm = (stream as FileStream);
if (fStrm != null) FileName = fStrm.Name;
NextBlk = BlockReaderFactory.Get(stream, fallbackCodePage);
}
#endif
/// <summary>
/// Marks a conservative lower bound for the buffer,
/// allowing space to be reclaimed. If an application
/// needs to call GetString at arbitrary past locations
/// in the input stream, Mark() is not called.
/// </summary>
public override void Mark() { data.Mark(bPos - 2); }
public override int Pos
{
get { return bPos; }
set { bPos = value; }
}
/// <summary>
/// Read returns the ordinal number of the next char, or
/// EOF (-1) for an end of stream. Note that the next
/// code point may require *two* calls of Read().
/// </summary>
/// <returns></returns>
public override int Read()
{
//
// Characters at positions
// [data.offset, data.offset + data.bldr.Length)
// are available in data.bldr.
//
if (bPos < data.MaxIndex)
{
// ch0 cannot be EOF
return (int)data[bPos++];
}
else // Read from underlying stream
{
// Experimental code, blocks of page size
char[] chrs = new char[4096];
int count = NextBlk(chrs, 0, 4096);
if (count == 0)
return EndOfFile;
else
{
data.Append(chrs, count);
return (int)data[bPos++];
}
}
}
public override string GetString(int begin, int limit)
{
return data.GetString(begin, limit);
}
public override string ToString()
{
return "StringBuilder buffer, encoding: " + this.EncodingName;
}
}
// =============== End ScanBuff-derived classes ==================
internal delegate int BlockReader(char[] block, int index, int number);
// A delegate factory, serving up a delegate that
// reads a block of characters from the underlying
// encoded stream, via a StreamReader object.
//
internal static class BlockReaderFactory
{
public static BlockReader Raw(Stream stream)
{
return delegate(char[] block, int index, int number)
{
byte[] b = new byte[number];
int count = stream.Read(b, 0, number);
int i = 0;
int j = index;
for (; i < count; i++, j++)
block[j] = (char)b[i];
return count;
};
}
#if (!BYTEMODE)
public static BlockReader Get(Stream stream, int fallbackCodePage)
{
Encoding encoding;
int preamble = Preamble(stream);
if (preamble != 0) // There is a valid BOM here!
encoding = Encoding.GetEncoding(preamble);
else if (fallbackCodePage == -1) // Fallback is "raw" bytes
return Raw(stream);
else if (fallbackCodePage != -2) // Anything but "guess"
encoding = Encoding.GetEncoding(fallbackCodePage);
else // This is the "guess" option
{
int guess = new Guesser(stream).GuessCodePage();
stream.Seek(0, SeekOrigin.Begin);
if (guess == -1) // ==> this is a 7-bit file
encoding = Encoding.ASCII;
else if (guess == 65001)
encoding = Encoding.UTF8;
else // ==> use the machine default
encoding = Encoding.Default;
}
StreamReader reader = new StreamReader(stream, encoding);
return reader.Read;
}
static int Preamble(Stream stream)
{
int b0 = stream.ReadByte();
int b1 = stream.ReadByte();
if (b0 == 0xfe && b1 == 0xff)
return 1201; // UTF16BE
if (b0 == 0xff && b1 == 0xfe)
return 1200; // UTF16LE
int b2 = stream.ReadByte();
if (b0 == 0xef && b1 == 0xbb && b2 == 0xbf)
return 65001; // UTF8
//
// There is no unicode preamble, so we
// return denoter for the machine default.
//
stream.Seek(0, SeekOrigin.Begin);
return 0;
}
#endif // !BYTEMODE
}
#endif // !NOFILES
#endregion Buffer classes
// ==============================================================
// ============ class CodePageHandling =============
// ==============================================================
#if (!NOFILES)
public static class CodePageHandling
{
public static int GetCodePage(string option)
{
string command = option.ToUpperInvariant();
if (command.StartsWith("CodePage:", StringComparison.OrdinalIgnoreCase))
command = command.Substring(9);
try
{
if (command.Equals("RAW"))
return -1;
else if (command.Equals("GUESS"))
return -2;
else if (command.Equals("DEFAULT"))
return 0;
else if (char.IsDigit(command[0]))
return int.Parse(command, CultureInfo.InvariantCulture);
else
{
Encoding enc = Encoding.GetEncoding(command);
return enc.CodePage;
}
}
catch (FormatException)
{
Console.Error.WriteLine(
"Invalid format \"{0}\", using machine default", option);
}
catch (ArgumentException)
{
Console.Error.WriteLine(
"Unknown code page \"{0}\", using machine default", option);
}
return 0;
}
}
#region guesser
#if (!BYTEMODE)
// ==============================================================
// ============ Encoding Guesser =============
// ==============================================================
/// <summary>
/// This class provides a simple finite state automaton that
/// scans the file looking for (1) valid UTF-8 byte patterns,
/// (2) bytes >= 0x80 which are not part of a UTF-8 sequence.
/// The method then guesses whether it is UTF-8 or maybe some
/// local machine default encoding. This works well for the
/// various Latin encodings.
/// </summary>
internal class Guesser
{
ScanBuff buffer;
public int GuessCodePage() { return Scan(); }
const int maxAccept = 10;
const int initial = 0;
const int eofNum = 0;
const int goStart = -1;
const int INITIAL = 0;
const int EndToken = 0;
#region user code
/*
* Reads the bytes of a file to determine if it is
* UTF-8 or a single-byte code page file.
*/
public long utfX;
public long uppr;
#endregion user code
int state;
int currentStart = startState[0];
int code;
#region ScannerTables
static int[] startState = new int[] { 11, 0 };
#region CharacterMap
static sbyte[] map = new sbyte[256] {
/* '\0' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x10' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x20' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '0' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '@' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 'P' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '`' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 'p' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x80' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\x90' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xA0' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xB0' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xC0' */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* '\xD0' */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* '\xE0' */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
/* '\xF0' */ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 };
#endregion
static sbyte[][] nextState = new sbyte[][] {
new sbyte[] {0, 0, 0, 0, 0, 0},
new sbyte[] {-1, -1, 10, -1, -1, -1},
new sbyte[] {-1, -1, -1, -1, -1, -1},
new sbyte[] {-1, -1, 8, -1, -1, -1},
new sbyte[] {-1, -1, 5, -1, -1, -1},
new sbyte[] {-1, -1, 6, -1, -1, -1},
new sbyte[] {-1, -1, 7, -1, -1, -1},
null,
new sbyte[] {-1, -1, 9, -1, -1, -1},
null,
null,
new sbyte[] {-1, 1, 2, 3, 4, 2}
};
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
// Reason for suppression: cannot have self-reference in array initializer.
static Guesser()
{
nextState[7] = nextState[2];
nextState[9] = nextState[2];
nextState[10] = nextState[2];
}
int NextState()
{
if (code == ScanBuff.EndOfFile)
return eofNum;
else
return nextState[state][map[code]];
}
#endregion
public Guesser(System.IO.Stream file) { SetSource(file); }
public void SetSource(System.IO.Stream source)
{
this.buffer = new BuildBuffer(source);
code = buffer.Read();
}
int Scan()
{
for (; ; )
{
int next;
state = currentStart;
while ((next = NextState()) == goStart)
code = buffer.Read();
state = next;
code = buffer.Read();
while ((next = NextState()) > eofNum)
{
state = next;
code = buffer.Read();
}
if (state <= maxAccept)
{
#region ActionSwitch
#pragma warning disable 162
switch (state)
{
case eofNum:
switch (currentStart)
{
case 11:
if (utfX == 0 && uppr == 0) return -1; /* raw ascii */
else if (uppr * 10 > utfX) return 0; /* default code page */
else return 65001; /* UTF-8 encoding */
break;
}
return EndToken;
case 1: // Recognized '{Upper128}', Shortest string "\xC0"
case 2: // Recognized '{Upper128}', Shortest string "\x80"
case 3: // Recognized '{Upper128}', Shortest string "\xE0"
case 4: // Recognized '{Upper128}', Shortest string "\xF0"
uppr++;
break;
case 5: // Recognized '{Utf8pfx4}{Utf8cont}', Shortest string "\xF0\x80"
uppr += 2;
break;
case 6: // Recognized '{Utf8pfx4}{Utf8cont}{2}', Shortest string "\xF0\x80\x80"
uppr += 3;
break;
case 7: // Recognized '{Utf8pfx4}{Utf8cont}{3}', Shortest string "\xF0\x80\x80\x80"
utfX += 3;
break;
case 8: // Recognized '{Utf8pfx3}{Utf8cont}', Shortest string "\xE0\x80"
uppr += 2;
break;
case 9: // Recognized '{Utf8pfx3}{Utf8cont}{2}', Shortest string "\xE0\x80\x80"
utfX += 2;
break;
case 10: // Recognized '{Utf8pfx2}{Utf8cont}', Shortest string "\xC0\x80"
utfX++;
break;
default:
break;
}
#pragma warning restore 162
#endregion
}
}
}
} // end class Guesser
#endif // !BYTEMODE
#endregion
#endif // !NOFILES
// End of code copied from embedded resource
}
@@ -0,0 +1,19 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
public interface IArithmeticExpression {
IArithmeticExpression Add(IArithmeticExpression factor) => new Sum(this, factor);
IArithmeticExpression Divide(IArithmeticExpression other) => new Quotient(this, other);
IArithmeticExpression Multiply(IArithmeticExpression other) => new Product(this, other);
IArithmeticExpression Subtract(IArithmeticExpression other) => new Difference(this, other);
IArithmeticExpression Negate() => new AdditiveInverse(this);
object? Evaluate(IDictionary<string, object?> env);
}
public interface IArithmeticExpression<T> : IArithmeticExpression where T : INumber<T> {
new T Evaluate(IDictionary<string, object?> env);
object? IArithmeticExpression.Evaluate(IDictionary<string, object?> env) =>
Evaluate(env);
}
@@ -0,0 +1,7 @@
namespace Bts.ArithmeticExpressions;
public interface IArithmeticProgram {
IEnumerable<string> OutputIdentifiers { get; }
IDictionary<string, object?> Evaluate(IDictionary<string, object?> env);
}
@@ -0,0 +1,11 @@
namespace Bts.ArithmeticExpressions;
class IntegerConstant : IArithmeticExpression<int> {
readonly int value;
public IntegerConstant(int value) =>
this.value = value;
public int Evaluate(IDictionary<string, object?> env) =>
value;
}
@@ -0,0 +1,45 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
class Product : IArithmeticExpression<double> {
readonly IArithmeticExpression left;
readonly IArithmeticExpression right;
public Product(IArithmeticExpression left, IArithmeticExpression right) {
this.left = left;
this.right = right;
}
public static Product<T, T, T> Create<T>(
IArithmeticExpression<T> left, IArithmeticExpression<T> right)
where T : INumber<T>, IMultiplyOperators<T, T, T> =>
new(left, right);
public double Evaluate(IDictionary<string, object?> env) =>
evaluateArg(left, nameof(left), env) * evaluateArg(right, nameof(right), env);
static double evaluateArg(
IArithmeticExpression expr, string name, IDictionary<string, object?> env) {
var obj = expr.Evaluate(env);
return ConvertUtils.try_convert(expr.Evaluate(env), out double dbl) ? dbl :
throw new Exception(
$"Invalid Product. {name} is of type {obj?.GetType()} (expected {typeof(double)})");
}
}
class Product<TLeft, TRight, TResult> : IArithmeticExpression<TResult>
where TLeft : INumber<TLeft>, IMultiplyOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> {
readonly IArithmeticExpression<TLeft> left;
readonly IArithmeticExpression<TRight> right;
public Product(IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right) {
this.left = left;
this.right = right;
}
public TResult Evaluate(IDictionary<string, object?> env) =>
left.Evaluate(env) * right.Evaluate(env);
}
@@ -0,0 +1,45 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
class Quotient : IArithmeticExpression<double> {
readonly IArithmeticExpression left;
readonly IArithmeticExpression right;
public Quotient(IArithmeticExpression left, IArithmeticExpression right) {
this.left = left;
this.right = right;
}
public static Quotient<T, T, T> Create<T>(
IArithmeticExpression<T> left, IArithmeticExpression<T> right)
where T : INumber<T>, IDivisionOperators<T, T, T> =>
new(left, right);
public double Evaluate(IDictionary<string, object?> env) =>
evaluateArg(left, nameof(left), env) / evaluateArg(right, nameof(right), env);
static double evaluateArg(
IArithmeticExpression expr, string name, IDictionary<string, object?> env) {
var obj = expr.Evaluate(env);
return ConvertUtils.try_convert(expr.Evaluate(env), out double dbl) ? dbl :
throw new Exception(
$"Invalid Quotient. {name} is of type {obj?.GetType()} (expected {typeof(double)})");
}
}
class Quotient<TLeft, TRight, TResult> : IArithmeticExpression<TResult>
where TLeft : INumber<TLeft>, IDivisionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> {
readonly IArithmeticExpression<TLeft> left;
readonly IArithmeticExpression<TRight> right;
public Quotient(IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right) {
this.left = left;
this.right = right;
}
public TResult Evaluate(IDictionary<string, object?> env) =>
left.Evaluate(env) / right.Evaluate(env);
}
@@ -0,0 +1,11 @@
namespace Bts.ArithmeticExpressions;
class RealConstant : IArithmeticExpression<double> {
readonly double value;
public RealConstant(double value) =>
this.value = value;
public double Evaluate(IDictionary<string, object?> env) =>
value;
}
@@ -0,0 +1,884 @@
// ==============================================================
// <auto-generated>
// This code automatically produced from an embedded resource.
// Do not edit this file, or it will become incompatible with
// the specification from which it was generated.
// </auto-generated>
// ==============================================================
// Gardens Point Parser Generator
// Copyright (c) Wayne Kelly, QUT 2005-2010
// (see accompanying GPPGcopyright.rtf)
using System;
using System.Text;
using System.Globalization;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;
namespace QUT.Gppg {
/// <summary>
/// Abstract class for GPPG shift-reduce parsers.
/// Parsers generated by GPPG derive from this base
/// class, overriding the abstract Initialize() and
/// DoAction() methods.
/// </summary>
/// <typeparam name="TValue">Semantic value type</typeparam>
/// <typeparam name="TSpan">Location type</typeparam>
#if EXPORT_GPPG
public abstract class ShiftReduceParser<TValue, TSpan>
#else
internal abstract class ShiftReduceParser<TValue, TSpan>
#endif
where TSpan : IMerge<TSpan>, new() {
private AbstractScanner<TValue, TSpan> scanner;
/// <summary>
/// The abstract scanner for this parser.
/// </summary>
protected AbstractScanner<TValue, TSpan> Scanner {
get { return scanner; }
set { scanner = value; }
}
/// <summary>
/// Constructor for base class
/// </summary>
/// <param name="scanner">Scanner instance for this parser</param>
protected ShiftReduceParser( AbstractScanner<TValue, TSpan> scanner ) {
this.scanner = scanner;
}
// ==============================================================
// TECHNICAL EXPLANATION.
// Why the next two fields are not exposed via properties.
// ==============================================================
// These fields are of the generic parameter types, and are
// frequently instantiated as struct types in derived classes.
// Semantic actions are defined in the derived classes and refer
// to instance fields of these structs. In such cases the code
// "get_CurrentSemanticValue().myField = blah;" will fail since
// the getter pushes the value of the field, not the reference.
// So, in the presence of properties, gppg would need to encode
// such field accesses as ...
// "tmp = get_CurrentSemanticValue(); // Fetch value
// tmp.myField = blah; // update
// set_CurrentSemanticValue(tmp); " // Write update back.
// There is no issue if TValue is restricted to be a ref type.
// The same explanation applies to scanner.yylval.
// ==============================================================
/// <summary>
/// The current value of the "$$" symbolic variable in the parser
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
protected TValue CurrentSemanticValue;
/// <summary>
/// The current value of the "@$" symbolic variable in the parser
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
protected TSpan CurrentLocationSpan;
protected int NextToken;
private TSpan LastSpan;
private State FsaState;
private bool recovering;
private int tokensSinceLastError;
private PushdownPrefixState<State> StateStack = new PushdownPrefixState<State>();
private PushdownPrefixState<TValue> valueStack = new PushdownPrefixState<TValue>();
private PushdownPrefixState<TSpan> locationStack = new PushdownPrefixState<TSpan>();
/// <summary>
/// The stack of semantic value (YYSTYPE) values.
/// </summary>
protected PushdownPrefixState<TValue> ValueStack { get { return valueStack; } }
/// <summary>
/// The stack of location value (YYLTYPE) varlues.
/// </summary>
protected PushdownPrefixState<TSpan> LocationStack { get { return locationStack; } }
private int errorToken;
private int endOfFileToken;
private string[] nonTerminals;
private State[] states;
private Rule[] rules;
/// <summary>
/// Initialization method to allow derived classes
/// to insert the rule list into this base class.
/// </summary>
/// <param name="rules">The array of Rule objects</param>
protected void InitRules( Rule[] rules ) { this.rules = rules; }
/// <summary>
/// Initialization method to allow derived classes to
/// insert the states table into this base class.
/// </summary>
/// <param name="states">The pre-initialized states table</param>
protected void InitStates( State[] states ) { this.states = states; }
/// <summary>
/// OBSOLETE FOR VERSION 1.4.0
/// </summary>
/// <param name="size"></param>
protected void InitStateTable( int size ) { states = new State[size]; }
/// <summary>
/// Initialization method to allow derived classes
/// to insert the special value for the error and EOF tokens.
/// </summary>
/// <param name="err">The error state ordinal</param>
/// <param name="end">The EOF stat ordinal</param>
protected void InitSpecialTokens( int err, int end ) {
errorToken = err;
endOfFileToken = end;
}
/// <summary>
/// Initialization method to allow derived classes to
/// insert the non-terminal symbol names into this base class.
/// </summary>
/// <param name="names">Non-terminal symbol names</param>
protected void InitNonTerminals( string[] names ) { nonTerminals = names; }
#region YYAbort, YYAccept etcetera.
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class AcceptException : Exception {
internal AcceptException() { }
protected AcceptException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class AbortException : Exception {
internal AbortException() { }
protected AbortException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class ErrorException : Exception {
internal ErrorException() { }
protected ErrorException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
// The following methods are only called from within
// a semantic action. The thrown exceptions can never
// propagate outside the ShiftReduceParser class in
// which they are nested.
/// <summary>
/// Force parser to terminate, returning "true"
/// </summary>
protected static void YYAccept() { throw new AcceptException(); }
/// <summary>
/// Force parser to terminate, returning "false"
/// </summary>
protected static void YYAbort() { throw new AbortException(); }
/// <summary>
/// Force parser to terminate, returning
/// "false" if error recovery fails.
/// </summary>
protected static void YYError() { throw new ErrorException(); }
/// <summary>
/// Check if parser in error recovery state.
/// </summary>
protected bool YYRecovering { get { return recovering; } }
#endregion
/// <summary>
/// Abstract base method. ShiftReduceParser calls this
/// to initialize the base class data structures. Concrete
/// parser classes must override this method.
/// </summary>
protected abstract void Initialize();
/// <summary>
/// Main entry point of the Shift-Reduce Parser.
/// </summary>
/// <returns>True if parse succeeds, else false for
/// unrecoverable errors</returns>
public bool Parse() {
Initialize(); // allow derived classes to instantiate rules, states and nonTerminals
NextToken = 0;
FsaState = states[0];
StateStack.Push( FsaState );
valueStack.Push( CurrentSemanticValue );
LocationStack.Push( CurrentLocationSpan );
while (true) {
#if TRACE_ACTIONS
Console.Error.WriteLine("Entering state {0} ", FsaState.number);
DisplayStack();
#endif
int action = FsaState.defaultAction;
if (FsaState.ParserTable != null) {
if (NextToken == 0) {
// We save the last token span, so that the location span
// of production right hand sides that begin or end with a
// nullable production will be correct.
LastSpan = scanner.yylloc;
NextToken = scanner.yylex();
#if TRACE_ACTIONS
Console.Error.WriteLine( "Reading: Next token is {0}", TerminalToString( NextToken ) );
#endif
}
#if TRACE_ACTIONS
else
Console.Error.WriteLine( "Next token is still {0}", TerminalToString( NextToken ) );
#endif
if (FsaState.ParserTable.ContainsKey( NextToken ))
action = FsaState.ParserTable[NextToken];
}
if (action > 0) // shift
{
Shift( action );
}
else if (action < 0) // reduce
{
try {
Reduce( -action );
if (action == -1) // accept
return true;
}
catch (AbortException) {
return false;
}
catch (AcceptException) {
return true;
}
catch (ErrorException) {
if (!ErrorRecovery())
return false;
else
throw; // Rethrow x, preserving information.
}
}
else if (action == 0 && !ErrorRecovery()) // error
return false;
}
}
private void Shift( int stateIndex ) {
#if TRACE_ACTIONS
Console.Error.Write("Shifting token {0}, ", TerminalToString(NextToken));
#endif
FsaState = states[stateIndex];
valueStack.Push( scanner.yylval );
StateStack.Push( FsaState );
LocationStack.Push( scanner.yylloc );
if (recovering) {
if (NextToken != errorToken)
tokensSinceLastError++;
if (tokensSinceLastError > 5)
recovering = false;
}
if (NextToken != endOfFileToken)
NextToken = 0;
}
private void Reduce( int ruleNumber ) {
#if TRACE_ACTIONS
DisplayRule(ruleNumber);
#endif
Rule rule = rules[ruleNumber];
int rhLen = rule.RightHandSide.Length;
//
// Default actions for unit productions.
//
if (rhLen == 1) {
CurrentSemanticValue = valueStack.TopElement(); // Default action: $$ = $1;
CurrentLocationSpan = LocationStack.TopElement(); // Default action "@$ = @1;
}
else if (rhLen == 0) {
// Create a new blank value.
// Explicit semantic action may mutate this value
CurrentSemanticValue = default( TValue );
// The location span for an empty production will start with the
// beginning of the next lexeme, and end with the finish of the
// previous lexeme. This gives the correct behaviour when this
// nonsense value is used in later Merge operations.
CurrentLocationSpan = (scanner.yylloc != null && LastSpan != null ?
scanner.yylloc.Merge( LastSpan ) :
default( TSpan ));
}
else {
// Default action: $$ = $1;
CurrentSemanticValue = valueStack[LocationStack.Depth - rhLen];
// Default action "@$ = @1.Merge(@N)" for location info.
TSpan at1 = LocationStack[LocationStack.Depth - rhLen];
TSpan atN = LocationStack[LocationStack.Depth - 1];
CurrentLocationSpan =
((at1 != null && atN != null) ? at1.Merge( atN ) : default( TSpan ));
}
DoAction( ruleNumber );
for (int i = 0; i < rule.RightHandSide.Length; i++) {
StateStack.Pop();
valueStack.Pop();
LocationStack.Pop();
}
FsaState = StateStack.TopElement();
if (FsaState.Goto.ContainsKey( rule.LeftHandSide ))
FsaState = states[FsaState.Goto[rule.LeftHandSide]];
StateStack.Push( FsaState );
valueStack.Push( CurrentSemanticValue );
LocationStack.Push( CurrentLocationSpan );
}
/// <summary>
/// Execute the selected action from array.
/// Must be overriden in derived classes.
/// </summary>
/// <param name="actionNumber">Index of the action to perform</param>
protected abstract void DoAction( int actionNumber );
private bool ErrorRecovery() {
bool discard;
if (!recovering) // if not recovering from previous error
ReportError();
if (!FindErrorRecoveryState())
return false;
//
// The interim fix for the "looping in error recovery"
// artifact involved moving the setting of the recovering
// bool until after invalid tokens have been discarded.
//
ShiftErrorToken();
discard = DiscardInvalidTokens();
recovering = true;
tokensSinceLastError = 0;
return discard;
}
private void ReportError() {
StringBuilder errorMsg = new StringBuilder();
errorMsg.AppendFormat( "Syntax error, unexpected {0}", TerminalToString( NextToken ) );
if (FsaState.ParserTable.Count < 7) {
bool first = true;
foreach (int terminal in FsaState.ParserTable.Keys) {
if (first)
errorMsg.Append( ", expecting " );
else
errorMsg.Append( ", or " );
errorMsg.Append( TerminalToString( terminal ) );
first = false;
}
}
scanner.yyerror( errorMsg.ToString() );
}
private void ShiftErrorToken() {
int old_next = NextToken;
NextToken = errorToken;
Shift( FsaState.ParserTable[NextToken] );
#if TRACE_ACTIONS
Console.Error.WriteLine("Entering state {0} ", FsaState.number);
#endif
NextToken = old_next;
}
private bool FindErrorRecoveryState() {
while (true) // pop states until one found that accepts error token
{
if (FsaState.ParserTable != null &&
FsaState.ParserTable.ContainsKey( errorToken ) &&
FsaState.ParserTable[errorToken] > 0) // shift
return true;
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: popping state {0}", StateStack.TopElement().number);
#endif
StateStack.Pop();
valueStack.Pop();
LocationStack.Pop();
#if TRACE_ACTIONS
DisplayStack();
#endif
if (StateStack.IsEmpty()) {
#if TRACE_ACTIONS
Console.Error.WriteLine("Aborting: didn't find a state that accepts error token");
#endif
return false;
}
else
FsaState = StateStack.TopElement();
}
}
private bool DiscardInvalidTokens() {
int action = FsaState.defaultAction;
if (FsaState.ParserTable != null) {
// Discard tokens until find one that works ...
while (true) {
if (NextToken == 0) {
#if TRACE_ACTIONS
Console.Error.Write("Reading a token: ");
#endif
NextToken = scanner.yylex();
}
#if TRACE_ACTIONS
Console.Error.WriteLine("Next token is {0}", TerminalToString(NextToken));
#endif
if (NextToken == endOfFileToken)
return false;
if (FsaState.ParserTable.ContainsKey( NextToken ))
action = FsaState.ParserTable[NextToken];
if (action != 0)
return true;
else {
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: Discarding {0}", TerminalToString(NextToken));
#endif
NextToken = 0;
}
}
}
else if (recovering && tokensSinceLastError == 0) {
//
// Boolean recovering is not set until after the first
// error token has been shifted. Thus if we get back
// here with recovering set and no tokens read we are
// looping on the same error recovery action. This
// happens if current_state.ParserTable is null because
// the state has an LR(0) reduction, but not all
// lookahead tokens are valid. This only occurs for
// error productions that *end* on "error".
//
// This action discards tokens one at a time until
// the looping stops. Another attack would be to always
// use the LALR(1) table if a production ends on "error"
//
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: panic discard of {0}", TerminalToString(NextToken));
#endif
if (NextToken == endOfFileToken)
return false;
NextToken = 0;
return true;
}
else
return true;
}
/// <summary>
/// Traditional YACC method. Discards the next input token.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyclearin" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyclearin" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
protected void yyclearin() { NextToken = 0; }
/// <summary>
/// Tradional YACC method. Clear the "recovering" flag.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerrok" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerrok" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
protected void yyerrok() { recovering = false; }
/// <summary>
/// OBSOLETE FOR VERSION 1.4.0
/// Method used by derived types to insert new
/// state instances in the "states" array.
/// </summary>
/// <param name="stateNumber">index of the state</param>
/// <param name="state">data for the state</param>
protected void AddState( int stateNumber, State state ) {
states[stateNumber] = state;
state.number = stateNumber;
}
private void DisplayStack() {
Console.Error.Write( "State stack is now:" );
for (int i = 0; i < StateStack.Depth; i++)
Console.Error.Write( " {0}", StateStack[i].number );
Console.Error.WriteLine();
}
private void DisplayRule( int ruleNumber ) {
Console.Error.Write( "Reducing stack by rule {0}, ", ruleNumber );
DisplayProduction( rules[ruleNumber] );
}
private void DisplayProduction( Rule rule ) {
if (rule.RightHandSide.Length == 0)
Console.Error.Write( "/* empty */ " );
else
foreach (int symbol in rule.RightHandSide)
Console.Error.Write( "{0} ", SymbolToString( symbol ) );
Console.Error.WriteLine( "-> {0}", SymbolToString( rule.LeftHandSide ) );
}
/// <summary>
/// Abstract state class naming terminal symbols.
/// This is overridden by derived classes with the
/// name (or alias) to be used in error messages.
/// </summary>
/// <param name="terminal">The terminal ordinal</param>
/// <returns></returns>
protected abstract string TerminalToString( int terminal );
private string SymbolToString( int symbol ) {
if (symbol < 0)
return nonTerminals[-symbol - 1];
else
return TerminalToString( symbol );
}
/// <summary>
/// Return text representation of argument character
/// </summary>
/// <param name="input">The character to convert</param>
/// <returns>String representation of the character</returns>
protected static string CharToString( char input ) {
switch (input) {
case '\a': return @"'\a'";
case '\b': return @"'\b'";
case '\f': return @"'\f'";
case '\n': return @"'\n'";
case '\r': return @"'\r'";
case '\t': return @"'\t'";
case '\v': return @"'\v'";
case '\0': return @"'\0'";
default: return string.Format( CultureInfo.InvariantCulture, "'{0}'", input );
}
}
}
/// <summary>
/// Classes implementing this interface must supply a
/// method that merges two location objects to return
/// a new object of the same type.
/// GPPG-generated parsers have the default location
/// action equivalent to "@$ = @1.Merge(@N);" where N
/// is the right-hand-side length of the production.
/// </summary>
/// <typeparam name="TSpan">The Location type</typeparam>
#if EXPORT_GPPG
public interface IMerge<TSpan>
#else
internal interface IMerge<TSpan>
#endif
{
/// <summary>
/// Interface method that creates a location object from
/// the current and last object. Typically used to create
/// a location object extending from the start of the @1
/// object to the end of the @N object.
/// </summary>
/// <param name="last">The lexically last object to merge</param>
/// <returns>The merged location object</returns>
TSpan Merge( TSpan last );
}
/// <summary>
/// This is the default class that carries location
/// information from the scanner to the parser.
/// If you don't declare "%YYLTYPE Foo" the parser
/// will expect to deal with this type.
/// </summary>
#if EXPORT_GPPG
public class LexLocation : IMerge<LexLocation>
#else
[SuppressMessage( "Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses" )]
internal class LexLocation : IMerge<LexLocation>
#endif
{
private int startLine; // start line
private int startColumn; // start column
private int endLine; // end line
private int endColumn; // end column
/// <summary>
/// The line at which the text span starts.
/// </summary>
public int StartLine { get { return startLine; } }
/// <summary>
/// The column at which the text span starts.
/// </summary>
public int StartColumn { get { return startColumn; } }
/// <summary>
/// The line on which the text span ends.
/// </summary>
public int EndLine { get { return endLine; } }
/// <summary>
/// The column of the first character
/// beyond the end of the text span.
/// </summary>
public int EndColumn { get { return endColumn; } }
/// <summary>
/// Default no-arg constructor.
/// </summary>
public LexLocation() { }
/// <summary>
/// Constructor for text-span with given start and end.
/// </summary>
/// <param name="sl">start line</param>
/// <param name="sc">start column</param>
/// <param name="el">end line </param>
/// <param name="ec">end column</param>
public LexLocation( int sl, int sc, int el, int ec ) { startLine = sl; startColumn = sc; endLine = el; endColumn = ec; }
/// <summary>
/// Create a text location which spans from the
/// start of "this" to the end of the argument "last"
/// </summary>
/// <param name="last">The last location in the result span</param>
/// <returns>The merged span</returns>
public LexLocation Merge( LexLocation last ) { return new LexLocation( this.startLine, this.startColumn, last.endLine, last.endColumn ); }
}
/// <summary>
/// Abstract scanner class that GPPG expects its scanners to
/// extend.
/// </summary>
/// <typeparam name="TValue">Semantic value type YYSTYPE</typeparam>
/// <typeparam name="TSpan">Source location type YYLTYPE</typeparam>
#if EXPORT_GPPG
public abstract class AbstractScanner<TValue, TSpan>
#else
internal abstract class AbstractScanner<TValue, TSpan>
#endif
where TSpan : IMerge<TSpan> {
/// <summary>
/// Lexical value optionally set by the scanner. The value
/// is of the %YYSTYPE type declared in the parser spec.
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylval" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylval" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
// A field must be declared for this value of parametric type,
// since it may be instantiated by a value struct. If it were
// implemented as a property, machine generated code in derived
// types would not be able to select on the returned value.
#pragma warning disable 649
public TValue yylval; // Lexical value: set by scanner
#pragma warning restore 649
/// <summary>
/// Current scanner location property. The value is of the
/// type declared by %YYLTYPE in the parser specification.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylloc" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylloc" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public virtual TSpan yylloc {
get { return default( TSpan ); } // Empty implementation allowing
set { /* skip */ } // yylloc to be ignored entirely.
}
/// <summary>
/// Main call point for LEX-like scanners. Returns an int
/// corresponding to the token recognized by the scanner.
/// </summary>
/// <returns>An int corresponding to the token</returns>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylex" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylex" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public abstract int yylex();
/// <summary>
/// Traditional error reporting provided by LEX-like scanners
/// to their YACC-like clients.
/// </summary>
/// <param name="format">Message format string</param>
/// <param name="args">Optional array of args</param>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerror" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerror" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public virtual void yyerror( string format, params object[] args ) { }
}
/// <summary>
/// Encapsulated state for the parser.
/// Opaque to users, visible to the tool-generated code.
/// </summary>
#if EXPORT_GPPG
public class State
{
/// <summary>
/// The number of states in the automaton.
/// </summary>
public int number;
#else
internal class State {
/// <summary>
/// The index of this state in the states array.
/// </summary>
internal int number;
#endif
internal Dictionary<int, int> ParserTable; // Terminal -> ParseAction
internal Dictionary<int, int> Goto; // NonTerminal -> State;
internal int defaultAction; // = 0; // ParseAction
/// <summary>
/// State transition data for this state. Pairs of elements of the
/// goto array associate symbol ordinals with next state indices.
/// The actions array is passed to another constructor.
/// </summary>
/// <param name="actions">The action list</param>c
/// <param name="goToList">Next state data</param>
public State( int[] actions, int[] goToList )
: this( actions ) {
Goto = new Dictionary<int, int>();
for (int i = 0; i < goToList.Length; i += 2)
Goto.Add( goToList[i], goToList[i + 1] );
}
/// <summary>
/// Action data for this state. Pairs of elements of the
/// action array associate action ordinals with each of
/// those symbols that have actions in the current state.
/// </summary>
/// <param name="actions">The action array</param>
public State( int[] actions ) {
ParserTable = new Dictionary<int, int>();
for (int i = 0; i < actions.Length; i += 2)
ParserTable.Add( actions[i], actions[i + 1] );
}
/// <summary>
/// Set the default action for this state.
/// </summary>
/// <param name="defaultAction">Ordinal of the default action</param>
public State( int defaultAction ) {
this.defaultAction = defaultAction;
}
/// <summary>
/// Set the default action and the state transition table.
/// </summary>
/// <param name="defaultAction">The default action</param>
/// <param name="goToList">Transitions from this state</param>
public State( int defaultAction, int[] goToList )
: this( defaultAction ) {
Goto = new Dictionary<int, int>();
for (int i = 0; i < goToList.Length; i += 2)
Goto.Add( goToList[i], goToList[i + 1] );
}
}
/// <summary>
/// Rule representation at runtime.
/// </summary>
#if EXPORT_GPPG
public class Rule
#else
internal class Rule
#endif
{
internal int LeftHandSide; // symbol
internal int[] RightHandSide; // symbols
/// <summary>
/// Rule constructor. This holds the ordinal of
/// the left hand side symbol, and the list of
/// right hand side symbols, in lexical order.
/// </summary>
/// <param name="left">The LHS non-terminal</param>
/// <param name="right">The RHS symbols, in lexical order</param>
public Rule( int left, int[] right ) {
this.LeftHandSide = left;
this.RightHandSide = right;
}
}
/// <summary>
/// Stack utility for the shift-reduce parser.
/// GPPG parsers have three instances:
/// (1) The parser state stack, T = QUT.Gppg.State,
/// (2) The semantic value stack, T = TValue,
/// (3) The location stack, T = TSpan.
/// </summary>
/// <typeparam name="T"></typeparam>
#if EXPORT_GPPG
public class PushdownPrefixState<T>
#else
internal class PushdownPrefixState<T>
#endif
{
// Note that we cannot use the BCL Stack<T> class
// here as derived types need to index into stacks.
//
private T[] array = new T[8];
private int tos = 0;
/// <summary>
/// Indexer for values of the stack below the top.
/// </summary>
/// <param name="index">index of the element, starting from the bottom</param>
/// <returns>the selected element</returns>
public T this[int index] { get { return array[index]; } }
/// <summary>
/// The current depth of the stack.
/// </summary>
public int Depth { get { return tos; } }
internal void Push( T value ) {
if (tos >= array.Length) {
T[] newarray = new T[array.Length * 2];
System.Array.Copy( array, newarray, tos );
array = newarray;
}
array[tos++] = value;
}
internal T Pop() {
T rslt = array[--tos];
array[tos] = default( T );
return rslt;
}
internal T TopElement() { return array[tos - 1]; }
internal bool IsEmpty() { return tos == 0; }
}
}
@@ -0,0 +1,45 @@
namespace Bts.ArithmeticExpressions;
using System.Numerics;
class Sum : IArithmeticExpression<double> {
readonly IArithmeticExpression left;
readonly IArithmeticExpression right;
public Sum(IArithmeticExpression left, IArithmeticExpression right) {
this.left = left;
this.right = right;
}
public static Sum<T, T, T> Create<T>(
IArithmeticExpression<T> left, IArithmeticExpression<T> right)
where T : INumber<T>, IAdditionOperators<T, T, T> =>
new(left, right);
public double Evaluate(IDictionary<string, object?> env) =>
evaluateArg(left, nameof(left), env) + evaluateArg(right, nameof(right), env);
static double evaluateArg(
IArithmeticExpression expr, string name, IDictionary<string, object?> env) {
var obj = expr.Evaluate(env);
return ConvertUtils.try_convert(expr.Evaluate(env), out double dbl) ? dbl :
throw new Exception(
$"Invalid Sum. {name} is of type {obj?.GetType()} (expected {typeof(double)})");
}
}
class Sum<TLeft, TRight, TResult> : IArithmeticExpression<TResult>
where TLeft : INumber<TLeft>, IAdditionOperators<TLeft, TRight, TResult>
where TRight : INumber<TRight>
where TResult : INumber<TResult> {
readonly IArithmeticExpression<TLeft> left;
readonly IArithmeticExpression<TRight> right;
public Sum(IArithmeticExpression<TLeft> left, IArithmeticExpression<TRight> right) {
this.left = left;
this.right = right;
}
public TResult Evaluate(IDictionary<string, object?> env) =>
left.Evaluate(env) + right.Evaluate(env);
}
@@ -0,0 +1,4 @@
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using System.Collections.Immutable;
@@ -0,0 +1,15 @@
namespace Bts.ArithmeticExpressions;
using System.Collections.Generic;
class Variable : IArithmeticExpression {
readonly string identifier;
public Variable(string identifier) =>
this.identifier = identifier;
public object? Evaluate(IDictionary<string, object?> env) =>
env.TryGetValue(identifier, out var val) ? val :
throw new KeyNotFoundException(
$"Failed to find {nameof(Variable)} '{identifier}'");
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,41 @@
using mbts.spark;
using System;
using System.Runtime;
namespace BCG.Utilities.General
{
/// <summary>
/// Summary description for Crono.
/// </summary>
public class Crono
{
public Crono()
{
myTimer = new HighPerformanceTimer();
}
public void start()
{myTimer.start();}
public void stop()
{myTimer.stop();}
public TimeSpan time
{
get
{
return new TimeSpan((long) (myTimer.totalMilliseconds * TimeSpan.TicksPerMillisecond));
}
}
public double miliseconds
{
get
{return myTimer.totalMilliseconds;}
}
private HighPerformanceTimer myTimer;
}
}
@@ -0,0 +1,60 @@
using System;
using Microsoft.Win32;
namespace BCG
{
internal static class ArrayRegistryHandler
{
public static void setValue (RegistryKey rk, string name, Array array)
{
rk.SetValue (name, array.Length);
Type t = array.GetType ().GetElementType ();
for (int i = 0; i < array.Length; i++)
{
if (t == typeof (int))
rk.SetValue (string.Concat (name, i.ToString ()), (int) array.GetValue (i));
else if (t == typeof (bool))
rk.SetValue (string.Concat (name, i.ToString ()), (bool) array.GetValue (i));
else
rk.SetValue (string.Concat (name, i.ToString ()), array.GetValue (i));
}
}
public static Array getValue (RegistryKey rk, string name, Type elementType)
{
object c = rk.GetValue (name, 0);
int count = (int) c;
Array result;
if (elementType == typeof (int) || elementType == typeof (System.Int32))
result = new int[count];
else if (elementType == typeof (bool))
result = new bool[count];
else if (elementType == typeof (double))
result = new double[count];
else if (elementType == typeof (DateTime))
result = new DateTime[count];
else
result = new string[count];
for (int i = 0; i < count; i++)
{
object val = rk.GetValue (string.Concat (name, i.ToString ()));
if (elementType == typeof (int) || elementType == typeof (System.Int32))
result.SetValue (int.Parse (val.ToString ()), i);
else if (elementType == typeof (bool))
result.SetValue (bool.Parse (val.ToString ()), i);
else if (elementType == typeof (double))
result.SetValue (double.Parse (val.ToString ()), i);
else if (elementType == typeof (DateTime))
result.SetValue (DateTime.Parse (val.ToString ()), i);
else
result.SetValue (val.ToString (), i);
}
return result;
}
}
}
@@ -0,0 +1,192 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using Microsoft.Win32;
namespace BCG
{
public enum RegistryRoot { HKCU, HKLM }
[Serializable]
public class BlueRegistry
{
public const string BlueSubKey = @"Software\Blue Capital Group\";
public static readonly string ApplicationPrefix;
protected string myFullPath;
public BlueRegistry(string subPath)
{
myFullPath = string.Concat(BlueRegistry.ApplicationPrefix, @"\", subPath);
}
public BlueRegistry(BlueRegistry parent, string subPath)
{
myFullPath = string.Concat(parent.myFullPath, @"\", subPath);
}
static BlueRegistry()
{
string app = "Applications";
Assembly ass = Assembly.GetEntryAssembly();
if (ass != null)
{
app = ass.GetName().Name;
object[] attrs = ass.GetCustomAttributes(
Type.GetType("System.Reflection.AssemblyTitleAttribute"), true);
if (attrs.Length > 0 && ((AssemblyTitleAttribute)attrs[0]).Title != string.Empty)
{
app = ((AssemblyTitleAttribute)attrs[0]).Title;
}
}
ApplicationPrefix = string.Concat(BlueRegistry.BlueSubKey, app);
}
public object getValue(string name)
{
return getValue(RegistryRoot.HKCU, name);
}
public object getValue(string name, object defaultValue)
{
return getValue(RegistryRoot.HKCU, name, defaultValue);
}
public void setValue(string name, object value)
{
setValue(RegistryRoot.HKCU, name, value);
}
public void deleteValue(string name)
{
deleteValue(RegistryRoot.HKCU, name);
}
private RegistryKey myGetRegistryKey(RegistryRoot root)
{
if (root == RegistryRoot.HKCU)
return Registry.CurrentUser.CreateSubKey(myFullPath);
else
return Registry.LocalMachine.CreateSubKey(myFullPath);
}
#region Generic Methods
internal virtual void setValue(RegistryRoot root, string name, object value)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
rk.SetValue(name, value);
}
}
internal virtual object getValue(RegistryRoot root, string name)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return rk.GetValue(name);
}
}
internal virtual void deleteValue(RegistryRoot root, string name)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
rk.DeleteValue(name);
}
}
internal virtual object getValue(RegistryRoot root, string name, object defaultValue)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return rk.GetValue(name, defaultValue);
}
}
#endregion
#region Double
internal virtual void setDouble(RegistryRoot root, string name, double value)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
DoubleRegistryHandler.setValue(rk, name, value);
}
}
internal virtual double getDouble(RegistryRoot root, string name)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return DoubleRegistryHandler.getValue(rk, name);
}
}
#endregion
#region Enum
internal virtual void setEnum(RegistryRoot root, string name, Enum value)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
EnumRegistryHandler.setValue(rk, name, value);
}
}
internal virtual Enum getEnum(RegistryRoot root, string name, Type enumType)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return EnumRegistryHandler.getValue(rk, name, enumType);
}
}
#endregion
#region Array
internal virtual void setArray(RegistryRoot root, string name, Array array)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
ArrayRegistryHandler.setValue(rk, name, array);
}
}
internal virtual Array getArray(RegistryRoot root, string name, Type elementType)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return ArrayRegistryHandler.getValue(rk, name, elementType);
}
}
#endregion
#region SortedList
internal virtual void setSortedList(RegistryRoot root, string name, SortedList slist)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
SortedListRegistryHandler.setValue(rk, name, slist);
}
}
internal virtual SortedList getSortedList(RegistryRoot root, string name)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
return SortedListRegistryHandler.getValue(rk, name);
}
}
internal virtual void deleteSortedList(RegistryRoot root, string name)
{
using (RegistryKey rk = myGetRegistryKey(root))
{
SortedListRegistryHandler.deleteValue(rk, name);
}
}
#endregion
}
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>
@@ -0,0 +1,22 @@
using System;
using Microsoft.Win32;
namespace BCG
{
internal static class DoubleRegistryHandler
{
public static void setValue (RegistryKey rk, string name, double value)
{
rk.SetValue (name, value.ToString ());
}
public static double getValue (RegistryKey rk, string name)
{
object v = rk.GetValue (name);
if (v != null)
return double.Parse (v.ToString ());
else
return double.NaN;
}
}
}
@@ -0,0 +1,22 @@
using System;
using Microsoft.Win32;
namespace BCG
{
internal static class EnumRegistryHandler
{
public static void setValue (RegistryKey rk, string name, Enum value)
{
rk.SetValue (name, value.ToString ());
}
public static Enum getValue (RegistryKey rk, string name, Type enumType)
{
object v = rk.GetValue (name);
if (v != null)
return (Enum) Enum.Parse (enumType, v.ToString ());
else
return null;
}
}
}
@@ -0,0 +1,166 @@
using System;
using System.Reflection;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using Microsoft.Win32;
namespace BCG
{
public class PersistWindowState
{
// event info that allows form to persist extra window state data
public delegate void WindowStateHandler (object sender, BlueRegistry bluereg);
public event WindowStateHandler LoadState;
public event WindowStateHandler SaveState;
private Form myParent;
private BlueRegistry myRegistry;
private int myNormalLeft;
private int myNormalTop;
private int myNormalWidth;
private int myNormalHeight;
private bool myResizable = true;
private FormWindowState myWindowState;
private bool myAllowSaveMinimized = false;
private bool mySavePosition = true;
private bool mySaveState = true;
public PersistWindowState (bool savePosition)
{
mySavePosition = savePosition;
}
public PersistWindowState ()
{
}
public bool saveState
{
[DebuggerStepThrough]
set { mySaveState = value; }
}
public Form parent
{
[DebuggerStepThrough]
get { return myParent; }
set
{
myParent = value;
myRegistry = new BlueRegistry (myParent.Text);
// subscribe to parent form's events
myParent.Closing += new System.ComponentModel.CancelEventHandler(myOnClosing);
myParent.Resize += new System.EventHandler(myOnResize);
myParent.Move += new System.EventHandler(myOnMove);
myParent.Load += new System.EventHandler(myOnLoad);
// get initial width and height in case form is never resized
myNormalWidth = myParent.Width;
myNormalHeight = myParent.Height;
}
}
public BlueRegistry blueRegistry
{
[DebuggerStepThrough]
get { return myRegistry; }
[DebuggerStepThrough]
set { myRegistry = value; }
}
public bool resizable
{
[DebuggerStepThrough]
set { myResizable = value; }
}
public bool allowSaveMinimized
{
[DebuggerStepThrough]
set { myAllowSaveMinimized = value; }
}
private void myOnResize(object sender, System.EventArgs e)
{
// save width and height
if (myParent.WindowState == FormWindowState.Normal && myResizable)
{
myNormalWidth = myParent.Width;
myNormalHeight = myParent.Height;
}
}
private void myOnMove(object sender, System.EventArgs e)
{
// save position
if(myParent.WindowState == FormWindowState.Normal)
{
myNormalLeft = myParent.Left;
myNormalTop = myParent.Top;
}
// save state
myWindowState = myParent.WindowState;
}
private void myOnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// save position, size and state
if (mySavePosition)
{
myRegistry.setValue (RegistryRoot.HKCU, "Left", myNormalLeft);
myRegistry.setValue (RegistryRoot.HKCU, "Top", myNormalTop);
}
if (myResizable)
{
myRegistry.setValue (RegistryRoot.HKCU, "Width", myNormalWidth);
myRegistry.setValue (RegistryRoot.HKCU, "Height", myNormalHeight);
}
// check if we are allowed to save the state as minimized (not normally)
if (!myAllowSaveMinimized)
{
if(myWindowState == FormWindowState.Minimized)
myWindowState = FormWindowState.Normal;
}
if (mySaveState)
myRegistry.setValue (RegistryRoot.HKCU, "WindowState", (int) myWindowState);
// fire SaveState event
if (SaveState != null)
SaveState (this, myRegistry);
}
private void myOnLoad (object sender, System.EventArgs e)
{
if (mySavePosition)
{
int left = (int) myRegistry.getValue (RegistryRoot.HKCU, "Left", myParent.Left);
int top = (int) myRegistry.getValue (RegistryRoot.HKCU, "Top", myParent.Top);
myParent.Location = new Point (left, top);
}
if (myResizable)
{
int width = (int) myRegistry.getValue (RegistryRoot.HKCU, "Width", myParent.Width);
int height = (int) myRegistry.getValue (RegistryRoot.HKCU, "Height", myParent.Height);
myParent.Size = new Size (width, height);
}
if (mySaveState)
{
FormWindowState windowState = (FormWindowState) myRegistry.getValue (RegistryRoot.HKCU, "WindowState",
(int) myParent.WindowState);
myParent.WindowState = windowState;
}
// fire LoadState event
if(LoadState != null)
LoadState (this, myRegistry);
}
}
}
@@ -0,0 +1,58 @@
using System;
using System.Collections;
using Microsoft.Win32;
namespace BCG
{
internal static class SortedListRegistryHandler
{
public static void setValue (RegistryKey rk, string name, SortedList slist)
{
rk.SetValue (name, slist.Count);
for (int i = 0; i < slist.Count; i++)
{
object key = slist.GetKey (i);
object val = slist.GetByIndex (i);
rk.SetValue (string.Concat (name, "_key", i.ToString ()), key);
rk.SetValue (string.Concat (name, "_val", i.ToString ()), val);
}
}
public static SortedList getValue (RegistryKey rk, string name)
{
object c = rk.GetValue (name, 0);
SortedList result = new SortedList ();
try
{
int count = (int) c;
for (int i = 0; i < count; i++)
{
object key = rk.GetValue (string.Concat (name, "_key", i.ToString ()));
object val = rk.GetValue (string.Concat (name, "_val", i.ToString ()));
result.Add (key, val);
}
}
catch (Exception)
{
result.Clear ();
}
return result;
}
public static void deleteValue (RegistryKey rk, string name)
{
object c = rk.GetValue (name, 0);
int count = (int) c;
for (int i = 0; i < count; i++)
{
rk.DeleteValue (string.Concat (name, "_key", i.ToString ()), false);
rk.DeleteValue (string.Concat (name, "_val", i.ToString ()), false);
}
rk.DeleteValue (name, false);
}
}
}
@@ -0,0 +1,21 @@
namespace Bts.BooleanExpressions.Arguments;
class Argument : IArgument {
readonly Func<IDictionary<string, object?>, object?> getValue;
public Argument(Func<IDictionary<string, object?>, object?> getValue) =>
this.getValue = getValue;
public object? GetValue(IDictionary<string, object?> env) =>
getValue(env);
}
class Argument<T> : IArgument<T> {
readonly Func<IDictionary<string, object?>, T> getValue;
public Argument(Func<IDictionary<string, object?>, T> getValue) =>
this.getValue = getValue;
public T GetValue(IDictionary<string, object?> env) =>
getValue(env);
}
@@ -0,0 +1,28 @@
namespace Bts.BooleanExpressions.Arguments;
using bts;
using Spark.Core.Collections;
static class ArgumentComparer {
public static int Compare(
IArgument value1, IArgument value2, IDictionary<string,object?> env) {
if (value1 is null) {
throw new ArgumentNullException(nameof(value1));
}
if (value2 is null) {
throw new ArgumentNullException(nameof(value2));
}
var cmp1 = value1.Cast<IComparable>();
var cmp2 = value2.Cast<IComparable>();
try {
var v1 = cmp1.GetValue(env);
var v2 = cmp2.GetValue(env);
return v1.GetType().IsNumeric() ?
new NumericComparer().Compare(v1, v2) : v1.CompareTo(v2);
} catch (Exception ex) {
Log.Ui.error($"Comparison failed: {ex}");
throw;
}
}
}
@@ -0,0 +1,11 @@
namespace Bts.BooleanExpressions.Arguments;
static class ArgumentExtensions {
public static IArgument<T> Cast<T>(this IArgument argument) =>
new Argument<T>(env => {
var obj = argument.GetValue(env);
return obj is T t ? t : throw new Exception(
$"Expected argument.GetValue to return instance of type {typeof(T)} " +
$"(actually {obj?.GetType()})");
});
}
@@ -0,0 +1,19 @@
namespace Bts.BooleanExpressions.Arguments;
using System.Text.RegularExpressions;
static class ArgumentMatcher {
public static bool Match(
IArgument arg1, IArgument arg2, IDictionary<string, object?> env) {
var lobj = arg1.GetValue(env);
var robj = arg2.GetValue(env);
if (DBNull.Value.Equals(lobj) && robj is string) {
return false;
}
if (lobj is string lstr && robj is string rstr) {
return Regex.IsMatch(lstr, rstr);
}
throw new Exception(
$"Type mismatch, cannot string match {lobj?.GetType()} with {robj?.GetType()}");
}
}
@@ -0,0 +1,16 @@
namespace Bts.BooleanExpressions.Arguments;
static class Constant {
public static Constant<T> Create<T>(T value) =>
new(value);
}
class Constant<T> : IArgument<T> {
readonly T value;
public Constant(T value) =>
this.value = value;
public T GetValue(IDictionary<string, object?> env) =>
value;
}
@@ -0,0 +1,29 @@
namespace Bts.BooleanExpressions.Arguments;
class Function : IArgument {
public const string FunctionPrefix = "function:";
readonly string name;
readonly IEnumerable<IArgument> args;
public Function(string name, IEnumerable<IArgument>? args = null) {
this.name = name;
this.args = args ?? Enumerable.Empty<IArgument>();
}
public delegate object? Delegate(List<object?> args);
public object? GetValue(IDictionary<string, object?> env) {
var argValues = args?.Select(a => a.GetValue(env))?.ToList() ?? new(0);
var k = FunctionPrefix + name;
if (!env.TryGetValue(k, out var fnObj)) {
throw new BooleanExpressionException($"Did not find function named '{name}'");
}
if (fnObj is not Delegate fn) {
throw new BooleanExpressionException(
$"Function {name} is not of type {typeof(Delegate)}");
}
return fn(argValues);
}
}
@@ -0,0 +1,12 @@
namespace Bts.BooleanExpressions;
interface IArgument {
object? GetValue(IDictionary<string, object?> env);
}
interface IArgument<T> : IArgument {
new T GetValue(IDictionary<string, object?> env);
object? IArgument.GetValue(IDictionary<string, object?> env) =>
GetValue(env);
}
@@ -0,0 +1,12 @@
namespace Bts.BooleanExpressions.Arguments;
class Variable : IArgument {
readonly string name;
public Variable(string name) =>
this.name = name;
public object? GetValue(IDictionary<string, object?> env) =>
env.TryGetValue(name, out var res) ?
res : throw new BooleanExpressionException($"Did not find variable named '{name}'");
}
@@ -0,0 +1,13 @@
namespace Bts.BooleanExpressions;
class BinaryOperator : IBinaryOperator {
readonly Func<IArgument?, IArgument?, IDictionary<string, object?>, bool> op;
public BinaryOperator(
Func<IArgument?, IArgument?, IDictionary<string, object?>, bool> op) =>
this.op = op;
public bool Evaluate(
IArgument? arg1, IArgument? arg2, IDictionary<string, object?> bindings) =>
op(arg1, arg2, bindings);
}
@@ -0,0 +1,19 @@
namespace Bts.BooleanExpressions;
using bts;
class BooleanExpression : IBooleanExpression {
readonly Func<IDictionary<string, object?>, bool> evaluate;
public BooleanExpression(Func<IDictionary<string, object?>, bool> evaluate) =>
this.evaluate = evaluate;
public bool Evaluate(IDictionary<string, object?> env) {
try {
return evaluate(env);
} catch (Exception ex) {
Log.assert(false, $"BooleanExpression.Evaluate failed: {ex}");
throw;
}
}
}
@@ -0,0 +1,6 @@
namespace Bts.BooleanExpressions;
public class BooleanExpressionException : Exception {
public BooleanExpressionException(string format, params object[] args)
: base(string.Format(format, args)) { }
}
@@ -0,0 +1,176 @@
%namespace Bts.BooleanExpressions
%partial
%visibility internal
%tokentype Token
%parsertype BooleanExpressionParserImpl
%union {
public int Integer;
public double Real;
public bool Boolean;
public string String;
public string Identifier;
public DateTime DateTime;
public ImmutableList<Definition> Definitions;
public Definition Definition;
public ImmutableList<string> Parameters;
public ImmutableList<IArgument> Arguments;
public IArgument Argument;
public BooleanExpression Expression;
public BoundBooleanExpression BoundExpression;
}
/* Constants */
%token BOOLEAN_CONSTANT, INTEGER_CONSTANT, STRING_CONSTANT, NULL_CONSTANT, DATE_TIME_CONSTANT, REAL_CONSTANT
/* Logical Operators */
%token OR, AND, XOR, NOT
/* Syntax */
%token PARENTHESIS_OPEN, PARENTHESIS_CLOSE, COMMA, IDENTIFIER, DEFINE, BEGIN_DEF, END_DEF
/* Relational */
%token EQUAL, LESS_EQUAL, GREATER_EQUAL, LESS, GREATER, DIFFERENT, DIFFERENT2, MATCHES
%left OR AND XOR
%right NOT
%nonassoc EQUAL
%type <BoundExpression> BoundExpression
%type <Definitions> Definitions
%type <Definition> Definition
%type <Expression> Expression
%type <Parameters> Parameters
%type <Arguments> Arguments
%type <Argument> Argument
%%
BoundExpression : Definitions Expression { $$ = new($2, $1); }
| Expression { $$ = new($1, ImmutableList<Definition>.Empty); };
Definitions :
Definition { $$ = ImmutableList<Definition>.Empty.Add($1); }
| Definitions Definition { $$ = $1.Add($2); };
Definition :
DEFINE IDENTIFIER PARENTHESIS_OPEN Parameters PARENTHESIS_CLOSE BEGIN_DEF Expression END_DEF {
$$ = new($2.Identifier, $4, $7);
};
Expression :
Expression OR Expression {
var left = $1;
var right = $3;
$$ = new(env => left.Evaluate(env) || right.Evaluate(env));
}
| Expression AND Expression {
var left = $1;
var right = $3;
$$ = new(env => left.Evaluate(env) && right.Evaluate(env));
}
| Expression XOR Expression {
var left = $1;
var right = $3;
$$ = new(env => left.Evaluate(env) != right.Evaluate(env));
}
| NOT Expression {
var exp = $2;
$$ = new(env => !exp.Evaluate(env));
}
| PARENTHESIS_OPEN Expression PARENTHESIS_CLOSE {
$$ = $2;
}
| Argument GREATER Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) > 0);
}
| Argument GREATER_EQUAL Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) >= 0);
}
| Argument EQUAL Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) == 0);
}
| Argument LESS_EQUAL Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) <= 0);
}
| Argument LESS Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) < 0);
}
| Argument DIFFERENT Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) != 0);
}
| Argument DIFFERENT2 Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentComparer.Compare(left, right, env) != 0);
}
| Argument MATCHES Argument {
var left = $1;
var right = $3;
$$ = new(env => Arguments.ArgumentMatcher.Match(left, right, env));
}
| Argument {
var arg = $1;
$$ = new(env => (bool)arg.GetValue(env));
}
;
Parameters :
{
$$ = ImmutableList<string>.Empty;
}
| IDENTIFIER {
$$ = ImmutableList<string>.Empty.Add($1.Identifier);
}
| IDENTIFIER COMMA Parameters {
$$ = $3.Insert(0, $1.Identifier);
}
;
Arguments :
{
$$ = ImmutableList<IArgument>.Empty;
}
| Argument {
$$ = ImmutableList<IArgument>.Empty.Add($1);
}
| Arguments COMMA Argument {
$$ = $1.Add($3);
}
;
Argument : IDENTIFIER
{
$$ = new Arguments.Variable($1.Identifier);
}
| BOOLEAN_CONSTANT {
$$ = Arguments.Constant.Create($1.Boolean);
}
| NULL_CONSTANT {
$$ = Arguments.Constant.Create(DBNull.Value);
}
| REAL_CONSTANT {
$$ = Arguments.Constant.Create($1.Real);
}
| STRING_CONSTANT {
$$ = Arguments.Constant.Create($1.String);
}
| INTEGER_CONSTANT {
$$ = Arguments.Constant.Create($1.Integer);
}
| DATE_TIME_CONSTANT {
$$ = Arguments.Constant.Create($1.DateTime);
}
| IDENTIFIER PARENTHESIS_OPEN Arguments PARENTHESIS_CLOSE {
$$ = new Arguments.Function($1.Identifier, $3);
}
;
@@ -0,0 +1,6 @@
namespace Bts.BooleanExpressions;
public class BooleanExpressionInterpreterException : System.Exception {
public BooleanExpressionInterpreterException(string message)
: base(message) { }
}
@@ -0,0 +1,16 @@
namespace Bts.BooleanExpressions;
using Bts.ExpressionCore;
public static class BooleanExpressionParser {
public static IBooleanExpression Parse(Stream expression) =>
new Parser().Parse(expression);
public static IBooleanExpression Parse(string expression) =>
new Parser().Parse(expression);
class Parser : IParser<IBooleanExpression> {
public IBooleanExpression Parse(Stream expression) =>
new BooleanExpressionParserImpl().Parse(expression).BoundExpression;
}
}
@@ -0,0 +1,22 @@
namespace Bts.BooleanExpressions;
using QUT.Gppg;
using System.IO;
using System.Text;
partial class BooleanExpressionParserImpl {
public BooleanExpressionParserImpl(AbstractScanner<ValueType, LexLocation>? scanner = null)
: base(scanner) { }
public ValueType Parse(string s) {
using var ms = new MemoryStream(Encoding.Default.GetBytes(s));
return Parse(ms);
}
public ValueType Parse(Stream stream) {
Scanner = new BooleanExpressionScanner(stream);
Parse();
return CurrentSemanticValue;
}
}
@@ -0,0 +1,13 @@
namespace Bts.BooleanExpressions;
using System;
partial class BooleanExpressionScanner {
bts.Logger Logger => bts.Log.Gen;
public override void yyerror(string format, params object[] args) {
base.yyerror(format, args);
Console.WriteLine(format, args);
Console.WriteLine();
}
}
@@ -0,0 +1,174 @@
%namespace Bts.BooleanExpressions
%scannertype BooleanExpressionScanner
%visibility internal
%tokentype Token
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers
/* Literal Constants */
Letter [a-zA-Z]
BooleanConstant 'true'|'false'
CSStringConstant \"([^\"\\]*|\\.)*\"
DateTimeConstant ([1-9]|'10'|'11'|'12')\/([1-9]|([1-2][0-9])|'30'|'31')\/[0-9][0-9][0-9]*
IntegerConstant -?[0-9]+
NullConstant 'null'
RealConstant -?[0-9]+\.[0-9]+
StringConstant \'([^\'\\]*|\\.)*\'
/* Relational Operators */
Equal =
LessEqual <=
GreaterEqual >=
Less <
Greater >
Different !=
Different2 <>
Matches ~=
/* Logical Operators */
Or or
And and
Not not
Xor xor
Define define
BeginDef \{
EndDef \}
Identifier {Letter}({Letter}*[0-9]*_*)*
/* Utility Tokens */
OpenParenthesis \(
CloseParenthesis \)
Comma ,
Negate '!'
MapsTo ':'
IsOfType '/'
%%
{StringConstant} {
yylval.String = yytext.Substring(1, yytext.Length - 2).Replace(@"\'", "'");
return (int)Token.STRING_CONSTANT;
}
{BooleanConstant} {
yylval.Boolean = yytext.ToLower() switch {
"true" => true,
"false" => false,
_ => throw new Exception(
string.Format("Expected yytext to be 'true' or 'false' (actually '{0}')", yytext))
};
return (int)Token.BOOLEAN_CONSTANT;
}
{NullConstant} {
yylval.Null = DBNull.Value;
return (int)Token.NULL_CONSTANT;
}
{DateTimeConstant} {
yylval.DateTime = DateTime.Parse(yytext);
return (int)Token.DATE_TIME_CONSTANT;
}
{IntegerConstant} {
yylval.Integer = int.Parse(yytext);
return (int)Token.INTEGER_CONSTANT;
}
{RealConstant} {
yylval.Real = double.Parse(yytext);
return (int)Token.REAL_CONSTANT;
}
{OpenParenthesis} {
return (int)Token.PARENTHESIS_OPEN;
}
{CloseParenthesis} {
return (int)Token.PARENTHESIS_CLOSE;
}
{Comma} {
return (int)Token.COMMA;
}
{Negate} {
return (int)Token.NEGATE;
}
{MapsTo} {
return (int)Token.MAPS_TO;
}
{Equal} {
return (int)Token.EQUAL;
}
{LessEqual} {
return (int)Token.LESS_EQUAL;
}
{GreaterEqual} {
return (int)Token.GREATER_EQUAL;
}
{Less} {
return (int)Token.LESS;
}
{Greater} {
return (int)Token.GREATER;
}
{Different} {
return (int)Token.DIFFERENT;
}
{Different2} {
return (int)Token.DIFFERENT2;
}
{Matches} {
return (int)Token.MATCHES;
}
{Or} {
return (int)Token.OR;
}
{And} {
return (int)Token.AND;
}
{Not} {
return (int)Token.NOT;
}
{Xor} {
return (int)Token.XOR;
}
{IsOfType} {
return (int)Token.IS_OF_TYPE;
}
{Define} {
return (int)Token.DEFINE;
}
{BeginDef} {
return (int)Token.BEGIN_DEF;
}
{EndDef} {
return (int)Token.END_DEF;
}
{Identifier} {
yylval.Identifier = yytext;
return (int)Token.IDENTIFIER;
}
%%
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExpressionCore\ExpressionCore.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
<ItemGroup>
<Scanner Include="BooleanExpressionTokens.lex" />
<Parser Include="BooleanExpressionGrammar.y" />
</ItemGroup>
</Project>
@@ -0,0 +1,32 @@
namespace Bts.BooleanExpressions;
using Bts.BooleanExpressions.Arguments;
class BoundBooleanExpression : IBooleanExpression {
readonly IBooleanExpression expression;
readonly IDictionary<string, FunctionDefinition> definitions;
readonly Dictionary<string, object?> identifiers;
public BoundBooleanExpression(
IBooleanExpression expression, IEnumerable<Definition>? definitions = null) {
this.expression = expression;
var defs = definitions ?? Enumerable.Empty<Definition>();
identifiers = defs.ToDictionary(
kv => kv.id, kv => (object?)kv.exp);
this.definitions = extractFunctions(defs).ToDictionary(kv => kv.Key, kv => kv.Value);
}
public bool Evaluate(IDictionary<string, object?> env) {
foreach (var kv in env) {
identifiers[kv.Key] = kv.Value;
}
env = new EvaluationEnvironment(identifiers, definitions);
return expression.Evaluate(env);
}
static IEnumerable<KeyValuePair<string, FunctionDefinition>> extractFunctions(
IEnumerable<Definition> definitions) =>
definitions.Select(d =>
new KeyValuePair<string, FunctionDefinition>(
Function.FunctionPrefix + d.id, new(d.id, d.args.ToList(), d.exp)));
}
@@ -0,0 +1,18 @@
namespace Bts.BooleanExpressions;
using System.Collections.Immutable;
public class Definition {
public Definition(string id, ImmutableList<string> args, IBooleanExpression exp) {
this.id = id;
this.args = args;
this.exp = exp;
}
public string id { get; }
public ImmutableList<string> args { get; }
public IBooleanExpression exp { get; }
public override string ToString() =>
$"{id}({string.Join(", ", args)} | {exp?.GetType()})";
}
@@ -0,0 +1,123 @@
namespace Bts.BooleanExpressions;
using Bts.BooleanExpressions.Arguments;
using Spark.Core.Collections;
using System.Collections;
class EvaluationEnvironment : IDictionary<string, object?> {
IDictionary<string, object?> userBindings;
IDictionary<string, FunctionDefinition> definitions;
public EvaluationEnvironment(
IDictionary<string, object?> userBindings,
IDictionary<string, FunctionDefinition> definitions) {
this.userBindings = userBindings;
this.definitions = definitions;
}
public int Count =>
definitions.Count + userBindings.Count;
public bool IsReadOnly => true;
public ICollection<string> Keys =>
AggregateCollection.Create(userBindings.Keys, definitions.Keys);
public ICollection<object?> Values =>
AggregateCollection.Create(
userBindings.Values, SelectorCollection.Create(definitions.Values, v => (object?)v));
public object? this[string key] {
get {
var keystr = key.ToString();
if (keystr is null || !definitions.TryGetValue(keystr, out var fd)) {
return userBindings.TryGetValue(key, out var ubnd) ? ubnd :
throw new KeyNotFoundException(
$"EvaluationEnvironment does not contain key '{key}'");
}
return new Function.Delegate(Evaluate);
object? Evaluate(List<object?> args) {
if (args.Count != fd.args.Count)
throw new BooleanExpressionInterpreterException(
$"Parameter count mismatch for {fd.name}. Expected: {fd.args} Got: {args.Count}");
else {
var newBindings = fd.args.Zip(args).ToDictionary();
return fd.expression.Evaluate(new EvaluationEnvironment(
AggregateDictionary.Create(newBindings, userBindings), definitions));
}
}
}
set => throw new NotSupportedException();
}
public bool ContainsKey(string key) {
var keystr = key.ToString();
return
(keystr is not null && definitions.ContainsKey(keystr)) ||
userBindings.ContainsKey(key);
}
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() {
foreach (var kv in userBindings) {
yield return kv;
}
foreach (var kv in definitions) {
if (!userBindings.ContainsKey(kv.Key)) {
yield return new(kv.Key, kv.Value);
}
}
}
public bool TryGetValue(string key, out object? value) {
var keystr = key.ToString();
if (keystr is null || !definitions.TryGetValue(keystr, out var fd)) {
return userBindings.TryGetValue(key, out value);
}
value = new Function.Delegate(Evaluate);
return true;
object? Evaluate(List<object?> args) {
if (args.Count != fd.args.Count)
throw new BooleanExpressionInterpreterException(
$"Parameter count mismatch for {fd.name}. Expected: {fd.args} Got: {args.Count}");
else {
var newBindings = fd.args.Zip(args).ToDictionary();
return fd.expression.Evaluate(new EvaluationEnvironment(
AggregateDictionary.Create(newBindings, userBindings), definitions));
}
}
}
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) {
foreach (var kv in userBindings) {
array[arrayIndex++] = kv;
}
foreach (var kv in definitions) {
array[arrayIndex++] = new(kv.Key, kv.Value);
}
}
void IDictionary<string, object?>.Add(string key, object? value) =>
throw new NotSupportedException();
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item) =>
throw new NotSupportedException();
void ICollection<KeyValuePair<string, object?>>.Clear() =>
throw new NotSupportedException();
bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item) =>
TryGetValue(item.Key, out var v) && Equals(v, item.Value);
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
bool IDictionary<string, object?>.Remove(string key) =>
throw new NotSupportedException();
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item) =>
throw new NotSupportedException();
}
@@ -0,0 +1,14 @@
namespace Bts.BooleanExpressions;
class FunctionDefinition {
public FunctionDefinition(
string name, IList<string> args, IBooleanExpression expression) {
this.name = name;
this.args = args;
this.expression = expression;
}
public string name { get; }
public IList<string> args { get; }
public IBooleanExpression expression { get; }
}
@@ -0,0 +1,720 @@
// ==============================================================
// <auto-generated>
// This code automatically produced from an embedded resource.
// Do not edit this file, or it will become incompatible with
// the specification from which it was generated.
// </auto-generated>
// ==============================================================
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Globalization;
namespace QUT.GplexBuffers
{
// Code copied from GPLEX embedded resource
[Serializable]
internal class BufferException : Exception
{
public BufferException() { }
public BufferException(string message) : base(message) { }
public BufferException(string message, Exception innerException)
: base(message, innerException) { }
protected BufferException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
internal abstract class ScanBuff
{
private string fileNm;
public const int EndOfFile = -1;
public const int UnicodeReplacementChar = 0xFFFD;
public bool IsFile { get { return (fileNm != null); } }
public string FileName { get { return fileNm; } set { fileNm = value; } }
public abstract int Pos { get; set; }
public abstract int Read();
public virtual void Mark() { }
public abstract string GetString(int begin, int limit);
public static ScanBuff GetBuffer(string source)
{
return new StringBuffer(source);
}
public static ScanBuff GetBuffer(IList<string> source)
{
return new LineBuffer(source);
}
#if (!NOFILES)
public static ScanBuff GetBuffer(Stream source)
{
return new BuildBuffer(source);
}
#if (!BYTEMODE)
public static ScanBuff GetBuffer(Stream source, int fallbackCodePage)
{
return new BuildBuffer(source, fallbackCodePage);
}
#endif // !BYTEMODE
#endif // !NOFILES
}
#region Buffer classes
// ==============================================================
// ===== Definitions for various ScanBuff derived classes ====
// ==============================================================
// =============== String input ================
// ==============================================================
/// <summary>
/// This class reads characters from a single string as
/// required, for example, by Visual Studio language services
/// </summary>
sealed class StringBuffer : ScanBuff
{
string str; // input buffer
int bPos; // current position in buffer
int sLen;
public StringBuffer(string source)
{
this.str = source;
this.sLen = source.Length;
this.FileName = null;
}
public override int Read()
{
if (bPos < sLen) return str[bPos++];
else if (bPos == sLen) { bPos++; return '\n'; } // one strike, see new line
else { bPos++; return EndOfFile; } // two strikes and you're out!
}
public override string GetString(int begin, int limit)
{
// "limit" can be greater than sLen with the BABEL
// option set. Read returns a "virtual" EOL if
// an attempt is made to read past the end of the
// string buffer. Without the guard any attempt
// to fetch yytext for a token that includes the
// EOL will throw an index exception.
if (limit > sLen) limit = sLen;
if (limit <= begin) return "";
else return str.Substring(begin, limit - begin);
}
public override int Pos
{
get { return bPos; }
set { bPos = value; }
}
public override string ToString() { return "StringBuffer"; }
}
// ==============================================================
// The LineBuff class contributed by Nigel Horspool,
// nigelh@cs.uvic.cs
// ==============================================================
sealed class LineBuffer : ScanBuff
{
IList<string> line; // list of source lines from a file
int numLines; // number of strings in line list
string curLine; // current line in that list
int cLine; // index of current line in the list
int curLen; // length of current line
int curLineStart; // position of line start in whole file
int curLineEnd; // position of line end in whole file
int maxPos; // max position ever visited in whole file
int cPos; // ordinal number of code in source
// Constructed from a list of strings, one per source line.
// The lines have had trailing '\n' characters removed.
public LineBuffer(IList<string> lineList)
{
line = lineList;
numLines = line.Count;
cPos = curLineStart = 0;
curLine = (numLines > 0 ? line[0] : "");
maxPos = curLineEnd = curLen = curLine.Length;
cLine = 1;
FileName = null;
}
public override int Read()
{
if (cPos < curLineEnd)
return curLine[cPos++ - curLineStart];
if (cPos++ == curLineEnd)
return '\n';
if (cLine >= numLines)
return EndOfFile;
curLine = line[cLine];
curLen = curLine.Length;
curLineStart = curLineEnd + 1;
curLineEnd = curLineStart + curLen;
if (curLineEnd > maxPos)
maxPos = curLineEnd;
cLine++;
return curLen > 0 ? curLine[0] : '\n';
}
// To speed up searches for the line containing a position
private int cachedPosition;
private int cachedIxdex;
private int cachedLineStart;
// Given a position pos within the entire source, the results are
// ix -- the index of the containing line
// lstart -- the position of the first character on that line
private void findIndex(int pos, out int ix, out int lstart)
{
if (pos >= cachedPosition)
{
ix = cachedIxdex; lstart = cachedLineStart;
}
else
{
ix = lstart = 0;
}
while (ix < numLines)
{
int len = line[ix].Length + 1;
if (pos < lstart + len) break;
lstart += len;
ix++;
}
cachedPosition = pos;
cachedIxdex = ix;
cachedLineStart = lstart;
}
public override string GetString(int begin, int limit)
{
if (begin >= maxPos || limit <= begin) return "";
int endIx, begIx, endLineStart, begLineStart;
findIndex(begin, out begIx, out begLineStart);
int begCol = begin - begLineStart;
findIndex(limit, out endIx, out endLineStart);
int endCol = limit - endLineStart;
string s = line[begIx];
if (begIx == endIx)
{
// the usual case, substring all on one line
return (endCol <= s.Length) ?
s.Substring(begCol, endCol - begCol)
: s.Substring(begCol) + "\n";
}
// the string spans multiple lines, yuk!
StringBuilder sb = new StringBuilder();
if (begCol < s.Length)
sb.Append(s.Substring(begCol));
for (; ; )
{
sb.Append("\n");
s = line[++begIx];
if (begIx >= endIx) break;
sb.Append(s);
}
if (endCol <= s.Length)
{
sb.Append(s.Substring(0, endCol));
}
else
{
sb.Append(s);
sb.Append("\n");
}
return sb.ToString();
}
public override int Pos
{
get { return cPos; }
set
{
cPos = value;
findIndex(cPos, out cLine, out curLineStart);
// cLine should be the *next* line after curLine.
curLine = (cLine < numLines ? line[cLine++] : "");
curLineEnd = curLineStart + curLine.Length;
}
}
public override string ToString() { return "LineBuffer"; }
}
#if (!NOFILES)
// ==============================================================
// ===== class BuildBuff : for unicode text files ========
// ==============================================================
class BuildBuffer : ScanBuff
{
// Double buffer for char stream.
class BufferElement
{
StringBuilder bldr = new StringBuilder();
StringBuilder next = new StringBuilder();
int minIx;
int maxIx;
int brkIx;
bool appendToNext;
internal BufferElement() { }
internal int MaxIndex { get { return maxIx; } }
// internal int MinIndex { get { return minIx; } }
internal char this[int index]
{
get
{
if (index < minIx || index >= maxIx)
throw new BufferException("Index was outside data buffer");
else if (index < brkIx)
return bldr[index - minIx];
else
return next[index - brkIx];
}
}
internal void Append(char[] block, int count)
{
maxIx += count;
if (appendToNext)
this.next.Append(block, 0, count);
else
{
this.bldr.Append(block, 0, count);
brkIx = maxIx;
appendToNext = true;
}
}
internal string GetString(int start, int limit)
{
if (limit <= start)
return "";
if (start >= minIx && limit <= maxIx)
if (limit < brkIx) // String entirely in bldr builder
return bldr.ToString(start - minIx, limit - start);
else if (start >= brkIx) // String entirely in next builder
return next.ToString(start - brkIx, limit - start);
else // Must do a string-concatenation
return
bldr.ToString(start - minIx, brkIx - start) +
next.ToString(0, limit - brkIx);
else
throw new BufferException("String was outside data buffer");
}
internal void Mark(int limit)
{
if (limit > brkIx + 16) // Rotate blocks
{
StringBuilder temp = bldr;
bldr = next;
next = temp;
next.Length = 0;
minIx = brkIx;
brkIx = maxIx;
}
}
}
BufferElement data = new BufferElement();
int bPos; // Postion index in the StringBuilder
BlockReader NextBlk; // Delegate that serves char-arrays;
private string EncodingName
{
get
{
StreamReader rdr = NextBlk.Target as StreamReader;
return (rdr == null ? "raw-bytes" : rdr.CurrentEncoding.BodyName);
}
}
public BuildBuffer(Stream stream)
{
FileStream fStrm = (stream as FileStream);
if (fStrm != null) FileName = fStrm.Name;
NextBlk = BlockReaderFactory.Raw(stream);
}
#if (!BYTEMODE)
public BuildBuffer(Stream stream, int fallbackCodePage)
{
FileStream fStrm = (stream as FileStream);
if (fStrm != null) FileName = fStrm.Name;
NextBlk = BlockReaderFactory.Get(stream, fallbackCodePage);
}
#endif
/// <summary>
/// Marks a conservative lower bound for the buffer,
/// allowing space to be reclaimed. If an application
/// needs to call GetString at arbitrary past locations
/// in the input stream, Mark() is not called.
/// </summary>
public override void Mark() { data.Mark(bPos - 2); }
public override int Pos
{
get { return bPos; }
set { bPos = value; }
}
/// <summary>
/// Read returns the ordinal number of the next char, or
/// EOF (-1) for an end of stream. Note that the next
/// code point may require *two* calls of Read().
/// </summary>
/// <returns></returns>
public override int Read()
{
//
// Characters at positions
// [data.offset, data.offset + data.bldr.Length)
// are available in data.bldr.
//
if (bPos < data.MaxIndex)
{
// ch0 cannot be EOF
return (int)data[bPos++];
}
else // Read from underlying stream
{
// Experimental code, blocks of page size
char[] chrs = new char[4096];
int count = NextBlk(chrs, 0, 4096);
if (count == 0)
return EndOfFile;
else
{
data.Append(chrs, count);
return (int)data[bPos++];
}
}
}
public override string GetString(int begin, int limit)
{
return data.GetString(begin, limit);
}
public override string ToString()
{
return "StringBuilder buffer, encoding: " + this.EncodingName;
}
}
// =============== End ScanBuff-derived classes ==================
internal delegate int BlockReader(char[] block, int index, int number);
// A delegate factory, serving up a delegate that
// reads a block of characters from the underlying
// encoded stream, via a StreamReader object.
//
internal static class BlockReaderFactory
{
public static BlockReader Raw(Stream stream)
{
return delegate(char[] block, int index, int number)
{
byte[] b = new byte[number];
int count = stream.Read(b, 0, number);
int i = 0;
int j = index;
for (; i < count; i++, j++)
block[j] = (char)b[i];
return count;
};
}
#if (!BYTEMODE)
public static BlockReader Get(Stream stream, int fallbackCodePage)
{
Encoding encoding;
int preamble = Preamble(stream);
if (preamble != 0) // There is a valid BOM here!
encoding = Encoding.GetEncoding(preamble);
else if (fallbackCodePage == -1) // Fallback is "raw" bytes
return Raw(stream);
else if (fallbackCodePage != -2) // Anything but "guess"
encoding = Encoding.GetEncoding(fallbackCodePage);
else // This is the "guess" option
{
int guess = new Guesser(stream).GuessCodePage();
stream.Seek(0, SeekOrigin.Begin);
if (guess == -1) // ==> this is a 7-bit file
encoding = Encoding.ASCII;
else if (guess == 65001)
encoding = Encoding.UTF8;
else // ==> use the machine default
encoding = Encoding.Default;
}
StreamReader reader = new StreamReader(stream, encoding);
return reader.Read;
}
static int Preamble(Stream stream)
{
int b0 = stream.ReadByte();
int b1 = stream.ReadByte();
if (b0 == 0xfe && b1 == 0xff)
return 1201; // UTF16BE
if (b0 == 0xff && b1 == 0xfe)
return 1200; // UTF16LE
int b2 = stream.ReadByte();
if (b0 == 0xef && b1 == 0xbb && b2 == 0xbf)
return 65001; // UTF8
//
// There is no unicode preamble, so we
// return denoter for the machine default.
//
stream.Seek(0, SeekOrigin.Begin);
return 0;
}
#endif // !BYTEMODE
}
#endif // !NOFILES
#endregion Buffer classes
// ==============================================================
// ============ class CodePageHandling =============
// ==============================================================
#if (!NOFILES)
public static class CodePageHandling
{
public static int GetCodePage(string option)
{
string command = option.ToUpperInvariant();
if (command.StartsWith("CodePage:", StringComparison.OrdinalIgnoreCase))
command = command.Substring(9);
try
{
if (command.Equals("RAW"))
return -1;
else if (command.Equals("GUESS"))
return -2;
else if (command.Equals("DEFAULT"))
return 0;
else if (char.IsDigit(command[0]))
return int.Parse(command, CultureInfo.InvariantCulture);
else
{
Encoding enc = Encoding.GetEncoding(command);
return enc.CodePage;
}
}
catch (FormatException)
{
Console.Error.WriteLine(
"Invalid format \"{0}\", using machine default", option);
}
catch (ArgumentException)
{
Console.Error.WriteLine(
"Unknown code page \"{0}\", using machine default", option);
}
return 0;
}
}
#region guesser
#if (!BYTEMODE)
// ==============================================================
// ============ Encoding Guesser =============
// ==============================================================
/// <summary>
/// This class provides a simple finite state automaton that
/// scans the file looking for (1) valid UTF-8 byte patterns,
/// (2) bytes >= 0x80 which are not part of a UTF-8 sequence.
/// The method then guesses whether it is UTF-8 or maybe some
/// local machine default encoding. This works well for the
/// various Latin encodings.
/// </summary>
internal class Guesser
{
ScanBuff buffer;
public int GuessCodePage() { return Scan(); }
const int maxAccept = 10;
const int initial = 0;
const int eofNum = 0;
const int goStart = -1;
const int INITIAL = 0;
const int EndToken = 0;
#region user code
/*
* Reads the bytes of a file to determine if it is
* UTF-8 or a single-byte code page file.
*/
public long utfX;
public long uppr;
#endregion user code
int state;
int currentStart = startState[0];
int code;
#region ScannerTables
static int[] startState = new int[] { 11, 0 };
#region CharacterMap
static sbyte[] map = new sbyte[256] {
/* '\0' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x10' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x20' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '0' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '@' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 'P' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '`' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 'p' */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* '\x80' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\x90' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xA0' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xB0' */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* '\xC0' */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* '\xD0' */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* '\xE0' */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
/* '\xF0' */ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 };
#endregion
static sbyte[][] nextState = new sbyte[][] {
new sbyte[] {0, 0, 0, 0, 0, 0},
new sbyte[] {-1, -1, 10, -1, -1, -1},
new sbyte[] {-1, -1, -1, -1, -1, -1},
new sbyte[] {-1, -1, 8, -1, -1, -1},
new sbyte[] {-1, -1, 5, -1, -1, -1},
new sbyte[] {-1, -1, 6, -1, -1, -1},
new sbyte[] {-1, -1, 7, -1, -1, -1},
null,
new sbyte[] {-1, -1, 9, -1, -1, -1},
null,
null,
new sbyte[] {-1, 1, 2, 3, 4, 2}
};
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
// Reason for suppression: cannot have self-reference in array initializer.
static Guesser()
{
nextState[7] = nextState[2];
nextState[9] = nextState[2];
nextState[10] = nextState[2];
}
int NextState()
{
if (code == ScanBuff.EndOfFile)
return eofNum;
else
return nextState[state][map[code]];
}
#endregion
public Guesser(System.IO.Stream file) { SetSource(file); }
public void SetSource(System.IO.Stream source)
{
this.buffer = new BuildBuffer(source);
code = buffer.Read();
}
int Scan()
{
for (; ; )
{
int next;
state = currentStart;
while ((next = NextState()) == goStart)
code = buffer.Read();
state = next;
code = buffer.Read();
while ((next = NextState()) > eofNum)
{
state = next;
code = buffer.Read();
}
if (state <= maxAccept)
{
#region ActionSwitch
#pragma warning disable 162
switch (state)
{
case eofNum:
switch (currentStart)
{
case 11:
if (utfX == 0 && uppr == 0) return -1; /* raw ascii */
else if (uppr * 10 > utfX) return 0; /* default code page */
else return 65001; /* UTF-8 encoding */
break;
}
return EndToken;
case 1: // Recognized '{Upper128}', Shortest string "\xC0"
case 2: // Recognized '{Upper128}', Shortest string "\x80"
case 3: // Recognized '{Upper128}', Shortest string "\xE0"
case 4: // Recognized '{Upper128}', Shortest string "\xF0"
uppr++;
break;
case 5: // Recognized '{Utf8pfx4}{Utf8cont}', Shortest string "\xF0\x80"
uppr += 2;
break;
case 6: // Recognized '{Utf8pfx4}{Utf8cont}{2}', Shortest string "\xF0\x80\x80"
uppr += 3;
break;
case 7: // Recognized '{Utf8pfx4}{Utf8cont}{3}', Shortest string "\xF0\x80\x80\x80"
utfX += 3;
break;
case 8: // Recognized '{Utf8pfx3}{Utf8cont}', Shortest string "\xE0\x80"
uppr += 2;
break;
case 9: // Recognized '{Utf8pfx3}{Utf8cont}{2}', Shortest string "\xE0\x80\x80"
utfX += 2;
break;
case 10: // Recognized '{Utf8pfx2}{Utf8cont}', Shortest string "\xC0\x80"
utfX++;
break;
default:
break;
}
#pragma warning restore 162
#endregion
}
}
}
} // end class Guesser
#endif // !BYTEMODE
#endregion
#endif // !NOFILES
// End of code copied from embedded resource
}
@@ -0,0 +1,5 @@
namespace Bts.BooleanExpressions;
interface IBinaryOperator {
bool Evaluate(IArgument? left, IArgument? right, IDictionary<string, object?> env);
}
@@ -0,0 +1,5 @@
namespace Bts.BooleanExpressions;
public interface IBooleanExpression {
bool Evaluate(IDictionary<string, object?> env);
}
@@ -0,0 +1,884 @@
// ==============================================================
// <auto-generated>
// This code automatically produced from an embedded resource.
// Do not edit this file, or it will become incompatible with
// the specification from which it was generated.
// </auto-generated>
// ==============================================================
// Gardens Point Parser Generator
// Copyright (c) Wayne Kelly, QUT 2005-2010
// (see accompanying GPPGcopyright.rtf)
using System;
using System.Text;
using System.Globalization;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;
namespace QUT.Gppg {
/// <summary>
/// Abstract class for GPPG shift-reduce parsers.
/// Parsers generated by GPPG derive from this base
/// class, overriding the abstract Initialize() and
/// DoAction() methods.
/// </summary>
/// <typeparam name="TValue">Semantic value type</typeparam>
/// <typeparam name="TSpan">Location type</typeparam>
#if EXPORT_GPPG
public abstract class ShiftReduceParser<TValue, TSpan>
#else
internal abstract class ShiftReduceParser<TValue, TSpan>
#endif
where TSpan : IMerge<TSpan>, new() {
private AbstractScanner<TValue, TSpan> scanner;
/// <summary>
/// The abstract scanner for this parser.
/// </summary>
protected AbstractScanner<TValue, TSpan> Scanner {
get { return scanner; }
set { scanner = value; }
}
/// <summary>
/// Constructor for base class
/// </summary>
/// <param name="scanner">Scanner instance for this parser</param>
protected ShiftReduceParser( AbstractScanner<TValue, TSpan> scanner ) {
this.scanner = scanner;
}
// ==============================================================
// TECHNICAL EXPLANATION.
// Why the next two fields are not exposed via properties.
// ==============================================================
// These fields are of the generic parameter types, and are
// frequently instantiated as struct types in derived classes.
// Semantic actions are defined in the derived classes and refer
// to instance fields of these structs. In such cases the code
// "get_CurrentSemanticValue().myField = blah;" will fail since
// the getter pushes the value of the field, not the reference.
// So, in the presence of properties, gppg would need to encode
// such field accesses as ...
// "tmp = get_CurrentSemanticValue(); // Fetch value
// tmp.myField = blah; // update
// set_CurrentSemanticValue(tmp); " // Write update back.
// There is no issue if TValue is restricted to be a ref type.
// The same explanation applies to scanner.yylval.
// ==============================================================
/// <summary>
/// The current value of the "$$" symbolic variable in the parser
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
protected TValue CurrentSemanticValue;
/// <summary>
/// The current value of the "@$" symbolic variable in the parser
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
protected TSpan CurrentLocationSpan;
protected int NextToken;
private TSpan LastSpan;
private State FsaState;
private bool recovering;
private int tokensSinceLastError;
private PushdownPrefixState<State> StateStack = new PushdownPrefixState<State>();
private PushdownPrefixState<TValue> valueStack = new PushdownPrefixState<TValue>();
private PushdownPrefixState<TSpan> locationStack = new PushdownPrefixState<TSpan>();
/// <summary>
/// The stack of semantic value (YYSTYPE) values.
/// </summary>
protected PushdownPrefixState<TValue> ValueStack { get { return valueStack; } }
/// <summary>
/// The stack of location value (YYLTYPE) varlues.
/// </summary>
protected PushdownPrefixState<TSpan> LocationStack { get { return locationStack; } }
private int errorToken;
private int endOfFileToken;
private string[] nonTerminals;
private State[] states;
private Rule[] rules;
/// <summary>
/// Initialization method to allow derived classes
/// to insert the rule list into this base class.
/// </summary>
/// <param name="rules">The array of Rule objects</param>
protected void InitRules( Rule[] rules ) { this.rules = rules; }
/// <summary>
/// Initialization method to allow derived classes to
/// insert the states table into this base class.
/// </summary>
/// <param name="states">The pre-initialized states table</param>
protected void InitStates( State[] states ) { this.states = states; }
/// <summary>
/// OBSOLETE FOR VERSION 1.4.0
/// </summary>
/// <param name="size"></param>
protected void InitStateTable( int size ) { states = new State[size]; }
/// <summary>
/// Initialization method to allow derived classes
/// to insert the special value for the error and EOF tokens.
/// </summary>
/// <param name="err">The error state ordinal</param>
/// <param name="end">The EOF stat ordinal</param>
protected void InitSpecialTokens( int err, int end ) {
errorToken = err;
endOfFileToken = end;
}
/// <summary>
/// Initialization method to allow derived classes to
/// insert the non-terminal symbol names into this base class.
/// </summary>
/// <param name="names">Non-terminal symbol names</param>
protected void InitNonTerminals( string[] names ) { nonTerminals = names; }
#region YYAbort, YYAccept etcetera.
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class AcceptException : Exception {
internal AcceptException() { }
protected AcceptException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class AbortException : Exception {
internal AbortException() { }
protected AbortException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
[Serializable]
[SuppressMessage( "Microsoft.Design", "CA1064:ExceptionsShouldBePublic" )]
// Reason for FxCop message suppression -
// This exception cannot escape from the local context
private class ErrorException : Exception {
internal ErrorException() { }
protected ErrorException( SerializationInfo i, StreamingContext c ) : base( i, c ) { }
}
// The following methods are only called from within
// a semantic action. The thrown exceptions can never
// propagate outside the ShiftReduceParser class in
// which they are nested.
/// <summary>
/// Force parser to terminate, returning "true"
/// </summary>
protected static void YYAccept() { throw new AcceptException(); }
/// <summary>
/// Force parser to terminate, returning "false"
/// </summary>
protected static void YYAbort() { throw new AbortException(); }
/// <summary>
/// Force parser to terminate, returning
/// "false" if error recovery fails.
/// </summary>
protected static void YYError() { throw new ErrorException(); }
/// <summary>
/// Check if parser in error recovery state.
/// </summary>
protected bool YYRecovering { get { return recovering; } }
#endregion
/// <summary>
/// Abstract base method. ShiftReduceParser calls this
/// to initialize the base class data structures. Concrete
/// parser classes must override this method.
/// </summary>
protected abstract void Initialize();
/// <summary>
/// Main entry point of the Shift-Reduce Parser.
/// </summary>
/// <returns>True if parse succeeds, else false for
/// unrecoverable errors</returns>
public bool Parse() {
Initialize(); // allow derived classes to instantiate rules, states and nonTerminals
NextToken = 0;
FsaState = states[0];
StateStack.Push( FsaState );
valueStack.Push( CurrentSemanticValue );
LocationStack.Push( CurrentLocationSpan );
while (true) {
#if TRACE_ACTIONS
Console.Error.WriteLine("Entering state {0} ", FsaState.number);
DisplayStack();
#endif
int action = FsaState.defaultAction;
if (FsaState.ParserTable != null) {
if (NextToken == 0) {
// We save the last token span, so that the location span
// of production right hand sides that begin or end with a
// nullable production will be correct.
LastSpan = scanner.yylloc;
NextToken = scanner.yylex();
#if TRACE_ACTIONS
Console.Error.WriteLine( "Reading: Next token is {0}", TerminalToString( NextToken ) );
#endif
}
#if TRACE_ACTIONS
else
Console.Error.WriteLine( "Next token is still {0}", TerminalToString( NextToken ) );
#endif
if (FsaState.ParserTable.ContainsKey( NextToken ))
action = FsaState.ParserTable[NextToken];
}
if (action > 0) // shift
{
Shift( action );
}
else if (action < 0) // reduce
{
try {
Reduce( -action );
if (action == -1) // accept
return true;
}
catch (AbortException) {
return false;
}
catch (AcceptException) {
return true;
}
catch (ErrorException) {
if (!ErrorRecovery())
return false;
else
throw; // Rethrow x, preserving information.
}
}
else if (action == 0 && !ErrorRecovery()) // error
return false;
}
}
private void Shift( int stateIndex ) {
#if TRACE_ACTIONS
Console.Error.Write("Shifting token {0}, ", TerminalToString(NextToken));
#endif
FsaState = states[stateIndex];
valueStack.Push( scanner.yylval );
StateStack.Push( FsaState );
LocationStack.Push( scanner.yylloc );
if (recovering) {
if (NextToken != errorToken)
tokensSinceLastError++;
if (tokensSinceLastError > 5)
recovering = false;
}
if (NextToken != endOfFileToken)
NextToken = 0;
}
private void Reduce( int ruleNumber ) {
#if TRACE_ACTIONS
DisplayRule(ruleNumber);
#endif
Rule rule = rules[ruleNumber];
int rhLen = rule.RightHandSide.Length;
//
// Default actions for unit productions.
//
if (rhLen == 1) {
CurrentSemanticValue = valueStack.TopElement(); // Default action: $$ = $1;
CurrentLocationSpan = LocationStack.TopElement(); // Default action "@$ = @1;
}
else if (rhLen == 0) {
// Create a new blank value.
// Explicit semantic action may mutate this value
CurrentSemanticValue = default( TValue );
// The location span for an empty production will start with the
// beginning of the next lexeme, and end with the finish of the
// previous lexeme. This gives the correct behaviour when this
// nonsense value is used in later Merge operations.
CurrentLocationSpan = (scanner.yylloc != null && LastSpan != null ?
scanner.yylloc.Merge( LastSpan ) :
default( TSpan ));
}
else {
// Default action: $$ = $1;
CurrentSemanticValue = valueStack[LocationStack.Depth - rhLen];
// Default action "@$ = @1.Merge(@N)" for location info.
TSpan at1 = LocationStack[LocationStack.Depth - rhLen];
TSpan atN = LocationStack[LocationStack.Depth - 1];
CurrentLocationSpan =
((at1 != null && atN != null) ? at1.Merge( atN ) : default( TSpan ));
}
DoAction( ruleNumber );
for (int i = 0; i < rule.RightHandSide.Length; i++) {
StateStack.Pop();
valueStack.Pop();
LocationStack.Pop();
}
FsaState = StateStack.TopElement();
if (FsaState.Goto.ContainsKey( rule.LeftHandSide ))
FsaState = states[FsaState.Goto[rule.LeftHandSide]];
StateStack.Push( FsaState );
valueStack.Push( CurrentSemanticValue );
LocationStack.Push( CurrentLocationSpan );
}
/// <summary>
/// Execute the selected action from array.
/// Must be overriden in derived classes.
/// </summary>
/// <param name="actionNumber">Index of the action to perform</param>
protected abstract void DoAction( int actionNumber );
private bool ErrorRecovery() {
bool discard;
if (!recovering) // if not recovering from previous error
ReportError();
if (!FindErrorRecoveryState())
return false;
//
// The interim fix for the "looping in error recovery"
// artifact involved moving the setting of the recovering
// bool until after invalid tokens have been discarded.
//
ShiftErrorToken();
discard = DiscardInvalidTokens();
recovering = true;
tokensSinceLastError = 0;
return discard;
}
private void ReportError() {
StringBuilder errorMsg = new StringBuilder();
errorMsg.AppendFormat( "Syntax error, unexpected {0}", TerminalToString( NextToken ) );
if (FsaState.ParserTable.Count < 7) {
bool first = true;
foreach (int terminal in FsaState.ParserTable.Keys) {
if (first)
errorMsg.Append( ", expecting " );
else
errorMsg.Append( ", or " );
errorMsg.Append( TerminalToString( terminal ) );
first = false;
}
}
scanner.yyerror( errorMsg.ToString() );
}
private void ShiftErrorToken() {
int old_next = NextToken;
NextToken = errorToken;
Shift( FsaState.ParserTable[NextToken] );
#if TRACE_ACTIONS
Console.Error.WriteLine("Entering state {0} ", FsaState.number);
#endif
NextToken = old_next;
}
private bool FindErrorRecoveryState() {
while (true) // pop states until one found that accepts error token
{
if (FsaState.ParserTable != null &&
FsaState.ParserTable.ContainsKey( errorToken ) &&
FsaState.ParserTable[errorToken] > 0) // shift
return true;
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: popping state {0}", StateStack.TopElement().number);
#endif
StateStack.Pop();
valueStack.Pop();
LocationStack.Pop();
#if TRACE_ACTIONS
DisplayStack();
#endif
if (StateStack.IsEmpty()) {
#if TRACE_ACTIONS
Console.Error.WriteLine("Aborting: didn't find a state that accepts error token");
#endif
return false;
}
else
FsaState = StateStack.TopElement();
}
}
private bool DiscardInvalidTokens() {
int action = FsaState.defaultAction;
if (FsaState.ParserTable != null) {
// Discard tokens until find one that works ...
while (true) {
if (NextToken == 0) {
#if TRACE_ACTIONS
Console.Error.Write("Reading a token: ");
#endif
NextToken = scanner.yylex();
}
#if TRACE_ACTIONS
Console.Error.WriteLine("Next token is {0}", TerminalToString(NextToken));
#endif
if (NextToken == endOfFileToken)
return false;
if (FsaState.ParserTable.ContainsKey( NextToken ))
action = FsaState.ParserTable[NextToken];
if (action != 0)
return true;
else {
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: Discarding {0}", TerminalToString(NextToken));
#endif
NextToken = 0;
}
}
}
else if (recovering && tokensSinceLastError == 0) {
//
// Boolean recovering is not set until after the first
// error token has been shifted. Thus if we get back
// here with recovering set and no tokens read we are
// looping on the same error recovery action. This
// happens if current_state.ParserTable is null because
// the state has an LR(0) reduction, but not all
// lookahead tokens are valid. This only occurs for
// error productions that *end* on "error".
//
// This action discards tokens one at a time until
// the looping stops. Another attack would be to always
// use the LALR(1) table if a production ends on "error"
//
#if TRACE_ACTIONS
Console.Error.WriteLine("Error: panic discard of {0}", TerminalToString(NextToken));
#endif
if (NextToken == endOfFileToken)
return false;
NextToken = 0;
return true;
}
else
return true;
}
/// <summary>
/// Traditional YACC method. Discards the next input token.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyclearin" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyclearin" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
protected void yyclearin() { NextToken = 0; }
/// <summary>
/// Tradional YACC method. Clear the "recovering" flag.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerrok" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerrok" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
protected void yyerrok() { recovering = false; }
/// <summary>
/// OBSOLETE FOR VERSION 1.4.0
/// Method used by derived types to insert new
/// state instances in the "states" array.
/// </summary>
/// <param name="stateNumber">index of the state</param>
/// <param name="state">data for the state</param>
protected void AddState( int stateNumber, State state ) {
states[stateNumber] = state;
state.number = stateNumber;
}
private void DisplayStack() {
Console.Error.Write( "State stack is now:" );
for (int i = 0; i < StateStack.Depth; i++)
Console.Error.Write( " {0}", StateStack[i].number );
Console.Error.WriteLine();
}
private void DisplayRule( int ruleNumber ) {
Console.Error.Write( "Reducing stack by rule {0}, ", ruleNumber );
DisplayProduction( rules[ruleNumber] );
}
private void DisplayProduction( Rule rule ) {
if (rule.RightHandSide.Length == 0)
Console.Error.Write( "/* empty */ " );
else
foreach (int symbol in rule.RightHandSide)
Console.Error.Write( "{0} ", SymbolToString( symbol ) );
Console.Error.WriteLine( "-> {0}", SymbolToString( rule.LeftHandSide ) );
}
/// <summary>
/// Abstract state class naming terminal symbols.
/// This is overridden by derived classes with the
/// name (or alias) to be used in error messages.
/// </summary>
/// <param name="terminal">The terminal ordinal</param>
/// <returns></returns>
protected abstract string TerminalToString( int terminal );
private string SymbolToString( int symbol ) {
if (symbol < 0)
return nonTerminals[-symbol - 1];
else
return TerminalToString( symbol );
}
/// <summary>
/// Return text representation of argument character
/// </summary>
/// <param name="input">The character to convert</param>
/// <returns>String representation of the character</returns>
protected static string CharToString( char input ) {
switch (input) {
case '\a': return @"'\a'";
case '\b': return @"'\b'";
case '\f': return @"'\f'";
case '\n': return @"'\n'";
case '\r': return @"'\r'";
case '\t': return @"'\t'";
case '\v': return @"'\v'";
case '\0': return @"'\0'";
default: return string.Format( CultureInfo.InvariantCulture, "'{0}'", input );
}
}
}
/// <summary>
/// Classes implementing this interface must supply a
/// method that merges two location objects to return
/// a new object of the same type.
/// GPPG-generated parsers have the default location
/// action equivalent to "@$ = @1.Merge(@N);" where N
/// is the right-hand-side length of the production.
/// </summary>
/// <typeparam name="TSpan">The Location type</typeparam>
#if EXPORT_GPPG
public interface IMerge<TSpan>
#else
internal interface IMerge<TSpan>
#endif
{
/// <summary>
/// Interface method that creates a location object from
/// the current and last object. Typically used to create
/// a location object extending from the start of the @1
/// object to the end of the @N object.
/// </summary>
/// <param name="last">The lexically last object to merge</param>
/// <returns>The merged location object</returns>
TSpan Merge( TSpan last );
}
/// <summary>
/// This is the default class that carries location
/// information from the scanner to the parser.
/// If you don't declare "%YYLTYPE Foo" the parser
/// will expect to deal with this type.
/// </summary>
#if EXPORT_GPPG
public class LexLocation : IMerge<LexLocation>
#else
[SuppressMessage( "Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses" )]
internal class LexLocation : IMerge<LexLocation>
#endif
{
private int startLine; // start line
private int startColumn; // start column
private int endLine; // end line
private int endColumn; // end column
/// <summary>
/// The line at which the text span starts.
/// </summary>
public int StartLine { get { return startLine; } }
/// <summary>
/// The column at which the text span starts.
/// </summary>
public int StartColumn { get { return startColumn; } }
/// <summary>
/// The line on which the text span ends.
/// </summary>
public int EndLine { get { return endLine; } }
/// <summary>
/// The column of the first character
/// beyond the end of the text span.
/// </summary>
public int EndColumn { get { return endColumn; } }
/// <summary>
/// Default no-arg constructor.
/// </summary>
public LexLocation() { }
/// <summary>
/// Constructor for text-span with given start and end.
/// </summary>
/// <param name="sl">start line</param>
/// <param name="sc">start column</param>
/// <param name="el">end line </param>
/// <param name="ec">end column</param>
public LexLocation( int sl, int sc, int el, int ec ) { startLine = sl; startColumn = sc; endLine = el; endColumn = ec; }
/// <summary>
/// Create a text location which spans from the
/// start of "this" to the end of the argument "last"
/// </summary>
/// <param name="last">The last location in the result span</param>
/// <returns>The merged span</returns>
public LexLocation Merge( LexLocation last ) { return new LexLocation( this.startLine, this.startColumn, last.endLine, last.endColumn ); }
}
/// <summary>
/// Abstract scanner class that GPPG expects its scanners to
/// extend.
/// </summary>
/// <typeparam name="TValue">Semantic value type YYSTYPE</typeparam>
/// <typeparam name="TSpan">Source location type YYLTYPE</typeparam>
#if EXPORT_GPPG
public abstract class AbstractScanner<TValue, TSpan>
#else
internal abstract class AbstractScanner<TValue, TSpan>
#endif
where TSpan : IMerge<TSpan> {
/// <summary>
/// Lexical value optionally set by the scanner. The value
/// is of the %YYSTYPE type declared in the parser spec.
/// </summary>
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylval" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylval" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
// A field must be declared for this value of parametric type,
// since it may be instantiated by a value struct. If it were
// implemented as a property, machine generated code in derived
// types would not be able to select on the returned value.
#pragma warning disable 649
public TValue yylval; // Lexical value: set by scanner
#pragma warning restore 649
/// <summary>
/// Current scanner location property. The value is of the
/// type declared by %YYLTYPE in the parser specification.
/// </summary>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylloc" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylloc" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public virtual TSpan yylloc {
get { return default( TSpan ); } // Empty implementation allowing
set { /* skip */ } // yylloc to be ignored entirely.
}
/// <summary>
/// Main call point for LEX-like scanners. Returns an int
/// corresponding to the token recognized by the scanner.
/// </summary>
/// <returns>An int corresponding to the token</returns>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylex" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylex" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public abstract int yylex();
/// <summary>
/// Traditional error reporting provided by LEX-like scanners
/// to their YACC-like clients.
/// </summary>
/// <param name="format">Message format string</param>
/// <param name="args">Optional array of args</param>
[SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerror" )]
[SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerror" )]
// Reason for FxCop message suppression -
// This is a traditional name for YACC-like functionality
public virtual void yyerror( string format, params object[] args ) { }
}
/// <summary>
/// Encapsulated state for the parser.
/// Opaque to users, visible to the tool-generated code.
/// </summary>
#if EXPORT_GPPG
public class State
{
/// <summary>
/// The number of states in the automaton.
/// </summary>
public int number;
#else
internal class State {
/// <summary>
/// The index of this state in the states array.
/// </summary>
internal int number;
#endif
internal Dictionary<int, int> ParserTable; // Terminal -> ParseAction
internal Dictionary<int, int> Goto; // NonTerminal -> State;
internal int defaultAction; // = 0; // ParseAction
/// <summary>
/// State transition data for this state. Pairs of elements of the
/// goto array associate symbol ordinals with next state indices.
/// The actions array is passed to another constructor.
/// </summary>
/// <param name="actions">The action list</param>c
/// <param name="goToList">Next state data</param>
public State( int[] actions, int[] goToList )
: this( actions ) {
Goto = new Dictionary<int, int>();
for (int i = 0; i < goToList.Length; i += 2)
Goto.Add( goToList[i], goToList[i + 1] );
}
/// <summary>
/// Action data for this state. Pairs of elements of the
/// action array associate action ordinals with each of
/// those symbols that have actions in the current state.
/// </summary>
/// <param name="actions">The action array</param>
public State( int[] actions ) {
ParserTable = new Dictionary<int, int>();
for (int i = 0; i < actions.Length; i += 2)
ParserTable.Add( actions[i], actions[i + 1] );
}
/// <summary>
/// Set the default action for this state.
/// </summary>
/// <param name="defaultAction">Ordinal of the default action</param>
public State( int defaultAction ) {
this.defaultAction = defaultAction;
}
/// <summary>
/// Set the default action and the state transition table.
/// </summary>
/// <param name="defaultAction">The default action</param>
/// <param name="goToList">Transitions from this state</param>
public State( int defaultAction, int[] goToList )
: this( defaultAction ) {
Goto = new Dictionary<int, int>();
for (int i = 0; i < goToList.Length; i += 2)
Goto.Add( goToList[i], goToList[i + 1] );
}
}
/// <summary>
/// Rule representation at runtime.
/// </summary>
#if EXPORT_GPPG
public class Rule
#else
internal class Rule
#endif
{
internal int LeftHandSide; // symbol
internal int[] RightHandSide; // symbols
/// <summary>
/// Rule constructor. This holds the ordinal of
/// the left hand side symbol, and the list of
/// right hand side symbols, in lexical order.
/// </summary>
/// <param name="left">The LHS non-terminal</param>
/// <param name="right">The RHS symbols, in lexical order</param>
public Rule( int left, int[] right ) {
this.LeftHandSide = left;
this.RightHandSide = right;
}
}
/// <summary>
/// Stack utility for the shift-reduce parser.
/// GPPG parsers have three instances:
/// (1) The parser state stack, T = QUT.Gppg.State,
/// (2) The semantic value stack, T = TValue,
/// (3) The location stack, T = TSpan.
/// </summary>
/// <typeparam name="T"></typeparam>
#if EXPORT_GPPG
public class PushdownPrefixState<T>
#else
internal class PushdownPrefixState<T>
#endif
{
// Note that we cannot use the BCL Stack<T> class
// here as derived types need to index into stacks.
//
private T[] array = new T[8];
private int tos = 0;
/// <summary>
/// Indexer for values of the stack below the top.
/// </summary>
/// <param name="index">index of the element, starting from the bottom</param>
/// <returns>the selected element</returns>
public T this[int index] { get { return array[index]; } }
/// <summary>
/// The current depth of the stack.
/// </summary>
public int Depth { get { return tos; } }
internal void Push( T value ) {
if (tos >= array.Length) {
T[] newarray = new T[array.Length * 2];
System.Array.Copy( array, newarray, tos );
array = newarray;
}
array[tos++] = value;
}
internal T Pop() {
T rslt = array[--tos];
array[tos] = default( T );
return rslt;
}
internal T TopElement() { return array[tos - 1]; }
internal bool IsEmpty() { return tos == 0; }
}
}
@@ -0,0 +1,4 @@
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using System.Collections.Immutable;
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\client\spark_websocket\spark_websocket.csproj" />
<ProjectReference Include="..\DataStreamExpressions\DataStreamExpressions.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public class ChatGptSession {
public static Task Start() {
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://0.0.0.0:5000")
.UseStartup<ChatGptSession>()
.Build();
return host.RunAsync();
}
public void Configure(IApplicationBuilder app) {
app.UseWebSockets();
app.Use(async (context, next) => {
if (context.WebSockets.IsWebSocketRequest) {
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
// Handle WebSocket connection
await HandleWebSocketConnection(webSocket);
} else {
await next();
}
});
}
async Task HandleWebSocketConnection(WebSocket webSocket) {
var buffer = new byte[1024];
WebSocketReceiveResult result;
while (webSocket.State == WebSocketState.Open) {
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text) {
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
// Echo the received message back to the client
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server: " + message)),
result.MessageType, result.EndOfMessage, CancellationToken.None);
} else if (result.MessageType == WebSocketMessageType.Close) {
// Close the WebSocket connection
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
}
}
}
@@ -0,0 +1,9 @@
namespace Bts.DataStream.Websocket;
class ClientSubscription(WebsocketClientID id, SubscriptionParams @params, IDisposable dispose) : IDisposable {
public SubscriptionParams Params => @params;
public WebsocketClientID Id => id;
public void Dispose() =>
dispose.Dispose();
}
@@ -0,0 +1,229 @@
namespace Bts.DataStream.Websocket;
using BCG;
using bts;
using bts.api.core;
using bts.collections;
using Bts.DataStreamExpressions;
using Bts.Websocket;
using mbts.utils;
using Spark.Core.Disposables;
using System.Reactive.Subjects;
using System.Text.Json;
using System.Text.Json.Nodes;
/// <summary>
/// Represents an I/O model (<see cref="ISubject{TSource, TResult}"/>), that accepts websocket
/// requests (from clients) as input, and emits data/notifications (to clients) as output.
/// This class's chief role is managing data subscriptions to sparkd on the clients' behalf.
/// </summary>
public class DataSync(ServiceContext serviceCtx, JsonSerializerOptions? serializerOpts = null) :
ISubject<WebsocketConnectionUpdate, IEnumerable<WebsocketOutgoingMessage>> {
readonly ServiceContext serviceCtx = serviceCtx;
readonly JsonSerializerOptions serializerOpts = serializerOpts ?? JsonSerializerOptions.Default;
readonly Dictionary<WebsocketClientID, List<ClientSubscription>> clients = [];
readonly object lockObj = new();
event Action<IEnumerable<WebsocketOutgoingMessage>>? requestSend;
public void OnCompleted() =>
Log.Api.info("DataSync incoming request stream has completed.");
public void OnError(Exception error) =>
Log.Api.error($"DataSync error: {error}");
public void OnNext(WebsocketConnectionUpdate value) {
switch (value.Type) {
case WebsocketConnectionUpdateType.IncomingMessage:
onIncomingMessage(value.IncomingMessage);
break;
case WebsocketConnectionUpdateType.Connect:
onConnect(value.Connect);
break;
case WebsocketConnectionUpdateType.Disconnect:
onDisconnect(value.Disconnect);
break;
default:
Log.assert(false, $"Unexpected WebsocketConnectionUpdate type: {value.Type}");
break;
}
}
public IDisposable Subscribe(IObserver<IEnumerable<WebsocketOutgoingMessage>> observer) {
lock (lockObj) {
requestSend += observer.OnNext;
return Disposable.Create(dispose);
}
void dispose() {
lock (lockObj) {
requestSend -= observer.OnNext;
}
}
}
static JsonRpcId? extractRpcId(string messageBody) {
try {
return JsonNode.Parse(messageBody)
?.AsObject()
?.GetValueOrDefault("id")
?.Deserialize<JsonRpcId>();
} catch {
return null;
}
}
void onConnect(WebsocketConnectNotification notification) {
var id = notification.Id;
lock (lockObj) {
clients.Add(id, new());
}
Log.Api.info($"DataStreamWebsocketDataSync client connected. clientId: {notification.Id}");
if (notification.Message is not null) {
onIncomingMessage(notification.Message);
}
}
void onDisconnect(WebsocketDisconnectNotification notification) {
Log.Api.warn($"DataStreamWebsocketDataSync client disconnected. clientId: {notification.Id}");
unsubscribeAll(notification.Id);
clients.Remove(notification.Id);
}
void onIncomingMessage(WebsocketIncomingMessage message) {
var id = message.sender_client_id;
if (!JsonRpcRequest.TryDeserialize(message, out var req, out var err)) {
onBadRequest(message, err);
return;
}
switch (req.Method!.ToLower()) {
case "subscribe":
subscribe(id, req);
break;
case "unsubscribe":
unsubscribe(id, req);
break;
case "unsubscribe_all":
unsubscribeAll(id);
break;
default:
Log.Api.error($"Received request of unknown type: {req}");
break;
}
}
void sendNotification<T>(WebsocketClientID id, string method, T rpcParams) {
var rpcNotification = JsonRpcRequest.CreateNotification(method, rpcParams);
var rpcNotificationStr = JsonSerializer.Serialize(rpcNotification, serializerOpts);
send(new WebsocketOutgoingMessage[] { new(id, rpcNotificationStr) });
}
void sendResponse<T>(WebsocketClientID id, JsonRpcId rpcId, T rpcParams) {
var rpcRes = JsonRpcResponse.Create(rpcId, rpcParams);
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
send(new WebsocketOutgoingMessage[] { new(id, rpcResStr )});
}
void sendError<T>(WebsocketClientID id, JsonRpcId rpcId, JsonRpcError<T> error) {
var rpcRes = JsonRpcResponse.CreateError(rpcId, error);
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
send(new WebsocketOutgoingMessage[] { new(id, rpcResStr) });
}
bool send(IEnumerable<WebsocketOutgoingMessage> messages) {
lock (lockObj) {
if (requestSend is null) {
Log.Api.warn(
$"Unable to send message(s) because '{nameof(requestSend)}' handler is null");
return false;
}
requestSend(messages);
return true;
}
}
void subscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
var subParams = req.Params!.Deserialize<SubscriptionParams>()!;
var exp = DataStreamExpressionsInterpreter.Evaluate(subParams.Query!);
var stream = exp.getView(new Dictionary<string, StatementExpression>(0));
var sub = new ClientSubscription(id, subParams, Disposable.Create(dispose));
lock (lockObj) {
clients[id].Add(sub);
sendResponse(id, req.Id!.Value, req.Params);
stream.onChange += onChange;
}
void onChange(object? newValue) {
var dp = ExceptionAssert.of_type<DataPacket>(newValue);
var rpcParams = new {
addedRows = dp.addedRows,
deletedRows = dp.deletedRows
};
sendNotification(id, "subscription", rpcParams);
}
void dispose() =>
stream.onChange -= onChange;
}
void unsubscribe(WebsocketClientID id, JsonRpcRequest<JsonObject?> req) {
var subParams = req.Params!.Deserialize<SubscriptionParams>();
var subParamsStr = JsonSerializer.Serialize(subParams, serializerOpts);
// Minimize amount of code inside lock (just need it to find/remove the subscription)
List<ClientSubscription>? subs = null;
ClientSubscription? sub = null;
lock (lockObj) {
subs = clients.GetValueOrDefault(id);
subs?.TryRemoveFirst(s => s!.Params.Equals(subParams), out sub);
}
if (subs is null) {
// Didn't find client (log/throw).
var msg = $"Unable to unsubscribe: did not find client. Id: {id}.\nParams:\n{subParamsStr}";
Log.Api.error(msg);
throw new Exception(msg);
}
if (sub is null) {
// Found client, but didn't find subscription (log/throw).
var msg = $"Unable to unsubscribe: did not find the specified subscription. " +
$"Id: {id}. Request:\n{subParams}";
Log.Api.error(msg);
throw new Exception(msg);
}
// Found client and subscription (dispose/remove subscription).
Log.Api.info($"Unsubscribing client. Id: {id}.\nParams:\n{subParamsStr}");
sub.Dispose();
}
void unsubscribeAll(WebsocketClientID id) {
lock (lockObj) {
if (!clients.TryGetValue(id, out var subs)) {
throw new Exception(
$"Unable to unsubscribe: did not find client with id {id}");
}
subs.DisposeAll();
subs.Clear();
}
}
void onBadRequest(WebsocketIncomingMessage req, string? errorMsg) {
var rpcId = extractRpcId(req.message_body);
var logMsg = $"Recieved invalid request:\n{req.message_body}";
if (errorMsg is not null) {
logMsg += $"\n\n**Exception**\n{errorMsg}";
}
Log.Api.error(logMsg);
if (rpcId is null) {
return;
}
// Send error back to client
var rpcErr = JsonRpcError.Create(
(long)JsonRpcErrorCode.InvalidRequest,
$"Received invalid request: {errorMsg ?? "unknown error"}",
req.message_body);
sendError(req.sender_client_id, rpcId.Value, rpcErr);
}
}
@@ -0,0 +1,34 @@
namespace Bts.DataStream.Websocket;
using Newtonsoft.Json.Linq;
using System.Diagnostics.CodeAnalysis;
public static class JObjectExtensions {
public static bool TryParse<T>(
this JObject filter, string key, [MaybeNull] out T obj, [NotNullWhen(false)] out string? error) {
obj = default;
var value = filter.GetValueOrDefault(key)?.ToString();
if (value == null) {
error = $"{key} not present";
return true;
}
return WebsocketJsonParsers.TryParse(value, key, out obj, out error);
}
public static bool TryParse<T>(
this JObject filter, string key, out T result, [NotNullWhen(false)] out string? error, T defaultValue) {
var value = filter.GetValueOrDefault(key)?.ToString();
if (value == null) {
result = defaultValue;
error = $"{key} not present";
return true;
}
if (WebsocketJsonParsers.TryParse<T>(value, key, out var maybeRes, out error)) {
result = maybeRes ?? defaultValue;
return true;
}
result = defaultValue;
return false;
}
}
@@ -0,0 +1,24 @@
namespace Bts.DataStream.Websocket;
using Newtonsoft.Json.Linq;
class JsonParseResult {
public string? MessageId { get; init; }
public RequestType Type { get; init; }
public JObject? Parameters { get; init; }
public string? Error { get; init; }
public bool Successful => Error is not null;
public static JsonParseResult CreateError(string error) =>
new() {
Error = error,
MessageId = "missing"
};
public static JsonParseResult Create(string messageId, RequestType type, JObject parameters) =>
new() {
MessageId = messageId,
Parameters = parameters,
Type = type
};
}
@@ -0,0 +1,6 @@
namespace Bts.DataStream.Websocket;
public enum JsonRpcErrorCode {
None = 0,
InvalidRequest = 1,
}
@@ -0,0 +1,48 @@
namespace Bts.DataStream.Websocket;
using Spark.Core;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class MultilineStringJsonConverter : JsonConverter<string> {
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
if (typeToConvert != typeof(string)) {
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
}
return reader.TokenType switch {
JsonTokenType.String => stringImpl(ref reader),
JsonTokenType.StartArray => arrayImpl(ref reader),
_ => throw new ArgumentException(
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
};
static string? stringImpl(ref Utf8JsonReader reader) =>
new(reader.GetString()!);
static string? arrayImpl(ref Utf8JsonReader reader) {
var lines = new List<string>(2);
while (reader.Read()) {
switch (reader.TokenType) {
case JsonTokenType.String:
lines.Add(reader.GetString()!);
break;
case JsonTokenType.EndArray:
return string.Join("\n", lines);
}
}
throw new JsonException(
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
}
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) {
var lines = value.Split("\n");
if (lines.Length < 2) {
writer.WriteRawValue(value);
return;
}
writer.WriteRawValue($"[{", ".join(lines)}]");
}
}
@@ -0,0 +1,8 @@
namespace Bts.DataStream.Websocket;
public enum RequestType {
Unspecified,
Subscribe,
Unsubscribe,
UnsubscribeAll,
}
@@ -0,0 +1,27 @@
namespace Bts.DataStream.Websocket;
using bts;
using bts.api.core;
using Bts.Websocket;
using Google.Protobuf;
using System.Text.Json;
public static class Session {
static int sessionIdCounter;
public static async Task Start(
ServiceContext context, WebsocketServerConfig config, JsonSerializerOptions? opts) {
opts ??= JsonSerializerOptions.Default;
var sessionId = Interlocked.Increment(ref sessionIdCounter);
var gateway = new WebsocketGatewayObservable(config);
var sync = new DataSync(context, opts);
using var syncSub = gateway.Subscribe(sync);
using var gwSub = sync.Subscribe(gateway);
Log.Api.info($"Starting DataStream Websocket Session {sessionId}");
Log.Api.info($"DataStream Websocket Session {sessionId} config:\n{JsonFormatter.Default.Format(config)}");
await gateway.ConnectAsync();
}
}
@@ -0,0 +1,110 @@
namespace Bts.DataStream.Websocket;
using Spark.Core;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
public class SubscriptionParams : IEquatable<SubscriptionParams> {
static class PropNames {
public const string QUERY = "query";
}
public SubscriptionParams(string? query) {
Query = query;
}
// Serializer supports plain string, or an array of lines for convenience
[MaybeNull]
[JsonPropertyName(PropNames.QUERY)]
[JsonConverter(typeof(MultilineStringJsonConverter))]
public string? Query { get; set; }
public bool Equals(SubscriptionParams? other) =>
other is not null &&
Query == other.Query;
class SubscriptionParamsConverter : JsonConverter<SubscriptionParams> {
public override bool CanConvert(Type typeToConvert) =>
typeToConvert == typeof(SubscriptionParams);
public override SubscriptionParams Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
if (typeToConvert != typeof(SubscriptionParams)) {
throw new ArgumentException($"Unable to parse {typeToConvert}", nameof(typeToConvert));
}
return read(ref reader);
}
SubscriptionParams read(ref Utf8JsonReader reader) {
return reader.TokenType switch {
JsonTokenType.String => stringImpl(ref reader),
JsonTokenType.StartArray => arrayImpl(ref reader),
JsonTokenType.StartObject => objectImpl(ref reader),
_ => throw new ArgumentException(
$"Unable to parse {typeof(SubscriptionParams)} from {reader.TokenType}"),
};
static SubscriptionParams stringImpl(ref Utf8JsonReader reader) =>
new(reader.GetString()!);
static SubscriptionParams arrayImpl(ref Utf8JsonReader reader) {
var lines = new List<string>(2);
while (reader.Read()) {
switch (reader.TokenType) {
case JsonTokenType.String:
lines.Add(reader.GetString()!);
break;
case JsonTokenType.EndArray:
return new("\n".join(lines));
}
}
throw new JsonException(
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
}
static SubscriptionParams objectImpl(ref Utf8JsonReader reader) {
var lines = new List<string>(2);
while (reader.Read()) {
switch (reader.TokenType) {
case JsonTokenType.PropertyName:
var propName = reader.GetString()!;
if (propName == PropNames.QUERY) {
switch (reader.TokenType) {
case JsonTokenType.StartArray:
return arrayImpl(ref reader);
case JsonTokenType.String:
return stringImpl(ref reader);
}
}
break;
case JsonTokenType.String:
lines.Add(reader.GetString()!);
break;
case JsonTokenType.EndArray:
return new("\n".join(lines));
}
}
throw new JsonException(
$"Failed to parse {typeof(SubscriptionParams)} (did not reach end of array)");
}
}
public override void Write(
Utf8JsonWriter writer, SubscriptionParams value, JsonSerializerOptions options) {
var q = value.Query;
var lines = q?.Split("\n");
if (lines is null || lines.Length < 2) {
writer.WriteString(PropNames.QUERY, value.Query);
return;
}
writer.WriteStartArray(PropNames.QUERY);
foreach (var l in lines) {
writer.WriteStringValue(l);
}
writer.WriteEndArray();
}
}
}
@@ -0,0 +1,5 @@
global using bts.websocket_core;
global using System;
global using System.Collections.Immutable;
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultContentItems>True</EnableDefaultContentItems>
</PropertyGroup>
</Project>
@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@@ -0,0 +1,27 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Bts.DataStream.WebsocketClient.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
@@ -0,0 +1,15 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<button id="connectButton">Connect to Websocket</button>
<div id="output"></div>
<input type="text" id="methodInput" placeholder="Enter method name" />
<input type="text" id="paramsInput" placeholder="Enter method parameters (if any)" />
<button id="sendMessageButton">Send JSON-RPC Request</button>
</div>
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Bts.DataStream.WebsocketClient.Pages;
public class IndexModel : PageModel {
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger) {
_logger = logger;
}
public void OnGet() {
}
}
@@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
Loaded 100 of 910 files, more files were not shown because too many files have changed in this diff. Show more