diff --git a/.editorconfig b/.editorconfig
index 4943eb4b..0959e515 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,62 +1,34 @@
-# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
-# C# files
[*.{cs,csx}]
-
-#### Core EditorConfig Options ####
-
-# Indentation and spacing
indent_size = 4
indent_style = tab
tab_width = 4
-
-# New line preferences
end_of_line = lf
insert_final_newline = true
-
-#### .NET Code Actions ####
-
-# Type members
dotnet_hide_advanced_members = false
dotnet_member_insertion_location = with_other_members_of_the_same_kind
dotnet_property_generation_behavior = prefer_throwing_properties
-
-# Symbol search
dotnet_search_reference_assemblies = true
-
-#### .NET Coding Conventions ####
-
-# Organize usings
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = true
file_header_template = unset
-
-# this. and Me. preferences
dotnet_style_qualification_for_event = false:warning
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_property = false:warning
-
-# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = false:warning
-
-# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
-
-# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
-
-# Expression-level preferences
dotnet_prefer_system_hash_code = true:warning
dotnet_style_coalesce_expression = true:warning
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
-dotnet_style_namespace_match_folder = true
+dotnet_style_namespace_match_folder = true:warning
dotnet_style_null_propagation = true:warning
dotnet_style_object_initializer = true:warning
dotnet_style_operator_placement_when_wrapping = beginning_of_line
@@ -72,28 +44,14 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_non_hidden_explicit_cast_in_source = true
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true
-
-# Field preferences
dotnet_style_readonly_field = true
-
-# Parameter preferences
dotnet_code_quality_unused_parameters = all:warning
-
-# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
-
-# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
-
-#### C# Coding Conventions ####
-
-# var preferences
csharp_style_var_elsewhere = true
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
-
-# Expression-bodied members
csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_constructors = true:warning
csharp_style_expression_bodied_indexers = true:warning
@@ -102,26 +60,18 @@ csharp_style_expression_bodied_local_functions = true:warning
csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_operators = true:warning
csharp_style_expression_bodied_properties = true:warning
-
-# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true:warning
csharp_style_prefer_pattern_matching = true:warning
csharp_style_prefer_switch_expression = true:warning
-
-# Null-checking preferences
csharp_style_conditional_delegate_call = true:warning
-
-# Modifier preferences
csharp_prefer_static_anonymous_function = true
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
-
-# Code-block preferences
csharp_prefer_braces = when_multiline:warning
csharp_prefer_simple_using_statement = true:warning
csharp_prefer_system_threading_lock = true
@@ -130,8 +80,6 @@ csharp_style_prefer_method_group_conversion = true:warning
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_simple_property_accessors = true
csharp_style_prefer_top_level_statements = false:error
-
-# Expression-level preferences
csharp_prefer_simple_default_expression = true:warning
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true:error
@@ -147,20 +95,12 @@ csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
-
-# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:error
-
-# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning
-
-#### C# Formatting Rules ####
-
-# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
@@ -168,16 +108,12 @@ csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
-
-# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
-
-# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
@@ -200,128 +136,71 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
-
-# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
-
-#### Naming styles ####
-
-# Naming rules
-
dotnet_naming_rule.interfaces_should_be_begins_with_i.severity = error
dotnet_naming_rule.interfaces_should_be_begins_with_i.symbols = interfaces
dotnet_naming_rule.interfaces_should_be_begins_with_i.style = begins_with_i
-
dotnet_naming_rule.classes_should_be_pascalcase.severity = error
dotnet_naming_rule.classes_should_be_pascalcase.symbols = classes
dotnet_naming_rule.classes_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.structures_and_enums_should_be_pascalcase.severity = error
dotnet_naming_rule.structures_and_enums_should_be_pascalcase.symbols = structures_and_enums
dotnet_naming_rule.structures_and_enums_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.async_methods_should_be_ends_with_async.severity = warning
dotnet_naming_rule.async_methods_should_be_ends_with_async.symbols = async_methods
dotnet_naming_rule.async_methods_should_be_ends_with_async.style = ends_with_async
-
dotnet_naming_rule.methods__events__delegates_and_local_functions_should_be_pascalcase.severity = error
dotnet_naming_rule.methods__events__delegates_and_local_functions_should_be_pascalcase.symbols = methods__events__delegates_and_local_functions
dotnet_naming_rule.methods__events__delegates_and_local_functions_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.properties_should_be_pascalcase.severity = error
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.generics_should_be_begins_with_t.severity = warning
dotnet_naming_rule.generics_should_be_begins_with_t.symbols = generics
dotnet_naming_rule.generics_should_be_begins_with_t.style = begins_with_t
-
dotnet_naming_rule.constants_should_be_pascalcase.severity = error
dotnet_naming_rule.constants_should_be_pascalcase.symbols = constants
dotnet_naming_rule.constants_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = error
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
-
dotnet_naming_rule.fields__parameters_and_locals_should_be_camelcase.severity = error
dotnet_naming_rule.fields__parameters_and_locals_should_be_camelcase.symbols = fields__parameters_and_locals
dotnet_naming_rule.fields__parameters_and_locals_should_be_camelcase.style = camelcase
-
dotnet_naming_rule.namespaces_should_be_pascalcase.severity = warning
dotnet_naming_rule.namespaces_should_be_pascalcase.symbols = namespaces
dotnet_naming_rule.namespaces_should_be_pascalcase.style = pascalcase
-
-# Symbol specifications
-
dotnet_naming_symbols.namespaces.applicable_kinds = namespace
dotnet_naming_symbols.namespaces.applicable_accessibilities = *
-dotnet_naming_symbols.namespaces.required_modifiers =
-
dotnet_naming_symbols.classes.applicable_kinds = class
dotnet_naming_symbols.classes.applicable_accessibilities = *
-dotnet_naming_symbols.classes.required_modifiers =
-
dotnet_naming_symbols.structures_and_enums.applicable_kinds = struct, enum
dotnet_naming_symbols.structures_and_enums.applicable_accessibilities = *
-dotnet_naming_symbols.structures_and_enums.required_modifiers =
-
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
-dotnet_naming_symbols.interfaces.required_modifiers =
-
dotnet_naming_symbols.properties.applicable_kinds = property
dotnet_naming_symbols.properties.applicable_accessibilities = *
-dotnet_naming_symbols.properties.required_modifiers =
-
dotnet_naming_symbols.methods__events__delegates_and_local_functions.applicable_kinds = event, delegate, method, local_function
dotnet_naming_symbols.methods__events__delegates_and_local_functions.applicable_accessibilities = *
-dotnet_naming_symbols.methods__events__delegates_and_local_functions.required_modifiers =
-
dotnet_naming_symbols.fields__parameters_and_locals.applicable_kinds = field, parameter, local
dotnet_naming_symbols.fields__parameters_and_locals.applicable_accessibilities = internal, private, protected, protected_internal, private_protected, local
-dotnet_naming_symbols.fields__parameters_and_locals.required_modifiers =
-
dotnet_naming_symbols.generics.applicable_kinds = type_parameter
dotnet_naming_symbols.generics.applicable_accessibilities = *
-dotnet_naming_symbols.generics.required_modifiers =
-
dotnet_naming_symbols.constants.applicable_kinds = field
dotnet_naming_symbols.constants.applicable_accessibilities = *
dotnet_naming_symbols.constants.required_modifiers = const
-
dotnet_naming_symbols.async_methods.applicable_kinds = method
dotnet_naming_symbols.async_methods.applicable_accessibilities = *
dotnet_naming_symbols.async_methods.required_modifiers = async
-
dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public
-dotnet_naming_symbols.public_fields.required_modifiers =
-
-# Naming styles
-
-dotnet_naming_style.pascalcase.required_prefix =
-dotnet_naming_style.pascalcase.required_suffix =
-dotnet_naming_style.pascalcase.word_separator =
dotnet_naming_style.pascalcase.capitalization = pascal_case
-
dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix =
-dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
-
-dotnet_naming_style.camelcase.required_prefix =
-dotnet_naming_style.camelcase.required_suffix =
-dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
-
dotnet_naming_style.begins_with_t.required_prefix = T
-dotnet_naming_style.begins_with_t.required_suffix =
-dotnet_naming_style.begins_with_t.word_separator =
dotnet_naming_style.begins_with_t.capitalization = pascal_case
-
-dotnet_naming_style.ends_with_async.required_prefix =
dotnet_naming_style.ends_with_async.required_suffix = Async
-dotnet_naming_style.ends_with_async.word_separator =
dotnet_naming_style.ends_with_async.capitalization = pascal_case
+
diff --git a/.gitignore b/.gitignore
index be19bc4b..88a90336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -378,9 +378,7 @@ _ReSharper*/
*__NOTYET
# Configs
-.vscode/*
-!.vscode/tasks.json
-!.vscode/enable.settings.jsonc
+.vscode/
# Site (on main)
site/
@@ -399,3 +397,9 @@ ADMONITION.md
docs/_site/
docs/__site/
docs/api/
+
+# SonarQube has config for SonarScanner and stuff - dont track it
+.sonarqube/
+
+# SonarLint is team configuration for SonarQube for IDE - track it
+!.sonarlint/
diff --git a/.sonarlint/RedSeaModernLanguage.json b/.sonarlint/RedSeaModernLanguage.json
index 520cef01..f854d17e 100644
--- a/.sonarlint/RedSeaModernLanguage.json
+++ b/.sonarlint/RedSeaModernLanguage.json
@@ -1,5 +1,5 @@
{
- "sonarCloudOrganization": "oas",
- "region": "EU",
- "projectKey": "oas_RedSeaModernLanguage"
+ "sonarCloudOrganization": "oas",
+ "projectKey": "oas_RedSeaModernLanguage",
+ "region": "EU"
}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 3c3b0d75..00000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "Build Docs",
- "type": "shell",
- "command": "mkdocs build --strict"
- },
- {
- "label": "Clean Docs",
- "type": "shell",
- "command": "mkdocs build --strict --clean"
- },
- {
- "label": "Serve Docs (Clean)",
- "type": "shell",
- "command": "mkdocs serve -s -c -o"
- },
- {
- "label": "Serve Docs (Dirty)",
- "type": "shell",
- "command": "mkdocs serve -s --dirty -o"
- },
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/src/RSML.CLI/RSML.CLI.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "publish",
- "command": "dotnet",
- "type": "process",
- "args": [
- "publish",
- "${workspaceFolder}/src/RSML.CLI/RSML.CLI.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary;ForceNoAlign"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "--project",
- "${workspaceFolder}/src/RSML.CLI/RSML.CLI.csproj"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
-}
diff --git a/Directory.Build.props b/Directory.Build.props
index 15961fb8..d9ac6812 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,63 +4,17 @@ Note for future self:
- Any property defined here can be explicitly overridden in any of said projects.
-->
-
-
- 3
- 3.0.0
- prerelease1
- TODO
-
+
+
-
-
- enable
- 14
- AnyCPU;ARM64;x64;x86;ARM32
- true
+
+
+
-
- OceanApocalypse
- OceanApocalypse
- Copyright 2025-2026 OceanApocalypse
- Apache-2.0
- git
- true
- en
-
+
-
-
- latest-all
- All
-
-
-
-
-
- $(NoWarn);CA1016;CA1034;CA1043;CA1708
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
- $(SemVerMajor).0.0.0
- $(VersionPrefix).$(GITHUB_RUN_NUMBER)
-
+
\ No newline at end of file
diff --git a/RedSeaModernLanguage.slnx b/RedSeaModernLanguage.slnx
index aafaf171..12ea3dc4 100644
--- a/RedSeaModernLanguage.slnx
+++ b/RedSeaModernLanguage.slnx
@@ -31,67 +31,79 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
index 0a74bdff..ee20d840 100644
--- a/benchmarks/Program.cs
+++ b/benchmarks/Program.cs
@@ -1,10 +1,6 @@
-using BenchmarkDotNet.Running;
-
-using OceanApocalypse.RSML.Benchmarks.Sources;
-
namespace OceanApocalypse.RSML.Benchmarks;
internal sealed class Program
{
- private static void Main(string[] args) => BenchmarkRunner.Run(args: args);
+ private static void Main(string[] args) { } // todo: => BenchmarkRunner.Run(args: args);
}
diff --git a/benchmarks/RSML.Benchmarks.csproj b/benchmarks/RSML.Benchmarks.csproj
index 75c60e2f..ae9ff59b 100644
--- a/benchmarks/RSML.Benchmarks.csproj
+++ b/benchmarks/RSML.Benchmarks.csproj
@@ -1,8 +1,6 @@
- net10.0;net8.0
Exe
- enable
OceanApocalypse.RSML.Benchmarks
pdbonly
true
@@ -18,7 +16,6 @@
-
-
+
\ No newline at end of file
diff --git a/benchmarks/Sources/BufferBenchmarks.cs b/benchmarks/Sources/BufferBenchmarks.cs
deleted file mode 100644
index ec1509ee..00000000
--- a/benchmarks/Sources/BufferBenchmarks.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-
-using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Engines;
-using BenchmarkDotNet.Jobs;
-
-using OceanApocalypse.RSML.Benchmarks.Helpers;
-using OceanApocalypse.RSML.Toolchain.Sources;
-
-
-namespace OceanApocalypse.RSML.Benchmarks.Sources;
-
-[SimpleJob(RuntimeMoniker.Net10_0)]
-[SimpleJob(RuntimeMoniker.Net80)]
-[SimpleJob(RuntimeMoniker.NativeAot10_0)]
-[SimpleJob(RuntimeMoniker.NativeAot80)]
-[MemoryDiagnoser]
-[CsvExporter]
-[RPlotExporter]
-[SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Benchmarks have to be public.")]
-public class BufferBenchmarks : IDisposable
-{
- private bool isDisposed;
- private readonly Consumer consumer = new();
- private string data = "";
- private ReadOnlyStringBuffer buffer = null!;
-
- [Params(1, 10, 100, 1_000)]
- public int RepeatCount { get; set; } // 1 is the string itself
-
- [GlobalSetup]
- public void Setup()
- {
- data = DataGenerator.GetSampleData(RepeatCount);
- buffer = new(data);
- }
-
- [Benchmark]
- public void ReadOnlyStringBuffer_GetLine()
- {
- for (int i = 0; i <= data.Length; i++)
- consumer.Consume(buffer.GetLineNumberFromIndex(i));
- }
-
- [GlobalCleanup]
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (isDisposed)
- return;
-
- if (disposing) // managed resources
- buffer.Dispose();
-
- isDisposed = true;
- }
-}
diff --git a/props/Analyzers.props b/props/Analyzers.props
new file mode 100644
index 00000000..4abd312f
--- /dev/null
+++ b/props/Analyzers.props
@@ -0,0 +1,15 @@
+
+
+
+ latest-all
+ All
+
+
+
+
+
+ $(NoWarn);CA1016;CA1034;CA1043;CA1708
+
+
\ No newline at end of file
diff --git a/props/Common.props b/props/Common.props
new file mode 100644
index 00000000..5fa3409d
--- /dev/null
+++ b/props/Common.props
@@ -0,0 +1,36 @@
+
+
+
+ enable
+ 14
+ AnyCPU;ARM64;x64;x86;ARM32
+ true
+
+ $(PackageId)
+
+
+
+ full
+
+
+
+ none
+ true
+
+
+
+
+
+
+
+
+
+ 0
+
+
\ No newline at end of file
diff --git a/props/Defaults.props b/props/Defaults.props
new file mode 100644
index 00000000..89108798
--- /dev/null
+++ b/props/Defaults.props
@@ -0,0 +1,9 @@
+
+
+
+ net10.0;net9.0
+ true
+
+ true
+
+
\ No newline at end of file
diff --git a/props/NuGet.props b/props/NuGet.props
new file mode 100644
index 00000000..9308b7a8
--- /dev/null
+++ b/props/NuGet.props
@@ -0,0 +1,23 @@
+
+
+ OceanApocalypse
+ OceanApocalypse
+ Copyright 2025-2026 OceanApocalypse
+ Apache-2.0
+
+ The only DSL that dynamically interprets different logic paths based on an
+ host's OS and CPU architecture.
+
+ https://github.com/OceanApocalypse/RedSeaModernLanguage/
+ https://oceanapocalypse.org/rsml-docs/
+
+ DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
+ README.md
+ icon.png
+
+ git
+ true
+ en
+
+
+
\ No newline at end of file
diff --git a/props/README.md b/props/README.md
new file mode 100644
index 00000000..8b5ebb20
--- /dev/null
+++ b/props/README.md
@@ -0,0 +1,47 @@
+# Properties set by the `.props` files in this directory
+- `Common.props`
+ - **Nullable context:** enabled
+ - **Language version:** 14
+ - **Platforms:** any CPU architecture, `arm64`, `arm`, `x64` and `x86`
+ - **Overflow/underflow checks:** enabled
+ - **Root namespace:** same as package ID, unless package ID is unset
+ - **Debug configuration**
+ - **Debug type:** full
+ - **Release configuration**
+ - **Debug type:** none
+ - **Optimizations:** enabled
+ - **Resources**
+ - **[/assets/icon.png](/assets/icon.png):** packed into package root; not visible in Solution Explorer
+ - **[/README.md](/README.md):** packed into package root; not visible in Solution Explorer
+ - **`GITHUB_RUN_NUMBER`:** 0 unless already set in the environment - meant for local build support (see `Versioning.props`)
+- `Default.props`
+ - **Target frameworks:** .NET 10.0 and .NET 8.0
+ - **AOT compatibility:** enabled
+ - **Generate documentation file on build:** yes
+- `Analyzers.props`
+ - **Analysis level:** set to `latest-all`
+ - **Analysis mode:** set to `All`
+ - **Disabled warnings:** CA1016, CA1034, CA1043 and CA1708, alongside the warnings disabled by default
+- `Versioning.props`
+ - **`SemVerMajor`** _(must be changed every new **major** version)_
+ - **`VersionPrefix`** _(must be changed every new version)_
+ - **`VersionSuffix`** _(must be changed every new version)_
+ - **`PackageReleaseNotes`** _(must be changed every new version)_
+ - **Assembly metadata:** a `SemVersion` injected into the assembly metadata, taking the `VersionPrefix` form if `VersionSuffix` is unset; otherwise, takes the form `VersionPrefix-VersionSuffix`, mimicking how `Version` is set internally by MSBuild
+ - **Assembly version:** set to `SemVerMajor.0.0.0` **always**
+ - **File version:** set to `VersionPrefix.GITHUB_RUN_NUMBER` **always**
+- `NuGet.props`
+ - **Authors:** OceanApocalypse
+ - **Company:** OceanApocalypse
+ - **Copyright message:** Copyright 2025-2026 OceanApocalypse
+ - **Package license:** Apache 2.0
+ - **Description:** set to `The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.`
+ - **Repo URL:** https://github.com/OceanApocalypse/RedSeaModernLanguage/
+ - **Package project URL:** https://oceanapocalypse.org/rsml-docs/
+ - **Package tags:** set to `DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss`
+ - **Package README file:** set to previously imported `README.md` (see `Common.props`)
+ - **Package icon file:** set to previously imported `icon.png` (see `Common.props`)
+ - **Repo type:** git
+ - **Package requires license acceptance:** yes
+ - **Neutral language:** English (`en`)
+
diff --git a/props/Versioning.props b/props/Versioning.props
new file mode 100644
index 00000000..6616b50d
--- /dev/null
+++ b/props/Versioning.props
@@ -0,0 +1,25 @@
+
+
+
+
+ 3
+ 3.0.0
+ prerelease1
+ TODO
+
+
+
+
+
+
+
+
+
+
+ $(SemVerMajor).0.0.0
+ $(VersionPrefix).$(GITHUB_RUN_NUMBER)
+
+
\ No newline at end of file
diff --git a/src/RSML.Abstractions.Diagnostics/AbsolutePosition.cs b/src/RSML.Abstractions.Diagnostics/AbsolutePosition.cs
new file mode 100644
index 00000000..fbd90ad7
--- /dev/null
+++ b/src/RSML.Abstractions.Diagnostics/AbsolutePosition.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
+
+///
+/// An absolute reader position containing information on line and column numbers.
+///
+public struct AbsolutePosition : IComparable, IEquatable, IEquatable>
+{
+ ///
+ /// A default valid position initialized at line 1 and column 1.
+ ///
+ public static readonly AbsolutePosition Default = new(1, 1);
+
+ ///
+ /// The 1-based line number.
+ ///
+ public int Line
+ {
+ get;
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
+ field = value;
+ }
+ } = 1;
+
+ ///
+ /// The 1-based column number.
+ ///
+ public int Column
+ {
+ get;
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
+ field = value;
+ }
+ } = 1;
+
+ ///
+ /// Indicates whether the current instance is valid. A position is considered
+ /// valid if the line is positive and the column is positive as well
+ /// (meaning all data has been initialized).
+ ///
+ public readonly bool IsValid => Line > 0 && Column > 0;
+
+ ///
+ /// Initializes a new reader position.
+ ///
+ /// The current 1-based line number.
+ /// The current 1-based column number.
+ ///
+ /// At least one of the parameters was negative or zero.
+ ///
+ public AbsolutePosition(int lineNumber, int columnNumber)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(lineNumber);
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(columnNumber);
+
+ Line = lineNumber;
+ Column = columnNumber;
+ }
+
+ ///
+ public readonly int CompareTo(AbsolutePosition other) => (Line, Column).CompareTo((other.Line, other.Column));
+
+ ///
+ /// Moves the current position to the start of the next line.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MoveToStartOfNextLine()
+ {
+ Line++;
+ Column = 1;
+ }
+
+ ///
+ public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj switch
+ {
+ AbsolutePosition pos => Equals(pos),
+ ValueTuple tuple => Equals(tuple),
+ _ => false
+ };
+
+ ///
+ public readonly bool Equals(AbsolutePosition other) => Line == other.Line && Column == other.Column;
+
+ ///
+ /// Indicates whether the current object is equal to a tuple representation
+ /// of an object of the same type.
+ ///
+ /// The tuple representation.
+ /// True if equals.
+ public readonly bool Equals(ValueTuple tuple) => Line == tuple.Item1 && Column == tuple.Item2;
+
+ ///
+ public override readonly int GetHashCode() => HashCode.Combine(Line, Column);
+
+ ///
+ /// Returns the tuple representation of the current instance.
+ ///
+ /// The tuple representation of the instance.
+ public readonly (int Line, int Column) AsTuple() => new(Line, Column);
+
+ ///
+ /// Indicates whether the instance to the left is equals to the instance on the right.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if equals.
+ public static bool operator ==(AbsolutePosition left, AbsolutePosition right) => left.Equals(right);
+
+ ///
+ /// Indicates whether the instance to the left is different from the instance on the right.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if different.
+ public static bool operator !=(AbsolutePosition left, AbsolutePosition right) => !(left == right);
+
+ ///
+ /// Indicates whether the instance to the left has a lower offset than the instance on the right.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if 's offset is less than 's.
+ public static bool operator <(AbsolutePosition left, AbsolutePosition right) => left.CompareTo(right) < 0;
+
+ ///
+ /// Indicates whether the instance to the left has a greater offset than the instance on the right.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if 's offset is greater than 's.
+ public static bool operator >(AbsolutePosition left, AbsolutePosition right) => left.CompareTo(right) > 0;
+
+ ///
+ /// Indicates whether the instance to the left has a lower offset than the instance on the right,
+ /// or if they're the same.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if 's offset is less than or equal to 's.
+ public static bool operator <=(AbsolutePosition left, AbsolutePosition right) => left == right || left < right;
+
+ ///
+ /// Indicates whether the instance to the left has a greater offset than the instance on the right,
+ /// or if they're the same.
+ ///
+ /// One of the instances.
+ /// One of the instances.
+ /// True if 's offset is greater than or equal to 's.
+ public static bool operator >=(AbsolutePosition left, AbsolutePosition right) => left == right || left > right;
+
+ ///
+ /// Throws an if the given position is invalid.
+ ///
+ /// The position to check.
+ /// The parameter name taken by the position.
+ /// Position was not initialized yet (was invalid).
+ public static void ThrowIfInvalid(AbsolutePosition position, string? paramName = null)
+ {
+ if (!position.IsValid)
+ throw new ArgumentException("Position was not initialized yet (was therefore invalid).", paramName ?? nameof(position));
+ }
+}
\ No newline at end of file
diff --git a/src/RSML.Toolchain.Sources/AssemblyInfo.cs b/src/RSML.Abstractions.Diagnostics/AssemblyInfo.cs
similarity index 100%
rename from src/RSML.Toolchain.Sources/AssemblyInfo.cs
rename to src/RSML.Abstractions.Diagnostics/AssemblyInfo.cs
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/Diagnostic.cs b/src/RSML.Abstractions.Diagnostics/Diagnostic.cs
similarity index 62%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/Diagnostic.cs
rename to src/RSML.Abstractions.Diagnostics/Diagnostic.cs
index a2274ab3..aab6539c 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/Diagnostic.cs
+++ b/src/RSML.Abstractions.Diagnostics/Diagnostic.cs
@@ -1,10 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
///
/// A diagnostic reported by RSML's API.
@@ -12,9 +10,24 @@ namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
public readonly struct Diagnostic : IFormattable, IEquatable
{
///
- /// The span the error relates to.
+ /// The start location the error relates to (inclusive).
+ ///
+ public AbsolutePosition StartLocation { get; }
+
+ ///
+ /// The inclusive offset at which the range starts (inclusive).
///
- public SourceSpan Span { get; }
+ public long StartOffset { get; }
+
+ ///
+ /// The end location the error relates to (exclusive).
+ ///
+ public AbsolutePosition EndLocation { get; }
+
+ ///
+ /// The exclusive offset at which the range ends (exclusive).
+ ///
+ public long EndOffset { get; }
///
/// The error's code. Contains information about the category of the error.
@@ -40,11 +53,10 @@ namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
/// The error code.
public Diagnostic(string code)
{
- ArgumentNullException.ThrowIfNullOrWhiteSpace(code);
+ ArgumentException.ThrowIfNullOrWhiteSpace(code);
ThrowIfInvalidErrorCode(code);
Code = code;
- Span = SourceSpan.Empty;
Message = "";
Severity = Severity.None;
}
@@ -54,11 +66,10 @@ public Diagnostic(string code)
/// A brief error message detailing why it has happened.
public Diagnostic(string code, string message)
{
- ArgumentNullException.ThrowIfNullOrWhiteSpace(code);
+ ArgumentException.ThrowIfNullOrWhiteSpace(code);
ThrowIfInvalidErrorCode(code);
Code = code;
- Span = SourceSpan.Empty;
Message = message;
Severity = Severity.None;
}
@@ -68,11 +79,10 @@ public Diagnostic(string code, string message)
/// The error's severity.
public Diagnostic(string code, Severity severity)
{
- ArgumentNullException.ThrowIfNullOrWhiteSpace(code);
+ ArgumentException.ThrowIfNullOrWhiteSpace(code);
ThrowIfInvalidErrorCode(code);
Code = code;
- Span = SourceSpan.Empty;
Message = "";
Severity = severity;
}
@@ -83,39 +93,43 @@ public Diagnostic(string code, Severity severity)
/// The error's severity.
public Diagnostic(string code, string message, Severity severity)
{
- ArgumentNullException.ThrowIfNullOrWhiteSpace(code);
+ ArgumentException.ThrowIfNullOrWhiteSpace(code);
ThrowIfInvalidErrorCode(code);
Code = code;
- Span = SourceSpan.Empty;
Message = message;
Severity = severity;
}
/// Creates a new diagnostic.
/// The error code.
- /// The span the error relates to.
+ /// The offset at which the range starts (inclusive).
+ /// More details on the range's start..
+ /// The offset at which the range ends (exclusive).
+ /// More details on the range's end.
/// A brief error message detailing why it has happened.
/// The error's severity.
- public Diagnostic(string code, SourceSpan span, string message, Severity severity)
+ public Diagnostic(string code, long spanStartOffset, AbsolutePosition spanStartDetails, long spanEndOffset, AbsolutePosition spanEndDetails, string message, Severity severity)
{
- ArgumentNullException.ThrowIfNullOrWhiteSpace(code);
+ ArgumentException.ThrowIfNullOrWhiteSpace(code);
ThrowIfInvalidErrorCode(code);
Code = code;
- Span = span;
Message = message;
Severity = severity;
+
+ StartOffset = spanStartOffset;
+ StartLocation = spanStartDetails;
+ EndOffset = spanEndOffset;
+ EndLocation = spanEndDetails;
}
///
- public override bool Equals(
- [NotNullWhen(true)]
- object? obj
- ) => obj is Diagnostic error && Equals(error);
+ public override bool Equals([NotNullWhen(true)] object? obj) => obj is Diagnostic error && Equals(error);
///
- public bool Equals(Diagnostic other) => Message == other.Message && Code == other.Code && Severity == other.Severity && Span.Equals(other.Span);
+ public bool Equals(Diagnostic other) =>
+ Message == other.Message && Code == other.Code && Severity == other.Severity && StartLocation.Equals(other.StartLocation) && EndLocation.Equals(other.EndLocation);
///
/// Checks if two s are equal to each other.
@@ -130,13 +144,13 @@ public override bool Equals(
public static bool operator !=(Diagnostic left, Diagnostic right) => !left.Equals(right);
///
- public override int GetHashCode() => unchecked(HashCode.Combine(Span, Code, Message, Severity));
+ public override int GetHashCode() => unchecked(HashCode.Combine(StartLocation, EndLocation, Code, Message, Severity));
///
/// Returns a generic string representation of the current instance.
///
/// The string representation.
- public override string ToString() => $"Diagnostic(Code={Code}, Span={Span}, Message={Message}, Severity={Severity})";
+ public override string ToString() => $"Diagnostic(Code={Code}, Start={StartLocation}, End={EndLocation}, Message={Message}, Severity={Severity})";
///
/// Given a format, tries to return a string that uses said format as a basis for the representation.
@@ -153,7 +167,7 @@ public string ToString(string? format, IFormatProvider? formatProvider)
case "I":
case "INIT":
case "NET":
- return $"new Diagnostic(\"{Code}\", {Span.ToString("ctor", null)}, \"{Message}\", {Severity})";
+ return $"new Diagnostic(\"{Code}\", \"{StartLocation}\", \"{EndLocation}\", \"{Message}\", {Severity})";
case "LOG":
string prefix = Severity switch
@@ -165,21 +179,29 @@ public string ToString(string? format, IFormatProvider? formatProvider)
_ => ""
};
- if (Span.IsSingleLine)
- return $"[{prefix}{Code}] @ L{Span.Start.Line + 1},C({Span.Start.Column + 1}..{Span.End.Column + 1}) : {Message}";
+ if (StartLocation.Line == EndLocation.Line)
+ return $"[{prefix}{Code}] @ L{StartLocation.Line + 1},C({StartLocation.Column + 1}..{EndLocation.Column + 1}) : {Message}";
- return $"[{prefix}{Code}] @ L({Span.Start.Line + 1}..{Span.End.Line + 1}),C({Span.Start.Column + 1}..{Span.End.Column + 1}) : {Message}";
+ return $"[{prefix}{Code}] @ L({StartLocation.Line + 1}..{EndLocation.Line + 1}),C({StartLocation.Column + 1}..{EndLocation.Column + 1}) : {Message}";
case "JSON":
- return
- $$"""
- {
- "errorCode": "{{Code}}",
- "span": {{Span.ToString("JSON", null)}},
- "message": "{{Message}}",
- "severity": "{{Severity}}"
- }
- """;
+ return $$"""
+ {
+ "errorCode": "{{Code}}",
+ "range": [
+ {
+ "offset": {{StartOffset}},
+ "line": {{StartLocation.Line}},
+ "column": {{StartLocation.Column}}
+ },
+ {
+ "offset": {{EndOffset}},
+ "line": {{EndLocation.Line}},
+ "column": {{EndLocation.Column}}
+ }
+ ]
+ }
+ """;
default:
return ToString();
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/DiagnosticCollector.cs b/src/RSML.Abstractions.Diagnostics/DiagnosticCollector.cs
similarity index 84%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/DiagnosticCollector.cs
rename to src/RSML.Abstractions.Diagnostics/DiagnosticCollector.cs
index 0737d78c..5235d505 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/DiagnosticCollector.cs
+++ b/src/RSML.Abstractions.Diagnostics/DiagnosticCollector.cs
@@ -2,19 +2,23 @@
using System.Collections.Generic;
using System.Collections.Immutable;
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
///
/// A list of RSML toolchain errors.
///
public sealed record DiagnosticCollector : IEnumerable
{
+ ///
+ /// Creates a new diagnostic collector.
+ ///
+ public DiagnosticCollector() => MinimumCriticalSeverity = Severity.Error;
+
///
/// Creates a new diagnostic collector.
///
/// The minimum diagnostic severity for a diagnostic to be considered critical.
- public DiagnosticCollector(Severity minimumCriticalSeverity = Severity.Error) => MinimumCriticalSeverity = minimumCriticalSeverity;
+ public DiagnosticCollector(Severity minimumCriticalSeverity) => MinimumCriticalSeverity = minimumCriticalSeverity;
private readonly List diagnostics = [];
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/ErrorCodes/InternalErrorCodes.cs b/src/RSML.Abstractions.Diagnostics/ErrorCodes/InternalErrorCodes.cs
similarity index 71%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/ErrorCodes/InternalErrorCodes.cs
rename to src/RSML.Abstractions.Diagnostics/ErrorCodes/InternalErrorCodes.cs
index 42e1b3ca..9e8f4866 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/ErrorCodes/InternalErrorCodes.cs
+++ b/src/RSML.Abstractions.Diagnostics/ErrorCodes/InternalErrorCodes.cs
@@ -1,4 +1,4 @@
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics.ErrorCodes;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics.ErrorCodes;
internal static class InternalErrorCodes
{
diff --git a/src/RSML.Abstractions.Diagnostics/GlobalSuppressions.cs b/src/RSML.Abstractions.Diagnostics/GlobalSuppressions.cs
new file mode 100644
index 00000000..d7490f6b
--- /dev/null
+++ b/src/RSML.Abstractions.Diagnostics/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "", Scope = "member", Target = "~M:OceanApocalypse.RSML.Abstractions.Diagnostics.DiagnosticCollector.GetAll~System.Collections.Immutable.ImmutableArray{OceanApocalypse.RSML.Abstractions.Diagnostics.Diagnostic}")]
diff --git a/src/RSML.Abstractions.Diagnostics/RSML.Abstractions.Diagnostics.csproj b/src/RSML.Abstractions.Diagnostics/RSML.Abstractions.Diagnostics.csproj
new file mode 100644
index 00000000..78bb2b74
--- /dev/null
+++ b/src/RSML.Abstractions.Diagnostics/RSML.Abstractions.Diagnostics.csproj
@@ -0,0 +1,18 @@
+
+
+
+ true
+ Library
+ OceanApocalypse.RSML.Abstractions.Diagnostics
+
+
+
+ True
+ OceanApocalypse.RSML.Abstractions.Diagnostics
+ RSML Diagnostics
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/Result.cs b/src/RSML.Abstractions.Diagnostics/Result.cs
similarity index 96%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/Result.cs
rename to src/RSML.Abstractions.Diagnostics/Result.cs
index 1d8f7077..68700f33 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/Result.cs
+++ b/src/RSML.Abstractions.Diagnostics/Result.cs
@@ -1,8 +1,8 @@
using System;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics.ErrorCodes;
+using OceanApocalypse.RSML.Abstractions.Diagnostics.ErrorCodes;
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
///
/// A collection of factory methods for easier initialization of objects.
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/Result{TValue}.cs b/src/RSML.Abstractions.Diagnostics/Result{TValue}.cs
similarity index 98%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/Result{TValue}.cs
rename to src/RSML.Abstractions.Diagnostics/Result{TValue}.cs
index 82215b34..c47d0e74 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/Result{TValue}.cs
+++ b/src/RSML.Abstractions.Diagnostics/Result{TValue}.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
///
/// An operation's result.
diff --git a/src/RSML.Toolchain.Abstractions/Diagnostics/Severity.cs b/src/RSML.Abstractions.Diagnostics/Severity.cs
similarity index 88%
rename from src/RSML.Toolchain.Abstractions/Diagnostics/Severity.cs
rename to src/RSML.Abstractions.Diagnostics/Severity.cs
index f79445bc..f8133253 100644
--- a/src/RSML.Toolchain.Abstractions/Diagnostics/Severity.cs
+++ b/src/RSML.Abstractions.Diagnostics/Severity.cs
@@ -1,4 +1,4 @@
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
+namespace OceanApocalypse.RSML.Abstractions.Diagnostics;
///
/// The severity of an error.
diff --git a/src/RSML.Toolchain.Abstractions/AssemblyInfo.cs b/src/RSML.Abstractions.Panic/AssemblyInfo.cs
similarity index 87%
rename from src/RSML.Toolchain.Abstractions/AssemblyInfo.cs
rename to src/RSML.Abstractions.Panic/AssemblyInfo.cs
index 3fa15394..1b4601d2 100644
--- a/src/RSML.Toolchain.Abstractions/AssemblyInfo.cs
+++ b/src/RSML.Abstractions.Panic/AssemblyInfo.cs
@@ -1,5 +1,4 @@
using System;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// In SDK-style projects such as this one, several assembly attributes that were historically
@@ -13,4 +12,3 @@
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
-[assembly: InternalsVisibleTo("RSML.Toolchain.Sources")]
diff --git a/src/RSML.Toolchain.Abstractions/Panic/BufferException.cs b/src/RSML.Abstractions.Panic/BufferException.cs
similarity index 83%
rename from src/RSML.Toolchain.Abstractions/Panic/BufferException.cs
rename to src/RSML.Abstractions.Panic/BufferException.cs
index 6313fe52..0324bae5 100644
--- a/src/RSML.Toolchain.Abstractions/Panic/BufferException.cs
+++ b/src/RSML.Abstractions.Panic/BufferException.cs
@@ -1,10 +1,10 @@
using System;
using System.IO;
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Panic;
+namespace OceanApocalypse.RSML.Abstractions.Panic;
///
-/// An exception that occurs in and types.
+/// An exception that occurs in buffer types.
///
public class BufferException : IOException
{
diff --git a/src/RSML.Abstractions.Panic/ExceededMaxAmountOfFailures.cs b/src/RSML.Abstractions.Panic/ExceededMaxAmountOfFailures.cs
new file mode 100644
index 00000000..3e478759
--- /dev/null
+++ b/src/RSML.Abstractions.Panic/ExceededMaxAmountOfFailures.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace OceanApocalypse.RSML.Abstractions.Panic;
+
+///
+/// An exception that is thrown when a given operation exceeds the maximum
+/// amount of internal failures it is allowed to endure.
+/// This class cannot be inherited.
+///
+public sealed class ExceededMaxAmountOfFailuresException : Exception
+{
+ ///
+ /// Creates a new exception of this type with no message.
+ ///
+ public ExceededMaxAmountOfFailuresException() : base() { }
+
+ ///
+ /// Creates a new exception of this type with a custom error message.
+ ///
+ /// The error message.
+ public ExceededMaxAmountOfFailuresException(string message) : base(message) { }
+
+ ///
+ /// Creates a new exception of this type with a custom error message and
+ /// a reference to the exception that caused this panic.
+ ///
+ /// The error message.
+ /// The exception that led to the panic.
+ public ExceededMaxAmountOfFailuresException(string? message, Exception innerException) : base(message, innerException) { }
+}
diff --git a/src/RSML.Abstractions.Panic/LexerException.cs b/src/RSML.Abstractions.Panic/LexerException.cs
new file mode 100644
index 00000000..3e00575f
--- /dev/null
+++ b/src/RSML.Abstractions.Panic/LexerException.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace OceanApocalypse.RSML.Abstractions.Panic;
+
+///
+/// An exception that occurs in lexer types.
+///
+public class LexerException : Exception
+{
+ ///
+ /// Creates a new lexer exception with no message.
+ ///
+ public LexerException() : base() { }
+
+ ///
+ /// Creates a new lexer exception with a custom error message.
+ ///
+ /// The error message.
+ public LexerException(string message) : base(message) { }
+
+ ///
+ /// Creates a new lexer exception with a custom error message and a reference
+ /// to the exception that caused this panic.
+ ///
+ /// The error message.
+ /// The exception that led to the panic.
+ public LexerException(string? message, Exception innerException) : base(message, innerException) { }
+}
diff --git a/src/RSML.Abstractions.Panic/RSML.Abstractions.Panic.csproj b/src/RSML.Abstractions.Panic/RSML.Abstractions.Panic.csproj
new file mode 100644
index 00000000..15d36a4e
--- /dev/null
+++ b/src/RSML.Abstractions.Panic/RSML.Abstractions.Panic.csproj
@@ -0,0 +1,14 @@
+
+
+
+ true
+ Library
+ OceanApocalypse.RSML.Abstractions.Panic
+
+
+
+ True
+ OceanApocalypse.RSML.Abstractions.Panic
+ RSML Panic
+
+
\ No newline at end of file
diff --git a/src/RSML.Abstractions.Toolchain/AssemblyInfo.cs b/src/RSML.Abstractions.Toolchain/AssemblyInfo.cs
new file mode 100644
index 00000000..1b4601d2
--- /dev/null
+++ b/src/RSML.Abstractions.Toolchain/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Runtime.InteropServices;
+
+// In SDK-style projects such as this one, several assembly attributes that were historically
+// defined in this file are now automatically added during build and populated with
+// values defined in project properties. For details of which attributes are included
+// and how to customise this process see: https://aka.ms/assembly-info-properties
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM
+// components. If you need to access a type in this assembly from COM, set the ComVisible
+// attribute to true on that type.
+[assembly: ComVisible(false)]
+
+[assembly: CLSCompliant(true)]
diff --git a/src/RSML.Toolchain.Abstractions/IToolchainComponent.cs b/src/RSML.Abstractions.Toolchain/IToolchainComponent.cs
similarity index 71%
rename from src/RSML.Toolchain.Abstractions/IToolchainComponent.cs
rename to src/RSML.Abstractions.Toolchain/IToolchainComponent.cs
index 09698824..85abbfaf 100644
--- a/src/RSML.Toolchain.Abstractions/IToolchainComponent.cs
+++ b/src/RSML.Abstractions.Toolchain/IToolchainComponent.cs
@@ -1,6 +1,6 @@
using System;
-namespace OceanApocalypse.RSML.Toolchain.Abstractions;
+namespace OceanApocalypse.RSML.Abstractions.Toolchain;
///
/// A component of the RSML toolchain.
@@ -10,11 +10,11 @@ public interface IToolchainComponent : IDisposable
///
/// Configurations for the toolchain component.
///
- ToolchainConfigurations Configuration { get; }
+ ToolchainConfiguration Configuration { get; }
///
/// Injects a configuration into the toolchain component, modifying it.
///
/// The configuration to inject.
- void Inject(ToolchainConfigurations configuration);
+ void Inject(ToolchainConfiguration configuration);
}
diff --git a/src/RSML.Abstractions.Toolchain/RSML.Abstractions.Toolchain.csproj b/src/RSML.Abstractions.Toolchain/RSML.Abstractions.Toolchain.csproj
new file mode 100644
index 00000000..f7e94d4d
--- /dev/null
+++ b/src/RSML.Abstractions.Toolchain/RSML.Abstractions.Toolchain.csproj
@@ -0,0 +1,19 @@
+
+
+ true
+ Library
+ OceanApocalypse.RSML.Abstractions.Toolchain
+
+
+
+ True
+ OceanApocalypse.RSML.Abstractions.Toolchain
+ RSML Toolchain Abstractions
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RSML.Abstractions.Toolchain/ToolchainConfiguration.cs b/src/RSML.Abstractions.Toolchain/ToolchainConfiguration.cs
new file mode 100644
index 00000000..b89e6310
--- /dev/null
+++ b/src/RSML.Abstractions.Toolchain/ToolchainConfiguration.cs
@@ -0,0 +1,77 @@
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Data;
+using System.Runtime.CompilerServices;
+
+namespace OceanApocalypse.RSML.Abstractions.Toolchain;
+
+///
+/// Contains configurations for the entire toolchain.
+///
+public record ToolchainConfiguration()
+{
+ private bool isFrozen;
+ private readonly Dictionary custom = [];
+
+ ///
+ /// The maximum amount of diagnostics needed for the toolchain to stop, per component.
+ /// 0 does not limit failures.
+ /// 1 simulates a fast fail: it's only recommended for CI purposes.
+ ///
+ public int MaximumAllowedFailuresPerComponent
+ {
+ get;
+ set
+ {
+ ThrowIfFrozen();
+ field = value;
+ }
+ } = 100;
+
+ ///
+ /// Whether or not the lexer should emit comment tokens.
+ /// Setting to false is only recommended if only interpreting (no analysis tools).
+ ///
+ public bool EmitComments
+ {
+ get;
+ set
+ {
+ ThrowIfFrozen();
+ field = value;
+ }
+ } = true;
+
+ ///
+ /// The custom configurations.
+ ///
+ public IReadOnlyDictionary CustomConfigurations => custom.ToFrozenDictionary();
+
+ ///
+ /// The default toolchain configuration.
+ ///
+ public static ToolchainConfiguration Default { get; } = new();
+
+ ///
+ /// Sets a custom configuration.
+ ///
+ /// The configuration's key.
+ /// The configuration's value.
+ public void SetCustom(string key, string value)
+ {
+ ThrowIfFrozen();
+ custom[key] = value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ThrowIfFrozen()
+ {
+ if (isFrozen)
+ throw new ReadOnlyException("Configurations are frozen and cannot be changed.");
+ }
+
+ ///
+ /// Freezes the configurations, preventing any future mutations.
+ ///
+ public void Freeze() => isFrozen = true;
+}
\ No newline at end of file
diff --git a/src/RSML.Abstractions/AssemblyInfo.cs b/src/RSML.Abstractions/AssemblyInfo.cs
new file mode 100644
index 00000000..1b4601d2
--- /dev/null
+++ b/src/RSML.Abstractions/AssemblyInfo.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Runtime.InteropServices;
+
+// In SDK-style projects such as this one, several assembly attributes that were historically
+// defined in this file are now automatically added during build and populated with
+// values defined in project properties. For details of which attributes are included
+// and how to customise this process see: https://aka.ms/assembly-info-properties
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM
+// components. If you need to access a type in this assembly from COM, set the ComVisible
+// attribute to true on that type.
+[assembly: ComVisible(false)]
+
+[assembly: CLSCompliant(true)]
diff --git a/src/RSML.Toolchain.Abstractions/Cache/ISupportsCache.cs b/src/RSML.Abstractions/Cache/ISupportsCache.cs
similarity index 91%
rename from src/RSML.Toolchain.Abstractions/Cache/ISupportsCache.cs
rename to src/RSML.Abstractions/Cache/ISupportsCache.cs
index f7648519..62695565 100644
--- a/src/RSML.Toolchain.Abstractions/Cache/ISupportsCache.cs
+++ b/src/RSML.Abstractions/Cache/ISupportsCache.cs
@@ -1,4 +1,4 @@
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Cache;
+namespace OceanApocalypse.RSML.Abstractions.Cache;
///
/// Represents a service or a type that supports cached data.
diff --git a/src/RSML.Abstractions/Extensions.cs b/src/RSML.Abstractions/Extensions.cs
new file mode 100644
index 00000000..5b60aa92
--- /dev/null
+++ b/src/RSML.Abstractions/Extensions.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Buffers;
+using System.Collections.Frozen;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace OceanApocalypse.RSML.Abstractions;
+
+///
+/// Extension members for characters.
+///
+public static class Extensions
+{
+ private readonly static UTF8Encoding exceptionlessUtf8Encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
+ private const byte UpperLowerDiffBit = 0b_0010_0000; // 0x20, binary seems best suited for this tho ngl
+
+ #region ASCII Characters
+ private const byte Tab = 0x9;
+ private const byte Lf = 0xA;
+ private const byte Cr = 0xD;
+ private const byte Space = 0x20;
+ private const byte Exclamation = 0x21;
+ private const byte And = 0x26;
+ private const byte LessThan = 0x3C;
+ private const byte GreaterThan = 0x3E;
+ private const byte UppercaseA = 0x41;
+ private const byte UppercaseZ = 0x5A;
+ private const byte Pipe = 0x7C;
+ #endregion
+
+ extension(byte item)
+ {
+ ///
+ /// Checks if the character in question represents an ASCII newline.
+ ///
+ /// True if ASCII newline.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsAsciiNewline() => item is Lf or Cr;
+
+ ///
+ /// Checks if the character in question is ASCII punctuation. Used by RSML.
+ ///
+ /// True if ASCII punctuation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsRsmlPunctuation() => item is (>= LessThan and <= GreaterThan) or Exclamation or And or Pipe;
+
+ ///
+ /// Checks if the character in question is ASCII whitespace.
+ ///
+ /// True if ASCII punctuation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsAsciiWhitespace() => item is Space or (>= Tab and <= Cr);
+
+ ///
+ /// Converts an ASCII letter to its uppercase form.
+ ///
+ /// The uppercase ASCII letter.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public byte ToAsciiUpperInvariant() => (byte)(item & (~UpperLowerDiffBit));
+
+ ///
+ /// Checks if a given character is an ASCII letter.
+ ///
+ /// True if the character is an ASCII letter.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsAsciiLetter() => ToAsciiUpperInvariant(item) is >= UppercaseA and <= UppercaseZ;
+
+ ///
+ /// Checks if a given character is an ASCII digit.
+ ///
+ /// True if the character is an ASCII digit.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsAsciiDigit() => item is >= 48 and <= 57; // 48 is '0' and 57 is '9'
+
+ ///
+ /// Checks if a given character falls under the ASCII category.
+ ///
+ /// True if the character is ASCII.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsAscii() => item is > 127;
+ }
+
+ extension(ReadOnlySequence sequence)
+ {
+ ///
+ /// Safely decodes a UTF-8 sequence to a UTF-16 character span.
+ ///
+ /// The destination span.
+ /// The UTF-8 encoding to use.
+ /// The amount of characters written.
+ public int SafelyDecodeToCharacterSpan(Span destination, UTF8Encoding? encoding = null)
+ {
+ int totalCharsWritten = 0;
+ Decoder decoder = (encoding ?? exceptionlessUtf8Encoding).GetDecoder();
+
+ foreach (var segment in sequence)
+ {
+ ReadOnlySpan span = segment.Span;
+ decoder.Convert(span, destination[totalCharsWritten..], false, out _, out int charsWritten, out _);
+ totalCharsWritten += charsWritten;
+ }
+
+ decoder.Convert([], destination[totalCharsWritten..], true, out _, out int flushCharsWritten, out _);
+ totalCharsWritten += flushCharsWritten;
+ return totalCharsWritten;
+ }
+ }
+
+ extension(FrozenSet stringSet)
+ {
+ ///
+ /// Checks if an immutable array of UTF-16 strings contains a given UTF-8 span.
+ ///
+ /// The span to check for.
+ /// The threshold that, when exceeded, ensures the code falls back to using arrays to avoid overflows.
+ /// True if found.
+ public bool ContainsUtf8(ReadOnlySequence seq, int stackAllocThreshold = 256)
+ {
+ if (stringSet.Count == 0 || seq.IsEmpty)
+ return false; // we fail fast over here
+
+ var lookup = stringSet.GetAlternateLookup>(); // lookup spans, not strings to avoid heap allocs obviously
+ long length = seq.Length;
+
+ if (length <= stackAllocThreshold) // the seq is small, we can use a span directly :)
+ {
+ Span charBuffer = stackalloc char[(int)length];
+ int charsWritten = seq.SafelyDecodeToCharacterSpan(charBuffer, exceptionlessUtf8Encoding);
+ return lookup.Contains(charBuffer[..charsWritten]); // make sure to slice to avoid reading garbage data broski
+ }
+ else
+ {
+ char[] pooledArray = ArrayPool.Shared.Rent((int)length); // we poolin' over here (direct span would be stack overflow)
+
+ try
+ {
+ Span charBuffer = pooledArray.AsSpan(0, (int)length); // get a span out of it, dum dum (we limit to length because sometimes it might pool larger arrays)
+ int charsWritten = seq.SafelyDecodeToCharacterSpan(charBuffer, exceptionlessUtf8Encoding);
+ return lookup.Contains(charBuffer[..charsWritten]); // make sure to slice to avoid reading garbage data brosquito
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(pooledArray); // we return to avoid leaking
+ }
+ }
+ }
+ }
+}
diff --git a/src/RSML.Abstractions/GlobalSuppressions.cs b/src/RSML.Abstractions/GlobalSuppressions.cs
new file mode 100644
index 00000000..3dc76b4a
--- /dev/null
+++ b/src/RSML.Abstractions/GlobalSuppressions.cs
@@ -0,0 +1,5 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
diff --git a/src/RSML.Abstractions/RSML.Abstractions.csproj b/src/RSML.Abstractions/RSML.Abstractions.csproj
new file mode 100644
index 00000000..fa222eb4
--- /dev/null
+++ b/src/RSML.Abstractions/RSML.Abstractions.csproj
@@ -0,0 +1,18 @@
+
+
+ true
+ Library
+ OceanApocalypse.RSML.Abstractions
+
+
+
+ True
+ OceanApocalypse.RSML.Abstractions
+ RSML Abstractions
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/RSML.Language.Lexing/AssemblyInfo.cs b/src/RSML.Language.Lexing/AssemblyInfo.cs
index 5017d626..1b4601d2 100644
--- a/src/RSML.Language.Lexing/AssemblyInfo.cs
+++ b/src/RSML.Language.Lexing/AssemblyInfo.cs
@@ -1,5 +1,4 @@
using System;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// In SDK-style projects such as this one, several assembly attributes that were historically
@@ -13,4 +12,3 @@
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
-[assembly: InternalsVisibleTo("RSML.Toolchain.Extensibility.Lexing")]
diff --git a/src/RSML.Language.Lexing/BufferLexer.cs b/src/RSML.Language.Lexing/BufferLexer.cs
deleted file mode 100644
index 508eb114..00000000
--- a/src/RSML.Language.Lexing/BufferLexer.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-
-using OceanApocalypse.RSML.Language.Lexing.Diagnostics;
-using OceanApocalypse.RSML.Language.Lexing.Tokens;
-using OceanApocalypse.RSML.Toolchain.Abstractions;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-namespace OceanApocalypse.RSML.Language.Lexing;
-
-///
-/// An implementation of a RSML lexer backed by a read-only or read-and-write buffer.
-///
-/// A buffer. Can be read-only () or read and write ().
-/// A collector for all emitted diagnostics.
-public class BufferLexer(IBuffer buffer, DiagnosticCollector diagnostics) : Lexer
-{
- private int cursor;
-
- ///
- /// :::note[Diagnostic output]
- /// This method does not add diagnostics to the collector
- /// (): it only returns them when it
- /// proves necessary.
- /// :::
- ///
- ///
- public override Result GetNextToken()
- {
- SkipWhitespaceAndComments();
-
- if (cursor >= buffer.Length)
- return Result.Success(new Token(TokenKind.Eof, null, SourceSpan.Empty));
-
- var startLoc = buffer.GetSourceLocation(cursor);
-
- char c = buffer[cursor];
-
- // strings
- if (c == '"')
- return ScanStringLiteral(startLoc);
-
- // number literals
- if (Char.IsAsciiDigit(c))
- return ScanNumber(startLoc);
-
- // identifiers and keywords
- if (Char.IsAsciiLetter(c) || c == '_')
- return ScanIdentifierOrKeyword(startLoc);
-
- if (c == '.')
- return Result.Success(new Token(TokenKind.MemberAccess, null, new(startLoc, buffer.GetSourceLocation(++cursor))));
-
- // todo: add the remaining possible paths
- return Result.Failure(new(LexerErrorCodes.FailedToLexToken, "Tried all possible token logic paths, but none was true.", Severity.Error));
- }
-
- ///
- public override IEnumerable Lex()
- {
- int maxFailedRunsLimit = 10;
- int failedRuns = 0;
-
- while (failedRuns < maxFailedRunsLimit)
- {
- var token = GetNextToken();
-
- if (token.IsError)
- {
- diagnostics.Add(token.Error);
- failedRuns++;
- continue;
- }
-
- if (token.Value.Kind == TokenKind.Eof)
- yield break;
-
- else
- yield return token.Value;
- }
- }
-
- private Result ScanNumber(SourceLocation startLoc)
- {
- bool dot = false;
-
- while (cursor < buffer.Length && (Char.IsAsciiDigit(buffer[cursor]) || buffer[cursor] == '_' || buffer[cursor] == '.'))
- {
- if (buffer[cursor] == '.')
- {
- if (dot)
- return Result.Success(new Token(TokenKind.Number, null, new(startLoc, buffer.GetSourceLocation(cursor - 1))));
-
- else
- dot = true;
- }
-
- cursor++;
- }
-
- return Result.Success(new Token(TokenKind.Number, null, new(startLoc, buffer.GetSourceLocation(cursor))));
- }
-
- private Result ScanStringLiteral(SourceLocation startLoc)
- {
- cursor++;
- bool escaping = false;
-
- while (cursor < buffer.Length)
- {
- if (buffer[cursor].IsNewline())
- {
- return Result.Failure(new(
- LexerErrorCodes.UnterminatedStringLiteral,
- new SourceSpan(startLoc, new(cursor, startLoc.Line, cursor - startLoc.Index + startLoc.Column)),
- "A string literal must begin and end in the same line.",
- Severity.Error
- ));
- }
-
- if (buffer[cursor] == '"' && !escaping)
- break;
-
- if (buffer[cursor] == '\\')
- escaping = !escaping;
-
- cursor++;
- }
-
- if (cursor < buffer.Length)
- cursor++; // skip end quote if anything beyond it
-
- return Result.Success(new Token(TokenKind.StringLiteral, null, new(startLoc, buffer.GetSourceLocation(cursor))));
- }
-
- private Result ScanIdentifierOrKeyword(SourceLocation startLoc)
- {
- while (cursor < buffer.Length && (Char.IsAsciiLetterOrDigit(buffer[cursor]) || buffer[cursor] == '_'))
- cursor++;
-
- SourceSpan span = new(startLoc, buffer.GetSourceLocation(cursor));
-
- if (Keywords.Contains(buffer[span]))
- {
- var token = new Token(GetKeywordTokenKind(buffer[span]), null, span); // is keyword
-
- return token.Kind == TokenKind.Unknown
- ? Result.Failure(new(
- LexerErrorCodes.FailedToIdentifyKeyword,
- new SourceSpan(startLoc, new(cursor, startLoc.Line, cursor - startLoc.Index + startLoc.Column)),
- "Despite identifying the object in question as a keyword, the lexer failed to resolve exactly which keyword it was." +
- "This likely means the keyword in question is reserved for future use, but isn't implemented yet.",
- Severity.Error
- ))
- : Result.Success(token);
- }
- else
- {
- return Result.Success(new Token(TokenKind.Identifier, null, span)); // is identifier
- }
- }
-
- private void SkipWhitespaceAndComments()
- {
- while (cursor < buffer.Length)
- {
- char c = buffer[cursor];
-
- if (Char.IsWhiteSpace(c))
- {
- cursor += buffer.CountUntilNotWhitespace(cursor);
- }
- else if (c == '#')
- {
- while (!buffer[cursor].IsNewline())
- cursor++;
- }
- else
- {
- break;
- }
- }
- }
-}
diff --git a/src/RSML.Language.Lexing/Diagnostics/LexerErrorCodes.cs b/src/RSML.Language.Lexing/Diagnostics/LexerErrorCodes.cs
index 700a8fc3..6834443d 100644
--- a/src/RSML.Language.Lexing/Diagnostics/LexerErrorCodes.cs
+++ b/src/RSML.Language.Lexing/Diagnostics/LexerErrorCodes.cs
@@ -6,4 +6,7 @@ internal static class LexerErrorCodes
public const string FailedToLexToken = "RL0001";
public const string UnterminatedStringLiteral = "RL0002";
public const string FailedToIdentifyKeyword = "RL0003";
+ public const string FailedToIdentifyPunctuation = "RL0004";
+ public const string ExpectedStdIdentifier = "RL0005";
+ public const string InvalidData = "RL0006";
}
diff --git a/src/RSML.Language.Lexing/GlobalSuppressions.cs b/src/RSML.Language.Lexing/GlobalSuppressions.cs
new file mode 100644
index 00000000..15e8ecdd
--- /dev/null
+++ b/src/RSML.Language.Lexing/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Would make the code ternary hell.", Scope = "member", Target = "~M:OceanApocalypse.RSML.Language.Lexing.Utf8.Utf8Lexer.GetNextToken(System.Buffers.SequenceReader{System.Byte}@,OceanApocalypse.RSML.Abstractions.Diagnostics.AbsolutePosition@)~OceanApocalypse.RSML.Abstractions.Diagnostics.Result{OceanApocalypse.RSML.Language.Lexing.Tokens.Token}")]
diff --git a/src/RSML.Language.Lexing/ILexer.cs b/src/RSML.Language.Lexing/ILexer.cs
index 36405eed..f177ca09 100644
--- a/src/RSML.Language.Lexing/ILexer.cs
+++ b/src/RSML.Language.Lexing/ILexer.cs
@@ -1,33 +1,24 @@
using System.Collections.Generic;
+using OceanApocalypse.RSML.Abstractions.Toolchain;
using OceanApocalypse.RSML.Language.Lexing.Tokens;
-using OceanApocalypse.RSML.Toolchain.Abstractions;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
-
namespace OceanApocalypse.RSML.Language.Lexing;
///
-/// Represents a lexer for RSML.
+/// Represents a lexer tasked with tokenizing RSML code.
///
-///
-/// :::tip[Avoid starting from scratch]
-/// If you want to add content on top of a lexer, without overriding
-/// the extra functionality it adds, you might want to take a look at
-/// .
-/// :::
-///
public interface ILexer : IToolchainComponent
{
- ///
- /// Tokenizes a source passed to the lexer.
+ ///
+ /// Tokenizes a string passed to the lexer.
///
/// The tokens.
- IEnumerable Lex();
+ IEnumerable Lex(string? data);
- ///
- /// Returns the next token.
+ ///
+ /// Tokenizes an array of characters passed to the lexer.
///
- /// The next token.
- Result GetNextToken();
+ /// The tokens.
+ IEnumerable Lex(char[] data);
}
diff --git a/src/RSML.Language.Lexing/Lexer.cs b/src/RSML.Language.Lexing/Lexer.cs
deleted file mode 100644
index 5787b92d..00000000
--- a/src/RSML.Language.Lexing/Lexer.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-
-using OceanApocalypse.RSML.Language.Lexing.Tokens;
-using OceanApocalypse.RSML.Toolchain.Abstractions;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
-
-namespace OceanApocalypse.RSML.Language.Lexing;
-
-///
-/// The base class for implementations of RSML lexers and tokenizers.
-///
-public abstract class Lexer : ILexer
-{
- ///
- /// The RSML keywords defined in its language specification.
- /// Also contains reserved keywords.
- ///
- public static readonly ImmutableArray Keywords = [
- // keywords
- "as", "end", "if", "let", "region", "requires", "return", "struct", "type",
- // modifiers
- "fn", "mut", "previous",
- // reserved keywords - not yet implemented but blocked from being used as identifiers
- "class", "interface"
- ];
-
- // this should always be synced with the keywords field
- internal static TokenKind GetKeywordTokenKind(scoped ReadOnlySpan keyword) => keyword switch
- {
- // keywords
- "as" => TokenKind.As,
- "end" => TokenKind.End,
- "if" => TokenKind.If,
- "let" => TokenKind.Let,
- "region" => TokenKind.Region,
- "requires" => TokenKind.Requires,
- "return" => TokenKind.Return,
- "struct" => TokenKind.Struct,
- "type" => TokenKind.Type,
-
- // modifiers
- "fn" => TokenKind.FunctionModifier,
- "mut" => TokenKind.MutableModifier,
- "previous" => TokenKind.PreviousModifier,
-
- _ => TokenKind.Unknown,
- };
-
- private bool isDisposed;
-
- ///
- public virtual ToolchainConfigurations Configuration { get; protected set; }
-
- ///
- public virtual void Inject(ToolchainConfigurations configuration) => Configuration |= configuration;
-
- ///
- public abstract IEnumerable Lex();
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Disposes of both managed and unmanaged resources.
- ///
- /// When set to false, disposes of unmanaged resources only.
- protected virtual void Dispose(bool disposing)
- {
- if (isDisposed)
- return;
-
- // dispose of managed stuff if disposing is true
-
- isDisposed = true;
- }
-
- ///
- public abstract Result GetNextToken();
-}
diff --git a/src/RSML.Language.Lexing/RSML.Language.Lexing.csproj b/src/RSML.Language.Lexing/RSML.Language.Lexing.csproj
index f9a69124..ca9166f9 100644
--- a/src/RSML.Language.Lexing/RSML.Language.Lexing.csproj
+++ b/src/RSML.Language.Lexing/RSML.Language.Lexing.csproj
@@ -1,54 +1,21 @@
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Language.Lexing
True
-
OceanApocalypse.RSML.Language.Lexing
RSML Lexers
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
-
+
+
+
+
diff --git a/src/RSML.Language.Lexing/ScannerLexer.cs b/src/RSML.Language.Lexing/ScannerLexer.cs
deleted file mode 100644
index ae055170..00000000
--- a/src/RSML.Language.Lexing/ScannerLexer.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-
-using OceanApocalypse.RSML.Language.Lexing.Tokens;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-namespace OceanApocalypse.RSML.Language.Lexing;
-
-///
-/// An implementation of a RSML lexer backed by a scanner.
-///
-/// A scanner.
-/// A collector for all emitted diagnostics.
-public class ScannerLexer(IScanner scanner, DiagnosticCollector diagnostics) : Lexer
-{
- ///
- public override Result GetNextToken() => throw new System.NotImplementedException();
-
- ///
- public override IEnumerable Lex() => throw new System.NotImplementedException();
-}
diff --git a/src/RSML.Language.Lexing/Tokens/Token.cs b/src/RSML.Language.Lexing/Tokens/Token.cs
index e06cb652..d301136e 100644
--- a/src/RSML.Language.Lexing/Tokens/Token.cs
+++ b/src/RSML.Language.Lexing/Tokens/Token.cs
@@ -1,4 +1,10 @@
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
+using System;
+using System.Buffers;
+using System.Collections.Frozen;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using OceanApocalypse.RSML.Abstractions;
namespace OceanApocalypse.RSML.Language.Lexing.Tokens;
@@ -7,12 +13,139 @@ namespace OceanApocalypse.RSML.Language.Lexing.Tokens;
/// Represents a RSML token.
///
/// An integer that identifies the type of token.
-/// The token's value.
-/// The span where the token occurs.
-public record struct Token(TokenKind Kind, object? Value, SourceSpan Span)
+/// The offset at which the token begins.
+/// The token's length.
+[StructLayout(LayoutKind.Sequential)]
+public record struct Token(TokenKind Kind, long StartOffset, long Length)
{
+ ///
+ /// Any keyword with anything that exceeds this length immediately skips the keyword check.
+ ///
+ private const int MaxKeywordLength = 32;
+
///
/// Empty token. Used when something goes wrong.
///
- public readonly static Token Empty = new(TokenKind.Unknown, null, SourceSpan.Empty);
+ public readonly static Token Empty = new(TokenKind.Unknown, 0L, 0L);
+
+ ///
+ /// The available keywords and keyword modifiers.
+ ///
+ public readonly static FrozenSet Keywords = [
+ "return", "if", "requires", "end", "previous", "region", "let",
+ "mut", "fn", "exec", "type", "as", "struct",
+ "class", "interface" // these 2 are reserved
+ ];
+
+ ///
+ /// Gets the token kind that applies to the given keyword or keyword modifier.
+ ///
+ /// The keyword or modifier sequence.
+ /// The matching token kind.
+ public static TokenKind GetKeywordKind(ReadOnlySequence sequence)
+ {
+ if (sequence.Length > MaxKeywordLength)
+ return TokenKind.Unknown; // we love failing fast
+
+ Span keywordBuffer = stackalloc char[MaxKeywordLength];
+ int keywordLength = sequence.SafelyDecodeToCharacterSpan(keywordBuffer);
+
+ return keywordBuffer[..keywordLength] switch
+ {
+ // keywords
+ "as" => TokenKind.AsKeyword,
+ "end" => TokenKind.EndKeyword,
+ "if" => TokenKind.IfKeyword,
+ "let" => TokenKind.LetKeyword,
+ "region" => TokenKind.RegionKeyword,
+ "requires" => TokenKind.RequiresKeyword,
+ "return" => TokenKind.ReturnKeyword,
+ "struct" => TokenKind.StructKeyword,
+ "type" => TokenKind.TypeKeyword,
+
+ // modifiers
+ "fn" => TokenKind.FunctionModifier,
+ "mut" => TokenKind.MutableModifier,
+ "previous" => TokenKind.PreviousModifier,
+
+ _ => TokenKind.Unknown,
+ };
+ }
+
+ ///
+ /// Gets the token kind that applies to the given punctuation symbol.
+ ///
+ /// The punctuation character.
+ ///
+ /// The character that follows .
+ /// Set to 0 if out of bounds/not punctuation.
+ ///
+ ///
+ public static TokenKind GetPunctuationKind(byte punctuation, byte peekedChar) => punctuation switch
+ {
+ // math operations
+ (byte)'+' => TokenKind.Plus,
+ (byte)'-' => TokenKind.Minus,
+ (byte)'*' => TokenKind.Star,
+ (byte)'/' => TokenKind.Slash,
+
+ // equality
+ (byte)'=' when peekedChar is (byte)'=' => TokenKind.EqualToOperator,
+ (byte)'!' when peekedChar is (byte)'=' => TokenKind.NotEqualToOperator,
+ (byte)'>' when peekedChar is (byte)'=' => TokenKind.GreaterThanOrEqualToOperator,
+ (byte)'<' when peekedChar is (byte)'=' => TokenKind.LessThanOrEqualToOperator,
+ (byte)'>' => TokenKind.GreaterThanOperator,
+ (byte)'<' => TokenKind.LessThanOperator,
+
+ // boolean logic
+ (byte)'&' when peekedChar is (byte)'&' => TokenKind.LogicAndOperator,
+ (byte)'|' when peekedChar is (byte)'|' => TokenKind.LogicOrOperator,
+ (byte)'!' => TokenKind.NotOperator,
+
+ (byte)'=' => TokenKind.AssignmentOperator,
+
+ // reserved
+ (byte)'&' => TokenKind.Unknown,
+ (byte)'|' => TokenKind.Unknown,
+
+ _ => TokenKind.Unknown
+ };
+
+ ///
+ /// Returns true if the given token kind matches a valid keyword that is not a modifier.
+ ///
+ /// The token kind to check against.
+ /// True if the kind is a keyword.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsStrictlyKeyword(TokenKind kind) =>
+ IsKeywordOrModifier(kind) && !IsStrictlyKeywordModifier(kind);
+
+ ///
+ /// Returns true if the given token kind matches a valid keyword modifier.
+ ///
+ /// The token kind to check against.
+ /// True if the kind is a keyword modifier.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsStrictlyKeywordModifier(TokenKind kind) =>
+ kind is TokenKind.PreviousModifier or TokenKind.MutableModifier or TokenKind.FunctionModifier;
+
+ ///
+ /// Returns true if the given token kind points to an identifier, be it
+ /// from the standard library or not.
+ ///
+ /// The token kind to check against.
+ /// True if the kind is an identifier.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsIdentifier(TokenKind kind) =>
+ kind is TokenKind.Identifier or TokenKind.StandardLibraryIdentifier;
+
+ ///
+ /// Returns true if the given token kind points to any keyword, be it
+ /// a keyword or a keyword modifier.
+ ///
+ /// The token kind to check against.
+ /// True if the kind is a keyword or modifier.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsKeywordOrModifier(TokenKind kind) =>
+ kind is >= TokenKind.ReturnKeyword and <= TokenKind.StructKeyword;
}
diff --git a/src/RSML.Language.Lexing/Tokens/TokenKind.cs b/src/RSML.Language.Lexing/Tokens/TokenKind.cs
index a5d99c18..e43962f9 100644
--- a/src/RSML.Language.Lexing/Tokens/TokenKind.cs
+++ b/src/RSML.Language.Lexing/Tokens/TokenKind.cs
@@ -8,6 +8,11 @@ public enum TokenKind
///
/// An unknown token kind.
///
+ ///
+ /// :::note
+ /// Unknown tokens usually lead to toolchain errors.
+ /// :::
+ ///
Unknown,
///
@@ -23,7 +28,7 @@ public enum TokenKind
///
/// A numeric literal.
///
- Number,
+ NumericLiteral,
///
/// A string literal.
@@ -31,29 +36,29 @@ public enum TokenKind
StringLiteral,
///
- /// A built-in constant.
+ /// A built-in identifier.
///
- SystemConstant,
+ StandardLibraryIdentifier,
///
/// The return keyword. Stops execution of the current scope with a given value.
///
- Return,
+ ReturnKeyword,
///
/// The if keyword. Conditionalizes a statement into running only if the condition is met.
///
- If,
+ IfKeyword,
///
/// The requires keyword. Indicates extensions the file depends on.
///
- Requires,
+ RequiresKeyword,
///
/// The end keyword. Ends the file.
///
- End,
+ EndKeyword,
///
/// The previous keyword. Modifies end into closing the previous region instead.
@@ -63,12 +68,12 @@ public enum TokenKind
///
/// The region keyword. Creates a conditionalized region.
///
- Region,
+ RegionKeyword,
///
/// The let keyword. Declares and assigns a constant.
///
- Let,
+ LetKeyword,
///
/// The mut keyword. Modifies let into creating a variable instead.
@@ -80,55 +85,61 @@ public enum TokenKind
///
FunctionModifier,
+ ///
+ /// The exec keyword. Executes a function without you having to use discards.
+ /// Treats every function as a void function.
+ ///
+ ExecKeyword,
+
///
/// The type keyword. Creates a type.
///
- Type,
+ TypeKeyword,
///
/// The as keyword.
///
- As,
+ AsKeyword,
///
/// The struct keyword. Used with type and as to create a struct type.
///
- Struct,
+ StructKeyword,
///
/// The assignment operator (=).
///
- Assignment,
+ AssignmentOperator,
///
/// The equality operator (==).
///
- Equality,
+ EqualToOperator,
///
/// The inequality operator (!=).
///
- Inequality,
+ NotEqualToOperator,
///
/// The greater-than operator (>).
///
- GreaterThan,
+ GreaterThanOperator,
///
/// The less-than operator (<).
///
- LessThan,
+ LessThanOperator,
///
/// The greater-than-or-equal-to operator (>=).
///
- GreaterThanOrEqualTo,
+ GreaterThanOrEqualToOperator,
///
/// The less-than-or-equal-to operator (<=).
///
- LessThanOrEqualTo,
+ LessThanOrEqualToOperator,
///
/// The colon (:).
@@ -173,7 +184,7 @@ public enum TokenKind
///
/// The closed brace (}).
///
- CloseBrace,
+ ClosedBrace,
///
/// The open parenthesis.
@@ -183,10 +194,38 @@ public enum TokenKind
///
/// The closed parenthesis.
///
- CloseParenthesis,
+ ClosedParenthesis,
///
/// The member access mark (.), which is a dot.
///
- MemberAccess
+ MemberAccess,
+
+ ///
+ /// The NOT operator. It swaps the boolean value of whatever
+ /// comes next.
+ ///
+ NotOperator,
+
+ ///
+ /// The logic AND operator. Returns true only if both the
+ /// left and right sides evaluate to true.
+ ///
+ LogicAndOperator,
+
+ ///
+ /// The logic OR operator. Returns true if either left, right
+ /// or both sides evaluate to true.
+ ///
+ LogicOrOperator,
+
+ ///
+ /// The at symbol (@). Reserved for future use.
+ ///
+ AtSymbol,
+
+ ///
+ /// A comment, including the # symbol.
+ ///
+ Comment
}
diff --git a/src/RSML.Language.Lexing/Utf8/IUnsafeLexer.cs b/src/RSML.Language.Lexing/Utf8/IUnsafeLexer.cs
new file mode 100644
index 00000000..214665da
--- /dev/null
+++ b/src/RSML.Language.Lexing/Utf8/IUnsafeLexer.cs
@@ -0,0 +1,19 @@
+using System;
+
+using OceanApocalypse.RSML.Language.Lexing.Tokens;
+
+namespace OceanApocalypse.RSML.Language.Lexing.Utf8;
+
+///
+/// Represents a lexer with attributes or methods that utilize the unsafe context.
+///
+[CLSCompliant(false)]
+public unsafe interface IUnsafeLexer : ILexer
+{
+ ///
+ /// Tokenizes an array of bytes passed to the lexer, with UTF-8 encoding.
+ ///
+ /// The tokens.
+ [CLSCompliant(false)]
+ Token* Lex(byte* data, int charCount);
+}
diff --git a/src/RSML.Language.Lexing/Utf8/IUtf8Lexer.cs b/src/RSML.Language.Lexing/Utf8/IUtf8Lexer.cs
new file mode 100644
index 00000000..7d0f2aeb
--- /dev/null
+++ b/src/RSML.Language.Lexing/Utf8/IUtf8Lexer.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+
+using OceanApocalypse.RSML.Abstractions.Diagnostics;
+using OceanApocalypse.RSML.Language.Lexing.Tokens;
+
+
+namespace OceanApocalypse.RSML.Language.Lexing.Utf8;
+
+///
+/// Represents a UTF-8 lexer for RSML.
+///
+public interface IUtf8Lexer : ILexer
+ where TInput : unmanaged, IEquatable
+{
+ ///
+ /// Tokenizes a source passed to the lexer.
+ ///
+ /// The tokens.
+ IEnumerable Lex(ReadOnlySequence data);
+
+ ///
+ /// Returns the next token.
+ ///
+ /// The reader whose data to read.
+ /// The current expected position.
+ /// The next token.
+ Result GetNextToken(ref SequenceReader reader, ref AbsolutePosition currentPosition);
+}
diff --git a/src/RSML.Language.Lexing/Utf8/Utf8Lexer.cs b/src/RSML.Language.Lexing/Utf8/Utf8Lexer.cs
new file mode 100644
index 00000000..4c796130
--- /dev/null
+++ b/src/RSML.Language.Lexing/Utf8/Utf8Lexer.cs
@@ -0,0 +1,418 @@
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Data;
+using System.Text;
+
+using OceanApocalypse.RSML.Abstractions;
+using OceanApocalypse.RSML.Abstractions.Diagnostics;
+using OceanApocalypse.RSML.Abstractions.Panic;
+using OceanApocalypse.RSML.Abstractions.Toolchain;
+using OceanApocalypse.RSML.Language.Lexing.Diagnostics;
+using OceanApocalypse.RSML.Language.Lexing.Tokens;
+
+namespace OceanApocalypse.RSML.Language.Lexing.Utf8;
+
+///
+/// An implementation of a RSML lexer backed by a given UTF-8 buffer.
+///
+///
+/// Initializes a new lexer with a given configuration and diagnostic collector.
+///
+/// A collector with all the diagnostics that were and will be emitted.
+/// Configurations for the toolchain components.
+public class Utf8Lexer(DiagnosticCollector diagnosticCollector, ToolchainConfiguration? configuration = null) : IUtf8Lexer
+{
+ private bool isDisposed;
+ private bool wasUsed;
+
+ ///
+ /// A collector containing all emitted diagnostics.
+ ///
+ protected DiagnosticCollector Diagnostics { get; } = diagnosticCollector;
+
+ ///
+ public ToolchainConfiguration Configuration { get; protected set; } = configuration ?? ToolchainConfiguration.Default;
+
+ ///
+ /// :::note[Diagnostic output]
+ /// This method does not add diagnostics to the collector
+ /// (): it only returns them when it
+ /// proves necessary.
+ /// :::
+ ///
+ ///
+ public Result GetNextToken(ref SequenceReader reader, ref AbsolutePosition currentPosition)
+ {
+ wasUsed = true;
+ AbsolutePosition.ThrowIfInvalid(currentPosition);
+
+ SkipWhitespaceAndComments(ref reader, ref currentPosition);
+
+ if (reader.End || !reader.TryPeek(out byte b))
+ return Result.Success(new Token(TokenKind.Eof, reader.Consumed, reader.Consumed));
+
+ if (!b.IsAscii())
+ {
+ return Result.Failure(new(
+ LexerErrorCodes.InvalidData,
+ reader.Consumed, currentPosition,
+ reader.Consumed, currentPosition,
+ "Expected an ASCII character but received a non-ASCII character.",
+ Severity.Error
+ ));
+ }
+
+ char c = (char)b;
+
+ // strings
+ if (c == '"')
+ return ScanStringLiteral(ref reader, ref currentPosition);
+
+ // number literals
+ if (b.IsAsciiDigit())
+ return ScanNumber(ref reader, startChar: b, ref currentPosition);
+
+ // identifiers and keywords
+ if (b.IsAsciiLetter() || c == '_')
+ return ScanIdentifierOrKeyword(ref reader, ref currentPosition);
+
+ // standard library identifiers
+ if (c == '$')
+ return ScanStdIdentifier(ref reader, ref currentPosition);
+
+ // member access notation
+ if (c == '.')
+ {
+ reader.Advance(1);
+ currentPosition.Column++;
+ return Result.Success(new Token(TokenKind.MemberAccess, reader.Consumed, 1));
+ }
+
+ // punctuation
+ if (b.IsRsmlPunctuation())
+ return ScanPunctuation(ref reader, ref currentPosition);
+
+ // comments
+ if (Configuration.EmitComments && b == (byte)'#')
+ return ScanComment(ref reader, ref currentPosition);
+
+ return Result.Failure(new(
+ code: LexerErrorCodes.FailedToLexToken,
+ "Tried all possible token logic paths, but none was true. This likely means you used a character not recognized by the lexer," +
+ "but it may also mean the lexer is mal-functioning.",
+ Severity.Critical
+ ));
+ }
+
+ ///
+ public IEnumerable Lex(ReadOnlySequence data)
+ {
+ wasUsed = true;
+ int failedRuns = 0;
+
+ var reader = new SequenceReader(data);
+ var position = AbsolutePosition.Default;
+ var writer = new ArrayBufferWriter((int)(data.Length / 2));
+
+ if (!position.IsValid)
+ {
+ position.Line = 1;
+ position.Column = 1;
+ }
+
+ while (Configuration.MaximumAllowedFailuresPerComponent <= 0 || failedRuns < Configuration.MaximumAllowedFailuresPerComponent)
+ {
+ Span tokens = writer.GetSpan(64);
+ int idx = 0;
+
+ while (idx < tokens.Length && Configuration.MaximumAllowedFailuresPerComponent <= 0 || failedRuns < Configuration.MaximumAllowedFailuresPerComponent)
+ {
+ var token = GetNextToken(ref reader, ref position);
+
+ if (token.IsError)
+ {
+ Diagnostics.Add(token.Error);
+ failedRuns++;
+ continue;
+ }
+
+ if (token.Value.Kind == TokenKind.Eof)
+ {
+ return writer.WrittenSpan.ToArray();
+ }
+
+ else
+ {
+ tokens[idx] = token.Value;
+ idx++;
+ }
+ }
+
+ writer.Advance(idx);
+ }
+
+ throw new ExceededMaxAmountOfFailuresException(
+ $"This instance of the lexer was allowed to fail up to {Configuration.MaximumAllowedFailuresPerComponent} times, yet it failed {failedRuns}."
+ );
+ }
+
+ private static Result ScanNumber(ref SequenceReader reader, byte startChar, ref AbsolutePosition position)
+ {
+ var startPos = position;
+ var startLoc = reader.Consumed;
+
+ byte b = startChar;
+ bool hasDotSeparator = false;
+
+ do
+ {
+ reader.Advance(1);
+ position.Column++;
+
+ if (!b.IsAscii())
+ {
+ return Result.Failure(new(
+ LexerErrorCodes.InvalidData,
+ startLoc, startPos,
+ startLoc, position,
+ "Expected an ASCII character but received a non-ASCII character.",
+ Severity.Error
+ ));
+ }
+
+ if (b == '.')
+ hasDotSeparator = true;
+
+ } while (!reader.End && reader.TryPeek(out b) && (b.IsAsciiDigit() || b == '_' || (b == '.' && !hasDotSeparator)));
+
+ return Result.Success(new Token(TokenKind.NumericLiteral, startLoc, reader.Consumed - startLoc));
+ }
+
+ private static Result ScanStringLiteral(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ var startPos = position;
+ var startLoc = reader.Consumed;
+ bool escaping = false;
+
+ if (reader.End)
+ {
+ return Result.Failure(new(
+ LexerErrorCodes.UnterminatedStringLiteral,
+ startLoc, startPos,
+ reader.Consumed, position,
+ "A string literal must begin and end in the same line.",
+ Severity.Error
+ ));
+ }
+
+ reader.Advance(1);
+ position.Column++;
+
+ while (!reader.End && reader.TryPeek(out byte b))
+ {
+ reader.Advance(1);
+ position.Column++;
+
+ if (b.IsAsciiNewline())
+ {
+ position.MoveToStartOfNextLine();
+
+ return Result.Failure(new(
+ LexerErrorCodes.UnterminatedStringLiteral,
+ startLoc, startPos,
+ reader.Consumed, position,
+ "A string literal must begin and end in the same line.",
+ Severity.Error
+ ));
+ }
+
+ if (b == '"' && !escaping)
+ break;
+
+ if (b == '\\')
+ escaping = !escaping;
+ }
+
+ return Result.Success(new Token(TokenKind.StringLiteral, startLoc, reader.Consumed - startLoc));
+ }
+
+ private static Result ScanStdIdentifier(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ var startPos = position;
+ var startLoc = reader.Consumed;
+
+ do
+ {
+ reader.Advance(1);
+ position.Column++;
+
+ } while (!reader.End && reader.TryPeek(out byte b) && (b.IsAsciiLetter() || b.IsAsciiDigit() || b == '_'));
+
+ return reader.Consumed == startLoc + 1
+ ? Result.Failure(new(
+ LexerErrorCodes.ExpectedStdIdentifier,
+ startLoc, startPos,
+ reader.Consumed, position,
+ "Expected a standard library identifier, yet there was no valid identifier after the $ symbol.",
+ Severity.Error
+ ))
+ : Result.Success(new Token(TokenKind.StandardLibraryIdentifier, startLoc, reader.Consumed - startLoc));
+ }
+
+ private static Result ScanIdentifierOrKeyword(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ var startPos = position;
+ var startLoc = reader.Consumed;
+
+ while (!reader.End && reader.TryPeek(out byte b) && (b.IsAsciiLetter() || b.IsAsciiDigit() || b == '_'))
+ {
+ reader.Advance(1);
+ position.Column++;
+ }
+
+ int sliceLength = (int)(reader.Consumed - startLoc);
+ var slice = reader.Sequence.Slice(startLoc, sliceLength);
+
+ if (Token.Keywords.ContainsUtf8(slice))
+ {
+ var token = new Token(Token.GetKeywordKind(slice), startLoc, sliceLength); // is keyword
+
+ return token.Kind == TokenKind.Unknown
+ ? Result.Failure(new(
+ LexerErrorCodes.FailedToIdentifyKeyword,
+ startLoc, startPos,
+ reader.Consumed, position,
+ "Despite identifying the object in question as a keyword, the lexer failed to resolve exactly which keyword it was." +
+ "This likely means the keyword in question is reserved for future use, but isn't implemented yet.",
+ Severity.Error
+ ))
+ : Result.Success(token);
+ }
+ else
+ {
+ return Result.Success(new Token(TokenKind.Identifier, startLoc, sliceLength)); // is identifier
+ }
+ }
+
+ private static Result ScanPunctuation(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ var startPos = position;
+ var startLoc = reader.Consumed;
+
+ reader.TryRead(out byte first); // will always work and will always be ASCII
+ position.Column++;
+
+ var successful = reader.TryPeek(out byte second);
+
+ if (successful && !second.IsAscii())
+ successful = false;
+
+ if (successful)
+ {
+ reader.Advance(1);
+ position.Column++;
+ }
+
+ TokenKind kind = Token.GetPunctuationKind(first, successful ? second : (byte)0);
+
+ return kind == TokenKind.Unknown
+ ? Result.Failure(new(
+ LexerErrorCodes.FailedToIdentifyPunctuation,
+ startLoc, startPos,
+ reader.Consumed, position,
+ "Despite identifying the object in question as punctuation, the lexer failed to resolve exactly which punctuation it was." +
+ "This might mean the punctuation in question is reserved for future use, and not implemented yet.",
+ Severity.Error
+ ))
+ : Result.Success(new Token(kind, startLoc, reader.Consumed - startLoc));
+ }
+
+ private static Result ScanComment(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ var startLoc = reader.Consumed;
+ var found = reader.TryAdvanceToAny([(byte)'\r', (byte)'\n'], advancePastDelimiter: false);
+ // not advancing past cuz it gets handled in next GetNextToken call
+
+ if (!found) // consume everything - we're EOF
+ reader.AdvanceToEnd();
+
+ int tokenLength = (int)(reader.Consumed - startLoc);
+ position.Column += tokenLength;
+ return Result.Success(new Token(TokenKind.Comment, startLoc, tokenLength));
+ }
+
+ private void SkipWhitespaceAndComments(ref SequenceReader reader, ref AbsolutePosition position)
+ {
+ while (!reader.End && reader.TryPeek(out byte b))
+ {
+ if (b.IsAsciiNewline())
+ {
+ reader.Advance(1);
+ position.MoveToStartOfNextLine();
+
+ if (b == (byte)'\r' && reader.TryPeek(out byte next) && next == (byte)'\n')
+ reader.Advance(1);
+ }
+ else if (b.IsAsciiWhitespace())
+ {
+ position.Column += (int)reader.AdvancePastAny([9, 11, 12, 30]);
+ }
+ else if (b == (byte)'#' && !Configuration.EmitComments)
+ {
+ _ = ScanComment(ref reader, ref position);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ ///
+ public void Inject(ToolchainConfiguration configuration)
+ {
+ if (wasUsed)
+ throw new ReadOnlyException("The configuration has already been apply and cannot be altered.");
+
+ Configuration = configuration;
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes of internally used unmanaged resources.
+ ///
+ /// If true, also disposes of managed resources.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (isDisposed)
+ return;
+
+ // unmanaged things here
+
+ if (disposing)
+ { } // managed things
+
+ isDisposed = true;
+ }
+
+ ///
+ public IEnumerable Lex(string? data)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(data);
+ return Lex(new ReadOnlySequence(Encoding.Default.GetBytes(data)));
+ }
+
+ ///
+ public IEnumerable Lex(char[] data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+ return Lex(new ReadOnlySequence(Encoding.Default.GetBytes(data)));
+ }
+}
diff --git a/src/RSML.Language.Parsing/IParser.cs b/src/RSML.Language.Parsing/IParser.cs
index a98e1407..aab60029 100644
--- a/src/RSML.Language.Parsing/IParser.cs
+++ b/src/RSML.Language.Parsing/IParser.cs
@@ -1,4 +1,4 @@
-using OceanApocalypse.RSML.Toolchain.Abstractions;
+using OceanApocalypse.RSML.Abstractions.Toolchain;
namespace OceanApocalypse.RSML.Language.Parsing;
diff --git a/src/RSML.Language.Parsing/Parser.cs b/src/RSML.Language.Parsing/Parser.cs
index aab33816..ae55cbd4 100644
--- a/src/RSML.Language.Parsing/Parser.cs
+++ b/src/RSML.Language.Parsing/Parser.cs
@@ -1,6 +1,6 @@
using System;
-using OceanApocalypse.RSML.Toolchain.Abstractions;
+using OceanApocalypse.RSML.Abstractions.Toolchain;
namespace OceanApocalypse.RSML.Language.Parsing;
@@ -9,10 +9,12 @@ namespace OceanApocalypse.RSML.Language.Parsing;
///
public abstract class Parser : IParser
{
+ // todo: make this implement IParser correctly
+
private bool isDisposed;
///
- public ToolchainConfigurations Configuration { get; protected set; }
+ ToolchainConfiguration IToolchainComponent.Configuration => throw new NotImplementedException();
///
public void Dispose()
@@ -22,7 +24,7 @@ public void Dispose()
}
///
- public virtual void Inject(ToolchainConfigurations configuration) => throw new NotImplementedException();
+ public void Inject(ToolchainConfiguration configuration) => throw new NotImplementedException();
///
/// Disposes of both managed and unmanaged resources.
diff --git a/src/RSML.Language.Parsing/RSML.Language.Parsing.csproj b/src/RSML.Language.Parsing/RSML.Language.Parsing.csproj
index 16ada047..9b53ea1d 100644
--- a/src/RSML.Language.Parsing/RSML.Language.Parsing.csproj
+++ b/src/RSML.Language.Parsing/RSML.Language.Parsing.csproj
@@ -1,54 +1,19 @@
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Language.Parsing
True
-
OceanApocalypse.RSML.Language.Parsing
RSML Parsers
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
-
+
+
diff --git a/src/RSML.Native/RSML.Native.csproj b/src/RSML.Native/RSML.Native.csproj
index 75d69787..922e5baf 100644
--- a/src/RSML.Native/RSML.Native.csproj
+++ b/src/RSML.Native/RSML.Native.csproj
@@ -2,7 +2,6 @@
net10.0
- true
OceanApocalypse.RSML.Native
Library
@@ -20,26 +19,9 @@
RSML Native ABI
Native C interopability for RSML.
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
DSL;native;interop;c;cpp;c++;language;logic;logic-path;rsml;rsea;decision;oss
- README.md
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
diff --git a/src/RSML.Toolchain.Abstractions/CharacterExtensions.cs b/src/RSML.Toolchain.Abstractions/CharacterExtensions.cs
deleted file mode 100644
index 0af8522c..00000000
--- a/src/RSML.Toolchain.Abstractions/CharacterExtensions.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.Runtime.CompilerServices;
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions;
-
-///
-/// Extension members for characters.
-///
-public static class CharacterExtensions
-{
- extension(char character)
- {
- ///
- /// Checks if the character in question represents a newline. Allowed newlines are:
- /// CR, LF, line break and paragraph break.
- ///
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsNewline() => character is '\r' or '\n' or '\u2028' or '\u2029';
-
- ///
- /// Checks if the character in question is ASCII punctuation. Used by RSML.
- ///
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsAsciiPunctuation() => character is '=' or '<' or '>' or '!' or '|' or '&';
- }
-
- extension(IImmutableList strings)
- {
- ///
- /// Checks if an immutable array of strings contains a given character span.
- ///
- /// The span to check for.
- /// The comparison mode to apply.
- /// True if found.
- public bool Contains(ReadOnlySpan span, StringComparison comparisonType = StringComparison.Ordinal)
- {
- foreach (string @string in strings)
- {
- if (!span.Equals(@string, comparisonType))
- return false;
- }
-
- return true;
- }
- }
-}
diff --git a/src/RSML.Toolchain.Abstractions/GlobalSuppressions.cs b/src/RSML.Toolchain.Abstractions/GlobalSuppressions.cs
deleted file mode 100644
index f5b887bf..00000000
--- a/src/RSML.Toolchain.Abstractions/GlobalSuppressions.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// This file is used by Code Analysis to maintain SuppressMessage
-// attributes that are applied to this project.
-// Project-level suppressions either have no target or are given
-// a specific target and scoped to a namespace, type, member, etc.
-
-using System.Diagnostics.CodeAnalysis;
-
-[assembly: SuppressMessage("Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = "Refers to a single location.", Scope = "member", Target = "~P:OceanApocalypse.RSML.Toolchain.Abstractions.Sources.IBuffer.Item(OceanApocalypse.RSML.Toolchain.Abstractions.Sources.SourceLocation)")]
-[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Hides an allocation.", Scope = "member", Target = "~M:OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics.DiagnosticCollector.GetAll~System.Collections.Immutable.ImmutableArray{OceanApocalypse.RSML.Toolchain.Abstractions.Diagnostics.Diagnostic}")]
diff --git a/src/RSML.Toolchain.Abstractions/RSML.Toolchain.Abstractions.csproj b/src/RSML.Toolchain.Abstractions/RSML.Toolchain.Abstractions.csproj
deleted file mode 100644
index f9933a7f..00000000
--- a/src/RSML.Toolchain.Abstractions/RSML.Toolchain.Abstractions.csproj
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
- net10.0;net8.0
- true
- true
-
- Library
- true
-
- OceanApocalypse.RSML.Toolchain.Abstractions
-
-
-
- True
-
- OceanApocalypse.RSML.Toolchain.Abstractions
- RSML Abstractions
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
diff --git a/src/RSML.Toolchain.Abstractions/Sources/IBuffer.cs b/src/RSML.Toolchain.Abstractions/Sources/IBuffer.cs
deleted file mode 100644
index b467bca8..00000000
--- a/src/RSML.Toolchain.Abstractions/Sources/IBuffer.cs
+++ /dev/null
@@ -1,209 +0,0 @@
-using System;
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-///
-/// Represents a buffer of characters.
-///
-public interface IBuffer : IDisposable, IEquatable, IEquatable, IEquatable, IEquatable>
-{
- // todo: add methods that can mutate the buffer (coming to v3.0.0-prerelease2 ??)
-
- ///
- /// Whether the source is completely empty.
- ///
- bool IsEmpty => Length == 0;
-
- ///
- /// Whether the source can be mutated.
- ///
- bool IsReadOnly { get; }
-
- ///
- /// The length of the source.
- ///
- int Length { get; }
-
- ///
- /// The total amount of lines in the buffer.
- ///
- ///
- /// Keep in mind lines might be empty.
- ///
- int LineCount { get; }
-
- ///
- /// Gets a single item out of the buffer.
- ///
- /// The index of the item to retrieve.
- /// The item.
- char this[int index] { get; }
-
- ///
- /// Gets a span of items out of the buffer.
- ///
- /// The start and end locations of the span to retrieve.
- /// The items.
- ReadOnlySpan this[SourceSpan span] { get; }
-
- ///
- /// Gets a single item out of the buffer.
- ///
- /// The location of the item to retrieve.
- /// The item.
- char this[SourceLocation location] { get; }
-
- ///
- /// Counts the amount of items until the next line separator in the buffer, relative to a given .
- /// Only line separators count - regular whitespace do not. CRLF counts as a single line separator, to avoid double counting.
- ///
- /// The index at which to start counting.
- ///
- /// Whether the line separator at which the method stopped is the CR in a CRLF sequence. If true, the next item in the buffer is LF.
- ///
- /// The index of the next line separator, relative to an .
- int CountUntilEndOfLine(int index, out bool isCrLf);
-
- ///
- /// Counts the amount of items until the next non-whitespace item in the buffer, relative to a given .
- /// Line separators are included in the whitespace category.
- ///
- /// The index at which to start counting.
- /// The index of the next non-whitespace item, relative to a .
- int CountUntilNotWhitespace(int index);
-
- ///
- /// Counts the amount of items until the next whitespace item in the buffer, relative to a given .
- /// Line separators are included in the whitespace category.
- ///
- /// The index at which to start counting.
- /// The index of the next whitespace item, relative to a .
- int CountUntilWhitespace(int index);
-
- ///
- /// Counts the amount of items, starting from a given ,
- /// while a returns true.
- ///
- ///
- /// A function that takes the current index (relative to ),
- /// which is incremented every item, and the item associated with it. Execution stops when
- /// the predicate returns false or the index is out of bounds.
- ///
- ///
- /// The index at which to start counting; all indexes will also be given to the
- /// as an offset that when added to the index of the position
- /// equal the actual index.
- ///
- /// The amount of items counted.
- int CountWhile(Func predicate, int index);
-
- ///
- /// Returns the length of a line given its 0-based line number.
- /// Line separators do not count towards the length.
- ///
- /// The 0-based line number.
- /// The length of the line.
- int GetLengthOfLine(int lineNumber);
-
- ///
- /// Returns the length of a line given a 0-based index of one
- /// of its items.
- /// Line separators do not count towards the length.
- ///
- /// The 0-based index whose line is considered.
- /// The length of the line.
- int GetLengthOfLineFromIndex(int index);
-
- ///
- /// Given a 0-based line number, returns the matching line as an array of buffer items.
- ///
- /// The 0-based line number.
- /// The line as an array of items.
- ReadOnlySpan GetLine(int lineNumber);
-
- ///
- /// Tries to read the line that contains the item at .
- /// No end of line characters are added.
- ///
- /// The index at which to determine what the current line is.
- /// The line, as an array of items.
- ReadOnlySpan GetLineFromIndex(int index);
-
- ///
- /// Determines the 0-based line number of the line that contains the item located at .
- ///
- /// The index whose parent line's number is to be returned.
- /// The 0-based number of the line that contains item located at .
- int GetLineNumberFromIndex(int index);
-
- ///
- /// Converts an index into a location.
- ///
- /// The index.
- /// The location.
- SourceLocation GetSourceLocation(int index);
-
- ///
- /// Converts the buffer region into a span.
- ///
- /// The starting index.
- /// The end index, which is included in the span.
- /// The span.
- SourceSpan GetSourceSpan(int startIndex, int endIndex);
-
- ///
- /// Slices a region of the buffer.
- ///
- /// The index of the first item in the slice.
- /// The amount of items to slice starting at .
- /// A slice, as an array of items.
- ReadOnlySpan Slice(int start, int length);
-
- ///
- /// Slices a region of the buffer into a performant span.
- ///
- /// The index of the first item in the slice.
- /// The span serving as the destination for the slice.
- bool TrySlice(int start, Span slice);
-
- ///
- /// Slices a region of the buffer into a performant span.
- ///
- /// The span indicating what the slice is.
- /// The span serving as the destination for the slice.
- bool TrySlice(SourceSpan sourceSpan, Span slice);
-
- ///
- /// Tries to return the item at .
- ///
- /// The index of the character.
- /// The item.
- /// False if the buffer is out of bounds or an exception occured.
- bool TryGetChar(int index, out char item);
-
- ///
- /// Tries to return the item at the specified .
- ///
- /// The item's location.
- /// The item.
- /// False if the buffer is out of bounds or an exception occured.
- bool TryGetChar(SourceLocation location, out char item);
-
- ///
- /// Given a 0-based line number, assigns the exact line to a result buffer ().
- /// No end of line characters are added.
- ///
- /// The 0-based line number.
- /// The destination buffer for the line.
- /// True if successful.
- bool TryGetLine(int lineNumber, Span destination);
-
- ///
- /// Tries to read the line that contains the item at .
- /// No end of line characters are added.
- ///
- /// The index at which to determine what the current line is.
- /// The destination span that will contain the line.
- /// True if successful.
- bool TryGetLineFromIndex(int index, Span destination);
-}
diff --git a/src/RSML.Toolchain.Abstractions/Sources/IScanner.cs b/src/RSML.Toolchain.Abstractions/Sources/IScanner.cs
deleted file mode 100644
index 043762ca..00000000
--- a/src/RSML.Toolchain.Abstractions/Sources/IScanner.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-///
-/// Represents a sequential scanner.
-///
-public interface IScanner : IDisposable, IEquatable
-{
- // todo: planned for v3.0.0-prerelease2
-
- ///
- /// The current index of the cursor.
- ///
- int CursorIndex { get; }
-
- ///
- /// Whether the source is completely empty.
- ///
- bool IsEmpty { get; }
-
- ///
- /// Whether the source can be mutated.
- ///
- bool IsReadOnly { get; }
-}
diff --git a/src/RSML.Toolchain.Abstractions/Sources/SourceLocation.cs b/src/RSML.Toolchain.Abstractions/Sources/SourceLocation.cs
deleted file mode 100644
index cd948177..00000000
--- a/src/RSML.Toolchain.Abstractions/Sources/SourceLocation.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-///
-/// Specifies the location of an item in a or .
-///
-/// The 0-based index.
-/// The 0-based line number.
-/// The 0-based column number (the index relative to the start of the line).
-public readonly struct SourceLocation(int index, int line, int column) : IEquatable, IEquatable, IFormattable,
- IComparable, IComparable
-{
- ///
- /// An empty source location.
- ///
- public static readonly SourceLocation Empty = new(0, 0, 0);
-
- ///
- /// The 0-based line number, counting from the start of the source.
- ///
- public int Line => line;
-
- ///
- /// The 0-based column number, which is the index of the item relative to the start of the line it is in.
- ///
- public int Column => column;
-
- ///
- /// The absolute 0-based index of the item in the source.
- ///
- public int Index => index;
-
- ///
- /// Compares the index of the location to another index.
- ///
- /// The index to compare against.
- public int CompareTo(int other) => Index.CompareTo(other);
-
- ///
- public int CompareTo(SourceLocation other) => throw new NotImplementedException();
-
- ///
- public override bool Equals(
- [NotNullWhen(true)]
- object? obj
- ) => obj switch
- {
- SourceLocation location => Equals(location),
- int idx => Index == idx,
- _ => false
- };
-
- ///
- /// Checks if two s are equal to each other.
- ///
- /// The other .
- /// True if equals.
- public bool Equals(SourceLocation other) => Index == other.Index && Line == other.Line && Column == other.Column;
-
- ///
- /// Checks if two indexes are equal to each other.
- ///
- /// The other location's index.
- /// True if equals.
- public bool Equals(int other) => Index.Equals(other);
-
- ///
- public override int GetHashCode() => unchecked(HashCode.Combine(Index, Line, Column));
-
- ///
- /// Returns a generic string representation of the current instance.
- ///
- /// The string representation.
- public override string ToString() => $"SourceLocation(Index={Index}, Line={Line + 1}, Column={Column + 1})";
-
- ///
- /// Given a format, tries to return a string that uses said format as a basis for the representation.
- /// If it fails, it defaults to .
- ///
- /// The format. Available formats are: CTOR (constructor-like string) and JSON (struct as JSON).
- /// Unused. Don't bother assigning it anything.
- /// The string representation.
- public string ToString(string? format, IFormatProvider? formatProvider) =>
- format switch
- {
- "CTOR" or "I" or "INIT" or "NET" => $"new SourceLocation({Index}, {Line}, {Column})",
- "JSON" => $$"""{ "index": {{Index}}, "line": {{Line + 1}}, "column": {{Column + 1}} }""",
- _ => ToString()
- };
-
- ///
- /// Checks if two s are equal to each other.
- ///
- /// True if equals.
- public static bool operator ==(SourceLocation left, SourceLocation right) => left.Equals(right);
-
- ///
- /// Checks if two s are different from each other.
- ///
- /// True if different.
- public static bool operator !=(SourceLocation left, SourceLocation right) => !left.Equals(right);
-
- ///
- /// Checks if is strictly less than .
- ///
- public static bool operator <(SourceLocation left, SourceLocation right) => left.Index < right.Index;
-
- ///
- /// Checks if is strictly greater than .
- ///
- public static bool operator >(SourceLocation left, SourceLocation right) => left.Index > right.Index;
-
- ///
- /// Checks if is greather than or equal to .
- ///
- public static bool operator >=(SourceLocation left, SourceLocation right) => left.Index >= right.Index;
-
- ///
- /// Checks if is less than or equal to .
- ///
- public static bool operator <=(SourceLocation left, SourceLocation right) => left.Index <= right.Index;
-}
diff --git a/src/RSML.Toolchain.Abstractions/Sources/SourceSpan.cs b/src/RSML.Toolchain.Abstractions/Sources/SourceSpan.cs
deleted file mode 100644
index 663241d8..00000000
--- a/src/RSML.Toolchain.Abstractions/Sources/SourceSpan.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-///
-/// Represents a span taken from a source.
-///
-public readonly struct SourceSpan : IFormattable, IEquatable
-{
- ///
- /// An empty span.
- ///
- public readonly static SourceSpan Empty = new(new(0, 0, 0), new(0, 0, 0));
-
- ///
- /// The start of the span.
- ///
- public readonly SourceLocation Start { get; }
-
- ///
- /// The end of the span.
- ///
- public readonly SourceLocation End { get; }
-
- ///
- /// The length of the span.
- ///
- public readonly int Length => End.Index - Start.Index;
-
- ///
- /// The span is located in a single line.
- ///
- public bool IsSingleLine => Start.Line == End.Line;
-
- ///
- /// Initializes a new span given a starting and an end indexes.
- ///
- /// The start index.
- /// The end index.
- /// The starting index is greater or equal to the end index.
- public SourceSpan(SourceLocation start, SourceLocation end)
- {
- if (start.Index > end.Index)
- throw new ArgumentException("The starting index must be less than the end index.");
-
- Start = start;
- End = end;
- }
-
- ///
- public override bool Equals(
- [NotNullWhen(true)]
- object? obj
- ) => obj is SourceSpan span && Equals(span);
-
- ///
- /// Checks whether two s are equals.
- ///
- /// The span to check against
- /// True if equals
- public bool Equals(SourceSpan other) => Start.Equals(other.Start) && End.Equals(other.End);
-
- ///
- /// Checks whether two s are equals.
- ///
- /// True if equals
- public static bool operator ==(SourceSpan left, SourceSpan right) => left.Equals(right);
-
- ///
- /// Checks whether two s are different from each other.
- ///
- /// True if different
- public static bool operator !=(SourceSpan left, SourceSpan right) => left.Equals(right);
-
- ///
- /// Returns a generic string representation of the current instance.
- ///
- /// The string representation.
- public override string ToString() => $"SourceSpan(Start={Start}, End={End})";
-
- ///
- /// Given a format, tries to return a string that uses said format as a basis for the representation.
- /// If it fails, it defaults to .
- ///
- /// The format. Available formats are: CTOR (constructor-like string) and JSON (struct as JSON).
- /// Unused. Don't bother assigning it anything.
- /// The string representation.
- public string ToString(string? format, IFormatProvider? formatProvider) =>
- format switch
- {
- "CTOR" or "I" or "INIT" or "NET" => $"new SourceSpan({Start.ToString("ctor", null)}, {End.ToString("ctor", null)})",
- "JSON" => $$"""{ "start": {{Start.ToString("JSON", null)}}, "end": {{End.ToString("JSON", null)}} }""",
- _ => ToString()
- };
-
- ///
- public override int GetHashCode() => unchecked(HashCode.Combine(Start, End));
-}
diff --git a/src/RSML.Toolchain.Abstractions/ToolchainConfigurations.cs b/src/RSML.Toolchain.Abstractions/ToolchainConfigurations.cs
deleted file mode 100644
index 800ed019..00000000
--- a/src/RSML.Toolchain.Abstractions/ToolchainConfigurations.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-
-
-namespace OceanApocalypse.RSML.Toolchain.Abstractions;
-
-///
-/// Configuration options for a .
-///
-[Flags]
-public enum ToolchainConfigurations
-{
- ///
- /// Optimizes the toolchain pipeline by disabling extension processing.
- ///
- ///
- /// :::warning
- /// This completely disables extensions, but does not warn you if there are active extensions,
- /// meaning sometimes you might be wondering why your extension is not working when, in reality,
- /// you've enabled this configuration.
- /// :::
- ///
- /// :::tip
- /// This configuration is automatically enabled when no extensions are enabled.
- /// :::
- ///
- DisableExtensionProcessing = 1,
-
- ///
- /// Only allows OceanApocalypse extensions, leading to an error if any non-OAS extension is active.
- ///
- ///
- /// :::note
- /// When used alongside , the non-OAS extensions will be disabled, but
- /// no errors will be thrown.
- /// :::
- ///
- AllowOnlyOASExtensions = 2,
-
- ///
- /// Ignores all errors caused by broken or faulty extensions.
- ///
- IgnoreBrokenExtensions = 4,
-
- ///
- /// Ignores all errors caused by injecting already injected extensions.
- ///
- IgnoreDuplicatedExtensions = 8,
-
- ///
- /// Ignores all errors thrown during pipeline creation and pipeline execution.
- ///
- ///
- /// :::danger
- /// This option is only needed in beyond extremely rare occasions.
- /// It emulates RSML v1.x.x behavior.
- /// :::
- ///
- IgnoreAllExtensibilityErrors = IgnoreBrokenExtensions | IgnoreDuplicatedExtensions
-}
diff --git a/src/RSML.Toolchain.Analysis/RSML.Toolchain.Analysis.csproj b/src/RSML.Toolchain.Analysis/RSML.Toolchain.Analysis.csproj
index 8c831863..c2d72937 100644
--- a/src/RSML.Toolchain.Analysis/RSML.Toolchain.Analysis.csproj
+++ b/src/RSML.Toolchain.Analysis/RSML.Toolchain.Analysis.csproj
@@ -1,50 +1,14 @@
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Toolchain.Analysis
True
-
OceanApocalypse.RSML.Toolchain.Analysis
RSML Analyzers
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
diff --git a/src/RSML.Toolchain.Execution/RSML.Toolchain.Execution.csproj b/src/RSML.Toolchain.Execution/RSML.Toolchain.Execution.csproj
index 1b067aef..3c7f23f6 100644
--- a/src/RSML.Toolchain.Execution/RSML.Toolchain.Execution.csproj
+++ b/src/RSML.Toolchain.Execution/RSML.Toolchain.Execution.csproj
@@ -1,50 +1,14 @@
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Toolchain.Execution
True
-
OceanApocalypse.RSML.Toolchain.Execution
RSML Interpreters
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
diff --git a/src/RSML.Toolchain.Extensibility.Execution/Interpreter.cs b/src/RSML.Toolchain.Extensibility.Execution/Interpreter.cs
index 9b978db0..88fa9cae 100644
--- a/src/RSML.Toolchain.Extensibility.Execution/Interpreter.cs
+++ b/src/RSML.Toolchain.Extensibility.Execution/Interpreter.cs
@@ -1,6 +1,6 @@
using System;
-using OceanApocalypse.RSML.Toolchain.Abstractions;
+using OceanApocalypse.RSML.Abstractions.Toolchain;
namespace OceanApocalypse.RSML.Toolchain.Extensibility.Execution;
@@ -9,14 +9,15 @@ namespace OceanApocalypse.RSML.Toolchain.Extensibility.Execution;
///
public abstract class Interpreter : IToolchainComponent
{
- // todo: add necessary content to IInterpreter
+ // todo: make this implement IToolchainComponent correctly
+
private bool isDisposed;
///
public bool IsMutable { get; protected set; } = true;
///
- public ToolchainConfigurations Configuration { get; protected set; }
+ ToolchainConfiguration IToolchainComponent.Configuration => throw new NotImplementedException();
///
public void Dispose()
@@ -29,7 +30,7 @@ public void Dispose()
public void Freeze() => IsMutable = false;
///
- public void Inject(ToolchainConfigurations configuration) => throw new NotImplementedException();
+ public void Inject(ToolchainConfiguration configuration) => throw new NotImplementedException();
///
/// Disposes of both managed and unmanaged resources.
diff --git a/src/RSML.Toolchain.Extensibility.Execution/RSML.Toolchain.Extensibility.Execution.csproj b/src/RSML.Toolchain.Extensibility.Execution/RSML.Toolchain.Extensibility.Execution.csproj
index 6cef39b5..1fc8abf6 100644
--- a/src/RSML.Toolchain.Extensibility.Execution/RSML.Toolchain.Extensibility.Execution.csproj
+++ b/src/RSML.Toolchain.Extensibility.Execution/RSML.Toolchain.Extensibility.Execution.csproj
@@ -1,54 +1,18 @@
-
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Toolchain.Extensibility.Execution
True
-
OceanApocalypse.RSML.Toolchain.Extensibility.Execution
RSML Extensible Interpreters
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
+
-
+
+
diff --git a/src/RSML.Toolchain.Extensibility.Lexing/RSML.Toolchain.Extensibility.Lexing.csproj b/src/RSML.Toolchain.Extensibility.Lexing/RSML.Toolchain.Extensibility.Lexing.csproj
index 707d5b71..c656e97a 100644
--- a/src/RSML.Toolchain.Extensibility.Lexing/RSML.Toolchain.Extensibility.Lexing.csproj
+++ b/src/RSML.Toolchain.Extensibility.Lexing/RSML.Toolchain.Extensibility.Lexing.csproj
@@ -1,50 +1,14 @@
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Toolchain.Extensibility.Lexing
True
-
OceanApocalypse.RSML.Toolchain.Extensibility.Lexing
RSML Extensible Lexers
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
diff --git a/src/RSML.Toolchain.Extensibility.Parsing/RSML.Toolchain.Extensibility.Parsing.csproj b/src/RSML.Toolchain.Extensibility.Parsing/RSML.Toolchain.Extensibility.Parsing.csproj
index ca28eb9d..6261a088 100644
--- a/src/RSML.Toolchain.Extensibility.Parsing/RSML.Toolchain.Extensibility.Parsing.csproj
+++ b/src/RSML.Toolchain.Extensibility.Parsing/RSML.Toolchain.Extensibility.Parsing.csproj
@@ -1,50 +1,13 @@
-
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML.Toolchain.Extensibility.Parsing
True
-
OceanApocalypse.RSML.Toolchain.Extensibility.Parsing
RSML Extensible Parsers
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
diff --git a/src/RSML.Toolchain.Sources/GlobalSuppressions.cs b/src/RSML.Toolchain.Sources/GlobalSuppressions.cs
deleted file mode 100644
index d9f1eafd..00000000
--- a/src/RSML.Toolchain.Sources/GlobalSuppressions.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-// This file is used by Code Analysis to maintain SuppressMessage
-// attributes that are applied to this project.
-// Project-level suppressions either have no target or are given
-// a specific target and scoped to a namespace, type, member, etc.
-
-using System.Diagnostics.CodeAnalysis;
-
-[assembly: SuppressMessage("Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = "Refers to a single location.", Scope = "member", Target = "~P:OceanApocalypse.RSML.Toolchain.Sources.ReadOnlyStringBuffer.Item(OceanApocalypse.RSML.Toolchain.Abstractions.Sources.SourceLocation)")]
-[assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Not an unnecessary suppression.", Scope = "member", Target = "~M:OceanApocalypse.RSML.Toolchain.Sources.ReadOnlyStringBuffer.#ctor(System.Byte*,System.Int32,System.Text.Encoding)")]
-[assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Not an unnecessary suppression.", Scope = "member", Target = "~M:OceanApocalypse.RSML.Toolchain.Sources.ReadOnlyStringBuffer.BuildCache")]
diff --git a/src/RSML.Toolchain.Sources/RSML.Toolchain.Sources.csproj b/src/RSML.Toolchain.Sources/RSML.Toolchain.Sources.csproj
deleted file mode 100644
index 93ce1f82..00000000
--- a/src/RSML.Toolchain.Sources/RSML.Toolchain.Sources.csproj
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
- net10.0;net8.0
- true
- true
-
- Library
- true
-
- OceanApocalypse.RSML.Toolchain.Sources
-
-
-
- True
-
- OceanApocalypse.RSML.Toolchain.Sources
- RSML Sources
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
-
- full
-
-
-
- none
- true
-
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
-
-
-
-
diff --git a/src/RSML.Toolchain.Sources/ReadOnlyStringBuffer.cs b/src/RSML.Toolchain.Sources/ReadOnlyStringBuffer.cs
deleted file mode 100644
index 6e4eb7d0..00000000
--- a/src/RSML.Toolchain.Sources/ReadOnlyStringBuffer.cs
+++ /dev/null
@@ -1,781 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-using OceanApocalypse.RSML.Toolchain.Abstractions;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Cache;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Panic;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-
-
-namespace OceanApocalypse.RSML.Toolchain.Sources;
-
-///
-/// A read-only buffer backed by a string. All operations opt for performance
-/// primarily via the internal use of over string allocations
-/// and also via caching.
-///
-public sealed class ReadOnlyStringBuffer : IBuffer, ISupportsCache
-{
- private const int AverageCharactersPerLine = 40;
- private const int ExtraCharacterCapacity = 64;
- private bool isDisposed;
-
- private readonly List lineStarts = [];
-
- private readonly List precededByCrLf = [];
-
- private readonly string data;
-
- ///
- public bool CacheExists { get; private set; }
-
- ///
- public bool IsEmpty => Length == 0;
-
- ///
- /// Always returns true, as only
- /// supports read-only content (hence the name).
- ///
- ///
- public bool IsReadOnly => true;
-
- ///
- public int Length => data.Length;
-
- ///
- /// automatically builds cache if
- /// no cached data exists. No calls
- /// are necessary.
- ///
- ///
- public int LineCount => RawLineCount - 1; // ignore the "fake" EOF line (convention)
-
- private int RawLineCount
- {
- get
- {
- ComputeLineStarts();
-
- return lineStarts.Count;
- }
- }
-
- ///
- public char this[int index] => data[index];
-
- ///
- public char this[SourceLocation location] => this[location.Index];
-
- ///
- public ReadOnlySpan this[SourceSpan span] => data.AsSpan().Slice(span.Start.Index, span.Length);
-
- ///
- /// Initializes a new
- /// with a string.
- ///
- /// The string that the buffer will wrap.
- public ReadOnlyStringBuffer(string content) => data = content;
-
- ///
- /// Initializes a new
- /// by allocating a string from a .
- ///
- /// The span pointing to the string's data.
- public ReadOnlyStringBuffer(ReadOnlySpan content) => data = content.ToString();
-
- ///
- /// Initializes a new
- /// with an array of characters.
- ///
- /// The array of characters to use for the buffer.
- public ReadOnlyStringBuffer(char[] content) => data = new(content);
-
- ///
- /// Initializes a new
- /// with an array of bytes and the encoding to use when decoding them.
- ///
- /// The array of bytes to use for the buffer.
- ///
- /// The encoding to use when decoding .
- /// Use null for the encoding.
- ///
- public ReadOnlyStringBuffer(byte[] content, Encoding? encoding = null) => data = encoding?.GetString(content) ?? Encoding.Default.GetString(content);
-
- ///
- /// Initializes a new
- /// with a pointer referencing an array of bytes and the encoding
- /// to use when decoding them.
- ///
- /// The pointer referecing the array of bytes to use for the buffer.
- /// The amount of bytes in the array referenced by .
- ///
- /// The encoding to use when decoding .
- /// Use null for the encoding.
- ///
- /// This method is not CLS-compliant due to the unsafe context and the use of pointers.
- [CLSCompliant(false)]
- public unsafe ReadOnlyStringBuffer(byte* contentPtr, int byteCount, Encoding? encoding = null) =>
- data = (encoding ?? Encoding.Default).GetString(contentPtr, byteCount);
-
- ///
- public void BuildCache() => ComputeLineStarts();
-
- ///
- public void BuildCache(bool forceRebuild) => ComputeLineStarts(forceRebuild);
-
- ///
- /// :::info[EOF Conventions]
- /// This method allows the EOF index as in-range. The convention is as follows:
- /// - If the index is EOF (), then the output is always 0 and is always false.
- /// - If the index is the last ( - 1), then the output is always 0.
- /// :::
- ///
- /// :::info[Value of 'isCrLf' parameter]
- /// is only true if all the following conditions are true:
- /// - The next line start counting from is preceded by a CRLF sequence.
- /// - does not point to the LF in the CRLF sequence.
- /// - does not point to EOF.
- /// :::
- ///
- ///
- public int CountUntilEndOfLine(int index, out bool isCrLf)
- {
- isCrLf = false;
-
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index, true);
-
- if (index == Length)
- return 0; // consumed the entire buffer
-
- ComputeLineStarts();
-
- int lineSep = GetNextLineStartPosition(index, out _);
- isCrLf = precededByCrLf.Contains(lineSep) && data[index] is not '\n'; // to us, CRLF is only when we're not standing on the LF
-
- if (isCrLf)
- lineSep--; // skip the extra line separator in the CRLF sequence
-
- if (!(IsLastLine(index) && !data[^1].IsNewline())) // if we're not on the last line and it doesn't end with a newline then
- {
- lineSep--;
- }
-
- return lineSep - index;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method allows the EOF index as in-range.
- /// If the index is EOF (), then the output is always 0.
- /// :::
- ///
- /// :::tip[About the return value]
- /// The return value, when summed with , becomes the index of the first character that
- /// is not whitespace, counting from .
- /// The only exception is if the buffer has been consumed (you pass EOF index or there's no more characters that are
- /// not whitespace), meaning the return value, when summed with is the value of
- /// , which is also the EOF index.
- /// :::
- ///
- ///
- public int CountUntilNotWhitespace(int index)
- {
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index, true);
-
- if (index == Length)
- return 0; // consumed the entire buffer
-
- var span = data.AsSpan(index);
- int count = 0;
-
- while (count < span.Length && Char.IsWhiteSpace(span[count]))
- count++;
-
- return count;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method allows the EOF index as in-range.
- /// If the index is EOF (), then the output is always 0.
- /// :::
- ///
- /// :::tip[About the return value]
- /// The return value, when summed with , becomes the index of the first character that
- /// is whitespace, counting from .
- /// The only exception is if the buffer has been consumed (you pass EOF index or there's no more characters that are
- /// whitespace), meaning the return value, when summed with is the value of
- /// , which is also the EOF index.
- /// :::
- ///
- ///
- public int CountUntilWhitespace(int index)
- {
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index, true);
-
- if (index == Length)
- return 0; // consumed the entire buffer
-
- var span = data.AsSpan(index);
- int count = 0;
-
- while (count < span.Length && !Char.IsWhiteSpace(span[count]))
- count++;
-
- return count;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method allows the EOF index as in-range.
- /// If the index is EOF (), then the output is always 0.
- /// :::
- ///
- /// :::tip[About the return value]
- /// The return value, when summed with , becomes the index of the first character that
- /// fails to verify the , counting from .
- /// The only exception is if the buffer has been consumed (you pass EOF index or there's no more characters that fail to verify
- /// the ), meaning the return value, when summed with is the value of
- /// , which is also the EOF index.
- /// :::
- ///
- ///
- public int CountWhile(Func predicate, int index)
- {
- if (predicate is null)
- throw new ArgumentNullException(nameof(predicate), "The object is null.");
-
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index, true);
-
- if (index == Length)
- return 0; // consumed the entire buffer
-
- var span = data.AsSpan(index);
- int count = 0;
-
- while (count < span.Length && predicate(count, span[count]))
- count++;
-
- return count;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// EOF is considered a 0-character sequence in line N, where N is .
- /// Keep in mind N does not point to an actual line (it's just a convention), as line numbers are 0-based
- /// (meaning the actual last line is located at N - 1).
- /// :::
- ///
- ///
- public int GetLengthOfLine(int lineNumber)
- {
- ThrowIfEmpty();
- ComputeLineStarts();
- ThrowIfLineNumberOutOfRange(lineNumber);
-
- if (lineNumber == RawLineCount - 1)
- return 0; // EOF means the line is empty
-
- int start = lineStarts[lineNumber];
- int end = lineStarts[lineNumber + 1];
-
- if (precededByCrLf.Contains(end))
- end--; // skip the extra line separator in the CRLF sequence
-
- if (!(lineNumber + 2 == RawLineCount && !data[^1].IsNewline()))
- {
- // if we're not on the last line and it doesn't end with newline then
- end--; // skip one more line separator
- }
-
- return end - start;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// EOF is considered a 0-character sequence in line N, where N is .
- /// Keep in mind N does not point to an actual line (it's just a convention), as line numbers are 0-based
- /// (meaning the actual last line is located at N - 1).
- /// :::
- ///
- ///
- public int GetLengthOfLineFromIndex(int index) => GetLengthOfLine(GetLineNumberFromIndex(index));
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// :::
- ///
- ///
- public int GetLineNumberFromIndex(int index)
- {
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index, true);
- ComputeLineStarts();
-
- int lineSepIndex = GetPreviousOrCurrentLineStartPositionInLineStartList(index);
- return lineSepIndex;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// EOF is considered a 0-character sequence in line N, where N is .
- /// Keep in mind N does not point to an actual line (it's just a convention), as line numbers are 0-based
- /// (meaning the actual last line is located at N - 1).
- /// :::
- ///
- ///
- public ReadOnlySpan GetLine(int lineNumber)
- {
- ThrowIfEmpty();
- ComputeLineStarts();
- ThrowIfLineNumberOutOfRange(lineNumber);
-
- if (lineNumber + 1 == RawLineCount)
- return String.Empty; // EOF means the line is empty
-
- int start = lineStarts[lineNumber];
- int length = GetLengthOfLine(lineNumber);
-
- return data.AsSpan(start, length);
- }
-
- ///
- public ReadOnlySpan GetLineFromIndex(int index) => GetLine(GetLineNumberFromIndex(index));
-
- ///
- /// :::warning[EOF Conventions]
- /// Unlike with other methods, this one
- /// does not follow EOF conventions and, because of that, does not accept the
- /// EOF index (index at ), because it is not
- /// considered a location.
- /// :::
- ///
- ///
- public SourceLocation GetSourceLocation(int index)
- {
- ThrowIfEmpty();
- index = NormalizeIndex(index);
- ThrowIfOutOfRange(index);
-
- if (index == 0) // best "best" case = triple zero
- return SourceLocation.Empty;
-
- ComputeLineStarts();
- int lineNumber = GetLineNumberFromIndex(index);
-
- return new(index, lineNumber, index - lineStarts[lineNumber]);
- }
-
- ///
- public SourceSpan GetSourceSpan(int startIndex, int endIndex)
- {
- var start = GetSourceLocation(startIndex);
- var end = GetSourceLocation(endIndex);
-
- return new(start, end);
- }
-
- ///
- /// :::warning[EOF Conventions]
- /// Unlike with other methods, this one
- /// does not follow EOF conventions and, because of that, does not accept the
- /// EOF index (index at ), because it is not
- /// considered part of any slice.
- /// :::
- ///
- ///
- public ReadOnlySpan Slice(int start, int length)
- {
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), "The slice length must be positive.");
-
- start = NormalizeIndex(start);
- ThrowIfOutOfRange(start, true, nameof(start));
-
- return data.AsSpan(start, length);
- }
-
- ///
- /// :::warning[EOF Conventions]
- /// Unlike with other methods, this one
- /// does not follow EOF conventions and, because of that, does not accept the
- /// EOF index (index at ), because it is not
- /// considered part of any slice.
- /// :::
- ///
- ///
- public bool TrySlice(int start, Span slice) => data.AsSpan(NormalizeIndex(start), slice.Length).TryCopyTo(slice);
-
- ///
- /// :::warning[EOF Conventions]
- /// Unlike with other methods, this one
- /// does not follow EOF conventions and, because of that, does not accept the
- /// EOF index (index at ), because it is not
- /// considered part of any slice.
- /// :::
- ///
- ///
- public bool TrySlice(SourceSpan sourceSpan, Span slice) => data.AsSpan(sourceSpan.Start.Index, sourceSpan.Length).TryCopyTo(slice);
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows the EOF convention where the EOF character
- /// is 0 ('\0') and the return value is false, due to EOF
- /// not being an actual buffer location.
- /// :::
- ///
- ///
- public bool TryGetChar(int index, out char item)
- {
- item = '\0'; // default
-
- if (IsEmpty)
- return false;
-
- index = NormalizeIndex(index);
-
- if (IsOutOfRange(index))
- return false;
-
- item = data[index];
-
- return true;
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows the EOF convention where the EOF character
- /// is 0 ('\0') and the return value is false, due to EOF
- /// not being an actual buffer location.
- /// :::
- ///
- ///
- public bool TryGetChar(SourceLocation location, out char item) => TryGetChar(location.Index, out item);
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// EOF is considered a 0-character sequence in line N, where N is .
- /// Keep in mind N does not point to an actual line (it's just a convention), as line numbers are 0-based
- /// (meaning the actual last line is located at N - 1).
- /// :::
- ///
- ///
- public bool TryGetLine(int lineNumber, Span destination)
- {
- ComputeLineStarts();
-
- if (IsEmpty || lineNumber < 0 || lineNumber >= RawLineCount)
- return false;
-
- if (lineNumber + 1 == RawLineCount)
- return true; // EOF means the line is empty (and destination is by default empty)
-
- int start = lineStarts[lineNumber];
- int length = GetLengthOfLine(lineNumber);
-
- return data.AsSpan(start, length).TryCopyTo(destination);
- }
-
- ///
- /// :::info[EOF Conventions]
- /// This method follows EOF conventions.
- /// EOF is considered a 0-character sequence in line N, where N is .
- /// Keep in mind N does not point to an actual line (it's just a convention), as line numbers are 0-based
- /// (meaning the actual last line is located at N - 1). If is EOF, the
- /// line will also be EOF.
- /// :::
- ///
- ///
- public bool TryGetLineFromIndex(int index, Span destination)
- {
- index = NormalizeIndex(index);
-
- if (IsEmpty || IsOutOfRange(index, followEofConvention: true)) // avoids panic from GetLineNumberFromIndex
- return false;
-
- var lineNumber = GetLineNumberFromIndex(index);
- return TryGetLine(lineNumber, destination);
- }
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- public override bool Equals(
- [NotNullWhen(true)]
- object? obj
- ) => obj switch
- {
- string str => Equals(str),
- char[] charArray => Equals(charArray),
- IBuffer buffer => Equals(buffer),
- ReadOnlyMemory readOnlyMemory => Equals(readOnlyMemory),
- null => false,
- _ => false
- };
-
- ///
- /// Checks if an array of characters is equal to the current instance.
- ///
- /// The array.
- /// True if equals.
- public bool Equals(char[]? other) => other is not null && data.Equals(other.AsSpan(), StringComparison.Ordinal);
-
- ///
- /// Checks if another read-only buffer is equal to the current instance.
- ///
- /// The other read-only buffer.
- /// True if equals.
- public bool Equals(IBuffer? other) => other is not null && data.Equals(other.ToString(), StringComparison.Ordinal);
-
- ///
- /// Checks if a read-only contiguous region of memory is equal to the current instance.
- ///
- /// The region of memory.
- /// True if equals.
- public bool Equals(ReadOnlyMemory other) => Length == other.Length && data.SequenceEqual(other.Span);
-
- ///
- /// Checks if a read-only contiguous region of memory is equal to the current instance.
- ///
- /// The region of memory.
- /// True if equals.
- public bool Equals(ReadOnlySpan other) => Length == other.Length && data.SequenceEqual(other);
-
- ///
- /// Checks if a string is equal to the current instance.
- ///
- /// The string.
- /// True if equals.
- public bool Equals(string? other) => other is not null && Length == other.Length && data.Equals(other, StringComparison.Ordinal);
-
- ///
- public override int GetHashCode() => unchecked(HashCode.Combine(data, lineStarts, precededByCrLf));
-
- ///
- /// Returns the buffer's content as a .
- ///
- /// The buffer's content.
- public override string ToString() => data;
-
- ///
- /// Checks if two read-only string buffers are equals.
- ///
- /// True if equals.
- public static bool operator ==(ReadOnlyStringBuffer left, ReadOnlyStringBuffer right) =>
- EqualityComparer.Default.Equals(left, right);
-
- ///
- /// Checks if two read-only string buffers are different.
- ///
- /// True if different.
- public static bool operator !=(ReadOnlyStringBuffer left, ReadOnlyStringBuffer right) => !(left == right);
-
- ///
- /// Disposes of both managed and unmanaged resources.
- ///
- /// When set to false, disposes of unmanaged resources only.
- private void Dispose(bool disposing)
- {
- if (isDisposed)
- return;
-
- if (disposing)
- {
- lineStarts.Clear();
- precededByCrLf.Clear();
- CacheExists = false;
- }
-
- isDisposed = true;
- }
-
- private void ComputeLineStarts(bool forceCache = false)
- {
- if (CacheExists && !forceCache)
- return;
-
- var span = data.AsSpan();
-
- lineStarts.Clear();
- precededByCrLf.Clear();
-
- lineStarts.Capacity = Math.Max(lineStarts.Capacity, span.Length / AverageCharactersPerLine + ExtraCharacterCapacity); // just a rough guess
- precededByCrLf.Capacity = Math.Max(precededByCrLf.Capacity, span.Length / AverageCharactersPerLine + (OperatingSystem.IsWindows() ? ExtraCharacterCapacity : 0));
-
- /* the following line ensures that if the last line:
- * ends with CR, LF, U2028 or U2029
- * ends with CRLF
- * does not end with any of the above
- *
- * it will be counted as a line no matter the outcome of the previous condition
- * this makes it more obvious from a human side like "bruv my string is abc\ndef two lines right"
- * it looks normal that there are 2 lines but someone will go "acshua'y, that's erm 1 line :skull:"
- * not with RSML's official buffers nah bro
- */
- int lastIndex = span.EndsWith("\r\n") ? span.Length - 2 : span.Length - 1;
- int i = 0;
-
- lineStarts.Add(0); // 0 is by convention the start of line (and also the start of the buffer)
-
- while (i < lastIndex)
- {
- if (!span[i].IsNewline())
- {
- i++;
- continue;
- }
-
- bool isCrLf = i < lastIndex && span[i] == '\r' && span[i + 1] == '\n';
- int nextStart = i + (isCrLf ? 2 : 1);
- lineStarts.Add(nextStart);
-
- if (isCrLf)
- precededByCrLf.Add(nextStart);
-
- i = nextStart;
- }
-
- lineStarts.Add(Length); // add the EOF as the start of a line
-
- CacheExists = true;
- }
-
- /// The insertion point of the next line start.
- /// The index, in where the line start is.
- /// The line start index, in .
- private int GetNextLineStartFromInsertionPoint(int insertionPoint, out int lsListIndex)
- {
- if (insertionPoint >= 0)
- {
- // 1st Case: the used index (might have been index + 1 based on the method that called this)
- // points to an actual line start
- lsListIndex = insertionPoint;
- return lineStarts[insertionPoint];
- }
-
- int nextIndex = ~insertionPoint;
-
- if (nextIndex == lineStarts.Count)
- {
- // 2nd Case: the next line start is outside of the buffer (EOF convention)
- lsListIndex = nextIndex;
- return Length;
- }
-
- lsListIndex = nextIndex;
-
- return lineStarts[nextIndex]; // 3rd Case: we found the next line start
- }
-
- private int GetNextLineStartPosition(int index, out int lsListIndex) =>
- // we use index + 1 to skip to the next line start if we're already standing on one :)
- GetNextLineStartFromInsertionPoint(lineStarts.BinarySearch(index + 1), out lsListIndex);
-
- /// The insertion point of the next line start.
- /// The line start index in , in .
- private int GetPreviousLineStartInLineStartListFromInsertionPoint(int insertionPoint)
- {
- if (insertionPoint >= 0)
- {
- // 1st Case: the used index (might have been index + 1 based on the method that called this)
- // points to an actual line start
- return insertionPoint;
- }
-
- int previousIndex = ~insertionPoint;
-
- if (previousIndex == lineStarts.Count)
- {
- // 2nd Case: the next line start is outside of the buffer (EOF convention)
- // however we're gonna decrement one because otherwise all indexes from the last line that are after
- // start of said line suddenly become part of the EOF line
- // however this might also mean we're at last character
- return previousIndex;
- }
-
- return previousIndex == 0 ? 0 : previousIndex - 1; // 3rd Case: we found the previous line start
- }
-
- private int GetPreviousOrCurrentLineStartPositionInLineStartList(int index)
- {
- if (index > 0)
- {
- // when the index is greater than 0, we can safely get the previous line start without getting a big shitty error
- // we use 0 as the start following standard conventions
- return GetPreviousLineStartInLineStartListFromInsertionPoint(lineStarts.BinarySearch(index));
- }
-
- return 0;
- }
-
- private bool IsLastLine(int index) => RawLineCount == 1 || index >= lineStarts[^2] && index < lineStarts[^1];
-
- ///
- /// If is set to 'false':
- /// True if index is greater than or equal to the length.
- /// This prevents throwing
- /// or .
- /// If is set to 'true':
- /// True if index is greater than the length.
- /// This does NOT prevent throwing
- /// or .
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool IsOutOfRange(int index, bool followEofConvention = false) =>
- index < 0 || index > Length || (!followEofConvention && index == Length);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int NormalizeIndex(int index) => index < 0 ? index + Length : index;
-
- private void ThrowIfLineNumberOutOfRange(int lineNumber, string? paramName = null)
- {
- if (lineNumber < 0 || lineNumber >= RawLineCount)
- {
- throw new ArgumentOutOfRangeException(
- paramName ?? nameof(lineNumber),
- "The line number is negative or greather than the buffer's line count, meaning it doesn't point to either any valid character or EOF."
- );
- }
- }
-
- private void ThrowIfOutOfRange(int index, bool followEofConvention = false, string? paramName = null)
- {
- if (IsOutOfRange(index, followEofConvention))
- {
- throw new ArgumentOutOfRangeException(
- paramName ?? nameof(index),
- followEofConvention
- ? "The index is negative or greather than the buffer's length, meaning it doesn't point to either any valid character or EOF."
- : "The index is negative, greater than or equal to the buffer's length, meaning it doesn't point to any valid character. EOF is not allowed."
- );
- }
- }
-
- private void ThrowIfEmpty()
- {
- if (IsEmpty)
- throw new BufferException("panic: The buffer is empty and, therefore, all indexes are out of range.");
- }
-}
diff --git a/src/RSML/RSML.csproj b/src/RSML/RSML.csproj
index c2ad29d7..a55037c4 100644
--- a/src/RSML/RSML.csproj
+++ b/src/RSML/RSML.csproj
@@ -1,13 +1,7 @@
-
- net10.0;net8.0
true
- true
-
Library
- true
-
OceanApocalypse.RSML
@@ -16,40 +10,5 @@
OceanApocalypse.RSML
Red Sea Modern Language (RSML)
- The only DSL that dynamically interprets different logic paths based on an host's OS and CPU architecture.
-
- https://OceanApocalypse.org/rsml-docs/
- icon.png
- https://github.com/OceanApocalypse/RedSeaModernLanguage/
-
- DSL;language;logic;logic-path;system;host;rsml;rsea;decision;oss
- README.md
-
-
-
- full
- True
-
-
-
- none
- true
-
-
-
- True
- \
- false
-
-
- True
- \
- false
-
-
-
-
-
-
diff --git a/tests/RSML.InternalTests/RSML.InternalTests.csproj b/tests/RSML.InternalTests/RSML.InternalTests.csproj
index 61e87b0f..728ce7f0 100644
--- a/tests/RSML.InternalTests/RSML.InternalTests.csproj
+++ b/tests/RSML.InternalTests/RSML.InternalTests.csproj
@@ -2,13 +2,10 @@
net10.0
- enable
Exe
true
OceanApocalypse.RSML.Tests.Internal
- latest
true
- AnyCPU;ARM64;x64;x86;ARM32
diff --git a/tests/RSML.NativeTests/RSML.NativeTests.csproj b/tests/RSML.NativeTests/RSML.NativeTests.csproj
index 468dc092..32e951a8 100644
--- a/tests/RSML.NativeTests/RSML.NativeTests.csproj
+++ b/tests/RSML.NativeTests/RSML.NativeTests.csproj
@@ -2,14 +2,10 @@
net10.0
- enable
Exe
- enable
- OceanApocalypse.RSML.Tests.Native
true
- latest
+ OceanApocalypse.RSML.Tests.Native
true
- AnyCPU;ARM64;x64;x86;ARM32
diff --git a/tests/RSML.Tests/RSML.Tests.csproj b/tests/RSML.Tests/RSML.Tests.csproj
index 725e13b9..2cdddebc 100644
--- a/tests/RSML.Tests/RSML.Tests.csproj
+++ b/tests/RSML.Tests/RSML.Tests.csproj
@@ -2,13 +2,10 @@
net10.0
- enable
Exe
true
OceanApocalypse.RSML.Tests
- latest
true
- AnyCPU;ARM64;x64;x86;ARM32
@@ -25,8 +22,8 @@
-
-
+
+
diff --git a/tests/RSML.Tests/Sources/ReadOnlyStringBufferTests.cs b/tests/RSML.Tests/Sources/ReadOnlyStringBufferTests.cs
deleted file mode 100644
index 3dbabe82..00000000
--- a/tests/RSML.Tests/Sources/ReadOnlyStringBufferTests.cs
+++ /dev/null
@@ -1,1306 +0,0 @@
-using System;
-using System.Text;
-
-using OceanApocalypse.RSML.Toolchain.Abstractions.Panic;
-using OceanApocalypse.RSML.Toolchain.Abstractions.Sources;
-using OceanApocalypse.RSML.Toolchain.Sources;
-
-namespace OceanApocalypse.RSML.Tests.Sources;
-
-///
-/// Tests for the official-provided class.
-///
-public class ReadOnlyStringBufferTests
-{
- private const string TestString01 = "Hey\r\nThis\rIs\u2029A Test \n Method\r\n\r\n.\u2028";
- private const string TestString02 = "Hey\r\nThis\rIs\u2029A Test \n Method\r\n\r\n.";
- private const string TestString03 = "This string has a lotofwhitespace charact\u2029\u2028ers out of\r\nnowhere !! ";
- private const string TestString04 = "this STRING MIXES a LOT\n of \u2029dIffereNT cas1ngs RAND0mLy!?";
- private const string TestString05 = "!rrrrrrrrr";
- private const string TestString06 = "r!!!!!!!!!";
- private const string TestString07 = "This tests the\nLine\rCount property\r\n";
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 3, true)] // H in "Hey"
- [InlineData(TestString01, 3, 0, true)] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, 0, false)] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, 4, false)] // T in "This"
- [InlineData(TestString01, 6, 3, false)] // h in "This"
- [InlineData(TestString01, 13, 7, false)] // A in "A Test"
- [InlineData(TestString01, 15, 5, false)] // T in "Test"
- [InlineData(TestString01, 22, 6, true)] // M in "Method"
- [InlineData(TestString01, 28, 0, true)] // First CR in "\r\n\r\n."
- [InlineData(TestString01, 29, 0, false)] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, 0, true)] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, 31, 0, false)] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, 1, false)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString01, 33, 0, false)] // U2028 in ".\u2028"
- [InlineData(TestString01, 34, 0, false)] // End of file
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, 3, true)] // H in "Hey"
- [InlineData(TestString02, 3, 0, true)] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, 0, false)] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, 4, false)] // T in "This"
- [InlineData(TestString02, 6, 3, false)] // h in "This"
- [InlineData(TestString02, 13, 7, false)] // A in "A Test"
- [InlineData(TestString02, 15, 5, false)] // T in "Test"
- [InlineData(TestString02, 22, 6, true)] // M in "Method"
- [InlineData(TestString02, 28, 0, true)] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, 0, false)] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, 0, true)] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, 0, false)] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, 1, false)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString02, 33, 0, false)] // End of file
- #endregion
- #region String with a lot of whitespace
- [InlineData(TestString03, 0, 50, false)]
- [InlineData(TestString03, 7, 43, false)]
- [InlineData(TestString03, 19, 31, false)]
- [InlineData(TestString03, 32, 18, false)]
- [InlineData(TestString03, 49, 1, false)]
- [InlineData(TestString03, 50, 0, false)]
- [InlineData(TestString03, 51, 0, false)]
- [InlineData(TestString03, 52, 10, true)]
- [InlineData(TestString03, 59, 3, true)]
- [InlineData(TestString03, 61, 1, true)]
- [InlineData(TestString03, 62, 0, true)]
- #endregion
- #region String with a single line
- [InlineData(TestString05, 0, 10, false)]
- [InlineData(TestString05, 9, 1, false)]
- [InlineData(TestString05, 10, 0, false)]
- [InlineData(TestString06, 0, 10, false)]
- [InlineData(TestString06, 3, 7, false)]
- [InlineData(TestString06, 6, 4, false)]
- [InlineData(TestString06, 8, 2, false)]
- [InlineData(TestString06, 9, 1, false)]
- [InlineData(TestString06, 10, 0, false)]
- #endregion
- public void CountUntilEndOfLine(string data, int index, int expectedCount, bool expectedCrLf)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedCount, buffer.CountUntilEndOfLine(index, out bool actualCrLf));
- Assert.Equal(expectedCrLf, actualCrLf);
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(2)]
- [InlineData(14)]
- [InlineData(99)]
- [InlineData(-10)]
- [InlineData(-1)]
- #endregion
- public void CountUntilEndOfLine_FailsIfEmpty(int index)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- bool isCrLf = true;
- Assert.Throws(() => buffer.CountUntilEndOfLine(index, out isCrLf));
- Assert.False(isCrLf);
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- [InlineData(TestString02, -36)]
- [InlineData(TestString05, -11)]
- #endregion
- public void CountUntilEndOfLine_FailsIfOutOfRange(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- bool isCrLf = true;
- Assert.Throws(() => buffer.CountUntilEndOfLine(index, out isCrLf));
- Assert.False(isCrLf);
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString03, 0, 0)] // T in "This"
- [InlineData(TestString03, 2, 0)] // i in "This"
- [InlineData(TestString03, 3, 0)] // s in "This"
- [InlineData(TestString03, 4, 2)] // whitespace after "This"
- [InlineData(TestString03, 5, 1)] // whitespace after "This"
- [InlineData(TestString03, 6, 0)] // s in "string"
- [InlineData(TestString03, 9, 0)] // i in "string"
- [InlineData(TestString03, 11, 0)] // g in "string"
- [InlineData(TestString03, 12, 5)] // whitespace after "string"
- [InlineData(TestString03, 14, 3)] // whitespace after "string"
- [InlineData(TestString03, 15, 2)] // whitespace after "string"
- [InlineData(TestString03, 16, 1)] // whitespace after "string"
- [InlineData(TestString03, 20, 1)] // whitespace after "has"
- [InlineData(TestString03, 21, 0)] // "a" after "has"
- [InlineData(TestString03, 28, 0)] // w in "lotofwhitespace"
- [InlineData(TestString03, 38, 5)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 39, 4)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 42, 1)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 46, 0)] // r in "charact\u2029\u2028"
- [InlineData(TestString03, 50, 2)] // U2029 in "charact\u2029\u2028"
- [InlineData(TestString03, 51, 1)] // U2028 in "charact\u2029\u2028"
- [InlineData(TestString03, 52, 0)] // e in "\u2029\u2028ers"
- [InlineData(TestString03, 59, 1)] // whitespace after "out"
- [InlineData(TestString03, 62, 2)] // CR in "\r\nnowhere"
- [InlineData(TestString03, 63, 1)] // LF in "\r\nnowhere"
- [InlineData(TestString03, 65, 0)] // o in "nowhere"
- [InlineData(TestString03, 69, 0)] // r in "nowhere"
- [InlineData(TestString03, 70, 0)] // last e in "nowhere"
- [InlineData(TestString03, 71, 1)] // whitespace after "nowhere"
- [InlineData(TestString03, 73, 0)] // second exclamation mark
- [InlineData(TestString03, 74, 1)] // whitespace after exclamation marks
- [InlineData(TestString03, 75, 0)] // End of file
- #endregion
- public void CountUntilNotWhitespace(string data, int index, int expectedCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedCount, buffer.CountUntilNotWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(2)]
- [InlineData(14)]
- [InlineData(99)]
- [InlineData(-10)]
- #endregion
- public void CountUntilNotWhitespace_FailsIfEmpty(int index)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.CountUntilNotWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void CountUntilNotWhitespace_FailsIfOutOfRange(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.CountUntilNotWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString03, 0, 4)] // T in "This"
- [InlineData(TestString03, 2, 2)] // i in "This"
- [InlineData(TestString03, 3, 1)] // s in "This"
- [InlineData(TestString03, 4, 0)] // whitespace after "This"
- [InlineData(TestString03, 5, 0)] // whitespace after "This"
- [InlineData(TestString03, 6, 6)] // s in "string"
- [InlineData(TestString03, 9, 3)] // i in "string"
- [InlineData(TestString03, 11, 1)] // g in "string"
- [InlineData(TestString03, 12, 0)] // whitespace after "string"
- [InlineData(TestString03, 14, 0)] // whitespace after "string"
- [InlineData(TestString03, 15, 0)] // whitespace after "string"
- [InlineData(TestString03, 16, 0)] // whitespace after "string"
- [InlineData(TestString03, 20, 0)] // whitespace after "has"
- [InlineData(TestString03, 21, 1)] // "a" after "has"
- [InlineData(TestString03, 28, 10)] // w in "lotofwhitespace"
- [InlineData(TestString03, 38, 0)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 39, 0)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 42, 0)] // whitespace after "lotofwhitespace"
- [InlineData(TestString03, 46, 4)] // r in "charact\u2029\u2028"
- [InlineData(TestString03, 50, 0)] // U2029 in "charact\u2029\u2028"
- [InlineData(TestString03, 51, 0)] // U2028 in "charact\u2029\u2028"
- [InlineData(TestString03, 52, 3)] // e in "\u2029\u2028ers"
- [InlineData(TestString03, 59, 0)] // whitespace after "out"
- [InlineData(TestString03, 62, 0)] // CR in "\r\nnowhere"
- [InlineData(TestString03, 63, 0)] // LF in "\r\nnowhere"
- [InlineData(TestString03, 65, 6)] // o in "nowhere"
- [InlineData(TestString03, 69, 2)] // r in "nowhere"
- [InlineData(TestString03, 70, 1)] // last e in "nowhere"
- [InlineData(TestString03, 71, 0)] // whitespace after "nowhere"
- [InlineData(TestString03, 73, 1)] // second exclamation mark
- [InlineData(TestString03, 74, 0)] // whitespace after exclamation marks
- [InlineData(TestString03, 75, 0)] // End of file
- #endregion
- public void CountUntilWhitespace(string data, int index, int expectedCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedCount, buffer.CountUntilWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(2)]
- [InlineData(14)]
- [InlineData(99)]
- [InlineData(-10)]
- #endregion
- public void CountUntilWhitespace_FailsIfEmpty(int index)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.CountUntilWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void CountUntilWhitespace_FailsIfOutOfRange(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.CountUntilWhitespace(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString03)]
- [InlineData(TestString04)]
- #endregion
- public void CountWhile_SameAsLengthIfAlwaysTrue(string data)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(data.Length, buffer.CountWhile((_, _) => true, 0));
- }
-
- [Theory]
- #region Regular string
- [InlineData(TestString03, 0, 8)] // T in "This
- [InlineData(TestString03, 2, 6)] // i in "This"
- [InlineData(TestString03, 5, 3)] // whitespace before "string"
- [InlineData(TestString03, 7, 1)] // t in "This string"
- [InlineData(TestString03, 8, 0)] // r in "This string"
- [InlineData(TestString03, 9, 37)] // i in "string"
- [InlineData(TestString03, 10, 36)]
- [InlineData(TestString03, 11, 35)]
- [InlineData(TestString03, 15, 31)]
- [InlineData(TestString03, 18, 28)]
- [InlineData(TestString03, 20, 26)]
- [InlineData(TestString03, 22, 24)]
- [InlineData(TestString03, 24, 22)]
- [InlineData(TestString03, 29, 17)]
- [InlineData(TestString03, 31, 15)]
- [InlineData(TestString03, 34, 12)]
- [InlineData(TestString03, 37, 9)]
- [InlineData(TestString03, 41, 5)]
- [InlineData(TestString03, 44, 2)]
- [InlineData(TestString03, 45, 1)]
- [InlineData(TestString03, 46, 0)]
- [InlineData(TestString03, 47, 6)] // second in "charact"
- [InlineData(TestString03, 48, 5)]
- [InlineData(TestString03, 50, 3)]
- [InlineData(TestString03, 51, 2)]
- [InlineData(TestString03, 53, 0)]
- [InlineData(TestString03, 54, 15)] // s in "ers "
- [InlineData(TestString03, 55, 14)]
- [InlineData(TestString03, 58, 11)]
- [InlineData(TestString03, 61, 8)]
- [InlineData(TestString03, 63, 6)]
- [InlineData(TestString03, 64, 5)]
- [InlineData(TestString03, 67, 2)]
- [InlineData(TestString03, 68, 1)]
- [InlineData(TestString03, 69, 0)]
- [InlineData(TestString03, 70, 5)]
- [InlineData(TestString03, 72, 3)] // after last r in the whole string
- [InlineData(TestString03, 74, 1)] // before End of file
- [InlineData(TestString03, 75, 0)] // End of file
- #endregion
- #region String with several r's
- [InlineData(TestString05, 0, 1)] // "!"
- [InlineData(TestString05, 1, 0)]
- [InlineData(TestString05, 4, 0)]
- [InlineData(TestString05, 7, 0)]
- [InlineData(TestString05, 9, 0)]
- [InlineData(TestString05, 10, 0)] // EOF
- #endregion
- #region String with a single r
- [InlineData(TestString06, 0, 0)] // "r"
- [InlineData(TestString06, 1, 9)]
- [InlineData(TestString06, 4, 6)]
- [InlineData(TestString06, 7, 3)]
- [InlineData(TestString06, 9, 1)]
- [InlineData(TestString06, 10, 0)] // EOF
- #endregion
- public void CountWhile_CountsWhileNotLowercaseR(string data, int index, int expectedCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedCount, buffer.CountWhile((_, c) => c != 'r', index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString04, 0, 0)] // t in "this STRING"
- [InlineData(TestString04, 3, 0)] // s in "this STRING"
- [InlineData(TestString04, 4, 14)] // whitespace after "this"
- [InlineData(TestString04, 5, 13)] // S in "STRING"
- [InlineData(TestString04, 6, 12)] // T in "STRING"
- [InlineData(TestString04, 8, 10)] // I in "STRING"
- [InlineData(TestString04, 9, 9)] // N in "STRING"
- [InlineData(TestString04, 10, 8)] // G in "STRING"
- [InlineData(TestString04, 11, 7)] // whitespace after "STRING"
- [InlineData(TestString04, 12, 6)] // M in "MIXES"
- [InlineData(TestString04, 14, 4)] // X in "MIXES"
- [InlineData(TestString04, 16, 2)] // S in "MIXES"
- [InlineData(TestString04, 17, 1)] // whitespace after "MIXES"
- [InlineData(TestString04, 18, 0)] // "a" surrounded by whitespace
- [InlineData(TestString04, 19, 6)] // whitespace after sole "a"
- [InlineData(TestString04, 21, 4)] // O in "LOT"
- [InlineData(TestString04, 22, 3)] // T in "LOT"
- [InlineData(TestString04, 23, 2)] // LF after "LOT"
- [InlineData(TestString04, 24, 1)] // whitespace after "LOT\n"
- [InlineData(TestString04, 26, 0)] // f in "of"
- [InlineData(TestString04, 27, 2)] // whitespace after "of"
- [InlineData(TestString04, 28, 1)] // U2029 after "of "
- [InlineData(TestString04, 29, 0)] // d in "dIffereNT"
- [InlineData(TestString04, 30, 1)] // I in "dIffereNT"
- [InlineData(TestString04, 31, 0)] // first f in "dIffereNT"
- [InlineData(TestString04, 32, 0)] // second f in "dIffereNT"
- [InlineData(TestString04, 35, 0)] // second e in "dIffereNT"
- [InlineData(TestString04, 36, 3)] // N in "dIffereNT"
- [InlineData(TestString04, 37, 2)] // T in "dIffereNT"
- [InlineData(TestString04, 38, 1)] // whitespace after "dIffereNT"
- [InlineData(TestString04, 39, 0)] // c in "cas1ngs"
- [InlineData(TestString04, 42, 0)] // 1 in "cas1ngs"
- [InlineData(TestString04, 46, 5)] // whitespace after "cas1ngs"
- [InlineData(TestString04, 49, 2)] // N in "RAND0mLy"
- [InlineData(TestString04, 51, 0)] // 0 in "RAND0mLy"
- [InlineData(TestString04, 55, 0)] // exclamation mark
- [InlineData(TestString04, 56, 0)] // question mark
- [InlineData(TestString04, 57, 0)] // End of file
- #endregion
- public void CountWhile_CountsWhileUppercaseOrWhitespace(string data, int index, int expectedCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedCount, buffer.CountWhile((_, c) => Char.IsWhiteSpace(c) || c is '\r' or '\n' or '\u2028' or '\u2029' || Char.IsUpper(c), index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(2)]
- [InlineData(14)]
- [InlineData(99)]
- [InlineData(-10)]
- #endregion
- public void CountWhile_FailsIfEmpty(int index)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.CountWhile((_, _) => true, index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void CountWhile_FailsIfOutOfRange(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.CountWhile((_, _) => true, index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void EqualsBuffer(string data) => Assert.Equal(new ReadOnlyStringBuffer(data), new ReadOnlyStringBuffer(data));
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void EqualsMemory(string data) => Assert.True(new ReadOnlyStringBuffer(data).Equals(data.AsMemory()));
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void EqualsString(string data) => Assert.True(new ReadOnlyStringBuffer(data).Equals(data));
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void EqualsSpan(string data) => Assert.True(new ReadOnlyStringBuffer(data).Equals(data.AsSpan()));
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 3)] // "Hey"
- [InlineData(TestString01, 1, 4)] // "This"
- [InlineData(TestString01, 2, 2)] // "Is"
- [InlineData(TestString01, 3, 7)] // "A Test "
- [InlineData(TestString01, 4, 7)] // " Method"
- [InlineData(TestString01, 5, 0)] // ""
- [InlineData(TestString01, 6, 1)] // "."
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, 3)] // "Hey"
- [InlineData(TestString02, 1, 4)] // "This"
- [InlineData(TestString02, 2, 2)] // "Is"
- [InlineData(TestString02, 3, 7)] // "A Test "
- [InlineData(TestString02, 4, 7)] // " Method"
- [InlineData(TestString02, 5, 0)] // ""
- [InlineData(TestString02, 6, 1)] // "."
- #endregion
- public void GetLengthOfLine(string data, int lineNumber, int expectedLength)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- buffer.BuildCache();
- Assert.Equal(expectedLength, buffer.GetLengthOfLine(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetLengthOfLine_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetLengthOfLine(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 8)]
- [InlineData(TestString01, -1)]
- [InlineData(TestString01, -4)]
- [InlineData(TestString01, 20)]
- [InlineData(TestString01, -20)]
- #endregion
- public void GetLengthOfLine_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetLengthOfLine(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 3)] // H in "Hey"
- [InlineData(TestString01, 3, 3)] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, 3)] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, 4)] // T in "This"
- [InlineData(TestString01, 6, 4)] // h in "This"
- [InlineData(TestString01, 13, 7)] // A in "A Test"
- [InlineData(TestString01, 15, 7)] // T in "Test"
- [InlineData(TestString01, 22, 7)] // M in "Method"
- [InlineData(TestString01, 28, 7)] // First CR in "\r\n\r\n."
- [InlineData(TestString01, 29, 7)] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, 0)] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, 31, 0)] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, 1)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString01, 33, 1)] // U2028 in ".\u2028"
- [InlineData(TestString01, 34, 0)] // End of file
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, 3)] // H in "Hey"
- [InlineData(TestString02, 3, 3)] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, 3)] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, 4)] // T in "This"
- [InlineData(TestString02, 6, 4)] // h in "This"
- [InlineData(TestString02, 13, 7)] // A in "A Test"
- [InlineData(TestString02, 15, 7)] // T in "Test"
- [InlineData(TestString02, 22, 7)] // M in "Method"
- [InlineData(TestString02, 28, 7)] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, 7)] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, 0)] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, 0)] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, 1)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString02, 33, 0)] // End of file
- #endregion
- public void GetLengthOfLineFromIndex(string data, int lineNumber, int expectedLength)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedLength, buffer.GetLengthOfLineFromIndex(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetLengthOfLineFromIndex_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetLengthOfLineFromIndex(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void GetLengthOfLineFromIndex_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetLengthOfLineFromIndex(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, "Hey")]
- [InlineData(TestString01, 1, "This")]
- [InlineData(TestString01, 2, "Is")]
- [InlineData(TestString01, 3, "A Test ")]
- [InlineData(TestString01, 4, " Method")]
- [InlineData(TestString01, 5, "")]
- [InlineData(TestString01, 6, ".")]
- [InlineData(TestString01, 7, "")] // eof
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, "Hey")]
- [InlineData(TestString02, 1, "This")]
- [InlineData(TestString02, 2, "Is")]
- [InlineData(TestString02, 3, "A Test ")]
- [InlineData(TestString02, 4, " Method")]
- [InlineData(TestString02, 5, "")]
- [InlineData(TestString02, 6, ".")]
- [InlineData(TestString02, 7, "")] // eof
- #endregion
- #region Single-line strings
- [InlineData(TestString05, 0, "!rrrrrrrrr")]
- [InlineData(TestString05, 1, "")] // eof
- [InlineData(TestString06, 0, "r!!!!!!!!!")]
- [InlineData(TestString06, 1, "")] // eof
- #endregion
- public void GetLine(string data, int lineNumber, string expectedLine)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedLine, buffer.GetLine(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetLine_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetLine(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 8)]
- [InlineData(TestString01, -1)]
- [InlineData(TestString01, -4)]
- [InlineData(TestString01, 20)]
- [InlineData(TestString01, -20)]
- [InlineData(TestString02, 8)]
- [InlineData(TestString05, 2)]
- #endregion
- public void GetLine_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetLine(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, "Hey")] // H in "Hey"
- [InlineData(TestString01, 2, "Hey")] // y in "Hey\r\n"
- [InlineData(TestString01, 3, "Hey")] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, "Hey")] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, "This")] // T in "This"
- [InlineData(TestString01, 6, "This")] // h in "This"
- [InlineData(TestString01, 11, "Is")] // s in "Is"
- [InlineData(TestString01, 12, "Is")] // U2029 before "A Test"
- [InlineData(TestString01, 13, "A Test ")] // A in "A Test"
- [InlineData(TestString01, 15, "A Test ")] // T in "Test"
- [InlineData(TestString01, 22, " Method")] // M in "Method"
- [InlineData(TestString01, 28, " Method")] // First CR in "\r\n\r\n."
- [InlineData(TestString01, 29, " Method")] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, "")] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, 31, "")] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, ".")] // Dot/point in "\r\n\r\n."
- [InlineData(TestString01, 33, ".")] // U2028 in ".\u2028"
- [InlineData(TestString01, 34, "")] // End of file
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, "Hey")] // H in "Hey"
- [InlineData(TestString02, 2, "Hey")] // y in "Hey\r\n"
- [InlineData(TestString02, 3, "Hey")] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, "Hey")] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, "This")] // T in "This"
- [InlineData(TestString02, 6, "This")] // h in "This"
- [InlineData(TestString02, 11, "Is")] // s in "Is"
- [InlineData(TestString02, 12, "Is")] // U2029 before "A Test"
- [InlineData(TestString02, 13, "A Test ")] // A in "A Test"
- [InlineData(TestString02, 15, "A Test ")] // T in "Test"
- [InlineData(TestString02, 22, " Method")] // M in "Method"
- [InlineData(TestString02, 28, " Method")] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, " Method")] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, "")] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, "")] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, ".")] // Dot/point in "\r\n\r\n."
- [InlineData(TestString02, 33, "")] // End of file
- #endregion
- public void GetLineFromIndex(string data, int index, string expectedLine)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedLine, buffer.GetLineFromIndex(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void GetLineFromIndex_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetLineFromIndex(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetLineFromIndex_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetLineFromIndex(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 0)] // H in "Hey"
- [InlineData(TestString01, 3, 0)] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, 0)] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, 1)] // T in "This"
- [InlineData(TestString01, 6, 1)] // h in "This"
- [InlineData(TestString01, 13, 3)] // A in "A Test"
- [InlineData(TestString01, 15, 3)] // T in "Test"
- [InlineData(TestString01, 22, 4)] // M in "Method"
- [InlineData(TestString01, 28, 4)] // First CR in "\r\n\r\n."
- [InlineData(TestString01, 29, 4)] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, 5)] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, 31, 5)] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, 6)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString01, 33, 6)] // U2028 in ".\u2028"
- [InlineData(TestString01, 34, 7)] // End of file
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, 0)] // H in "Hey"
- [InlineData(TestString02, 3, 0)] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, 0)] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, 1)] // T in "This"
- [InlineData(TestString02, 6, 1)] // h in "This"
- [InlineData(TestString02, 13, 3)] // A in "A Test"
- [InlineData(TestString02, 15, 3)] // T in "Test"
- [InlineData(TestString02, 22, 4)] // M in "Method"
- [InlineData(TestString02, 28, 4)] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, 4)] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, 5)] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, 5)] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, 6)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString02, 33, 7)] // EOF
- #endregion
- public void GetLineNumberFromIndex(string data, int index, int expectedLineCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- buffer.BuildCache();
- Assert.Equal(expectedLineCount, buffer.GetLineNumberFromIndex(index));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void GetLineNumberFromIndex_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetLineNumberFromIndex(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetLineNumberFromIndex_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetLineNumberFromIndex(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 0, 0, 0)] // H in "Hey"
- [InlineData(TestString01, 3, 3, 0, 3)] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, 4, 0, 4)] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, 5, 1, 0)] // T in "This"
- [InlineData(TestString01, 6, 6, 1, 1)] // h in "This"
- [InlineData(TestString01, 13, 13, 3, 0)] // A in "A Test"
- [InlineData(TestString01, 15, 15, 3, 2)] // T in "Test"
- [InlineData(TestString01, 22, 22, 4, 1)] // M in "Method"
- [InlineData(TestString01, 28, 28, 4, 7)] // First CR in "\r\n\r\n."
- [InlineData(TestString01, -5, 29, 4, 8)] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, 30, 5, 0)] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, -3, 31, 5, 1)] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, 32, 6, 0)] // Dot/point in "\r\n\r\n."
- [InlineData(TestString01, 33, 33, 6, 1)] // U2028 in ".\u2028"
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, 0, 0, 0)] // H in "Hey"
- [InlineData(TestString02, 3, 3, 0, 3)] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, 4, 0, 4)] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, 5, 1, 0)] // T in "This"
- [InlineData(TestString02, 6, 6, 1, 1)] // h in "This"
- [InlineData(TestString02, 13, 13, 3, 0)] // A in "A Test"
- [InlineData(TestString02, 15, 15, 3, 2)] // T in "Test"
- [InlineData(TestString02, 22, 22, 4, 1)] // M in "Method"
- [InlineData(TestString02, 28, 28, 4, 7)] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, 29, 4, 8)] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, 30, 5, 0)] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, 31, 5, 1)] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, 32, 6, 0)] // Dot/point in "\r\n\r\n."
- #endregion
- public void GetSourceLocation(string data, int index, int expectedIndex, int expectedLine, int expectedColumn)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- var location = buffer.GetSourceLocation(index);
-
- Assert.Equal(expectedIndex, location.Index);
- Assert.Equal(expectedLine, location.Line);
- Assert.Equal(expectedColumn, location.Column);
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 99)]
- [InlineData(TestString01, 34)] // EOF is not accepted for this method
- [InlineData(TestString01, 35)]
- [InlineData(TestString01, -35)]
- #endregion
- public void GetSourceLocation_FailsIfOutOfRange(string data, int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Throws(() => buffer.GetSourceLocation(lineNumber));
- }
-
- [Theory]
- #region Attributes
- [InlineData(0)]
- [InlineData(20)]
- [InlineData(69)]
- [InlineData(136)]
- [InlineData(-4)]
- #endregion
- public void GetSourceLocation_FailsIfEmpty(int lineNumber)
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.Throws(() => buffer.GetSourceLocation(lineNumber));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 3, new int[] { 0, 0, 0, 3, 0, 3 })]
- [InlineData(TestString01, 3, 5, new int[] { 3, 0, 3, 5, 1, 0 })]
- [InlineData(TestString01, 4, 33, new int[] { 4, 0, 4, 33, 6, 1 })]
- [InlineData(TestString01, 5, 6, new int[] { 5, 1, 0, 6, 1, 1 })]
- [InlineData(TestString01, 6, 15, new int[] { 6, 1, 1, 15, 3, 2 })]
- [InlineData(TestString01, 13, 16, new int[] { 13, 3, 0, 16, 3, 3 })]
- [InlineData(TestString01, 15, 22, new int[] { 15, 3, 2, 22, 4, 1 })]
- [InlineData(TestString01, 28, -5, new int[] { 28, 4, 7, 29, 4, 8 })]
- [InlineData(TestString01, 30, 32, new int[] { 30, 5, 0, 32, 6, 0 })]
- [InlineData(TestString01, -3, 33, new int[] { 31, 5, 1, 33, 6, 1 })]
- #endregion
- #region String ends with newline
- [InlineData(TestString02, 0, 3, new int[] { 0, 0, 0, 3, 0, 3 })]
- [InlineData(TestString02, 3, 5, new int[] { 3, 0, 3, 5, 1, 0 })]
- [InlineData(TestString02, 5, 6, new int[] { 5, 1, 0, 6, 1, 1 })]
- [InlineData(TestString02, 6, 15, new int[] { 6, 1, 1, 15, 3, 2 })]
- [InlineData(TestString02, 13, 16, new int[] { 13, 3, 0, 16, 3, 3 })]
- [InlineData(TestString02, 15, 22, new int[] { 15, 3, 2, 22, 4, 1 })]
- #endregion
- public void GetSourceSpan(string data, int startIndex, int endIndex, int[] expectations)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- var span = buffer.GetSourceSpan(startIndex, endIndex);
-
- Assert.Equal(expectations[0], span.Start.Index);
- Assert.Equal(expectations[1], span.Start.Line);
- Assert.Equal(expectations[2], span.Start.Column);
-
- Assert.Equal(expectations[3], span.End.Index);
- Assert.Equal(expectations[4], span.End.Line);
- Assert.Equal(expectations[5], span.End.Column);
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0)]
- [InlineData(TestString01, 3)]
- [InlineData(TestString01, 7)]
- [InlineData(TestString01, 8)]
- [InlineData(TestString01, 10)]
- [InlineData(TestString01, 12)]
- [InlineData(TestString01, 15)]
- [InlineData(TestString01, 21)]
- [InlineData(TestString01, 24)]
- [InlineData(TestString01, 26)]
- [InlineData(TestString01, 27)]
- [InlineData(TestString01, 30)]
- [InlineData(TestString01, 32)]
- [InlineData(TestString01, 33)]
- #endregion
- #region String ends with newline
- [InlineData(TestString02, 0)]
- [InlineData(TestString02, 2)]
- [InlineData(TestString02, 5)]
- [InlineData(TestString02, 9)]
- [InlineData(TestString02, 10)]
- [InlineData(TestString02, 12)]
- [InlineData(TestString02, 15)]
- [InlineData(TestString02, 19)]
- [InlineData(TestString02, 23)]
- [InlineData(TestString02, 27)]
- [InlineData(TestString02, 29)]
- [InlineData(TestString02, 31)]
- #endregion
- #region String with a lot of whitespace
- [InlineData(TestString03, 0)]
- [InlineData(TestString03, 2)]
- [InlineData(TestString03, 15)]
- [InlineData(TestString03, 29)]
- [InlineData(TestString03, 30)]
- [InlineData(TestString03, 42)]
- [InlineData(TestString03, 55)]
- [InlineData(TestString03, 69)]
- [InlineData(TestString03, 73)]
- #endregion
- #region String with a single line
- [InlineData(TestString05, 0)]
- [InlineData(TestString05, 2)]
- [InlineData(TestString05, 5)]
- [InlineData(TestString05, 9)]
- [InlineData(TestString06, 1)]
- [InlineData(TestString06, 4)]
- [InlineData(TestString06, 6)]
- [InlineData(TestString06, 8)]
- #endregion
- public void IndexAccessor(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(data[index], buffer[index]);
- Assert.Equal(data[index], buffer[new SourceLocation(index, 0, 0)]);
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01, 7)]
- [InlineData(TestString02, 7)]
- [InlineData(TestString03, 4)]
- [InlineData(TestString04, 3)]
- [InlineData(TestString05, 1)]
- [InlineData(TestString06, 1)]
- [InlineData(TestString07, 3)]
- #endregion
- public void LineCount_MatchesActualLineCountWithNoEof(string data, int expectedLineCount)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.Equal(expectedLineCount, buffer.LineCount);
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString02)]
- [InlineData(TestString03)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void Constructor_CharArray(string data)
- {
- var array = data.ToCharArray();
- Assert.Equal(data, array);
- Assert.True(new ReadOnlyStringBuffer(array).Equals(data));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString01)]
- [InlineData(TestString02)]
- [InlineData(TestString03)]
- [InlineData(TestString04)]
- [InlineData(TestString05)]
- [InlineData(TestString06)]
- #endregion
- public unsafe void Constructor_BytePointer(string data)
- {
- var byteCount = Encoding.Default.GetByteCount(data);
-
- fixed (byte* pointer = Encoding.Default.GetBytes(data))
- {
- Assert.Equal(data, Encoding.Default.GetString(pointer, byteCount));
- Assert.True(new ReadOnlyStringBuffer(pointer, byteCount).Equals(data));
- }
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString02)]
- [InlineData(TestString03)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void Constructor_ByteArray(string data)
- {
- var array = Encoding.Default.GetBytes(data);
- Assert.True(new ReadOnlyStringBuffer(array).Equals(data));
- }
-
- [Theory]
- #region Attributes
- [InlineData(TestString02)]
- [InlineData(TestString03)]
- [InlineData(TestString04)]
- [InlineData(TestString06)]
- #endregion
- public void Constructor_ReadOnlySpan(string data)
- {
- ReadOnlySpan span = data.AsSpan();
- Assert.True(new ReadOnlyStringBuffer(span).Equals(data));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 4, "Hey\r")]
- [InlineData(TestString01, 3, 2, "\r\n")]
- [InlineData(TestString01, 5, 1, "T")]
- [InlineData(TestString01, 7, 6, "is\rIs\u2029")]
- [InlineData(TestString01, 12, 8, "\u2029A Test ")]
- [InlineData(TestString01, 16, 4, "est ")]
- [InlineData(TestString01, 30, 3, "\r\n.")]
- [InlineData(TestString01, -3, 3, "\n.\u2028")]
- #endregion
- public void Slice_CharArray(string data, int index, int length, string expectedSlice)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- var slice = buffer.Slice(index, length);
-
- Assert.Equal(expectedSlice, new string(slice));
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, "Hey\r")]
- [InlineData(TestString01, 3, "\r\n")]
- [InlineData(TestString01, 5, "T")]
- [InlineData(TestString01, 7, "is\rIs\u2029")]
- [InlineData(TestString01, 12, "\u2029A Test ")]
- [InlineData(TestString01, 16, "est ")]
- [InlineData(TestString01, 30, "\r\n.")]
- [InlineData(TestString01, -3, "\n.\u2028")]
- #endregion
- public void Slice_CharSpan(string data, int index, string expectedSlice)
- {
- Span span = stackalloc char[expectedSlice.Length];
-
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.True(buffer.TrySlice(index, span));
-
- Assert.Equal(expectedSlice, span);
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, 4, "Hey\r")]
- [InlineData(TestString01, 3, 5, "\r\n")]
- [InlineData(TestString01, 5, 6, "T")]
- [InlineData(TestString01, 7, 13, "is\rIs\u2029")]
- [InlineData(TestString01, 12, 20, "\u2029A Test ")]
- [InlineData(TestString01, 16, 20, "est ")]
- [InlineData(TestString01, 30, 33, "\r\n.")]
- [InlineData(TestString01, 31, 34, "\n.\u2028")]
- #endregion
- public void Slice_SourceSpan(string data, int startIndex, int endIndex, string expectedSlice)
- {
- Span span = stackalloc char[expectedSlice.Length];
-
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.True(buffer.TrySlice(new SourceSpan(new(startIndex, 0, 0), new(endIndex, 0, 0)), span));
-
- Assert.Equal(expectedSlice, span);
- }
-
- [Fact]
- public void Slice_SourceSpan_FailsIfSpanTooSmall()
- {
- var buffer = new ReadOnlyStringBuffer(TestString05);
- Assert.False(buffer.TrySlice(new SourceSpan(new(0, 0, 0), new(7, 0, 0)), stackalloc char[3]));
- }
-
- [Fact]
- public void ToString_SameAsInputData()
- {
- var buffer = new ReadOnlyStringBuffer(TestString02);
- Assert.Equal(TestString02, buffer.ToString());
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0)]
- [InlineData(TestString01, 3)]
- [InlineData(TestString01, 7)]
- [InlineData(TestString01, 8)]
- [InlineData(TestString01, 10)]
- [InlineData(TestString01, 12)]
- [InlineData(TestString01, 15)]
- [InlineData(TestString01, 21)]
- [InlineData(TestString01, 24)]
- [InlineData(TestString01, 26)]
- [InlineData(TestString01, 27)]
- [InlineData(TestString01, 30)]
- [InlineData(TestString01, 32)]
- [InlineData(TestString01, 33)]
- #endregion
- #region String ends with newline
- [InlineData(TestString02, 0)]
- [InlineData(TestString02, 2)]
- [InlineData(TestString02, 5)]
- [InlineData(TestString02, 9)]
- [InlineData(TestString02, 10)]
- [InlineData(TestString02, 12)]
- [InlineData(TestString02, 15)]
- [InlineData(TestString02, 19)]
- [InlineData(TestString02, 23)]
- [InlineData(TestString02, 27)]
- [InlineData(TestString02, 29)]
- [InlineData(TestString02, 31)]
- #endregion
- #region String with a lot of whitespace
- [InlineData(TestString03, 0)]
- [InlineData(TestString03, 2)]
- [InlineData(TestString03, 15)]
- [InlineData(TestString03, 29)]
- [InlineData(TestString03, 30)]
- [InlineData(TestString03, 42)]
- [InlineData(TestString03, 55)]
- [InlineData(TestString03, 69)]
- [InlineData(TestString03, 73)]
- #endregion
- #region String with a single line
- [InlineData(TestString05, 0)]
- [InlineData(TestString05, 2)]
- [InlineData(TestString05, 5)]
- [InlineData(TestString05, 9)]
- [InlineData(TestString06, 1)]
- [InlineData(TestString06, 4)]
- [InlineData(TestString06, 6)]
- [InlineData(TestString06, 8)]
- #endregion
- public void TryGetChar(string data, int index)
- {
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.True(buffer.TryGetChar(index, out char item));
- Assert.Equal(data[index], item);
- }
-
- [Fact]
- public void TryGetChar_FollowsSpecificEofConvention()
- {
- var buffer = new ReadOnlyStringBuffer(TestString05);
- Assert.False(buffer.TryGetChar(10, out char item));
- Assert.Equal('\0', item);
- }
-
- [Fact]
- public void TryGetChar_FailsIfEmpty()
- {
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.False(buffer.TryGetChar(0, out char item));
- Assert.Equal('\0', item);
- }
-
- [Fact]
- public void TryGetChar_FailsIfOutOfRange()
- {
- var buffer = new ReadOnlyStringBuffer(TestString06);
- Assert.False(buffer.TryGetChar(14, out char item));
- Assert.Equal('\0', item);
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, "Hey")]
- [InlineData(TestString01, 1, "This")]
- [InlineData(TestString01, 2, "Is")]
- [InlineData(TestString01, 3, "A Test ")]
- [InlineData(TestString01, 4, " Method")]
- [InlineData(TestString01, 5, "")]
- [InlineData(TestString01, 6, ".")]
- [InlineData(TestString01, 7, "")] // eof
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, "Hey")]
- [InlineData(TestString02, 1, "This")]
- [InlineData(TestString02, 2, "Is")]
- [InlineData(TestString02, 3, "A Test ")]
- [InlineData(TestString02, 4, " Method")]
- [InlineData(TestString02, 5, "")]
- [InlineData(TestString02, 6, ".")]
- [InlineData(TestString02, 7, "")] // eof
- #endregion
- #region Single-line strings
- [InlineData(TestString05, 0, "!rrrrrrrrr")]
- [InlineData(TestString05, 1, "")] // eof
- [InlineData(TestString06, 0, "r!!!!!!!!!")]
- [InlineData(TestString06, 1, "")] // eof
- #endregion
- public void TryGetLine(string data, int lineNumber, string expectedLine)
- {
- Span span = stackalloc char[expectedLine.Length];
-
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.True(buffer.TryGetLine(lineNumber, span));
- Assert.Equal(expectedLine, span);
- }
-
- [Fact]
- public void TryGetLine_FailsIfEmpty()
- {
- Span span = [];
-
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.False(buffer.TryGetLine(0, span));
- Assert.True(span.IsEmpty);
- }
-
- [Fact]
- public void TryGetLine_FailsIfOutOfRange()
- {
- Span span = stackalloc char[10];
-
- var buffer = new ReadOnlyStringBuffer(TestString05);
- const string emptySequence = "\0\0\0\0\0\0\0\0\0\0";
-
- Assert.False(buffer.TryGetLine(2, span));
- Assert.Equal(emptySequence, span);
-
- Assert.False(buffer.TryGetLine(-1, span));
- Assert.Equal(emptySequence, span);
- }
-
- [Fact]
- public void TryGetLine_FalseIfSpanTooSmall()
- {
- Span span = stackalloc char[5];
-
- var buffer = new ReadOnlyStringBuffer(TestString05);
- Assert.False(buffer.TryGetLine(0, span));
- Assert.Equal("\0\0\0\0\0", span);
- }
-
- [Theory]
- #region String ends with newline
- [InlineData(TestString01, 0, "Hey")] // H in "Hey"
- [InlineData(TestString01, 3, "Hey")] // CR in "Hey\r\n"
- [InlineData(TestString01, 4, "Hey")] // LF in "Hey\r\n"
- [InlineData(TestString01, 5, "This")] // T in "This"
- [InlineData(TestString01, 13, "A Test ")] // A in "A Test"
- [InlineData(TestString01, 15, "A Test ")] // T in "Test"
- [InlineData(TestString01, 22, " Method")] // M in "Method"
- [InlineData(TestString01, 28, " Method")] // First CR in "\r\n\r\n."
- [InlineData(TestString01, 29, " Method")] // First LF in "\r\n\r\n."
- [InlineData(TestString01, 30, "")] // Second CR in "\r\n\r\n."
- [InlineData(TestString01, 31, "")] // Second LF in "\r\n\r\n."
- [InlineData(TestString01, 32, ".")] // Dot/point in Second LF in "\r\n\r\n."
- [InlineData(TestString01, 33, ".")] // U2028 in ".\u2028"
- [InlineData(TestString01, 34, "")] // End of file
- #endregion
- #region String ends without newline
- [InlineData(TestString02, 0, "Hey")] // H in "Hey"
- [InlineData(TestString02, 3, "Hey")] // CR in "Hey\r\n"
- [InlineData(TestString02, 4, "Hey")] // LF in "Hey\r\n"
- [InlineData(TestString02, 5, "This")] // T in "This"
- [InlineData(TestString02, 13, "A Test ")] // A in "A Test"
- [InlineData(TestString02, 15, "A Test ")] // T in "Test"
- [InlineData(TestString02, 22, " Method")] // M in "Method"
- [InlineData(TestString02, 28, " Method")] // First CR in "\r\n\r\n."
- [InlineData(TestString02, 29, " Method")] // First LF in "\r\n\r\n."
- [InlineData(TestString02, 30, "")] // Second CR in "\r\n\r\n."
- [InlineData(TestString02, -2, "")] // Second LF in "\r\n\r\n."
- [InlineData(TestString02, -1, ".")] // Dot/point in Second LF in "\r\n\r\n."
- [InlineData(TestString02, 33, "")] // End of file
- #endregion
- public void TryGetLineFromIndex(string data, int index, string expectedLine)
- {
- Span span = stackalloc char[expectedLine.Length];
-
- var buffer = new ReadOnlyStringBuffer(data);
- Assert.True(buffer.TryGetLineFromIndex(index, span));
- Assert.Equal(expectedLine, span.ToString());
- }
-
- [Fact]
- public void TryGetLineFromIndex_FailsIfEmpty()
- {
- Span span = [];
-
- var buffer = new ReadOnlyStringBuffer(String.Empty);
- Assert.False(buffer.TryGetLineFromIndex(0, span));
- Assert.True(span.IsEmpty);
- }
-
- [Fact]
- public void TryGetLineFromIndex_FailsIfOutOfRange()
- {
- Span span = stackalloc char[10];
-
- var buffer = new ReadOnlyStringBuffer(TestString05);
- const string emptySequence = "\0\0\0\0\0\0\0\0\0\0";
-
- Assert.False(buffer.TryGetLineFromIndex(12, span));
- Assert.Equal(emptySequence, span);
-
- Assert.False(buffer.TryGetLineFromIndex(-12, span));
- Assert.Equal(emptySequence, span);
- }
-
- [Fact]
- public void TryGetLineFromIndex_FalseIfSpanTooSmall()
- {
- Span span = stackalloc char[5];
-
- var buffer = new ReadOnlyStringBuffer(TestString05);
- Assert.False(buffer.TryGetLineFromIndex(0, span));
- Assert.Equal("\0\0\0\0\0", span);
- }
-}