Compare commits

..
Author SHA1 Message Date
John Colvin 1e4fdcc404 check pt: Before going to Chi (11/07/23, random scripts, etc.) 2024-01-23 10:39:29 -06:00
John Colvin ddfd34da80 check pt: Before going to Chi (11/07/23) 2024-01-23 10:39:28 -06:00
John Colvin ea322ddb5c add util: StringStreamEnumerable 2024-01-23 10:39:28 -06:00
John Colvin 477b05d06f add websocket server config, README 2024-01-23 10:39:28 -06:00
John Colvin 7413790bd4 refactor: Change WebsocketServerConfig.port to uint32
It should always be a number between 1-65535
2024-01-23 10:39:28 -06:00
John Colvin 7a8643b186 feat: Add Bts.DataStream.Websocket/Server 2024-01-23 10:39:25 -06:00
John Colvin e4359c43ac feat: Add JsonRpc types for websocket paradigm 2024-01-23 10:35:47 -06:00
John Colvin 71eab08be1 refactor: Add WebsocketGatewayObservable
An IObservable wrapper for IWebsocketConnectionGateway
2024-01-23 10:35:47 -06:00
John Colvin 09a57631c3 refactor: Remove nullability constraint in GetValueOrDefault 2024-01-23 10:35:47 -06:00
John Colvin 7956754e73 refactor: Use events to avoid circular deps for ws sync/gateway 2024-01-23 10:35:47 -06:00
John Colvin d6abd90bfc refactor: Let caller specify nullability of T in json parse fn 2024-01-23 10:35:47 -06:00
John Colvin ce7ec0c68d refactor: Get rid of redundant UniversalNumericComparer 2024-01-23 10:35:47 -06:00
John Colvin 07fb055bf2 refactor: Reorganize NumericComparer, support all types 2024-01-23 10:35:47 -06:00
John Colvin cae9268f94 docker: Update default compiler versions
Via update-alternatives
2024-01-19 16:00:57 -05:00
John Colvin 2d8f942e62 cleanup: Remove leftover legacy files 2024-01-19 15:25:12 -05:00
John Colvin 23c5a4c46b build: Upgrade c# to net8.0 (spark_websocket, Spark.Admin.WebApp) 2024-01-19 15:24:16 -05:00
John Colvin adc043a1ec docker: Upgrade dotnet 7 -> 8 2024-01-19 15:22:08 -05:00
John Colvin 8bcae22020 Check pt before trip to NC 2024-01-18 2024-01-18 10:26:04 -05:00
John Colvin 5882332129 build: Upgrade c# to net8.0 2024-01-18 10:25:50 -05:00
John Colvin 22f1e61f82 Add bones for Bts.DataStreamWebApp.Client 2024-01-18 00:33:23 -05:00
John Colvin 8bb7262d81 Add bones for Bts.DataStreamWebApp.Server 2024-01-18 00:32:30 -05:00
116 changed files with 6440 additions and 11751 deletions

No files matched your search

+16 -30
View File
@@ -207,23 +207,17 @@ dotnet_naming_symbols.parameters.applicable_accessibilities = public,internal,pr
dotnet_naming_symbols.private_symbols.applicable_kinds = property,method,field,event,delegate
dotnet_naming_symbols.private_symbols.applicable_accessibilities = protected,private
# local symbols
dotnet_naming_symbols.local_symbols.applicable_kinds = local
# parameter symbols
dotnet_naming_symbols.parameter_symbols.applicable_kinds = parameter
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.snake_case.required_prefix =
dotnet_naming_style.snake_case.required_suffix =
dotnet_naming_style.snake_case.word_separator = _
dotnet_naming_style.snake_case.capitalization = all_lower
dotnet_naming_style.camel_case.required_prefix =
dotnet_naming_style.camel_case.required_suffix =
dotnet_naming_style.camel_case.word_separator =
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.private_snake_case.required_prefix = _
dotnet_naming_style.private_snake_case.required_suffix =
dotnet_naming_style.private_snake_case.word_separator = _
dotnet_naming_style.private_snake_case.capitalization = all_lower
dotnet_naming_style.user_types.required_prefix =
dotnet_naming_style.user_types.required_suffix =
@@ -235,10 +229,6 @@ dotnet_naming_style.interface.required_suffix =
dotnet_naming_style.interface.word_separator =
dotnet_naming_style.interface.capitalization = pascal_case
dotnet_naming_style.local.capitalization = camel_case
dotnet_naming_style.parameter.capitalization = camel_case
# rules
dotnet_naming_rule.user_types.symbols = user_types
dotnet_naming_rule.user_types.style = user_types
@@ -248,18 +238,14 @@ dotnet_naming_rule.interfaces.symbols = interface
dotnet_naming_rule.interfaces.style = interface_style
dotnet_naming_rule.interfaces.severity = suggestion
dotnet_naming_rule.public_members.symbols = public_symbols
dotnet_naming_rule.public_members.style = pascal_case
dotnet_naming_rule.public_members.severity = suggestion
dotnet_naming_rule.public_members_snake_case.symbols = public_symbols
dotnet_naming_rule.public_members_snake_case.style = snake_case
dotnet_naming_rule.public_members_snake_case.severity = suggestion
dotnet_naming_rule.private_members.symbols = private_symbols
dotnet_naming_rule.private_members.style = camel_case
dotnet_naming_rule.private_members.severity = suggestion
dotnet_naming_rule.private_members_snake_case.symbols = private_symbols
dotnet_naming_rule.private_members_snake_case.style = private_snake_case
dotnet_naming_rule.private_members_snake_case.severity = suggestion
dotnet_naming_rule.variables_are_camel_case.severity = suggestion
dotnet_naming_rule.variables_are_camel_case.symbols = local_symbols
dotnet_naming_rule.variables_are_camel_case.style = local
dotnet_naming_rule.parameters.symbols = parameter_symbols
dotnet_naming_rule.parameters.style = parameter
dotnet_naming_rule.parameters.symbols = parameters
dotnet_naming_rule.parameters.style = snake_case
dotnet_naming_rule.parameters.severity = suggestion
+1 -1
View File
@@ -1,6 +1,6 @@
[submodule "externals"]
path = externals
url = https://x-token-auth:ATCTT3xFfGN0h_ENnUSfEAIzbfVzkQPLG26cIli50MtcTwZ7VEc8lin5tJYqibjlBJ9PM9R642Ra5GjiL52TBfbO8KeUBT0UoMT7gLQHL2Y1TkW9yZr6NkvsoDkSl49DeD4Ijiu4_0u4qEm4oMVDDg2_fB059-uOWOo7ZL94tGZET8CSVpFIGvs=B0E03660@bitbucket.org/bluetradesys/externals.git
url = git@bitbucket.org:bluetradesys/externals.git
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
-49
View File
@@ -1,49 +0,0 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
# and commit this file to your remote git repository to share the goodness with others.
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
image: gitpod/workspace-dotnet-8
tasks:
- name: Install Deps
init: |
(
set -e
sudo install-packages rapidjson-dev
sudo install-packages libssh2-1-dev
sudo install-packages openssh-server
sudo install-packages liblttng-ust-dev
sudo install-packages libpcap0.8-dev
sudo install-packages ccache
sudo ln -s $(which ccache) /usr/local/bin/clang
sudo ln -s $(which ccache) /usr/local/bin/clang++
sudo ln -s $(which ccache) /usr/local/bin/clang-17
sudo ln -s $(which ccache) /usr/local/bin/clang++-17
sudo ln -s $(which ccache) /usr/local/bin/gcc
sudo ln -s $(which ccache) /usr/local/bin/g++
sudo ln -s $(which ccache) /usr/local/bin/gcc-12
sudo ln -s $(which ccache) /usr/local/bin/g++-12
if [ -e externals/src/emumba/install_emumba-deribit-connector.sh ]; then
externals/src/emumba/install_emumba-deribit-connector.sh
fi
)
- name: Setup Python venv
init: |
python -m pip install --upgrade pip
python -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
ports:
- name: sparkd config
port: 8000
onOpen: notify
- name: sparkd bus
port: 8001
gitConfig:
alias.st: "status"
status.short: "true"
core.pager: "less -FXRS"
+107 -87
View File
@@ -1,46 +1,42 @@
FROM ubuntu:latest
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=America/New_York
# Light Deps - should be quick to download/install.
# Longer running procs should be executed in individual RUNs.
# light deps (should be quick to download/install)
RUN apt-get update && apt-get install -y \
build-essential \
ca-certificates \
curl \
gnupg \
liblttng-ust-dev \
libpcap0.8-dev \
locales \
ninja-build \
software-properties-common \
sqlite3 \
tzdata
ca-certificates \
curl \
gnupg \
liblttng-ust-dev \
libpcap0.8-dev \
locales \
ninja-build \
software-properties-common \
sqlite3 \
tzdata
# Clang 17
RUN \
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key 2> /dev/null \
| gpg --dearmor - \
| tee /usr/share/keyrings/llvm-snapshot.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg] http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" \
| tee -a "/etc/apt/sources.list.d/llvm.list" \
&& apt-get update \
&& apt-get install -y \
clang-17 \
clang-format-17 \
clangd-17
# G++
# clang
RUN apt-get update && apt-get install -y \
g++
clang-13 \
clang-format-13 \
clangd-13 \
clang-15 \
clang-format-15 \
clangd-15
# Python
# g++
RUN apt-get update && apt-get install -y \
python3-pip \
python3-venv \
&& python3 -m pip install --upgrade pip
g++-11 \
g++-12
# Dotnet
# python
RUN apt-get update && apt-get install -y \
python3-pip \
python3-venv \
&& python3 -m pip install --upgrade pip
# dotnet
RUN \
curl -fsSLO "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" \
&& dpkg -i packages-microsoft-prod.deb \
@@ -48,65 +44,89 @@ RUN \
&& apt-get update \
&& apt-get install -y dotnet-sdk-8.0
# SSH
# ssh
RUN apt-get update && apt-get install -y \
libssh2-1-dev \
openssh-server
libssh2-1-dev \
openssh-server
# CMake
# cmake
RUN \
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 \
&& apt-get install -y cmake
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
# 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
&& 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
# Dev Utils
# 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 \
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
# dev utils
RUN apt-get update && apt-get install -y \
ccache \
fish \
gdb \
git-all \
sudo \
vim
fish \
git-all \
sudo \
vim \
wget
# Set up default executables (update-alternatives)
# set up default executables (update-alternatives)
RUN \
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-17 100; \
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 100; \
update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100; \
update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-17 100; \
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100; \
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100; \
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100; \
update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-15 100; \
update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-15 100; \
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100; \
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
# Add compiler symlinks for ccache
RUN \
ln -s /usr/bin/ccache /usr/local/bin/clang; \
ln -s /usr/bin/ccache /usr/local/bin/clang++; \
ln -s /usr/bin/ccache /usr/local/bin/clang-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang-15; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-15; \
ln -s /usr/bin/ccache /usr/local/bin/clang-17; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-17; \
ln -s /usr/bin/ccache /usr/local/bin/gcc; \
ln -s /usr/bin/ccache /usr/local/bin/g++; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-11; \
ln -s /usr/bin/ccache /usr/local/bin/g++-11; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-12; \
ln -s /usr/bin/ccache /usr/local/bin/g++-12
ln -s /usr/bin/ccache /usr/local/bin/clang; \
ln -s /usr/bin/ccache /usr/local/bin/clang++; \
ln -s /usr/bin/ccache /usr/local/bin/clang-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang-15; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-15; \
ln -s /usr/bin/ccache /usr/local/bin/gcc; \
ln -s /usr/bin/ccache /usr/local/bin/g++; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-11; \
ln -s /usr/bin/ccache /usr/local/bin/g++-11; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-12; \
ln -s /usr/bin/ccache /usr/local/bin/g++-12
RUN locale-gen en_US.UTF-8
@@ -115,11 +135,11 @@ ARG docker_uid="1000"
ARG docker_pass="bts"
RUN \
groupadd -g "$docker_uid" "$docker_user" \
&& useradd \
-m \
-u "$docker_uid" \
-g "$docker_uid" \
-G sudo \
--password "$(openssl passwd -1 "$docker_pass")" \
"$docker_user"
groupadd -g "$docker_uid" "$docker_user" \
&& useradd \
-m \
-u "$docker_uid" \
-g "$docker_uid" \
-G sudo \
--password "$(openssl passwd -1 "$docker_pass")" \
"$docker_user"
@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.2" />
<PackageReference Include="YaccLexTools" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YaccLexTools" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DataStreamCore\DataStreamCore.csproj" />
<ProjectReference Include="..\ExpressionCore\ExpressionCore.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
<ItemGroup Label="ColumnExpression">
<Scanner Include="ColumnExpressionTokens.lex" />
<Parser Include="ColumnExpressionGrammar.y" />
</ItemGroup>
</Project>
@@ -1,62 +0,0 @@
namespace Bts.ColumnExpressions;
public class ColumnExpression {
public ColumnExpression(string name, Type? type = null, object? value = null) {
Name = name;
Type = type
?? value?.GetType()
?? throw new ArgumentException(
"Type must be specified explicitly when value is null", nameof(type));
Value = value
?? DBNull.Value;
}
public string Name { get; }
public Type Type { get; }
public object Value { get; }
public static ColumnExpression Create<T>(string name, T value, Type? type = null) {
// Try to convert value to 'type' to verify they are compatible
// Note: Can't use `System.Type.IsAssignableFrom` because it doesn't
// account for implicit conversions (e.g., int -> long), just inheritance.
if (type is not null && !ConvertUtils.try_convert(type, value, out _)) {
throw new ArgumentException(
$"Type '{type}' is not assignable from value type '{value?.GetType()}'");
}
return new(name, type ?? typeof(T), value);
}
internal static Type ParseType(string typeId) {
return Candidates().ExceptNull().FirstOrDefault() ??
throw new ArgumentException($"Failed to parse Type from '{typeId}'", nameof(typeId));
IEnumerable<Type?> Candidates() {
if (typeId.Length == 0) {
yield break;
}
yield return Type.GetType(typeId);
yield return Type.GetType($"System.{typeId}");
if (typeId.Length > 1) {
yield return Type.GetType($"System.{char.ToUpper(typeId[0]) + typeId[1..]}");
}
yield return typeId.ToLower() switch {
"float64" or "double" or "real64" or "number" => typeof(double),
"float32" or "single" or "real32" or "real" or "float" => typeof(float),
"int16" or "short" or "integer16" or "signed16" => typeof(short),
"int32" or "int" or "integer" or "integer32" or "signed" => typeof(int),
"int64" or "long" or "integer64" or "signed64" => typeof(long),
"uint16" or "ushort" or "unsigned16" => typeof(ushort),
"uint32" or "uint" or "unsigned" => typeof(uint),
"uint64" or "ulong" or "unsigned64" => typeof(ulong),
"char" => typeof(char),
"byte" or "uint8" => typeof(byte),
"bool" or "boolean" => typeof(bool),
"string" or "text" => typeof(string),
"date" or "datetime" => typeof(DateTime),
_ => null
};
}
}
}
@@ -1,49 +0,0 @@
%namespace Bts.ColumnExpressions
%partial
%visibility internal
%tokentype ColumnExpressionToken
%parsertype ColumnExpressionParser
%union {
public int Integer;
public double Real;
public bool Boolean;
public string String;
public string Identifier;
public string TypeId;
public DateTime DateTime;
public Tuple<string, Type> ColumnDefinition;
public ColumnExpression ColumnExpression;
public ImmutableList<ColumnExpression> ColumnExpressions;
}
%token BOOLEAN_CONSTANT, INTEGER_CONSTANT, REAL_CONSTANT, DATE_TIME_CONSTANT, STRING_CONSTANT
%token IDENTIFIER, EQUALS, TYPE_ID, OF_TYPE
%nonassoc EQUALS
%type <ColumnDefinition> ColumnDefinition
%type <ColumnExpression> ColumnExpression
%type <ColumnExpressions> ColumnExpressions
%%
ColumnExpressions :
ColumnExpression { $$ = ImmutableList.CreateRange(new[] { $1 }); }
| ColumnExpressions ColumnExpression { $$ = $1.Add($2); }
;
ColumnExpression :
ColumnDefinition EQUALS INTEGER_CONSTANT { $$ = ColumnExpression.Create($1.Item1, $3.Integer, $1.Item2); }
| ColumnDefinition EQUALS REAL_CONSTANT { $$ = ColumnExpression.Create($1.Item1, $3.Real, $1.Item2); }
| ColumnDefinition EQUALS DATE_TIME_CONSTANT { $$ = ColumnExpression.Create($1.Item1, $3.DateTime, $1.Item2); }
| ColumnDefinition EQUALS BOOLEAN_CONSTANT { $$ = ColumnExpression.Create($1.Item1, $3.Boolean, $1.Item2); }
| ColumnDefinition EQUALS STRING_CONSTANT { $$ = ColumnExpression.Create($1.Item1, $3.String, $1.Item2); }
| ColumnDefinition { $$ = new($1.Item1, $1.Item2); }
;
ColumnDefinition :
IDENTIFIER { $$ = new($1.Identifier, null); }
| IDENTIFIER OF_TYPE IDENTIFIER { $$ = new($1.Identifier, ColumnExpression.ParseType($3.Identifier)); }
| IDENTIFIER OF_TYPE TYPE_ID { $$ = new($1.Identifier, ColumnExpression.ParseType($3.TypeId)); }
;
@@ -1,18 +0,0 @@
namespace Bts.ColumnExpressions;
using System.IO;
using System.Text;
public class ColumnExpressionInterpreter {
public static IList<ColumnExpression> Evaluate(Stream stream) {
var parser = new ColumnExpressionParser();
var res = parser.Parse(stream);
return res?.ColumnExpressions ?? ImmutableList<ColumnExpression>.Empty.Add(
res?.ColumnExpression ?? throw new Exception("Failed to parse column(s) expression"));
}
public static IList<ColumnExpression> Evaluate(string str) {
using var ms = new MemoryStream(Encoding.Default.GetBytes(str));
return Evaluate(ms);
}
}
@@ -1,18 +0,0 @@
namespace Bts.ColumnExpressions;
using System.Text;
partial class ColumnExpressionParser {
public ColumnExpressionParser() : base(null) { }
public ValueType? Parse(string s) {
using var ms = new MemoryStream(Encoding.Default.GetBytes(s));
return Parse(ms);
}
public ValueType? Parse(Stream stream) {
Scanner = new ColumnExpressionScanner(stream);
Parse();
return CurrentSemanticValue;
}
}
@@ -1,9 +0,0 @@
namespace Bts.ColumnExpressions;
partial class ColumnExpressionScanner {
public override void yyerror(string format, params object[] args) {
base.yyerror(format, args);
Console.WriteLine(format, args);
Console.WriteLine();
}
}
@@ -1,85 +0,0 @@
%namespace Bts.ColumnExpressions
%scannertype ColumnExpressionScanner
%visibility internal
%tokentype ColumnExpressionToken
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers
/* Literal Constants */
Letter [a-zA-Z]
BooleanConstant true|false
StringConstant '([^\'\\]*|\\.)*'
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]+
/* Relational Operators */
Equals =
OfType /
Identifier {Letter}({Letter}*[0-9]*_*)*
NamespacePrefix {Identifier}\.
TypeId {NamespacePrefix}*{Identifier}
/* Utility Tokens */
OpenParenthesis \(
CloseParenthesis \)
Comma ,
QuoteOpen \[
QuoteClose \]
/* Comments */
LineComment \/\/[^\n\r]*(\n|\r|\r\n)
%%
{Equals} {
return (int)ColumnExpressionToken.EQUALS;
}
{OfType} {
return (int)ColumnExpressionToken.OF_TYPE;
}
{StringConstant} {
yylval.String = yytext.Substring(1, yytext.Length - 2).Replace(@"\'","'");
return (int)ColumnExpressionToken.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)ColumnExpressionToken.BOOLEAN_CONSTANT;
}
{DateTimeConstant} {
yylval.DateTime = DateTime.Parse(yytext);
return (int)ColumnExpressionToken.DATE_TIME_CONSTANT;
}
{IntegerConstant} {
yylval.Integer = int.Parse(yytext);
return (int)ColumnExpressionToken.INTEGER_CONSTANT;
}
{RealConstant} {
yylval.Real = double.Parse(yytext);
return (int)ColumnExpressionToken.REAL_CONSTANT;
}
{Identifier} {
yylval.Identifier = yytext;
return (int)ColumnExpressionToken.IDENTIFIER;
}
{TypeId} {
yylval.TypeId = yytext;
return (int)ColumnExpressionToken.TYPE_ID;
}
%%
@@ -1,720 +0,0 @@
// ==============================================================
// <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
}
@@ -1,884 +0,0 @@
// ==============================================================
// <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; }
}
}
@@ -1,4 +0,0 @@
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using System.Collections.Immutable;
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YaccLexTools" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DataStreamCore\DataStreamCore.csproj" />
<ProjectReference Include="..\ExpressionCore\ExpressionCore.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
</ItemGroup>
<ItemGroup Label="ColumnSortExpression">
<Scanner Include="ColumnSortExpressionTokens.lex" />
<Parser Include="ColumnSortExpressionGrammar.y" />
</ItemGroup>
</Project>
@@ -1,11 +0,0 @@
namespace Bts.ColumnSortExpressions;
public class ColumnSortExpression {
public ColumnSortExpression(string name, bool isAscending = true) {
Name = name;
IsAscending = isAscending;
}
public string Name { get; }
public bool IsAscending { get; }
}
@@ -1,29 +0,0 @@
%namespace Bts.ColumnSortExpressions
%partial
%visibility internal
%tokentype ColumnSortExpressionToken
%parsertype ColumnSortExpressionParser
%union {
public string Identifier;
public ColumnSortExpression Column;
public ImmutableList<ColumnSortExpression> Columns;
}
%token ASCENDING, DESCENDING, IDENTIFIER
%type <Column> Column
%type <Columns> Columns
%%
Columns :
Column { $$ = ImmutableList.CreateRange(new[] { $1 }); }
| Columns Column { $$ = $1.Add($2); }
;
Column :
IDENTIFIER { $$ = new($1.Identifier, true); }
| ASCENDING IDENTIFIER { $$ = new($2.Identifier, true); }
| DESCENDING IDENTIFIER { $$ = new($2.Identifier, false); }
;
@@ -1,18 +0,0 @@
namespace Bts.ColumnSortExpressions;
using System.IO;
using System.Text;
public class ColumnSortExpressionInterpreter {
public static IList<ColumnSortExpression> Evaluate(Stream stream) {
var parser = new ColumnSortExpressionParser();
var res = parser.Parse(stream);
return res?.Columns ?? ImmutableList<ColumnSortExpression>.Empty.Add(
res?.Column ?? throw new Exception("Failed to parse column(s) expression"));
}
public static IList<ColumnSortExpression> Evaluate(string str) {
using var ms = new MemoryStream(Encoding.Default.GetBytes(str));
return Evaluate(ms);
}
}
@@ -1,18 +0,0 @@
namespace Bts.ColumnSortExpressions;
using System.Text;
partial class ColumnSortExpressionParser {
public ColumnSortExpressionParser() : base(null) { }
public ValueType? Parse(string s) {
using var ms = new MemoryStream(Encoding.Default.GetBytes(s));
return Parse(ms);
}
public ValueType? Parse(Stream stream) {
Scanner = new ColumnSortExpressionScanner(stream);
Parse();
return CurrentSemanticValue;
}
}
@@ -1,9 +0,0 @@
namespace Bts.ColumnSortExpressions;
partial class ColumnSortExpressionScanner {
public override void yyerror(string format, params object[] args) {
base.yyerror(format, args);
Console.WriteLine(format, args);
Console.WriteLine();
}
}
@@ -1,30 +0,0 @@
%namespace Bts.ColumnSortExpressions
%scannertype ColumnSortExpressionScanner
%visibility internal
%tokentype ColumnSortExpressionToken
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers
/* Literal Constants */
Letter [a-zA-Z]
Identifier {Letter}({Letter}*[0-9]*_*)*
Ascending +
Descending -
%%
{Ascending} {
return (int)ColumnSortExpressionToken.ASCENDING;
}
{Descending} {
return (int)ColumnSortExpressionToken.DESCENDING;
}
{Identifier} {
yylval.Identifier = yytext;
return (int)ColumnSortExpressionToken.IDENTIFIER;
}
%%
@@ -1,720 +0,0 @@
// ==============================================================
// <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
}
@@ -1,884 +0,0 @@
// ==============================================================
// <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; }
}
}
@@ -1,4 +0,0 @@
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using System.Collections.Immutable;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,301 +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 Spark.Core.Json;
using System.Buffers;
using System.Reactive.Subjects;
using System.Text;
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(JsonSerializerOptions? serializerOpts = null) :
ISubject<WebsocketConnectionUpdate, IEnumerable<WebsocketOutgoingMessage>> {
protected readonly object lockObj = new();
readonly JsonSerializerOptions serializerOpts = serializerOpts ?? JsonSerializerOptions.Default;
readonly Dictionary<WebsocketClientID, List<ClientSubscription>> clients = [];
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;
}
}
static string getRpcNotification(DataPacket dp) {
using (var buffer = new MemoryStream())
using (var writer = new Utf8JsonWriter(buffer))
using (writer.WriteObject()) {
writer.WriteString("method", "subscription");
using (writer.WriteObject("params")) {
writer.Write("tableName", dp.schema.tableName);
writer.Write("columns", dp.schema.columnInfo);
var cellJsonFactories = dp.schema.columnInfo.Select(getCellJsonFactory).ToList();
using (writer.WriteArray("added")) {
foreach (IBlueRow row in (dp.addedRows?.Cast<IBlueRow>()).empty_if_null()) {
using (writer.WriteObject()) {
for (int i = 0; i < cellJsonFactories.Count; i++) {
writer.Write(dp.schema.columnInfo[i].colName, cellJsonFactories[i](row));
}
}
}
}
using (writer.WriteArray("deleted")) {
foreach (IBlueRow row in (dp.deletedRows?.Cast<IBlueRow>()).empty_if_null()) {
using (writer.WriteObject()) {
for (int i = 0; i < cellJsonFactories.Count; i++) {
writer.Write(dp.schema.columnInfo[i].colName, cellJsonFactories[i](row));
}
}
}
}
}
writer.WriteString("jsonrpc", "2.0");
writer.Flush();
return Encoding.UTF8.GetString(buffer.ToArray());
}
}
static Func<IBlueRow, JsonNode?> getCellJsonFactory(ColumnInfo colInfo) {
var t = colInfo.colType;
if (t == typeof(int)) {
return NullCheckWrapper(v => (int)v);
}
if (t == typeof(uint)) {
return NullCheckWrapper(v => (uint)v);
}
if (t == typeof(long)) {
return NullCheckWrapper(v => (long)v);
}
if (t == typeof(ulong)) {
return NullCheckWrapper(v => (ulong)v);
}
if (t == typeof(short)) {
return NullCheckWrapper(v => (short)v);
}
if (t == typeof(ushort)) {
return NullCheckWrapper(v => (ushort)v);
}
if (t == typeof(double)) {
return NullCheckWrapper(v => (double)v);
}
if (t == typeof(float)) {
return NullCheckWrapper(v => (float)v);
}
if (t == typeof(bool)) {
return NullCheckWrapper(v => (bool)v);
}
return NullCheckWrapper(v => (string)v);
Func<IBlueRow, JsonNode?> NullCheckWrapper(Func<object, JsonNode> toJson) =>
r => {
var obj = r[colInfo.colNo];
if (obj is null) {
return null;
}
return toJson(obj);
};
}
void onConnect(WebsocketConnectNotification notification) {
var id = notification.Id;
lock (lockObj) {
clients.Add(id, []);
}
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 "publish":
Log.Api.error("DataStreamWebsocket api 'publish' not implemented.");
// TODO
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 sendResponse<T>(WebsocketClientID id, JsonRpcId rpcId, T rpcParams) {
var rpcRes = JsonRpcResponse.Create(rpcId, rpcParams);
var rpcResStr = JsonSerializer.Serialize(rpcRes, serializerOpts);
send([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(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 notif = getRpcNotification(dp);
send([new(id, notif)]);
}
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);
}
}
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);
}
}
@@ -1,48 +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)}]");
}
}
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)}]");
}
}
@@ -1,27 +1,27 @@
namespace Bts.DataStream.Websocket;
using bts;
using Bts.Websocket;
using Google.Protobuf;
using System.Text.Json;
public static class Session {
static int sessionIdCounter;
public static async Task Start(
WebsocketServerConfig config, JsonSerializerOptions? opts) {
opts ??= JsonSerializerOptions.Default;
var sessionId = Interlocked.Increment(ref sessionIdCounter);
var gateway = new WebsocketGatewayObservable(config);
var sync = new DataSync(opts);
using (gateway.Subscribe(sync))
using (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();
}
}
}
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();
}
}
@@ -1,110 +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();
}
}
}
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();
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultContentItems>True</EnableDefaultContentItems>
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -39,7 +39,7 @@ static async Task run(
Console.WriteLine("Starting DataStreamWebsocketServer...");
var serviceCtx = ServiceContext.create(sparkdConfigSource);
await serviceCtx.login(user, password);
await Session.Start(wssConfig, opts);
await Session.Start(serviceCtx, wssConfig, opts);
// if (sparkdConfigSource is not null && sparkdConfigSource.Length > 9999) {
// } else {
// await ChatGptSession.Start();
@@ -9,8 +9,7 @@
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-use-websocket": "^4.5.0"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
@@ -3326,15 +3325,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-use-websocket": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.5.0.tgz",
"integrity": "sha512-oxYVLWM3Lv0InCfjW7hG/Hk0hkE0P1SiLd5/I3d5x0W4riAnDUkD4VEu7qNVAqxNjBF3nU7k0jLMOetLXpwfsA==",
"peerDependencies": {
"react": ">= 18.0.0",
"react-dom": ">= 18.0.0"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
@@ -11,8 +11,7 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-use-websocket": "^4.5.0"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
@@ -21,24 +21,3 @@ th, td {
background-color: var(--cell-background-color);
color: var(--cell-color);
}
textarea#query-input {
height: 5em;
width: 50em;
}
button#query-button {
height: 5em;
vertical-align: top;
}
table#query-output {
float: none;
clear: both;
}
p#footer {
float: none;
clear: both;
vertical-align: bottom;
}
@@ -1,28 +1,75 @@
import { useEffect, useState, Component } from "react";
import { useWebSocket } from "react-use-websocket";
import "./App.css";
import { useEffect, useState, Component } from 'react';
import './App.css';
function App() {
const [forecasts, setForecasts] = useState();
useEffect(() => {
populateWeatherData();
}, []);
const contents = forecasts === undefined
? <p><em>Loading... Please refresh once the ASP.NET backend has started. See <a href="https://aka.ms/jspsintegrationreact">https://aka.ms/jspsintegrationreact</a> for more details.</em></p>
: <table className="table table-striped" aria-labelledby="tabelLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{forecasts.map(forecast =>
<tr key={forecast.date}>
<td>{forecast.date}</td>
<td>{forecast.temperatureC}</td>
<td>{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
)}
</tbody>
</table>;
return (
<div>
<h1 id="tabelLabel">Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
async function populateWeatherData() {
const response = await fetch('weatherforecast');
const data = await response.json();
setForecasts(data);
}
}
export default App;
export class Home extends Component {
static displayName = Home.name;
// static sparkdHost = '192.168.50.94';
static sparkdHost = "localhost";
static sparkdHost = 'localhost';
// static websocket_url = 'wss://markets.api.uat.cmegroup.com/marketdatastream/v1';
// static access_token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjF0al9MTVFrT2pGVzJWQTZvdkZzOEZHUWViMCIsInBpLmF0bSI6IjEifQ.eyJzY29wZSI6W10sImNsaWVudF9pZCI6ImFwaV90aGlua2FscGhhMzIxIiwiYXVkIjoiTnIiLCJqdGkiOiIweERtb3NnU2NHcE5GUEFaIiwic3ViIjoiYXBpX3RoaW5rYWxwaGEzMjEiLCJleHAiOjE2OTgzNTY5OTJ9.uJJEhWpujGiyJ5_lypiIPB8xR0GkuXqydtCugp5xD0hyTuX6VkQdNoY_oaRXXGRCUPCvfsXpU-HfL7pRkcXCjMLTrVIqf3RjD4_gar_ExbIb180nbYMG87E4rOILi5bIin51KOcMNBdHW7pTKwsS221XGyIAKIj65WxD-K8QSVr1HN6s7KDfYdez5dstWWC6xGg_3IZkrJjx8Df1HNT73K9PwUaAl7gcK4_2RSr8L636QbF3_y4Tv26g5IEv90Mc1mnAFPvUGI-nDR4s5Vso0398DlYl_tw-zbQQmn-QuQ6rS_ZIcZKgyoHKwD-3lgV-J1_--ndHdh8qwJC7Hk8ZKg'
render() {
let socket = null;
const wsUrl = `ws://localhost:5268/ws`;
function connectToSparkd() {
const wsUrl = `ws://${Home.sparkdHost}:8080`;
// const wsUrl = Home.websocket_url;
if (socket == null) {
socket = new WebSocket(wsUrl);
socket = new WebSocket(wsUrl)
}
if (socket && socket.readyState === WebSocket.OPEN) {
return;
}
const output = document.getElementById("query-output");
const output = document.getElementById('query-output');
socket.onopen = (event) => {
console.log(`Connected to ${wsUrl}`);
};
@@ -52,16 +99,31 @@ export class Home extends Component {
connectToSparkd();
}
if (socket == null || !socket) {
console.log("Failed to connect to websocket server");
console.log('Failed to connect to websocket server');
return;
}
if (socket.readyState === WebSocket.OPEN) {
const queryInput = document.getElementById("query-input");
const queryInput = document.getElementById('query-input');
// const req = {
// header: {
// messageType: "subscribe",
// sentTime: "",
// version: "",
// requestId: "1",
// token: ""
// },
// payload: {
// subscriptionMessageTypes: ["TRD"],
// subscriptions: [{
// exchangeMic: "XCME", productType: "FUT", productCode: "BTC"
// }]
// }
// };
const rpcReq = {
jsonrpc: "2.0",
method: "subscribe",
jsonrpc: '2.0',
method: 'subscribe',
params: {
query: queryInput.value,
query: queryInput.value
},
id: 1,
};
@@ -74,19 +136,19 @@ export class Home extends Component {
<h1>Data Stream Query Analyzer</h1>
<p>Run queries against the BTS Cloud</p>
<div>
<textarea id="query-input" placeholder="Enter Query"></textarea>
<button id="query-button" onClick={executeQuery}>
Execute
</button>
<textarea id="query-input" placeholder='Enter Query'></textarea>
</div>
<table id="query-output"></table>
<p id="footer">
The <code>ClientApp</code> subdirectory is a standard React
application based on the <code>create-react-app</code> template. If
you open a command prompt in that directory, you can run{" "}
<code>npm</code> commands such as <code>npm test</code> or{" "}
<code>npm install</code>.
</p>
<div>
<button id="query-button" onClick={executeQuery}>Execute</button>
</div>
<table id="query-output">
</table>
<ul>
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
<li><strong>Development server integration</strong>. In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files.</li>
</ul>
<p>The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>npm</code> commands such as <code>npm test</code> or <code>npm install</code>.</p>
</div>
);
}
@@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {Home} from './App.jsx'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Home />
<App />
</React.StrictMode>,
)
@@ -11,11 +11,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
<Version>8.*-*</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
@@ -25,11 +23,6 @@
</ProjectReference>
<ProjectReference Include="..\DataStream\DataStream.csproj" />
<ProjectReference Include="..\SharedStreams\SharedStreams.csproj" />
<ProjectReference Include="..\..\client\spark_api\spark_api.csproj" />
<ProjectReference Include="..\..\client\spark_websocket\spark_websocket.csproj" />
</ItemGroup>
<!-- <ItemGroup>
<Content Update="wwwroot\alt-index.html" CopyToPublishDirectory="Never" />
</ItemGroup> -->
</Project>
@@ -1,117 +1,25 @@
namespace Bts.DataStreamWebApp.Server.Controllers;
using BCG;
using bts.api.core;
using Bts.DataStreamExpressions;
using Microsoft.AspNetCore.Mvc;
using Spark.Core.Disposables;
[ApiController]
[Route("[controller]")]
public class DataStreamSubscriptionController : ControllerBase {
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
private readonly ILogger<DataStreamSubscriptionController> _logger;
// [Route("[controller]")]
[Route("/ws")]
public class DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger) : ControllerBase {
readonly ILogger<DataStreamSubscriptionController> _logger = logger;
IDisposable? streamSubscription;
[Route("/ws")]
public async Task Get() {
if (!HttpContext.WebSockets.IsWebSocketRequest) {
_logger.LogError("Expected websocket request on path '{}'", HttpContext.Request.Path);
return;
}
Console.WriteLine("Establishing websocket connection...");
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
Console.WriteLine($"Websocket connection established.");
await runWebSocketServer(null, "bts", string.Empty, webSocket);
public DataStreamSubscriptionController(ILogger<DataStreamSubscriptionController> logger) {
_logger = logger;
}
public static async Task Echo(WebSocket webSocket) {
var buffer = new byte[1024 * 4];
var receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
while (!receiveResult.CloseStatus.HasValue) {
await webSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
receiveResult.MessageType,
receiveResult.EndOfMessage,
CancellationToken.None);
receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(
receiveResult.CloseStatus.Value,
receiveResult.CloseStatusDescription,
CancellationToken.None);
}
async Task<int> runWebSocketServer(string? sparkd, string user, string password, WebSocket webSocket) {
var context = ServiceContext.create(sparkd);
await context.login(user, password);
var buffer = new byte[1024 * 4];
var receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
try {
while (!receiveResult.CloseStatus.HasValue) {
receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
var msg = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
// var queryArgs = new QueryArguments { Query = msg };
var rpcReq = JsonSerializer.Deserialize<Bts.Websocket.JsonRpcRequest<DataStreamSubscription>>(msg);
var queryArgs = rpcReq!.Params;
// if (queryArgs is null) {
// Console.WriteLine("Invalid queryArgs.");
// continue;
// }
var exp = DataStreamExpressionsInterpreter.Evaluate(queryArgs.Query!);
if (exp is null) {
Console.WriteLine($"Invalid query expression: {queryArgs.Query}.");
continue;
}
var vs = exp.getView(new Dictionary<string, StatementExpression>(0));
Disposable.dispose(ref streamSubscription);
vs.onChange += onStreamChanged;
streamSubscription = Disposable.Create(() => vs.onChange -= onStreamChanged);
// await webSocket.SendAsync(
// new ArraySegment<byte>(buffer, 0, receiveResult.Count),
// receiveResult.MessageType,
// receiveResult.EndOfMessage,
// CancellationToken.None);
}
} catch (Exception ex) {
Console.WriteLine($"Exception on WebsocketServer thread: {ex}");
return 1;
}
return 0;
async void onStreamChanged(object newValue) {
if (newValue is not DataPacket dp) {
Console.WriteLine(
$"OnStreamChanged delegate expected DataPacket (actually received {newValue?.GetType()}.");
return;
}
Console.WriteLine("Received stream update.");
var added_rows_str = dp.addedRows.get_printed_rows(val => val.Length);
var msg = $"Received new rows:\n{added_rows_str}";
// var payload = new ReadOnlyMemory<byte>([.. msg]);
var encoded = Encoding.UTF8.GetBytes(msg);
var payload = new ArraySegment<byte>(encoded, 0, encoded.Length);
await webSocket.SendAsync(
payload,
receiveResult.MessageType,
receiveResult.EndOfMessage,
CancellationToken.None);
}
[HttpGet(Name = "GetDataStreamSubscription")]
public IEnumerable<DataStreamSubscription> Get() {
return Enumerable.Range(1, 5).Select(index => new DataStreamSubscription {
Query = "fake queronious"
})
.ToArray();
}
}
@@ -3,16 +3,25 @@ using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase {
static readonly string[] Summaries = [
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
readonly ILogger<WeatherForecastController> _logger = logger;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get() {
return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
@@ -1,8 +1,5 @@
namespace Bts.DataStreamWebApp.Server;
using System.Text.Json.Serialization;
public class DataStreamSubscription {
[JsonPropertyName("query")]
public string? Query { get; set; }
}
@@ -1,8 +0,0 @@
namespace Bts.DataStreamWebApp.Server;
using BCG;
class DataStreamUpdate {
public List<IBlueRow> addedRows { get; set; } = new(0);
public List<IBlueRow> deletedRows { get; set; } = new(0);
}
@@ -1,14 +0,0 @@
namespace Bts.DataStreamWebApp.Server;
using CommandLine;
class DataStreamWebAppServerOptions {
[Option("sparkd")]
public string? sparkd { get; set; }
[Option("user", Default = "bts")]
public string? user { get; set; }
[Option("password", Default = "")]
public string? password { get; set; }
}
@@ -1,58 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace Bts.DataStreamWebApp.Server {
// public class DataStreamWebSocketHandler {
// readonly WebSocketConnectionManager _connectionManager = new();
// readonly ILogger<DataStreamWebSocketHandler> _logger;
// public DataStreamWebSocketHandler(
// WebSocketConnectionManager connectionManager,
// ILogger<DataStreamWebSocketHandler> logger
// ) {
// _connectionManager = connectionManager;
// _logger = logger;
// }
// public async Task HandleWebSocket(HttpContext context, WebSocket webSocket) {
// var socketId = _connectionManager.AddSocket(webSocket);
// _logger.LogInformation($"WebSocket connection established with ID {socketId}");
// while (webSocket.State == WebSocketState.Open) {
// var message = await ReceiveMessageAsync(webSocket);
// if (message != null) {
// _logger.LogInformation($"Received message from ID {socketId}: {message}");
// await BroadcastMessageAsync(message);
// }
// }
// _connectionManager.RemoveSocket(socketId);
// _logger.LogInformation($"WebSocket connection closed with ID {socketId}");
// }
// async Task<string?> ReceiveMessageAsync(WebSocket webSocket) {
// var buffer = new byte[1024];
// var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
// if (result.CloseStatus.HasValue) {
// return null;
// }
// return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
// }
// async Task BroadcastMessageAsync(string message) {
// foreach (var socket in _connectionManager.GetAllSockets()) {
// if (socket.Value.State == WebSocketState.Open) {
// await socket.Value.SendAsync(System.Text.Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, CancellationToken.None);
// }
// }
// }
// }
}
@@ -1,166 +1,30 @@
using BCG;
var builder = WebApplication.CreateBuilder(args);
using bts;
using bts.api.core;
using bts.utils;
// Add services to the container.
using Bts.DataStreamExpressions;
using Bts.DataStreamWebApp.Server;
using Bts.DataStreamWebApp.Server.Controllers;
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
using CommandLine;
var app = builder.Build();
using mbts.config;
app.UseDefaultFiles();
app.UseStaticFiles();
using System.Net.WebSockets;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
static string fmtReq(HttpRequest req) =>
$"{req.Protocol}://{req.Host}/{req.Path}?{req.QueryString}";
static string fmtDataPacket(DataPacket dp) {
var sb = new StringBuilder();
var rows = dp.addedRows.Cast<IBlueRow>().ToList();
var colWidths = dp.schema.columnInfo.Select(c => rows.Max(r => r[c.colNo]?.ToString()?.Length ?? 0)).ToArray();
foreach (IBlueRow r in dp.addedRows) {
for (int i = 0; i < colWidths.Length; i++) {
var str = r[i]?.ToString() ?? string.Empty;
if (i > 0) {
sb.Append(" | ");
}
sb.Append(str);
var width = colWidths[i];
if (str.Length < width) {
sb.Append(new string(' ', width - str.Length));
}
}
sb.AppendLine();
}
return sb.ToString();
// var upd = new DataStreamUpdate();
// upd.addedRows.AddRange(dp.addedRows.Cast<IBlueRow>());
// upd.deletedRows.AddRange(dp.deletedRows.Cast<IBlueRow>());
// return fmtDataStreamUpdate(upd);
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
static string fmtDataStreamUpdate(DataStreamUpdate rows) {
return JsonSerializer.Serialize(rows);
}
app.UseHttpsRedirection();
var parser = new Parser(ps => {
ps.CaseInsensitiveEnumValues = true;
ps.HelpWriter = Console.Out;
});
app.UseAuthorization();
await parser.ParseArguments<DataStreamWebAppServerOptions>(args).
MapResult(run, errors => Task.FromResult(1));
app.MapControllers();
async Task<int> run(DataStreamWebAppServerOptions opts) {
BTSNativeMethods.provide_command_line(args, args.Length);
Configuration.initialize();
var context = ServiceContext.create(opts.sparkd);
await context.login(opts.user!, opts.password!);
var vsWorkLoop = Observable.Interval(TimeSpan.FromMilliseconds(200)).Subscribe(_ => {
ViewStreamDatabase.instance.doWork();
MasterWorker.getInstance(false).doWork();
});
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
var wsOpts = new WebSocketOptions {
KeepAliveInterval = TimeSpan.FromMinutes(2)
};
app.UseWebSockets(wsOpts);
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapFallbackToFile("/index.html");
app.MapFallbackToFile("/index.html");
app.Use(async (context, next) => {
var reqDesc = fmtReq(context.Request);
Console.WriteLine($"Webserver received: {reqDesc}");
if (context.Request.Path == "/ws") {
if (context.WebSockets.IsWebSocketRequest) {
Console.WriteLine("Accepting websocket connection...");
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
Console.WriteLine("Websocket connection accepted.");
await runWebSocketServer(
opts.sparkd, opts.user!, opts.password!, webSocket);
} else {
context.Response.StatusCode = StatusCodes.Status400BadRequest;
}
} else {
Console.WriteLine($"Webserver ignoring req: {reqDesc}");
await next(context);
}
});
try {
await app.RunAsync();
} catch (Exception ex) {
Console.WriteLine($"Exception on DataStreamWebsocketServer thread: {ex}");
return 1;
}
Spark.Core.Disposables.Disposable.dispose(ref vsWorkLoop);
return 0;
}
async Task<int> runWebSocketServer(string? sparkd, string user, string password, WebSocket webSocket) {
var buffer = new byte[1024 * 4];
var receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
try {
while (!receiveResult.CloseStatus.HasValue) {
Console.WriteLine("Awaiting websocket message from client");
receiveResult = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
var msg = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
var rpcReq = JsonSerializer.Deserialize<Bts.Websocket.JsonRpcRequest<DataStreamSubscription>>(msg);
var queryArgs = rpcReq!.Params!;
// var queryArgs = new DataStreamSubscription { Query = msg };
var exp = DataStreamExpressionsInterpreter.Evaluate(queryArgs.Query!);
if (exp is null) {
Console.WriteLine($"Invalid query expression: {queryArgs.Query}.");
continue;
}
var vs = exp.getView(new Dictionary<string, StatementExpression>(0));
Console.WriteLine("Subscribing to stream...");
vs.onChange += onStreamChanged;
await webSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
receiveResult.MessageType,
receiveResult.EndOfMessage,
CancellationToken.None);
}
} catch (Exception ex) {
Console.WriteLine($"Exception on WebsocketServer thread: {ex}");
return 1;
}
return 0;
async void onStreamChanged(object newValue) {
if (newValue is not DataPacket dp) {
Console.WriteLine(
$"OnStreamChanged delegate expected DataPacket (actually received {newValue?.GetType()}.");
return;
}
Console.WriteLine("Received stream update.");
var sb = new StringBuilder();
var updateDesc = fmtDataPacket(dp);
var msg = $"Received new rows:\n{updateDesc}";
// var payload = new ReadOnlyMemory<byte>([.. msg]);
var encoded = Encoding.UTF8.GetBytes(msg);
var payload = new ArraySegment<byte>(encoded, 0, encoded.Length);
await webSocket.SendAsync(
payload,
receiveResult.MessageType,
receiveResult.EndOfMessage,
CancellationToken.None);
}
}
app.Run();
@@ -1,10 +1,5 @@
{
"profiles": {
"default": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5400"
},
"http": {
"commandName": "Project",
"launchBrowser": true,
@@ -45,4 +40,4 @@
"sslPort": 44383
}
}
}
}
@@ -1,37 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Net.WebSockets;
namespace Bts.DataStreamWebApp.Server {
// public class WebSocketConnectionManager {
// private readonly ConcurrentDictionary<Guid, WebSocket> _sockets = new ConcurrentDictionary<Guid, WebSocket>();
// public Guid AddSocket(WebSocket socket) {
// var socketId = Guid.NewGuid();
// _sockets.TryAdd(socketId, socket);
// return socketId;
// }
// public WebSocket? GetSocket(Guid socketId) {
// _sockets.TryGetValue(socketId, out var socket);
// return socket;
// }
// public ConcurrentDictionary<Guid, WebSocket>.ValueCollection GetAllSockets() {
// return _sockets.Values;
// }
// public Guid? GetSocketId(WebSocket socket) {
// foreach (var (key, value) in _sockets) {
// if (value == socket) {
// return key;
// }
// }
// return null;
// }
// public void RemoveSocket(Guid socketId) {
// _sockets.TryRemove(socketId, out _);
// }
// }
}
@@ -1,168 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table {
border: 0
}
.commslog-data {
font-family: Consolas, Courier New, Courier, monospace;
}
.commslog-server {
background-color: red;
color: white
}
.commslog-client {
background-color: green;
color: white
}
</style>
</head>
<body>
<h1>ALT WebSocket Sample Application</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Message to send:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">From</td>
<td style="width: 100px">To</td>
<td>Data</td>
</tr>
</thead>
<tbody id="commsLog">
</tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
function updateState() {
function disable() {
sendMessage.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
function enable() {
sendMessage.disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
}
connectionUrl.disabled = true;
connectButton.disabled = true;
if (!socket) {
disable();
} else {
switch (socket.readyState) {
case WebSocket.CLOSED:
stateLabel.innerHTML = "Closed";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "Closing...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "Connecting...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "Open";
enable();
break;
default:
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
socket.close(1000, "Closing from client");
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
var data = sendMessage.value;
socket.send(data);
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
};
connectButton.onclick = function() {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection opened</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,176 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table {
border: 0
}
.commslog-data {
font-family: Consolas, Courier New, Courier, monospace;
}
.commslog-server {
background-color: red;
color: white
}
.commslog-client {
background-color: green;
color: white
}
</style>
</head>
<body>
<h1>DataStreams via WebSocket</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Query:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">From</td>
<td style="width: 100px">To</td>
<td>Data</td>
</tr>
</thead>
<tbody id="commsLog">
</tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
function updateState() {
function disable() {
sendMessage.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
function enable() {
sendMessage.disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
}
connectionUrl.disabled = true;
connectButton.disabled = true;
if (!socket) {
disable();
} else {
switch (socket.readyState) {
case WebSocket.CLOSED:
stateLabel.innerHTML = "Closed";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "Closing...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "Connecting...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "Open";
enable();
break;
default:
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
socket.close(1000, "Closing from client");
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
const rpcReq = {
jsonrpc: "2.0",
method: "subscribe",
params: {
query: sendMessage.value,
},
id: 1,
};
const rpcReqStr = JSON.stringify(rpcReq);
socket.send(rpcReqStr);
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-data">' + htmlEscape(rpcReqStr) + '</td></tr>';
};
connectButton.onclick = function() {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection opened</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
</script>
</body>
</html>
@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.2" />
<PackageReference Include="YaccLexTools" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
File diff suppressed because it is too large. Load diff
@@ -7,7 +7,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,6 +1,4 @@
// See https://aka.ms/new-console-template for more information
using BCG;
using DataStream.Console;
using ExpressionTests;
@@ -9,43 +7,26 @@ using Spark.Core.Collections;
using System.Reactive.Disposables;
var schema = new BlueTableSchema([
new("id", 0, typeof(int)),
new("name", 1, typeof(string)),
new("value", 2, typeof(int)),
],
"MyTable"
);
var rows = new BlueTable(schema);
var mbr = new MutableBlueRow(schema);
mbr.setValue("id", 1);
mbr.setValue("name", "Jerry");
mbr.setValue("value", 13);
rows.addRow(mbr);
var serializer = new UniversalSerializer(schema);
serializer.serialize(StreamOpCode.create, new[] { rows }, out var bytes);
var bytesDesc = BitConverter.ToString(bytes).Replace("-", "\\x");
Console.WriteLine(bytesDesc);
// try {
// foreach (var arg in args) {
// if (File.Exists(arg)) {
// using var fs = File.OpenRead(arg) ??
// throw new Exception("File.OpenRead returned null. LIES!");
// var expressionLines = new StringStreamEnumerable(new(fs));
// var test = ExpressionTestParser.Parse(expressionLines);
// TempExpressionTests.Run(test);
// }
// }
// if (args.Length == 0) {
// TempExpressionTests.Run("rv = 3 + 4", new Dictionary<string, string> { { "rv", "7" } });
// }
// } catch (Exception ex) {
// using (ConsoleHelper.PushBg(ConsoleColor.Red)) {
// Console.WriteLine($"Failed! {ex}");
// }
// return;
// }
try {
foreach (var arg in args) {
if (File.Exists(arg)) {
using var fs = File.OpenRead(arg) ??
throw new Exception("File.OpenRead returned null. LIES!");
var expressionLines = new StringStreamEnumerable(new(fs));
var test = ExpressionTestParser.Parse(expressionLines);
TempExpressionTests.Run(test);
}
}
if (args.Length == 0) {
TempExpressionTests.Run("rv = 3 + 4", new Dictionary<string, string> { { "rv", "7" } });
}
} catch (Exception ex) {
using (ConsoleHelper.PushBg(ConsoleColor.Red)) {
Console.WriteLine($"Failed! {ex}");
}
return;
}
// using var _ = ConsoleHelper.PushFg(ConsoleColor.Green);
// Console.WriteLine($"Passed!");
using var _ = ConsoleHelper.PushFg(ConsoleColor.Green);
Console.WriteLine($"Passed!");
@@ -9,8 +9,6 @@
<ProjectReference Include="..\ArithmeticExpressions\ArithmeticExpressions.csproj" />
<ProjectReference Include="..\BaseUtilities\BaseUtilities.csproj" />
<ProjectReference Include="..\BooleanExpressions\BooleanExpressions.csproj" />
<ProjectReference Include="..\Bts.ColumnExpressions\Bts.ColumnExpressions.csproj" />
<ProjectReference Include="..\Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj" />
<ProjectReference Include="..\DataStreamCore\DataStreamCore.csproj" />
<ProjectReference Include="..\DataStreamExpressions\DataStreamExpressions.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
@@ -1,79 +0,0 @@
using BCG.Utilities.General;
using System;
namespace BCG {
public class TCPConnectionTestStream : ViewStream, IWorker, ITCPConnectionHandler, ITCPMessageHandler {
class State {
public int n_sends = 0;
public int n_recvs = 0;
}
private readonly string _ip;
private readonly int _port;
private readonly ViewStreamDatabase _vsd;
State _state = new State();
private readonly int _msg_size;
private IBlueTableSchema _schema;
private TimeSpan _send_period;
private ITCPConnection _connection = null;
private byte[] _buff;
public TCPConnectionTestStream(string ip, int port, int msg_size, int send_period_seconds) :
this(ip, port, msg_size, send_period_seconds, ViewStreamDatabase.instance) { }
public TCPConnectionTestStream(string ip, int port, int msg_size, int send_period_seconds, ViewStreamDatabase vsd) {
_ip = ip;
_port = port;
_msg_size = msg_size;
_vsd = vsd;
_schema = BlueDataUtility.form_schema("TCPClient",
Tuple.Create("id", TypeCode.Int32),
Tuple.Create("n_sends", TypeCode.UInt32),
Tuple.Create("n_recvs", TypeCode.UInt32));
_send_period = TimeSpan.FromSeconds(send_period_seconds);
_buff = new byte[msg_size];
for (int i = 0; i < _buff.Length; i++) {
_buff[i] = (byte)i;
}
}
protected override void myOnFirstSubscriber() {
MasterWorker.getInstance(false).addWorker(this, _send_period);
var server_address = new BlutoSubAddress(_ip, _port);
bts.AsyncTCPConnector_APM.connect_to(server_address, this);
}
protected override void myOnLastSubscriber() {
MasterWorker.getInstance(false).removeWorker(this);
}
public void doWork() {
if (_connection != null) {
_connection.send(_buff);
_state.n_sends++;
}
IBlueRow r = new BlueRow(_schema, 0, _state.n_sends, _state.n_recvs);
BlueTable rows = new BlueTable(r.schema);
rows.addRow(r);
var dp = BlueDataUtility.build_dp_inc(rows, myLastValue as DataPacket, true);
myFireEvent(dp);
}
public void onConnection(ITCPConnection connection) {
_connection = connection;
_connection.register(this);
}
public bool isDisposed { get; }
public void onData(RemoteMessage msg) {
bts.Log.Gen.info($"TCPConnectionTestStream received msg: {msg}");
_state.n_recvs++;
}
public void Dispose() {
}
}
}
@@ -1,6 +1,11 @@
namespace BCG;
public class AddColumnsStream : MapStream {
public AddColumnsStream(ViewStream vs, string columnDef)
: base(vs, new ColumnAdder(columnDef)) { }
}
// using System;
// using System.Data;
// using System.Collections;
// using BCG.Utilities;
// using BCG.Utilities.General;
// namespace BCG {
// public class AddColumnsStream : MapStream {
// public AddColumnsStream(ViewStream vs, string columnDef) : base(vs, new ColumnAdder(columnDef)) { }
// }
// }
@@ -1,52 +1,63 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Bts.ColumnExpressions;
namespace BCG;
/// <summary>
/// Provides methods to add columns to the schema
/// </summary>
/// <param name="columnsExpression">
/// String represting the columns to add to the output table
/// </param>
public class ColumnAdder : IStreamMapper {
// Store tuples of (column, index) to preserve order
protected readonly IDictionary<string, (ColumnExpression column, int index)> myNewColumns;
protected IBlueTableSchema? mySchema;
public ColumnAdder(string columnsExpression) =>
myNewColumns = (ColumnExpressionInterpreter.Evaluate(columnsExpression) ??
throw new Exception("Invalid columnn expression: " + columnsExpression))
.Select((c, i) => (c, i))
.ToDictionary(c => c.c.Name, c => (c.c, c.i));
[MemberNotNull(nameof(mySchema))]
public virtual IBlueRow map(IBlueRow row) {
// Only rebuild schema if necessary (i.e., if it's null)
mySchema ??= buildSchema(row.schema, myNewColumns);
return new AddColumnsRow(mySchema, row);
}
[MemberNotNull(nameof(mySchema))]
public IBlueTableSchema getOutputSchema(IBlueTableSchema inputSchema) =>
// Always rebuild schema (even if it's already non-null)
mySchema = buildSchema(inputSchema, myNewColumns);
[MemberNotNull(nameof(mySchema))]
static BlueTableSchema buildSchema(
IBlueTableSchema schema,
IDictionary<string, (ColumnExpression column, int index)> newColumns
) {
ColumnInfo[] cis = new ColumnInfo[schema.columnNames.Length + newColumns.Count];
schema.columnInfo.CopyTo(cis, 0);
foreach (var (col, index) in newColumns.Values) {
var schemaIdx = schema.columnInfo.Length + index;
cis[schemaIdx] = new ColumnInfo(col.Name, schemaIdx, col.Type, col.Value);
}
return new BlueTableSchema(cis, schema.tableName);
}
}
// using System;
// using System.Collections;
// using BCG.Utilities;
// using BCG.Evaluator.AddColumnsExpression;
// namespace BCG {
// /// <summary>
// /// Provides methods to add columns to the schema
// /// </summary>
// public class ColumnAdder : IStreamMapper {
// protected IDictionary myNewColumns;
// protected IBlueTableSchema mySchema;
// static syntax myParser =
// new syntax();
// /// <summary>
// /// string represting the columns to be added to the output table.
// /// </summary>
// /// <param name="colAddExpression"></param>
// public ColumnAdder(string columnDef) {
// AddColumnsExpression ace;
// if (DataStreamLoadFileConfig.instance.useStaticParser) {
// lock (myParser) {
// ace =
// (AddColumnsExpression)myParser.Parse(columnDef);
// }
// } else {
// syntax parser = new syntax();
// ace =
// (AddColumnsExpression)parser.Parse(columnDef);
// }
// if (ace == null)
// throw new Exception("Invalid columnn expression: " + columnDef);
// myNewColumns = ace.columns;
// }
// public virtual IBlueRow map(IBlueRow row) {
// if (mySchema == null)
// myConstructSchema(row.schema);
// return new AddColumnsRow(mySchema, row);
// }
// public IBlueTableSchema getOutputSchema(IBlueTableSchema inputSchema) {
// myConstructSchema(inputSchema);
// return mySchema;
// }
// private void myConstructSchema(IBlueTableSchema schema) {
// ColumnInfo[] cis = new ColumnInfo[schema.columnNames.Length + myNewColumns.Count];
// int ciIndex = 0;
// foreach (ColumnInfo ci in schema.columnInfo)
// cis[ciIndex++] = ci;
// foreach (DictionaryEntry de in myNewColumns)
// cis[ciIndex] = new ColumnInfo((string)de.Key, ciIndex++,
// ((AddColumnsExpression.ConstantInfo)de.Value).t, ((AddColumnsExpression.ConstantInfo)de.Value).v);
// mySchema = new BlueTableSchema(cis, schema.tableName);
// }
// }
// }
@@ -1,107 +1,115 @@
using Bts.ColumnSortExpressions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace BCG;
public class SortRow : IComparable, IBlueRow {
readonly IBlueRow row;
readonly IEnumerable<ColumnSortExpression>? columns;
readonly IComparer? comparer;
public SortRow(IBlueRow row, IList<ColumnSortExpression> columns) {
this.row = row;
this.columns = columns;
}
public SortRow(IBlueRow row, IComparer comparer) {
this.row = row;
this.comparer = comparer;
}
public IBlueRow internal_row { get { return row; } }
#region IComparable method
public int CompareTo(object? obj) {
if (obj is not IBlueRow otherRow) {
throw new ArgumentException(
$"Expected obj to be of type {typeof(IBlueRow)} (actually {obj?.GetType()})",
nameof(obj));
}
if (comparer is not null) {
return comparer.Compare(this, otherRow);
}
return columns.empty_if_null().Select(Compare).FirstOrDefault(c => c != 0);
int Compare(ColumnSortExpression col) {
var otherVal = otherRow[col.Name];
int rv = myIsNull(this[col.Name]) ?
(myIsNull(otherVal) ? 0 : -1) :
(myIsNull(otherVal) ? 1 : ((IComparable)this[col.Name]).CompareTo(otherVal));
rv *= col.IsAscending ? 1 : -1;
return rv;
}
}
#endregion
#region IBlueRow members
public IBlueTableSchema schema { get { return row.schema; } }
#endregion
#region ICollection members
public int Count { get { return row.Count; } }
public bool IsSynchronized { get { return false; } }
public object SyncRoot { get { return this; } }
public void CopyTo(Array array, int index) { row.CopyTo(array, index); }
#endregion
#region IDictionary members
public bool IsFixedSize { get { return true; } }
public bool IsReadOnly { get { return true; } }
/// <summary>
/// The key can either be the string corrsponding to the column name or
/// the order of the column
/// </summary>
public object this[object key] {
get {
if (key is string)
return this[(string)key];
return this[(int)key];
}
set { throw new NotSupportedException("The IBlueRow is readonly"); }
}
public object this[int index] => row[index];
public object this[string colName] => row[colName];
public ICollection Keys => row.Keys;
public ICollection Values => row.Values;
public bool Contains(object key) => row.Contains(key);
void IDictionary.Add(object key, object? value) =>
throw new NotSupportedException("The IBlueRow is readonly");
void IDictionary.Clear() =>
throw new NotSupportedException("The IBlueRow is readonly");
IDictionaryEnumerator IDictionary.GetEnumerator() { return row.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return row.GetEnumerator(); }
public void Remove(object key) { throw new NotSupportedException("The IBlueRow is readonly"); }
#endregion
bool myIsNull(object? o) =>
o is null || o == null || o is DBNull;
}
// using System;
// using System.Collections;
// using BCG.Utilities;
// using BCG.Evaluator.SortExpression;
// namespace BCG
// {
// public class SortRow : IComparable, IBlueRow
// {
// private IBlueRow myRow;
// private IEnumerable myColumns;
// private IComparer myComparer;
// public SortRow (IBlueRow row, SortExpression sortExpression)
// {
// myRow = row;
// myColumns = sortExpression.columns;
// }
// public SortRow (IBlueRow row, IEnumerable columns)
// {
// myRow = row;
// myColumns = columns;
// }
// public SortRow (IBlueRow row, IComparer comparer)
// {
// myRow = row;
// myComparer = comparer;
// }
// public IBlueRow internal_row { get { return myRow; } }
// #region IComparable method
// public int CompareTo (object obj)
// {
// IBlueRow otherRow = (IBlueRow)obj;
// if (myComparer == null)
// {
// foreach (SortExpression.ColumnCriteria cc in myColumns)
// {
// int retVal = myIsNull (this[cc.columnName]) ?
// myIsNull (otherRow[cc.columnName]) ? 0 : -1 :
// myIsNull (otherRow[cc.columnName]) ? 1 : ((IComparable)this[cc.columnName]).CompareTo (otherRow[cc.columnName]);
// retVal *= cc.ascending ? 1 : -1;
// if (retVal != 0)
// return retVal;
// }
// return 0;
// }
// else
// return myComparer.Compare (this, otherRow);
// }
// #endregion
// #region IBlueRow members
// public IBlueTableSchema schema{get{return myRow.schema;}}
// #endregion
// #region ICollection members
// public int Count {get{return myRow.Count;}}
// public bool IsSynchronized {get{return false;}}
// public object SyncRoot {get{return this;}}
// public void CopyTo (Array array, int index) {myRow.CopyTo (array, index);}
// #endregion
// #region IDictionary members
// public bool IsFixedSize {get{return true;}}
// public bool IsReadOnly {get{return true;}}
// /// <summary>
// /// The key can either be the string corrsponding to the column name or
// /// the order of the column
// /// </summary>
// public object this [object key]
// {
// get
// {
// if (key is string)
// return this[(string)key];
// return this[(int)key];
// }
// set {throw new NotSupportedException ("The IBlueRow is readonly");}
// }
// public object this [int index] {get{return myRow[index];}}
// public object this [string colName] {get{return myRow[colName];}}
// public ICollection Keys{get{return myRow.Keys;}}
// public ICollection Values{get{return myRow.Values;}}
// public void Add (object key, object value) {throw new NotSupportedException ("The IBlueRow is readonly");}
// public void Clear () {throw new NotSupportedException ("The IBlueRow is readonly");}
// public bool Contains (object key){return myRow.Contains (key);}
// IDictionaryEnumerator IDictionary.GetEnumerator() {return myRow.GetEnumerator();}
// IEnumerator IEnumerable.GetEnumerator () {return myRow.GetEnumerator();}
// public void Remove (object key) {throw new NotSupportedException ("The IBlueRow is readonly");}
// #endregion
// private bool myIsNull (object o) {return o == null || o is DBNull;}
// }
// }
@@ -1,99 +1,129 @@
using Bts.ColumnSortExpressions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace BCG;
/// <summary>
/// Defines a Sort stream, with performance of O(n) for all the increments. It uses simple merge sort
/// to merge the additions to the accumulates table
/// </summary>
public class SortStream : ViewStream {
ViewStream stream;
IList<ColumnSortExpression> sortCols;
IComparer? comparer;
DataPacket? lastFiredPacket;
DataPacket? lastReceivedPacket;
public SortStream(ViewStream stream, string sortExpression) {
this.stream = stream;
SortExpression = sortExpression;
}
public SortStream(ViewStream vs, string colName, bool isAsc) {
stream = vs;
SortExpression = isAsc ? "+" + colName : "-" + colName;
}
public SortStream(ViewStream stream, IComparer comparer) {
this.stream = stream;
this.comparer = comparer;
sortCols = Array.Empty<ColumnSortExpression>();
}
[MemberNotNull(nameof(sortCols))]
public string SortExpression {
set {
comparer = null;
lastFiredPacket = null;
sortCols = ColumnSortExpressionInterpreter.Evaluate(value);
if (lastReceivedPacket is not null) {
myChangeHandler(lastReceivedPacket);
}
}
}
public IComparer Comparer {
set {
comparer = value;
lastFiredPacket = null;
if (lastReceivedPacket is not null) {
myChangeHandler(lastReceivedPacket);
}
}
}
public void myChangeHandlerTest(object o) =>
myChangeHandler(o);
[MemberNotNull(nameof(lastFiredPacket))]
internal void myChangeHandler(object o) {
lastReceivedPacket = (DataPacket)o;
// First callback, so we need to sort the whole table, this is the
// only time we will do that for all the other sorting operations would
// be sorting the increments and inserting them into the whole list
if (lastFiredPacket is null) {
lastFiredPacket = new() {
newRows = comparer is null ?
new SortedTable(lastReceivedPacket.newRows, sortCols) :
new SortedTable(lastReceivedPacket.newRows, comparer)
};
lastFiredPacket.addedRows = new OrderedBlueTable(lastFiredPacket.newRows);
lastFiredPacket.deletedRows = new BlueTable(lastFiredPacket.newRows.schema);
myFireEvent(lastFiredPacket.Clone());
return;
}
// Deletes
lastFiredPacket.deletedRows =
comparer == null ?
new SortedTable(lastReceivedPacket.deletedRows, sortCols) :
new SortedTable(lastReceivedPacket.deletedRows, comparer);
((SortedTable)lastFiredPacket.newRows).deleteRows(lastFiredPacket.deletedRows);
// Adds
lastFiredPacket.addedRows = new BlueTable(lastReceivedPacket.addedRows);
((SortedTable)lastFiredPacket.newRows).addRows(lastFiredPacket.addedRows);
myFireEvent(lastFiredPacket.Clone());
}
protected override void myOnFirstSubscriber() =>
stream.onChange += myChangeHandler;
protected override void myOnLastSubscriber() {
lastFiredPacket = null;
lastReceivedPacket = null;
stream.onChange -= myChangeHandler;
}
}
// using System;
// using System.Collections;
// using BCG.Evaluator.SortExpression;
// namespace BCG
// {
// /// <summary>
// /// Defines a Sort stream, with performance of O(n) for all the increments. It uses simple merge sort
// /// to merge the additions to the accumulates table
// /// </summary>
// public class SortStream : ViewStream
// {
// private ViewStream myStream;
// private IEnumerable mySortCols;
// private IComparer myComparer;
// private DataPacket myLastFiredPacket;
// private DataPacket myLastRecievedPacket;
// public SortStream (ViewStream stream, string sortExpression)
// {
// myStream = stream;
// mySetSortExpression (sortExpression);
// }
// public SortStream (ViewStream vs, string colName, bool isAsc)
// {
// myStream = vs;
// string sortExpression = isAsc ? "+" + colName : "-" + colName;
// mySetSortExpression (sortExpression);
// }
// public SortStream (ViewStream vs, IComparer comparer)
// {
// myStream = vs;
// myComparer = comparer;
// }
// public string sortExpression
// {
// set
// {
// myComparer = null;
// myLastFiredPacket = null;
// mySetSortExpression (value);
// myChangeHandler (myLastRecievedPacket);
// }
// }
// public IComparer comparer
// {
// set
// {
// myComparer = value;
// myLastFiredPacket = null;
// myChangeHandler(myLastRecievedPacket);
// }
// }
// private void mySetSortExpression (string sortExpression)
// {
// syntax parser =
// new syntax();
// mySortCols = ((SortExpression)
// parser.Parse (sortExpression)).columns;
// }
// protected override void myOnFirstSubscriber()
// {
// myStream.onChange += new DataStream.ChangeDelegate (myChangeHandler);
// }
// protected override void myOnLastSubscriber()
// {
// myInit ();
// myStream.onChange -= new DataStream.ChangeDelegate (myChangeHandler);
// }
// internal void myChangeHandler (object o)
// {
// myLastRecievedPacket = (DataPacket)o;
// //first callback, so we need to sort the whole table, this is the
// //only time we will do that for all the other sorting operations would
// //be sorting the increments and inserting them into the whole list
// if (myLastFiredPacket == null)
// {
// myLastFiredPacket = new DataPacket ();
// myLastFiredPacket.newRows =
// myComparer == null ?
// new SortedTable (myLastRecievedPacket.newRows, mySortCols) :
// new SortedTable (myLastRecievedPacket.newRows, myComparer);
// myLastFiredPacket.addedRows = new OrderedBlueTable(myLastFiredPacket.newRows);
// myLastFiredPacket.deletedRows = new BlueTable (myLastFiredPacket.newRows.schema);
// myFireEvent (myLastFiredPacket.Clone());
// return;
// }
// myHandleDeletes (myLastRecievedPacket);
// myHandleAdds (myLastRecievedPacket);
// myFireEvent (myLastFiredPacket.Clone());
// }
// public void myChangeHandlerTest(object o)
// {
// myChangeHandler(o);
// }
// private void myHandleDeletes (DataPacket dp)
// {
// myLastFiredPacket.deletedRows =
// myComparer == null ?
// new SortedTable (dp.deletedRows, mySortCols) :
// new SortedTable (dp.deletedRows, myComparer);
// ((SortedTable)(myLastFiredPacket.newRows)).deleteRows (myLastFiredPacket.deletedRows);
// }
// private void myHandleAdds (DataPacket dp)
// {
// myLastFiredPacket.addedRows = new BlueTable (dp.addedRows);
// ((SortedTable)(myLastFiredPacket.newRows)).addRows (myLastFiredPacket.addedRows);
// }
// private void myInit ()
// {
// myLastFiredPacket = null;
// myLastRecievedPacket = null;
// }
// }
// }
@@ -1,108 +1,136 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Bts.ColumnSortExpressions;
namespace BCG;
/// <summary>
/// Represents an IBlueRowCollection with the rows sorted according to the
/// </summary>
public class SortedTable : IBlueRowCollection {
readonly List<SortRow> rows = new(0);
readonly Dictionary<int, IBlueRow> rowIndex = new(0);
readonly IList<ColumnSortExpression> sortCols = Array.Empty<ColumnSortExpression>();
readonly IComparer? comparer;
/// <summary>
/// Sorts the rows using sortExpression as the sorting criteria
/// </summary>
/// <param name="rows">Rows to be inserted into the table</param>
/// <param name="sortCols">Sorting criteria</param>
public SortedTable(IBlueRowCollection rows, IList<ColumnSortExpression> sortCols) {
this.sortCols = sortCols;
init(rows);
}
public SortedTable(IBlueRowCollection rows, IComparer comparer) {
this.comparer = comparer;
init(rows);
}
public int Count => rows.Count;
public bool IsSynchronized => false;
public IBlueTableSchema schema { get; private set; }
public object SyncRoot => this;
public IBlueRow this[int id] => rowIndex[id]
?? throw new BlueDataException(
$"ID '{id}' does not exist in table '{schema?.tableName}'");
public void addRows(IBlueRowCollection rows) {
// Requires merger of the addedRows to the already existing ones
if (rows.Count == 0) {
// At times there are calls with 0 additions, in that case
// we of course need no processing
return;
}
SortedTable addedRows =
comparer == null ? new SortedTable(rows, sortCols) :
new SortedTable(rows, comparer);
int addedRowsIndex = 0;
SortRow newRow = addedRows.getRowByIndex(addedRowsIndex);
for (int i = 0; i < this.rows.Count; i++) {
if (newRow.CompareTo(this.rows[i]) <= 0) {
this.rows.Insert(i, newRow);
rowIndex[(int)newRow["id"]] = newRow;
if (++addedRowsIndex == addedRows.Count) {
return;
}
newRow = addedRows.getRowByIndex(addedRowsIndex);
}
}
if (addedRowsIndex < addedRows.Count) {
for (int i = addedRowsIndex; i < addedRows.Count; i++) {
newRow = addedRows.getRowByIndex(i);
this.rows.Add(newRow);
rowIndex[(int)newRow["id"]] = newRow;
}
}
}
public void deleteRows(IBlueRowCollection deletedRows) {
foreach (IBlueRow deletedRow in deletedRows) {
var row = rowIndex[(int)deletedRow["id"]!] as SortRow
?? throw new InvalidOperationException("Index did not return IBlueRow");
rows.Remove(row);
rowIndex.Remove((int)row["id"]!);
}
}
public SortRow getRowByIndex(int index) =>
rows[index]!;
public bool containsID(int id) =>
rowIndex[id] is not null;
public IEnumerator GetEnumerator() =>
rows.GetEnumerator();
public void CopyTo(Array array, int index) {
for (int i = 0; i < Count; i++) {
array.SetValue(rows[i], index + i);
}
}
[MemberNotNull(nameof(schema))]
void init(IBlueRowCollection rows) {
schema = rows.schema;
foreach (IBlueRow row in rows) {
SortRow newRow = comparer is null ?
new(row, sortCols) : new(row, comparer);
this.rows.Add(newRow);
rowIndex[(int)row["id"]!] = newRow;
}
this.rows.Sort();
}
}
// using System;
// using System.Collections;
// using BCG.Evaluator.SortExpression;
// namespace BCG
// {
// /// <summary>
// /// Represents an IBlueRowCollection with the rows sorted according to the
// ///
// /// </summary>
// public class SortedTable : IBlueRowCollection
// {
// private ArrayList myRows = new ArrayList ();
// private Hashtable myIndex = new Hashtable ();
// private IBlueTableSchema mySchema;
// private IEnumerable mySortCols;
// private IComparer myComparer;
// #region SortedTable method(s)
// public void addRows (IBlueRowCollection rows)
// {
// //requires merger of the addedRows to the already existing ones
// if (rows.Count == 0)
// //at times there are calls with 0 additions, in that case
// //we ofcourse need no processing
// return;
// SortedTable addedRows =
// myComparer == null ? new SortedTable (rows, mySortCols) :
// new SortedTable (rows, myComparer);
// int addedRowsIndex = 0;
// SortRow newRow = addedRows.getRowByIndex (addedRowsIndex);
// for (int i = 0; i < myRows.Count; i ++)
// if (newRow.CompareTo(myRows[i]) <= 0)
// {
// myRows.Insert (i, newRow);
// myIndex[newRow["id"]] = newRow;
// if (++addedRowsIndex == addedRows.Count)
// return;
// newRow = addedRows.getRowByIndex (addedRowsIndex);
// }
// if (addedRowsIndex < addedRows.Count)
// for (int i = addedRowsIndex; i < addedRows.Count; i++)
// {
// newRow = addedRows.getRowByIndex (i);
// myRows.Add (newRow);
// myIndex[newRow["id"]] = newRow;
// }
// }
// public void deleteRows (IBlueRowCollection deletedRows)
// {
// foreach (IBlueRow deletedRow in deletedRows)
// {
// IBlueRow row = (SortRow)myIndex[deletedRow["id"]];
// myRows.Remove (row);
// myIndex.Remove (row["id"]);
// }
// }
// public SortRow getRowByIndex (int index) {return (SortRow)myRows[index];}
// #endregion
// #region Constructor(s)
// /// <summary>
// /// Sorts the rows using sortExpression as the sorting criteria
// /// </summary>
// /// <param name="rows">
// /// Rows to be inserted into the table
// /// </param>
// /// <param name="sortExpression">
// /// Sorting criteria
// /// </param>
// public SortedTable (IBlueRowCollection rows, SortExpression sortExpression)
// {
// mySortCols = sortExpression.columns;
// myConstructorHelper (rows);
// }
// public SortedTable (IBlueRowCollection rows, IEnumerable sortCols)
// {
// mySortCols = sortCols;
// myConstructorHelper (rows);
// }
// public SortedTable (IBlueRowCollection rows, IComparer comparer)
// {
// myComparer = comparer;
// myConstructorHelper (rows);
// }
// private void myConstructorHelper (IBlueRowCollection rows)
// {
// mySchema = rows.schema;
// foreach (IBlueRow row in rows)
// {
// SortRow newRow =
// myComparer == null ?
// new SortRow (row, mySortCols) : new SortRow (row, myComparer);
// myRows.Add (newRow);
// myIndex[row["id"]] = newRow;
// }
// myRows.Sort ();
// }
// #endregion
// #region IBlueRowCollections members
// public IBlueRow this[int id]
// {
// get
// {
// if (myIndex[id] == null)
// throw new BlueDataException ("ID " + id.ToString() + " does not exist in table " + mySchema.tableName);
// return (IBlueRow)myIndex[id];
// }
// }
// public IBlueTableSchema schema {get{return mySchema;}}
// public bool containsID (int id) {return myIndex[id] != null;}
// #endregion
// #region ICollection members
// public IEnumerator GetEnumerator () {return myRows.GetEnumerator ();}
// public bool IsSynchronized {get{return false;}}
// public object SyncRoot {get{return this;}}
// public void CopyTo (Array array, int index){myRows.CopyTo (array, index);}
// public int Count{get{return myRows.Count;}}
// #endregion
// }
// }
@@ -48,12 +48,6 @@ namespace BCG {
rv.Add(r.get<T>(col));
return rv;
}
public static IEnumerable<T> get_colvalues<T>(IBlueRowCollection rows, string col) {
List<T> rv = new List<T>();
foreach (IBlueRow r in rows)
rv.Add(r.get<T>(col));
return rv;
}
public static IDictionary<T, IEnumerable<IBlueRow>> group_by<T>(IBlueRowCollection rows, string gp_col) {
Dictionary<T, List<IBlueRow>> rv = new Dictionary<T, List<IBlueRow>>();
@@ -247,68 +241,6 @@ namespace BCG {
return new BlueTableSchema(table_name, cis.ToArray());
}
public static TypeCode convert_column_type(IEnumerable<string> col_vals) {
// bool
bool success = true;
foreach (var val in col_vals) {
bool parsed_val;
success = success && Boolean.TryParse(val, out parsed_val);
if (!success)
break;
}
if (success)
return TypeCode.Boolean;
// int
success = true;
foreach (var val in col_vals) {
int parsed_val;
success = success && Int32.TryParse(val, out parsed_val);
if (!success)
break;
}
if (success)
return TypeCode.Int32;
// try double
success = true;
foreach (var val in col_vals) {
double parsed_val;
success = success && Double.TryParse(val, out parsed_val);
if (!success)
break;
}
if (success)
return TypeCode.Double;
// try DateTime
success = true;
foreach (var val in col_vals) {
DateTime parsed_val;
success = success && DateTime.TryParse(val, out parsed_val);
if (!success)
break;
}
if (success)
return TypeCode.DateTime;
bts.Log.assert(false);
return TypeCode.Empty;
}
public static IBlueTableSchema to_typed_schema(IBlueRowCollection rows) {
List<ColumnInfo> cis = new List<ColumnInfo>();
foreach (var col in rows.schema.columnInfo) {
var text_vals = BlueDataUtility.get_colvalues<string>(rows, col.colName);
TypeCode type_code = convert_column_type(text_vals);
cis.Add(new ColumnInfo(col.colName, cis.Count, _typecode_to_type[type_code]));
}
return new BlueTableSchema(cis.ToArray(), rows.schema.tableName);
}
// public static IBlueRow convert_string_row(IBlueRow r, IBlueTableSchema out_schema) {
// foreach (var ci in out_schema.columnInfo){
// }
// }
/// <summary>
/// Gets the Hashcode of the row with the boolean indicating whether the order of the rows should
/// be alphabetic or not
@@ -1,103 +1,95 @@
using System;
using System.Text.Json.Serialization;
namespace BCG
{
/// <summary>
/// Resprsents information about a column schema, ie its ordinal, the type, the name and constant value or expression. where applicable
/// </summary>
[Serializable]
public class ColumnInfo : IComparable
{
public static bool isAllowedType (Type t)
{
return
t.Equals (typeof (System.Int32)) ||
t.Equals (typeof (System.Int64)) ||
t.Equals (typeof (System.Double)) ||
t.Equals (typeof (System.String)) ||
t.Equals (typeof (System.DateTime)) ? true :
false;
}
//the default column info does not cach values in a lookup table, therefore it is in group 0
public ColumnInfo(string colName, int colNo, Type colType)
: this(colName, colNo, colType, 0)
{
}
public ColumnInfo(string colName, int colNo, Type colType, short colGroup)
{
myColName = colName;
myColNo = colNo;
myColType = colType;
myLookupTableGroup = colGroup;
myColValue = null;
}
public ColumnInfo (string colName, int colNo, object colValue)
: this (colName, colNo, colValue.GetType()) {myColValue = colValue == null ? DBNull.Value : colValue;}
public ColumnInfo (string colName, int colNo, Type colType, object colValue)
: this (colName, colNo, colType)
{
myColValue = colValue == null ? DBNull.Value : colValue;
}
public int CompareTo (object obj)
{
int otherColNo = ((ColumnInfo)(obj)).colNo;
if (myColNo < otherColNo)
return -1;
else if (myColNo > otherColNo)
return 1;
return 0;
}
//public bool isCalculated{get{return myExpression != null;}}
public bool isConstant {get{return myColValue != null;}}
private int myColNo;
private string myColName;
private Type myColType;
//private string myExpression;
private object myColValue;
//no table will ever have more than 65,536 columns
//if it does, we are in serious trouble
private short myLookupTableGroup;
[JsonPropertyName("index")]
public int colNo{get{return myColNo;}}
[JsonPropertyName("name")]
public string colName {get{return myColName;}}
[JsonPropertyName("type")]
public Type colType {get{return myColType;}}
//this value corresponds to how the data in the column is serialized.
//if the group number is 0, the values are not stored in a table
//if the vlaue is non-zero, all columns with the the same lookup table group are stored in the same lookup table together
//are stored together in a lookup table
[JsonIgnore]
public short lookupTableGroup
{
get { return myLookupTableGroup; }
set { myLookupTableGroup = value; }
}
//public string expression {get{return myExpression;}}
[JsonIgnore]
public object colValue {get{return myColValue;}}
}
}
using System;
namespace BCG
{
/// <summary>
/// Resprsents information about a column schema, ie its ordinal, the type, the name and constant value or expression. where applicable
/// </summary>
[Serializable]
public class ColumnInfo : IComparable
{
public static bool isAllowedType (Type t)
{
return
t.Equals (typeof (System.Int32)) ||
t.Equals (typeof (System.Int64)) ||
t.Equals (typeof (System.Double)) ||
t.Equals (typeof (System.String)) ||
t.Equals (typeof (System.DateTime)) ? true :
false;
}
//the default column info does not cach values in a lookup table, therefore it is in group 0
public ColumnInfo(string colName, int colNo, Type colType)
: this(colName, colNo, colType, 0)
{
}
public ColumnInfo(string colName, int colNo, Type colType, short colGroup)
{
myColName = colName;
myColNo = colNo;
myColType = colType;
myLookupTableGroup = colGroup;
myColValue = null;
}
public ColumnInfo (string colName, int colNo, object colValue)
: this (colName, colNo, colValue.GetType()) {myColValue = colValue == null ? DBNull.Value : colValue;}
public ColumnInfo (string colName, int colNo, Type colType, object colValue)
: this (colName, colNo, colType)
{
myColValue = colValue == null ? DBNull.Value : colValue;
}
public int CompareTo (object obj)
{
int otherColNo = ((ColumnInfo)(obj)).colNo;
if (myColNo < otherColNo)
return -1;
else if (myColNo > otherColNo)
return 1;
return 0;
}
//public bool isCalculated{get{return myExpression != null;}}
public bool isConstant {get{return myColValue != null;}}
private int myColNo;
private string myColName;
private Type myColType;
//private string myExpression;
private object myColValue;
//no table will ever have more than 65,536 columns
//if it does, we are in serious trouble
private short myLookupTableGroup;
public int colNo{get{return myColNo;}}
public string colName {get{return myColName;}}
public Type colType {get{return myColType;}}
//this value corresponds to how the data in the column is serialized.
//if the group number is 0, the values are not stored in a table
//if the vlaue is non-zero, all columns with the the same lookup table group are stored in the same lookup table together
//are stored together in a lookup table
public short lookupTableGroup
{
get { return myLookupTableGroup; }
set { myLookupTableGroup = value; }
}
//public string expression {get{return myExpression;}}
public object colValue {get{return myColValue;}}
}
}
@@ -1,63 +0,0 @@
// #nullable enable
// using BCG;
// using Spark.Core.Json;
// using System;
// using System.Text.Json;
// using System.Text.Json.Nodes;
// using System.Text.Json.Serialization;
// namespace Bts.DataStreamCore;
// class BlueRowJsonConverter : JsonConverter<IBlueRow> {
// public override IBlueRow Read(
// ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
// var row = JsonSerializer.Deserialize<JsonObject>(ref reader, options)!;
// var schemaj = row["schema"]!.GetValue<JsonObject>() ??
// throw new InvalidOperationException("Failed to read schema");
// // var columns = schemaj["columns"]
// var mbr = new MutableBlueRow(schema);
// int colNum = 0;
// switch (reader.TokenType) {
// case JsonTokenType.StartArray:
// while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) {
// mbr.setValue(colNum++, readCellValue(ref reader));
// colNum++;
// }
// break;
// case JsonTokenType.StartObject:
// while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) {
// if (reader.TokenType != JsonTokenType.PropertyName) {
// throw new InvalidOperationException(
// "Expected 'PropertyName' immediately after 'StartObject' token in row json");
// }
// var colName = reader.GetString();
// if (!reader.Read()) {
// throw new InvalidOperationException(
// "Failed to read token immediately after 'PropertyName' token in row json");
// }
// mbr.setValue(colName, readCellValue(ref reader));
// }
// break;
// }
// if (mbr.Count != schema.columnInfo.Length) {
// throw new InvalidOperationException(
// $"Row json array length '{mbr.Count}' does not match the schema's " +
// $"column length '{schema.columnInfo.Length}'.");
// }
// return mbr;
// static object? readCellValue(ref Utf8JsonReader reader) =>
// reader.TokenType switch {
// JsonTokenType.String => reader.GetString(),
// JsonTokenType.Number => reader.GetDouble(),
// JsonTokenType.True or JsonTokenType.False => reader.GetBoolean(),
// _ => throw new InvalidOperationException("Failed to read cell value"),
// };
// }
// public override void Write(Utf8JsonWriter writer, IBlueRow value, JsonSerializerOptions options) {
// throw new NotImplementedException();
// }
// }
@@ -1,186 +0,0 @@
// #nullable enable
// using BCG;
// using Spark.Core.Json;
// using System;
// using System.Collections.Generic;
// using System.Text.Json;
// using System.Text.Json.Nodes;
// using System.Text.Json.Serialization;
// namespace Bts.DataStreamCore;
// public class DataStreamUpdateJsonConverter : JsonConverter<DataStreamUpdate> {
// /// <summary>
// /// Whether or not to include a field for current rows (i.e., snapshot of current state). By
// /// default, only incremental changes are included ('added' and 'deleted').
// /// </summary>
// public bool IncludeCurrent { get; set; }
// /// <summary>
// /// Whether to treat rows as objects (name/value pairs), or arrays (list of values). Arrays
// /// result in smaller payloads, but aren't as easy to read as name/value pairs (for mere humans).
// /// </summary>
// public bool UseObjectRows { get; set; }
// public override DataStreamUpdate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
// var dpj = JsonSerializer.Deserialize<JsonObject>(ref reader);
// if (dpj is null) {
// return null;
// }
// var tableName = dpj["tableName"]!.AsString();
// var columns = dpj["columns"]!.AsArray()
// .Select((c, i) => {
// var dataType = c!["type"]!.AsEnum<DataStreamDataType>();
// return (info: new ColumnInfo(c["name"]!.AsString(), i, dataType.ToType()), dataType);
// })
// .ToArray();
// var schema = new BlueTableSchema(columns.Cut1().ToArray(), tableName);
// return new() {
// TableName = tableName,
// AddedRows = ToRows(dpj.GetArrayOrEmpty("added")).ToList(),
// DeletedRows = ToRows(dpj.GetArrayOrEmpty("deleted")).ToList(),
// Columns = [.. schema.columnInfo]
// };
// IEnumerable<IBlueRow> ToRows(JsonArray rows) {
// foreach (var row in rows) {
// var mbr = new MutableBlueRow(schema);
// switch (row!.GetValueKind()) {
// case JsonValueKind.Object:
// var rowObj = row.AsObject();
// for (int i = 0; i < columns.Length; i++) {
// SetCellValue(mbr, i, rowObj[columns[i].info.colName]!, columns[i].dataType);
// }
// break;
// case JsonValueKind.Array:
// var rowArr = row.AsArray();
// for (int i = 0; i < columns.Length; i++) {
// SetCellValue(mbr, i, rowArr[i]!, columns[i].dataType);
// }
// break;
// }
// yield return mbr;
// }
// }
// static void SetCellValue(MutableBlueRow mbr, int i, JsonNode cell, DataStreamDataType dataType) {
// switch (dataType) {
// case DataStreamDataType.String:
// mbr.setValue(i, cell.GetValue<string>());
// break;
// case DataStreamDataType.Int32:
// mbr.setValue(i, cell.GetValue<int>());
// break;
// case DataStreamDataType.Int64:
// mbr.setValue(i, cell.GetValue<long>());
// break;
// case DataStreamDataType.Single:
// mbr.setValue(i, cell.GetValue<float>());
// break;
// case DataStreamDataType.Double:
// mbr.setValue(i, cell.GetValue<double>());
// break;
// }
// }
// }
// public override void Write(Utf8JsonWriter writer, DataStreamUpdate value, JsonSerializerOptions options) {
// using var _ = writer.WriteObject();
// writer.WriteString("tableName", value.TableName);
// using (writer.WriteArray("columns")) {
// foreach (var col in value.Columns.empty_if_null()) {
// using (writer.WriteObject()) {
// writer.WriteString("name", col.colName);
// writer.WriteNumber("index", col.colNo);
// // TODO: use language agnostic type names (e.g., 'string', 'integer', 'float', 'date' etc.)
// writer.WriteString("type", col.colType.ToString());
// }
// }
// }
// var cellWriters = value.Columns!.Select(getCellWriter).ToList();
// using (writer.WriteArray("added")) {
// foreach (IBlueRow row in value.AddedRows!) {
// using (writer.WriteArray()) {
// for (int i = 0; i < cellWriters.Count; i++) {
// cellWriters[i](row[i]);
// }
// }
// }
// }
// using (writer.WriteArray("deleted")) {
// foreach (IBlueRow row in value.DeletedRows!) {
// using (writer.WriteArray()) {
// for (int i = 0; i < cellWriters.Count; i++) {
// cellWriters[i](row[i]);
// }
// }
// }
// }
// if (IncludeCurrent) {
// using (writer.WriteArray("current")) {
// foreach (IBlueRow row in value.newRows) {
// using (writer.WriteArray()) {
// for (int i = 0; i < cellWriters.Count; i++) {
// cellWriters[i](row[i]);
// }
// }
// }
// }
// }
// Action<object?> getCellWriter(ColumnInfo colInfo) {
// var writeVal = getCellValueWriter(colInfo);
// if (!UseObjectRows) {
// return writeVal;
// }
// return v => {
// writer.WritePropertyName(colInfo.colName);
// writeVal(v);
// };
// }
// Action<object?> getCellValueWriter(ColumnInfo colInfo) {
// var t = colInfo.colType;
// if (t == typeof(int)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((int)v));
// }
// if (t == typeof(uint)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((uint)v));
// }
// if (t == typeof(long)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((long)v));
// }
// if (t == typeof(ulong)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((ulong)v));
// }
// if (t == typeof(short)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((short)v));
// }
// if (t == typeof(ushort)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((ushort)v));
// }
// if (t == typeof(double)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((double)v));
// }
// if (t == typeof(float)) {
// return nullCheckWrapper(v => writer.WriteNumberValue((float)v));
// }
// if (t == typeof(bool)) {
// return nullCheckWrapper(v => writer.WriteBooleanValue((bool)v));
// }
// return nullCheckWrapper(v => writer.WriteStringValue(v?.ToString()));
// Action<object?> nullCheckWrapper(Action<object> writeNonNull) =>
// v => {
// if (v is null) {
// writer.WriteNullValue();
// } else {
// writeNonNull(v);
// }
// };
// }
// }
// }
@@ -4,7 +4,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net8.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
@@ -1,11 +0,0 @@
namespace Bts.DataStreamCore;
public enum DataStreamDataType {
Any,
Bytes,
String,
Int32,
Int64,
Single,
Double
}
@@ -1,42 +0,0 @@
#nullable enable
using Bts.DataStreamCore;
public static class DataStreamExtensions {
public static Type ToType(this DataStreamDataType dataType) =>
dataType switch {
DataStreamDataType.Any => typeof(object),
DataStreamDataType.String => typeof(string),
DataStreamDataType.Int32 => typeof(int),
DataStreamDataType.Int64 => typeof(long),
DataStreamDataType.Single => typeof(float),
DataStreamDataType.Double => typeof(double),
_ => throw new ArgumentException($"DataType '{dataType}' not supported.", nameof(dataType)),
};
public static DataStreamDataType FromType(Type type) {
if (type == typeof(object)) {
return DataStreamDataType.Any;
}
if (type == typeof(string)) {
return DataStreamDataType.String;
}
if (type == typeof(int)) {
return DataStreamDataType.Int32;
}
if (type == typeof(long)) {
return DataStreamDataType.Int64;
}
if (type == typeof(float)) {
return DataStreamDataType.Single;
}
if (type == typeof(double)) {
return DataStreamDataType.Double;
}
if (type == typeof(byte[])) {
return DataStreamDataType.Bytes;
}
throw new ArgumentException(
$"Unable to convert type '{type}' to {typeof(DataStreamDataType)}");
}
}
@@ -1,41 +0,0 @@
#nullable enable
using BCG;
using Spark.Core.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Bts.DataStreamCore;
public class DataStreamUpdate {
[JsonPropertyName("tableName")]
public string? TableName { get; set; }
[JsonPropertyName("columns")]
public List<ColumnInfo>? Columns { get; set; }
[JsonPropertyName("addedRows")]
public List<IBlueRow>? AddedRows { get; set; }
[JsonPropertyName("deletedRows")]
public List<IBlueRow>? DeletedRows { get; set; }
public DataPacket ToDataPacket(BlueTable state, bool applyChanges = true) {
var schema = new BlueTableSchema([.. Columns!], TableName);
var addedRowsTable = new BlueTable(schema);
var deletedRowsTable = new BlueTable(schema);
if (applyChanges) {
state.deleteRows(deletedRowsTable);
state.addRows(addedRowsTable);
}
return new(state, addedRowsTable, deletedRowsTable);
}
public static string ToJson() {
return "";
}
}
@@ -1 +0,0 @@
using System.Linq;
@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
<PackageReference Include="YaccLexTools" Version="1.2.2" />
<PackageReference Include="YaccLexTools" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
File diff suppressed because it is too large. Load diff
@@ -1,12 +0,0 @@
{
"user": "bts",
"prepublish_tables": {
"bbo": "bbo()",
"instruments": "instruments()",
"timesales": "timesales(10000)",
"books": "books()",
"seconds_1_10": "second(1,10)",
"literal_420": "literal(\"x\", \"420.0\")",
"mongodb_testcollection": "mongodb(\"mongodb+srv://johnc-dev:DMhDhP!g-TKeK.5@atlas-online-archive-5e18ab3c77c74d1cffa64c03-m7jkr.a.query.mongodb.net/?ssl=true&authSource=admin\",\"admin\",\"test_collection\")"
}
}
@@ -1,4 +0,0 @@
{
"user": "bts",
"simple_echo_client": true
}
@@ -1,16 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\client\spark_api\spark_api.csproj" />
<ProjectReference Include="..\..\client\spark_core\spark_core.csproj" />
@@ -20,4 +9,11 @@
<ProjectReference Include="..\PubSub\PubSub.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
@@ -1,76 +0,0 @@
namespace Bts.DataStreamPublisher;
using CommandLine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
public class DataStreamPublisherConfig {
/// <summary>
/// The login user for sparkd
/// </summary>
[JsonPropertyName("user")]
[Option("user", HelpText = "The sparkd login user")]
public string? User { get; set; }
/// <summary>
/// The login password for sparkd
/// </summary>
[JsonPropertyName("password")]
[Option("pass", HelpText = "The sparkd login password")]
public string? Password { get; set; }
/// <summary>
/// The config server url for sparkd
/// </summary>
[JsonPropertyName("sparkd")]
[Option("sparkd", HelpText = "The sparkd config server")]
public string? Sparkd { get; set; }
/// <summary>
/// The tables to publish on startup. Each key/value represents a table name/query to publish.
/// </summary>
[JsonPropertyName("prepublish_tables")]
public Dictionary<string, string>? PrepublishTables { get; set; }
[JsonPropertyName("simple_echo_client")]
[Option("simple-echo-client", HelpText = "Run a simple echo client (won't publish tables or connect to sparkd)")]
public bool? SimpleEchoClient { get; set; }
[Option(
"config",
MetaValue = "PATH",
HelpText = "Path to json file representing these options (DataStreamPublisherConfig)")]
[JsonIgnore]
public string? ConfigFile { get; set; }
public void MergeWith(DataStreamPublisherConfig other) {
User ??= other.User;
Password ??= other.Password;
Sparkd ??= other.Sparkd;
PrepublishTables ??= other.PrepublishTables;
SimpleEchoClient ??= other.SimpleEchoClient;
ConfigFile ??= other.ConfigFile;
}
/// <summary>
/// Merges options with those defined in `ConfigFile` (does not overwrite
/// non-null existing values).
/// </summary>
/// <exception cref="InvalidOperationException">
/// JsonSerializer.Deserialize returns null
/// </exception>
public void MergeWithConfigFile() {
if (ConfigFile is null) {
return;
}
var cfgString = File.ReadAllText(ConfigFile);
var parsedCfg = JsonSerializer.Deserialize<DataStreamPublisherConfig>(cfgString) ??
throw new InvalidOperationException($"Failed to parse json from {ConfigFile}");
MergeWith(parsedCfg);
ConfigFile = null;
}
}
@@ -1,91 +1,91 @@
using BCG;
using bts;
using bts.api.core;
using bts.utils;
using Bts.DataStreamExpressions;
using Bts.DataStreamPublisher;
using CommandLine;
using mbts.config;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
await Parser.Default.ParseArguments<DataStreamPublisherConfig>(args)
.MapResult(RunAsync, errs => Task.FromResult(1));
async Task<int> RunAsync(DataStreamPublisherConfig cfg) {
try {
cfg.MergeWithConfigFile();
Configuration.initialize(cfg.Sparkd);
Log.Gen.info("Starting DataStreamPublisher");
var service_ctx = ServiceContext.create(cfg.Sparkd);
await service_ctx.login(cfg.User ?? "bts", cfg.Password ?? string.Empty);
if (cfg.SimpleEchoClient ?? false) {
start_echo_client();
} else {
_publish_streams(cfg.PrepublishTables ?? []);
}
} catch (Exception ex) {
// Write the error to console first, in case the logging system is compromised.
var error_msg = $"DataStreamPublisher threw exception:\n{ex}";
Console.Error.WriteLine(error_msg);
Console.Error.Flush();
bts.Log.Gen.error(error_msg);
return 1;
}
return 0;
}
static ViewStream build_stream(string stream_expression) {
try {
bts.Log.Gen.info($"build_stream stream_expression: {stream_expression}");
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
return rv;
} catch (Exception ex) {
bts.Log.assert(false, $"Could not build stream: {stream_expression} ex: {ex}");
return null;
}
}
static void start_echo_client() {
Console.WriteLine("Starting DSPublisher in TCP client echo mode, Hit key to continue");
Console.ReadLine();
SimpleTCPClient client = new SimpleTCPClient("127.0.0.1", 12345);
client.start();
while (true) {
System.Threading.Thread.Sleep(1000);
}
// await SimpleTCPClient.start("127.0.0.1", 12345);
// Task.Delay(1000000000).Wait();
}
void _publish_streams(IEnumerable<KeyValuePair<string, string>> prepublish_streams) {
PubTableBroker pb_broker = new PubTableBroker();
CloudStreamManager csm = new CloudStreamManager();
foreach (var kv in prepublish_streams) {
var name = kv.Key;
var exp = kv.Value;
BCG.ViewStream vs = build_stream(exp);
ViewPublisher vpub = new ViewPublisher(name, pb_broker, vs);
vs.onChange += on_change;
}
while (true) {
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
ViewStreamDatabase.instance.doWork();
csm.doWork();
MasterWorker.getInstance(false).doWork();
}
}
void on_change(object obj) {
bts.Log.Gen.info($"DSPublisher on_change dp: {obj}");
}
using System;
using mbts.config;
using bts.utils;
using System.Threading.Tasks;
using Bts.DataStreamExpressions;
using System.Collections.Generic;
using BCG;
using System.Threading;
bts.Log.Gen.info($"Starting DSPublisher");
Console.WriteLine("Starting DSPublisher...");
bool run_echo_tcp_client_only = false;
static void init(string[] args) {
Func<string, string, Task> login = async (l, p) => {
var login = l;
var passwd = p;
var service_ctx = bts.api.core.ServiceContext.create();
await service_ctx.login(login, passwd);
};
BTSNativeMethods.provide_command_line(args, args.Length);
Configuration.initialize();
Task.Run(async () => await login("bts", String.Empty));
}
init(args);
if (run_echo_tcp_client_only)
start_echo_client();
else
_publish_streams();
static ViewStream build_stream(string stream_expression) {
try {
bts.Log.Gen.info($"build_stream stream_expression: {stream_expression}");
var exp = DataStreamExpressionsInterpreter.Evaluate(stream_expression);
var rv = exp.getView(new Dictionary<string, StatementExpression>(0));
return rv;
} catch (Exception ex) {
bts.Log.assert(false, $"Could not build stream: {stream_expression} ex: {ex}");
return null;
}
}
static void start_echo_client() {
Console.WriteLine("Starting DSPublisher in TCP client echo mode, Hit key to continue");
Console.ReadLine();
SimpleTCPClient client = new SimpleTCPClient("127.0.0.1", 12345);
client.start();
while (true) {
System.Threading.Thread.Sleep(1000);
}
// await SimpleTCPClient.start("127.0.0.1", 12345);
// Task.Delay(1000000000).Wait();
}
void _publish_streams() {
PubTableBroker pb_broker = new PubTableBroker();
CloudStreamManager csm = new CloudStreamManager();
// get it from config
Dictionary<string, string> streams_to_publish = new Dictionary<string, string>() {
//{"bbo","bbo()"},
//{"instruments","instruments()"},
//{"timesales","timesales(10000)"},
//{"books","books()"},
{"seconds_1_10","second(1,10)"},
{"literal_420","literal(\"x\", \"420.0\")"},
{"mongodb_testcollection", "mongodb(\"mongodb://192.168.1.80:27017\",\"local\",\"test_collection\")"}
};
// {"bbo_ts","timeSeries(bbo())"}
foreach (var kv in streams_to_publish) {
var name = kv.Key;
var exp = kv.Value;
BCG.ViewStream vs = build_stream(exp);
ViewPublisher vpub = new ViewPublisher(name, pb_broker, vs);
vs.onChange += on_change;
}
while (true) {
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
ViewStreamDatabase.instance.doWork();
csm.doWork();
MasterWorker.getInstance(false).doWork();
}
}
void on_change(object obj) {
bts.Log.Gen.info($"DSPublisher on_change dp: {obj}");
}
-18
View File
@@ -38,10 +38,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamTests", "DataStre
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedStreams", "SharedStreams\SharedStreams.csproj", "{C1EBC423-4668-43D4-B7E5-A291086BA823}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bts.ColumnExpressions\Bts.ColumnExpressions.csproj", "{134C3915-CB2B-494C-BD62-E0B18250CBF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStreamWebApp.Server", "Bts.DataStreamWebApp.Server\Bts.DataStreamWebApp.Server.csproj", "{6DE5E4CE-423E-4571-81F7-539D268D6059}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.DataStream.Websocket", "Bts.DataStream.Websocket\Bts.DataStream.Websocket.csproj", "{088655DA-3126-4F8A-9AC7-9061FDA891FF}"
@@ -56,8 +52,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionTests", "Expressi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionTester", "ExpressionTester\ExpressionTester.csproj", "{0908C8F3-5E8A-41C7-979C-1C7C38D431C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStream.Console", "DataStream.Console\DataStream.Console.csproj", "{B2968D7D-A41A-41E0-940A-37C7C2CA9513}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -139,14 +133,6 @@ Global
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1EBC423-4668-43D4-B7E5-A291086BA823}.Release|Any CPU.Build.0 = Release|Any CPU
{134C3915-CB2B-494C-BD62-E0B18250CBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{134C3915-CB2B-494C-BD62-E0B18250CBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{134C3915-CB2B-494C-BD62-E0B18250CBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{134C3915-CB2B-494C-BD62-E0B18250CBF5}.Release|Any CPU.Build.0 = Release|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6DE5E4CE-423E-4571-81F7-539D268D6059}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -175,9 +161,5 @@ Global
{0908C8F3-5E8A-41C7-979C-1C7C38D431C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0908C8F3-5E8A-41C7-979C-1C7C38D431C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0908C8F3-5E8A-41C7-979C-1C7C38D431C8}.Release|Any CPU.Build.0 = Release|Any CPU
{B2968D7D-A41A-41E0-940A-37C7C2CA9513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2968D7D-A41A-41E0-940A-37C7C2CA9513}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2968D7D-A41A-41E0-940A-37C7C2CA9513}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2968D7D-A41A-41E0-940A-37C7C2CA9513}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -6,7 +6,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -64,7 +64,7 @@ namespace bts {
newConn.getPeerAddress(out ip, out port);
string connMsg = String.Format("Connection to port {2} made from {0}:{1}", ip, port, _port);
// BlueEventLog.logEvent(connMsg, PS3LogLevels.connection);
Console.WriteLine(connMsg);
//Console.WriteLine(connMsg);
}
@@ -72,4 +72,4 @@ namespace bts {
}
}
}
@@ -480,6 +480,7 @@ namespace bts {
_on_read_data_completely(rcv_message);
_read_next_msg();
return;
return;
} else if (msg_type == HB_MSG_SIGNAL) {
_on_read_hb_completely(rcv_message);
_read_next_msg();
@@ -580,4 +581,4 @@ namespace bts {
}
}
}
}
}
@@ -1,42 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Bts.PubSub;
public class SimpleTCPServer2 {
private static Socket? _acceptSocket;
private static int _port = 12345;
public static void StartServer() {
_acceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, _port);
_acceptSocket.Bind(endpoint);
_acceptSocket.Listen(250);
Console.WriteLine($"Server listening on port {_port}");
while (true) {
Socket clientSocket = _acceptSocket.Accept();
HandleClient(clientSocket);
}
}
static void HandleClient(Socket clientSocket) {
// Example: Echo back received messages
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = clientSocket.Receive(buffer)) > 0) {
string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Received data: {receivedData}");
// Echo back to the client
clientSocket.Send(Encoding.UTF8.GetBytes(receivedData));
}
Console.WriteLine("Client disconnected");
clientSocket.Close();
}
}
+57 -43
View File
@@ -1,43 +1,57 @@
using BCG;
using bts.utils;
using mbts.config;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
bool run_echo_tcp_server_only = true;
Configuration.initialize();
if (run_echo_tcp_server_only) {
Console.WriteLine("Running as TCP Echo server, Hit enter to continue");
Console.ReadLine();
var port = 12345;
Console.WriteLine($"ready to listen to port: {port}");
SimpleTCPServer sts = new SimpleTCPServer(port);
sts.init();
Task.Delay(1000000000).Wait();
}
PubSub3Config config = PubSub3Config.instance;
long currentMemory = 0;
int defaultWaitTime = 100;
bool gced = false;
new Thread(() => {
while (true) {
gced = false;
currentMemory = GC.GetTotalMemory(false);
if (config.PS3_GCActivationThreshold > 0 && currentMemory > config.PS3_GCActivationThreshold) {
Console.WriteLine($"GCing memory, mem before GC = {currentMemory}");
GC.Collect();
gced = true;
}
Thread.Sleep(gced ? config.PS3_WaitPeriodAfterGC : defaultWaitTime);
}
}).Start();
PubSubMessageReactor pubSubServer = new PubSubMessageReactor();
pubSubServer.start();
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
//using BCG.Utilities;
//using BCG.Utilities.General;
namespace BCG {
class PubSub2Main {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(string[] args) {
bool run_echo_tcp_server_only = false;
bts.utils.BTSNativeMethods.provide_command_line(new string[] { }, 0);
mbts.config.Configuration.initialize();
if (run_echo_tcp_server_only) {
Console.WriteLine("Running as TCP Echo server, Hit enter to continue");
Console.ReadLine();
SimpleTCPServer sts = new SimpleTCPServer(12345);
sts.init();
Task.Delay(1000000000).Wait();
}
if (args.Length > 0 && args[0] == "daemon")
args = args.Skip(1).ToArray();
//CoreServicesInit.init();
PubSub3Config config = PubSub3Config.instance;
long currentMemory = 0;
int defaultWaitTime = 100;
bool gced = false;
new Thread(() => {
while (true) {
gced = false;
currentMemory = GC.GetTotalMemory(false);
if (config.PS3_GCActivationThreshold > 0 && currentMemory > config.PS3_GCActivationThreshold) {
string gcMsg = String.Format("GCing memory, mem before GC = {0}", currentMemory);
// BlueEventLog.logEvent(gcMsg, PS3LogLevels.info);
Console.WriteLine(gcMsg);
GC.Collect();
gced = true;
}
Thread.Sleep(gced ? config.PS3_WaitPeriodAfterGC : defaultWaitTime);
}
}).Start();
PubSubMessageReactor pubSubServer = new PubSubMessageReactor();
pubSubServer.start();
}
}
}
@@ -1,127 +1,125 @@
namespace BCG {
using BCG.Utilities.General;
using bts.api;
using bts.api.domain;
using bts.api.synthetic;
using bts.md;
using bts.spark;
using bts.utils;
using mbts;
using mbts.co2;
using mbts.config;
using mbts.spark;
using mbts.ui;
using Spark.Core.Data;
using Spark.Core.Disposables;
using Spark.Core.Reactive;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using bts.api.core;
using Spark.Api.MarketData;
using SecurityKey = System.UInt32;
using System.Data;
using System.Reflection.Metadata;
[DataStreamUserRespresentation("price_stats")]
public class PriceStatsStream : ViewStream, IWorker {
private readonly TimeSpan _poll_period = TimeSpan.FromMilliseconds(500);
private DateTime _last_poll_time = DateTime.MinValue;
private ViewStreamDatabase _vsd;
private int _id = 0;
IReadOnlyDictionary<SecurityKey, PriceStats> _cached_state = null;
MarketDataClient _md_client;
private bool _dirty = false;
private readonly IBlueTableSchema _pstats_scheam = BlueDataUtility.form_schema("bbo",
Tuple.Create("id", TypeCode.Int32),
Tuple.Create("sec_key", TypeCode.Int32),
Tuple.Create("sec", TypeCode.String),
Tuple.Create("open", TypeCode.UInt32),
Tuple.Create("close", TypeCode.Double),
Tuple.Create("settlement", TypeCode.Single));
IDisposable? subscription;
// TODO: add cons for symbol, exchange etc
public PriceStatsStream()
: this(ViewStreamDatabase.instance) {
}
public PriceStatsStream(ViewStreamDatabase vsd) {
bts.Log.Gen.info("PriceStatsStream.cons");
_vsd = vsd;
_md_client = new MarketDataClient(new MDSource(TimeSpan.FromSeconds(0.5)));
}
protected override void myOnFirstSubscriber() {
_vsd.addWorker(this);
subscription = _md_client.price_stats().Subscribe(_on_price_stats);
}
private void _log_updates(TableUpdate<PriceStats, uint> update) {
int i = 0;
// foreach (BookTuple bt in update.added) {
// bts.Log.Gen.info($"PriceStatsStream added: idx: {i++} bid: {bt.book.bids[0].price} / {bt.book.bids[0].size} ask: {bt.book.asks[0].price} / {bt.book.asks[0].size}");
// }
// foreach (Change<BookTuple> c in update.changes) {
// var bt = c.New;
// bts.Log.Gen.info($"PriceStatsStream changes: idx: {i} bid: {bt.book.bids[0].price} / {bt.book.bids[0].size} ask: {bt.book.asks[0].price} / {bt.book.asks[0].size}");
// }
}
private void _on_price_stats(TableUpdate<PriceStats, uint> update) {
_log_updates(update);
// just cacching in here, will populate the rows in _poll_scan and fire the datapacket
bts.Log.Gen.info($"PriceStatsStream._on_price_stats state: {update.state.Count()} changes: {update.changes.Count()} added: {update.added.Count()}");
_cached_state = update.state;
_dirty = true;
}
protected override void myOnLastSubscriber() {
_vsd.removeWorker(this);
Disposable.dispose(ref subscription);
}
private void _scan() {
// this shouldn be made incremental using the TableUpdate
bts.Log.Gen.info("PriceStatsStream._scan_bbo");
if (_cached_state == null)
return;
BlueTable new_state = new BlueTable(_pstats_scheam);
foreach (var kv in _cached_state) {
SecurityKey skey = kv.Key;
int id = Convert.ToInt32(skey);
PriceStats pstats = kv.Value;
IBlueRow r = new BlueRow(_pstats_scheam, id, skey, pstats.security.ToString(), pstats.open, pstats.close, pstats.settlement);
new_state.addRow(r);
}
DataPacket dp = null;
if (myLastValue == null) {
dp = new DataPacket(new_state, new_state, new BlueTable(_pstats_scheam));
} else {
DataPacket old_dp = myLastValue as DataPacket;
IBlueRowCollection added_rows, del_rows;
BlueDataUtility.compute_differences_fast(new_state, old_dp.newRows, out del_rows, out added_rows);
dp = new DataPacket(new_state, added_rows, del_rows);
}
if (dp.addedRows.Count == 0 && dp.deletedRows.Count == 0)
return;
myFireEvent(dp);
}
public void doWork() {
if (_dirty && DateTime.Now - _last_poll_time > _poll_period) {
_last_poll_time = DateTime.Now;
_dirty = false;
_scan();
}
}
}
}
namespace BCG {
using BCG.Utilities.General;
using bts.api;
using bts.api.domain;
using bts.api.synthetic;
using bts.md;
using bts.spark;
using bts.utils;
using mbts;
using mbts.co2;
using mbts.config;
using mbts.spark;
using mbts.ui;
using Spark.Core.Data;
using Spark.Core.Disposables;
using Spark.Core.Reactive;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using bts.api.core;
using Spark.Api.MarketData;
using SecurityKey = System.UInt32;
using System.Data;
using System.Reflection.Metadata;
[DataStreamUserRespresentation("price_stats")]
public class PriceStatsStream : ViewStream, IWorker {
private readonly TimeSpan _poll_period = TimeSpan.FromMilliseconds(500);
private DateTime _last_poll_time = DateTime.MinValue;
private ViewStreamDatabase _vsd;
private int _id = 0;
IReadOnlyDictionary<SecurityKey, PriceStats> _cached_state = null;
MarketDataClient _md_client;
private bool _dirty = false;
private readonly IBlueTableSchema _pstats_scheam = BlueDataUtility.form_schema("bbo",
Tuple.Create("id", TypeCode.Int32),
Tuple.Create("sec_key", TypeCode.Int32),
Tuple.Create("sec", TypeCode.String),
Tuple.Create("open", TypeCode.UInt32),
Tuple.Create("close", TypeCode.Double),
Tuple.Create("settlement", TypeCode.Single));
// TODO: add cons for symbol, exchange etc
public PriceStatsStream()
: this(ViewStreamDatabase.instance) {
}
public PriceStatsStream(ViewStreamDatabase vsd) {
bts.Log.Gen.info("PriceStatsStream.cons");
_vsd = vsd;
_md_client = new MarketDataClient(new MDSource(TimeSpan.FromSeconds(0.5)));
}
protected override void myOnFirstSubscriber() {
_vsd.addWorker(this);
_md_client.price_stats().Subscribe(_on_price_stats);
}
private void _log_updates(TableUpdate<PriceStats, uint> update) {
int i = 0;
// foreach (BookTuple bt in update.added) {
// bts.Log.Gen.info($"PriceStatsStream added: idx: {i++} bid: {bt.book.bids[0].price} / {bt.book.bids[0].size} ask: {bt.book.asks[0].price} / {bt.book.asks[0].size}");
// }
// foreach (Change<BookTuple> c in update.changes) {
// var bt = c.New;
// bts.Log.Gen.info($"PriceStatsStream changes: idx: {i} bid: {bt.book.bids[0].price} / {bt.book.bids[0].size} ask: {bt.book.asks[0].price} / {bt.book.asks[0].size}");
// }
}
private void _on_price_stats(TableUpdate<PriceStats, uint> update) {
_log_updates(update);
// just cacching in here, will populate the rows in _poll_scan and fire the datapacket
bts.Log.Gen.info($"PriceStatsStream._on_price_stats state: {update.state.Count()} changes: {update.changes.Count()} added: {update.added.Count()}");
_cached_state = update.state;
_dirty = false;
}
protected override void myOnLastSubscriber() {
_vsd.removeWorker(this);
}
private void _scan() {
// this shouldn be made incremental using the TableUpdate
bts.Log.Gen.info("PriceStatsStream._scan_bbo");
if (_cached_state == null)
return;
BlueTable new_state = new BlueTable(_pstats_scheam);
foreach (var kv in _cached_state) {
SecurityKey skey = kv.Key;
int id = Convert.ToInt32(skey);
PriceStats pstats = kv.Value;
IBlueRow r = new BlueRow(_pstats_scheam, id, skey, pstats.security.ToString(), pstats.open, pstats.close, pstats.settlement);
new_state.addRow(r);
}
DataPacket dp = null;
if (myLastValue == null) {
dp = new DataPacket(new_state, new_state, new BlueTable(_pstats_scheam));
} else {
DataPacket old_dp = myLastValue as DataPacket;
IBlueRowCollection added_rows, del_rows;
BlueDataUtility.compute_differences_fast(new_state, old_dp.newRows, out del_rows, out added_rows);
dp = new DataPacket(new_state, added_rows, del_rows);
}
if (dp.addedRows.Count == 0 && dp.deletedRows.Count == 0)
return;
myFireEvent(dp);
}
public void doWork() {
if (_dirty && DateTime.Now - _last_poll_time > _poll_period) {
_last_poll_time = DateTime.Now;
_dirty = false;
_scan();
}
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<RuntimeIdentifiers>any</RuntimeIdentifiers>
<RuntimeIdentifiers>win10-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
<Authors>Blue Trading Systems</Authors>
<Company>Blue Trading Systems</Company>
<Product>Spark</Product>
+45 -45
View File
@@ -1,45 +1,45 @@
namespace Spark.Console {
using bts;
using bts.utils;
using System;
using CommandLine;
class Program {
static int Main(string[] args) {
// Initializing config is up to each individual command
// We call provide_command_line here in case config is first
// initialized from c++ instead of c#
BTSNativeMethods.provide_command_line(args, args.Length);
var parser = new Parser(ps => {
ps.CaseInsensitiveEnumValues = true;
ps.HelpWriter = Console.Out;
});
// Note: this can be made to infer the options type list from the list of delegates
return parser.parse_and_run<
ConfigCommandOptions,
ScriptCommandOptions,
TailServiceOptions,
TestServiceOptions,
SettingsCommandOptions,
TestCommandOptions,
OrderStressOptions,
MBOCommandOptions,
int
>(
args,
ConfigCommand.run,
ScriptCommand.run,
TailService.run,
TestService.run,
SettingsCommand.run,
TestCommand.run,
OrderStressCommand.run,
MBOCommand.run,
errors => 1);
}
}
}
namespace Spark.Console {
using bts;
using bts.utils;
using System;
using CommandLine;
class Program {
static int Main(string[] args) {
// Initializing config is up to each individual command
// We call provide_command_line here in case config is first
// initialized from c++ instead of c#
BTSNativeMethods.provide_command_line(args, args.Length);
var parser = new Parser(ps => {
ps.CaseInsensitiveEnumValues = true;
ps.HelpWriter = Console.Out;
});
// Note: this can be made to infer the options type list from the list of delegates
return parser.parse_and_run<
ConfigCommandOptions,
ScriptCommandOptions,
TailServiceOptions,
TestServiceOptions,
SettingsCommandOptions,
TestCommandOptions,
OrderStressOptions,
MBOCommandOptions,
int
>(
BTS.remove_config_arg(args),
ConfigCommand.run,
ScriptCommand.run,
TailService.run,
TestService.run,
SettingsCommand.run,
TestCommand.run,
OrderStressCommand.run,
MBOCommand.run,
errors => 1);
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<PlatformTarget>x64</PlatformTarget>
<LanguageTargets Condition="'$(VisualStudioVersion)'=='17.0'">
C:/Program Files/Microsoft Visual Studio/2022/Community/Msbuild/Current/Bin/amd64/Microsoft.CSharp.targets
@@ -70,7 +70,8 @@ namespace mbts.ui.controls {
public double border_thickness => _border_thickness.Value;
void _apply_animation() {
if (_animations.Select(a => a.background_animation).ExceptNull().TryGetFirst(out var bga)) {
var bga = _animations.OfType<BackgroundAnimation>().FirstOrDefault();
if (bga != null) {
_background.set(bga.brush);
if (bga.pen != null) {
_border.set(bga.pen.Brush);
@@ -1,280 +1,287 @@
#nullable enable
namespace bts.md {
using bts.api;
using bts.api.core;
using bts.collections;
using bts.utils;
using mbts;
using mbts.co2;
using mbts.spark;
using Spark.Api.MarketData;
using Spark.Core;
using Spark.Core.Reactive;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public class MarketDataClient {
readonly MDSource _source;
readonly TimeSpan _poll_interval;
readonly TimeSpan _slow_poll_interval;
readonly uint _num_depth_levels;
public MarketDataClient(MDSource source)
: this(source, 5, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(1)) { }
public MarketDataClient(
MDSource source,
uint num_depth_levels,
TimeSpan poll_interval,
TimeSpan slow_poll_interval) {
_source = source;
_poll_interval = poll_interval;
if (slow_poll_interval < poll_interval) {
Log.Api.warn("MarketDataClient 'slow_poll_interval' smaller than 'poll_interval'");
}
_slow_poll_interval = slow_poll_interval;
_num_depth_levels = num_depth_levels;
}
public IObservable<IReadOnlyDictionary<uint, SecurityImmutable>> securities(
SecurityCriteria? criteria = null) =>
criteria.subscribe(_source.subscribe_securities())
.Select(secs => secs.ToDictionary(s => s.Id));
public IObservable<TableUpdate<SecurityImmutable, uint>> securities_incremental(
SecurityCriteria? criteria = null) =>
criteria is null ? securities_incremental() : securities_incremental(criteria.includes);
public IObservable<TableUpdate<SecurityImmutable, uint>> securities_incremental(
Func<SecurityImmutable, bool> filter) {
var secs_src = _source.subscribe_securities().ToSelector(
sec_mds => MappedDictionary.create(sec_mds, s => s.security));
if (filter is not null) {
secs_src = secs_src.extend(
secs => new FilteredDictionary<uint, SecurityImmutable>(secs, filter));
}
return secs_src.Table(s => s.Id);
}
public IObservable<TableUpdate<BookTuple, uint>> books(SecurityCriteria? criteria = null) =>
books(criteria is null ? null :
new Func<SecurityImmutable, bool>(s => criteria.includes(s)));
public IObservable<TableUpdate<BookTuple, uint>> books(Func<SecurityImmutable, bool>? filter) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect book changes. To avoid maintaining complicated state, create a brand new
// IObservable of book updates whenever the securities change.
var secs = _source.subscribe_securities();
var filtered_secs = filter is null ?
secs.Select(mds => mds.Values) :
secs.Select(mds => mds.Values.Where(md => filter(md.security)));
return filtered_secs.Select(fixed_sec_market_data).FlattenTableSeries();
IObservable<TableUpdate<BookTuple, uint>> fixed_sec_market_data(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollableBooks.
var cached_books = security_mds
.Select(s => new PollableBook(s, _num_depth_levels))
.ToList();
return Observable.Interval(_poll_interval).Scan(
TableUpdate<BookTuple, uint>.mk_empty(get_sec_id),
poll_books);
uint get_sec_id(BookTuple book) =>
book.security.Id;
TableUpdate<BookTuple, uint> poll_books(TableUpdate<BookTuple, uint> table, long _) {
var bd = table.to_builder();
foreach (var cbook in cached_books) {
if (cbook.poll_changes()) {
bd.add_or_update(cbook.to_book_tuple());
}
}
return bd.to_table();
}
}
}
public IObservable<IList<TradeInfo>> trades(TradesSubscriptionFilter filter) {
var sec_mds = filter.security_criteria == null ?
_source.subscribe_securities() :
filter.security_criteria.subscribe_md_map(_source.subscribe_securities());
return TradesSource.create(_source.consumer, sec_mds, _poll_interval);
}
public IObservable<TableUpdate<PriceTicker, uint>> price_ticker(
SecurityCriteria? criteria = null) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect ticker changes. To avoid maintaining complicated state, create a brand new
// IObservable of ticker updates whenever the securities change.
return criteria.subscribe_md(_source.subscribe_securities())
.Select(fixed_sec_price_tickers)
.FlattenTableSeries();
IObservable<TableUpdate<PriceTicker, uint>> fixed_sec_price_tickers(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollablePriceTickers.
var cached_tickers = security_mds
.Select(s => new PollablePriceTicker(s))
.ToList();
return Observable.Interval(_poll_interval).Scan(
TableUpdate<PriceTicker, uint>.mk_empty(get_sec_id),
poll_tickers);
uint get_sec_id(PriceTicker ticker) =>
ticker.security.Id;
TableUpdate<PriceTicker, uint> poll_tickers(TableUpdate<PriceTicker, uint> table, long _) {
bool is_first_update = table.state.Count == 0;
var bd = table.to_builder();
foreach (var cticker in cached_tickers) {
if (cticker.poll_changes() || is_first_update) {
bd.add_or_update(cticker.to_ticker());
}
}
return bd.to_table();
}
}
}
public IObservable<TableUpdate<PriceStats, uint>> price_stats(
SecurityCriteria? criteria = null) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect stats changes. To avoid maintaining complicated state, create a brand new
// IObservable of stats updates whenever the securities change.
return criteria.subscribe_md(_source.subscribe_securities())
.Select(fixed_sec_price_stats)
.FlattenTableSeries();
IObservable<TableUpdate<PriceStats, uint>> fixed_sec_price_stats(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollablePriceStats.
var cached_statss = security_mds
.Select(s => new PollablePriceStats(s))
.ToList();
return Observable.Interval(_slow_poll_interval).Scan(
TableUpdate<PriceStats, uint>.mk_empty(get_sec_id),
poll_stats);
uint get_sec_id(PriceStats stats) =>
stats.security.Id;
TableUpdate<PriceStats, uint> poll_stats(TableUpdate<PriceStats, uint> table, long _) {
bool is_first_update = table.state.Count == 0;
var bd = table.to_builder();
foreach (var cstats in cached_statss) {
if (cstats.poll_changes() || is_first_update) {
bd.add_or_update(cstats.to_stats());
}
}
return bd.to_table();
}
}
}
class PollableBook {
readonly SecurityMD _security_md;
readonly uint _num_depth_levels;
ulong _last_update_time = 0;
// Reuse the same arrays as an optimization to avoid reallocating memory on each poll.
// Note: this will break if the code isn't called in a thread-safe manner.
NativePriceSize[] _bids;
NativePriceSize[] _asks;
public PollableBook(SecurityMD security_md, uint num_depth_levels) {
_security_md = security_md;
_num_depth_levels = num_depth_levels;
_bids = new NativePriceSize[_num_depth_levels];
_asks = new NativePriceSize[_num_depth_levels];
}
public BookTuple to_book_tuple() {
var book = new Book(
_security_md.security.Id, _to_book_side(_bids), _to_book_side(_asks));
return new(_security_md.security, book, _security_md.quantity_display);
}
public bool poll_changes() =>
_security_md.md.get_depth(
ref _last_update_time, ref _bids, ref _asks, _num_depth_levels);
static List<PriceSize> _to_book_side(NativePriceSize[] native_book_side) =>
native_book_side
.Select(nps => new PriceSize(nps.size, nps.direct_size, nps.implied_size, nps.price))
.ToList();
}
class PollablePriceTicker {
readonly SecurityMD _security_md;
ulong _bbo_poll_time = 0;
ulong _last_poll_time = 0;
NativePriceSize _bid;
NativePriceSize _ask;
NativePriceSize _last;
PriceTicker _ticker;
ulong _volume;
public PollablePriceTicker(SecurityMD security_md) =>
_security_md = security_md;
public PriceTicker to_ticker() =>
_ticker;
public bool poll_changes() {
var md = _security_md.md;
md.get_bbo(ref _bbo_poll_time, out _bid, out _ask);
md.get_last_trade(ref _last_poll_time, out _last, out _, out _volume);
return Swap.toggle_to(ref _ticker, _mk_ticker());
}
PriceTicker _mk_ticker() {
var md = _security_md.md;
return new(
_security_md.security,
_security_md.quantity_display,
md.session_high,
md.session_low,
_volume,
_bid.price,
_bid.size,
_ask.price,
_ask.size,
md.fixing_price,
_last.price,
_last.size);
}
}
class PollablePriceStats {
readonly SecurityMD _security_md;
PriceStats _stats;
public PollablePriceStats(SecurityMD security_md) {
_security_md = security_md;
_stats = _mk_stats();
}
public PriceStats to_stats() =>
_stats;
public bool poll_changes() =>
Swap.toggle_to(ref _stats, _mk_stats());
PriceStats _mk_stats() {
var md = _security_md.md;
return new(
_security_md.security,
md.open_price,
md.close_price,
md.settlement_price);
}
}
}
}
#nullable enable
namespace bts.md {
using bts.api;
using bts.api.core;
using bts.collections;
using mbts;
using mbts.co2;
using mbts.spark;
using Spark.Api.MarketData;
using Spark.Core;
using Spark.Core.Reactive;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public class MarketDataClient {
readonly MDSource _source;
readonly TimeSpan _poll_interval;
readonly TimeSpan _slow_poll_interval;
readonly uint _num_depth_levels;
public MarketDataClient(MDSource source)
: this(source, 5, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(1)) { }
public MarketDataClient(
MDSource source,
uint num_depth_levels,
TimeSpan poll_interval,
TimeSpan slow_poll_interval) {
_source = source;
_poll_interval = poll_interval;
if (slow_poll_interval < poll_interval) {
Log.Api.warn("MarketDataClient 'slow_poll_interval' smaller than 'poll_interval'");
}
_slow_poll_interval = slow_poll_interval;
_num_depth_levels = num_depth_levels;
}
public IObservable<IReadOnlyDictionary<uint, SecurityImmutable>> securities(
SecurityCriteria? criteria = null) =>
criteria.subscribe(_source.subscribe_securities())
.Select(secs => secs.ToDictionary(s => s.Id));
public IObservable<TableUpdate<SecurityImmutable, uint>> securities_incremental(
SecurityCriteria? criteria = null) =>
criteria is null ? securities_incremental() : securities_incremental(criteria.includes);
public IObservable<TableUpdate<SecurityImmutable, uint>> securities_incremental(
Func<SecurityImmutable, bool> filter) {
var secs_src = _source.subscribe_securities().ToSelector(
sec_mds => MappedDictionary.create(sec_mds, s => s.security));
if (filter is not null) {
secs_src = secs_src.extend(
secs => new FilteredDictionary<uint, SecurityImmutable>(secs, filter));
}
return secs_src.Table(s => s.Id);
}
public IObservable<TableUpdate<BookTuple, uint>> books(SecurityCriteria? criteria = null) =>
books(criteria is null ? null :
new Func<SecurityImmutable, bool>(s => criteria.includes(s)));
public IObservable<TableUpdate<BookTuple, uint>> books(Func<SecurityImmutable, bool>? filter) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect book changes. To avoid maintaining complicated state, create a brand new
// IObservable of book updates whenever the securities change.
var secs = _source.subscribe_securities();
var filtered_secs = filter is null ?
secs.Select(mds => mds.Values) :
secs.Select(mds => mds.Values.Where(md => filter(md.security)));
return filtered_secs.Select(fixed_sec_market_data).FlattenTableSeries();
IObservable<TableUpdate<BookTuple, uint>> fixed_sec_market_data(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollableBooks.
var cached_books = security_mds
.Select(s => new PollableBook(s, _num_depth_levels))
.ToList();
return Observable.Interval(_poll_interval).Scan(
TableUpdate<BookTuple, uint>.mk_empty(get_sec_id),
poll_books);
uint get_sec_id(BookTuple book) =>
book.security.Id;
TableUpdate<BookTuple, uint> poll_books(TableUpdate<BookTuple, uint> table, long _) {
var bd = table.to_builder();
foreach (var cbook in cached_books) {
if (cbook.poll_changes()) {
bd.add_or_update(cbook.to_book_tuple());
}
}
return bd.to_table();
}
}
}
public IObservable<IList<TradeInfo>> trades(TradesSubscriptionFilter filter) {
var sec_mds = filter.security_criteria == null ?
_source.subscribe_securities() :
filter.security_criteria.subscribe_md_map(_source.subscribe_securities());
return TradesSource.create(_source.consumer, sec_mds, _poll_interval);
}
public IObservable<TableUpdate<PriceTicker, uint>> price_ticker(
SecurityCriteria? criteria = null) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect ticker changes. To avoid maintaining complicated state, create a brand new
// IObservable of ticker updates whenever the securities change.
return criteria.subscribe_md(_source.subscribe_securities())
.Select(fixed_sec_price_tickers)
.FlattenTableSeries();
IObservable<TableUpdate<PriceTicker, uint>> fixed_sec_price_tickers(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollablePriceTickers.
var cached_tickers = security_mds
.Select(s => new PollablePriceTicker(s))
.ToList();
return Observable.Interval(_poll_interval).Scan(
TableUpdate<PriceTicker, uint>.mk_empty(get_sec_id),
poll_tickers);
uint get_sec_id(PriceTicker ticker) =>
ticker.security.Id;
TableUpdate<PriceTicker, uint> poll_tickers(TableUpdate<PriceTicker, uint> table, long _) {
var bd = table.to_builder();
foreach (var cticker in cached_tickers) {
if (cticker.poll_changes()) {
bd.add_or_update(cticker.to_ticker());
}
}
return bd.to_table();
}
}
}
public IObservable<TableUpdate<PriceStats, uint>> price_stats(
SecurityCriteria? criteria = null) {
// This MDSource's IObservable provides callbacks when a security is added or removed, but
// doesn't detect stats changes. To avoid maintaining complicated state, create a brand new
// IObservable of stats updates whenever the securities change.
return criteria.subscribe_md(_source.subscribe_securities())
.Select(fixed_sec_price_stats)
.FlattenTableSeries();
IObservable<TableUpdate<PriceStats, uint>> fixed_sec_price_stats(
IEnumerable<SecurityMD> security_mds) {
// Convert 'security_mds' to a list of PollablePriceStats.
List<PollablePriceStats>? cached_statss = security_mds
.Select(s => new PollablePriceStats(s))
.ToList();
var seed = TableUpdate<PriceStats, uint>.mk_empty(get_sec_id);
seed = get_seed(seed);
return Observable.Interval(_slow_poll_interval).Scan(
seed,
poll_stats);
uint get_sec_id(PriceStats stats) =>
stats.security.Id;
TableUpdate<PriceStats, uint> get_seed(TableUpdate<PriceStats, uint> table) {
if (cached_statss == null) {
return table;
}
var bd = table.to_builder();
foreach (var cstats in cached_statss) {
bd.add_or_update(cstats.to_stats());
}
return bd.to_table();
}
TableUpdate<PriceStats, uint> poll_stats(TableUpdate<PriceStats, uint> table, long _) {
var bd = table.to_builder();
foreach (var cstats in cached_statss) {
if (cstats.poll_changes()) {
bd.add_or_update(cstats.to_stats());
}
}
return bd.to_table();
}
}
}
class PollableBook {
readonly SecurityMD _security_md;
readonly uint _num_depth_levels;
ulong _last_update_time = 0;
// Reuse the same arrays as an optimization to avoid reallocating memory on each poll.
// Note: this will break if the code isn't called in a thread-safe manner.
NativePriceSize[] _bids;
NativePriceSize[] _asks;
public PollableBook(SecurityMD security_md, uint num_depth_levels) {
_security_md = security_md;
_num_depth_levels = num_depth_levels;
_bids = new NativePriceSize[_num_depth_levels];
_asks = new NativePriceSize[_num_depth_levels];
}
public BookTuple to_book_tuple() {
var book = new Book(
_security_md.security.Id, _to_book_side(_bids), _to_book_side(_asks));
return new(_security_md.security, book, _security_md.quantity_display);
}
public bool poll_changes() =>
_security_md.md.get_depth(
ref _last_update_time, ref _bids, ref _asks, _num_depth_levels);
static List<PriceSize> _to_book_side(NativePriceSize[] native_book_side) =>
native_book_side
.Select(nps => new PriceSize(nps.size, nps.direct_size, nps.implied_size, nps.price))
.ToList();
}
class PollablePriceTicker {
readonly SecurityMD _security_md;
ulong _bbo_poll_time = 0;
ulong _last_poll_time = 0;
NativePriceSize _bid;
NativePriceSize _ask;
NativePriceSize _last;
PriceTicker _ticker;
ulong _volume;
public PollablePriceTicker(SecurityMD security_md) =>
_security_md = security_md;
public PriceTicker to_ticker() =>
_ticker;
public bool poll_changes() {
var md = _security_md.md;
md.get_bbo(ref _bbo_poll_time, out _bid, out _ask);
md.get_last_trade(ref _last_poll_time, out _last, out _, out _volume);
return Swap.toggle_to(ref _ticker, _mk_ticker());
}
PriceTicker _mk_ticker() {
var md = _security_md.md;
return new(
_security_md.security,
_security_md.quantity_display,
md.session_high,
md.session_low,
_volume,
_bid.price,
_bid.size,
_ask.price,
_ask.size,
md.fixing_price,
_last.price,
_last.size);
}
}
class PollablePriceStats {
readonly SecurityMD _security_md;
PriceStats _stats;
public PollablePriceStats(SecurityMD security_md) {
_security_md = security_md;
_stats = _mk_stats();
}
public PriceStats to_stats() =>
_stats;
public bool poll_changes() =>
Swap.toggle_to(ref _stats, _mk_stats());
PriceStats _mk_stats() {
var md = _security_md.md;
return new(
_security_md.security,
md.open_price,
md.close_price,
md.settlement_price);
}
}
}
}
+110 -109
View File
@@ -1,109 +1,110 @@
#nullable enable
namespace bts.api.core {
using bts.api.user;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
public sealed class ServiceContext : Spark.Api.IServiceProvider, IDisposable {
IntPtr _qbus;
AuthToken? _token;
public ServiceContext(string? config_source = null) {
BTS.init(config_source);
_qbus = ApiNativeMethods.api_create_quickbus(null, 0);
}
internal ServiceContext(IntPtr qbus) =>
_qbus = qbus;
public AuthTokenImmutable? token => _token?.immutable();
public UserTuple? user { get; private set; }
public bool is_authenticated => _token is not null;
internal IntPtr native_context => _qbus;
public static ServiceContext create(string? config_server = null) {
BTS.init(config_server);
var qbus = ApiNativeMethods.api_create_quickbus(null, 0);
return new ServiceContext(qbus);
}
public T locate<T>(string qualifier = "") where T : class, IApiClient {
var ctr = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(ci => ci.GetParameters()
.SingleOrDefault()?.ParameterType == typeof(ClientChannel));
if (ctr is null) {
throw new InvalidOperationException(
$"Unable to find appropriate constructor for type '{typeof(T)}'");
}
ClientChannel channel = new QuickbusClientChannel(this, typeof(T).Name, qualifier);
return (T)ctr.Invoke(new object[] { channel });
}
public T GetService<T>(string? qualifier) where T : class, IApiClient =>
locate<T>(qualifier ?? string.Empty);
public async Task<bool> try_login(string username, string password) {
var res = await login(username, password);
return res.success;
}
public async Task<LoginResult> login(string username, string password) {
var res = await impl();
if (!res.success) {
Log.Api.error($"Login failed: for username: {username} password: {password} reason: {res.reason}");
}
return res;
async Task<LoginResult> impl() {
var auth_service = locate<AuthService>();
var result = await auth_service.authenticate_login_async(
new AuthenticateLoginRequest() {
Login = username ?? string.Empty,
Password = password ?? string.Empty
});
if (result.success) {
if (result.value.IsValid) {
_token = result.value.Token;
user = new(result.value.UserId, username);
return new LoginResult(result.value.Reason, true);
} else {
return new LoginResult(result.value.Reason);
}
} else {
return new LoginResult(result.error_message);
}
}
}
public void login(AuthTokenImmutable token) {
user = UserTuple.empty;
_token = token.CloneToMutable();
}
public void Dispose() {
if (_qbus != IntPtr.Zero) {
ApiNativeMethods.api_destroy_quickbus(_qbus);
_qbus = IntPtr.Zero;
}
}
internal ServerChannel publish<T>(T service, string service_type, string qualifier) where T : IApiServer {
var ch = new ServerChannel(this, service_type, qualifier ?? "");
ch.on_request += service.dispatch;
return ch;
}
}
public class LoginResult {
public LoginResult() { success = true; }
public LoginResult(string reason) { success = false; this.reason = reason; }
public LoginResult(string reason, bool success) { this.success = success; this.reason = reason; }
public bool success { get; }
public string? reason { get; }
}
}
#nullable enable
namespace bts.api.core {
using bts.api.user;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
public sealed class ServiceContext : Spark.Api.IServiceProvider, IDisposable {
IntPtr _qbus;
AuthToken? _token;
public ServiceContext(string? config_source = null) {
BTS.init(config_source);
_qbus = ApiNativeMethods.api_create_quickbus(null, 0);
}
internal ServiceContext(IntPtr qbus) =>
_qbus = qbus;
public AuthTokenImmutable? token => _token?.immutable();
public UserTuple? user { get; private set; }
public bool is_authenticated => _token is not null;
internal IntPtr native_context => _qbus;
public static ServiceContext create(string? config_server = null) {
var args = Environment.GetCommandLineArgs();
BTS.init(config_server, args);
var qbus = ApiNativeMethods.api_create_quickbus(null, 0);
return new ServiceContext(qbus);
}
public T locate<T>(string qualifier = "") where T : class, IApiClient {
var ctr = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(ci => ci.GetParameters()
.SingleOrDefault()?.ParameterType == typeof(ClientChannel));
if (ctr is null) {
throw new InvalidOperationException(
$"Unable to find appropriate constructor for type '{typeof(T)}'");
}
ClientChannel channel = new QuickbusClientChannel(this, typeof(T).Name, qualifier);
return (T)ctr.Invoke(new object[] { channel });
}
public T GetService<T>(string? qualifier) where T : class, IApiClient =>
locate<T>(qualifier ?? string.Empty);
public async Task<bool> try_login(string username, string password) {
var res = await login(username, password);
return res.success;
}
public async Task<LoginResult> login(string username, string password) {
var res = await impl();
if (!res.success) {
Log.Api.error($"Login failed: for username: {username} password: {password} reason: {res.reason}");
}
return res;
async Task<LoginResult> impl() {
var auth_service = locate<AuthService>();
var result = await auth_service.authenticate_login_async(
new AuthenticateLoginRequest() {
Login = username ?? string.Empty,
Password = password ?? string.Empty
});
if (result.success) {
if (result.value.IsValid) {
_token = result.value.Token;
user = new(result.value.UserId, username);
return new LoginResult(result.value.Reason, true);
} else {
return new LoginResult(result.value.Reason);
}
} else {
return new LoginResult(result.error_message);
}
}
}
public void login(AuthTokenImmutable token) {
user = UserTuple.empty;
_token = token.CloneToMutable();
}
public void Dispose() {
if (_qbus != IntPtr.Zero) {
ApiNativeMethods.api_destroy_quickbus(_qbus);
_qbus = IntPtr.Zero;
}
}
internal ServerChannel publish<T>(T service, string service_type, string qualifier) where T : IApiServer {
var ch = new ServerChannel(this, service_type, qualifier ?? "");
ch.on_request += service.dispatch;
return ch;
}
}
public class LoginResult {
public LoginResult() { success = true; }
public LoginResult(string reason) { success = false; this.reason = reason; }
public LoginResult(string reason, bool success) { this.success = success; this.reason = reason; }
public bool success { get; }
public string? reason { get; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Threading.Tasks;
using bts.api;
using bts.api.core;
namespace bts.api
{
public static class Locator
{
readonly static Lazy<ServiceContext> _context = new Lazy<ServiceContext>(() => new ServiceContext(), true);
public static T locate<T>(string qualifier="") where T : class, IApiClient
{
return _context.Value.locate<T>(qualifier);
}
static public async Task<LoginResult> login(string username, string password)
{
return await _context.Value.login(username, password);
}
static public void login_with_token(AuthTokenImmutable token)
{
_context.Value.login(token);
}
static public AuthTokenImmutable? token => _context.Value.token;
}
}
+1 -1
View File
@@ -40,7 +40,7 @@
DefaultEndPoint = "/",
Host = "titan",
Scheme = "http",
Port = 8080,
Port = "8080",
KeepAliveIntervalSecs = 0.1,
};
sync.init(gwy);
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more