diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 32bd053..7d534b5 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -10,16 +10,14 @@ jobs: build: runs-on: ubuntu-latest - container: - image: google/dart:latest - steps: - uses: actions/checkout@v2 with: submodules: true - - - name: Print Dart SDK version - run: dart --version + + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable - name: Install dependencies run: dart pub get diff --git a/.gitmodules b/.gitmodules index 0eac695..0eae3c3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "test/kdl-org"] path = test/kdl-org url = git@github.com:kdl-org/kdl +[submodule "test/v1/kdl-org"] + path = test/v1/kdl-org + url = git@github.com:kdl-org/kdl + branch = release/v1 diff --git a/.tool-versions b/.tool-versions index 91bd690..77a40a3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -dart 3.2.6 +dart 3.6.0 diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..fde4885 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:lints/recommended.yaml + +linter: + rules: + - public_member_api_docs diff --git a/example/Cargo.kdl b/example/Cargo.kdl deleted file mode 100644 index 7b58dba..0000000 --- a/example/Cargo.kdl +++ /dev/null @@ -1,13 +0,0 @@ -package { - name "kdl" - version "0.0.0" - description "kat's document language" - authors "Kat Marchán " - license-file "LICENSE.md" - edition "2018" -} - -dependencies { - nom "6.0.1" - thiserror "1.0.22" -} diff --git a/example/ci.kdl b/example/ci.kdl deleted file mode 100644 index b0c4014..0000000 --- a/example/ci.kdl +++ /dev/null @@ -1,46 +0,0 @@ -// This example is a GitHub Action if it used KDL syntax. -name "CI" - -on "push" "pull_request" - -env { - RUSTFLAGS "-Dwarnings" -} - -jobs { - fmt_and_docs "Check fmt & build docs" { - runs-on "ubuntu-latest" - steps { - step uses="actions/checkout@v1" - step "Install Rust" uses="actions-rs/toolchain@v1" { - profile "minimal" - toolchain "stable" - components "rustfmt" - override true - } - step "rustfmt" run="cargo fmt --all -- --check" - step "docs" run="cargo doc --no-deps" - } - } - build_and_test "Build & Test" { - runs-on "${{ matrix.os }}" - strategy { - matrix { - rust "1.46.0" "stable" - os "ubuntu-latest" "macOS-latest" "windows-latest" - } - } - - steps { - step uses="actions/checkout@v1" - step "Install Rust" uses="actions-rs/toolchain@v1" { - profile "minimal" - toolchain "${{ matrix.rust }}" - components "clippy" - override true - } - step "Clippy" run="cargo clippy --all -- -D warnings" - step "Run tests" run="cargo test --all --verbose" - } - } -} diff --git a/example/lib/main.dart b/example/lib/main.dart index 874604a..ae8d15a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,7 @@ import 'package:kdl/kdl.dart'; main() { - var document = Kdl.parseDocument(""" + var document = KdlDocument.parse(""" node 1 2 3 "foo" bar="baz" { childNode 1 childNode 2 diff --git a/example/nuget.kdl b/example/nuget.kdl deleted file mode 100644 index 9ab4aa1..0000000 --- a/example/nuget.kdl +++ /dev/null @@ -1,178 +0,0 @@ -// Based on https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj -Project { - PropertyGroup { - IsCommandLinePackage true - } - - Import Project=r"$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'README.md'))\build\common.props" - Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" - Import Project="ilmerge.props" - - PropertyGroup { - RootNamespace "NuGet.CommandLine" - AssemblyName "NuGet" - AssemblyTitle "NuGet Command Line" - PackageId "NuGet.CommandLine" - TargetFramework "$(NETFXTargetFramework)" - GenerateDocumentationFile false - Description "NuGet Command Line Interface." - ApplicationManifest "app.manifest" - Shipping true - OutputType "Exe" - ComVisible false - // Pack properties - PackProject true - IncludeBuildOutput false - TargetsForTfmSpecificContentInPackage "$(TargetsForTfmSpecificContentInPackage)" "CreateCommandlineNupkg" - SuppressDependenciesWhenPacking true - DevelopmentDependency true - PackageRequireLicenseAcceptance false - UsePublicApiAnalyzer false - } - - Target Name="CreateCommandlineNupkg" { - ItemGroup { - TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" { - PackagePath "tools/" - } - TfmSpecificPackageFile Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb" { - PackagePath "tools/" - } - } - } - - ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" { - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.FuncTest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293" - } - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293" - } - } - - ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" { - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.FuncTest" - } - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.Test" - } - } - - ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))" { - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293" - } - } - - ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))" { - AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo" { - _Parameter1 "NuGet.CommandLine.Test" - } - } - - ItemGroup { - Reference Include="Microsoft.Build.Utilities.v4.0" - Reference Include="Microsoft.CSharp" - Reference Include="System" - Reference Include="System.ComponentModel.Composition" - Reference Include="System.ComponentModel.Composition.Registration" - Reference Include="System.ComponentModel.DataAnnotations" - Reference Include="System.IO.Compression" - Reference Include="System.Net.Http" - Reference Include="System.Xml" - Reference Include="System.Xml.Linq" - Reference Include="NuGet.Core" { - HintPath r"$(SolutionPackagesFolder)nuget.core\2.14.0-rtm-832\lib\net40-Client\NuGet.Core.dll" - Aliases "CoreV2" - } - } - ItemGroup { - PackageReference Include="Microsoft.VisualStudio.Setup.Configuration.Interop" - ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.PackageManagement\NuGet.PackageManagement.csproj" - ProjectReference Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.Build.Tasks.csproj" - } - - ItemGroup { - EmbeddedResource Update="NuGetCommand.resx" { - Generator "ResXFileCodeGenerator" - LastGenOutput "NuGetCommand.Designer.cs" - } - Compile Update="NuGetCommand.Designer.cs" { - DesignTime true - AutoGen true - DependentUpon "NuGetCommand.resx" - } - EmbeddedResource Update="NuGetResources.resx" { - // Strings are shared by other projects, use public strings. - Generator "PublicResXFileCodeGenerator" - LastGenOutput "NuGetResources.Designer.cs" - } - Compile Update="NuGetResources.Designer.cs" { - DesignTime true - AutoGen true - DependentUpon "NuGetResources.resx" - } - } - - ItemGroup { - EmbeddedResource Include=r"$(NuGetCoreSrcDirectory)NuGet.Build.Tasks\NuGet.targets" { - Link "NuGet.targets" - SubType "Designer" - } - } - - // Since we are moving some code and strings from NuGet.CommandLine to NuGet.Commands, we opted to go through normal localization process (build .resources.dll) and then add them to the ILMerged nuget.exe - // This will also be called from CI build, after assemblies are localized, since our test infra takes nuget.exe before Localization - Target Name="ILMergeNuGetExe" \ - AfterTargets="Build" \ - Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(SkipILMergeOfNuGetExe)' != 'true'" \ - { - PropertyGroup { - // when done after build, no localizedartifacts are built yet, so expected localized artifact count is 0. - ExpectedLocalizedArtifactCount 0 Condition="'$(ExpectedLocalizedArtifactCount)' == ''" - } - ItemGroup { - BuildArtifacts Include=r"$(OutputPath)\*.dll" Exclude="@(MergeExclude)" - // NuGet.exe needs all NuGet.Commands.resources.dll merged in - LocalizedArtifacts Include=r"$(ArtifactsDirectory)\NuGet.Commands\**\$(NETFXTargetFramework)\**\*.resources.dll" - } - Error Text="Build dependencies are inconsistent with mergeinclude specified in ilmerge.props" \ - Condition="'@(BuildArtifacts->Count())' != '@(MergeInclude->Count())'" - Error Text="Satellite assemblies count ILMerged into NuGet.exe should be $(ExpectedLocalizedArtifactCount), but was: @(LocalizedArtifacts->Count())" \ - Condition="'@(LocalizedArtifacts->Count())' != '$(ExpectedLocalizedArtifactCount)'" - PropertyGroup { - PathToBuiltNuGetExe "$(OutputPath)NuGet.exe" - IlmergeCommand r"$(ILMergeExePath) /lib:$(OutputPath) /out:$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe @(MergeAllowDup -> '/allowdup:%(Identity)', ' ') /log:$(OutputPath)IlMergeLog.txt" - IlmergeCommand Condition="Exists($(MS_PFX_PATH))" "$(IlmergeCommand) /delaysign /keyfile:$(MS_PFX_PATH)" - // LocalizedArtifacts need fullpath, since there will be duplicate file names - IlmergeCommand "$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(filename)%(extension)', ' ') @(LocalizedArtifacts->'%(fullpath)', ' ')" - } - MakeDir Directories="$(ArtifactsDirectory)$(VsixOutputDirName)" - Exec Command="$(IlmergeCommand)" ContinueOnError="false" - } - - Import Project="$(BuildCommonDirectory)common.targets" - Import Project="$(BuildCommonDirectory)embedinterop.targets" - - // Do nothing. This basically strips away the framework assemblies from the resulting nuspec. - Target Name="_GetFrameworkAssemblyReferences" DependsOnTargets="ResolveReferences" - - Target Name="GetSigningInputs" Returns="@(DllsToSign)" { - ItemGroup { - DllsToSign Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" { - StrongName "MsSharedLib72" - Authenticode "Microsoft400" - } - } - } - - Target Name="GetSymbolsToIndex" Returns="@(SymbolsToIndex)" { - ItemGroup { - SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe" - SymbolsToIndex Include=r"$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.pdb" - } - } - - Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" -} diff --git a/lib/kdl.dart b/lib/kdl.dart index 93dab14..0bc52e9 100644 --- a/lib/kdl.dart +++ b/lib/kdl.dart @@ -1,7 +1,6 @@ -import 'package:kdl/src/parser.dart'; +/// Exports the public interface of KDL +library; -abstract class Kdl { - static parseDocument(String string, { Map typeParsers = const {}, bool parseTypes = true }) { - return KdlParser().parse(string, typeParsers: typeParsers, parseTypes: parseTypes); - } -} +export 'src/document.dart'; +export 'src/exception.dart'; +export 'src/types.dart'; diff --git a/lib/src/document.dart b/lib/src/document.dart index 517d8af..114396d 100644 --- a/lib/src/document.dart +++ b/lib/src/document.dart @@ -1,5 +1,9 @@ -import "package:kdl/src/string_dumper.dart"; +import 'dart:collection'; + +import 'package:kdl/src/exception.dart'; +import 'package:kdl/src/parser.dart'; import 'package:big_decimal/big_decimal.dart'; +import 'package:kdl/src/tokenizer.dart'; _sameNodes(List nodes, List otherNodes) { if (nodes.length != otherNodes.length) return false; @@ -11,61 +15,217 @@ _sameNodes(List nodes, List otherNodes) { return true; } -class KdlDocument { +/// KDL Root node +class KdlDocument with IterableMixin { + /// Top level nodes List nodes = []; - KdlDocument(List initialNodes) { - this.nodes = initialNodes; + /// Parse a KDL document + /// + /// By default, attempts to parse v2 syntax, and falls back to v1 if that + /// fails. You can override this behaviour by explicitely passing `version`. + /// + /// By default, parses well-known types as specified by [defaultValueTypes]. + /// You can turn this off by passing `parseTypes: false`. You can also add + /// your own custom types by passing `valueTypes` and `nodeTypes`. + static KdlDocument parse(String string, + {int? version, + Map> valueTypes = const {}, + Map> nodeTypes = const {}, + bool parseTypes = true}) { + switch (version) { + case 1: + return KdlV1Parser().parse(string, + valueTypes: valueTypes, + nodeTypes: nodeTypes, + parseTypes: parseTypes); + case 2: + return KdlParser().parse(string, + valueTypes: valueTypes, + nodeTypes: nodeTypes, + parseTypes: parseTypes); + case null: + try { + return parse(string, + version: 2, + valueTypes: valueTypes, + nodeTypes: nodeTypes, + parseTypes: parseTypes); + } on KdlVersionMismatchException catch (e) { + return parse(string, + version: e.version, + valueTypes: valueTypes, + nodeTypes: nodeTypes, + parseTypes: parseTypes); + } on KdlParseException { + return parse(string, + version: 1, valueTypes: valueTypes, nodeTypes: nodeTypes); + } + default: + throw KdlException( + "Unsupported version $version, supported versions are 1 or 2"); + } + } + + /// Create a new KdlDocument with the given nodes + KdlDocument([List? initialNodes]) { + nodes = initialNodes ?? []; + } + + /// Return the value of the first arg of the requested node + arg(key) { + return this[key].arguments.first.value; + } + + /// Return the argument values of the requested node + Iterable args(key) { + return this[key].arguments.map((arg) => arg.value); + } + + /// Return the first argument value of each node named '-' that is a child of + /// the requested node + Iterable dashVals(key) { + return this[key] + .children + .where((node) => node.name == "-") + .map((node) => node.arguments.first) + .map((arg) => arg.value); + } + + /// Request a node. If key is an `int`, return the node by index. If the key is + /// a `String`, return the first node with that name + KdlNode operator [](key) { + if (key is int) { + return nodes[key]; + } else if (key is String) { + return nodes.firstWhere((node) => node.name == key); + } else { + throw ArgumentError("document can only be indexed with Int/String"); + } } @override - bool operator ==(other) => other is KdlDocument && _sameNodes(nodes, other.nodes); + bool operator ==(other) => + other is KdlDocument && _sameNodes(nodes, other.nodes); @override int get hashCode => nodes.hashCode; + @override + Iterator get iterator => nodes.iterator; + @override String toString() { - return nodes.map((e) => e.toString()).join("\n") + "\n"; + return "${nodes.map((e) => e.toString()).join("\n")}\n"; } } -class KdlNode { +/// Function signature for converting `KdlValue` and `KdlNode` into custom types. +/// Return `null` to skip parsing and keep the original value. +typedef KdlTypeParser = T? Function(T, String type); + +/// A KDL node. Nodes can have positional arguments, key=value properties, and +/// other nodes as children. +class KdlNode with IterableMixin { + /// The name of the node String name = ''; - String? type = null; + + /// Optional type annotation + String? type; + + /// Child nodes List children = []; + + /// Positional arguments List arguments = []; + + /// Key=value properties Map properties = {}; - KdlNode(String name, { - List? children = null, - List? arguments = null, - Map? properties = null, - String? type = null - }) { - this.name = name; + /// Construct a new KDL node + KdlNode(this.name, + {List? children, + List? arguments, + Map? properties, + this.type}) { this.children = children ?? []; this.arguments = arguments ?? []; this.properties = properties ?? {}; - this.type = type; + } + + /// Returns true if this node has at least one child node + bool get hasChildren { + return children.isNotEmpty; + } + + /// Request a child node by key. If key is an `int`, return the node by index. + /// If the key is a `String`, return the first node with that name + KdlNode child(key) { + if (key is int) { + return children[key]; + } else if (key is String) { + return children.firstWhere((node) => node.name == key); + } else { + throw ArgumentError("node can only be indexed with Int/String"); + } + } + + /// Return the value of the first arg of the requested child node + arg(key) { + return child(key).arguments.first.value; + } + + /// Return the argument values of the requested child node + args(key) { + return child(key).arguments.map((arg) => arg.value); + } + + /// Return the first argument value of each node named '-' that is a + /// child of the requested child node + dashVals(key) { + return child(key) + .children + .where((node) => node.name == "-") + .map((node) => node.arguments.first) + .map((arg) => arg.value); + } + + /// If `key` is an `int`, return the value of the appropriate positional + /// argument. If `key` is a `String`, return the value of the matching + /// property + operator [](key) { + if (key is int) { + return arguments[key].value; + } else if (key is String) { + return properties[key]?.value; + } else { + throw ArgumentError("node can only be indexed with Int/String"); + } } @override - bool operator ==(other) => other is KdlNode - && name == other.name - && _sameNodes(this.children, other.children) - && _sameArguments(other.arguments) - && _sameProperties(other.properties); + bool operator ==(other) => + other is KdlNode && + name == other.name && + _sameNodes(children, other.children) && + _sameArguments(other.arguments) && + _sameProperties(other.properties); @override int get hashCode => [children, arguments, properties].hashCode; + @override + Iterator get iterator => children.iterator; + @override String toString() { return _toStringWithIndentation(0); } - KdlNode asType(String type, [Function? parser]) { + /// Sets the type of a node. If a `parser` is provided, calls the function + /// with the node and type and returns the result if any, otherwise returns + /// `this` + KdlNode asType(String type, [KdlTypeParser? parser]) { if (parser == null) { this.type = type; return this; @@ -73,11 +233,7 @@ class KdlNode { var result = parser(this, type); - if (result == null) return this.asType(type); - - if (!(result is KdlNode)) { - throw "expected parser to return an instance of KdlNode, got ${result.runtimeType}"; - } + if (result == null) return asType(type); return result; } @@ -85,16 +241,19 @@ class KdlNode { String _toStringWithIndentation(int indentation) { String indent = " " * indentation; String typeStr = type != null ? "(${_idToString(type!)})" : ""; - String s = "${indent}${typeStr}${_idToString(name)}"; + String s = "$indent$typeStr${_idToString(name)}"; if (arguments.isNotEmpty) { s += " ${arguments.map((a) => a.toString()).join(' ')}"; } if (properties.isNotEmpty) { - s += " ${properties.entries.map((e) => "${_idToString(e.key)}=${e.value}").join(' ')}"; + s += + " ${properties.entries.map((e) => "${_idToString(e.key)}=${e.value}").join(' ')}"; } if (children.isNotEmpty) { - var childrenStr = children.map((e) => e._toStringWithIndentation(indentation + 1)).join("\n"); - s += " {\n${childrenStr}\n${indent}}"; + var childrenStr = children + .map((e) => e._toStringWithIndentation(indentation + 1)) + .join("\n"); + s += " {\n$childrenStr\n$indent}"; } return s; } @@ -112,30 +271,52 @@ class KdlNode { _sameProperties(Map otherProps) { if (properties.length != otherProps.length) return false; - return properties.entries.every((element) => otherProps[element.key] == element.value); + return properties.entries + .every((element) => otherProps[element.key] == element.value); } _idToString(String id) { - return StringDumper(id).stringifyIdentifier(); + return _StringDumper(id).dump(); } } +/// Base class for all KDL Value types abstract class KdlValue { + /// Internally stored value late T value; - String? type = null; - KdlValue(this.value, [this.type]) { - this.value = value; - this.type = type; - } + /// Optional type annotation + String? type; + + /// Construct a new KDL Value with a given internal value and optional type + KdlValue(this.value, [this.type]); + /// Create the appropriate KDL Value from the given native value static KdlValue from(v, [String? type]) { if (v is String) return KdlString(v, type); if (v is int) return KdlInt(v, type); - if (v is BigDecimal) return KdlFloat(v, type); + if (v is double) return KdlDouble(v, type); + if (v is BigDecimal) return KdlBigDecimal(v, type); if (v is bool) return KdlBool(v, type); - if (v == null) return KdlNull(type); - throw "No KDL value for ${v}"; + if (v == null) { + if (type == null) { + return KdlNull(); + } else { + return KdlNull.withType(type); + } + } + + throw "No KDL value for $v"; + } + + @override + int get hashCode => value.hashCode; + + @override + bool operator ==(other) { + if (other is KdlValue) return value == other.value; + + return value == other; } @override @@ -143,11 +324,14 @@ abstract class KdlValue { if (type == null) { return _stringifyValue(); } else { - return "(${StringDumper(type!).stringifyIdentifier()})${_stringifyValue()}"; + return "(${_StringDumper(type!).dump()})${_stringifyValue()}"; } } - asType(String type, [Function? parser]) { + /// Sets the type of a value. If a `parser` is provided, calls the function + /// with the value and type and returns the result if any, otherwise returns + /// `this` + KdlValue asType(String type, [KdlTypeParser? parser]) { if (parser == null) { this.type = type; return this; @@ -156,10 +340,6 @@ abstract class KdlValue { var result = parser(this, type); if (result == null) return asType(type); - if (!(result is KdlValue)) { - throw "expected parser to return an instance of KdlValue, got ${result.runtimeType}"; - } - return result; } @@ -168,64 +348,110 @@ abstract class KdlValue { } } +/// KDL Value wrapping a `String` class KdlString extends KdlValue { - KdlString(String value, [String? type]) : super(value, type); + /// Construct a new KDL String + KdlString(super.value, [super.type]); + + @override + String _stringifyValue() { + return _StringDumper(value).dump(); + } +} + +/// KDL Value wrapping a `BigDecimal` +class KdlBigDecimal extends KdlValue { + /// Construct a new KDL BigDecimal + KdlBigDecimal(super.value, [super.type]); + + /// Construct a new KDL BigDecimal using a native `num` + KdlBigDecimal.from(num value, [String? type]) + : super(BigDecimal.parse(value.toString()), type); @override - bool operator ==(other) => other is KdlString && this.value == other.value; + bool operator ==(other) { + if (other is KdlBigDecimal) return value == other.value; + return value == other; + } @override int get hashCode => value.hashCode; @override String _stringifyValue() { - return StringDumper(value).dump(); + return value.toString().toUpperCase(); } } -class KdlFloat extends KdlValue { - KdlFloat(BigDecimal value, [String? type]) : super(value, type); - KdlFloat.from(num value, [String? type]) : super(BigDecimal.parse(value.toString()), type); +/// KDL Value wrapping a `double` +class KdlDouble extends KdlValue { + /// Construct a new KDL Double + KdlDouble(super.value, [super.type]); @override - bool operator ==(other) => other is KdlFloat && this.value == other.value; + bool operator ==(other) { + var otherValue = other; + if (other is KdlValue) otherValue = other.value; + + if (value.isNaN && otherValue is double && otherValue.isNaN) return true; + return value == otherValue; + } @override int get hashCode => value.hashCode; @override String _stringifyValue() { + if (value.isNaN) return '#nan'; + if (value == double.infinity) return '#inf'; + if (value == -double.infinity) return '#-inf'; + return value.toString().toUpperCase(); } } +/// KDL Value wrapping integer types class KdlInt extends KdlValue { - KdlInt(I value, [String? type]) : super(value, type); + /// Construct a new KDL Int + KdlInt(super.value, [super.type]); @override - bool operator ==(other) => other is KdlInt && this.value == other.value; + bool operator ==(other) => other is KdlInt && value == other.value; @override int get hashCode => value.hashCode; } +/// KDL Value wrapping a `bool` class KdlBool extends KdlValue { - KdlBool(bool value, [String? type]) : super(value, type); + /// Construct a new KDL Bool + KdlBool(super.value, [super.type]); @override - bool operator ==(other) => other is KdlBool && this.value == other.value; + bool operator ==(other) => other is KdlBool && value == other.value; @override int get hashCode => value.hashCode; @override String _stringifyValue() { - return value ? 'true' : 'false'; + return value ? '#true' : '#false'; } } +/// KDL Value representing `null` class KdlNull extends KdlValue { - KdlNull([String? type]) : super(null, type); + static final KdlNull _singleton = KdlNull._internal(); + + /// Return the untyped singleton KDL Null + factory KdlNull() { + return _singleton; + } + + KdlNull._internal() : super(null, null); + + /// Construct a new KDL Null with a type annotation + KdlNull.withType(String type) : super(null, type); @override bool operator ==(other) => other is KdlNull; @@ -235,6 +461,85 @@ class KdlNull extends KdlValue { @override String _stringifyValue() { - return 'null'; + return '#null'; + } + + /// Returns a new KDL Null with the given type. If a parser is passed in, + /// calls it with the current value and returns the result if any, otherwise + /// falls back to creating a new Null with the given type annotation. + @override + KdlValue asType(String type, [KdlTypeParser? parser]) { + if (parser == null) { + return KdlNull.withType(type); + } + + var result = parser(this, type); + if (result == null) return asType(type); + + return result; + } +} + +class _StringDumper { + final String _string; + + _StringDumper(this._string); + + String dump() { + if (_isBareIdentifier()) return _string; + + return "\"${_string.runes.map(_escape).join('')}\""; + } + + String _escape(int rune) { + switch (rune) { + case 10: + return "\\n"; + case 13: + return "\\r"; + case 9: + return "\\t"; + case 92: + return "\\\\"; + case 34: + return "\\\""; + case 8: + return "\\b"; + case 12: + return "\\f"; + default: + return String.fromCharCode(rune); + } + } + + static final forbidden = [ + ...KdlTokenizer.symbols.keys.map((e) => e.runes.single), + ...KdlTokenizer.whitespace.map((e) => e.runes.single), + ...KdlTokenizer.newlines.map((e) => e.runes.single), + ..."()[]/\\\"#".runes, + ...List.generate(0x20, (e) => e), + ]; + + bool _isBareIdentifier() { + if ([ + '', + 'true', + 'false', + 'null', + 'inf', + '-inf', + 'nan', + '#true', + '#false', + '#null', + '#inf', + '#-inf', + '#nan' + ].contains(_string) || + RegExp(r"^\.?\d").hasMatch(_string)) { + return false; + } + + return !_string.runes.any((c) => forbidden.contains(c)); } } diff --git a/lib/src/exception.dart b/lib/src/exception.dart new file mode 100644 index 0000000..1cf7ade --- /dev/null +++ b/lib/src/exception.dart @@ -0,0 +1,53 @@ +/// Generic KDL Exception +class KdlException implements Exception { + /// Exception message + String message; + + /// Construct a new KDL Exception with the given message + KdlException(this.message); + + @override + String toString() => "KdlException: $message"; +} + +/// Exception thrown when attempting to parse a versioned KDL document with the +/// incorrect parser version +class KdlVersionMismatchException extends KdlException { + /// The version of the document + int version; + + /// The version of the parser + int parserVersion; + + /// Construct a new KDL Version Mismatch Exception for the given document and + /// parser versions + KdlVersionMismatchException(this.version, this.parserVersion) + : super("Version mismatch, document specified v$version, " + "but this is a v$parserVersion parser"); +} + +/// Exception thrown when attempting to parse an invalid KDL document +class KdlParseException extends KdlException { + /// The line number where the parse error happened + int? line; + + /// The column where the parse error happened + int? column; + + /// Construct a new KDL Parse Exception with the given message, and optionally + /// the line and column numbers where the error happened + KdlParseException(super.message, [this.line, this.column]); + + @override + String toString() { + String report = "KdlParseException: $message"; + if (line != null) { + var location = line.toString(); + if (column != null) { + location += ":$column"; + } + report += " ($location)"; + } + return report; + } +} diff --git a/lib/src/parser.dart b/lib/src/parser.dart index c1c1fa6..0e6dadb 100644 --- a/lib/src/parser.dart +++ b/lib/src/parser.dart @@ -1,226 +1,431 @@ import 'package:kdl/src/document.dart'; import 'package:kdl/src/tokenizer.dart'; import 'package:kdl/src/types.dart'; +import 'package:kdl/src/exception.dart'; +/// KDL 2.0.0 Parser class KdlParser { - late KdlTokenizer tokenizer; - late Map typeParsers; + /// Default KDLValue types for well-known types + static const Map> defaultValueTypes = { + 'date-time': KdlDateTime.convert, + 'time': KdlTime.convert, + 'date': KdlDate.convert, + 'duration': KdlDuration.convert, + 'decimal': KdlDecimal.convert, + 'currency': KdlCurrency.convert, + 'country-2': KdlCountry2.convert, + 'country-3': KdlCountry3.convert, + 'country-subdivision': KdlCountrySubdivision.convert, + 'email': KdlEmail.convert, + 'idn-email': KdlIdnEmail.convert, + 'hostname': KdlHostname.convert, + 'idn-hostname': KdlIdnHostname.convert, + 'ipv4': KdlIPV4.convert, + 'ipv6': KdlIPV6.convert, + 'url': KdlUrl.convert, + 'url-reference': KdlUrlReference.convert, + 'irl': KdlIRL.convert, + 'irl-reference': KdlIrlReference.convert, + 'url-template': KdlUrlTemplate.convert, + 'uuid': KdlUuid.convert, + 'regex': KdlRegex.convert, + 'base64': KdlBase64.convert, + }; - parse(String string, { Map typeParsers = const {}, bool parseTypes = true }) { - this.tokenizer = KdlTokenizer(string); + late KdlTokenizer _tokenizer; + Map> _valueTypes = {}; + Map> _nodeTypes = {}; + int _depth = 0; + int get _parserVersion => 2; + + /// Parse a string into a KdlDocument + /// optionally passing in custom `valueTypes` and `nodeTypes` + /// or turning off type parsing with `parseTypes: false` + KdlDocument parse(String string, + {Map> valueTypes = const {}, + Map> nodeTypes = const {}, + bool parseTypes = true}) { + _tokenizer = _createTokenizer(string); + _checkVersion(); if (parseTypes) { - this.typeParsers = { ...KdlTypes.MAPPING, ...typeParsers }; - } else { - this.typeParsers = {}; + _valueTypes = {...defaultValueTypes, ...valueTypes}; + _nodeTypes = nodeTypes; } return _document(); } - _document() { - var nodes = _nodes(); - _eatLinespaces(); + KdlTokenizer _createTokenizer(String string) { + return KdlTokenizer(string); + } + + void _checkVersion() { + var docVersion = _tokenizer.versionDirective(); + if (docVersion == null) return; + if (docVersion != _parserVersion) { + throw KdlVersionMismatchException(docVersion, _parserVersion); + } + } + + KdlDocument _document() { + var nodes = _nodeList(); + _linespaceStar(); _expectEndOfFile(); return KdlDocument(nodes); } - _nodes() { + List _nodeList() { List nodes = []; - var n; - while ((n = _node()) != false) { - if (n != null) nodes.add(n); + KdlNode? node; + while (((node, _) = _node()).$2 != false) { + if (node != null) nodes.add(node); } return nodes; } - _node() { - _eatLinespaces(); + (KdlNode?, bool) _node() { + _linespaceStar(); var commented = false; - if (tokenizer.peekToken()[0] == KdlToken.SLASHDASH) { - tokenizer.nextToken(); - _eatWhitespace(); + if (_tokenizer.peekToken().type == KdlTerm.slashdash) { + _slashdash(); commented = true; } - var node, type; + KdlNode node; + String? ty; try { - type = _type(); + ty = _type(); node = KdlNode(_identifier()); } catch (error) { - if (type != null) throw error; - return false; + if (ty != null) rethrow; + return (null, false); } _argsPropsChildren(node); - if (commented) return null; + if (commented) return (null, true); - if (type != null) { - return node.asType(type, typeParsers[type]); + if (ty != null) { + return (node.asType(ty, _nodeTypes[ty]), true); } - return node; + return (node, true); } - _identifier() { - var t = tokenizer.peekToken(); - if (t[0] == KdlToken.IDENT || t[0] == KdlToken.STRING || t[0] == KdlToken.RAWSTRING) { - tokenizer.nextToken(); - return t[1]; + String _identifier() { + var t = _tokenizer.peekToken(); + if (t.type == KdlTerm.ident || + t.type == KdlTerm.string || + t.type == KdlTerm.rawstring) { + _tokenizer.nextToken(); + return t.value; } - throw "Expected identifier, got ${t[0]}"; + throw _ex("Expected identifier, got ${t.type}", t); } - _eatWhitespace() { - var t = tokenizer.peekToken(); - while (t[0] == KdlToken.WS || t[0] == KdlToken.ESCLINE) { - tokenizer.nextToken(); - t = tokenizer.peekToken(); + void _wsStar() { + var t = _tokenizer.peekToken(); + while (t.type == KdlTerm.whitespace) { + _tokenizer.nextToken(); + t = _tokenizer.peekToken(); } } - _eatLinespaces() { - while (_isLinespace(tokenizer.peekToken())) { - tokenizer.nextToken(); + void _linespaceStar() { + while (_isLinespace(_tokenizer.peekToken())) { + _tokenizer.nextToken(); } } - _isLinespace(t) { - return (t[0] == KdlToken.NEWLINE || t[0] == KdlToken.WS); + bool _isLinespace(KdlToken t) { + return (t.type == KdlTerm.newline || t.type == KdlTerm.whitespace); } - _argsPropsChildren(KdlNode node) { + void _argsPropsChildren(KdlNode node) { var commented = false; + var hasChildren = false; while (true) { - _eatWhitespace(); - switch (tokenizer.peekToken()[0]) { - case KdlToken.IDENT: - var p = _prop(); - if (!commented) { - node.properties[p[0]] = p[1]; - } - commented = false; - break; - case KdlToken.LBRACE: - var children = _children(); - if (!commented) { - node.children = children; - } - _expectNodeTerm(); - return; - case KdlToken.SLASHDASH: - commented = true; - tokenizer.nextToken(); - _eatWhitespace(); - break; - case KdlToken.NEWLINE: - case KdlToken.EOF: - case KdlToken.SEMICOLON: - tokenizer.nextToken(); - return; - case KdlToken.STRING: - var t = tokenizer.peekTokenAfterNext(); - if (t[0] == KdlToken.EQUALS) { - var p = _prop(); - if (!commented) { - node.properties[p[0]] = p[1]; + var peek = _tokenizer.peekToken(); + switch (peek.type) { + case KdlTerm.whitespace: + _wsStar(); + peek = _tokenizer.peekToken(); + if (peek.type == KdlTerm.slashdash) { + _slashdash(); + peek = _tokenizer.peekToken(); + commented = true; } - } else { - var v = _value(); - if (!commented) { - node.arguments.add(v); + switch (peek.type) { + case KdlTerm.string: + case KdlTerm.ident: + if (hasChildren) throw _ex("Unexpected ${peek.type}", peek); + var t = _tokenizer.peekTokenAfterNext(); + if (t.type == KdlTerm.equals) { + var p = _prop(); + if (!commented) node.properties[p.$1] = p.$2; + } else { + var v = _value(); + if (!commented) node.arguments.add(v); + } + commented = false; + break; + case KdlTerm.newline: + case KdlTerm.eof: + case KdlTerm.semicolon: + _tokenizer.nextToken(); + return; + case KdlTerm.lbrace: + _lbrace(node, commented); + hasChildren = true; + commented = false; + break; + case KdlTerm.rbrace: + _rbrace(); + return; + default: + var v = _value(); + if (hasChildren) throw _ex("Unexpected ${peek.type}", peek); + if (!commented) node.arguments.add(v); + commented = false; + break; } - } - commented = false; - break; - default: - var v = _value(); - if (!commented) { - node.arguments.add(v); - } - commented = false; - break; + break; + case KdlTerm.eof: + case KdlTerm.semicolon: + case KdlTerm.newline: + _tokenizer.nextToken(); + return; + case KdlTerm.lbrace: + _lbrace(node, commented); + hasChildren = true; + commented = false; + break; + case KdlTerm.rbrace: + _rbrace(); + return; + default: + throw _ex("Unexpected ${peek.type}", peek); } } } - _prop() { + void _lbrace(KdlNode node, bool commented) { + if (!commented && node.hasChildren) throw _ex("Unexpected {"); + _depth += 1; + var children = _children(); + _depth -= 1; + if (!commented) { + node.children = children; + } + } + + void _rbrace() { + if (_depth == 0) throw "Unexpected }"; + } + + (String, KdlValue) _prop() { var name = _identifier(); - _expect(KdlToken.EQUALS); - var value = _value(); - return [name, value]; + _expect(KdlTerm.equals); + var val = _value(); + return (name, val); } - _children() { - _expect(KdlToken.LBRACE); - var nodes = _nodes(); - _eatLinespaces(); - _expect(KdlToken.RBRACE); + List _children() { + _expect(KdlTerm.lbrace); + var nodes = _nodeList(); + _linespaceStar(); + _expect(KdlTerm.rbrace); return nodes; } - _value() { - var type = _type(); - var t = tokenizer.nextToken(); + KdlValue _value() { + var ty = _type(); + var t = _tokenizer.nextToken(); var v = _valueWithoutType(t); - if (type == null) { + if (ty == null) { return v; } else { - return v.asType(type, typeParsers[type]); - } - } - - _valueWithoutType(List t) { - switch (t[0]) { - case KdlToken.STRING: - case KdlToken.RAWSTRING: - return KdlString(t[1]); - case KdlToken.INTEGER: - return KdlInt(t[1]); - case KdlToken.FLOAT: - return KdlFloat(t[1]); - case KdlToken.TRUE: - case KdlToken.FALSE: - return KdlBool(t[1]); - case KdlToken.NULL: + return v.asType(ty, _valueTypes[ty]); + } + } + + KdlValue _valueWithoutType(KdlToken t) { + switch (t.type) { + case KdlTerm.ident: + case KdlTerm.string: + case KdlTerm.rawstring: + return KdlString(t.value); + case KdlTerm.integer: + return KdlInt(t.value); + case KdlTerm.decimal: + return KdlBigDecimal(t.value); + case KdlTerm.double: + return KdlDouble(t.value); + case KdlTerm.trueKeyword: + case KdlTerm.falseKeyword: + return KdlBool(t.value); + case KdlTerm.nullKeyword: return KdlNull(); default: - throw "Expected value, got ${t[0]}"; + throw _ex("Expected value, got ${t.type}", t); } } - _type() { - if (tokenizer.peekToken()[0] != KdlToken.LPAREN) return null; - _expect(KdlToken.LPAREN); - var type = _identifier(); - _expect(KdlToken.RPAREN); - return type; + String? _type() { + if (_tokenizer.peekToken().type != KdlTerm.lparen) return null; + _expect(KdlTerm.lparen); + _wsStar(); + var ty = _identifier(); + _wsStar(); + _expect(KdlTerm.rparen); + _wsStar(); + return ty; } - _expect(KdlToken type) { - var t = tokenizer.peekToken()[0]; - if (t == type) { - return tokenizer.nextToken()[1]; - } else { - throw "Expected ${type}, got ${t}"; + void _slashdash() { + var t = _tokenizer.nextToken(); + if (t.type != KdlTerm.slashdash) { + throw _ex("Expected SLASHDASH, found ${t.type}", t); + } + _linespaceStar(); + var peek = _tokenizer.peekToken(); + switch (peek.type) { + case KdlTerm.rbrace: + case KdlTerm.eof: + case KdlTerm.semicolon: + throw _ex("Unexpected ${peek.type} after SLASHDASH", peek); + default: + break; } } - _expectNodeTerm() { - _eatWhitespace(); - var t = tokenizer.peekToken()[0]; - if (t == KdlToken.NEWLINE || t == KdlToken.SEMICOLON || t == KdlToken.EOF) { - tokenizer.nextToken(); + _expect(KdlTerm type) { + var t = _tokenizer.peekToken(); + if (t.type == type) { + return _tokenizer.nextToken().value; } else { - throw "Unexpected ${t}"; + throw _ex("Expected $type, got ${t.type}", t); + } + } + + void _expectEndOfFile() { + var t = _tokenizer.peekToken(); + if (t.type == KdlTerm.eof) return; + + throw _ex("Expected EOF, got ${t.type}", t); + } + + KdlParseException _ex(String message, [KdlToken? token]) { + token = token ?? _tokenizer.peekToken(); + return KdlParseException(message, token.line, token.column); + } +} + +/// KDL 1.0 Parser +class KdlV1Parser extends KdlParser { + @override + int get _parserVersion => 1; + + @override + KdlTokenizer _createTokenizer(String string) { + return KdlV1Tokenizer(string); + } + + @override + void _argsPropsChildren(KdlNode node) { + var commented = false; + while (true) { + _wsStar(); + switch (_tokenizer.peekToken().type) { + case KdlTerm.ident: + var p = _prop(); + if (!commented) { + node.properties[p.$1] = p.$2; + } + commented = false; + break; + case KdlTerm.lbrace: + var childNodes = _children(); + if (!commented) { + node.children = childNodes; + } + _expectNodeTerm(); + return; + case KdlTerm.slashdash: + commented = true; + _tokenizer.nextToken(); + _wsStar(); + break; + case KdlTerm.newline: + case KdlTerm.eof: + case KdlTerm.semicolon: + _tokenizer.nextToken(); + return; + case KdlTerm.string: + var t = _tokenizer.peekTokenAfterNext(); + if (t.type == KdlTerm.equals) { + var p = _prop(); + if (!commented) { + node.properties[p.$1] = p.$2; + } + } else { + var v = _value(); + if (!commented) { + node.arguments.add(v); + } + } + commented = false; + break; + default: + var v = _value(); + if (!commented) { + node.arguments.add(v); + } + commented = false; + break; + } + } + } + + @override + KdlValue _valueWithoutType(KdlToken t) { + switch (t.type) { + case KdlTerm.string: + case KdlTerm.rawstring: + return KdlString(t.value); + case KdlTerm.integer: + return KdlInt(t.value); + case KdlTerm.decimal: + return KdlBigDecimal(t.value); + case KdlTerm.double: + return KdlDouble(t.value); + case KdlTerm.trueKeyword: + case KdlTerm.falseKeyword: + return KdlBool(t.value); + case KdlTerm.nullKeyword: + return KdlNull(); + default: + throw _ex("Expected value, got ${t.type}", t); } } - _expectEndOfFile() { - var t = tokenizer.peekToken()[0]; - if (t == KdlToken.EOF || t == false) return; + @override + String? _type() { + if (_tokenizer.peekToken().type != KdlTerm.lparen) return null; + _expect(KdlTerm.lparen); + var ty = _identifier(); + _expect(KdlTerm.rparen); + return ty; + } - throw "Expected EOF, got ${t}"; + _expectNodeTerm() { + _wsStar(); + var t = _tokenizer.peekToken().type; + if (t == KdlTerm.newline || t == KdlTerm.semicolon || t == KdlTerm.eof) { + _tokenizer.nextToken(); + } else { + throw "Unexpected $t"; + } } } diff --git a/lib/src/string_dumper.dart b/lib/src/string_dumper.dart deleted file mode 100644 index bb6fd99..0000000 --- a/lib/src/string_dumper.dart +++ /dev/null @@ -1,39 +0,0 @@ -class StringDumper { - String string = ''; - - StringDumper(String string) { - this.string = string; - } - - String dump() { - return "\"${string.runes.map(_escape).join('')}\""; - } - - String stringifyIdentifier() { - if (_isBareIdentifier()) { - return string; - } else { - return dump(); - } - } - - String _escape(int rune) { - switch (rune) { - case 10: return "\\n"; - case 13: return "\\r"; - case 9: return "\\t"; - case 92: return "\\\\"; - case 34: return "\\\""; - case 8: return "\\b"; - case 12: return "\\f"; - default: return String.fromCharCodes([rune]); - } - } - - static final ESCAPE_CHARS = RegExp.escape("\\/(){}<>;[]=,\""); - static final BARE_ID_RGX = RegExp("^([^0-9\\-+\\s${ESCAPE_CHARS}][^\\s${ESCAPE_CHARS}]*|" + - "[\\-+](?!true|false|null)[^0-9\\s${ESCAPE_CHARS}][^\\s${ESCAPE_CHARS}]*)\$"); - bool _isBareIdentifier() { - return BARE_ID_RGX.hasMatch(string); - } -} diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart index 54a8f0d..7509986 100644 --- a/lib/src/tokenizer.dart +++ b/lib/src/tokenizer.dart @@ -1,10 +1,14 @@ import 'dart:collection'; import 'package:big_decimal/big_decimal.dart'; +import 'package:kdl/src/exception.dart'; -enum KdlTokenizerContext { +enum _KdlTokenizerContext { ident, + keyword, string, rawstring, + multiLineString, + multiLineRawstring, binary, octal, hexadecimal, @@ -12,459 +16,1323 @@ enum KdlTokenizerContext { singleLineComment, multiLineComment, whitespace, + equals, } -enum KdlToken { - IDENT, - STRING, - RAWSTRING, - INTEGER, - FLOAT, - TRUE, - FALSE, - NULL, - WS, - NEWLINE, - LBRACE, - RBRACE, - LPAREN, - RPAREN, - EQUALS, - SEMICOLON, - EOF, - SLASHDASH, - ESCLINE, +/// KDL Token types, aka Terminals +enum KdlTerm { + /// Identifier + ident, + + /// String + string, + + /// Rawstring + rawstring, + + /// Integer + integer, + + /// Floating-point literal + decimal, + + /// Floating-point special values, e.g. `#inf` + double, + + /// #true + trueKeyword, + + /// #false + falseKeyword, + + /// #null + nullKeyword, + + /// Whitespace + whitespace, + + /// Newline + newline, + + /// Left brace `{` + lbrace, + + /// Right brace `}` + rbrace, + + /// Left parenthesis `(` + lparen, + + /// Right parenthesis `)` + rparen, + + /// Equals `=` + equals, + + /// Semi-colon `;` + semicolon, + + /// End-of-file + eof, + + /// Slashdash `/-` + slashdash, +} + +List _charRange(int from, int to) => + List.generate(to - from + 1, (i) => String.fromCharCode(i + from)); + +String _debom(String str) { + if (str.startsWith("\uFEFF")) { + return str.substring(1); + } + + return str; } +/// Represents a terminal token +class KdlToken { + /// The type of the token + KdlTerm type; + + /// The parsed value resulting from the token + dynamic value; + + /// Starting line number where the token was found + int? line; + + /// Starting column number where the token was found + int? column; + + /// Construct a new token + KdlToken(this.type, this.value, [this.line, this.column]); + @override + bool operator ==(other) { + if (other is KdlToken) { + return type == other.type && + value == other.value && + (line == null || other.line == null || line == other.line) && + (column == null || other.column == null || column == other.column); + } + return false; + } + + @override + String toString() { + return "KdlToken($type, ${value.toString()}, $line, $column)"; + } + + @override + int get hashCode => [type, value, line, column].hashCode; +} + +/// Turns strings into a list of tokens class KdlTokenizer { - static const SYMBOLS = { - '(': KdlToken.LPAREN, - ')': KdlToken.RPAREN, - '{': KdlToken.LBRACE, - '}': KdlToken.RBRACE, - '=': KdlToken.EQUALS, - '=': KdlToken.EQUALS, - ';': KdlToken.SEMICOLON + /// Symbol characters + static final symbols = { + '{': KdlTerm.lbrace, + '}': KdlTerm.rbrace, + ';': KdlTerm.semicolon, + '=': KdlTerm.equals }; - static const WHITESPACE = [ - "\u0009", "\u0020", "\u00A0", "\u1680", - "\u2000", "\u2001", "\u2002", "\u2003", - "\u2004", "\u2005", "\u2006", "\u2007", - "\u2008", "\u2009", "\u200A", "\u202F", - "\u205F", "\u3000" + /// Whitespace characters + static const whitespace = [ + "\u0009", + "\u0020", + "\u00A0", + "\u1680", + "\u2000", + "\u2001", + "\u2002", + "\u2003", + "\u2004", + "\u2005", + "\u2006", + "\u2007", + "\u2008", + "\u2009", + "\u200A", + "\u202F", + "\u205F", + "\u3000" ]; + static final _ws = "[${RegExp.escape(whitespace.join())}]"; + static final _wsStar = RegExp("^$_ws*\$"); - static const NEWLINES = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]; + /// Newline characters + static const newlines = [ + "\u000A", + "\u0085", + "\u000B", + "\u000C", + "\u2028", + "\u2029" + ]; + static final _newlinesPattern = + RegExp("${newlines.map(RegExp.escape).join('|')}|\\r\\n?"); - static final NON_IDENTIFIER_CHARS = [ + static final _nonIdentifierChars = [ null, - ...WHITESPACE, - ...NEWLINES, - ...SYMBOLS.keys, - "\r", "\\", "<", ">", "[", "]", '"', ",", "/", - List.generate(0x20, (index) => String.fromCharCode(index))]; - static final NON_INITIAL_IDENTIFIER_CHARS = [ - ...NON_IDENTIFIER_CHARS, - List.generate(10, (index) => index.toString()) + ...whitespace, + ...newlines, + ...symbols.keys, + "\r", + "\\", + "[", + "]", + "(", + ")", + '"', + "/", + "#", + ..._charRange(0x0000, 0x0020), + ]; + static final _nonInitialIdentifierChars = [ + ..._nonIdentifierChars, + ...List.generate(10, (index) => index.toString()), + ]; + + static final _forbidden = [ + ..._charRange(0x0000, 0x0008), + ..._charRange(0x000E, 0x001F), + "\u007F", + ..._charRange(0x200E, 0x200F), + ..._charRange(0x202A, 0x202E), + ..._charRange(0x2066, 0x2069), + "\uFEFF" ]; - String str = ''; - KdlTokenizerContext? context = null; - int rawstringHashes = -1; - int index = 0; - String buffer = ""; - bool done = false; - KdlTokenizerContext? previousContext = null; - int commentNesting = 0; - Queue peekedTokens = Queue(); - bool inType = false; - KdlToken? lastToken = null; + String _str = ''; + _KdlTokenizerContext? _ctx; + int _rawstringHashes = -1; + int _index = 0; + final int _start; + String _buffer = ""; + bool _done = false; + _KdlTokenizerContext? _previousContext; + int _commentNesting = 0; + final Queue _peekedTokens = Queue(); + bool _inType = false; + KdlToken? _lastToken; + int _line = 1; + int _column = 1; + int _lineAtStart = 1; + int _columnAtStart = 1; - KdlTokenizer(String str, { int start = 0 }) { - this.str = str; - this.index = start; + /// Create a new KDL Tokenizer + KdlTokenizer(String str, {int start = 0}) : _start = start { + _str = _debom(str); + _index = _start; } - _setContext(KdlTokenizerContext? ctx) { - this.previousContext = this.context; - this.context = ctx; + static final _versionPattern = + RegExp("\\/-$_ws*kdl-version$_ws+(\\d+)$_ws*${_newlinesPattern.pattern}"); + + /// Reads the version of the document if there is one, e.g. `/- kdl-version 2` + versionDirective() { + var match = _versionPattern.matchAsPrefix(_str); + if (match == null) return null; + var m = match.group(1); + if (m == null) return null; + return int.parse(m); } - peekToken() { - if (this.peekedTokens.isEmpty) { - this.peekedTokens.add(_nextToken()); + /// Consumes the entire string and returns all tokens + allTokens() { + List a = []; + while (!_done) { + a.add(nextToken()); } - return this.peekedTokens.first; + return a; + } + + set _context(_KdlTokenizerContext? ctx) { + _previousContext = _ctx; + _ctx = ctx; } - peekTokenAfterNext() { - if (this.peekedTokens.isEmpty) { - this.peekedTokens.add(_nextToken()); - this.peekedTokens.add(_nextToken()); - } else if (this.peekedTokens.length == 1) { - this.peekedTokens.add(_nextToken()); + _KdlTokenizerContext? get _context => _ctx; + + /// Read the next token without consuming it + KdlToken peekToken() { + if (_peekedTokens.isEmpty) { + _peekedTokens.add(_readToken()); } - return this.peekedTokens.elementAt(1); + return _peekedTokens.first; } - nextToken() { - if (this.peekedTokens.isNotEmpty) { - return this.peekedTokens.removeFirst(); - } else { - return _nextToken(); - } + /// Read two tokens ahead without consuming + KdlToken peekTokenAfterNext() { + if (_peekedTokens.isEmpty) { + _peekedTokens.add(_readToken()); + _peekedTokens.add(_readToken()); + } else if (_peekedTokens.length == 1) { + _peekedTokens.add(_readToken()); + } + return _peekedTokens.elementAt(1); } - _nextToken() { - var token = this._readNextToken(); - if (token[0] != false) lastToken = token[0]; - return token; + /// Consume the next token and return it + KdlToken nextToken() { + if (_peekedTokens.isNotEmpty) { + return _peekedTokens.removeFirst(); + } else { + return _readToken(); + } } - _readNextToken() { - this.context = null; - this.previousContext = null; + KdlToken _readToken() { + _context = null; + _previousContext = null; + _lineAtStart = _line; + _columnAtStart = _column; while (true) { - var c = _charAt(this.index); - if (this.context == null) { - if (c == '"') { - _setContext(KdlTokenizerContext.string); - this.buffer = ''; - this.index += 1; - } else if (c == 'r') { - if (_charAt(this.index + 1) == '"') { - _setContext(KdlTokenizerContext.rawstring); - this.index += 2; - this.rawstringHashes = 0; - this.buffer = ''; - continue; - } else if (_charAt(this.index + 1) == '#') { - var i = this.index + 1; - this.rawstringHashes = 0; - while (_charAt(i) == '#') { - this.rawstringHashes += 1; + var c = _char(_index); + if (_context == null) { + if (c == null) { + if (_done) { + return _token(KdlTerm.eof, null); + } + _done = true; + return _token(KdlTerm.eof, ''); + } else if (c == '"') { + if (_char(_index + 1) == '"' && _char(_index + 2) == '"') { + String nl = _expectNewline(_index + 3); + _context = _KdlTokenizerContext.multiLineString; + _buffer = ''; + _traverse(3 + nl.runes.length); + } else { + _context = _KdlTokenizerContext.string; + _buffer = ''; + _traverse(1); + } + } else if (c == '#') { + if (_char(_index + 1) == '"') { + if (_char(_index + 2) == '"' && _char(_index + 3) == '"') { + String nl = _expectNewline(_index + 4); + _context = _KdlTokenizerContext.multiLineRawstring; + _rawstringHashes = 1; + _buffer = ''; + _traverse(4 + nl.runes.length); + continue; + } else { + _context = _KdlTokenizerContext.rawstring; + _traverse(2); + _rawstringHashes = 1; + _buffer = ''; + continue; + } + } else if (_char(_index + 1) == '#') { + var i = _index + 1; + _rawstringHashes = 1; + while (_char(i) == '#') { + _rawstringHashes += 1; i += 1; } - if (_charAt(i) == '"') { - _setContext(KdlTokenizerContext.rawstring); - this.index = i + 1; - this.buffer = ''; - continue; + if (_char(i) == '"') { + if (_char(i + 1) == '"' && _char(i + 2) == '"') { + String nl = _expectNewline(i + 3); + _context = _KdlTokenizerContext.multiLineRawstring; + _buffer = ''; + _traverse(_rawstringHashes + 3 + nl.runes.length); + continue; + } else { + _context = _KdlTokenizerContext.rawstring; + _traverse(_rawstringHashes + 1); + _buffer = ''; + continue; + } } } - _setContext(KdlTokenizerContext.ident); - this.buffer = c; - this.index += 1; - } else if (c != null && RegExp(r"[0-9\-+]").hasMatch(c)) { - var n = _charAt(this.index + 1); - var n2 = _charAt(this.index + 2); + _context = _KdlTokenizerContext.keyword; + _buffer = c; + _traverse(1); + } else if (c == '-') { + var n = _char(_index + 1); + var n2 = _char(_index + 2); + if (n != null && RegExp(r"[0-9]").hasMatch(n)) { + if (n == '0' && n2 != null && RegExp(r"[box]").hasMatch(n2)) { + _context = _integerContext(n2); + _traverse(2); + } else { + _context = _KdlTokenizerContext.decimal; + } + } else { + _context = _KdlTokenizerContext.ident; + } + _buffer = c; + _traverse(1); + } else if (RegExp(r"[0-9+]").hasMatch(c)) { + var n = _char(_index + 1); + var n2 = _char(_index + 2); if (c == '0' && n != null && RegExp("[box]").hasMatch(n)) { - this.index += 2; - this.buffer = ''; - _setContext(_integerContext(n)); - } else if ((c == '-' || c == '+') && n == '0' && RegExp("[box]").hasMatch(n2)) { - this.index += 3; - this.buffer = c; - _setContext(_integerContext(n2)); + _buffer = ''; + _context = _integerContext(n); + _traverse(2); + } else if (c == '+' && n == '0' && RegExp("[box]").hasMatch(n2)) { + _buffer = c; + _context = _integerContext(n2); + _traverse(3); } else { - _setContext(KdlTokenizerContext.decimal); - this.index += 1; - this.buffer = c; + _buffer = c; + _context = _KdlTokenizerContext.decimal; + _traverse(1); } } else if (c == "\\") { - var t = KdlTokenizer(this.str, start: this.index + 1); + var t = KdlTokenizer(_str, start: _index + 1); var la = t.nextToken(); - if (la[0] == KdlToken.NEWLINE || la[0] == KdlToken.EOF) { - this.index = t.index; - return [KdlToken.ESCLINE, "\\${la[1]}"]; - } else if (la[0] == KdlToken.WS) { + if (la.type == KdlTerm.newline || la.type == KdlTerm.eof) { + _buffer = "$c${la.value}"; + _context = _KdlTokenizerContext.whitespace; + _traverseTo(t._index); + continue; + } else if (la.type == KdlTerm.whitespace) { var lan = t.nextToken(); - if (lan[0] == KdlToken.NEWLINE || lan[0] == KdlToken.EOF) { - this.index = t.index; - return [KdlToken.ESCLINE, "\\${la[1]}${lan[1]}"]; + if (lan.type == KdlTerm.newline || lan.type == KdlTerm.eof) { + _buffer = "$c${la.value}"; + if (lan.type == KdlTerm.newline) _buffer += "\n"; + _context = _KdlTokenizerContext.whitespace; + _traverseTo(t._index); + continue; } } - throw "Unexpected '\\'"; - } else if (SYMBOLS.containsKey(c)) { - if (c == '(') inType = true; - if (c == ')') inType = false; - this.index += 1; - return [SYMBOLS[c], c]; - } else if (c == "\r") { - var n = _charAt(this.index + 1); - if (n == "\n") { - this.index += 2; - return [KdlToken.NEWLINE, "${c}${n}"]; - } else { - this.index += 1; - return [KdlToken.NEWLINE, c]; - } - } else if (NEWLINES.contains(c)) { - this.index += 1; - return [KdlToken.NEWLINE, c]; + _fail("Unexpected '\\'"); + } else if (c == '=') { + _buffer = c; + _context = _KdlTokenizerContext.equals; + _traverse(1); + } else if (symbols.containsKey(c)) { + _traverse(1); + return KdlToken(symbols[c]!, c); + } else if (c == "\r" || newlines.contains(c)) { + String nl = _expectNewline(_index); + _traverse(nl.runes.length); + return _token(KdlTerm.newline, nl); } else if (c == "/") { - var n = _charAt(this.index + 1); + var n = _char(_index + 1); if (n == '/') { - if (inType || lastToken == KdlToken.RPAREN) throw "Unexpected '/'"; - _setContext(KdlTokenizerContext.singleLineComment); - this.index += 2; + if (_inType || _lastToken?.type == KdlTerm.rparen) { + _fail("Unexpected '/'"); + } + _context = _KdlTokenizerContext.singleLineComment; + _traverse(2); } else if (n == '*') { - if (inType || lastToken == KdlToken.RPAREN) throw "Unexpected '/'"; - _setContext(KdlTokenizerContext.multiLineComment); - this.commentNesting = 1; - this.index += 2; + _commentNesting = 1; + _context = _KdlTokenizerContext.multiLineComment; + _traverse(2); } else if (n == '-') { - this.index += 2; - return [KdlToken.SLASHDASH, '/-']; + _traverse(2); + return _token(KdlTerm.slashdash, '/-'); } else { - _setContext(KdlTokenizerContext.ident); - this.buffer = c; - this.index += 1; + _fail("Unexpected character '$c'"); } - } else if (WHITESPACE.contains(c)) { - _setContext(KdlTokenizerContext.whitespace); - this.buffer = c; - this.index += 1; - } else if (c == null) { - if (this.done) { - return [false, false]; - } - this.done = true; - return [KdlToken.EOF, '']; - } else if (!NON_INITIAL_IDENTIFIER_CHARS.contains(c)) { - _setContext(KdlTokenizerContext.ident); - this.buffer = c; - this.index += 1; + } else if (whitespace.contains(c)) { + _buffer = c; + _context = _KdlTokenizerContext.whitespace; + _traverse(1); + } else if (!_nonInitialIdentifierChars.contains(c)) { + _buffer = c; + _context = _KdlTokenizerContext.ident; + _traverse(1); + } else if (c == '(') { + _inType = true; + _traverse(1); + return _token(KdlTerm.lparen, c); + } else if (c == ')') { + _inType = false; + _traverse(1); + return _token(KdlTerm.rparen, c); } else { - throw "Unexpected character '${c}'"; + _fail("Unexpected character '$c'"); } } else { - switch(this.context) { - case KdlTokenizerContext.ident: - if (!NON_IDENTIFIER_CHARS.contains(c)) { - this.index += 1; - this.buffer += c; - break; - } else { - switch (this.buffer) { - case 'true': return [KdlToken.TRUE, true]; - case 'false': return [KdlToken.FALSE, false]; - case 'null': return [KdlToken.NULL, null]; - default: return [KdlToken.IDENT, this.buffer]; + switch (_context) { + case _KdlTokenizerContext.ident: + if (!_nonIdentifierChars.contains(c)) { + _buffer += c; + _traverse(1); + break; + } else { + if (['true', 'false', 'null', 'inf', '-inf', 'nan'] + .contains(_buffer)) { + _fail("Identifier cannot be a literal"); + } else if (RegExp(r"^\.\d").hasMatch(_buffer)) { + _fail("Identifier cannot look like an illegal float"); + } else { + return _token(KdlTerm.ident, _buffer); + } + } + case _KdlTokenizerContext.keyword: + if (c != null && RegExp(r"[a-z\-]").hasMatch(c)) { + _buffer += c; + _traverse(1); + break; + } else { + switch (_buffer) { + case '#true': + return _token(KdlTerm.trueKeyword, true); + case '#false': + return _token(KdlTerm.falseKeyword, false); + case '#null': + return _token(KdlTerm.nullKeyword, null); + case '#inf': + return _token(KdlTerm.double, double.infinity); + case '#-inf': + return _token(KdlTerm.double, -double.infinity); + case '#nan': + return _token(KdlTerm.double, double.nan); + default: + _fail("Unknown keyword $_buffer"); + } + } + case _KdlTokenizerContext.string: + switch (c) { + case '\\': + _buffer += c; + var c2 = _char(_index + 1); + _buffer += c2; + if (newlines.contains(c2)) { + var i = 2; + while (newlines.contains(c2 = _char(_index + i))) { + _buffer += c2; + i += 1; + } + _traverse(i); + } else { + _traverse(2); + } + break; + case '"': + _traverse(1); + return _token(KdlTerm.string, _unescape(_buffer)); + case '': + case null: + _fail("Unterminated string literal"); + default: + if (newlines.contains(c)) { + _fail("Unexpected NEWLINE in single-line string"); + } + _buffer += c; + _traverse(1); + break; } - } - case KdlTokenizerContext.string: - switch (c) { - case '\\': - this.buffer += c; - this.buffer += _charAt(this.index + 1); - this.index += 2; break; - case '"': - this.index += 1; - return [KdlToken.STRING, _convertEscapes(this.buffer)]; - case '': - throw "Unterminated string literal"; - default: - this.buffer += c; - this.index += 1; + case _KdlTokenizerContext.multiLineString: + switch (c) { + case '\\': + _buffer += c; + _buffer += _char(_index + 1); + _traverse(2); + break; + case '"': + if (_char(_index + 1) == '"' && _char(_index + 2) == '"') { + _traverse(3); + return _token(KdlTerm.string, + _unescapeNonWs(_dedent(_unescapeWs(_buffer)))); + } + _buffer += c; + _traverse(1); + break; + case null: + _fail("Unterminated multi-line string literal"); + default: + _buffer += c; + _traverse(1); + break; + } break; - } - break; - case KdlTokenizerContext.rawstring: - if (c == null) { - throw "Unterminated rawstring literal"; - } + case _KdlTokenizerContext.rawstring: + if (c == null) { + _fail("Unterminated rawstring literal"); + } - if (c == '"') { - var h = 0; - while (_charAt(this.index + 1 + h) == '#' && h < this.rawstringHashes) { - h += 1; + if (c == '"') { + var h = 0; + while (_char(_index + 1 + h) == '#' && h < _rawstringHashes) { + h += 1; + } + if (h == _rawstringHashes) { + _traverse(1 + h); + return _token(KdlTerm.rawstring, _buffer); + } + } else if (newlines.contains(c)) { + _fail("Unexpected NEWLINE in single-line string"); } - if (h == this.rawstringHashes) { - this.index += 1 + h; - return [KdlToken.RAWSTRING, this.buffer]; + + _buffer += c; + _traverse(1); + break; + case _KdlTokenizerContext.multiLineRawstring: + if (c == null) { + _fail("Unterminated multi-line rawstring literal"); } - } - this.buffer += c; - this.index += 1; - break; - case KdlTokenizerContext.decimal: - if (c != null && RegExp(r"[0-9.\-+_eE]").hasMatch(c)) { - this.index += 1; - this.buffer += c; - } else if (WHITESPACE.contains(c) || NEWLINES.contains(c) || c == null) { - return _parseDecimal(this.buffer); - } else { - throw "Unexpected '$c'"; - } - break; - case KdlTokenizerContext.hexadecimal: - if (c != null && RegExp(r"[0-9a-fA-F_]").hasMatch(c)) { - this.index += 1; - this.buffer += c; - } else if (WHITESPACE.contains(c) || NEWLINES.contains(c) || c == null) { - return _parseHexadecimal(this.buffer); - } else { - throw "Unexpected '$c'"; - } - break; - case KdlTokenizerContext.octal: - if (c != null && RegExp(r"[0-7_]").hasMatch(c)) { - this.index += 1; - this.buffer += c; - } else if (WHITESPACE.contains(c) || NEWLINES.contains(c) || c == null) { - return _parseOctal(this.buffer); - } else { - throw "Unexpected '$c'"; - } - break; - case KdlTokenizerContext.binary: - if (c != null && RegExp(r"[01_]").hasMatch(c)) { - this.index += 1; - this.buffer += c; - } else if (WHITESPACE.contains(c) || NEWLINES.contains(c) || c == null) { - return _parseBinary(this.buffer); - } else { - throw "Unexpected '$c'"; - } - break; - case KdlTokenizerContext.singleLineComment: - if (NEWLINES.contains(c) || c == "\r") { - _setContext(null); - continue; - } else if (c == null) { - this.done = true; - return [KdlToken.EOF, '']; - } else { - this.index += 1; - } - break; - case KdlTokenizerContext.multiLineComment: - var n = _charAt(this.index + 1); - if (c == '/' && n == '*') { - this.commentNesting += 1; - this.index += 2; - } else if (c == '*' && n == '/') { - this.commentNesting -= 1; - this.index += 2; - if (this.commentNesting == 0) { - _revertContext(); + if (c == '"' && + _char(_index + 1) == '"' && + _char(_index + 2) == '"' && + _char(_index + 3) == '#') { + var h = 1; + while (_char(_index + 3 + h) == '#' && h < _rawstringHashes) { + h += 1; + } + if (h == _rawstringHashes) { + _traverse(3 + h); + return _token(KdlTerm.rawstring, _dedent(_buffer)); + } } - } else { - this.index += 1; - } - break; - case KdlTokenizerContext.whitespace: - if (WHITESPACE.contains(c)) { - this.index += 1; - this.buffer += c; - } else if (c == "/" && _charAt(this.index + 1) == '*') { - _setContext(KdlTokenizerContext.multiLineComment); - this.commentNesting = 1; - this.index += 2; - } else { - return [KdlToken.WS, this.buffer]; - } - break; - case null: - throw "Unexpected null context"; + + _buffer += c; + _traverse(1); + break; + case _KdlTokenizerContext.decimal: + if (c != null && RegExp(r"[0-9.\-+_eE]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (whitespace.contains(c) || + newlines.contains(c) || + c == null) { + return _parseDecimal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.hexadecimal: + if (c != null && RegExp(r"[0-9a-fA-F_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (whitespace.contains(c) || + newlines.contains(c) || + c == null) { + return _parseHexadecimal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.octal: + if (c != null && RegExp(r"[0-7_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (whitespace.contains(c) || + newlines.contains(c) || + c == null) { + return _parseOctal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.binary: + if (c != null && RegExp(r"[01_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (whitespace.contains(c) || + newlines.contains(c) || + c == null) { + return _parseBinary(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.singleLineComment: + if (newlines.contains(c) || c == "\r") { + _context = null; + _columnAtStart = _column; + continue; + } else if (c == null) { + _done = true; + return _token(KdlTerm.eof, ''); + } else { + _traverse(1); + } + break; + case _KdlTokenizerContext.multiLineComment: + var n = _char(_index + 1); + if (c == '/' && n == '*') { + _commentNesting += 1; + _traverse(2); + } else if (c == '*' && n == '/') { + _commentNesting -= 1; + _traverse(2); + if (_commentNesting == 0) { + _revertContext(); + } + } else { + _traverse(1); + } + break; + case _KdlTokenizerContext.whitespace: + if (whitespace.contains(c)) { + _buffer += c; + _traverse(1); + } else if (c == '=') { + _buffer += c; + _context = _KdlTokenizerContext.equals; + _traverse(1); + } else if (c == "\\") { + var t = KdlTokenizer(_str, start: _index + 1); + var la = t.nextToken(); + if (la.type == KdlTerm.newline || la.type == KdlTerm.eof) { + _buffer += "$c${la.value}"; + _traverseTo(t._index); + continue; + } else if (la.type == KdlTerm.whitespace) { + var lan = t.nextToken(); + if (lan.type == KdlTerm.newline || lan.type == KdlTerm.eof) { + _buffer += "$c${la.value}"; + if (lan.type == KdlTerm.newline) _buffer += "\n"; + _traverseTo(t._index); + continue; + } + } + _fail("Unexpected '\\'"); + } else if (c == "/" && _char(_index + 1) == '*') { + _commentNesting = 1; + _context = _KdlTokenizerContext.multiLineComment; + _traverse(2); + } else { + return _token(KdlTerm.whitespace, _buffer); + } + break; + case _KdlTokenizerContext.equals: + var t = KdlTokenizer(_str, start: _index); + var la = t.nextToken(); + if (la.type == KdlTerm.whitespace) { + _buffer += la.value; + _traverseTo(t._index); + } + return _token(KdlTerm.equals, _buffer); + case null: + _fail("Unexpected null context"); } } } } - _charAt(int i) { - if (i < 0 || i >= this.str.length) { + _char(int i) { + if (i < 0 || i >= _str.runes.length) { return null; } - return this.str[i]; + var char = String.fromCharCode(_str.runes.elementAt(i)); + if (_forbidden.contains(char)) { + _fail("Forbidden character: $char"); + } + return char; } - _revertContext() { - this.context = this.previousContext; - this.previousContext = null; + KdlToken _token(KdlTerm type, value) { + return _lastToken = KdlToken(type, value, _lineAtStart, _columnAtStart); + } + + void _traverse([int n = 1]) { + for (int i = 0; i < n; i++) { + var c = _char(_index + i); + if (c == "\r") { + _column = 1; + } else if (newlines.contains(c)) { + _line += 1; + _column = 1; + } else { + _column += 1; + } + } + _index += n; + } + + void _traverseTo(i) { + _traverse(i - _index); + } + + void _fail(message) { + throw KdlParseException(message, _line, _column); + } + + void _revertContext() { + _ctx = _previousContext; + _previousContext = null; + } + + String _expectNewline(int i) { + var c = _char(i); + if (c == "\r") { + var n = _char(i + 1); + if (n == "\n") { + return "$c$n"; + } + } else if (!newlines.contains(c)) { + _fail("Expected NEWLINE, found '$c'"); + } + return c; } _integerContext(String n) { switch (n) { - case 'b': return KdlTokenizerContext.binary; - case 'o': return KdlTokenizerContext.octal; - case 'x': return KdlTokenizerContext.hexadecimal; + case 'b': + return _KdlTokenizerContext.binary; + case 'o': + return _KdlTokenizerContext.octal; + case 'x': + return _KdlTokenizerContext.hexadecimal; } } _parseDecimal(String s) { - if (RegExp("[.eE]").hasMatch(s)) { - _checkFloat(s); - return [KdlToken.FLOAT, BigDecimal.parse(_munchUnderscores(s))]; + try { + if (RegExp("[.eE]").hasMatch(s)) { + _checkFloat(s); + return KdlToken( + KdlTerm.decimal, BigDecimal.parse(_munchUnderscores(s))); + } + _checkInt(s); + return _token(KdlTerm.integer, _parseInteger(_munchUnderscores(s), 10)); + } catch (e) { + if (_nonInitialIdentifierChars + .contains(String.fromCharCode(s.runes.first)) || + s.runes.skip(1).any( + (c) => _nonIdentifierChars.contains(String.fromCharCode(c)))) { + rethrow; + } + return _token(KdlTerm.ident, s); } - _checkInt(s); - return [KdlToken.INTEGER, _parseInteger(_munchUnderscores(s), 10)]; } _checkFloat(String s) { - if (!RegExp(r"^[+-]?[0-9][0-9_]*(\.[0-9][0-9_]*)?([eE][+-]?[0-9][0-9_]*)?$").hasMatch(s)) { - throw "Invalid float: ${s}"; + if (!RegExp(r"^[+-]?[0-9][0-9_]*(\.[0-9][0-9_]*)?([eE][+-]?[0-9][0-9_]*)?$") + .hasMatch(s)) { + _fail("Invalid float: $s"); } } - + _checkInt(String s) { if (!RegExp(r"^[+-]?[0-9][0-9_]*$").hasMatch(s)) { - throw "Invalid integer: ${s}"; + _fail("Invalid integer: $s"); } } - + _parseHexadecimal(String s) { - if (!RegExp(r"^[+-]?[0-9a-fA-F][0-9a-fA-F_]*$").hasMatch(s)) throw "Invalid hexadecimal: ${s}"; - return [KdlToken.INTEGER, _parseInteger(_munchUnderscores(s), 16)]; + if (!RegExp(r"^[+-]?[0-9a-fA-F][0-9a-fA-F_]*$").hasMatch(s)) { + _fail("Invalid hexadecimal: $s"); + } + return _token(KdlTerm.integer, _parseInteger(_munchUnderscores(s), 16)); } - + _parseOctal(String s) { - if (!RegExp(r"^[+-]?[0-7][0-7_]*$").hasMatch(s)) throw "Invalid octal: ${s}"; - return [KdlToken.INTEGER, _parseInteger(_munchUnderscores(s), 8)]; + if (!RegExp(r"^[+-]?[0-7][0-7_]*$").hasMatch(s)) { + _fail("Invalid octal: $s"); + } + return _token(KdlTerm.integer, _parseInteger(_munchUnderscores(s), 8)); } - + _parseBinary(String s) { - if (!RegExp(r"^[+-]?[01][01_]*$").hasMatch(s)) throw "Invalid binary: ${s}"; - return [KdlToken.INTEGER, _parseInteger(_munchUnderscores(s), 2)]; + if (!RegExp(r"^[+-]?[01][01_]*$").hasMatch(s)) _fail("Invalid binary: $s"); + return _token(KdlTerm.integer, _parseInteger(_munchUnderscores(s), 2)); } _munchUnderscores(String s) { return s.replaceAll('_', ''); } - _convertEscapes(String string) { - return string.replaceAllMapped(RegExp(r"\\[^u]"), (match) { - switch (match.group(0)) { - case r'\n': return "\n"; - case r'\r': return "\r"; - case r'\t': return "\t"; - case r'\\': return "\\"; - case r'\"': return "\""; - case r'\b': return "\b"; - case r'\f': return "\f"; - case r'\/': return "/"; - default: throw "Unexpected escape '${match.group(0)}'"; + _unescapeWs(String string) { + return string.replaceAllMapped(RegExp(r"\\(\\|\s+)"), (match) { + var m = match.group(0); + switch (m) { + case r'\\': + return r'\\'; + default: + return ''; } + }); + } + + static final _unescapeWsPattern = + "[${whitespace.map(RegExp.escape).join()}${newlines.map(RegExp.escape).join()}\\r]+"; + static final _unescapePattern = RegExp("\\\\(?:$_unescapeWsPattern|[^u])"); + static final _unescapeNonWsPattern = RegExp(r"\\(?:[^u])"); + + _unescapeNonWs(String string) { + return _unescapeRgx(string, _unescapeNonWsPattern); + } + + _unescape(String string) { + return _unescapeRgx(string, _unescapePattern); + } + + _unescapeRgx(String string, RegExp rgx) { + return string.replaceAllMapped(rgx, (match) { + return _replaceEsc(match.group(0)); }).replaceAllMapped(RegExp(r"\\u\{[0-9a-fA-F]{1,6}\}"), (match) { String m = match.group(0) ?? ''; int i = int.parse(m.substring(3, m.length - 1), radix: 16); - if (i < 0 || i > 0x10FFFF) { - throw "Invalid code point ${m}"; + if (i < 0 || i > 0x10FFFF || (i >= 0xD800 && i <= 0xDFFF)) { + _fail("Invalid code point $m"); } return String.fromCharCode(i); }); } + _replaceEsc(String? m) { + switch (m) { + case r'\n': + return "\n"; + case r'\r': + return "\r"; + case r'\t': + return "\t"; + case r'\\': + return "\\"; + case r'\"': + return "\""; + case r'\b': + return "\b"; + case r'\f': + return "\f"; + case "\\\n": + return ""; + case r'\s': + return ' '; + default: + if (m != null && RegExp("\\\\$_unescapeWsPattern").hasMatch(m)) { + return ''; + } + _fail("Unexpected escape '$m'"); + } + } + _parseInteger(String string, int radix) { try { return int.parse(string, radix: radix); - } catch (FormatException) { + } on FormatException { return BigInt.parse(string, radix: radix); } } + + _dedent(String string) { + var [...lines, indent] = string.split(_newlinesPattern); + if (!_wsStar.hasMatch(indent)) { + _fail("Invalid multiline string final line"); + } + + var valid = RegExp("${RegExp.escape(indent)}(.*)"); + + return lines.map((line) { + if (_wsStar.hasMatch(line)) { + return ''; + } + var m = valid.matchAsPrefix(line); + if (m != null) { + return m.group(1); + } + _fail("Invalid multiline string indentation"); + }).join("\n"); + } +} + +/// Tokenizer for KDLV1Parser +class KdlV1Tokenizer extends KdlTokenizer { + static final _symbols = { + '{': KdlTerm.lbrace, + '}': KdlTerm.rbrace, + '(': KdlTerm.lparen, + ')': KdlTerm.rparen, + ';': KdlTerm.semicolon, + '=': KdlTerm.equals + }; + + static const _newlines = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]; + static final _newlinesPattern = + RegExp("${_newlines.map(RegExp.escape).join('|')}|\\r\\n?"); + + static final _nonIdentifierChars = [ + null, + ...KdlTokenizer.whitespace, + ..._newlines, + ..._symbols.keys, + "\r", + "\\", + "<", + ">", + "[", + "]", + '"', + ",", + "/", + ..._charRange(0x0000, 0x0020), + ]; + static final _nonInitialIdentifierChars = [ + ..._nonIdentifierChars, + List.generate(10, (index) => index.toString()) + ]; + + /// Construct a new KDL V1 Tokenizer + KdlV1Tokenizer(super.str); + + static final _versionPattern = RegExp( + "\\/-${KdlTokenizer._ws}*kdl-version${KdlTokenizer._ws}+(\\d+)${KdlTokenizer._ws}*${_newlinesPattern.pattern}"); + + @override + versionDirective() { + var match = _versionPattern.matchAsPrefix(_str); + if (match == null) return null; + var m = match.group(1); + if (m == null) return null; + return int.parse(m); + } + + @override + KdlToken _readToken() { + _context = null; + _previousContext = null; + _lineAtStart = _line; + _columnAtStart = _column; + while (true) { + var c = _char(_index); + if (_context == null) { + if (c == null) { + if (_done) { + return _token(KdlTerm.eof, null); + } + _done = true; + return _token(KdlTerm.eof, ''); + } else if (c == '"') { + _context = _KdlTokenizerContext.string; + _buffer = ''; + _traverse(1); + } else if (c == 'r') { + if (_char(_index + 1) == '"') { + _context = _KdlTokenizerContext.rawstring; + _traverse(2); + _rawstringHashes = 0; + _buffer = ''; + continue; + } else if (_char(_index + 1) == '#') { + var i = _index + 1; + _rawstringHashes = 0; + while (_char(i) == '#') { + _rawstringHashes += 1; + i += 1; + } + if (_char(i) == '"') { + _context = _KdlTokenizerContext.rawstring; + _traverse(_rawstringHashes + 2); + _buffer = ''; + continue; + } + } + _context = _KdlTokenizerContext.ident; + _buffer = c; + _traverse(1); + } else if (c == '-') { + var n = _char(_index + 1); + var n2 = _char(_index + 2); + if (n != null && RegExp(r"[0-9]").hasMatch(n)) { + if (n == '0' && n2 != null && RegExp(r"[box]").hasMatch(n2)) { + _context = _integerContext(n2); + _traverse(2); + } else { + _context = _KdlTokenizerContext.decimal; + } + } else { + _context = _KdlTokenizerContext.ident; + } + _buffer = c; + _traverse(1); + } else if (RegExp(r"[0-9+]").hasMatch(c)) { + var n = _char(_index + 1); + var n2 = _char(_index + 2); + if (c == '0' && n != null && RegExp("[box]").hasMatch(n)) { + _buffer = ''; + _context = _integerContext(n); + _traverse(2); + } else if (c == '+' && n == '0' && RegExp("[box]").hasMatch(n2)) { + _buffer = c; + _context = _integerContext(n2); + _traverse(3); + } else { + _buffer = c; + _context = _KdlTokenizerContext.decimal; + _traverse(1); + } + } else if (c == "\\") { + var t = KdlTokenizer(_str, start: _index + 1); + var la = t.nextToken(); + if (la.type == KdlTerm.newline || la.type == KdlTerm.eof) { + _buffer = "$c${la.value}"; + _context = _KdlTokenizerContext.whitespace; + _traverseTo(t._index); + continue; + } else if (la.type == KdlTerm.whitespace) { + var lan = t.nextToken(); + if (lan.type == KdlTerm.newline || lan.type == KdlTerm.eof) { + _buffer = "$c${la.value}"; + if (lan.type == KdlTerm.newline) _buffer += "\n"; + _context = _KdlTokenizerContext.whitespace; + _traverseTo(t._index); + continue; + } + } + _fail("Unexpected '\\'"); + } else if (_symbols.containsKey(c)) { + if (c == '(') { + _inType = true; + } else if (c == ')') { + _inType = false; + } + _traverse(1); + return _token(_symbols[c]!, c); + } else if (c == "\r" || _newlines.contains(c)) { + String nl = _expectNewline(_index); + _traverse(nl.runes.length); + return _token(KdlTerm.newline, nl); + } else if (c == "/") { + var n = _char(_index + 1); + if (n == '/') { + if (_inType || _lastToken?.type == KdlTerm.rparen) { + _fail("Unexpected '/'"); + } + _context = _KdlTokenizerContext.singleLineComment; + _traverse(2); + } else if (n == '*') { + if (_inType || _lastToken?.type == KdlTerm.rparen) { + _fail("Unexpected '/'"); + } + _commentNesting = 1; + _context = _KdlTokenizerContext.multiLineComment; + _traverse(2); + } else if (n == '-') { + _traverse(2); + return _token(KdlTerm.slashdash, '/-'); + } else { + _fail("Unexpected character '$c'"); + } + } else if (KdlTokenizer.whitespace.contains(c)) { + _buffer = c; + _context = _KdlTokenizerContext.whitespace; + _traverse(1); + } else if (!_nonInitialIdentifierChars.contains(c)) { + _buffer = c; + _context = _KdlTokenizerContext.ident; + _traverse(1); + } else { + _fail("Unexpected character '$c'"); + } + } else { + switch (_context) { + case _KdlTokenizerContext.ident: + if (!_nonIdentifierChars.contains(c)) { + _buffer += c; + _traverse(1); + break; + } else { + switch (_buffer) { + case 'true': + return _token(KdlTerm.trueKeyword, true); + case 'false': + return _token(KdlTerm.falseKeyword, false); + case 'null': + return _token(KdlTerm.nullKeyword, null); + default: + return _token(KdlTerm.ident, _buffer); + } + } + case _KdlTokenizerContext.string: + switch (c) { + case '\\': + _buffer += c; + var c2 = _char(_index + 1); + _buffer += c2; + if (_newlines.contains(c2)) { + var i = 2; + while (_newlines.contains(c2 = _char(_index + i))) { + _buffer += c2; + i += 1; + } + _traverse(i); + } else { + _traverse(2); + } + break; + case '"': + _traverse(1); + return _token(KdlTerm.string, _unescape(_buffer)); + case '': + case null: + _fail("Unterminated string literal"); + default: + _buffer += c; + _traverse(1); + break; + } + break; + case _KdlTokenizerContext.rawstring: + if (c == null) { + _fail("Unterminated rawstring literal"); + } + + if (c == '"') { + var h = 0; + while (_char(_index + 1 + h) == '#' && h < _rawstringHashes) { + h += 1; + } + if (h == _rawstringHashes) { + _traverse(1 + h); + return _token(KdlTerm.rawstring, _buffer); + } + } + _buffer += c; + _traverse(1); + break; + case _KdlTokenizerContext.decimal: + if (c != null && RegExp(r"[0-9.\-+_eE]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (KdlTokenizer.whitespace.contains(c) || + _newlines.contains(c) || + c == null) { + return _parseDecimal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.hexadecimal: + if (c != null && RegExp(r"[0-9a-fA-F_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (KdlTokenizer.whitespace.contains(c) || + _newlines.contains(c) || + c == null) { + return _parseHexadecimal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.octal: + if (c != null && RegExp(r"[0-7_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (KdlTokenizer.whitespace.contains(c) || + _newlines.contains(c) || + c == null) { + return _parseOctal(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.binary: + if (c != null && RegExp(r"[01_]").hasMatch(c)) { + _buffer += c; + _traverse(1); + } else if (KdlTokenizer.whitespace.contains(c) || + _newlines.contains(c) || + c == null) { + return _parseBinary(_buffer); + } else { + _fail("Unexpected '$c'"); + } + break; + case _KdlTokenizerContext.singleLineComment: + if (_newlines.contains(c) || c == "\r") { + _context = null; + _columnAtStart = _column; + continue; + } else if (c == null) { + _done = true; + return _token(KdlTerm.eof, ''); + } else { + _traverse(1); + } + break; + case _KdlTokenizerContext.multiLineComment: + var n = _char(_index + 1); + if (c == '/' && n == '*') { + _commentNesting += 1; + _traverse(2); + } else if (c == '*' && n == '/') { + _commentNesting -= 1; + _traverse(2); + if (_commentNesting == 0) { + _revertContext(); + } + } else { + _traverse(1); + } + break; + case _KdlTokenizerContext.whitespace: + if (KdlTokenizer.whitespace.contains(c)) { + _buffer += c; + _traverse(1); + } else if (c == "\\") { + var t = KdlTokenizer(_str, start: _index + 1); + var la = t.nextToken(); + if (la.type == KdlTerm.newline || la.type == KdlTerm.eof) { + _buffer += "$c${la.value}"; + _traverseTo(t._index); + continue; + } else if (la.type == KdlTerm.whitespace) { + var lan = t.nextToken(); + if (lan.type == KdlTerm.newline || lan.type == KdlTerm.eof) { + _buffer += "$c${la.value}"; + if (lan.type == KdlTerm.newline) _buffer += "\n"; + _traverseTo(t._index); + continue; + } + } + _fail("Unexpected '\\'"); + } else if (c == "/" && _char(_index + 1) == '*') { + _commentNesting = 1; + _context = _KdlTokenizerContext.multiLineComment; + _traverse(2); + } else { + return _token(KdlTerm.whitespace, _buffer); + } + break; + case null: + _fail("Unexpected null context"); + default: + _fail("Unknown context $_context"); + } + } + } + } + + @override + _parseDecimal(String s) { + if (RegExp("[.eE]").hasMatch(s)) { + _checkFloat(s); + return _token(KdlTerm.decimal, BigDecimal.parse(_munchUnderscores(s))); + } + _checkInt(s); + return _token(KdlTerm.integer, _parseInteger(_munchUnderscores(s), 10)); + } + + @override + _replaceEsc(String? m) { + switch (m) { + case r'\n': + return "\n"; + case r'\r': + return "\r"; + case r'\t': + return "\t"; + case r'\\': + return "\\"; + case r'\"': + return "\""; + case r'\b': + return "\b"; + case r'\f': + return "\f"; + case "\\\n": + return ""; + case r'\s': + return ' '; + case r'\/': + return '/'; + default: + if (m != null && + RegExp("\\\\${KdlTokenizer._unescapeWsPattern}").hasMatch(m)) { + return ''; + } + _fail("Unexpected escape '$m'"); + } + } } diff --git a/lib/src/types.dart b/lib/src/types.dart index 9100513..2c38fa2 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -1,42 +1,14 @@ -import "./types/date_time.dart"; -import "./types/duration.dart"; -import "./types/decimal.dart"; -import "./types/currency.dart"; -import "./types/country.dart"; -import "./types/email.dart"; -import "./types/hostname.dart"; -import "./types/ip.dart"; -import "./types/url.dart"; -import "./types/irl.dart"; -import "./types/url_template.dart"; -import "./types/uuid.dart"; -import "./types/regex.dart"; -import "./types/base64.dart"; - -class KdlTypes { - static Map MAPPING = const { - 'date-time': KdlDateTime.call, - 'time': KdlTime.call, - 'date': KdlDate.call, - 'duration': KdlDuration.call, - 'decimal': KdlDecimal.call, - 'currency': KdlCurrency.call, - 'country-2': KdlCountry2.call, - 'country-3': KdlCountry3.call, - 'country-subdivision': KdlCountrySubdivision.call, - 'email': KdlEmail.call, - 'idn-email': KdlIDNEmail.call, - 'hostname': KdlHostname.call, - 'idn-hostname': KdlIDNHostname.call, - 'ipv4': KdlIPV4.call, - 'ipv6': KdlIPV6.call, - 'url': KdlURL.call, - 'url-reference': KdlURLReference.call, - 'irl': KdlIRL.call, - 'irl-reference': KdlIRLReference.call, - 'url-template': KdlURLTemplate.call, - 'uuid': KdlUUID.call, - 'regex': KdlRegex.call, - 'base64': KdlBase64.call, - }; -} +export "./types/date_time.dart"; +export "./types/duration.dart"; +export "./types/decimal.dart"; +export "./types/currency.dart"; +export "./types/country.dart"; +export "./types/email.dart"; +export "./types/hostname.dart"; +export "./types/ip.dart"; +export "./types/url.dart"; +export "./types/irl.dart"; +export "./types/url_template.dart"; +export "./types/uuid.dart"; +export "./types/regex.dart"; +export "./types/base64.dart"; diff --git a/lib/src/types/base64.dart b/lib/src/types/base64.dart index 80f3296..1abb3a9 100644 --- a/lib/src/types/base64.dart +++ b/lib/src/types/base64.dart @@ -3,11 +3,14 @@ import 'dart:typed_data'; import "../document.dart"; +/// A Base64-encoded string, denoting arbitrary binary data. class KdlBase64 extends KdlValue { - KdlBase64(Uint8List value, [String? type]) : super(value, type); + /// Construct a new `KdlBase64` + KdlBase64(super.value, [super.type]); - static call(KdlValue value, [String type = 'base64']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlBase64` + static KdlBase64? convert(KdlValue value, [String type = 'base64']) { + if (value is! KdlString) return null; return KdlBase64(base64.decode(value.value), type); } diff --git a/lib/src/types/country.dart b/lib/src/types/country.dart index c68b7ca..9ebf6df 100644 --- a/lib/src/types/country.dart +++ b/lib/src/types/country.dart @@ -2,58 +2,73 @@ import "../document.dart"; import "./country/iso3166_countries.dart"; import "./country/iso3166_subdivisions.dart"; +/// Base-class for ISO 3166-1 country codes class KdlCountry extends KdlValue { - KdlCountry(Country value, [String? type]) : super(value, type); + /// Construct a new `KdlCountry` + KdlCountry(super.value, [super.type]); } +/// ISO 3166-1 alpha-2 country code. class KdlCountry2 extends KdlCountry { - KdlCountry2(Country value, [String? type]) : super(value, type); + /// Construct a new `KdlCountry2` + KdlCountry2(super.value, [super.type]); - static call(KdlValue value, [String type = 'country2']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into `KdlCountry2` + static KdlCountry2? convert(KdlValue value, [String type = 'country2']) { + if (value is! KdlString) return null; - var country = Country.COUNTRIES2[value.value]; + var country = Country.countries2[value.value]; if (country == null) throw "invalid country2: ${value.value}"; return KdlCountry2(country, type); } } +/// ISO 3166-1 alpha-3 country code. class KdlCountry3 extends KdlCountry { - KdlCountry3(Country value, [String? type]) : super(value, type); + /// Construct a new `KdlCountry3` + KdlCountry3(super.value, [super.type]); - static call(KdlValue value, [String type = 'country3']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into `KdlCountry3` + static KdlCountry3? convert(KdlValue value, [String type = 'country3']) { + if (value is! KdlString) return null; - var country = Country.COUNTRIES3[value.value]; + var country = Country.countries3[value.value]; if (country == null) throw "invalid country3: ${value.value}"; return KdlCountry3(country, type); } } +/// ISO 3166-2 country subdivision code. class KdlCountrySubdivision extends KdlValue { + /// Name of the subdivision String name; + + /// The country in which the subdivision resides Country country; - KdlCountrySubdivision(String value, this.name, this.country, [String? type]) : super(value, type); + /// Construct a new KDL Country Subdivision + KdlCountrySubdivision(super.value, this.name, this.country, [super.type]); - static call(KdlValue value, [String type = 'country-subdivision']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlCountrySubdivision` + static KdlCountrySubdivision? convert(KdlValue value, + [String type = 'country-subdivision']) { + if (value is! KdlString) return null; var parts = value.value.split('-'); if (parts.length != 2) throw "invalid country subdivision: ${value.value}"; var country2 = parts[0]; var subdivisionCode = parts[1]; - var country = Country.COUNTRIES2[country2]; - if (country == null) throw "invalid country2: ${country2}"; + var country = Country.countries2[country2]; + if (country == null) throw "invalid country2: $country2"; - var subdivisions = COUNTRY_SUBDIVISIONS[country2]; - if (subdivisions == null) throw "invalid country: ${country2}"; + var subdivisions = countrySubdivisions[country2]; + if (subdivisions == null) throw "invalid country: $country2"; var subdivision = subdivisions[value.value]; - if (subdivision == null) throw "invalid subdivision: ${subdivisionCode}"; + if (subdivision == null) throw "invalid subdivision: $subdivisionCode"; return KdlCountrySubdivision(value.value, subdivision, country, type); } diff --git a/lib/src/types/country/iso3166_countries.dart b/lib/src/types/country/iso3166_countries.dart index 626211c..62758d6 100644 --- a/lib/src/types/country/iso3166_countries.dart +++ b/lib/src/types/country/iso3166_countries.dart @@ -1,522 +1,931 @@ +/// Represents a country class Country { + /// Alpha-3 country code String alpha3; + + /// Alpha-2 country code String alpha2; + + /// Numeric country code int numericCode; + + /// Name of the country String name; - Country({ this.alpha3 = '', this.alpha2 = '', this.numericCode = 0, this.name = '' }); + /// Construct a new instance of `Country` + Country( + {this.alpha3 = '', + this.alpha2 = '', + this.numericCode = 0, + this.name = ''}); + + @override + bool operator ==(other) => + other is Country && + other.alpha3 == alpha3 && + other.alpha2 == alpha2 && + other.numericCode == numericCode && + other.name == name; @override - bool operator ==(other) => other is Country && - other.alpha3 == alpha3 && - other.alpha2 == alpha2 && - other.numericCode == numericCode && - other.name == name; + int get hashCode => [alpha2, alpha2, numericCode, name].hashCode; @override - String toString() => "alpha3:$alpha3 alpha2:$alpha2 numericCode:$numericCode name:$name"; + String toString() => + "alpha3:$alpha3 alpha2:$alpha2 numericCode:$numericCode name:$name"; - static Map COUNTRIES3 = { - 'AFG': Country(alpha3: 'AFG', alpha2: 'AF', numericCode: 4, name: 'Afghanistan'), - 'ALA': Country(alpha3: 'ALA', alpha2: 'AX', numericCode: 248, name: 'Åland Islands'), - 'ALB': Country(alpha3: 'ALB', alpha2: 'AL', numericCode: 8, name: 'Albania'), - 'DZA': Country(alpha3: 'DZA', alpha2: 'DZ', numericCode: 12, name: 'Algeria'), - 'ASM': Country(alpha3: 'ASM', alpha2: 'AS', numericCode: 16, name: 'American Samoa'), - 'AND': Country(alpha3: 'AND', alpha2: 'AD', numericCode: 20, name: 'Andorra'), - 'AGO': Country(alpha3: 'AGO', alpha2: 'AO', numericCode: 24, name: 'Angola'), - 'AIA': Country(alpha3: 'AIA', alpha2: 'AI', numericCode: 660, name: 'Anguilla'), - 'ATA': Country(alpha3: 'ATA', alpha2: 'AQ', numericCode: 10, name: 'Antarctica'), - 'ATG': Country(alpha3: 'ATG', alpha2: 'AG', numericCode: 28, name: 'Antigua and Barbuda'), - 'ARG': Country(alpha3: 'ARG', alpha2: 'AR', numericCode: 32, name: 'Argentina'), - 'ARM': Country(alpha3: 'ARM', alpha2: 'AM', numericCode: 51, name: 'Armenia'), - 'ABW': Country(alpha3: 'ABW', alpha2: 'AW', numericCode: 533, name: 'Aruba'), - 'AUS': Country(alpha3: 'AUS', alpha2: 'AU', numericCode: 36, name: 'Australia'), - 'AUT': Country(alpha3: 'AUT', alpha2: 'AT', numericCode: 40, name: 'Austria'), - 'AZE': Country(alpha3: 'AZE', alpha2: 'AZ', numericCode: 31, name: 'Azerbaijan'), - 'BHS': Country(alpha3: 'BHS', alpha2: 'BS', numericCode: 44, name: 'Bahamas'), - 'BHR': Country(alpha3: 'BHR', alpha2: 'BH', numericCode: 48, name: 'Bahrain'), - 'BGD': Country(alpha3: 'BGD', alpha2: 'BD', numericCode: 50, name: 'Bangladesh'), - 'BRB': Country(alpha3: 'BRB', alpha2: 'BB', numericCode: 52, name: 'Barbados'), - 'BLR': Country(alpha3: 'BLR', alpha2: 'BY', numericCode: 112, name: 'Belarus'), - 'BEL': Country(alpha3: 'BEL', alpha2: 'BE', numericCode: 56, name: 'Belgium'), - 'BLZ': Country(alpha3: 'BLZ', alpha2: 'BZ', numericCode: 84, name: 'Belize'), - 'BEN': Country(alpha3: 'BEN', alpha2: 'BJ', numericCode: 204, name: 'Benin'), - 'BMU': Country(alpha3: 'BMU', alpha2: 'BM', numericCode: 60, name: 'Bermuda'), - 'BTN': Country(alpha3: 'BTN', alpha2: 'BT', numericCode: 64, name: 'Bhutan'), - 'BOL': Country(alpha3: 'BOL', alpha2: 'BO', numericCode: 68, name: 'Bolivia (Plurinational State of)'), - 'BES': Country(alpha3: 'BES', alpha2: 'BQ', numericCode: 535, name: 'Bonaire, Sint Eustatius and Saba[d]'), - 'BIH': Country(alpha3: 'BIH', alpha2: 'BA', numericCode: 70, name: 'Bosnia and Herzegovina'), - 'BWA': Country(alpha3: 'BWA', alpha2: 'BW', numericCode: 72, name: 'Botswana'), - 'BVT': Country(alpha3: 'BVT', alpha2: 'BV', numericCode: 74, name: 'Bouvet Island'), - 'BRA': Country(alpha3: 'BRA', alpha2: 'BR', numericCode: 76, name: 'Brazil'), - 'IOT': Country(alpha3: 'IOT', alpha2: 'IO', numericCode: 86, name: 'British Indian Ocean Territory'), - 'BRN': Country(alpha3: 'BRN', alpha2: 'BN', numericCode: 96, name: 'Brunei Darussalam'), - 'BGR': Country(alpha3: 'BGR', alpha2: 'BG', numericCode: 100, name: 'Bulgaria'), - 'BFA': Country(alpha3: 'BFA', alpha2: 'BF', numericCode: 854, name: 'Burkina Faso'), - 'BDI': Country(alpha3: 'BDI', alpha2: 'BI', numericCode: 108, name: 'Burundi'), - 'CPV': Country(alpha3: 'CPV', alpha2: 'CV', numericCode: 132, name: 'Cabo Verde'), - 'KHM': Country(alpha3: 'KHM', alpha2: 'KH', numericCode: 116, name: 'Cambodia'), - 'CMR': Country(alpha3: 'CMR', alpha2: 'CM', numericCode: 120, name: 'Cameroon'), - 'CAN': Country(alpha3: 'CAN', alpha2: 'CA', numericCode: 124, name: 'Canada'), - 'CYM': Country(alpha3: 'CYM', alpha2: 'KY', numericCode: 136, name: 'Cayman Islands'), - 'CAF': Country(alpha3: 'CAF', alpha2: 'CF', numericCode: 140, name: 'Central African Republic'), + /// Mapping of all countries by alpha-3 code + static Map countries3 = { + 'AFG': Country( + alpha3: 'AFG', alpha2: 'AF', numericCode: 4, name: 'Afghanistan'), + 'ALA': Country( + alpha3: 'ALA', alpha2: 'AX', numericCode: 248, name: 'Åland Islands'), + 'ALB': + Country(alpha3: 'ALB', alpha2: 'AL', numericCode: 8, name: 'Albania'), + 'DZA': + Country(alpha3: 'DZA', alpha2: 'DZ', numericCode: 12, name: 'Algeria'), + 'ASM': Country( + alpha3: 'ASM', alpha2: 'AS', numericCode: 16, name: 'American Samoa'), + 'AND': + Country(alpha3: 'AND', alpha2: 'AD', numericCode: 20, name: 'Andorra'), + 'AGO': + Country(alpha3: 'AGO', alpha2: 'AO', numericCode: 24, name: 'Angola'), + 'AIA': Country( + alpha3: 'AIA', alpha2: 'AI', numericCode: 660, name: 'Anguilla'), + 'ATA': Country( + alpha3: 'ATA', alpha2: 'AQ', numericCode: 10, name: 'Antarctica'), + 'ATG': Country( + alpha3: 'ATG', + alpha2: 'AG', + numericCode: 28, + name: 'Antigua and Barbuda'), + 'ARG': Country( + alpha3: 'ARG', alpha2: 'AR', numericCode: 32, name: 'Argentina'), + 'ARM': + Country(alpha3: 'ARM', alpha2: 'AM', numericCode: 51, name: 'Armenia'), + 'ABW': + Country(alpha3: 'ABW', alpha2: 'AW', numericCode: 533, name: 'Aruba'), + 'AUS': Country( + alpha3: 'AUS', alpha2: 'AU', numericCode: 36, name: 'Australia'), + 'AUT': + Country(alpha3: 'AUT', alpha2: 'AT', numericCode: 40, name: 'Austria'), + 'AZE': Country( + alpha3: 'AZE', alpha2: 'AZ', numericCode: 31, name: 'Azerbaijan'), + 'BHS': + Country(alpha3: 'BHS', alpha2: 'BS', numericCode: 44, name: 'Bahamas'), + 'BHR': + Country(alpha3: 'BHR', alpha2: 'BH', numericCode: 48, name: 'Bahrain'), + 'BGD': Country( + alpha3: 'BGD', alpha2: 'BD', numericCode: 50, name: 'Bangladesh'), + 'BRB': + Country(alpha3: 'BRB', alpha2: 'BB', numericCode: 52, name: 'Barbados'), + 'BLR': + Country(alpha3: 'BLR', alpha2: 'BY', numericCode: 112, name: 'Belarus'), + 'BEL': + Country(alpha3: 'BEL', alpha2: 'BE', numericCode: 56, name: 'Belgium'), + 'BLZ': + Country(alpha3: 'BLZ', alpha2: 'BZ', numericCode: 84, name: 'Belize'), + 'BEN': + Country(alpha3: 'BEN', alpha2: 'BJ', numericCode: 204, name: 'Benin'), + 'BMU': + Country(alpha3: 'BMU', alpha2: 'BM', numericCode: 60, name: 'Bermuda'), + 'BTN': + Country(alpha3: 'BTN', alpha2: 'BT', numericCode: 64, name: 'Bhutan'), + 'BOL': Country( + alpha3: 'BOL', + alpha2: 'BO', + numericCode: 68, + name: 'Bolivia (Plurinational State of)'), + 'BES': Country( + alpha3: 'BES', + alpha2: 'BQ', + numericCode: 535, + name: 'Bonaire, Sint Eustatius and Saba[d]'), + 'BIH': Country( + alpha3: 'BIH', + alpha2: 'BA', + numericCode: 70, + name: 'Bosnia and Herzegovina'), + 'BWA': + Country(alpha3: 'BWA', alpha2: 'BW', numericCode: 72, name: 'Botswana'), + 'BVT': Country( + alpha3: 'BVT', alpha2: 'BV', numericCode: 74, name: 'Bouvet Island'), + 'BRA': + Country(alpha3: 'BRA', alpha2: 'BR', numericCode: 76, name: 'Brazil'), + 'IOT': Country( + alpha3: 'IOT', + alpha2: 'IO', + numericCode: 86, + name: 'British Indian Ocean Territory'), + 'BRN': Country( + alpha3: 'BRN', + alpha2: 'BN', + numericCode: 96, + name: 'Brunei Darussalam'), + 'BGR': Country( + alpha3: 'BGR', alpha2: 'BG', numericCode: 100, name: 'Bulgaria'), + 'BFA': Country( + alpha3: 'BFA', alpha2: 'BF', numericCode: 854, name: 'Burkina Faso'), + 'BDI': + Country(alpha3: 'BDI', alpha2: 'BI', numericCode: 108, name: 'Burundi'), + 'CPV': Country( + alpha3: 'CPV', alpha2: 'CV', numericCode: 132, name: 'Cabo Verde'), + 'KHM': Country( + alpha3: 'KHM', alpha2: 'KH', numericCode: 116, name: 'Cambodia'), + 'CMR': Country( + alpha3: 'CMR', alpha2: 'CM', numericCode: 120, name: 'Cameroon'), + 'CAN': + Country(alpha3: 'CAN', alpha2: 'CA', numericCode: 124, name: 'Canada'), + 'CYM': Country( + alpha3: 'CYM', alpha2: 'KY', numericCode: 136, name: 'Cayman Islands'), + 'CAF': Country( + alpha3: 'CAF', + alpha2: 'CF', + numericCode: 140, + name: 'Central African Republic'), 'TCD': Country(alpha3: 'TCD', alpha2: 'TD', numericCode: 148, name: 'Chad'), - 'CHL': Country(alpha3: 'CHL', alpha2: 'CL', numericCode: 152, name: 'Chile'), - 'CHN': Country(alpha3: 'CHN', alpha2: 'CN', numericCode: 156, name: 'China'), - 'CXR': Country(alpha3: 'CXR', alpha2: 'CX', numericCode: 162, name: 'Christmas Island'), - 'CCK': Country(alpha3: 'CCK', alpha2: 'CC', numericCode: 166, name: 'Cocos (Keeling) Islands'), - 'COL': Country(alpha3: 'COL', alpha2: 'CO', numericCode: 170, name: 'Colombia'), - 'COM': Country(alpha3: 'COM', alpha2: 'KM', numericCode: 174, name: 'Comoros'), - 'COG': Country(alpha3: 'COG', alpha2: 'CG', numericCode: 178, name: 'Congo'), - 'COD': Country(alpha3: 'COD', alpha2: 'CD', numericCode: 180, name: 'Congo, Democratic Republic of the'), - 'COK': Country(alpha3: 'COK', alpha2: 'CK', numericCode: 184, name: 'Cook Islands'), - 'CRI': Country(alpha3: 'CRI', alpha2: 'CR', numericCode: 188, name: 'Costa Rica'), - 'CIV': Country(alpha3: 'CIV', alpha2: 'CI', numericCode: 384, name: 'Côte d\'Ivoire'), - 'HRV': Country(alpha3: 'HRV', alpha2: 'HR', numericCode: 191, name: 'Croatia'), + 'CHL': + Country(alpha3: 'CHL', alpha2: 'CL', numericCode: 152, name: 'Chile'), + 'CHN': + Country(alpha3: 'CHN', alpha2: 'CN', numericCode: 156, name: 'China'), + 'CXR': Country( + alpha3: 'CXR', + alpha2: 'CX', + numericCode: 162, + name: 'Christmas Island'), + 'CCK': Country( + alpha3: 'CCK', + alpha2: 'CC', + numericCode: 166, + name: 'Cocos (Keeling) Islands'), + 'COL': Country( + alpha3: 'COL', alpha2: 'CO', numericCode: 170, name: 'Colombia'), + 'COM': + Country(alpha3: 'COM', alpha2: 'KM', numericCode: 174, name: 'Comoros'), + 'COG': + Country(alpha3: 'COG', alpha2: 'CG', numericCode: 178, name: 'Congo'), + 'COD': Country( + alpha3: 'COD', + alpha2: 'CD', + numericCode: 180, + name: 'Congo, Democratic Republic of the'), + 'COK': Country( + alpha3: 'COK', alpha2: 'CK', numericCode: 184, name: 'Cook Islands'), + 'CRI': Country( + alpha3: 'CRI', alpha2: 'CR', numericCode: 188, name: 'Costa Rica'), + 'CIV': Country( + alpha3: 'CIV', alpha2: 'CI', numericCode: 384, name: 'Côte d\'Ivoire'), + 'HRV': + Country(alpha3: 'HRV', alpha2: 'HR', numericCode: 191, name: 'Croatia'), 'CUB': Country(alpha3: 'CUB', alpha2: 'CU', numericCode: 192, name: 'Cuba'), - 'CUW': Country(alpha3: 'CUW', alpha2: 'CW', numericCode: 531, name: 'Curaçao'), - 'CYP': Country(alpha3: 'CYP', alpha2: 'CY', numericCode: 196, name: 'Cyprus'), - 'CZE': Country(alpha3: 'CZE', alpha2: 'CZ', numericCode: 203, name: 'Czechia'), - 'DNK': Country(alpha3: 'DNK', alpha2: 'DK', numericCode: 208, name: 'Denmark'), - 'DJI': Country(alpha3: 'DJI', alpha2: 'DJ', numericCode: 262, name: 'Djibouti'), - 'DMA': Country(alpha3: 'DMA', alpha2: 'DM', numericCode: 212, name: 'Dominica'), - 'DOM': Country(alpha3: 'DOM', alpha2: 'DO', numericCode: 214, name: 'Dominican Republic'), - 'ECU': Country(alpha3: 'ECU', alpha2: 'EC', numericCode: 218, name: 'Ecuador'), - 'EGY': Country(alpha3: 'EGY', alpha2: 'EG', numericCode: 818, name: 'Egypt'), - 'SLV': Country(alpha3: 'SLV', alpha2: 'SV', numericCode: 222, name: 'El Salvador'), - 'GNQ': Country(alpha3: 'GNQ', alpha2: 'GQ', numericCode: 226, name: 'Equatorial Guinea'), - 'ERI': Country(alpha3: 'ERI', alpha2: 'ER', numericCode: 232, name: 'Eritrea'), - 'EST': Country(alpha3: 'EST', alpha2: 'EE', numericCode: 233, name: 'Estonia'), - 'SWZ': Country(alpha3: 'SWZ', alpha2: 'SZ', numericCode: 748, name: 'Eswatini'), - 'ETH': Country(alpha3: 'ETH', alpha2: 'ET', numericCode: 231, name: 'Ethiopia'), - 'FLK': Country(alpha3: 'FLK', alpha2: 'FK', numericCode: 238, name: 'Falkland Islands (Malvinas)'), - 'FRO': Country(alpha3: 'FRO', alpha2: 'FO', numericCode: 234, name: 'Faroe Islands'), + 'CUW': + Country(alpha3: 'CUW', alpha2: 'CW', numericCode: 531, name: 'Curaçao'), + 'CYP': + Country(alpha3: 'CYP', alpha2: 'CY', numericCode: 196, name: 'Cyprus'), + 'CZE': + Country(alpha3: 'CZE', alpha2: 'CZ', numericCode: 203, name: 'Czechia'), + 'DNK': + Country(alpha3: 'DNK', alpha2: 'DK', numericCode: 208, name: 'Denmark'), + 'DJI': Country( + alpha3: 'DJI', alpha2: 'DJ', numericCode: 262, name: 'Djibouti'), + 'DMA': Country( + alpha3: 'DMA', alpha2: 'DM', numericCode: 212, name: 'Dominica'), + 'DOM': Country( + alpha3: 'DOM', + alpha2: 'DO', + numericCode: 214, + name: 'Dominican Republic'), + 'ECU': + Country(alpha3: 'ECU', alpha2: 'EC', numericCode: 218, name: 'Ecuador'), + 'EGY': + Country(alpha3: 'EGY', alpha2: 'EG', numericCode: 818, name: 'Egypt'), + 'SLV': Country( + alpha3: 'SLV', alpha2: 'SV', numericCode: 222, name: 'El Salvador'), + 'GNQ': Country( + alpha3: 'GNQ', + alpha2: 'GQ', + numericCode: 226, + name: 'Equatorial Guinea'), + 'ERI': + Country(alpha3: 'ERI', alpha2: 'ER', numericCode: 232, name: 'Eritrea'), + 'EST': + Country(alpha3: 'EST', alpha2: 'EE', numericCode: 233, name: 'Estonia'), + 'SWZ': Country( + alpha3: 'SWZ', alpha2: 'SZ', numericCode: 748, name: 'Eswatini'), + 'ETH': Country( + alpha3: 'ETH', alpha2: 'ET', numericCode: 231, name: 'Ethiopia'), + 'FLK': Country( + alpha3: 'FLK', + alpha2: 'FK', + numericCode: 238, + name: 'Falkland Islands (Malvinas)'), + 'FRO': Country( + alpha3: 'FRO', alpha2: 'FO', numericCode: 234, name: 'Faroe Islands'), 'FJI': Country(alpha3: 'FJI', alpha2: 'FJ', numericCode: 242, name: 'Fiji'), - 'FIN': Country(alpha3: 'FIN', alpha2: 'FI', numericCode: 246, name: 'Finland'), - 'FRA': Country(alpha3: 'FRA', alpha2: 'FR', numericCode: 250, name: 'France'), - 'GUF': Country(alpha3: 'GUF', alpha2: 'GF', numericCode: 254, name: 'French Guiana'), - 'PYF': Country(alpha3: 'PYF', alpha2: 'PF', numericCode: 258, name: 'French Polynesia'), - 'ATF': Country(alpha3: 'ATF', alpha2: 'TF', numericCode: 260, name: 'French Southern Territories'), - 'GAB': Country(alpha3: 'GAB', alpha2: 'GA', numericCode: 266, name: 'Gabon'), - 'GMB': Country(alpha3: 'GMB', alpha2: 'GM', numericCode: 270, name: 'Gambia'), - 'GEO': Country(alpha3: 'GEO', alpha2: 'GE', numericCode: 268, name: 'Georgia'), - 'DEU': Country(alpha3: 'DEU', alpha2: 'DE', numericCode: 276, name: 'Germany'), - 'GHA': Country(alpha3: 'GHA', alpha2: 'GH', numericCode: 288, name: 'Ghana'), - 'GIB': Country(alpha3: 'GIB', alpha2: 'GI', numericCode: 292, name: 'Gibraltar'), - 'GRC': Country(alpha3: 'GRC', alpha2: 'GR', numericCode: 300, name: 'Greece'), - 'GRL': Country(alpha3: 'GRL', alpha2: 'GL', numericCode: 304, name: 'Greenland'), - 'GRD': Country(alpha3: 'GRD', alpha2: 'GD', numericCode: 308, name: 'Grenada'), - 'GLP': Country(alpha3: 'GLP', alpha2: 'GP', numericCode: 312, name: 'Guadeloupe'), + 'FIN': + Country(alpha3: 'FIN', alpha2: 'FI', numericCode: 246, name: 'Finland'), + 'FRA': + Country(alpha3: 'FRA', alpha2: 'FR', numericCode: 250, name: 'France'), + 'GUF': Country( + alpha3: 'GUF', alpha2: 'GF', numericCode: 254, name: 'French Guiana'), + 'PYF': Country( + alpha3: 'PYF', + alpha2: 'PF', + numericCode: 258, + name: 'French Polynesia'), + 'ATF': Country( + alpha3: 'ATF', + alpha2: 'TF', + numericCode: 260, + name: 'French Southern Territories'), + 'GAB': + Country(alpha3: 'GAB', alpha2: 'GA', numericCode: 266, name: 'Gabon'), + 'GMB': + Country(alpha3: 'GMB', alpha2: 'GM', numericCode: 270, name: 'Gambia'), + 'GEO': + Country(alpha3: 'GEO', alpha2: 'GE', numericCode: 268, name: 'Georgia'), + 'DEU': + Country(alpha3: 'DEU', alpha2: 'DE', numericCode: 276, name: 'Germany'), + 'GHA': + Country(alpha3: 'GHA', alpha2: 'GH', numericCode: 288, name: 'Ghana'), + 'GIB': Country( + alpha3: 'GIB', alpha2: 'GI', numericCode: 292, name: 'Gibraltar'), + 'GRC': + Country(alpha3: 'GRC', alpha2: 'GR', numericCode: 300, name: 'Greece'), + 'GRL': Country( + alpha3: 'GRL', alpha2: 'GL', numericCode: 304, name: 'Greenland'), + 'GRD': + Country(alpha3: 'GRD', alpha2: 'GD', numericCode: 308, name: 'Grenada'), + 'GLP': Country( + alpha3: 'GLP', alpha2: 'GP', numericCode: 312, name: 'Guadeloupe'), 'GUM': Country(alpha3: 'GUM', alpha2: 'GU', numericCode: 316, name: 'Guam'), - 'GTM': Country(alpha3: 'GTM', alpha2: 'GT', numericCode: 320, name: 'Guatemala'), - 'GGY': Country(alpha3: 'GGY', alpha2: 'GG', numericCode: 831, name: 'Guernsey'), - 'GIN': Country(alpha3: 'GIN', alpha2: 'GN', numericCode: 324, name: 'Guinea'), - 'GNB': Country(alpha3: 'GNB', alpha2: 'GW', numericCode: 624, name: 'Guinea-Bissau'), - 'GUY': Country(alpha3: 'GUY', alpha2: 'GY', numericCode: 328, name: 'Guyana'), - 'HTI': Country(alpha3: 'HTI', alpha2: 'HT', numericCode: 332, name: 'Haiti'), - 'HMD': Country(alpha3: 'HMD', alpha2: 'HM', numericCode: 334, name: 'Heard Island and McDonald Islands'), - 'VAT': Country(alpha3: 'VAT', alpha2: 'VA', numericCode: 336, name: 'Holy See'), - 'HND': Country(alpha3: 'HND', alpha2: 'HN', numericCode: 340, name: 'Honduras'), - 'HKG': Country(alpha3: 'HKG', alpha2: 'HK', numericCode: 344, name: 'Hong Kong'), - 'HUN': Country(alpha3: 'HUN', alpha2: 'HU', numericCode: 348, name: 'Hungary'), - 'ISL': Country(alpha3: 'ISL', alpha2: 'IS', numericCode: 352, name: 'Iceland'), - 'IND': Country(alpha3: 'IND', alpha2: 'IN', numericCode: 356, name: 'India'), - 'IDN': Country(alpha3: 'IDN', alpha2: 'ID', numericCode: 360, name: 'Indonesia'), - 'IRN': Country(alpha3: 'IRN', alpha2: 'IR', numericCode: 364, name: 'Iran (Islamic Republic of)'), + 'GTM': Country( + alpha3: 'GTM', alpha2: 'GT', numericCode: 320, name: 'Guatemala'), + 'GGY': Country( + alpha3: 'GGY', alpha2: 'GG', numericCode: 831, name: 'Guernsey'), + 'GIN': + Country(alpha3: 'GIN', alpha2: 'GN', numericCode: 324, name: 'Guinea'), + 'GNB': Country( + alpha3: 'GNB', alpha2: 'GW', numericCode: 624, name: 'Guinea-Bissau'), + 'GUY': + Country(alpha3: 'GUY', alpha2: 'GY', numericCode: 328, name: 'Guyana'), + 'HTI': + Country(alpha3: 'HTI', alpha2: 'HT', numericCode: 332, name: 'Haiti'), + 'HMD': Country( + alpha3: 'HMD', + alpha2: 'HM', + numericCode: 334, + name: 'Heard Island and McDonald Islands'), + 'VAT': Country( + alpha3: 'VAT', alpha2: 'VA', numericCode: 336, name: 'Holy See'), + 'HND': Country( + alpha3: 'HND', alpha2: 'HN', numericCode: 340, name: 'Honduras'), + 'HKG': Country( + alpha3: 'HKG', alpha2: 'HK', numericCode: 344, name: 'Hong Kong'), + 'HUN': + Country(alpha3: 'HUN', alpha2: 'HU', numericCode: 348, name: 'Hungary'), + 'ISL': + Country(alpha3: 'ISL', alpha2: 'IS', numericCode: 352, name: 'Iceland'), + 'IND': + Country(alpha3: 'IND', alpha2: 'IN', numericCode: 356, name: 'India'), + 'IDN': Country( + alpha3: 'IDN', alpha2: 'ID', numericCode: 360, name: 'Indonesia'), + 'IRN': Country( + alpha3: 'IRN', + alpha2: 'IR', + numericCode: 364, + name: 'Iran (Islamic Republic of)'), 'IRQ': Country(alpha3: 'IRQ', alpha2: 'IQ', numericCode: 368, name: 'Iraq'), - 'IRL': Country(alpha3: 'IRL', alpha2: 'IE', numericCode: 372, name: 'Ireland'), - 'IMN': Country(alpha3: 'IMN', alpha2: 'IM', numericCode: 833, name: 'Isle of Man'), - 'ISR': Country(alpha3: 'ISR', alpha2: 'IL', numericCode: 376, name: 'Israel'), - 'ITA': Country(alpha3: 'ITA', alpha2: 'IT', numericCode: 380, name: 'Italy'), - 'JAM': Country(alpha3: 'JAM', alpha2: 'JM', numericCode: 388, name: 'Jamaica'), - 'JPN': Country(alpha3: 'JPN', alpha2: 'JP', numericCode: 392, name: 'Japan'), - 'JEY': Country(alpha3: 'JEY', alpha2: 'JE', numericCode: 832, name: 'Jersey'), - 'JOR': Country(alpha3: 'JOR', alpha2: 'JO', numericCode: 400, name: 'Jordan'), - 'KAZ': Country(alpha3: 'KAZ', alpha2: 'KZ', numericCode: 398, name: 'Kazakhstan'), - 'KEN': Country(alpha3: 'KEN', alpha2: 'KE', numericCode: 404, name: 'Kenya'), - 'KIR': Country(alpha3: 'KIR', alpha2: 'KI', numericCode: 296, name: 'Kiribati'), - 'PRK': Country(alpha3: 'PRK', alpha2: 'KP', numericCode: 408, name: 'Korea (Democratic People\'s Republic of)'), - 'KOR': Country(alpha3: 'KOR', alpha2: 'KR', numericCode: 410, name: 'Korea, Republic of'), - 'KWT': Country(alpha3: 'KWT', alpha2: 'KW', numericCode: 414, name: 'Kuwait'), - 'KGZ': Country(alpha3: 'KGZ', alpha2: 'KG', numericCode: 417, name: 'Kyrgyzstan'), - 'LAO': Country(alpha3: 'LAO', alpha2: 'LA', numericCode: 418, name: 'Lao People\'s Democratic Republic'), - 'LVA': Country(alpha3: 'LVA', alpha2: 'LV', numericCode: 428, name: 'Latvia'), - 'LBN': Country(alpha3: 'LBN', alpha2: 'LB', numericCode: 422, name: 'Lebanon'), - 'LSO': Country(alpha3: 'LSO', alpha2: 'LS', numericCode: 426, name: 'Lesotho'), - 'LBR': Country(alpha3: 'LBR', alpha2: 'LR', numericCode: 430, name: 'Liberia'), - 'LBY': Country(alpha3: 'LBY', alpha2: 'LY', numericCode: 434, name: 'Libya'), - 'LIE': Country(alpha3: 'LIE', alpha2: 'LI', numericCode: 438, name: 'Liechtenstein'), - 'LTU': Country(alpha3: 'LTU', alpha2: 'LT', numericCode: 440, name: 'Lithuania'), - 'LUX': Country(alpha3: 'LUX', alpha2: 'LU', numericCode: 442, name: 'Luxembourg'), - 'MAC': Country(alpha3: 'MAC', alpha2: 'MO', numericCode: 446, name: 'Macao'), - 'MDG': Country(alpha3: 'MDG', alpha2: 'MG', numericCode: 450, name: 'Madagascar'), - 'MWI': Country(alpha3: 'MWI', alpha2: 'MW', numericCode: 454, name: 'Malawi'), - 'MYS': Country(alpha3: 'MYS', alpha2: 'MY', numericCode: 458, name: 'Malaysia'), - 'MDV': Country(alpha3: 'MDV', alpha2: 'MV', numericCode: 462, name: 'Maldives'), + 'IRL': + Country(alpha3: 'IRL', alpha2: 'IE', numericCode: 372, name: 'Ireland'), + 'IMN': Country( + alpha3: 'IMN', alpha2: 'IM', numericCode: 833, name: 'Isle of Man'), + 'ISR': + Country(alpha3: 'ISR', alpha2: 'IL', numericCode: 376, name: 'Israel'), + 'ITA': + Country(alpha3: 'ITA', alpha2: 'IT', numericCode: 380, name: 'Italy'), + 'JAM': + Country(alpha3: 'JAM', alpha2: 'JM', numericCode: 388, name: 'Jamaica'), + 'JPN': + Country(alpha3: 'JPN', alpha2: 'JP', numericCode: 392, name: 'Japan'), + 'JEY': + Country(alpha3: 'JEY', alpha2: 'JE', numericCode: 832, name: 'Jersey'), + 'JOR': + Country(alpha3: 'JOR', alpha2: 'JO', numericCode: 400, name: 'Jordan'), + 'KAZ': Country( + alpha3: 'KAZ', alpha2: 'KZ', numericCode: 398, name: 'Kazakhstan'), + 'KEN': + Country(alpha3: 'KEN', alpha2: 'KE', numericCode: 404, name: 'Kenya'), + 'KIR': Country( + alpha3: 'KIR', alpha2: 'KI', numericCode: 296, name: 'Kiribati'), + 'PRK': Country( + alpha3: 'PRK', + alpha2: 'KP', + numericCode: 408, + name: 'Korea (Democratic People\'s Republic of)'), + 'KOR': Country( + alpha3: 'KOR', + alpha2: 'KR', + numericCode: 410, + name: 'Korea, Republic of'), + 'KWT': + Country(alpha3: 'KWT', alpha2: 'KW', numericCode: 414, name: 'Kuwait'), + 'KGZ': Country( + alpha3: 'KGZ', alpha2: 'KG', numericCode: 417, name: 'Kyrgyzstan'), + 'LAO': Country( + alpha3: 'LAO', + alpha2: 'LA', + numericCode: 418, + name: 'Lao People\'s Democratic Republic'), + 'LVA': + Country(alpha3: 'LVA', alpha2: 'LV', numericCode: 428, name: 'Latvia'), + 'LBN': + Country(alpha3: 'LBN', alpha2: 'LB', numericCode: 422, name: 'Lebanon'), + 'LSO': + Country(alpha3: 'LSO', alpha2: 'LS', numericCode: 426, name: 'Lesotho'), + 'LBR': + Country(alpha3: 'LBR', alpha2: 'LR', numericCode: 430, name: 'Liberia'), + 'LBY': + Country(alpha3: 'LBY', alpha2: 'LY', numericCode: 434, name: 'Libya'), + 'LIE': Country( + alpha3: 'LIE', alpha2: 'LI', numericCode: 438, name: 'Liechtenstein'), + 'LTU': Country( + alpha3: 'LTU', alpha2: 'LT', numericCode: 440, name: 'Lithuania'), + 'LUX': Country( + alpha3: 'LUX', alpha2: 'LU', numericCode: 442, name: 'Luxembourg'), + 'MAC': + Country(alpha3: 'MAC', alpha2: 'MO', numericCode: 446, name: 'Macao'), + 'MDG': Country( + alpha3: 'MDG', alpha2: 'MG', numericCode: 450, name: 'Madagascar'), + 'MWI': + Country(alpha3: 'MWI', alpha2: 'MW', numericCode: 454, name: 'Malawi'), + 'MYS': Country( + alpha3: 'MYS', alpha2: 'MY', numericCode: 458, name: 'Malaysia'), + 'MDV': Country( + alpha3: 'MDV', alpha2: 'MV', numericCode: 462, name: 'Maldives'), 'MLI': Country(alpha3: 'MLI', alpha2: 'ML', numericCode: 466, name: 'Mali'), - 'MLT': Country(alpha3: 'MLT', alpha2: 'MT', numericCode: 470, name: 'Malta'), - 'MHL': Country(alpha3: 'MHL', alpha2: 'MH', numericCode: 584, name: 'Marshall Islands'), - 'MTQ': Country(alpha3: 'MTQ', alpha2: 'MQ', numericCode: 474, name: 'Martinique'), - 'MRT': Country(alpha3: 'MRT', alpha2: 'MR', numericCode: 478, name: 'Mauritania'), - 'MUS': Country(alpha3: 'MUS', alpha2: 'MU', numericCode: 480, name: 'Mauritius'), - 'MYT': Country(alpha3: 'MYT', alpha2: 'YT', numericCode: 175, name: 'Mayotte'), - 'MEX': Country(alpha3: 'MEX', alpha2: 'MX', numericCode: 484, name: 'Mexico'), - 'FSM': Country(alpha3: 'FSM', alpha2: 'FM', numericCode: 583, name: 'Micronesia (Federated States of)'), - 'MDA': Country(alpha3: 'MDA', alpha2: 'MD', numericCode: 498, name: 'Moldova, Republic of'), - 'MCO': Country(alpha3: 'MCO', alpha2: 'MC', numericCode: 492, name: 'Monaco'), - 'MNG': Country(alpha3: 'MNG', alpha2: 'MN', numericCode: 496, name: 'Mongolia'), - 'MNE': Country(alpha3: 'MNE', alpha2: 'ME', numericCode: 499, name: 'Montenegro'), - 'MSR': Country(alpha3: 'MSR', alpha2: 'MS', numericCode: 500, name: 'Montserrat'), - 'MAR': Country(alpha3: 'MAR', alpha2: 'MA', numericCode: 504, name: 'Morocco'), - 'MOZ': Country(alpha3: 'MOZ', alpha2: 'MZ', numericCode: 508, name: 'Mozambique'), - 'MMR': Country(alpha3: 'MMR', alpha2: 'MM', numericCode: 104, name: 'Myanmar'), - 'NAM': Country(alpha3: 'NAM', alpha2: 'NA', numericCode: 516, name: 'Namibia'), - 'NRU': Country(alpha3: 'NRU', alpha2: 'NR', numericCode: 520, name: 'Nauru'), - 'NPL': Country(alpha3: 'NPL', alpha2: 'NP', numericCode: 524, name: 'Nepal'), - 'NLD': Country(alpha3: 'NLD', alpha2: 'NL', numericCode: 528, name: 'Netherlands'), - 'NCL': Country(alpha3: 'NCL', alpha2: 'NC', numericCode: 540, name: 'New Caledonia'), - 'NZL': Country(alpha3: 'NZL', alpha2: 'NZ', numericCode: 554, name: 'New Zealand'), - 'NIC': Country(alpha3: 'NIC', alpha2: 'NI', numericCode: 558, name: 'Nicaragua'), - 'NER': Country(alpha3: 'NER', alpha2: 'NE', numericCode: 562, name: 'Niger'), - 'NGA': Country(alpha3: 'NGA', alpha2: 'NG', numericCode: 566, name: 'Nigeria'), + 'MLT': + Country(alpha3: 'MLT', alpha2: 'MT', numericCode: 470, name: 'Malta'), + 'MHL': Country( + alpha3: 'MHL', + alpha2: 'MH', + numericCode: 584, + name: 'Marshall Islands'), + 'MTQ': Country( + alpha3: 'MTQ', alpha2: 'MQ', numericCode: 474, name: 'Martinique'), + 'MRT': Country( + alpha3: 'MRT', alpha2: 'MR', numericCode: 478, name: 'Mauritania'), + 'MUS': Country( + alpha3: 'MUS', alpha2: 'MU', numericCode: 480, name: 'Mauritius'), + 'MYT': + Country(alpha3: 'MYT', alpha2: 'YT', numericCode: 175, name: 'Mayotte'), + 'MEX': + Country(alpha3: 'MEX', alpha2: 'MX', numericCode: 484, name: 'Mexico'), + 'FSM': Country( + alpha3: 'FSM', + alpha2: 'FM', + numericCode: 583, + name: 'Micronesia (Federated States of)'), + 'MDA': Country( + alpha3: 'MDA', + alpha2: 'MD', + numericCode: 498, + name: 'Moldova, Republic of'), + 'MCO': + Country(alpha3: 'MCO', alpha2: 'MC', numericCode: 492, name: 'Monaco'), + 'MNG': Country( + alpha3: 'MNG', alpha2: 'MN', numericCode: 496, name: 'Mongolia'), + 'MNE': Country( + alpha3: 'MNE', alpha2: 'ME', numericCode: 499, name: 'Montenegro'), + 'MSR': Country( + alpha3: 'MSR', alpha2: 'MS', numericCode: 500, name: 'Montserrat'), + 'MAR': + Country(alpha3: 'MAR', alpha2: 'MA', numericCode: 504, name: 'Morocco'), + 'MOZ': Country( + alpha3: 'MOZ', alpha2: 'MZ', numericCode: 508, name: 'Mozambique'), + 'MMR': + Country(alpha3: 'MMR', alpha2: 'MM', numericCode: 104, name: 'Myanmar'), + 'NAM': + Country(alpha3: 'NAM', alpha2: 'NA', numericCode: 516, name: 'Namibia'), + 'NRU': + Country(alpha3: 'NRU', alpha2: 'NR', numericCode: 520, name: 'Nauru'), + 'NPL': + Country(alpha3: 'NPL', alpha2: 'NP', numericCode: 524, name: 'Nepal'), + 'NLD': Country( + alpha3: 'NLD', alpha2: 'NL', numericCode: 528, name: 'Netherlands'), + 'NCL': Country( + alpha3: 'NCL', alpha2: 'NC', numericCode: 540, name: 'New Caledonia'), + 'NZL': Country( + alpha3: 'NZL', alpha2: 'NZ', numericCode: 554, name: 'New Zealand'), + 'NIC': Country( + alpha3: 'NIC', alpha2: 'NI', numericCode: 558, name: 'Nicaragua'), + 'NER': + Country(alpha3: 'NER', alpha2: 'NE', numericCode: 562, name: 'Niger'), + 'NGA': + Country(alpha3: 'NGA', alpha2: 'NG', numericCode: 566, name: 'Nigeria'), 'NIU': Country(alpha3: 'NIU', alpha2: 'NU', numericCode: 570, name: 'Niue'), - 'NFK': Country(alpha3: 'NFK', alpha2: 'NF', numericCode: 574, name: 'Norfolk Island'), - 'MKD': Country(alpha3: 'MKD', alpha2: 'MK', numericCode: 807, name: 'North Macedonia'), - 'MNP': Country(alpha3: 'MNP', alpha2: 'MP', numericCode: 580, name: 'Northern Mariana Islands'), - 'NOR': Country(alpha3: 'NOR', alpha2: 'NO', numericCode: 578, name: 'Norway'), + 'NFK': Country( + alpha3: 'NFK', alpha2: 'NF', numericCode: 574, name: 'Norfolk Island'), + 'MKD': Country( + alpha3: 'MKD', alpha2: 'MK', numericCode: 807, name: 'North Macedonia'), + 'MNP': Country( + alpha3: 'MNP', + alpha2: 'MP', + numericCode: 580, + name: 'Northern Mariana Islands'), + 'NOR': + Country(alpha3: 'NOR', alpha2: 'NO', numericCode: 578, name: 'Norway'), 'OMN': Country(alpha3: 'OMN', alpha2: 'OM', numericCode: 512, name: 'Oman'), - 'PAK': Country(alpha3: 'PAK', alpha2: 'PK', numericCode: 586, name: 'Pakistan'), - 'PLW': Country(alpha3: 'PLW', alpha2: 'PW', numericCode: 585, name: 'Palau'), - 'PSE': Country(alpha3: 'PSE', alpha2: 'PS', numericCode: 275, name: 'Palestine, State of'), - 'PAN': Country(alpha3: 'PAN', alpha2: 'PA', numericCode: 591, name: 'Panama'), - 'PNG': Country(alpha3: 'PNG', alpha2: 'PG', numericCode: 598, name: 'Papua New Guinea'), - 'PRY': Country(alpha3: 'PRY', alpha2: 'PY', numericCode: 600, name: 'Paraguay'), + 'PAK': Country( + alpha3: 'PAK', alpha2: 'PK', numericCode: 586, name: 'Pakistan'), + 'PLW': + Country(alpha3: 'PLW', alpha2: 'PW', numericCode: 585, name: 'Palau'), + 'PSE': Country( + alpha3: 'PSE', + alpha2: 'PS', + numericCode: 275, + name: 'Palestine, State of'), + 'PAN': + Country(alpha3: 'PAN', alpha2: 'PA', numericCode: 591, name: 'Panama'), + 'PNG': Country( + alpha3: 'PNG', + alpha2: 'PG', + numericCode: 598, + name: 'Papua New Guinea'), + 'PRY': Country( + alpha3: 'PRY', alpha2: 'PY', numericCode: 600, name: 'Paraguay'), 'PER': Country(alpha3: 'PER', alpha2: 'PE', numericCode: 604, name: 'Peru'), - 'PHL': Country(alpha3: 'PHL', alpha2: 'PH', numericCode: 608, name: 'Philippines'), - 'PCN': Country(alpha3: 'PCN', alpha2: 'PN', numericCode: 612, name: 'Pitcairn'), - 'POL': Country(alpha3: 'POL', alpha2: 'PL', numericCode: 616, name: 'Poland'), - 'PRT': Country(alpha3: 'PRT', alpha2: 'PT', numericCode: 620, name: 'Portugal'), - 'PRI': Country(alpha3: 'PRI', alpha2: 'PR', numericCode: 630, name: 'Puerto Rico'), - 'QAT': Country(alpha3: 'QAT', alpha2: 'QA', numericCode: 634, name: 'Qatar'), - 'REU': Country(alpha3: 'REU', alpha2: 'RE', numericCode: 638, name: 'Réunion'), - 'ROU': Country(alpha3: 'ROU', alpha2: 'RO', numericCode: 642, name: 'Romania'), - 'RUS': Country(alpha3: 'RUS', alpha2: 'RU', numericCode: 643, name: 'Russian Federation'), - 'RWA': Country(alpha3: 'RWA', alpha2: 'RW', numericCode: 646, name: 'Rwanda'), - 'BLM': Country(alpha3: 'BLM', alpha2: 'BL', numericCode: 652, name: 'Saint Barthélemy'), - 'SHN': Country(alpha3: 'SHN', alpha2: 'SH', numericCode: 654, name: 'Saint Helena, Ascension and Tristan da Cunha[e]'), - 'KNA': Country(alpha3: 'KNA', alpha2: 'KN', numericCode: 659, name: 'Saint Kitts and Nevis'), - 'LCA': Country(alpha3: 'LCA', alpha2: 'LC', numericCode: 662, name: 'Saint Lucia'), - 'MAF': Country(alpha3: 'MAF', alpha2: 'MF', numericCode: 663, name: 'Saint Martin (French part)'), - 'SPM': Country(alpha3: 'SPM', alpha2: 'PM', numericCode: 666, name: 'Saint Pierre and Miquelon'), - 'VCT': Country(alpha3: 'VCT', alpha2: 'VC', numericCode: 670, name: 'Saint Vincent and the Grenadines'), - 'WSM': Country(alpha3: 'WSM', alpha2: 'WS', numericCode: 882, name: 'Samoa'), - 'SMR': Country(alpha3: 'SMR', alpha2: 'SM', numericCode: 674, name: 'San Marino'), - 'STP': Country(alpha3: 'STP', alpha2: 'ST', numericCode: 678, name: 'Sao Tome and Principe'), - 'SAU': Country(alpha3: 'SAU', alpha2: 'SA', numericCode: 682, name: 'Saudi Arabia'), - 'SEN': Country(alpha3: 'SEN', alpha2: 'SN', numericCode: 686, name: 'Senegal'), - 'SRB': Country(alpha3: 'SRB', alpha2: 'RS', numericCode: 688, name: 'Serbia'), - 'SYC': Country(alpha3: 'SYC', alpha2: 'SC', numericCode: 690, name: 'Seychelles'), - 'SLE': Country(alpha3: 'SLE', alpha2: 'SL', numericCode: 694, name: 'Sierra Leone'), - 'SGP': Country(alpha3: 'SGP', alpha2: 'SG', numericCode: 702, name: 'Singapore'), - 'SXM': Country(alpha3: 'SXM', alpha2: 'SX', numericCode: 534, name: 'Sint Maarten (Dutch part)'), - 'SVK': Country(alpha3: 'SVK', alpha2: 'SK', numericCode: 703, name: 'Slovakia'), - 'SVN': Country(alpha3: 'SVN', alpha2: 'SI', numericCode: 705, name: 'Slovenia'), - 'SLB': Country(alpha3: 'SLB', alpha2: 'SB', numericCode: 90, name: 'Solomon Islands'), - 'SOM': Country(alpha3: 'SOM', alpha2: 'SO', numericCode: 706, name: 'Somalia'), - 'ZAF': Country(alpha3: 'ZAF', alpha2: 'ZA', numericCode: 710, name: 'South Africa'), - 'SGS': Country(alpha3: 'SGS', alpha2: 'GS', numericCode: 239, name: 'South Georgia and the South Sandwich Islands'), - 'SSD': Country(alpha3: 'SSD', alpha2: 'SS', numericCode: 728, name: 'South Sudan'), - 'ESP': Country(alpha3: 'ESP', alpha2: 'ES', numericCode: 724, name: 'Spain'), - 'LKA': Country(alpha3: 'LKA', alpha2: 'LK', numericCode: 144, name: 'Sri Lanka'), - 'SDN': Country(alpha3: 'SDN', alpha2: 'SD', numericCode: 729, name: 'Sudan'), - 'SUR': Country(alpha3: 'SUR', alpha2: 'SR', numericCode: 740, name: 'Suriname'), - 'SJM': Country(alpha3: 'SJM', alpha2: 'SJ', numericCode: 744, name: 'Svalbard and Jan Mayen[f]'), - 'SWE': Country(alpha3: 'SWE', alpha2: 'SE', numericCode: 752, name: 'Sweden'), - 'CHE': Country(alpha3: 'CHE', alpha2: 'CH', numericCode: 756, name: 'Switzerland'), - 'SYR': Country(alpha3: 'SYR', alpha2: 'SY', numericCode: 760, name: 'Syrian Arab Republic'), - 'TWN': Country(alpha3: 'TWN', alpha2: 'TW', numericCode: 158, name: 'Taiwan, Province of China'), - 'TJK': Country(alpha3: 'TJK', alpha2: 'TJ', numericCode: 762, name: 'Tajikistan'), - 'TZA': Country(alpha3: 'TZA', alpha2: 'TZ', numericCode: 834, name: 'Tanzania, United Republic of'), - 'THA': Country(alpha3: 'THA', alpha2: 'TH', numericCode: 764, name: 'Thailand'), - 'TLS': Country(alpha3: 'TLS', alpha2: 'TL', numericCode: 626, name: 'Timor-Leste'), + 'PHL': Country( + alpha3: 'PHL', alpha2: 'PH', numericCode: 608, name: 'Philippines'), + 'PCN': Country( + alpha3: 'PCN', alpha2: 'PN', numericCode: 612, name: 'Pitcairn'), + 'POL': + Country(alpha3: 'POL', alpha2: 'PL', numericCode: 616, name: 'Poland'), + 'PRT': Country( + alpha3: 'PRT', alpha2: 'PT', numericCode: 620, name: 'Portugal'), + 'PRI': Country( + alpha3: 'PRI', alpha2: 'PR', numericCode: 630, name: 'Puerto Rico'), + 'QAT': + Country(alpha3: 'QAT', alpha2: 'QA', numericCode: 634, name: 'Qatar'), + 'REU': + Country(alpha3: 'REU', alpha2: 'RE', numericCode: 638, name: 'Réunion'), + 'ROU': + Country(alpha3: 'ROU', alpha2: 'RO', numericCode: 642, name: 'Romania'), + 'RUS': Country( + alpha3: 'RUS', + alpha2: 'RU', + numericCode: 643, + name: 'Russian Federation'), + 'RWA': + Country(alpha3: 'RWA', alpha2: 'RW', numericCode: 646, name: 'Rwanda'), + 'BLM': Country( + alpha3: 'BLM', + alpha2: 'BL', + numericCode: 652, + name: 'Saint Barthélemy'), + 'SHN': Country( + alpha3: 'SHN', + alpha2: 'SH', + numericCode: 654, + name: 'Saint Helena, Ascension and Tristan da Cunha[e]'), + 'KNA': Country( + alpha3: 'KNA', + alpha2: 'KN', + numericCode: 659, + name: 'Saint Kitts and Nevis'), + 'LCA': Country( + alpha3: 'LCA', alpha2: 'LC', numericCode: 662, name: 'Saint Lucia'), + 'MAF': Country( + alpha3: 'MAF', + alpha2: 'MF', + numericCode: 663, + name: 'Saint Martin (French part)'), + 'SPM': Country( + alpha3: 'SPM', + alpha2: 'PM', + numericCode: 666, + name: 'Saint Pierre and Miquelon'), + 'VCT': Country( + alpha3: 'VCT', + alpha2: 'VC', + numericCode: 670, + name: 'Saint Vincent and the Grenadines'), + 'WSM': + Country(alpha3: 'WSM', alpha2: 'WS', numericCode: 882, name: 'Samoa'), + 'SMR': Country( + alpha3: 'SMR', alpha2: 'SM', numericCode: 674, name: 'San Marino'), + 'STP': Country( + alpha3: 'STP', + alpha2: 'ST', + numericCode: 678, + name: 'Sao Tome and Principe'), + 'SAU': Country( + alpha3: 'SAU', alpha2: 'SA', numericCode: 682, name: 'Saudi Arabia'), + 'SEN': + Country(alpha3: 'SEN', alpha2: 'SN', numericCode: 686, name: 'Senegal'), + 'SRB': + Country(alpha3: 'SRB', alpha2: 'RS', numericCode: 688, name: 'Serbia'), + 'SYC': Country( + alpha3: 'SYC', alpha2: 'SC', numericCode: 690, name: 'Seychelles'), + 'SLE': Country( + alpha3: 'SLE', alpha2: 'SL', numericCode: 694, name: 'Sierra Leone'), + 'SGP': Country( + alpha3: 'SGP', alpha2: 'SG', numericCode: 702, name: 'Singapore'), + 'SXM': Country( + alpha3: 'SXM', + alpha2: 'SX', + numericCode: 534, + name: 'Sint Maarten (Dutch part)'), + 'SVK': Country( + alpha3: 'SVK', alpha2: 'SK', numericCode: 703, name: 'Slovakia'), + 'SVN': Country( + alpha3: 'SVN', alpha2: 'SI', numericCode: 705, name: 'Slovenia'), + 'SLB': Country( + alpha3: 'SLB', alpha2: 'SB', numericCode: 90, name: 'Solomon Islands'), + 'SOM': + Country(alpha3: 'SOM', alpha2: 'SO', numericCode: 706, name: 'Somalia'), + 'ZAF': Country( + alpha3: 'ZAF', alpha2: 'ZA', numericCode: 710, name: 'South Africa'), + 'SGS': Country( + alpha3: 'SGS', + alpha2: 'GS', + numericCode: 239, + name: 'South Georgia and the South Sandwich Islands'), + 'SSD': Country( + alpha3: 'SSD', alpha2: 'SS', numericCode: 728, name: 'South Sudan'), + 'ESP': + Country(alpha3: 'ESP', alpha2: 'ES', numericCode: 724, name: 'Spain'), + 'LKA': Country( + alpha3: 'LKA', alpha2: 'LK', numericCode: 144, name: 'Sri Lanka'), + 'SDN': + Country(alpha3: 'SDN', alpha2: 'SD', numericCode: 729, name: 'Sudan'), + 'SUR': Country( + alpha3: 'SUR', alpha2: 'SR', numericCode: 740, name: 'Suriname'), + 'SJM': Country( + alpha3: 'SJM', + alpha2: 'SJ', + numericCode: 744, + name: 'Svalbard and Jan Mayen[f]'), + 'SWE': + Country(alpha3: 'SWE', alpha2: 'SE', numericCode: 752, name: 'Sweden'), + 'CHE': Country( + alpha3: 'CHE', alpha2: 'CH', numericCode: 756, name: 'Switzerland'), + 'SYR': Country( + alpha3: 'SYR', + alpha2: 'SY', + numericCode: 760, + name: 'Syrian Arab Republic'), + 'TWN': Country( + alpha3: 'TWN', + alpha2: 'TW', + numericCode: 158, + name: 'Taiwan, Province of China'), + 'TJK': Country( + alpha3: 'TJK', alpha2: 'TJ', numericCode: 762, name: 'Tajikistan'), + 'TZA': Country( + alpha3: 'TZA', + alpha2: 'TZ', + numericCode: 834, + name: 'Tanzania, United Republic of'), + 'THA': Country( + alpha3: 'THA', alpha2: 'TH', numericCode: 764, name: 'Thailand'), + 'TLS': Country( + alpha3: 'TLS', alpha2: 'TL', numericCode: 626, name: 'Timor-Leste'), 'TGO': Country(alpha3: 'TGO', alpha2: 'TG', numericCode: 768, name: 'Togo'), - 'TKL': Country(alpha3: 'TKL', alpha2: 'TK', numericCode: 772, name: 'Tokelau'), - 'TON': Country(alpha3: 'TON', alpha2: 'TO', numericCode: 776, name: 'Tonga'), - 'TTO': Country(alpha3: 'TTO', alpha2: 'TT', numericCode: 780, name: 'Trinidad and Tobago'), - 'TUN': Country(alpha3: 'TUN', alpha2: 'TN', numericCode: 788, name: 'Tunisia'), - 'TUR': Country(alpha3: 'TUR', alpha2: 'TR', numericCode: 792, name: 'Turkey'), - 'TKM': Country(alpha3: 'TKM', alpha2: 'TM', numericCode: 795, name: 'Turkmenistan'), - 'TCA': Country(alpha3: 'TCA', alpha2: 'TC', numericCode: 796, name: 'Turks and Caicos Islands'), - 'TUV': Country(alpha3: 'TUV', alpha2: 'TV', numericCode: 798, name: 'Tuvalu'), - 'UGA': Country(alpha3: 'UGA', alpha2: 'UG', numericCode: 800, name: 'Uganda'), - 'UKR': Country(alpha3: 'UKR', alpha2: 'UA', numericCode: 804, name: 'Ukraine'), - 'ARE': Country(alpha3: 'ARE', alpha2: 'AE', numericCode: 784, name: 'United Arab Emirates'), - 'GBR': Country(alpha3: 'GBR', alpha2: 'GB', numericCode: 826, name: 'United Kingdom of Great Britain and Northern Ireland'), - 'USA': Country(alpha3: 'USA', alpha2: 'US', numericCode: 840, name: 'United States of America'), - 'UMI': Country(alpha3: 'UMI', alpha2: 'UM', numericCode: 581, name: 'United States Minor Outlying Islands[h]'), - 'URY': Country(alpha3: 'URY', alpha2: 'UY', numericCode: 858, name: 'Uruguay'), - 'UZB': Country(alpha3: 'UZB', alpha2: 'UZ', numericCode: 860, name: 'Uzbekistan'), - 'VUT': Country(alpha3: 'VUT', alpha2: 'VU', numericCode: 548, name: 'Vanuatu'), - 'VEN': Country(alpha3: 'VEN', alpha2: 'VE', numericCode: 862, name: 'Venezuela (Bolivarian Republic of)'), - 'VNM': Country(alpha3: 'VNM', alpha2: 'VN', numericCode: 704, name: 'Viet Nam'), - 'VGB': Country(alpha3: 'VGB', alpha2: 'VG', numericCode: 92, name: 'Virgin Islands (British)'), - 'VIR': Country(alpha3: 'VIR', alpha2: 'VI', numericCode: 850, name: 'Virgin Islands (U.S.)'), - 'WLF': Country(alpha3: 'WLF', alpha2: 'WF', numericCode: 876, name: 'Wallis and Futuna'), - 'ESH': Country(alpha3: 'ESH', alpha2: 'EH', numericCode: 732, name: 'Western Sahara'), - 'YEM': Country(alpha3: 'YEM', alpha2: 'YE', numericCode: 887, name: 'Yemen'), - 'ZMB': Country(alpha3: 'ZMB', alpha2: 'ZM', numericCode: 894, name: 'Zambia'), - 'ZWE': Country(alpha3: 'ZWE', alpha2: 'ZW', numericCode: 716, name: 'Zimbabwe'), + 'TKL': + Country(alpha3: 'TKL', alpha2: 'TK', numericCode: 772, name: 'Tokelau'), + 'TON': + Country(alpha3: 'TON', alpha2: 'TO', numericCode: 776, name: 'Tonga'), + 'TTO': Country( + alpha3: 'TTO', + alpha2: 'TT', + numericCode: 780, + name: 'Trinidad and Tobago'), + 'TUN': + Country(alpha3: 'TUN', alpha2: 'TN', numericCode: 788, name: 'Tunisia'), + 'TUR': + Country(alpha3: 'TUR', alpha2: 'TR', numericCode: 792, name: 'Turkey'), + 'TKM': Country( + alpha3: 'TKM', alpha2: 'TM', numericCode: 795, name: 'Turkmenistan'), + 'TCA': Country( + alpha3: 'TCA', + alpha2: 'TC', + numericCode: 796, + name: 'Turks and Caicos Islands'), + 'TUV': + Country(alpha3: 'TUV', alpha2: 'TV', numericCode: 798, name: 'Tuvalu'), + 'UGA': + Country(alpha3: 'UGA', alpha2: 'UG', numericCode: 800, name: 'Uganda'), + 'UKR': + Country(alpha3: 'UKR', alpha2: 'UA', numericCode: 804, name: 'Ukraine'), + 'ARE': Country( + alpha3: 'ARE', + alpha2: 'AE', + numericCode: 784, + name: 'United Arab Emirates'), + 'GBR': Country( + alpha3: 'GBR', + alpha2: 'GB', + numericCode: 826, + name: 'United Kingdom of Great Britain and Northern Ireland'), + 'USA': Country( + alpha3: 'USA', + alpha2: 'US', + numericCode: 840, + name: 'United States of America'), + 'UMI': Country( + alpha3: 'UMI', + alpha2: 'UM', + numericCode: 581, + name: 'United States Minor Outlying Islands[h]'), + 'URY': + Country(alpha3: 'URY', alpha2: 'UY', numericCode: 858, name: 'Uruguay'), + 'UZB': Country( + alpha3: 'UZB', alpha2: 'UZ', numericCode: 860, name: 'Uzbekistan'), + 'VUT': + Country(alpha3: 'VUT', alpha2: 'VU', numericCode: 548, name: 'Vanuatu'), + 'VEN': Country( + alpha3: 'VEN', + alpha2: 'VE', + numericCode: 862, + name: 'Venezuela (Bolivarian Republic of)'), + 'VNM': Country( + alpha3: 'VNM', alpha2: 'VN', numericCode: 704, name: 'Viet Nam'), + 'VGB': Country( + alpha3: 'VGB', + alpha2: 'VG', + numericCode: 92, + name: 'Virgin Islands (British)'), + 'VIR': Country( + alpha3: 'VIR', + alpha2: 'VI', + numericCode: 850, + name: 'Virgin Islands (U.S.)'), + 'WLF': Country( + alpha3: 'WLF', + alpha2: 'WF', + numericCode: 876, + name: 'Wallis and Futuna'), + 'ESH': Country( + alpha3: 'ESH', alpha2: 'EH', numericCode: 732, name: 'Western Sahara'), + 'YEM': + Country(alpha3: 'YEM', alpha2: 'YE', numericCode: 887, name: 'Yemen'), + 'ZMB': + Country(alpha3: 'ZMB', alpha2: 'ZM', numericCode: 894, name: 'Zambia'), + 'ZWE': Country( + alpha3: 'ZWE', alpha2: 'ZW', numericCode: 716, name: 'Zimbabwe'), }; - static Map COUNTRIES2 = { - 'AF': COUNTRIES3['AFG']!, - 'AX': COUNTRIES3['ALA']!, - 'AL': COUNTRIES3['ALB']!, - 'DZ': COUNTRIES3['DZA']!, - 'AS': COUNTRIES3['ASM']!, - 'AD': COUNTRIES3['AND']!, - 'AO': COUNTRIES3['AGO']!, - 'AI': COUNTRIES3['AIA']!, - 'AQ': COUNTRIES3['ATA']!, - 'AG': COUNTRIES3['ATG']!, - 'AR': COUNTRIES3['ARG']!, - 'AM': COUNTRIES3['ARM']!, - 'AW': COUNTRIES3['ABW']!, - 'AU': COUNTRIES3['AUS']!, - 'AT': COUNTRIES3['AUT']!, - 'AZ': COUNTRIES3['AZE']!, - 'BS': COUNTRIES3['BHS']!, - 'BH': COUNTRIES3['BHR']!, - 'BD': COUNTRIES3['BGD']!, - 'BB': COUNTRIES3['BRB']!, - 'BY': COUNTRIES3['BLR']!, - 'BE': COUNTRIES3['BEL']!, - 'BZ': COUNTRIES3['BLZ']!, - 'BJ': COUNTRIES3['BEN']!, - 'BM': COUNTRIES3['BMU']!, - 'BT': COUNTRIES3['BTN']!, - 'BO': COUNTRIES3['BOL']!, - 'BQ': COUNTRIES3['BES']!, - 'BA': COUNTRIES3['BIH']!, - 'BW': COUNTRIES3['BWA']!, - 'BV': COUNTRIES3['BVT']!, - 'BR': COUNTRIES3['BRA']!, - 'IO': COUNTRIES3['IOT']!, - 'BN': COUNTRIES3['BRN']!, - 'BG': COUNTRIES3['BGR']!, - 'BF': COUNTRIES3['BFA']!, - 'BI': COUNTRIES3['BDI']!, - 'CV': COUNTRIES3['CPV']!, - 'KH': COUNTRIES3['KHM']!, - 'CM': COUNTRIES3['CMR']!, - 'CA': COUNTRIES3['CAN']!, - 'KY': COUNTRIES3['CYM']!, - 'CF': COUNTRIES3['CAF']!, - 'TD': COUNTRIES3['TCD']!, - 'CL': COUNTRIES3['CHL']!, - 'CN': COUNTRIES3['CHN']!, - 'CX': COUNTRIES3['CXR']!, - 'CC': COUNTRIES3['CCK']!, - 'CO': COUNTRIES3['COL']!, - 'KM': COUNTRIES3['COM']!, - 'CG': COUNTRIES3['COG']!, - 'CD': COUNTRIES3['COD']!, - 'CK': COUNTRIES3['COK']!, - 'CR': COUNTRIES3['CRI']!, - 'CI': COUNTRIES3['CIV']!, - 'HR': COUNTRIES3['HRV']!, - 'CU': COUNTRIES3['CUB']!, - 'CW': COUNTRIES3['CUW']!, - 'CY': COUNTRIES3['CYP']!, - 'CZ': COUNTRIES3['CZE']!, - 'DK': COUNTRIES3['DNK']!, - 'DJ': COUNTRIES3['DJI']!, - 'DM': COUNTRIES3['DMA']!, - 'DO': COUNTRIES3['DOM']!, - 'EC': COUNTRIES3['ECU']!, - 'EG': COUNTRIES3['EGY']!, - 'SV': COUNTRIES3['SLV']!, - 'GQ': COUNTRIES3['GNQ']!, - 'ER': COUNTRIES3['ERI']!, - 'EE': COUNTRIES3['EST']!, - 'SZ': COUNTRIES3['SWZ']!, - 'ET': COUNTRIES3['ETH']!, - 'FK': COUNTRIES3['FLK']!, - 'FO': COUNTRIES3['FRO']!, - 'FJ': COUNTRIES3['FJI']!, - 'FI': COUNTRIES3['FIN']!, - 'FR': COUNTRIES3['FRA']!, - 'GF': COUNTRIES3['GUF']!, - 'PF': COUNTRIES3['PYF']!, - 'TF': COUNTRIES3['ATF']!, - 'GA': COUNTRIES3['GAB']!, - 'GM': COUNTRIES3['GMB']!, - 'GE': COUNTRIES3['GEO']!, - 'DE': COUNTRIES3['DEU']!, - 'GH': COUNTRIES3['GHA']!, - 'GI': COUNTRIES3['GIB']!, - 'GR': COUNTRIES3['GRC']!, - 'GL': COUNTRIES3['GRL']!, - 'GD': COUNTRIES3['GRD']!, - 'GP': COUNTRIES3['GLP']!, - 'GU': COUNTRIES3['GUM']!, - 'GT': COUNTRIES3['GTM']!, - 'GG': COUNTRIES3['GGY']!, - 'GN': COUNTRIES3['GIN']!, - 'GW': COUNTRIES3['GNB']!, - 'GY': COUNTRIES3['GUY']!, - 'HT': COUNTRIES3['HTI']!, - 'HM': COUNTRIES3['HMD']!, - 'VA': COUNTRIES3['VAT']!, - 'HN': COUNTRIES3['HND']!, - 'HK': COUNTRIES3['HKG']!, - 'HU': COUNTRIES3['HUN']!, - 'IS': COUNTRIES3['ISL']!, - 'IN': COUNTRIES3['IND']!, - 'ID': COUNTRIES3['IDN']!, - 'IR': COUNTRIES3['IRN']!, - 'IQ': COUNTRIES3['IRQ']!, - 'IE': COUNTRIES3['IRL']!, - 'IM': COUNTRIES3['IMN']!, - 'IL': COUNTRIES3['ISR']!, - 'IT': COUNTRIES3['ITA']!, - 'JM': COUNTRIES3['JAM']!, - 'JP': COUNTRIES3['JPN']!, - 'JE': COUNTRIES3['JEY']!, - 'JO': COUNTRIES3['JOR']!, - 'KZ': COUNTRIES3['KAZ']!, - 'KE': COUNTRIES3['KEN']!, - 'KI': COUNTRIES3['KIR']!, - 'KP': COUNTRIES3['PRK']!, - 'KR': COUNTRIES3['KOR']!, - 'KW': COUNTRIES3['KWT']!, - 'KG': COUNTRIES3['KGZ']!, - 'LA': COUNTRIES3['LAO']!, - 'LV': COUNTRIES3['LVA']!, - 'LB': COUNTRIES3['LBN']!, - 'LS': COUNTRIES3['LSO']!, - 'LR': COUNTRIES3['LBR']!, - 'LY': COUNTRIES3['LBY']!, - 'LI': COUNTRIES3['LIE']!, - 'LT': COUNTRIES3['LTU']!, - 'LU': COUNTRIES3['LUX']!, - 'MO': COUNTRIES3['MAC']!, - 'MG': COUNTRIES3['MDG']!, - 'MW': COUNTRIES3['MWI']!, - 'MY': COUNTRIES3['MYS']!, - 'MV': COUNTRIES3['MDV']!, - 'ML': COUNTRIES3['MLI']!, - 'MT': COUNTRIES3['MLT']!, - 'MH': COUNTRIES3['MHL']!, - 'MQ': COUNTRIES3['MTQ']!, - 'MR': COUNTRIES3['MRT']!, - 'MU': COUNTRIES3['MUS']!, - 'YT': COUNTRIES3['MYT']!, - 'MX': COUNTRIES3['MEX']!, - 'FM': COUNTRIES3['FSM']!, - 'MD': COUNTRIES3['MDA']!, - 'MC': COUNTRIES3['MCO']!, - 'MN': COUNTRIES3['MNG']!, - 'ME': COUNTRIES3['MNE']!, - 'MS': COUNTRIES3['MSR']!, - 'MA': COUNTRIES3['MAR']!, - 'MZ': COUNTRIES3['MOZ']!, - 'MM': COUNTRIES3['MMR']!, - 'NA': COUNTRIES3['NAM']!, - 'NR': COUNTRIES3['NRU']!, - 'NP': COUNTRIES3['NPL']!, - 'NL': COUNTRIES3['NLD']!, - 'NC': COUNTRIES3['NCL']!, - 'NZ': COUNTRIES3['NZL']!, - 'NI': COUNTRIES3['NIC']!, - 'NE': COUNTRIES3['NER']!, - 'NG': COUNTRIES3['NGA']!, - 'NU': COUNTRIES3['NIU']!, - 'NF': COUNTRIES3['NFK']!, - 'MK': COUNTRIES3['MKD']!, - 'MP': COUNTRIES3['MNP']!, - 'NO': COUNTRIES3['NOR']!, - 'OM': COUNTRIES3['OMN']!, - 'PK': COUNTRIES3['PAK']!, - 'PW': COUNTRIES3['PLW']!, - 'PS': COUNTRIES3['PSE']!, - 'PA': COUNTRIES3['PAN']!, - 'PG': COUNTRIES3['PNG']!, - 'PY': COUNTRIES3['PRY']!, - 'PE': COUNTRIES3['PER']!, - 'PH': COUNTRIES3['PHL']!, - 'PN': COUNTRIES3['PCN']!, - 'PL': COUNTRIES3['POL']!, - 'PT': COUNTRIES3['PRT']!, - 'PR': COUNTRIES3['PRI']!, - 'QA': COUNTRIES3['QAT']!, - 'RE': COUNTRIES3['REU']!, - 'RO': COUNTRIES3['ROU']!, - 'RU': COUNTRIES3['RUS']!, - 'RW': COUNTRIES3['RWA']!, - 'BL': COUNTRIES3['BLM']!, - 'SH': COUNTRIES3['SHN']!, - 'KN': COUNTRIES3['KNA']!, - 'LC': COUNTRIES3['LCA']!, - 'MF': COUNTRIES3['MAF']!, - 'PM': COUNTRIES3['SPM']!, - 'VC': COUNTRIES3['VCT']!, - 'WS': COUNTRIES3['WSM']!, - 'SM': COUNTRIES3['SMR']!, - 'ST': COUNTRIES3['STP']!, - 'SA': COUNTRIES3['SAU']!, - 'SN': COUNTRIES3['SEN']!, - 'RS': COUNTRIES3['SRB']!, - 'SC': COUNTRIES3['SYC']!, - 'SL': COUNTRIES3['SLE']!, - 'SG': COUNTRIES3['SGP']!, - 'SX': COUNTRIES3['SXM']!, - 'SK': COUNTRIES3['SVK']!, - 'SI': COUNTRIES3['SVN']!, - 'SB': COUNTRIES3['SLB']!, - 'SO': COUNTRIES3['SOM']!, - 'ZA': COUNTRIES3['ZAF']!, - 'GS': COUNTRIES3['SGS']!, - 'SS': COUNTRIES3['SSD']!, - 'ES': COUNTRIES3['ESP']!, - 'LK': COUNTRIES3['LKA']!, - 'SD': COUNTRIES3['SDN']!, - 'SR': COUNTRIES3['SUR']!, - 'SJ': COUNTRIES3['SJM']!, - 'SE': COUNTRIES3['SWE']!, - 'CH': COUNTRIES3['CHE']!, - 'SY': COUNTRIES3['SYR']!, - 'TW': COUNTRIES3['TWN']!, - 'TJ': COUNTRIES3['TJK']!, - 'TZ': COUNTRIES3['TZA']!, - 'TH': COUNTRIES3['THA']!, - 'TL': COUNTRIES3['TLS']!, - 'TG': COUNTRIES3['TGO']!, - 'TK': COUNTRIES3['TKL']!, - 'TO': COUNTRIES3['TON']!, - 'TT': COUNTRIES3['TTO']!, - 'TN': COUNTRIES3['TUN']!, - 'TR': COUNTRIES3['TUR']!, - 'TM': COUNTRIES3['TKM']!, - 'TC': COUNTRIES3['TCA']!, - 'TV': COUNTRIES3['TUV']!, - 'UG': COUNTRIES3['UGA']!, - 'UA': COUNTRIES3['UKR']!, - 'AE': COUNTRIES3['ARE']!, - 'GB': COUNTRIES3['GBR']!, - 'US': COUNTRIES3['USA']!, - 'UM': COUNTRIES3['UMI']!, - 'UY': COUNTRIES3['URY']!, - 'UZ': COUNTRIES3['UZB']!, - 'VU': COUNTRIES3['VUT']!, - 'VE': COUNTRIES3['VEN']!, - 'VN': COUNTRIES3['VNM']!, - 'VG': COUNTRIES3['VGB']!, - 'VI': COUNTRIES3['VIR']!, - 'WF': COUNTRIES3['WLF']!, - 'EH': COUNTRIES3['ESH']!, - 'YE': COUNTRIES3['YEM']!, - 'ZM': COUNTRIES3['ZMB']!, - 'ZW': COUNTRIES3['ZWE']!, + /// Mapping of all countries by alpha-3 code + static Map countries2 = { + 'AF': countries3['AFG']!, + 'AX': countries3['ALA']!, + 'AL': countries3['ALB']!, + 'DZ': countries3['DZA']!, + 'AS': countries3['ASM']!, + 'AD': countries3['AND']!, + 'AO': countries3['AGO']!, + 'AI': countries3['AIA']!, + 'AQ': countries3['ATA']!, + 'AG': countries3['ATG']!, + 'AR': countries3['ARG']!, + 'AM': countries3['ARM']!, + 'AW': countries3['ABW']!, + 'AU': countries3['AUS']!, + 'AT': countries3['AUT']!, + 'AZ': countries3['AZE']!, + 'BS': countries3['BHS']!, + 'BH': countries3['BHR']!, + 'BD': countries3['BGD']!, + 'BB': countries3['BRB']!, + 'BY': countries3['BLR']!, + 'BE': countries3['BEL']!, + 'BZ': countries3['BLZ']!, + 'BJ': countries3['BEN']!, + 'BM': countries3['BMU']!, + 'BT': countries3['BTN']!, + 'BO': countries3['BOL']!, + 'BQ': countries3['BES']!, + 'BA': countries3['BIH']!, + 'BW': countries3['BWA']!, + 'BV': countries3['BVT']!, + 'BR': countries3['BRA']!, + 'IO': countries3['IOT']!, + 'BN': countries3['BRN']!, + 'BG': countries3['BGR']!, + 'BF': countries3['BFA']!, + 'BI': countries3['BDI']!, + 'CV': countries3['CPV']!, + 'KH': countries3['KHM']!, + 'CM': countries3['CMR']!, + 'CA': countries3['CAN']!, + 'KY': countries3['CYM']!, + 'CF': countries3['CAF']!, + 'TD': countries3['TCD']!, + 'CL': countries3['CHL']!, + 'CN': countries3['CHN']!, + 'CX': countries3['CXR']!, + 'CC': countries3['CCK']!, + 'CO': countries3['COL']!, + 'KM': countries3['COM']!, + 'CG': countries3['COG']!, + 'CD': countries3['COD']!, + 'CK': countries3['COK']!, + 'CR': countries3['CRI']!, + 'CI': countries3['CIV']!, + 'HR': countries3['HRV']!, + 'CU': countries3['CUB']!, + 'CW': countries3['CUW']!, + 'CY': countries3['CYP']!, + 'CZ': countries3['CZE']!, + 'DK': countries3['DNK']!, + 'DJ': countries3['DJI']!, + 'DM': countries3['DMA']!, + 'DO': countries3['DOM']!, + 'EC': countries3['ECU']!, + 'EG': countries3['EGY']!, + 'SV': countries3['SLV']!, + 'GQ': countries3['GNQ']!, + 'ER': countries3['ERI']!, + 'EE': countries3['EST']!, + 'SZ': countries3['SWZ']!, + 'ET': countries3['ETH']!, + 'FK': countries3['FLK']!, + 'FO': countries3['FRO']!, + 'FJ': countries3['FJI']!, + 'FI': countries3['FIN']!, + 'FR': countries3['FRA']!, + 'GF': countries3['GUF']!, + 'PF': countries3['PYF']!, + 'TF': countries3['ATF']!, + 'GA': countries3['GAB']!, + 'GM': countries3['GMB']!, + 'GE': countries3['GEO']!, + 'DE': countries3['DEU']!, + 'GH': countries3['GHA']!, + 'GI': countries3['GIB']!, + 'GR': countries3['GRC']!, + 'GL': countries3['GRL']!, + 'GD': countries3['GRD']!, + 'GP': countries3['GLP']!, + 'GU': countries3['GUM']!, + 'GT': countries3['GTM']!, + 'GG': countries3['GGY']!, + 'GN': countries3['GIN']!, + 'GW': countries3['GNB']!, + 'GY': countries3['GUY']!, + 'HT': countries3['HTI']!, + 'HM': countries3['HMD']!, + 'VA': countries3['VAT']!, + 'HN': countries3['HND']!, + 'HK': countries3['HKG']!, + 'HU': countries3['HUN']!, + 'IS': countries3['ISL']!, + 'IN': countries3['IND']!, + 'ID': countries3['IDN']!, + 'IR': countries3['IRN']!, + 'IQ': countries3['IRQ']!, + 'IE': countries3['IRL']!, + 'IM': countries3['IMN']!, + 'IL': countries3['ISR']!, + 'IT': countries3['ITA']!, + 'JM': countries3['JAM']!, + 'JP': countries3['JPN']!, + 'JE': countries3['JEY']!, + 'JO': countries3['JOR']!, + 'KZ': countries3['KAZ']!, + 'KE': countries3['KEN']!, + 'KI': countries3['KIR']!, + 'KP': countries3['PRK']!, + 'KR': countries3['KOR']!, + 'KW': countries3['KWT']!, + 'KG': countries3['KGZ']!, + 'LA': countries3['LAO']!, + 'LV': countries3['LVA']!, + 'LB': countries3['LBN']!, + 'LS': countries3['LSO']!, + 'LR': countries3['LBR']!, + 'LY': countries3['LBY']!, + 'LI': countries3['LIE']!, + 'LT': countries3['LTU']!, + 'LU': countries3['LUX']!, + 'MO': countries3['MAC']!, + 'MG': countries3['MDG']!, + 'MW': countries3['MWI']!, + 'MY': countries3['MYS']!, + 'MV': countries3['MDV']!, + 'ML': countries3['MLI']!, + 'MT': countries3['MLT']!, + 'MH': countries3['MHL']!, + 'MQ': countries3['MTQ']!, + 'MR': countries3['MRT']!, + 'MU': countries3['MUS']!, + 'YT': countries3['MYT']!, + 'MX': countries3['MEX']!, + 'FM': countries3['FSM']!, + 'MD': countries3['MDA']!, + 'MC': countries3['MCO']!, + 'MN': countries3['MNG']!, + 'ME': countries3['MNE']!, + 'MS': countries3['MSR']!, + 'MA': countries3['MAR']!, + 'MZ': countries3['MOZ']!, + 'MM': countries3['MMR']!, + 'NA': countries3['NAM']!, + 'NR': countries3['NRU']!, + 'NP': countries3['NPL']!, + 'NL': countries3['NLD']!, + 'NC': countries3['NCL']!, + 'NZ': countries3['NZL']!, + 'NI': countries3['NIC']!, + 'NE': countries3['NER']!, + 'NG': countries3['NGA']!, + 'NU': countries3['NIU']!, + 'NF': countries3['NFK']!, + 'MK': countries3['MKD']!, + 'MP': countries3['MNP']!, + 'NO': countries3['NOR']!, + 'OM': countries3['OMN']!, + 'PK': countries3['PAK']!, + 'PW': countries3['PLW']!, + 'PS': countries3['PSE']!, + 'PA': countries3['PAN']!, + 'PG': countries3['PNG']!, + 'PY': countries3['PRY']!, + 'PE': countries3['PER']!, + 'PH': countries3['PHL']!, + 'PN': countries3['PCN']!, + 'PL': countries3['POL']!, + 'PT': countries3['PRT']!, + 'PR': countries3['PRI']!, + 'QA': countries3['QAT']!, + 'RE': countries3['REU']!, + 'RO': countries3['ROU']!, + 'RU': countries3['RUS']!, + 'RW': countries3['RWA']!, + 'BL': countries3['BLM']!, + 'SH': countries3['SHN']!, + 'KN': countries3['KNA']!, + 'LC': countries3['LCA']!, + 'MF': countries3['MAF']!, + 'PM': countries3['SPM']!, + 'VC': countries3['VCT']!, + 'WS': countries3['WSM']!, + 'SM': countries3['SMR']!, + 'ST': countries3['STP']!, + 'SA': countries3['SAU']!, + 'SN': countries3['SEN']!, + 'RS': countries3['SRB']!, + 'SC': countries3['SYC']!, + 'SL': countries3['SLE']!, + 'SG': countries3['SGP']!, + 'SX': countries3['SXM']!, + 'SK': countries3['SVK']!, + 'SI': countries3['SVN']!, + 'SB': countries3['SLB']!, + 'SO': countries3['SOM']!, + 'ZA': countries3['ZAF']!, + 'GS': countries3['SGS']!, + 'SS': countries3['SSD']!, + 'ES': countries3['ESP']!, + 'LK': countries3['LKA']!, + 'SD': countries3['SDN']!, + 'SR': countries3['SUR']!, + 'SJ': countries3['SJM']!, + 'SE': countries3['SWE']!, + 'CH': countries3['CHE']!, + 'SY': countries3['SYR']!, + 'TW': countries3['TWN']!, + 'TJ': countries3['TJK']!, + 'TZ': countries3['TZA']!, + 'TH': countries3['THA']!, + 'TL': countries3['TLS']!, + 'TG': countries3['TGO']!, + 'TK': countries3['TKL']!, + 'TO': countries3['TON']!, + 'TT': countries3['TTO']!, + 'TN': countries3['TUN']!, + 'TR': countries3['TUR']!, + 'TM': countries3['TKM']!, + 'TC': countries3['TCA']!, + 'TV': countries3['TUV']!, + 'UG': countries3['UGA']!, + 'UA': countries3['UKR']!, + 'AE': countries3['ARE']!, + 'GB': countries3['GBR']!, + 'US': countries3['USA']!, + 'UM': countries3['UMI']!, + 'UY': countries3['URY']!, + 'UZ': countries3['UZB']!, + 'VU': countries3['VUT']!, + 'VE': countries3['VEN']!, + 'VN': countries3['VNM']!, + 'VG': countries3['VGB']!, + 'VI': countries3['VIR']!, + 'WF': countries3['WLF']!, + 'EH': countries3['ESH']!, + 'YE': countries3['YEM']!, + 'ZM': countries3['ZMB']!, + 'ZW': countries3['ZWE']!, }; } diff --git a/lib/src/types/country/iso3166_subdivisions.dart b/lib/src/types/country/iso3166_subdivisions.dart index 0d549c5..417bf93 100644 --- a/lib/src/types/country/iso3166_subdivisions.dart +++ b/lib/src/types/country/iso3166_subdivisions.dart @@ -1,5 +1,6 @@ -// From: https://en.wikipedia.org/wiki/ISO_3166-2#Current_codes -const Map> COUNTRY_SUBDIVISIONS = { +/// Mapping of country subdivisions by alpha-2 code +/// From: https://en.wikipedia.org/wiki/ISO_3166-2#Current_codes +const Map> countrySubdivisions = { "AD": { "AD-07": "Andorra la Vella", "AD-02": "Canillo", @@ -158,9 +159,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "AU-ACT": "Australian Capital Territory", "AU-NT": "Northern Territory" }, - "AZ": { - "AZ-NX": "Naxçıvan" - }, + "AZ": {"AZ-NX": "Naxçıvan"}, "BA": { "BA-BIH": "Federacija Bosne i Hercegovine", "BA-SRP": "Republika Srpska", @@ -296,11 +295,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "BO-S": "Santa Cruz", "BO-T": "Tarija" }, - "BQ": { - "BQ-BO": "Bonaire", - "BQ-SA": "Saba", - "BQ-SE": "Sint Eustatius" - }, + "BQ": {"BQ-BO": "Bonaire", "BQ-SA": "Saba", "BQ-SE": "Sint Eustatius"}, "BR": { "BR-AC": "Acre", "BR-AL": "Alagoas", @@ -669,10 +664,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "CU-05": "Villa Clara", "CU-99": "Isla de la Juventud" }, - "CV": { - "CV-B": "Ilhas de Barlavento", - "CV-S": "Ilhas de Sotavento" - }, + "CV": {"CV-B": "Ilhas de Barlavento", "CV-S": "Ilhas de Sotavento"}, "CY": { "CY-04": "Ammochostos", "CY-06": "Keryneia", @@ -1055,10 +1047,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "GN-N": "Nzérékoré", "GN-C": "Conakry" }, - "GQ": { - "GQ-C": "Región Continental", - "GQ-I": "Región Insular" - }, + "GQ": {"GQ-C": "Región Continental", "GQ-I": "Región Insular"}, "GR": { "GR-A": "Anatolikí Makedonía kai Thráki", "GR-I": "Attikí", @@ -1098,11 +1087,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "GT-TO": "Totonicapán", "GT-ZA": "Zacapa" }, - "GW": { - "GW-L": "Leste", - "GW-N": "Norte", - "GW-S": "Sul" - }, + "GW": {"GW-L": "Leste", "GW-N": "Norte", "GW-S": "Sul"}, "GY": { "GY-BA": "Barima-Waini", "GY-CU": "Cuyuni-Mazaruni", @@ -1528,15 +1513,8 @@ const Map> COUNTRY_SUBDIVISIONS = { "KI-L": "Line Islands", "KI-P": "Phoenix Islands" }, - "KM": { - "KM-G": "Grande Comore", - "KM-A": "Anjouan", - "KM-M": "Mohéli" - }, - "KN": { - "KN-K": "Saint Kitts", - "KN-N": "Nevis" - }, + "KM": {"KM-G": "Grande Comore", "KM-A": "Anjouan", "KM-M": "Mohéli"}, + "KN": {"KN-K": "Saint Kitts", "KN-N": "Nevis"}, "KP": { "KP-01": "Pyongyang", "KP-13": "Rason", @@ -1969,10 +1947,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "MG-A": "Toamasina", "MG-U": "Toliara" }, - "MH": { - "MH-L": "Ralik chain", - "MH-T": "Ratak chain" - }, + "MH": {"MH-L": "Ralik chain", "MH-T": "Ratak chain"}, "MK": { "MK-801": "Aerodrom", "MK-802": "Aračinovo", @@ -2207,10 +2182,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "MU-RR": "Rivière du Rempart", "MU-SA": "Savanne" }, - "MV": { - "MV-01": "Addu City", - "MV-MLE": "Male" - }, + "MV": {"MV-01": "Addu City", "MV-MLE": "Male"}, "MW": { "MW-C": "Central Region", "MW-N": "Northern Region", @@ -2386,7 +2358,8 @@ const Map> COUNTRY_SUBDIVISIONS = { "NO-18": "Nordland", "NO-03": "Oslo", "NO-11": "Rogaland", - "NO-54": "Troms og Finnmark / Romsa ja Finnmárku (se) / Tromssan ja Finmarkun (-)", + "NO-54": + "Troms og Finnmark / Romsa ja Finnmárku (se) / Tromssan ja Finmarkun (-)", "NO-50": "Trøndelag / Trööndelage (-)", "NO-38": "Vestfold og Telemark", "NO-46": "Vestland", @@ -2693,10 +2666,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "RO-VN": "Vrancea", "RO-B": "București" }, - "RS": { - "RS-KM": "Kosovo-Metohija[a]", - "RS-VO": "Vojvodina" - }, + "RS": {"RS-KM": "Kosovo-Metohija[a]", "RS-VO": "Vojvodina"}, "RU": { "RU-AD": "Adygeya, Respublika", "RU-AL": "Altay, Respublika", @@ -2715,7 +2685,8 @@ const Map> COUNTRY_SUBDIVISIONS = { "RU-ME": "Mariy El, Respublika", "RU-MO": "Mordoviya, Respublika", "RU-SA": "Saha, Respublika(local variant is Jakutija)", - "RU-SE": "Severnaya Osetiya, Respublika(local variant is Alaniya [Respublika Severnaya Osetiya – Alaniya])", + "RU-SE": + "Severnaya Osetiya, Respublika(local variant is Alaniya [Respublika Severnaya Osetiya – Alaniya])", "RU-TA": "Tatarstan, Respublika", "RU-TY": "Tyva, Respublika(local variant is Tuva)", "RU-UD": "Udmurtskaya Respublika", @@ -3841,11 +3812,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "VU-TAE": "Taféa", "VU-TOB": "Torba" }, - "WF": { - "WF-AL": "Alo", - "WF-SG": "Sigave", - "WF-UV": "Uvea" - }, + "WF": {"WF-AL": "Alo", "WF-SG": "Sigave", "WF-UV": "Uvea"}, "WS": { "WS-AA": "A'ana", "WS-AL": "Aiga-i-le-Tai", @@ -3893,7 +3860,7 @@ const Map> COUNTRY_SUBDIVISIONS = { "ZA-NC": "Northern Cape", "ZA-NW": "North-West", "ZA-WC": "Western Cape" -}, + }, "ZM": { "ZM-02": "Central", "ZM-08": "Copperbelt", diff --git a/lib/src/types/currency.dart b/lib/src/types/currency.dart index 38cfde0..ffe471e 100644 --- a/lib/src/types/currency.dart +++ b/lib/src/types/currency.dart @@ -1,13 +1,16 @@ import "../document.dart"; import "./currency/iso4217_currencies.dart"; +/// ISO 4217 currency code. class KdlCurrency extends KdlValue { - KdlCurrency(Currency value, [String? type]) : super(value, type); + /// Construct a new `KdlCurrency` + KdlCurrency(super.value, [super.type]); - static call(KdlValue value, [String type = 'currency']) { - if (!(value is KdlString)) return null; - - var currency = Currency.CURRENCIES[value.value]; + /// Convert a `KdlString` into a `KdlCurrency` + static KdlCurrency? convert(KdlValue value, [String type = 'currency']) { + if (value is! KdlString) return null; + + var currency = Currency.currencies[value.value]; if (currency == null) throw "invalid currency: ${value.value}"; return KdlCurrency(currency, type); diff --git a/lib/src/types/currency/iso4217_currencies.dart b/lib/src/types/currency/iso4217_currencies.dart index 4e51029..50e76fc 100644 --- a/lib/src/types/currency/iso4217_currencies.dart +++ b/lib/src/types/currency/iso4217_currencies.dart @@ -1,31 +1,49 @@ +/// Represents a currency class Currency { + /// Numeric currenct code int numericCode; + + /// Minor unit decimal places int? minorUnit; + + /// Name of the currency String name; - Currency({ this.numericCode = 0, this.minorUnit, this.name = ''}); + /// Construct a new instance of `Currency` + Currency({this.numericCode = 0, this.minorUnit, this.name = ''}); + + @override + bool operator ==(other) => + other is Currency && + other.numericCode == numericCode && + other.minorUnit == minorUnit && + other.name == name; @override - bool operator ==(other) => other is Currency && - other.numericCode == numericCode && - other.minorUnit == minorUnit && - other.name == name; + int get hashCode => [numericCode, minorUnit, name].hashCode; @override - String toString() => "numericCode:$numericCode minorUnit:$minorUnit name:$name"; - - static Map CURRENCIES = { - 'AED': Currency(numericCode: 784, minorUnit: 2, name: 'United Arab Emirates dirham'), + String toString() => + "numericCode:$numericCode minorUnit:$minorUnit name:$name"; + + /// Mapping of currencies by their alpha-3 currency code + static Map currencies = { + 'AED': Currency( + numericCode: 784, minorUnit: 2, name: 'United Arab Emirates dirham'), 'AFN': Currency(numericCode: 971, minorUnit: 2, name: 'Afghan afghani'), 'ALL': Currency(numericCode: 8, minorUnit: 2, name: 'Albanian lek'), 'AMD': Currency(numericCode: 51, minorUnit: 2, name: 'Armenian dram'), - 'ANG': Currency(numericCode: 532, minorUnit: 2, name: 'Netherlands Antillean guilder'), + 'ANG': Currency( + numericCode: 532, minorUnit: 2, name: 'Netherlands Antillean guilder'), 'AOA': Currency(numericCode: 973, minorUnit: 2, name: 'Angolan kwanza'), 'ARS': Currency(numericCode: 32, minorUnit: 2, name: 'Argentine peso'), 'AUD': Currency(numericCode: 36, minorUnit: 2, name: 'Australian dollar'), 'AWG': Currency(numericCode: 533, minorUnit: 2, name: 'Aruban florin'), 'AZN': Currency(numericCode: 944, minorUnit: 2, name: 'Azerbaijani manat'), - 'BAM': Currency(numericCode: 977, minorUnit: 2, name: 'Bosnia and Herzegovina convertible mark'), + 'BAM': Currency( + numericCode: 977, + minorUnit: 2, + name: 'Bosnia and Herzegovina convertible mark'), 'BBD': Currency(numericCode: 52, minorUnit: 2, name: 'Barbados dollar'), 'BDT': Currency(numericCode: 50, minorUnit: 2, name: 'Bangladeshi taka'), 'BGN': Currency(numericCode: 975, minorUnit: 2, name: 'Bulgarian lev'), @@ -34,7 +52,8 @@ class Currency { 'BMD': Currency(numericCode: 60, minorUnit: 2, name: 'Bermudian dollar'), 'BND': Currency(numericCode: 96, minorUnit: 2, name: 'Brunei dollar'), 'BOB': Currency(numericCode: 68, minorUnit: 2, name: 'Boliviano'), - 'BOV': Currency(numericCode: 984, minorUnit: 2, name: 'Bolivian Mvdol (funds code)'), + 'BOV': Currency( + numericCode: 984, minorUnit: 2, name: 'Bolivian Mvdol (funds code)'), 'BRL': Currency(numericCode: 986, minorUnit: 2, name: 'Brazilian real'), 'BSD': Currency(numericCode: 44, minorUnit: 2, name: 'Bahamian dollar'), 'BTN': Currency(numericCode: 64, minorUnit: 2, name: 'Bhutanese ngultrum'), @@ -43,18 +62,30 @@ class Currency { 'BZD': Currency(numericCode: 84, minorUnit: 2, name: 'Belize dollar'), 'CAD': Currency(numericCode: 124, minorUnit: 2, name: 'Canadian dollar'), 'CDF': Currency(numericCode: 976, minorUnit: 2, name: 'Congolese franc'), - 'CHE': Currency(numericCode: 947, minorUnit: 2, name: 'WIR euro (complementary currency)'), + 'CHE': Currency( + numericCode: 947, + minorUnit: 2, + name: 'WIR euro (complementary currency)'), 'CHF': Currency(numericCode: 756, minorUnit: 2, name: 'Swiss franc'), - 'CHW': Currency(numericCode: 948, minorUnit: 2, name: 'WIR franc (complementary currency)'), - 'CLF': Currency(numericCode: 990, minorUnit: 4, name: 'Unidad de Fomento (funds code)'), + 'CHW': Currency( + numericCode: 948, + minorUnit: 2, + name: 'WIR franc (complementary currency)'), + 'CLF': Currency( + numericCode: 990, minorUnit: 4, name: 'Unidad de Fomento (funds code)'), 'CLP': Currency(numericCode: 152, minorUnit: 0, name: 'Chilean peso'), 'CNY': Currency(numericCode: 156, minorUnit: 2, name: 'Chinese yuan[8]'), 'COP': Currency(numericCode: 170, minorUnit: 2, name: 'Colombian peso'), - 'COU': Currency(numericCode: 970, minorUnit: 2, name: 'Unidad de Valor Real (UVR) (funds code)'), + 'COU': Currency( + numericCode: 970, + minorUnit: 2, + name: 'Unidad de Valor Real (UVR) (funds code)'), 'CRC': Currency(numericCode: 188, minorUnit: 2, name: 'Costa Rican colon'), - 'CUC': Currency(numericCode: 931, minorUnit: 2, name: 'Cuban convertible peso'), + 'CUC': Currency( + numericCode: 931, minorUnit: 2, name: 'Cuban convertible peso'), 'CUP': Currency(numericCode: 192, minorUnit: 2, name: 'Cuban peso'), - 'CVE': Currency(numericCode: 132, minorUnit: 2, name: 'Cape Verdean escudo'), + 'CVE': + Currency(numericCode: 132, minorUnit: 2, name: 'Cape Verdean escudo'), 'CZK': Currency(numericCode: 203, minorUnit: 2, name: 'Czech koruna'), 'DJF': Currency(numericCode: 262, minorUnit: 0, name: 'Djiboutian franc'), 'DKK': Currency(numericCode: 208, minorUnit: 2, name: 'Danish krone'), @@ -65,7 +96,8 @@ class Currency { 'ETB': Currency(numericCode: 230, minorUnit: 2, name: 'Ethiopian birr'), 'EUR': Currency(numericCode: 978, minorUnit: 2, name: 'Euro'), 'FJD': Currency(numericCode: 242, minorUnit: 2, name: 'Fiji dollar'), - 'FKP': Currency(numericCode: 238, minorUnit: 2, name: 'Falkland Islands pound'), + 'FKP': Currency( + numericCode: 238, minorUnit: 2, name: 'Falkland Islands pound'), 'GBP': Currency(numericCode: 826, minorUnit: 2, name: 'Pound sterling'), 'GEL': Currency(numericCode: 981, minorUnit: 2, name: 'Georgian lari'), 'GHS': Currency(numericCode: 936, minorUnit: 2, name: 'Ghanaian cedi'), @@ -95,7 +127,8 @@ class Currency { 'KPW': Currency(numericCode: 408, minorUnit: 2, name: 'North Korean won'), 'KRW': Currency(numericCode: 410, minorUnit: 0, name: 'South Korean won'), 'KWD': Currency(numericCode: 414, minorUnit: 3, name: 'Kuwaiti dinar'), - 'KYD': Currency(numericCode: 136, minorUnit: 2, name: 'Cayman Islands dollar'), + 'KYD': + Currency(numericCode: 136, minorUnit: 2, name: 'Cayman Islands dollar'), 'KZT': Currency(numericCode: 398, minorUnit: 2, name: 'Kazakhstani tenge'), 'LAK': Currency(numericCode: 418, minorUnit: 2, name: 'Lao kip'), 'LBP': Currency(numericCode: 422, minorUnit: 2, name: 'Lebanese pound'), @@ -110,12 +143,16 @@ class Currency { 'MMK': Currency(numericCode: 104, minorUnit: 2, name: 'Myanmar kyat'), 'MNT': Currency(numericCode: 496, minorUnit: 2, name: 'Mongolian tögrög'), 'MOP': Currency(numericCode: 446, minorUnit: 2, name: 'Macanese pataca'), - 'MRU': Currency(numericCode: 929, minorUnit: 2, name: 'Mauritanian ouguiya'), + 'MRU': + Currency(numericCode: 929, minorUnit: 2, name: 'Mauritanian ouguiya'), 'MUR': Currency(numericCode: 480, minorUnit: 2, name: 'Mauritian rupee'), 'MVR': Currency(numericCode: 462, minorUnit: 2, name: 'Maldivian rufiyaa'), 'MWK': Currency(numericCode: 454, minorUnit: 2, name: 'Malawian kwacha'), 'MXN': Currency(numericCode: 484, minorUnit: 2, name: 'Mexican peso'), - 'MXV': Currency(numericCode: 979, minorUnit: 2, name: 'Mexican Unidad de Inversion (UDI) (funds code)'), + 'MXV': Currency( + numericCode: 979, + minorUnit: 2, + name: 'Mexican Unidad de Inversion (UDI) (funds code)'), 'MYR': Currency(numericCode: 458, minorUnit: 2, name: 'Malaysian ringgit'), 'MZN': Currency(numericCode: 943, minorUnit: 2, name: 'Mozambican metical'), 'NAD': Currency(numericCode: 516, minorUnit: 2, name: 'Namibian dollar'), @@ -127,7 +164,8 @@ class Currency { 'OMR': Currency(numericCode: 512, minorUnit: 3, name: 'Omani rial'), 'PAB': Currency(numericCode: 590, minorUnit: 2, name: 'Panamanian balboa'), 'PEN': Currency(numericCode: 604, minorUnit: 2, name: 'Peruvian sol'), - 'PGK': Currency(numericCode: 598, minorUnit: 2, name: 'Papua New Guinean kina'), + 'PGK': Currency( + numericCode: 598, minorUnit: 2, name: 'Papua New Guinean kina'), 'PHP': Currency(numericCode: 608, minorUnit: 2, name: 'Philippine peso'), 'PKR': Currency(numericCode: 586, minorUnit: 2, name: 'Pakistani rupee'), 'PLN': Currency(numericCode: 985, minorUnit: 2, name: 'Polish złoty'), @@ -138,17 +176,21 @@ class Currency { 'RUB': Currency(numericCode: 643, minorUnit: 2, name: 'Russian ruble'), 'RWF': Currency(numericCode: 646, minorUnit: 0, name: 'Rwandan franc'), 'SAR': Currency(numericCode: 682, minorUnit: 2, name: 'Saudi riyal'), - 'SBD': Currency(numericCode: 90, minorUnit: 2, name: 'Solomon Islands dollar'), + 'SBD': + Currency(numericCode: 90, minorUnit: 2, name: 'Solomon Islands dollar'), 'SCR': Currency(numericCode: 690, minorUnit: 2, name: 'Seychelles rupee'), 'SDG': Currency(numericCode: 938, minorUnit: 2, name: 'Sudanese pound'), 'SEK': Currency(numericCode: 752, minorUnit: 2, name: 'Swedish krona'), 'SGD': Currency(numericCode: 702, minorUnit: 2, name: 'Singapore dollar'), 'SHP': Currency(numericCode: 654, minorUnit: 2, name: 'Saint Helena pound'), - 'SLL': Currency(numericCode: 694, minorUnit: 2, name: 'Sierra Leonean leone'), + 'SLL': + Currency(numericCode: 694, minorUnit: 2, name: 'Sierra Leonean leone'), 'SOS': Currency(numericCode: 706, minorUnit: 2, name: 'Somali shilling'), 'SRD': Currency(numericCode: 968, minorUnit: 2, name: 'Surinamese dollar'), - 'SSP': Currency(numericCode: 728, minorUnit: 2, name: 'South Sudanese pound'), - 'STN': Currency(numericCode: 930, minorUnit: 2, name: 'São Tomé and Príncipe dobra'), + 'SSP': + Currency(numericCode: 728, minorUnit: 2, name: 'South Sudanese pound'), + 'STN': Currency( + numericCode: 930, minorUnit: 2, name: 'São Tomé and Príncipe dobra'), 'SVC': Currency(numericCode: 222, minorUnit: 2, name: 'Salvadoran colón'), 'SYP': Currency(numericCode: 760, minorUnit: 2, name: 'Syrian pound'), 'SZL': Currency(numericCode: 748, minorUnit: 2, name: 'Swazi lilangeni'), @@ -158,38 +200,69 @@ class Currency { 'TND': Currency(numericCode: 788, minorUnit: 3, name: 'Tunisian dinar'), 'TOP': Currency(numericCode: 776, minorUnit: 2, name: 'Tongan paʻanga'), 'TRY': Currency(numericCode: 949, minorUnit: 2, name: 'Turkish lira'), - 'TTD': Currency(numericCode: 780, minorUnit: 2, name: 'Trinidad and Tobago dollar'), + 'TTD': Currency( + numericCode: 780, minorUnit: 2, name: 'Trinidad and Tobago dollar'), 'TWD': Currency(numericCode: 901, minorUnit: 2, name: 'New Taiwan dollar'), 'TZS': Currency(numericCode: 834, minorUnit: 2, name: 'Tanzanian shilling'), 'UAH': Currency(numericCode: 980, minorUnit: 2, name: 'Ukrainian hryvnia'), 'UGX': Currency(numericCode: 800, minorUnit: 0, name: 'Ugandan shilling'), - 'USD': Currency(numericCode: 840, minorUnit: 2, name: 'United States dollar'), - 'USN': Currency(numericCode: 997, minorUnit: 2, name: 'United States dollar (next day) (funds code)'), - 'UYI': Currency(numericCode: 940, minorUnit: 0, name: 'Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code)'), + 'USD': + Currency(numericCode: 840, minorUnit: 2, name: 'United States dollar'), + 'USN': Currency( + numericCode: 997, + minorUnit: 2, + name: 'United States dollar (next day) (funds code)'), + 'UYI': Currency( + numericCode: 940, + minorUnit: 0, + name: 'Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code)'), 'UYU': Currency(numericCode: 858, minorUnit: 2, name: 'Uruguayan peso'), 'UYW': Currency(numericCode: 927, minorUnit: 4, name: 'Unidad previsional'), 'UZS': Currency(numericCode: 860, minorUnit: 2, name: 'Uzbekistan som'), - 'VED': Currency(numericCode: 926, minorUnit: 2, name: 'Venezuelan bolívar digital'), - 'VES': Currency(numericCode: 928, minorUnit: 2, name: 'Venezuelan bolívar soberano'), + 'VED': Currency( + numericCode: 926, minorUnit: 2, name: 'Venezuelan bolívar digital'), + 'VES': Currency( + numericCode: 928, minorUnit: 2, name: 'Venezuelan bolívar soberano'), 'VND': Currency(numericCode: 704, minorUnit: 0, name: 'Vietnamese đồng'), 'VUV': Currency(numericCode: 548, minorUnit: 0, name: 'Vanuatu vatu'), 'WST': Currency(numericCode: 882, minorUnit: 2, name: 'Samoan tala'), 'XAF': Currency(numericCode: 950, minorUnit: 0, name: 'CFA franc BEAC'), - 'XAG': Currency(numericCode: 961, minorUnit: null, name: 'Silver (one troy ounce)'), - 'XAU': Currency(numericCode: 959, minorUnit: null, name: 'Gold (one troy ounce)'), - 'XBA': Currency(numericCode: 955, minorUnit: null, name: 'European Composite Unit (EURCO) (bond market unit)'), - 'XBB': Currency(numericCode: 956, minorUnit: null, name: 'European Monetary Unit (E.M.U.-6) (bond market unit)'), - 'XBC': Currency(numericCode: 957, minorUnit: null, name: 'European Unit of Account 9 (E.U.A.-9) (bond market unit)'), - 'XBD': Currency(numericCode: 958, minorUnit: null, name: 'European Unit of Account 17 (E.U.A.-17) (bond market unit)'), - 'XCD': Currency(numericCode: 951, minorUnit: 2, name: 'East Caribbean dollar'), - 'XDR': Currency(numericCode: 960, minorUnit: null, name: 'Special drawing rights'), + 'XAG': Currency( + numericCode: 961, minorUnit: null, name: 'Silver (one troy ounce)'), + 'XAU': Currency( + numericCode: 959, minorUnit: null, name: 'Gold (one troy ounce)'), + 'XBA': Currency( + numericCode: 955, + minorUnit: null, + name: 'European Composite Unit (EURCO) (bond market unit)'), + 'XBB': Currency( + numericCode: 956, + minorUnit: null, + name: 'European Monetary Unit (E.M.U.-6) (bond market unit)'), + 'XBC': Currency( + numericCode: 957, + minorUnit: null, + name: 'European Unit of Account 9 (E.U.A.-9) (bond market unit)'), + 'XBD': Currency( + numericCode: 958, + minorUnit: null, + name: 'European Unit of Account 17 (E.U.A.-17) (bond market unit)'), + 'XCD': + Currency(numericCode: 951, minorUnit: 2, name: 'East Caribbean dollar'), + 'XDR': Currency( + numericCode: 960, minorUnit: null, name: 'Special drawing rights'), 'XOF': Currency(numericCode: 952, minorUnit: 0, name: 'CFA franc BCEAO'), - 'XPD': Currency(numericCode: 964, minorUnit: null, name: 'Palladium (one troy ounce)'), - 'XPF': Currency(numericCode: 953, minorUnit: 0, name: 'CFP franc (franc Pacifique)'), - 'XPT': Currency(numericCode: 962, minorUnit: null, name: 'Platinum (one troy ounce)'), + 'XPD': Currency( + numericCode: 964, minorUnit: null, name: 'Palladium (one troy ounce)'), + 'XPF': Currency( + numericCode: 953, minorUnit: 0, name: 'CFP franc (franc Pacifique)'), + 'XPT': Currency( + numericCode: 962, minorUnit: null, name: 'Platinum (one troy ounce)'), 'XSU': Currency(numericCode: 994, minorUnit: null, name: 'SUCRE'), - 'XTS': Currency(numericCode: 963, minorUnit: null, name: 'Code reserved for testing'), - 'XUA': Currency(numericCode: 965, minorUnit: null, name: 'ADB Unit of Account'), + 'XTS': Currency( + numericCode: 963, minorUnit: null, name: 'Code reserved for testing'), + 'XUA': Currency( + numericCode: 965, minorUnit: null, name: 'ADB Unit of Account'), 'XXX': Currency(numericCode: 999, minorUnit: null, name: 'No currency'), 'YER': Currency(numericCode: 886, minorUnit: 2, name: 'Yemeni rial'), 'ZAR': Currency(numericCode: 710, minorUnit: 2, name: 'South African rand'), @@ -197,4 +270,3 @@ class Currency { 'ZWL': Currency(numericCode: 932, minorUnit: 2, name: 'Zimbabwean dollar') }; } - diff --git a/lib/src/types/date_time.dart b/lib/src/types/date_time.dart index 3284010..7006b6f 100644 --- a/lib/src/types/date_time.dart +++ b/lib/src/types/date_time.dart @@ -1,20 +1,26 @@ import "../document.dart"; +/// ISO8601 date/time format. class KdlDateTime extends KdlValue { - KdlDateTime(DateTime value, [String? type]) : super(value, type); + /// Construct a new `KdlDateTime` + KdlDateTime(super.value, [super.type]); - static call(KdlValue value, [String type = 'date-time']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlDateTime` + static KdlDateTime? convert(KdlValue value, [String type = 'date-time']) { + if (value is! KdlString) return null; return KdlDateTime(DateTime.parse(value.value), type); } } +/// "Time" section of ISO8601. class KdlTime extends KdlDateTime { - KdlTime(DateTime value, [String? type]) : super(value, type); + /// Construct a new `KdlTime` + KdlTime(super.value, [super.type]); - static call(KdlValue value, [String type = 'time']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlTime` + static KdlTime? convert(KdlValue value, [String type = 'time']) { + if (value is! KdlString) return null; var time = value.value; if (!time.startsWith('T')) time = "T$time"; @@ -24,11 +30,14 @@ class KdlTime extends KdlDateTime { } } +/// "Date" section of ISO8601. class KdlDate extends KdlDateTime { - KdlDate(DateTime value, [String? type]) : super(value, type); + /// Construct a new `KdlDate` + KdlDate(super.value, [super.type]); - static call(KdlValue value, [String type = 'date']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlDate` + static KdlDate? convert(KdlValue value, [String type = 'date']) { + if (value is! KdlString) return null; return KdlDate(DateTime.parse(value.value), type); } diff --git a/lib/src/types/decimal.dart b/lib/src/types/decimal.dart index 3ec4eae..3e9e608 100644 --- a/lib/src/types/decimal.dart +++ b/lib/src/types/decimal.dart @@ -2,11 +2,14 @@ import 'package:big_decimal/big_decimal.dart'; import "../document.dart"; +/// IEEE 754-2008 decimal string format. class KdlDecimal extends KdlValue { - KdlDecimal(BigDecimal value, [String? type]) : super(value, type); + /// Construct a new `KdlDecimal` + KdlDecimal(super.value, [super.type]); - static call(KdlValue value, [String type = 'decimal']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlDecimal` + static KdlDecimal? convert(KdlValue value, [String type = 'decimal']) { + if (value is! KdlString) return null; return KdlDecimal(BigDecimal.parse(value.value), type); } diff --git a/lib/src/types/duration.dart b/lib/src/types/duration.dart index f866bca..72f17ed 100644 --- a/lib/src/types/duration.dart +++ b/lib/src/types/duration.dart @@ -1,16 +1,31 @@ import "../document.dart"; import "./duration/iso8601_parser.dart"; -class ISODuration { +/// Represents a ISO8601 duration +class Duration { + /// Number of years num years; + + /// Number of months num months; + + /// Number of weeks num weeks; + + /// Number of days num days; + + /// Nubmer of hours num hours; + + /// Number of minutes num minutes; + + /// Number of seconds num seconds; - ISODuration({ + /// Construct a new duration, defaulting to 0 seconds + Duration({ this.years = 0, this.months = 0, this.weeks = 0, @@ -20,37 +35,47 @@ class ISODuration { this.seconds = 0, }); - ISODuration.fromParts(Map parts) : - years = parts['years'] ?? 0, - months = parts['months'] ?? 0, - weeks = parts['weeks'] ?? 0, - days = parts['days'] ?? 0, - hours = parts['hours'] ?? 0, - minutes = parts['minutes'] ?? 0, - seconds = parts['seconds'] ?? 0; + /// Construct a new duration from a map of parts + Duration.fromParts(Map parts) + : years = parts['years'] ?? 0, + months = parts['months'] ?? 0, + weeks = parts['weeks'] ?? 0, + days = parts['days'] ?? 0, + hours = parts['hours'] ?? 0, + minutes = parts['minutes'] ?? 0, + seconds = parts['seconds'] ?? 0; + + @override + bool operator ==(other) => + other is Duration && + other.years == years && + other.months == months && + other.weeks == weeks && + other.days == days && + other.hours == hours && + other.minutes == minutes && + other.seconds == seconds; @override - bool operator ==(other) => other is ISODuration && - other.years == years && - other.months == months && - other.weeks == weeks && - other.days == days && - other.hours == hours && - other.minutes == minutes && - other.seconds == seconds; + int get hashCode => + [years, months, weeks, days, hours, minutes, seconds].hashCode; @override - String toString() => "years:$years months:$months weeks:$weeks days:$days hours:$hours minutes:$minutes seconds:$seconds"; + String toString() => + "years:$years months:$months weeks:$weeks days:$days hours:$hours minutes:$minutes seconds:$seconds"; } -class KdlDuration extends KdlValue { - KdlDuration(ISODuration value, [String? type]) : super(value, type); +/// ISO8601 duration format. +class KdlDuration extends KdlValue { + /// Construct a new `KdlDuration` + KdlDuration(super.value, [super.type]); - static call(KdlValue value, [String type = 'duration']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlDuration` + static KdlDuration? convert(KdlValue value, [String type = 'duration']) { + if (value is! KdlString) return null; - var parts = ISO8601DurationParser(value.value).parse(); + var parts = Iso8601DurationParser(value.value).parse(); - return KdlDuration(ISODuration.fromParts(parts), type); + return KdlDuration(Duration.fromParts(parts), type); } } diff --git a/lib/src/types/duration/iso8601_parser.dart b/lib/src/types/duration/iso8601_parser.dart index aabef32..9d0e2a0 100644 --- a/lib/src/types/duration/iso8601_parser.dart +++ b/lib/src/types/duration/iso8601_parser.dart @@ -23,122 +23,134 @@ import 'package:string_scanner/string_scanner.dart'; -enum DurationParsingMode { - Start, - Sign, - Date, - Time, +enum _DurationParsingMode { + start, + sign, + date, + time, } -// Parses a string formatted according to ISO 8601 Duration into the hash. -// -// See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. -// -// This parser allows negative parts to be present in pattern. -class ISO8601DurationParser { - static final PERIOD_OR_COMMA = RegExp('\.|,/'); - static const PERIOD = '.'; - static const COMMA = ','; - - static final SIGN_MARKER = RegExp('\\A-|\\+|'); - static final DATE_MARKER = RegExp('P'); - static final TIME_MARKER = RegExp('T'); - static final DATE_COMPONENT = RegExp('(-?\\d+(?:[.,]\\d+)?)(Y|M|D|W)'); - static final TIME_COMPONENT = RegExp('(-?\\d+(?:[.,]\\d+)?)(H|M|S)'); - - static const DATE_TO_PART = { 'Y': 'years', 'M': 'months', 'W': 'weeks', 'D': 'days' }; - static const TIME_TO_PART = { 'H': 'hours', 'M': 'minutes', 'S': 'seconds' }; - - static const DATE_COMPONENTS = ['years', 'months', 'days']; - static const TIME_COMPONENTS = ['hours', 'minutes', 'seconds']; - - Map parts; - StringScanner scanner; - DurationParsingMode mode; - int sign; - - ISO8601DurationParser(String string) : - scanner = StringScanner(string), - parts = {}, - mode = DurationParsingMode.Start, - sign = 1; - - parse() { +/// Parses a string formatted according to ISO 8601 Duration into the hash. +/// +/// See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. +/// +/// This parser allows negative parts to be present in pattern. +class Iso8601DurationParser { + static final _periodOrComma = RegExp('\\.|,'); + static const _period = '.'; + static const _comma = ','; + + static final _signMarker = RegExp('^-|\\+|'); + static final _dateMarker = RegExp('P'); + static final _timeMarker = RegExp('T'); + static final _dateComponent = RegExp('(-?\\d+(?:[.,]\\d+)?)(Y|M|D|W)'); + static final _timeComponent = RegExp('(-?\\d+(?:[.,]\\d+)?)(H|M|S)'); + + static const _dateToPart = { + 'Y': 'years', + 'M': 'months', + 'W': 'weeks', + 'D': 'days' + }; + static const _timeToPart = {'H': 'hours', 'M': 'minutes', 'S': 'seconds'}; + + static const _dateComponents = ['years', 'months', 'days']; + static const _timeComponents = ['hours', 'minutes', 'seconds']; + + final Map _parts; + final StringScanner _scanner; + _DurationParsingMode _mode; + int _sign; + + /// Construct a new parser to parse the given string + Iso8601DurationParser(String string) + : _scanner = StringScanner(string), + _parts = {}, + _mode = _DurationParsingMode.start, + _sign = 1; + + /// Parse the string into a map of duration parts + Map parse() { while (!_isFinished()) { - switch(mode) { - case DurationParsingMode.Start: - if (_scan(SIGN_MARKER)) { - sign = scanner.lastMatch![0] == '-' ? -1 : 1; - mode = DurationParsingMode.Sign; - } else { - _raiseParsingError(); - } - break; - case DurationParsingMode.Sign: - if (_scan(DATE_MARKER)) { - mode = DurationParsingMode.Date; - } else { - _raiseParsingError(); - } - break; - case DurationParsingMode.Date: - if (_scan(TIME_MARKER)) { - mode = DurationParsingMode.Time; - } else if (_scan(DATE_COMPONENT)) { - parts[DATE_TO_PART[scanner.lastMatch![2]!]!] = _number() * sign; - } else { - _raiseParsingError(); - } - break; - case DurationParsingMode.Time: - if (_scan(TIME_COMPONENT)) { - parts[TIME_TO_PART[scanner.lastMatch![2]!]!] = _number() * sign; - } else { - _raiseParsingError(); - } - break; + switch (_mode) { + case _DurationParsingMode.start: + if (_scan(_signMarker)) { + _sign = _scanner.lastMatch![0] == '-' ? -1 : 1; + _mode = _DurationParsingMode.sign; + } else { + _raiseParsingError(); + } + break; + case _DurationParsingMode.sign: + if (_scan(_dateMarker)) { + _mode = _DurationParsingMode.date; + } else { + _raiseParsingError(); + } + break; + case _DurationParsingMode.date: + if (_scan(_timeMarker)) { + _mode = _DurationParsingMode.time; + } else if (_scan(_dateComponent)) { + _parts[_dateToPart[_scanner.lastMatch![2]!]!] = _number() * _sign; + } else { + _raiseParsingError(); + } + break; + case _DurationParsingMode.time: + if (_scan(_timeComponent)) { + _parts[_timeToPart[_scanner.lastMatch![2]!]!] = _number() * _sign; + } else { + _raiseParsingError(); + } + break; } } _validate(); - return parts; + return _parts; } bool _isFinished() { - return scanner.rest.length == 0; + return _scanner.rest.isEmpty; } // Parses number which can be a float with either comma or period. num _number() { - return (PERIOD_OR_COMMA.hasMatch(scanner.lastMatch![1]!)) ? - double.parse(scanner.lastMatch![1]!.replaceAll(COMMA, PERIOD)) : - int.parse(scanner.lastMatch![1]!); + return (_periodOrComma.hasMatch(_scanner.lastMatch![1]!)) + ? double.parse(_scanner.lastMatch![1]!.replaceAll(_comma, _period)) + : int.parse(_scanner.lastMatch![1]!); } bool _scan(pattern) { - return scanner.scan(pattern); + return _scanner.scan(pattern); } void _raiseParsingError([String? reason]) { - throw "Invalid ISO 8601 duration: ${scanner.string} $reason".trim(); + throw "Invalid ISO 8601 duration: ${_scanner.string} $reason".trim(); } // Checks for various semantic errors as stated in ISO 8601 standard. bool _validate() { - if (parts.isEmpty) _raiseParsingError('is empty duration'); + if (_parts.isEmpty) _raiseParsingError('is empty duration'); // Mixing any of Y, M, D with W is invalid. - if (parts.containsKey('weeks') && DATE_COMPONENTS.any((e) => parts.containsKey(e))) { + if (_parts.containsKey('weeks') && + _dateComponents.any((e) => _parts.containsKey(e))) { _raiseParsingError('mixing weeks with other date parts not allowed'); } // Specifying an empty T part is invalid. - if (mode == DurationParsingMode.Time && !TIME_COMPONENTS.any((e) => parts.containsKey(e))) { + if (_mode == _DurationParsingMode.time && + !_timeComponents.any((e) => _parts.containsKey(e))) { _raiseParsingError('time part marker is present but time part is empty'); } - var fractions = parts.values.where((a) => a != 0).where((a) => (a % 1) != 0); - if (!fractions.isEmpty && !(fractions.length == 1 && fractions.last == parts.values.where((a) => a != 0).last)) { + var fractions = + _parts.values.where((a) => a != 0).where((a) => (a % 1) != 0); + if (fractions.isNotEmpty && + !(fractions.length == 1 && + fractions.last == _parts.values.where((a) => a != 0).last)) { _raiseParsingError('(only last part can be fractional)'); } diff --git a/lib/src/types/email.dart b/lib/src/types/email.dart index 1be3ab1..0b19b96 100644 --- a/lib/src/types/email.dart +++ b/lib/src/types/email.dart @@ -1,38 +1,250 @@ +import "./hostname/validator.dart"; + import "../document.dart"; -import "./email/parser.dart"; +/// RFC5322 email address. class KdlEmail extends KdlValue { + /// Local email address part String local; + + /// Domain email address part String domain; - KdlEmail(String value, this.local, this.domain, [String? type]) : super(value, type); + /// Construct a new `KdlEmail` + KdlEmail(super.value, this.local, this.domain, [super.type]); - static call(KdlValue value, [String type = 'email']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlEmail` + static KdlEmail? convert(KdlValue value, [String type = 'email']) { + if (value is! KdlString) return null; - var parts = EmailParser(value.value).parse(); + var parts = _EmailParser(value.value).parse(); return KdlEmail(value.value, parts[0], parts[1], type); } } -class KdlIDNEmail extends KdlEmail { +/// RFC6531 internationalized email address. +class KdlIdnEmail extends KdlEmail { + /// Unicode value String unicodeValue; + + /// Unicode IDN String unicodeDomain; - KdlIDNEmail(String value, this.unicodeValue, String local, String domain, this.unicodeDomain, [String? type]) : super(value, local, domain, type); - static call(KdlValue value, [String type = 'idn-email']) { - if (!(value is KdlString)) return null; + /// Construct a new `KdlIDNEmail` + KdlIdnEmail(super.value, this.unicodeValue, super.local, super.domain, + this.unicodeDomain, + [super.type]); + + /// Convert a `KdlString` into a `KdlIDNEmail` + static KdlIdnEmail? convert(KdlValue value, [String type = 'idn-email']) { + if (value is! KdlString) return null; + + var parts = _EmailParser(value.value, idn: true).parse(); + + return KdlIdnEmail("${parts[0]}@${parts[1]}", "${parts[0]}@${parts[2]}", + parts[0], parts[1], parts[2], type); + } +} + +enum _EmailParserContext { start, afterDot, afterPart, afterAt, afterDomain } + +class _EmailParser { + final String _string; + final bool _idn; + final _EmailTokenizer _tokenizer; + + _EmailParser(this._string, {bool idn = false}) + : _idn = idn, + _tokenizer = _EmailTokenizer(_string, idn: idn); + + List parse() { + String local = ''; + late String unicodeDomain; + late String domain; + var context = _EmailParserContext.start; + + while (true) { + var token = _tokenizer.nextToken(); + + switch (token.type) { + case _EmailTokenType.emailPart: + switch (context) { + case _EmailParserContext.start: + case _EmailParserContext.afterDot: + local += token.value; + context = _EmailParserContext.afterPart; + break; + default: + throw "invalid email $_string (unexpected part ${token.value} at $context)"; + } + break; + case _EmailTokenType.dot: + switch (context) { + case _EmailParserContext.afterPart: + local += token.value; + context = _EmailParserContext.afterDot; + break; + default: + throw "invalid email $_string (unexpected dot at $context)"; + } + break; + case _EmailTokenType.at: + switch (context) { + case _EmailParserContext.afterPart: + context = _EmailParserContext.afterAt; + break; + default: + throw "invalid email $_string (unexpected dot at $context)"; + } + break; + case _EmailTokenType.domain: + switch (context) { + case _EmailParserContext.afterAt: + var validator = _idn + ? IdnHostnameValidator(token.value) + : HostnameValidator(token.value); + if (!validator.isValid()) throw "invalid hostname ${token.value}"; + + unicodeDomain = validator.unicode; + domain = validator.ascii; + context = _EmailParserContext.afterDomain; + break; + default: + throw "invalid email $_string (unexpected domain at $context)"; + } + break; + case _EmailTokenType.end: + switch (context) { + case _EmailParserContext.afterDomain: + if (local.length > 64) { + throw "invalid email $_string (local part length ${local.length} exceeds maximum of 64)"; + } + + return [local, domain, unicodeDomain]; + default: + throw "invalid email $_string (unexpected end at $context)"; + } + } + } + } +} + +enum _EmailTokenType { + emailPart, + dot, + at, + domain, + end, +} + +class _EmailToken { + _EmailTokenType type; + String value; + + _EmailToken(this.type, this.value); + + @override + String toString() => "$type:$value"; +} + +enum _EmailTokenizerContext { + start, + emailPart, + quote, +} + +class _EmailTokenizer { + static final _localPartAscii = RegExp(r"[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~]"); + static final _localPartIdn = RegExp(r"""[^\x00-\x1f\s".@]"""); - var parts = EmailParser(value.value, idn: true).parse(); + final String _string; + final bool _idn; + int _index = 0; + bool _afterAt = false; - return KdlIDNEmail( - "${parts[0]}@${parts[1]}", - "${parts[0]}@${parts[2]}", - parts[0], - parts[1], - parts[2], - type - ); + _EmailTokenizer(this._string, {bool idn = false}) : _idn = idn; + + String _substring(int start, [int? end]) { + return String.fromCharCodes( + _string.runes.toList().sublist(start, end ?? _length(_string))); + } + + int _length(String str) { + return str.runes.length; } + + _EmailToken nextToken() { + if (_afterAt) { + if (_index < _length(_string)) { + var domainStart = _index; + _index = _length(_string); + return _EmailToken(_EmailTokenType.domain, _substring(domainStart)); + } else { + return _EmailToken(_EmailTokenType.end, ''); + } + } + var context = _EmailTokenizerContext.start; + var buffer = ''; + while (true) { + if (_index >= _length(_string)) { + return _EmailToken(_EmailTokenType.end, ''); + } + var c = _charAt(_index); + + switch (context) { + case _EmailTokenizerContext.start: + switch (c) { + case '.': + _index++; + return _EmailToken(_EmailTokenType.dot, '.'); + case '@': + _afterAt = true; + _index++; + return _EmailToken(_EmailTokenType.at, '@'); + case '"': + context = _EmailTokenizerContext.quote; + _index++; + break; + default: + if (_localPartChars().hasMatch(c)) { + context = _EmailTokenizerContext.emailPart; + buffer += c; + _index++; + break; + } + throw "invalid email $_string, (unexpected $c)"; + } + break; + case _EmailTokenizerContext.emailPart: + if (_localPartChars().hasMatch(c)) { + buffer += c; + _index++; + } else if (c == '.' || c == '@') { + return _EmailToken(_EmailTokenType.emailPart, buffer); + } else { + throw "invalid email $_string (unexpected $c)"; + } + break; + case _EmailTokenizerContext.quote: + if (c == '"') { + var n = _charAt(_index + 1); + if (n != '.' && n != '@') { + throw "invalid email $_string (unexpected $c)"; + } + + _index++; + return _EmailToken(_EmailTokenType.emailPart, buffer); + } else { + buffer += c; + _index += 1; + } + break; + } + } + } + + RegExp _localPartChars() => _idn ? _localPartIdn : _localPartAscii; + + String _charAt(int i) => String.fromCharCode(_string.runes.elementAt(i)); } diff --git a/lib/src/types/email/parser.dart b/lib/src/types/email/parser.dart deleted file mode 100644 index 0800541..0000000 --- a/lib/src/types/email/parser.dart +++ /dev/null @@ -1,203 +0,0 @@ -import "../hostname/validator.dart"; - -enum EmailParserContext { - Start, - AfterDot, - AfterPart, - AfterAt, - AfterDomain -} - -class EmailParser { - String string; - bool idn; - EmailTokenizer tokenizer; - - EmailParser(this.string, {this.idn = false}) : - tokenizer = EmailTokenizer(string, idn: idn); - - List parse() { - String local = ''; - late String unicodeDomain; - late String domain; - var context = EmailParserContext.Start; - - while (true) { - var token = tokenizer.nextToken(); - - switch (token.type) { - case EmailTokenType.Part: - switch (context) { - case EmailParserContext.Start: - case EmailParserContext.AfterDot: - local += token.value; - context = EmailParserContext.AfterPart; - break; - default: - throw "invalid email $string (unexpected part ${token.value} at $context)"; - } - break; - case EmailTokenType.Dot: - switch (context) { - case EmailParserContext.AfterPart: - local += token.value; - context = EmailParserContext.AfterDot; - break; - default: - throw "invalid email $string (unexpected dot at $context)"; - } - break; - case EmailTokenType.At: - switch (context) { - case EmailParserContext.AfterPart: - context = EmailParserContext.AfterAt; - break; - default: - throw "invalid email $string (unexpected dot at $context)"; - } - break; - case EmailTokenType.Domain: - switch (context) { - case EmailParserContext.AfterAt: - var validator = idn ? IDNHostnameValidator(token.value) : HostnameValidator(token.value); - if (!validator.isValid()) throw "invalid hostname ${token.value}"; - - unicodeDomain = validator.unicode; - domain = validator.ascii; - context = EmailParserContext.AfterDomain; - break; - default: - throw "invalid email $string (unexpected domain at $context)"; - } - break; - case EmailTokenType.End: - switch (context) { - case EmailParserContext.AfterDomain: - if (local.length > 64) { - throw "invalid email $string (local part length ${local.length} exceeds maximum of 64)"; - } - - return [local, domain, unicodeDomain]; - default: - throw "invalid email $string (unexpected end at $context)"; - } - } - } - } -} - -enum EmailTokenType { - Part, - Dot, - At, - Domain, - End, -} - -class EmailToken { - EmailTokenType type; - String value; - - EmailToken(this.type, this.value); - - @override - String toString() => "$type:$value"; -} - -enum EmailTokenizerContext { - Start, - Part, - Quote, -} - -class EmailTokenizer { - static final LOCAL_PART_ASCII = RegExp(r"[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~]"); - static final LOCAL_PART_IDN = RegExp(r"""[^\x00-\x1f\s".@]"""); - - String string; - bool idn; - int index = 0; - bool afterAt = false; - - EmailTokenizer(this.string, {this.idn = false}); - - String _substring(int start, [int? end]) { - return String.fromCharCodes(string.runes.toList().sublist(start, end ?? _length(string))); - } - - int _length(String str) { - return str.runes.length; - } - - EmailToken nextToken() { - if (afterAt) { - if (index < _length(string)) { - var domainStart = index; - index = _length(string); - return EmailToken(EmailTokenType.Domain, _substring(domainStart)); - } else { - return EmailToken(EmailTokenType.End, ''); - } - } - var context = EmailTokenizerContext.Start; - var buffer = ''; - while (true) { - if (index >= _length(string)) return EmailToken(EmailTokenType.End, ''); - var c = _charAt(index); - - switch (context) { - case EmailTokenizerContext.Start: - switch (c) { - case '.': - index++; - return EmailToken(EmailTokenType.Dot, '.'); - case '@': - afterAt = true; - index++; - return EmailToken(EmailTokenType.At, '@'); - case '"': - context = EmailTokenizerContext.Quote; - index++; - break; - default: - if (_localPartChars().hasMatch(c)) { - context = EmailTokenizerContext.Part; - buffer += c; - index++; - break; - } - throw "invalid email $string, (unexpected $c)"; - } - break; - case EmailTokenizerContext.Part: - if (_localPartChars().hasMatch(c)) { - buffer += c; - index++; - } else if (c == '.' || c == '@') { - return EmailToken(EmailTokenType.Part, buffer); - } else { - throw "invalid email $string (unexpected $c)"; - } - break; - case EmailTokenizerContext.Quote: - if (c == '"') { - var n = _charAt(index + 1); - if (n != '.' && n != '@') { - throw "invalid email $string (unexpected $c)"; - } - - index++; - return EmailToken(EmailTokenType.Part, buffer); - } else { - buffer += c; - index += 1; - } - break; - } - } - } - - RegExp _localPartChars() => idn ? LOCAL_PART_IDN : LOCAL_PART_ASCII; - - String _charAt(int i) => String.fromCharCode(string.runes.elementAt(i)); -} diff --git a/lib/src/types/hostname.dart b/lib/src/types/hostname.dart index 3a0988e..15a51ee 100644 --- a/lib/src/types/hostname.dart +++ b/lib/src/types/hostname.dart @@ -1,11 +1,14 @@ import "../document.dart"; import "./hostname/validator.dart"; +/// RFC1132 internet hostname (only ASCII segments) class KdlHostname extends KdlValue { - KdlHostname(String value, [String? type]) : super(value, type); + /// Construct a new `KdlHostname` + KdlHostname(super.value, [super.type]); - static call(KdlValue value, [String type = 'hostname']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlHostname` + static KdlHostname? convert(KdlValue value, [String type = 'hostname']) { + if (value is! KdlString) return null; var validator = HostnameValidator(value.value); if (!validator.isValid()) throw "invalid hostname ${value.value}"; @@ -13,16 +16,22 @@ class KdlHostname extends KdlValue { } } -class KdlIDNHostname extends KdlHostname { +/// RFC5890 internationalized internet hostname +/// (only `xn--`-prefixed ASCII "punycode" segments, or non-ASCII segments) +class KdlIdnHostname extends KdlHostname { + /// Unicode value String unicodeValue; - KdlIDNHostname(String value, this.unicodeValue, [String? type]) : super(value, type); + /// Construct a new `KdlIDNHostname` + KdlIdnHostname(String value, this.unicodeValue, [String? type]) + : super(value, type); - static call(KdlValue value, [String type = 'idn-hostname']) { - if (!(value is KdlString)) return null; - var validator = IDNHostnameValidator(value.value); + /// Convert a `KdlString` into a `KdlIDNHostname` + static KdlIdnHostname? convert(KdlValue value, [String type = 'idn-hostname']) { + if (value is! KdlString) return null; + var validator = IdnHostnameValidator(value.value); if (!validator.isValid()) throw "invalid hostname ${value.value}"; - return KdlIDNHostname(validator.ascii, validator.unicode, type); + return KdlIdnHostname(validator.ascii, validator.unicode, type); } } diff --git a/lib/src/types/hostname/IDNAConverter.dart b/lib/src/types/hostname/idna_converter.dart similarity index 64% rename from lib/src/types/hostname/IDNAConverter.dart rename to lib/src/types/hostname/idna_converter.dart index 4f70d45..8ecb2fd 100644 --- a/lib/src/types/hostname/IDNAConverter.dart +++ b/lib/src/types/hostname/idna_converter.dart @@ -23,38 +23,38 @@ /// /// Implementation of IDNA - RFC 3490 standard converter according to /// -class IDNAConverter { - static const INVALID_INPUT = 'Invalid input'; - static const OVERFLOW = 'Overflow: input needs wider integers to process'; - static const NOT_BASIC = 'Illegal input >= 0x80 (not a basic code point)'; +class IdnaConverter { + static const _invalidInput = 'Invalid input'; + static const _overflow = 'Overflow: input needs wider integers to process'; + static const _notBasic = 'Illegal input >= 0x80 (not a basic code point)'; - static const int base = 36; - static const int tMin = 1; - static const int tMax = 26; + static const int _base = 36; + static const int _tMin = 1; + static const int _tMax = 26; - static const int skew = 38; - static const int damp = 700; + static const int _skew = 38; + static const int _damp = 700; - static const int initialBias = 72; - static const int initialN = 128; // 0x80 - static const delimiter = '-'; // '\x2D' + static const int _initialBias = 72; + static const int _initialN = 128; // 0x80 + static const _delimiter = '-'; // '\x2D' /// Highest positive signed 32-bit float value - static const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1 + static const _maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1 - static RegExp regexPunycode = RegExp(r'^xn--'); - static RegExp regexNonASCII = RegExp(r'[^\0-\x7E]'); // non-ASCII chars - static RegExp regexSeparators = + static final RegExp _regexPunycode = RegExp(r'^xn--'); + static final RegExp _regexNonASCII = RegExp(r'[^\0-\x7E]'); // non-ASCII chars + static final RegExp _regexSeparators = RegExp(r'[\u002E\u3002\uFF0E\uFF61]'); // RFC 3490 separators - static RegExp regexUrlprefix = RegExp(r'^http://|^https://'); + static final RegExp _regexUrlprefix = RegExp(r'^http://|^https://'); /// /// Converts a string that contains unicode symbols to a string with only ASCII symbols. /// static String encode(String input) { - var n = initialN; + var n = _initialN; var delta = 0; - var bias = initialBias; + var bias = _initialBias; var output = []; // Copy all basic code points to the output @@ -69,12 +69,12 @@ class IDNAConverter { // Append delimiter if (b > 0) { - output.add(delimiter.codeUnitAt(0)); + output.add(_delimiter.codeUnitAt(0)); } var h = b; while (h < input.length) { - var m = maxInt; + var m = _maxInt; // Find the minimum code point >= n for (var i = 0; i < input.length; i++) { @@ -84,8 +84,8 @@ class IDNAConverter { } } - if (m - n > (maxInt - delta) / (h + 1)) { - throw ArgumentError(OVERFLOW); + if (m - n > (_maxInt - delta) / (h + 1)) { + throw ArgumentError(_overflow); } delta = delta + (m - n) * (h + 1); n = m; @@ -95,30 +95,30 @@ class IDNAConverter { if (c < n) { delta++; if (0 == delta) { - throw ArgumentError(OVERFLOW); + throw ArgumentError(_overflow); } } if (c == n) { var q = delta; - for (var k = base;; k += base) { + for (var k = _base;; k += _base) { int t; if (k <= bias) { - t = tMin; - } else if (k >= bias + tMax) { - t = tMax; + t = _tMin; + } else if (k >= bias + _tMax) { + t = _tMax; } else { t = k - bias; } if (q < t) { break; } - output.add((digitToBasic(t + (q - t) % (base - t)))); - q = ((q - t) / (base - t)).floor(); + output.add((digitToBasic(t + (q - t) % (_base - t)))); + q = ((q - t) / (_base - t)).floor(); } output.add(digitToBasic(q)); - bias = adapt(delta, h + 1, h == b); + bias = _adapt(delta, h + 1, h == b); delta = 0; h++; } @@ -135,17 +135,17 @@ class IDNAConverter { /// Decode a ASCII string to the corresponding unicode string. /// static String decode(String input) { - var n = initialN; + var n = _initialN; var i = 0; - var bias = initialBias; + var bias = _initialBias; var output = []; - var d = input.lastIndexOf(delimiter); + var d = input.lastIndexOf(_delimiter); if (d > 0) { for (var j = 0; j < d; j++) { var c = input.codeUnitAt(j); if (!isBasic(c)) { - throw ArgumentError(INVALID_INPUT); + throw ArgumentError(_invalidInput); } output.add(c); } @@ -158,36 +158,36 @@ class IDNAConverter { var oldi = i; var w = 1; - for (var k = base;; k += base) { + for (var k = _base;; k += _base) { if (d == input.length) { - throw ArgumentError(INVALID_INPUT); + throw ArgumentError(_invalidInput); } var c = input.codeUnitAt(d++); var digit = basicToDigit(c); - if (digit > (maxInt - i) / w) { - throw ArgumentError(OVERFLOW); + if (digit > (_maxInt - i) / w) { + throw ArgumentError(_overflow); } i = i + digit * w; int t; if (k <= bias) { - t = tMin; - } else if (k >= bias + tMax) { - t = tMax; + t = _tMin; + } else if (k >= bias + _tMax) { + t = _tMax; } else { t = k - bias; } if (digit < t) { break; } - w = w * (base - t); + w = w * (_base - t); } - bias = adapt(i - oldi, output.length + 1, oldi == 0); + bias = _adapt(i - oldi, output.length + 1, oldi == 0); - if (i / (output.length + 1) > maxInt - n) { - throw ArgumentError(OVERFLOW); + if (i / (output.length + 1) > _maxInt - n) { + throw ArgumentError(_overflow); } n = (n + i / (output.length + 1)).floor(); @@ -199,9 +199,9 @@ class IDNAConverter { return String.fromCharCodes(output); } - static int adapt(int delta, int numpoints, bool first) { + static int _adapt(int delta, int numpoints, bool first) { if (first) { - delta = (delta / damp).floor(); + delta = (delta / _damp).floor(); } else { delta = (delta / 2).floor(); } @@ -209,12 +209,12 @@ class IDNAConverter { delta = delta + (delta / numpoints).floor(); var k = 0; - while (delta > ((base - tMin) * tMax) / 2) { - delta = (delta / (base - tMin)).floor(); - k = k + base; + while (delta > ((_base - _tMin) * _tMax) / 2) { + delta = (delta / (_base - _tMin)).floor(); + k = k + _base; } - return (k + ((base - tMin + 1) * delta) / (delta + skew)).floor(); + return (k + ((_base - _tMin + 1) * delta) / (delta + _skew)).floor(); } /// @@ -237,7 +237,7 @@ class IDNAConverter { // 26..35 : '0'..'9'; return digit - 26 + '0'.codeUnitAt(0); } else { - throw ArgumentError(INVALID_INPUT); + throw ArgumentError(_invalidInput); } } @@ -256,7 +256,7 @@ class IDNAConverter { // 'a'..'z' : 0..25 return codePoint - 'a'.codeUnitAt(0); } else { - throw ArgumentError(INVALID_INPUT); + throw ArgumentError(_invalidInput); } } @@ -275,44 +275,42 @@ class IDNAConverter { } static String _urlConvert(String url, bool shouldencode) { - var _url = []; - var _result = []; - if (regexUrlprefix.hasMatch(url)) { - _url = url.split('/'); + var url0 = []; + var result = []; + if (_regexUrlprefix.hasMatch(url)) { + url0 = url.split('/'); } else { - _url.add(url); + url0.add(url); } - _url.forEach((String _suburl) { - _suburl = _suburl.replaceAll(regexSeparators, '\x2E'); + for (var suburl in url0) { + suburl = suburl.replaceAll(_regexSeparators, '\x2E'); - var _split = _suburl.split('.'); + var split = suburl.split('.'); - var _join = []; + var join = []; - _split.forEach((elem) { + for (var elem in split) { // do decode and encode if (shouldencode) { - if (regexPunycode.hasMatch(elem) || - regexNonASCII.hasMatch(elem) == false) { - _join.add(elem); + if (_regexPunycode.hasMatch(elem) || + _regexNonASCII.hasMatch(elem) == false) { + join.add(elem); } else { - _join.add('xn--' + encode(elem)); + join.add('xn--${encode(elem)}'); } } else { - if (regexNonASCII.hasMatch(elem)) { - throw ArgumentError(NOT_BASIC); + if (_regexNonASCII.hasMatch(elem)) { + throw ArgumentError(_notBasic); } else { - _join.add(regexPunycode.hasMatch(elem) - ? decode(elem.replaceFirst(regexPunycode, '')) + join.add(_regexPunycode.hasMatch(elem) + ? decode(elem.replaceFirst(_regexPunycode, '')) : elem); } } - }); - _result.add(_join.isNotEmpty ? _join.join('.') : _suburl); - }); + } + result.add(join.isNotEmpty ? join.join('.') : suburl); + } - return _result.length > 1 - ? _result.join('/') - : _result.elementAt(0); + return result.length > 1 ? result.join('/') : result.elementAt(0); } } diff --git a/lib/src/types/hostname/validator.dart b/lib/src/types/hostname/validator.dart index 8113f6b..9096f54 100644 --- a/lib/src/types/hostname/validator.dart +++ b/lib/src/types/hostname/validator.dart @@ -1,44 +1,58 @@ -import "./IDNAConverter.dart"; +import "./idna_converter.dart"; +/// Validates hostnames class HostnameValidator { - static final PART_RGX = RegExp("^[a-z0-9_][a-z0-9_\\-]{0,62}\$", caseSensitive: false); + static final _partRgx = + RegExp("^[a-z0-9_][a-z0-9_\\-]{0,62}\$", caseSensitive: false); - String string; - String get ascii => string; - String get unicode => string; + final String _string; - HostnameValidator(this.string); + /// ASCII Hostname value + String get ascii => _string; + /// Unicode hostname value + String get unicode => _string; + + /// Construct a new `HostnameValidator` to validate the given string + HostnameValidator(this._string); + + /// Return true if the string is a valid hostname bool isValid() { - if (string.length > 253) return false; + if (_string.length > 253) return false; - return !string.split('.').any((part) => !_validPart(part)); + return !_string.split('.').any((part) => !_validPart(part)); } bool _validPart(String part) { if (part.isEmpty) return false; if (part.startsWith('-') || part.endsWith('-')) return false; - return PART_RGX.hasMatch(part); - } - - static validator() { - + return _partRgx.hasMatch(part); } } -class IDNHostnameValidator extends HostnameValidator { +/// Hostname validator for Internationalized Domain Names +class IdnHostnameValidator extends HostnameValidator { + @override String unicode; - IDNHostnameValidator.fromAscii(String string) : unicode = IDNAConverter.urlDecode(string), super(string); - IDNHostnameValidator.fromUnicode(String string) : unicode = string, super(IDNAConverter.urlEncode(string)); + /// Validate an ASCII IDN Hostname + IdnHostnameValidator.fromAscii(super.string) + : unicode = IdnaConverter.urlDecode(string); + + /// Validate a Unicode IDN Hostname + IdnHostnameValidator.fromUnicode(String string) + : unicode = string, + super(IdnaConverter.urlEncode(string)); - factory IDNHostnameValidator(String string) { + /// Constructs the appropriate IDN Hostname Validator depending on if the + /// hostname is in ASCII or Unicode format + factory IdnHostnameValidator(String string) { var isAscii = string.split('.').any((x) => x.startsWith('xn--')); if (isAscii) { - return IDNHostnameValidator.fromAscii(string); + return IdnHostnameValidator.fromAscii(string); } else { - return IDNHostnameValidator.fromUnicode(string); + return IdnHostnameValidator.fromUnicode(string); } } } diff --git a/lib/src/types/ip.dart b/lib/src/types/ip.dart index 9a6f8e5..405eaf7 100644 --- a/lib/src/types/ip.dart +++ b/lib/src/types/ip.dart @@ -1,34 +1,152 @@ -import "dart:io"; -import "../document.dart"; +// IPv4 and IPv6 Regular Expression patterns copied from Ruby's lib/resolv.rb +// +// Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. -class KdlIP extends KdlValue { - KdlIP(InternetAddress value, [String? type]) : super(value, type); +import "package:kdl/src/document.dart"; +import "package:kdl/src/exception.dart"; + +/// Base class for IPv4 and IPv6 addresses +class KdlIP extends KdlValue { + /// Construct a new `KdlIP` + KdlIP(super.value, [super.type]); } +/// RFC2673 dotted-quad IPv4 address. class KdlIPV4 extends KdlIP { - KdlIPV4(InternetAddress value, [String? type]) : super(value, type); + static final _decbyte = r""" + 0 + |1(?:[0-9][0-9]?)? + |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? + |[3-9][0-9]? + """ + .trim() + .split('\n') + .map((v) => v.trim()) + .join(); + static final _regexp = + RegExp("^($_decbyte)\\.($_decbyte)\\.($_decbyte)\\.($_decbyte)\$"); + + /// Construct a new `KdlIPV4` + KdlIPV4(super.value, [super.type]); - static call(KdlValue value, [String type = 'ipv4']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlIPV4` + static KdlIPV4? convert(KdlValue value, [String type = 'ipv4']) { + if (value is! KdlString) return null; + var addr = value.value; - var addr = InternetAddress(value.value); - if (addr.type != InternetAddressType.IPv4) { - throw "invalid ipv4 address: ${value.value}"; + if (!_regexp.hasMatch(addr)) { + throw KdlException("invalid ipv4 address: $addr"); } return KdlIPV4(addr, type); } } +/// RFC2373 IPv6 address. class KdlIPV6 extends KdlIP { - KdlIPV6(InternetAddress value, [String? type]) : super(value, type); + // IPv6 address format a:b:c:d:e:f:g:h + static final _regexp8Hex = r""" + (?:[0-9A-Fa-f]{1,4}:){7} + [0-9A-Fa-f]{1,4} + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // Compressed IPv6 address format a::b + static final _regexpCompressedHex = r""" + ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?):: + ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z + static final _regexp6Hex4Dec = r""" + ((?:[0-9A-Fa-f]{1,4}:){6,6}) + (\d+)\.(\d+)\.(\d+)\.(\d+) + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z + static final _regexpCompressedHex4Dec = r""" + ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?):: + ((?:[0-9A-Fa-f]{1,4}:)*) + (\d+)\.(\d+)\.(\d+)\.(\d+) + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // IPv6 link local address format fe80:b:c:d:e:f:g:h%em1 + static final _regexp8HexLinkLocal = r""" + [Ff][Ee]80 + (?::[0-9A-Fa-f]{1,4}){7} + %[-0-9A-Za-z._~]+ + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // Compressed IPv6 link local address format fe80::b%em1 + static final _regexpCompressedHexLinkLocal = r""" + [Ff][Ee]80: + (?: + ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?):: + ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) + | + :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) + )? + :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+ + """ + .split('\n') + .map((v) => v.trim()) + .join(); + + // A composite IPv6 address Regexp. + static final _regexp = RegExp("^${[ + _regexp8Hex, + _regexpCompressedHex, + _regexp6Hex4Dec, + _regexpCompressedHex4Dec, + _regexp8HexLinkLocal, + _regexpCompressedHexLinkLocal + ].map((v) => "(?:$v)").join('|')}\$"); + + /// Construct a new `KdlIPV6` + KdlIPV6(super.value, [super.type]); - static call(KdlValue value, [String type = 'ipv6']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlIPV6` + static KdlIPV6? convert(KdlValue value, [String type = 'ipv6']) { + if (value is! KdlString) return null; + var addr = value.value; - var addr = InternetAddress(value.value); - if (addr.type != InternetAddressType.IPv6) { - throw "invalid ipv6 address: ${value.value}"; + if (!_regexp.hasMatch(addr)) { + throw KdlException("invalid ipv6 address: $addr"); } return KdlIPV6(addr, type); diff --git a/lib/src/types/irl.dart b/lib/src/types/irl.dart index bec2cc4..4d47060 100644 --- a/lib/src/types/irl.dart +++ b/lib/src/types/irl.dart @@ -1,72 +1,64 @@ import "../document.dart"; import "./irl/parser.dart"; -class KdlIRLReference extends KdlValue { +/// RFC3987 Internationalized Resource Identifier. +class KdlIRL extends KdlValue { + /// Unicode value String unicodeValue; + + /// Unicode domain String? unicodeDomain; + + /// Unicode path String? unicodePath; + + /// Unicode search query String? unicodeSearch; + + /// Unicode hash value String? unicodeHash; - KdlIRLReference( - Uri value, - this.unicodeValue, - this.unicodeDomain, - this.unicodePath, - this.unicodeSearch, - this.unicodeHash, - [String? type] - ) : super(value, type); + /// Construct a new `KdlIRL` + KdlIRL(super.value, this.unicodeValue, this.unicodeDomain, this.unicodePath, + this.unicodeSearch, this.unicodeHash, + [super.type]); + + KdlIRL._from(Irl value, [String? type]) + : this( + Uri.parse(value.asciiValue), + value.unicodeValue, + value.unicodeDomain, + value.unicodePath, + value.unicodeSearch, + value.unicodeHash, + type); - static call(KdlValue value, [String type = 'irl-reference']) { - if (!(value is KdlString)) return null; + /// Converts a `KdlString` into a `KdlIRL` + static KdlIRL? convert(KdlValue value, [String type = 'irl']) { + if (value is! KdlString) return null; - var params = IRLReferenceParser(value.value).parse(); + var irl = IrlParser(value.value, isReference: false).parse(); - return KdlIRLReference( - Uri.parse(params[0]), - params[1], - params[2], - params[3], - params[4], - params[5], - type, - ); + return KdlIRL._from(irl, type); } } -class KdlIRL extends KdlIRLReference { - KdlIRL( - Uri value, - String unicodeValue, - String? unicodeDomain, - String? unicodePath, - String? unicodeSearch, - String? unicodeHash, - [String? type] - ) : super( - value, - unicodeValue, - unicodeDomain, - unicodePath, - unicodeSearch, - unicodeHash, - type - ); +/// RFC3987 Internationalized Resource Identifier Reference. +class KdlIrlReference extends KdlIRL { + /// Constructs a new `KdlIRLReference` + KdlIrlReference(super.value, super.unicodeValue, super.unicodeDomain, + super.unicodePath, super.unicodeSearch, super.unicodeHash, + [super.type]); + + KdlIrlReference._from(super.value, [super.type]) : super._from(); - static call(KdlValue value, [String type = 'irl-reference']) { - if (!(value is KdlString)) return null; + /// Converts a `KdlString` into a `KdlIRLReference` + static KdlIrlReference? convert(KdlValue value, + [String type = 'irl-reference']) { + if (value is! KdlString) return null; - var params = IRLParser(value.value).parse(); + var irl = IrlParser(value.value).parse(); - return KdlIRL( - Uri.parse(params[0]), - params[1], - params[2], - params[3], - params[4], - params[5], - type, - ); + return KdlIrlReference._from(irl, type); } } diff --git a/lib/src/types/irl/parser.dart b/lib/src/types/irl/parser.dart index 8e74c1a..9bf8001 100644 --- a/lib/src/types/irl/parser.dart +++ b/lib/src/types/irl/parser.dart @@ -2,44 +2,164 @@ import "dart:convert"; import "../hostname/validator.dart"; -class IRLReferenceParser { - static final RGX = RegExp(r"^(?:(?:([a-z][a-z0-9+.\-]+)):\/\/([^@]+@)?([^\/?#]+)?)?(\/?[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$", caseSensitive: false); - static final PERCENT_RGX = RegExp(r"%([a-f0-9]{2})", caseSensitive: false); +/// RFC3987 Internationalized Resource Identifier. +class Irl { + /// ASCII punycode value + String asciiValue; - static const RESERVED_URL_CHARS = [ - '!', '#', '&', "'", '(', ')', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']', '%' + /// Unicode value + String unicodeValue; + + /// Unicode domain + String? unicodeDomain; + + /// Unicode path + String? unicodePath; + + /// Unicode search query + String? unicodeSearch; + + /// Unicode hash value + String? unicodeHash; + + /// Construct a new IRL + Irl(this.asciiValue, this.unicodeValue, + [this.unicodeDomain, + this.unicodePath, + this.unicodeSearch, + this.unicodeHash]); +} + +/// Parses a string into an IRL +class IrlParser { + static final _rgx = RegExp( + r"^(?:(?:([a-z][a-z0-9+.\-]+)):\/\/([^@]+@)?([^\/?#]+)?)?(\/?[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$", + caseSensitive: false); + + static const _reservedUrlChars = [ + '!', + '#', + '&', + "'", + '(', + ')', + '*', + '+', + ',', + '/', + ':', + ';', + '=', + '?', + '@', + '[', + ']', + '%' ]; - static const UNRESERVED_URL_CHARS = [ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', '.', '~' + static const _unreservedUrlChars = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '-', + '_', + '.', + '~' ]; - static final URL_CHARS = RESERVED_URL_CHARS + UNRESERVED_URL_CHARS; + static final _urlChars = _reservedUrlChars + _unreservedUrlChars; - String string; - - IRLReferenceParser(this.string); + final String _string; + final bool _isReference; - parse() { + /// Construct a new parser to parse the given string + /// pass `isReference: false` to validate this as a full IRL + /// (i.e. has a scheme) + IrlParser(this._string, {isReference = true}) : _isReference = isReference; + + /// Parse the string into an IRL + Irl parse() { List parts = _parseUrl(); var scheme = parts[0]; + if (!_isReference && (scheme == null || scheme.isEmpty)) { + throw "invalid IRL $_string"; + } var auth = parts[1]; var domain = parts[2]; var path = parts[3]; var search = parts[4]; var hash = parts[5]; - String? unicodeDomain = null; - String? unicodePath = null; - String? unicodeSearch = null; - String? unicodeHash = null; + String? unicodeDomain; + String? unicodePath; + String? unicodeSearch; + String? unicodeHash; - if (string.runes.any((rune) => rune > 127)) { + if (_string.runes.any((rune) => rune > 127)) { unicodePath = path; path = _encode(unicodePath); unicodeSearch = search; - var searchParams = unicodeSearch == null ? null : unicodeSearch.split('&').map((x) => x.split('=')); - search = searchParams == null ? null : searchParams.map((x) => "${_encode(x[0])}=${_encode(x[1])}").join('&'); + var searchParams = unicodeSearch?.split('&').map((x) => x.split('=')); + search = searchParams + ?.map((x) => "${_encode(x[0])}=${_encode(x[1])}") + .join('&'); unicodeHash = hash; hash = _encode(hash); } else { @@ -49,52 +169,51 @@ class IRLReferenceParser { } if (domain != null) { - var validator = IDNHostnameValidator(domain); + var validator = IdnHostnameValidator(domain); domain = validator.ascii; unicodeDomain = validator.unicode; } else { unicodeDomain = domain; } - var unicodeValue = _buildUriString(scheme, auth, unicodeDomain, unicodePath, unicodeSearch, unicodeHash); + var unicodeValue = _buildUriString( + scheme, auth, unicodeDomain, unicodePath, unicodeSearch, unicodeHash); var asciiValue = _buildUriString(scheme, auth, domain, path, search, hash); - return [ - asciiValue, - unicodeValue, - unicodeDomain, - unicodePath, - unicodeSearch, - unicodeHash - ]; + return Irl(asciiValue, unicodeValue, unicodeDomain, unicodePath, + unicodeSearch, unicodeHash); } List _parseUrl() { - var match = RGX.firstMatch(string); - if (match == null) throw "invalid IRL $string"; + var match = _rgx.firstMatch(_string); + if (match == null) throw "invalid IRL $_string"; - var parts = match.groups([1,2,3,4,5,6]); - if (parts.any((part) => !_isValidUrlPart(part))) throw "invalid IRL $string"; + var parts = match.groups([1, 2, 3, 4, 5, 6]); + if (parts.any((part) => !_isValidUrlPart(part))) { + throw "invalid IRL $_string"; + } return parts; } - static _isValidUrlPart(String? string) { + static bool _isValidUrlPart(String? string) { if (string == null) return true; return !string.runes.any((rune) => - rune <= 127 && !URL_CHARS.contains(String.fromCharCode(rune))); + rune <= 127 && !_urlChars.contains(String.fromCharCode(rune))); } - static _encode(String? string) { + static String? _encode(String? string) { if (string == null) return null; return string.runes - .map((c) => c <= 127 ? String.fromCharCode(c) : percentEncode(String.fromCharCode(c))) - .join(); + .map((c) => c <= 127 + ? String.fromCharCode(c) + : percentEncode(String.fromCharCode(c))) + .join(); } - static _decode(String? string) { + static String? _decode(String? string) { if (string == null) return null; List bytes = []; @@ -111,11 +230,17 @@ class IRLReferenceParser { return utf8.decode(bytes); } - static percentEncode(String c) { - return utf8.encode(c).map((b) => "%${b.toRadixString(16)}").join().toUpperCase(); + /// URL-encode the given string + static String percentEncode(String c) { + return utf8 + .encode(c) + .map((b) => "%${b.toRadixString(16)}") + .join() + .toUpperCase(); } - static _buildUriString(String? scheme, String? auth, String? domain, String? path, String? search, String? hash) { + static String _buildUriString(String? scheme, String? auth, String? domain, + String? path, String? search, String? hash) { var string = ''; if (scheme != null) string += "$scheme://"; if (auth != null) string += auth; @@ -126,16 +251,3 @@ class IRLReferenceParser { return string; } } - -class IRLParser extends IRLReferenceParser { - IRLParser(String string): super(string); - - @override - _parseUrl() { - var parts = super._parseUrl(); - var scheme = parts[0]; - if (scheme == null || scheme.isEmpty) throw "invalid IRL $string"; - - return parts; - } -} diff --git a/lib/src/types/regex.dart b/lib/src/types/regex.dart index 8cfd574..c7f8637 100644 --- a/lib/src/types/regex.dart +++ b/lib/src/types/regex.dart @@ -1,10 +1,13 @@ import "../document.dart"; +/// Regular expression. class KdlRegex extends KdlValue { - KdlRegex(RegExp value, [String? type]) : super(value, type); + /// Construct a new `KdlRegex` + KdlRegex(super.value, [super.type]); - static call(KdlValue value, [String type = 'regex']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlRegex` + static KdlRegex? convert(KdlValue value, [String type = 'regex']) { + if (value is! KdlString) return null; return KdlRegex(RegExp(value.value), type); } diff --git a/lib/src/types/url.dart b/lib/src/types/url.dart index 38da6c6..a8cee83 100644 --- a/lib/src/types/url.dart +++ b/lib/src/types/url.dart @@ -1,24 +1,31 @@ import "../document.dart"; -class KdlURLReference extends KdlValue { - KdlURLReference(Uri value, [String? type]) : super(value, type); +/// RFC3986 URI Reference. +class KdlUrlReference extends KdlValue { + /// Constructs a new `KdlURLReference` + KdlUrlReference(super.value, [super.type]); - static call(KdlValue value, [String type = 'url-reference']) { - if (!(value is KdlString)) return null; + /// Converts a `KdlString` into a `KdlURLReference` + static KdlUrlReference? convert(KdlValue value, + [String type = 'url-reference']) { + if (value is! KdlString) return null; - return KdlURLReference(Uri.parse(value.value), type); + return KdlUrlReference(Uri.parse(value.value), type); } } -class KdlURL extends KdlURLReference { - KdlURL(Uri value, [String? type]) : super(value, type); +/// RFC3986 URI. +class KdlUrl extends KdlUrlReference { + /// Constructs a new `KdlURL` + KdlUrl(super.value, [super.type]); - static call(KdlValue value, [String type = 'url']) { - if (!(value is KdlString)) return null; + /// Converts a `KdlString` into a `KdlURL` + static KdlUrl? convert(KdlValue value, [String type = 'url']) { + if (value is! KdlString) return null; var uri = Uri.parse(value.value); if (uri.scheme == '') throw "Invalid URL: ${value.value}"; - return KdlURL(Uri.parse(value.value), type); + return KdlUrl(Uri.parse(value.value), type); } } diff --git a/lib/src/types/url_template.dart b/lib/src/types/url_template.dart index a639614..1af505f 100644 --- a/lib/src/types/url_template.dart +++ b/lib/src/types/url_template.dart @@ -1,16 +1,20 @@ import "../document.dart"; import "./url_template/parser.dart"; -class KdlURLTemplate extends KdlValue { - KdlURLTemplate(URLTemplate value, [String? type]) : super(value, type); +/// RFC6570 URI Template. +class KdlUrlTemplate extends KdlValue { + /// Construct a new `KdlURLTemplate` + KdlUrlTemplate(super.value, [super.type]); - static call(KdlValue value, [String type = 'url-emplate']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlURLTemplate` + static KdlUrlTemplate? convert(KdlValue value, [String type = 'url-emplate']) { + if (value is! KdlString) return null; - var template = URLTemplateParser(value.value).parse(); + var template = UrlTemplateParser(value.value).parse(); - return KdlURLTemplate(template, type); + return KdlUrlTemplate(template, type); } + /// Expand the template into a Uri using the given values Uri expand(values) => value.expand(values); } diff --git a/lib/src/types/url_template/parser.dart b/lib/src/types/url_template/parser.dart index ee6dc0c..31b7de2 100644 --- a/lib/src/types/url_template/parser.dart +++ b/lib/src/types/url_template/parser.dart @@ -2,99 +2,123 @@ import "dart:math"; import "../irl/parser.dart"; -class URLTemplate { - List parts; - - URLTemplate(this.parts); - - expand(values) { - var result = parts.map((part) => part.expand(values)).join(); - var parser = IRLReferenceParser(result); - var uri = parser.parse()[0]; - return Uri.parse(uri); +/// RFC6570 URI Template. +class UrlTemplate { + final List<_UrlTemplatePart> _parts; + + /// Construct a new URL template with the given parts + UrlTemplate(this._parts); + + /// Expand the template into a Uri with the given values + Uri expand(values) { + var result = _parts.map((p) => p._expand(values)).join(); + var parser = IrlParser(result); + return Uri.parse(parser.parse().asciiValue); } @override - String toString() => parts.map((part) => part.toString()).join(); + String toString() => _parts.map((p) => p.toString()).join(); } -enum URLTemplateParserContext { - Start, - Literal, - Expansion, +enum _UrlTemplateParserContext { + start, + literal, + expansion, } -class URLTemplateParser { - static final UNRESERVED = RegExp(r"[a-zA-Z0-9\-._~]"); - static final RESERVED = RegExp(r"[:/?#\[\]@!$&'()*+,;=]"); +/// Parses a string into a URLTemplate +class UrlTemplateParser { + static final _unreserved = RegExp(r"[a-zA-Z0-9\-._~]"); + static final _reserved = RegExp(r"[:/?#\[\]@!$&'()*+,;=]"); - String string; - int index = 0; + final String _string; + int _index = 0; - URLTemplateParser(this.string); + /// Construct a new URL template parser for parsing the given string + UrlTemplateParser(this._string); - URLTemplate parse() { - List result = []; - URLTemplatePart? token = null; + /// Parse the string into a URL template + UrlTemplate parse() { + List<_UrlTemplatePart> result = []; + _UrlTemplatePart? token; while ((token = _nextToken()) != null) { result.add(token!); } - return URLTemplate(result); + return UrlTemplate(result); } - URLTemplatePart? _nextToken() { + _UrlTemplatePart? _nextToken() { var buffer = ''; - var context = URLTemplateParserContext.Start; - late URLTemplatePart expansion; + var context = _UrlTemplateParserContext.start; + late _UrlTemplatePart expansion; while (true) { - var c = index < string.length ? string[index] : null; + var c = _index < _string.length ? _string[_index] : null; switch (context) { - case URLTemplateParserContext.Start: + case _UrlTemplateParserContext.start: switch (c) { case '{': - context = URLTemplateParserContext.Expansion; + context = _UrlTemplateParserContext.expansion; buffer = ''; - var n = index < string.length - 1 ? string[index + 1] : null; + var n = _index < _string.length - 1 ? _string[_index + 1] : null; switch (n) { - case '+': expansion = ReservedExpansion(); break; - case '#': expansion = FragmentExpansion(); break; - case '.': expansion = LabelExpansion(); break; - case '/': expansion = PathExpansion(); break; - case ';': expansion = ParameterExpansion(); break; - case '?': expansion = QueryExpansion(); break; - case '&': expansion = QueryContinuation(); break; - default: expansion = StringExpansion(); break; + case '+': + expansion = _ReservedExpansion(); + break; + case '#': + expansion = _FragmentExpansion(); + break; + case '.': + expansion = _LabelExpansion(); + break; + case '/': + expansion = _PathExpansion(); + break; + case ';': + expansion = _ParameterExpansion(); + break; + case '?': + expansion = _QueryExpansion(); + break; + case '&': + expansion = _QueryContinuation(); + break; + default: + expansion = _StringExpansion(); + break; } - index += (expansion.runtimeType == StringExpansion) ? 1 : 2; + _index += (expansion.runtimeType == _StringExpansion) ? 1 : 2; break; - case null: return null; + case null: + return null; default: - buffer = c!; - index++; - context = URLTemplateParserContext.Literal; + buffer = c; + _index++; + context = _UrlTemplateParserContext.literal; break; } break; - case URLTemplateParserContext.Literal: + case _UrlTemplateParserContext.literal: switch (c) { - case '{': case null: return StringLiteral(buffer); + case '{': + case null: + return _StringLiteral(buffer); default: - buffer += c!; - index++; + buffer += c; + _index++; break; } break; - case URLTemplateParserContext.Expansion: + case _UrlTemplateParserContext.expansion: switch (c) { case '}': - index++; + _index++; _parseVariables(buffer, expansion); return expansion; case null: throw 'unterminated expansion'; default: - buffer += c!; - index++; + buffer += c; + _index++; break; } break; @@ -102,39 +126,39 @@ class URLTemplateParser { } } - void _parseVariables(String string, URLTemplatePart part) { - part.variables = string.split(',').map((str) { + void _parseVariables(String string, _UrlTemplatePart part) { + part._variables = string.split(',').map((str) { var match = RegExp(r"^(.*)\*$").firstMatch(str); if (match != null) { - return URLTemplateVariable( + return _UrlTemplateVariable( match[1]!, explode: true, - allowReserved: part.allowReserved, - withName: part.withName, - keepEmpties: part.keepEmpties, + allowReserved: part._allowReserved, + withName: part._withName, + keepEmpties: part._keepEmpties, ); } match = RegExp(r"^(.*):(\d+)$").firstMatch(str); if (match != null) { - return URLTemplateVariable( + return _UrlTemplateVariable( match[1]!, limit: int.parse(match[2]!), - allowReserved: part.allowReserved, - withName: part.withName, - keepEmpties: part.keepEmpties, + allowReserved: part._allowReserved, + withName: part._withName, + keepEmpties: part._keepEmpties, ); } - return URLTemplateVariable( + return _UrlTemplateVariable( str, - allowReserved: part.allowReserved, - withName: part.withName, - keepEmpties: part.keepEmpties, + allowReserved: part._allowReserved, + withName: part._withName, + keepEmpties: part._keepEmpties, ); }).toList(); } } -class URLTemplateVariable { +class _UrlTemplateVariable { String name; int? limit; bool explode; @@ -142,13 +166,12 @@ class URLTemplateVariable { bool withName; bool keepEmpties; - URLTemplateVariable(this.name, { - this.limit, - this.explode = false, - this.allowReserved = false, - this.withName = false, - this.keepEmpties = false - }); + _UrlTemplateVariable(this.name, + {this.limit, + this.explode = false, + this.allowReserved = false, + this.withName = false, + this.keepEmpties = false}); expand(value) { if (explode) { @@ -186,10 +209,10 @@ class URLTemplateVariable { } if (value is Map) { var list = []; - value.entries.forEach((entry) { + for (var entry in value.entries) { list.add(entry.key); list.add(entry.value); - }); + } return _flatten(list); } if (value is List) { @@ -197,7 +220,7 @@ class URLTemplateVariable { return result.isEmpty ? null : result.join(','); } } - + _encode(value) { if (value == null) return null; @@ -205,10 +228,11 @@ class URLTemplateVariable { var result = ''; for (int i = 0; i < string.length; i++) { var c = string[i]; - if (URLTemplateParser.UNRESERVED.hasMatch(c) || (allowReserved && URLTemplateParser.RESERVED.hasMatch(c))) { + if (UrlTemplateParser._unreserved.hasMatch(c) || + (allowReserved && UrlTemplateParser._reserved.hasMatch(c))) { result += c; } else { - result += IRLReferenceParser.percentEncode(c); + result += IrlParser.percentEncode(c); } } return result; @@ -231,131 +255,130 @@ class URLTemplateVariable { } } -abstract class URLTemplatePart { - List variables; +abstract class _UrlTemplatePart { + List<_UrlTemplateVariable> _variables; - URLTemplatePart([this.variables = const []]); + _UrlTemplatePart([this._variables = const []]); - _expandVariables(values) { + _expandVariables(Map values) { var list = []; - variables.forEach((variable) { + for (var variable in _variables) { var expanded = variable.expand(values[variable.name]); if (expanded != null) list.addAll(expanded); - }); + } return list; } - String get separator => ','; - String get prefix => ''; - bool get allowReserved => false; - bool get withName => false; - bool get keepEmpties => false; + String get _separator => ','; + String get _prefix => ''; + bool get _allowReserved => false; + bool get _withName => false; + bool get _keepEmpties => false; - String expand(values); + String _expand(values); } -class StringLiteral extends URLTemplatePart { +class _StringLiteral extends _UrlTemplatePart { String value; - StringLiteral(this.value) : super([]); + _StringLiteral(this.value) : super([]); @override - expand(values) => value; + _expand(values) => value; @override String toString() => value; } -class StringExpansion extends URLTemplatePart { - StringExpansion([List variables = const []]) : super(variables); - +class _StringExpansion extends _UrlTemplatePart { @override - expand(values) { + _expand(values) { var expanded = _expandVariables(values); if (expanded.isEmpty) return ''; - return prefix + expanded.join(separator); + return _prefix + expanded.join(_separator); } @override - String toString() => "{${variables.join(',')}}"; + String toString() => "{${_variables.join(',')}}"; } -class ReservedExpansion extends StringExpansion { +class _ReservedExpansion extends _StringExpansion { @override - bool get allowReserved => true; + bool get _allowReserved => true; @override - String toString() => "{+${variables.join(',')}}"; + String toString() => "{+${_variables.join(',')}}"; } -class FragmentExpansion extends StringExpansion { +class _FragmentExpansion extends _StringExpansion { @override - String get prefix => '#'; + String get _prefix => '#'; @override - bool get allowReserved => true; + bool get _allowReserved => true; @override - String toString() => "{#${variables.join(',')}}"; + String toString() => "{#${_variables.join(',')}}"; } -class LabelExpansion extends StringExpansion { +class _LabelExpansion extends _StringExpansion { @override - String get prefix => '.'; + String get _prefix => '.'; @override - String get separator => '.'; + String get _separator => '.'; @override - String toString() => "{.${variables.join(',')}}"; + String toString() => "{.${_variables.join(',')}}"; } -class PathExpansion extends StringExpansion { +class _PathExpansion extends _StringExpansion { @override - String get prefix => '/'; + String get _prefix => '/'; @override - String get separator => '/'; + String get _separator => '/'; @override - String toString() => "{/${variables.join(',')}}"; + String toString() => "{/${_variables.join(',')}}"; } -class ParameterExpansion extends StringExpansion { +class _ParameterExpansion extends _StringExpansion { @override - String get prefix => ';'; + String get _prefix => ';'; @override - String get separator => ';'; + String get _separator => ';'; @override - bool get withName => true; + bool get _withName => true; @override - String toString() => "{;${variables.join(',')}}"; + String toString() => "{;${_variables.join(',')}}"; } -class QueryExpansion extends StringExpansion { +class _QueryExpansion extends _StringExpansion { @override - String get prefix => '?'; + String get _prefix => '?'; @override - String get separator => '&'; + String get _separator => '&'; @override - bool get withName => true; + bool get _withName => true; @override - bool get keepEmpties => true; + bool get _keepEmpties => true; @override - String toString() => "{?${variables.join(',')}}"; + String toString() => "{?${_variables.join(',')}}"; } -class QueryContinuation extends QueryExpansion { - @override get prefix => '&'; +class _QueryContinuation extends _QueryExpansion { + @override + get _prefix => '&'; @override - String toString() => "{&${variables.join(',')}}"; + String toString() => "{&${_variables.join(',')}}"; } diff --git a/lib/src/types/uuid.dart b/lib/src/types/uuid.dart index 65652cb..64df613 100644 --- a/lib/src/types/uuid.dart +++ b/lib/src/types/uuid.dart @@ -1,15 +1,19 @@ import "../document.dart"; -class KdlUUID extends KdlValue { - KdlUUID(String value, [String? type]) : super(value, type); - static final _RGX = RegExp(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); +/// RFC4122 UUID. +class KdlUuid extends KdlValue { + /// Consutrct a new `KdlUUID` + KdlUuid(super.value, [super.type]); + static final _regexp = + RegExp(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); - static call(KdlValue value, [String type = 'uuid']) { - if (!(value is KdlString)) return null; + /// Convert a `KdlString` into a `KdlUUID` + static KdlUuid? convert(KdlValue value, [String type = 'uuid']) { + if (value is! KdlString) return null; String uuid = value.value.toLowerCase(); - if (!_RGX.hasMatch(uuid)) throw "${value.value} is not a valid uuid"; + if (!_regexp.hasMatch(uuid)) throw "${value.value} is not a valid uuid"; - return KdlUUID(uuid, type); + return KdlUuid(uuid, type); } } diff --git a/pubspec.yaml b/pubspec.yaml index 76ab916..b7e4d61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,13 @@ name: kdl -version: 1.0.1 -description: Dart implementation of the KDL Document Language +version: 2.0.0 +description: KDL is a small, pleasant document language with XML-like node semantics that looks like you're invoking a bunch of CLI commands! repository: https://github.com/danini-the-panini/kdl-dart environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: big_decimal: ^0.5.0 - string_scanner: ^1.1.1 + string_scanner: ^1.2.0 dev_dependencies: - test: ^1.21.4 - path: ^1.8.0 + test: ^1.25.2 + path: ^1.9.0 + lints: ^5.1.1 diff --git a/test/document_test.dart b/test/document_test.dart index e2cfa57..a1378d5 100644 --- a/test/document_test.dart +++ b/test/document_test.dart @@ -1,10 +1,67 @@ import 'package:test/test.dart'; -import '../lib/src/document.dart'; +import 'package:kdl/src/document.dart'; void main() { test('equals', () { expect(KdlDocument([]), equals(KdlDocument([]))); expect(KdlDocument([]) == KdlDocument([]), equals(true)); }); + + test('[]', () { + var doc = KdlDocument([ + KdlNode("foo"), + KdlNode("bar") + ]); + + expect(doc[0], doc.nodes[0]); + expect(doc[1], doc.nodes[1]); + expect(doc["foo"], doc.nodes[0]); + expect(doc["bar"], doc.nodes[1]); + + expect(() { doc[null]; }, throwsA(anything)); + }); + + test('arg', () { + var doc = KdlDocument([ + KdlNode("foo", arguments: [KdlString("bar")]), + KdlNode("baz", arguments: [KdlString("qux")]) + ]); + + expect(doc.arg(0), equals("bar")); + expect(doc.arg(1), equals("qux")); + expect(doc.arg("foo"), equals("bar")); + expect(doc.arg("baz"), equals("qux")); + + expect(() { doc.arg(null); }, throwsA(anything)); + }); + + test('args', () { + var doc = KdlDocument([ + KdlNode("foo", arguments: [KdlString("bar"), KdlString("baz")]), + KdlNode("qux", arguments: [KdlString("norf")]) + ]); + + expect(doc.args(0), equals(["bar", "baz"])); + expect(doc.args(1), equals(["norf"])); + expect(doc.args("foo"), equals(["bar", "baz"])); + expect(doc.args("qux"), equals(["norf"])); + + expect(() { doc.args(null); }, throwsA(anything)); + }); + + test('dashVals', () { + var doc = KdlDocument([ + KdlNode("node", children: [ + KdlNode("-", arguments: [KdlString("foo")]), + KdlNode("-", arguments: [KdlString("bar")]), + KdlNode("-", arguments: [KdlString("baz")]) + ]) + ]); + + expect(doc.dashVals(0), equals(["foo", "bar", "baz"])); + expect(doc.dashVals("node"), equals(["foo", "bar", "baz"])); + + expect(() { doc.dashVals(null); }, throwsA(anything)); + }); } diff --git a/test/example_test.dart b/test/example_test.dart index b59b80b..2142f8c 100644 --- a/test/example_test.dart +++ b/test/example_test.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:kdl/kdl.dart'; -typedef T VarArgsCallback(List args); +typedef VarArgsCallback = T Function(List args); class VarArgsFunction { final VarArgsCallback callback; @@ -22,10 +22,10 @@ class VarArgsFunction { } main() { - KdlNode? currentNode = null; - KdlDocument? currentDocument = null; + KdlNode? currentNode; + KdlDocument? currentDocument; - dynamic _ = VarArgsFunction((args) { + dynamic n = VarArgsFunction((args) { var argv = List.from(args); var block = () {}; var kwargs = {}; @@ -47,57 +47,58 @@ main() { } }); - var nodes = (block) { + nodes(block) { var doc = KdlDocument([]); currentDocument = doc; block.call(); currentDocument = null; return doc; - }; + } test('ci', () async { - var string = await new File('./example/ci.kdl').readAsString(); - var doc = Kdl.parseDocument(string); + var string = await File('./test/kdl-org/examples/ci.kdl').readAsString(); + var doc = KdlDocument.parse(string); var expectedDoc = nodes(() { - _("name", "CI"); - _("on", "push", "pull_request"); - _("env", () { - _("RUSTFLAGS", "-Dwarnings"); + n("name", "CI"); + n("on", "push", "pull_request"); + n("env", () { + n("RUSTFLAGS", "-Dwarnings"); }); - _("jobs", () { - _("fmt_and_docs", "Check fmt & build docs", () { - _("runs-on", "ubuntu-latest"); - _("steps", () { - _("step", { "uses": "actions/checkout@v1" }); - _("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { - _("profile", "minimal"); - _("toolchain", "stable"); - _("components", "rustfmt"); - _("override", true); + n("jobs", () { + n("fmt_and_docs", "Check fmt & build docs", () { + n("runs-on", "ubuntu-latest"); + n("steps", () { + n("step", { "uses": "actions/checkout@v1" }); + n("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { + n("profile", "minimal"); + n("toolchain", "stable"); + n("components", "rustfmt"); + n("override", true); }); - _("step", "rustfmt", { "run": "cargo fmt --all -- --check" }); - _("step", "docs", { "run": "cargo doc --no-deps" }); + n("step", "rustfmt", () { n("run", "cargo", "fmt", "--all", "--", "--check"); }); + n("step", "docs", () { n("run", "cargo", "doc", "--no-deps"); }); }); }); - _("build_and_test", "Build & Test", () { - _("runs-on", r"${{ matrix.os }}"); - _("strategy", () { - _("matrix", () { - _("rust", "1.46.0", "stable"); - _("os", "ubuntu-latest", "macOS-latest", "windows-latest"); + n("build_and_test", "Build & Test", () { + n("runs-on", r"${{ matrix.os }}"); + n("strategy", () { + n("matrix", () { + n("rust", "1.46.0", "stable"); + n("os", "ubuntu-latest", "macOS-latest", "windows-latest"); }); }); - _("steps", () { - _("step", { "uses": "actions/checkout@v1" }); - _("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { - _("profile", "minimal"); - _("toolchain", r"${{ matrix.rust }}"); - _("components", "clippy"); - _("override", true); + n("steps", () { + n("step", { "uses": "actions/checkout@v1" }); + n("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { + n("profile", "minimal"); + n("toolchain", r"${{ matrix.rust }}"); + n("components", "clippy"); + n("override", true); }); - _("step", "Clippy", { "run": "cargo clippy --all -- -D warnings" }); - _("step", "Run tests", { "run": "cargo test --all --verbose" }); + n("step", "Clippy", () { n("run", "cargo", "clippy", "--all", "--", "-D", "warnings"); }); + n("step", "Run tests", () { n("run", "cargo", "test", "--all", "--verbose"); }); + n("step", "Other Stuff", { "run": " echo foo\n echo bar\n echo baz" }); }); }); }); @@ -107,20 +108,20 @@ main() { }); test('cargo', () async { - var string = await new File('./example/Cargo.kdl').readAsString(); - var doc = Kdl.parseDocument(string); + var string = await File('./test/kdl-org/examples/Cargo.kdl').readAsString(); + var doc = KdlDocument.parse(string); var expectedDoc = nodes(() { - _("package", () { - _("name", "kdl"); - _("version", "0.0.0"); - _("description", "kat's document language"); - _("authors", "Kat Marchán "); - _("license-file", "LICENSE.md"); - _("edition", "2018"); + n("package", () { + n("name", "kdl"); + n("version", "0.0.0"); + n("description", "The kdl document language"); + n("authors", "Kat Marchán "); + n("license-file", "LICENSE.md"); + n("edition", "2018"); }); - _("dependencies", () { - _("nom", "6.0.1"); - _("thiserror", "1.0.22"); + n("dependencies", () { + n("nom", "6.0.1"); + n("thiserror", "1.0.22"); }); }); @@ -128,11 +129,22 @@ main() { }); test('nuget', () async { - var string = await new File('./example/nuget.kdl').readAsString(); - var doc = Kdl.parseDocument(string); + var string = await File('./test/kdl-org/examples/nuget.kdl').readAsString(); + var doc = KdlDocument.parse(string); + + expect(doc, isNotNull); + }); + + test('kdl-schema', () async { + var string = await File('./test/kdl-org/examples/kdl-schema.kdl').readAsString(); + var doc = KdlDocument.parse(string); + + expect(doc, isNotNull); + }); - // This file is particularly large. It would be nice to validate it, but for now - // I'm just going to settle for making sure it parses. + test('website', () async { + var string = await File('./test/kdl-org/examples/website.kdl').readAsString(); + var doc = KdlDocument.parse(string); expect(doc, isNotNull); }); diff --git a/test/kdl-org b/test/kdl-org index cfd86ce..ce3b2ee 160000 --- a/test/kdl-org +++ b/test/kdl-org @@ -1 +1 @@ -Subproject commit cfd86ce70a52cdd030eb84de1afa886471e0952c +Subproject commit ce3b2eeb7fa2a3c6ac303485513cabd06f97c4e9 diff --git a/test/node_test.dart b/test/node_test.dart new file mode 100644 index 0000000..9e18878 --- /dev/null +++ b/test/node_test.dart @@ -0,0 +1,79 @@ +import 'package:test/test.dart'; + +import 'package:kdl/src/document.dart'; + +void main() { + test('child', () { + var node = KdlNode("node", children: [ + KdlNode("foo"), + KdlNode("bar") + ]); + + expect(node.child(0), node.children[0]); + expect(node.child(1), node.children[1]); + expect(node.child("foo"), node.children[0]); + expect(node.child("bar"), node.children[1]); + + expect(() { node.child(null); }, throwsA(anything)); + }); + + test('[]', () { + var node = KdlNode("node", arguments: [ + KdlInt(1), + KdlString("two"), + ], properties: { + 'three': KdlInt(3), + 'four': KdlInt(4), + }); + + expect(node[0], 1); + expect(node[1], "two"); + expect(node["three"], 3); + expect(node["four"], 4); + + expect(() { node[null]; }, throwsA(anything)); + }); + + test('arg', () { + var node = KdlNode("node", children: [ + KdlNode("foo", arguments: [KdlString("bar")]), + KdlNode("baz", arguments: [KdlString("qux")]) + ]); + + expect(node.arg(0), equals("bar")); + expect(node.arg(1), equals("qux")); + expect(node.arg("foo"), equals("bar")); + expect(node.arg("baz"), equals("qux")); + + expect(() { node.arg(null); }, throwsA(anything)); + }); + + test('args', () { + var node = KdlNode("node", children: [ + KdlNode("foo", arguments: [KdlString("bar"), KdlString("baz")]), + KdlNode("qux", arguments: [KdlString("norf")]) + ]); + + expect(node.args(0), equals(["bar", "baz"])); + expect(node.args(1), equals(["norf"])); + expect(node.args("foo"), equals(["bar", "baz"])); + expect(node.args("qux"), equals(["norf"])); + + expect(() { node.args(null); }, throwsA(anything)); + }); + + test('dashVals', () { + var node = KdlNode("node", children: [ + KdlNode("node", children: [ + KdlNode("-", arguments: [KdlString("foo")]), + KdlNode("-", arguments: [KdlString("bar")]), + KdlNode("-", arguments: [KdlString("baz")]) + ]) + ]); + + expect(node.dashVals(0), equals(["foo", "bar", "baz"])); + expect(node.dashVals("node"), equals(["foo", "bar", "baz"])); + + expect(() { node.dashVals(null); }, throwsA(anything)); + }); +} diff --git a/test/parser_test.dart b/test/parser_test.dart index 9a2a06b..6b81517 100644 --- a/test/parser_test.dart +++ b/test/parser_test.dart @@ -1,7 +1,9 @@ +import 'package:big_decimal/big_decimal.dart'; import 'package:test/test.dart'; import 'package:kdl/src/document.dart'; import 'package:kdl/src/parser.dart'; +import 'package:kdl/src/exception.dart'; void main() { late KdlParser parser; @@ -21,28 +23,53 @@ void main() { expect(parser.parse("node\n"), equals(KdlDocument([KdlNode('node')]))); expect(parser.parse("\nnode\n"), equals(KdlDocument([KdlNode('node')]))); expect(parser.parse("node1\nnode2"), - equals(KdlDocument([ - KdlNode('node1'), - KdlNode('node2') - ])) - ); + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); }); test('node', () { expect(parser.parse('node;'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse('node 1'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(1)])]))); - expect(parser.parse('node 1 2 "3" true false null'), equals(KdlDocument([ - KdlNode('node', arguments: [ - KdlInt(1), - KdlInt(2), - KdlString("3"), - KdlBool(true), - KdlBool(false), - KdlNull() - ]) - ]))); - expect(parser.parse("node {\n node2\n}"), equals(KdlDocument([KdlNode('node', children: [KdlNode('node2')])]))); - expect(parser.parse("node { node2; }"), equals(KdlDocument([KdlNode('node', children: [KdlNode('node2')])]))); + expect( + parser.parse('node 1'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse('node 1 2 "3" #true #false #null'), + equals(KdlDocument([ + KdlNode('node', arguments: [ + KdlInt(1), + KdlInt(2), + KdlString("3"), + KdlBool(true), + KdlBool(false), + KdlNull() + ]) + ]))); + expect( + parser.parse("node {\n node2\n}"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + expect( + parser.parse("node {\n node2 \n}"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + expect( + parser.parse("node { node2; }"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + expect( + parser.parse("node { node2 }"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + expect( + parser.parse("node { node2; node3 }"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2'), KdlNode('node3')]) + ]))); }); test('node_slashdash_comment', () { @@ -50,118 +77,365 @@ void main() { expect(parser.parse('/- node'), equals(KdlDocument([]))); expect(parser.parse("/- node\n"), equals(KdlDocument([]))); expect(parser.parse('/-node 1 2 3'), equals(KdlDocument([]))); - expect(parser.parse('/-node key=false'), equals(KdlDocument([]))); + expect(parser.parse('/-node key=#false'), equals(KdlDocument([]))); expect(parser.parse("/-node{\nnode\n}"), equals(KdlDocument([]))); - expect(parser.parse("/-node 1 2 3 key=\"value\" \\\n{\nnode\n}"), equals(KdlDocument([]))); + expect(parser.parse("/-node 1 2 3 key=\"value\" \\\n{\nnode\n}"), + equals(KdlDocument([]))); }); test('arg_slashdash_comment', () { expect(parser.parse('node /-1'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse('node /-1 2'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(2)])]))); - expect(parser.parse('node 1 /- 2 3'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(1), KdlInt(3)])]))); + expect( + parser.parse('node /-1 2'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(2)]) + ]))); + expect( + parser.parse('node 1 /- 2 3'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1), KdlInt(3)]) + ]))); expect(parser.parse('node /--1'), equals(KdlDocument([KdlNode('node')]))); expect(parser.parse('node /- -1'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse("node \\\n/- -1"), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse("node \\\n/- -1"), equals(KdlDocument([KdlNode('node')]))); }); test('prop_slashdash_comment', () { - expect(parser.parse('node /-key=1'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse('node /- key=1'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse('node key=1 /-key2=2'), equals(KdlDocument([KdlNode('node', properties: { 'key': KdlInt(1) })]))); + expect( + parser.parse('node /-key=1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node /- key=1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node key=1 /-key2=2'), + equals(KdlDocument([ + KdlNode('node', properties: {'key': KdlInt(1)}) + ]))); }); test('children_slashdash_comment', () { expect(parser.parse('node /-{}'), equals(KdlDocument([KdlNode('node')]))); expect(parser.parse('node /- {}'), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse("node /-{\nnode2\n}"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node /-{\nnode2\n}"), + equals(KdlDocument([KdlNode('node')]))); }); test('string', () { - expect(parser.parse('node ""'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("")])]))); - expect(parser.parse('node "hello"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("hello")])]))); - expect(parser.parse(r'node "hello\nworld"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("hello\nworld")])]))); - expect(parser.parse(r'node "\u{10FFF}"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("\u{10FFF}")])]))); - expect(parser.parse(r'node "\"\\\/\b\f\n\r\t"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("\"\\/\u{08}\u{0C}\n\r\t")])]))); - expect(parser.parse(r'node "\u{10}"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString("\u{10}")])]))); - expect(() { parser.parse(r'node "\i"'); }, throwsA(anything)); - expect(() { parser.parse(r'node "\u{c0ffee}"'); }, throwsA(anything)); + expect( + parser.parse('node ""'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("")]) + ]))); + expect( + parser.parse('node "hello"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("hello")]) + ]))); + expect( + parser.parse(r'node "hello\nworld"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("hello\nworld")]) + ]))); + expect( + parser.parse(r'node -flag'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("-flag")]) + ]))); + expect( + parser.parse(r'node --flagg'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("--flagg")]) + ]))); + expect( + parser.parse(r'node "\u{10FFF}"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\u{10FFF}")]) + ]))); + expect( + parser.parse(r'node "\"\\\b\f\n\r\t"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\"\\\u{08}\u{0C}\n\r\t")]) + ]))); + expect( + parser.parse(r'node "\u{10}"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\u{10}")]) + ]))); + expect(() { + parser.parse(r'node "\i"'); + }, throwsA(isA())); + expect(() { + parser.parse(r'node "\u{c0ffee}"'); + }, throwsA(isA())); + expect(() { + parser.parse(r'node "oops'); + }, throwsA(isA())); + }); + + test('unindented multiline strings', () { + expect( + parser.parse('node """\n foo\n bar\n baz\n qux\n """'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("foo\nbar\n baz\nqux")]) + ]))); + expect( + parser.parse('node #"""\n foo\n bar\n baz\n qux\n """#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("foo\nbar\n baz\nqux")]) + ]))); + expect(() { + parser.parse('node """\n foo\n bar\n baz\n """'); + }, throwsA(isA())); + expect(() { + parser.parse('node #"""\n foo\n bar\n baz\n """#'); + }, throwsA(isA())); }); test('float', () { - expect(parser.parse('node 1.0'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(1.0)])]))); - expect(parser.parse('node 0.0'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(0.0)])]))); - expect(parser.parse('node -1.0'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(-1.0)])]))); - expect(parser.parse('node +1.0'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(1.0)])]))); - expect(parser.parse('node 1.0e10'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(1.0e10)])]))); - expect(parser.parse('node 1.0e-10'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(1.0e-10)])]))); - expect(parser.parse('node 123_456_789.0'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(123456789.0)])]))); - expect(parser.parse('node 123_456_789.0_'), equals(KdlDocument([KdlNode('node', arguments: [KdlFloat.from(123456789.0)])]))); - expect(() { parser.parse('node ?1.0'); }, throwsA(anything)); - expect(() { parser.parse('node _1.0'); }, throwsA(anything)); - expect(() { parser.parse('node 1._0'); }, throwsA(anything)); - expect(() { parser.parse('node 1.'); }, throwsA(anything)); - expect(() { parser.parse('node .0'); }, throwsA(anything)); + expect( + parser.parse('node 1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0)]) + ]))); + expect( + parser.parse('node 0.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(0.0)]) + ]))); + expect( + parser.parse('node -1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(-1.0)]) + ]))); + expect( + parser.parse('node +1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0)]) + ]))); + expect( + parser.parse('node 1.0e10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0e10)]) + ]))); + expect( + parser.parse('node 1.0e-10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0e-10)]) + ]))); + expect( + parser.parse('node 123_456_789.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(123456789.0)]) + ]))); + expect( + parser.parse('node 123_456_789.0_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(123456789.0)]) + ]))); + expect(() { + parser.parse('node 1._0'); + }, throwsA(isA())); + expect(() { + parser.parse('node 1.'); + }, throwsA(isA())); + expect(() { + parser.parse('node 1.0v2'); + }, throwsA(isA())); + expect(() { + parser.parse('node -1em'); + }, throwsA(isA())); + expect(() { + parser.parse('node .0'); + }, throwsA(isA())); }); test('integer', () { - expect(parser.parse('node 0'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(0)])]))); - expect(parser.parse('node 0123456789'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(123456789)])]))); - expect(parser.parse('node 0123_456_789'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(123456789)])]))); - expect(parser.parse('node 0123_456_789_'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(123456789)])]))); - expect(parser.parse('node +0123456789'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(123456789)])]))); - expect(parser.parse('node -0123456789'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(-123456789)])]))); - expect(() { parser.parse('node ?0123456789'); }, throwsA(anything)); - expect(() { parser.parse('node _0123456789'); }, throwsA(anything)); - expect(() { parser.parse('node a'); }, throwsA(anything)); - expect(() { parser.parse('node --'); }, throwsA(anything)); + expect( + parser.parse('node 0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0)]) + ]))); + expect( + parser.parse('node 0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node 0123_456_789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node 0123_456_789_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node +0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node -0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(-123456789)]) + ]))); }); test('hexadecimal', () { - expect(parser.parse('node 0x0123456789abcdef'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)])]))); - expect(parser.parse('node 0x01234567_89abcdef'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)])]))); - expect(parser.parse('node 0x01234567_89abcdef_'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)])]))); - expect(() { parser.parse('node 0x_123'); }, throwsA(anything)); - expect(() { parser.parse('node 0xg'); }, throwsA(anything)); - expect(() { parser.parse('node 0xx'); }, throwsA(anything)); + expect( + parser.parse('node 0x0123456789abcdef'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect( + parser.parse('node 0x01234567_89abcdef'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect( + parser.parse('node 0x01234567_89abcdef_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect(() { + parser.parse('node 0x_123'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0xg'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0xx'); + }, throwsA(isA())); }); test('octal', () { - expect(parser.parse('node 0o01234567'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(342391)])]))); - expect(parser.parse('node 0o0123_4567'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(342391)])]))); - expect(parser.parse('node 0o01234567_'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(342391)])]))); - expect(() { parser.parse('node 0o_123'); }, throwsA(anything)); - expect(() { parser.parse('node 0o8'); }, throwsA(anything)); - expect(() { parser.parse('node 0oo'); }, throwsA(anything)); + expect( + parser.parse('node 0o01234567'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect( + parser.parse('node 0o0123_4567'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect( + parser.parse('node 0o01234567_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect(() { + parser.parse('node 0o_123'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0o8'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0oo'); + }, throwsA(isA())); }); test('binary', () { - expect(parser.parse('node 0b0101'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(5)])]))); - expect(parser.parse('node 0b01_10'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(6)])]))); - expect(parser.parse('node 0b01___10'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(6)])]))); - expect(parser.parse('node 0b0110_'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(6)])]))); - expect(() { parser.parse('node 0b_0110'); }, throwsA(anything)); - expect(() { parser.parse('node 0b20'); }, throwsA(anything)); - expect(() { parser.parse('node 0bb'); }, throwsA(anything)); + expect( + parser.parse('node 0b0101'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(5)]) + ]))); + expect( + parser.parse('node 0b01_10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect( + parser.parse('node 0b01___10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect( + parser.parse('node 0b0110_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect(() { + parser.parse('node 0b_0110'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0b20'); + }, throwsA(isA())); + expect(() { + parser.parse('node 0bb'); + }, throwsA(isA())); }); test('raw_string', () { - expect(parser.parse(r'node r"foo"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString('foo')])]))); - expect(parser.parse(r'node r"foo\nbar"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString(r'foo\nbar')])]))); - expect(parser.parse(r'node r#"foo"#'), equals(KdlDocument([KdlNode('node', arguments: [KdlString('foo')])]))); - expect(parser.parse(r'node r##"foo"##'), equals(KdlDocument([KdlNode('node', arguments: [KdlString('foo')])]))); - expect(parser.parse(r'node r"\nfoo\r"'), equals(KdlDocument([KdlNode('node', arguments: [KdlString(r'\nfoo\r')])]))); - expect(() { parser.parse('node r##"foo"#'); }, throwsA(anything)); + expect( + parser.parse(r'node #"foo"#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node #"foo\nbar"#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString(r'foo\nbar')]) + ]))); + expect( + parser.parse(r'node #"foo"#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node ##"foo"##'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node #"\nfoo\r"#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString(r'\nfoo\r')]) + ]))); + expect(() { + parser.parse('node ##"foo"#'); + }, throwsA(isA())); }); test('boolean', () { - expect(parser.parse('node true'), equals(KdlDocument([KdlNode('node', arguments: [KdlBool(true)])]))); - expect(parser.parse('node false'), equals(KdlDocument([KdlNode('node', arguments: [KdlBool(false)])]))); + expect( + parser.parse('node #true'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBool(true)]) + ]))); + expect( + parser.parse('node #false'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBool(false)]) + ]))); + }); + + test('null', () { + expect( + parser.parse('node #null'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlNull()]) + ]))); }); test('node_space', () { - expect(parser.parse('node 1'), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(1)])]))); - expect(parser.parse("node\t1"), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(1)])]))); - expect(parser.parse("node\t \\ // hello\n 1"), equals(KdlDocument([KdlNode('node', arguments: [KdlInt(1)])]))); + expect( + parser.parse('node 1'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse("node\t1"), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse("node\t \\ // hello\n 1"), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); }); test('single_line_comment', () { @@ -170,8 +444,10 @@ void main() { expect(parser.parse("//hello\n"), equals(KdlDocument([]))); expect(parser.parse("//hello\r\n"), equals(KdlDocument([]))); expect(parser.parse("//hello\n\r"), equals(KdlDocument([]))); - expect(parser.parse("//hello\rworld"), equals(KdlDocument([KdlNode('world')]))); - expect(parser.parse("//hello\nworld\r\n"), equals(KdlDocument([KdlNode('world')]))); + expect(parser.parse("//hello\rworld"), + equals(KdlDocument([KdlNode('world')]))); + expect(parser.parse("//hello\nworld\r\n"), + equals(KdlDocument([KdlNode('world')]))); }); test('multi_line_comment', () { @@ -180,26 +456,200 @@ void main() { expect(parser.parse("/*\nhello\r\n*/"), equals(KdlDocument([]))); expect(parser.parse("/*\nhello** /\n*/"), equals(KdlDocument([]))); expect(parser.parse("/**\nhello** /\n*/"), equals(KdlDocument([]))); - expect(parser.parse('/*hello*/world'), equals(KdlDocument([KdlNode('world')]))); + expect(parser.parse('/*hello*/world'), + equals(KdlDocument([KdlNode('world')]))); }); test('escline', () { - expect(parser.parse("foo\\\n 1"), equals(KdlDocument([KdlNode('foo', arguments: [KdlInt(1)])]))); - expect(() { parser.parse("node\\\nnode2"); }, throwsA(anything)); - expect(() { parser.parse("node\n \\\n//comment\n node2"); }, throwsA(anything)); + expect( + parser.parse("node\\\n 1"), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect(parser.parse("node\\\n"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node\\ \n"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node\\\n "), equals(KdlDocument([KdlNode('node')]))); + expect(() { + parser.parse('node \\foo'); + }, throwsA(isA())); + expect(() { + parser.parse('node\\\\\nnode2'); + }, throwsA(isA())); + expect(() { + parser.parse('node \\\\\nnode2'); + }, throwsA(isA())); }); test('whitespace', () { expect(parser.parse(" node"), equals(KdlDocument([KdlNode('node')]))); expect(parser.parse("\tnode"), equals(KdlDocument([KdlNode('node')]))); - expect(parser.parse("/* \nfoo\r\n */ etc"), equals(KdlDocument([KdlNode('etc')]))); + expect(parser.parse("/* \nfoo\r\n */ etc"), + equals(KdlDocument([KdlNode('etc')]))); }); test('newline', () { - expect(parser.parse("node1\nnode2"), equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); - expect(parser.parse("node1\rnode2"), equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); - expect(parser.parse("node1\r\nnode2"), equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); - expect(parser.parse("node1\n\nnode2"), equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\rnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\r\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\n\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + }); + + test('basic', () { + var doc = parser.parse('title "Hello, World"'); + var nodes = KdlDocument([ + KdlNode('title', arguments: [KdlString("Hello, World")]), + ]); + expect(doc, equals(nodes)); + }); + + test('multiple values', () { + var doc = parser.parse('bookmarks 12 15 188 1234'); + var nodes = KdlDocument([ + KdlNode('bookmarks', + arguments: [KdlInt(12), KdlInt(15), KdlInt(188), KdlInt(1234)]), + ]); + expect(doc, equals(nodes)); + }); + + test('properties', () { + var doc = parser.parse(""" +author "Alex Monad" email="alex@example.com" active= #true +foo bar =#true "baz" quux =\\ + #false 1 2 3 + """ + .trim()); + var nodes = KdlDocument([ + KdlNode( + 'author', + arguments: [KdlString("Alex Monad")], + properties: { + 'email': KdlString("alex@example.com"), + 'active': KdlBool(true), + }, + ), + KdlNode( + 'foo', + arguments: [KdlString("baz"), KdlInt(1), KdlInt(2), KdlInt(3)], + properties: { + 'bar': KdlBool(true), + 'quux': KdlBool(false), + }, + ), + ]); + expect(doc, equals(nodes)); + }); + + test('nested child nodes', () { + var doc = parser.parse(""" +contents { + section "First section" { + paragraph "This is the first paragraph" + paragraph "This is the second paragraph" + } +} + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('contents', children: [ + KdlNode('section', arguments: [ + KdlString("First section") + ], children: [ + KdlNode('paragraph', + arguments: [KdlString("This is the first paragraph")]), + KdlNode('paragraph', + arguments: [KdlString("This is the second paragraph")]), + ]), + ]), + ]); + expect(doc, equals(nodes)); + }); + + test('semicolon', () { + var doc = parser.parse("node1; node2; node3;"); + var nodes = KdlDocument([ + KdlNode('node1'), + KdlNode('node2'), + KdlNode('node3'), + ]); + expect(doc, equals(nodes)); + }); + + test('optional child semicolon', () { + var doc = parser.parse('node {foo;bar;baz}'); + var nodes = KdlDocument([ + KdlNode('node', children: [ + KdlNode('foo'), + KdlNode('bar'), + KdlNode('baz'), + ]), + ]); + expect(doc, equals(nodes)); + }); + + test('raw strings', () { + var doc = parser.parse(""" +node "this\\nhas\\tescapes" +other #"C:\\Users\\zkat\\"# +other-raw #"hello"world"# + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('node', arguments: [KdlString("this\nhas\tescapes")]), + KdlNode('other', arguments: [KdlString("C:\\Users\\zkat\\")]), + KdlNode('other-raw', arguments: [KdlString("hello\"world")]), + ]); + expect(doc, equals(nodes)); + }); + + test('multiline strings', () { + var doc = parser.parse(""" +string \""" +my +multiline +value +\""" + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('string', arguments: [KdlString("my\nmultiline\nvalue")]), + ]); + expect(doc, equals(nodes)); + + expect(() { + parser.parse('node """foo"""'); + }, throwsA(isA())); + expect(() { + parser.parse('node #"""foo"""#'); + }, throwsA(isA())); + expect(() { + parser.parse('node """\n oops'); + }, throwsA(isA())); + expect(() { + parser.parse('node #"""\n oops'); + }, throwsA(isA())); + }); + + test('numbers', () { + var doc = parser.parse(""" + num 1.234e-42 + my-hex 0xdeadbeef + my-octal 0o755 + my-binary 0b10101101 + bignum 1_000_000 + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('num', arguments: [KdlBigDecimal(BigDecimal.parse('1.234e-42'))]), + KdlNode('my-hex', arguments: [KdlInt(0xdeadbeef)]), + KdlNode('my-octal', arguments: [KdlInt(493)]), + KdlNode('my-binary', arguments: [KdlInt(173)]), + KdlNode('bignum', arguments: [KdlInt(1000000)]), + ]); + expect(doc, equals(nodes)); }); test('comments', () { @@ -210,39 +660,144 @@ void main() { C style multiline */ - tag /*foo=true*/ bar=false + tag /*foo=#true*/ bar=#false /*/* hello */*/ - """.trim()); + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('tag', properties: {'bar': KdlBool(false)}) + ]); + expect(doc, equals(nodes)); + }); + + test('slash dash', () { + var doc = parser.parse(""" +/-mynode "foo" key=1 { + a + b + c +} + +mynode /- "commented" "not commented" /-key="value" /-{ + a + b +} + """ + .trim()); var nodes = KdlDocument([ - KdlNode('tag', properties: { 'bar': KdlBool(false) }) + KdlNode('mynode', arguments: [KdlString("not commented")]), + ]); + expect(doc, equals(nodes)); + }); + + test('multiline nodes', () { + var doc = parser.parse(""" +title \\ + "Some title" + +my-node 1 2 \\ // comments are ok after \\ + 3 4 + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('title', arguments: [KdlString("Some title")]), + KdlNode('my-node', + arguments: [KdlInt(1), KdlInt(2), KdlInt(3), KdlInt(4)]), ]); expect(doc, equals(nodes)); }); test('utf8', () { var doc = parser.parse(""" - smile "😁" - ノード お名前="☜(゚ヮ゚☜)" - """.trim()); +smile "😁" +ノード お名前="☜(゚ヮ゚☜)" + """ + .trim()); var nodes = KdlDocument([ KdlNode('smile', arguments: [KdlString('😁')]), - KdlNode('ノード', properties: { 'お名前': KdlString('☜(゚ヮ゚☜)') }) + KdlNode('ノード', properties: {'お名前': KdlString('☜(゚ヮ゚☜)')}) ]); expect(doc, equals(nodes)); }); test('node_names', () { var doc = parser.parse(r""" - "!@#$@$%Q#$%~@!40" "1.2.3" "!!!!!"=true - foo123~!@#$%^&*.:'|?+ "weeee" - """.trim()); +"!@$@$%Q$%~@!40" "1.2.3" "!!!!!"=#true +foo123~!@$%^&*.:'|?+ "weeee" +- 1 + """ + .trim()); + var nodes = KdlDocument([ + KdlNode(r"!@$@$%Q$%~@!40", + arguments: [KdlString("1.2.3")], + properties: {"!!!!!": KdlBool(true)}), + KdlNode(r"foo123~!@$%^&*.:'|?+", arguments: [KdlString("weeee")]), + KdlNode('-', arguments: [KdlInt(1)]), + ]); + expect(doc, equals(nodes)); + }); + + test('escaping', () { + var doc = parser.parse(""" +node1 "\\u{1f600}" +node2 "\\n\\t\\r\\\\\\"\\f\\b" + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('node1', arguments: [KdlString('😀')]), + KdlNode('node2', arguments: [KdlString("\n\t\r\\\"\f\b")]), + ]); + expect(doc, equals(nodes)); + }); + + test('node type', () { + var doc = parser.parse("(foo)node"); + var nodes = KdlDocument([ + KdlNode('node', type: 'foo'), + ]); + expect(doc, equals(nodes)); + }); + + test('value type', () { + var doc = parser.parse('node (foo)"bar"'); + var nodes = KdlDocument([ + KdlNode('node', arguments: [KdlString("bar").asType("foo")]), + ]); + expect(doc, equals(nodes)); + }); + + test('property type', () { + var doc = parser.parse('node baz=(foo)"bar"'); var nodes = KdlDocument([ - KdlNode(r"!@#$@$%Q#$%~@!40", arguments: [KdlString("1.2.3")], properties: { "!!!!!": KdlBool(true) }), - KdlNode(r"foo123~!@#$%^&*.:'|?+", arguments: [KdlString("weeee")]) + KdlNode('node', properties: {'baz': KdlString("bar").asType("foo")}), ]); expect(doc, equals(nodes)); }); + + test('child type', () { + var doc = parser.parse(""" +node { + (foo)bar +} + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('node', children: [ + KdlNode('bar', type: 'foo'), + ]), + ]); + expect(doc, equals(nodes)); + }); + + test('version directive', () { + var doc = parser.parse('/- kdl-version 2\nnode foo'); + expect(doc, isNotNull); + + expect(() { + parser.parse('/- kdl-version 1\nnode "foo"'); + }, throwsA(isA())); + }); } diff --git a/test/spec_test.dart b/test/spec_test.dart index 6b59819..d6cfb0f 100644 --- a/test/spec_test.dart +++ b/test/spec_test.dart @@ -3,18 +3,19 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:kdl/src/parser.dart'; +import 'package:kdl/src/exception.dart'; void main() { KdlParser parser = KdlParser(); var testCasesPath = p.join(Directory.current.path, 'test', 'kdl-org', 'tests', 'test_cases'); - var inputsDir = new Directory(p.join(testCasesPath, 'input')); + var inputsDir = Directory(p.join(testCasesPath, 'input')); var expectedPath = p.join(testCasesPath, 'expected_kdl'); for (var entry in inputsDir.listSync()) { if (entry is Directory) continue; var inputFile = (entry as File); var inputName = p.basenameWithoutExtension(inputFile.path); - var expectedFile = new File(p.join(expectedPath, "${inputName}.kdl")); + var expectedFile = File(p.join(expectedPath, "$inputName.kdl")); if (expectedFile.existsSync()) { test("$inputName matches expected output", () async { var input = await inputFile.readAsString(); @@ -24,7 +25,7 @@ void main() { } else { test("$inputName does not parse", () async { var input = await inputFile.readAsString(); - expect(() { parser.parse(input); }, throwsA(anything)); + expect(() { parser.parse(input); }, throwsA(isA())); }); } } diff --git a/test/tokenizer_test.dart b/test/tokenizer_test.dart index 3b5229e..19856e0 100644 --- a/test/tokenizer_test.dart +++ b/test/tokenizer_test.dart @@ -1,172 +1,271 @@ import 'package:test/test.dart'; import 'package:big_decimal/big_decimal.dart'; -import '../lib/src/tokenizer.dart'; +import 'package:kdl/src/tokenizer.dart'; void main() { test('peek and peek after next', () { var tokenizer = KdlTokenizer("node 1 2 3"); - expect(tokenizer.peekToken(), equals([KdlToken.IDENT, "node"])); - expect(tokenizer.peekTokenAfterNext(), equals([KdlToken.WS, " "])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, "node"])); - expect(tokenizer.peekToken(), equals([KdlToken.WS, " "])); - expect(tokenizer.peekTokenAfterNext(), equals([KdlToken.INTEGER, 1])); + expect(tokenizer.peekToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect(tokenizer.peekTokenAfterNext(), + equals(KdlToken(KdlTerm.whitespace, " "))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect(tokenizer.peekToken(), equals(KdlToken(KdlTerm.whitespace, " "))); + expect( + tokenizer.peekTokenAfterNext(), equals(KdlToken(KdlTerm.integer, 1))); }); test('identifier', () { - expect(KdlTokenizer("foo").nextToken(), equals([KdlToken.IDENT, "foo"])); - expect(KdlTokenizer("foo-bar123").nextToken(), equals([KdlToken.IDENT, "foo-bar123"])); + expect(KdlTokenizer("foo").nextToken(), + equals(KdlToken(KdlTerm.ident, "foo"))); + expect(KdlTokenizer("foo-bar123").nextToken(), + equals(KdlToken(KdlTerm.ident, "foo-bar123"))); + expect(KdlTokenizer("-").nextToken(), equals(KdlToken(KdlTerm.ident, "-"))); + expect( + KdlTokenizer("--").nextToken(), equals(KdlToken(KdlTerm.ident, "--"))); }); test('string', () { - expect(KdlTokenizer('"foo"').nextToken(), equals([KdlToken.STRING, "foo"])); - expect(KdlTokenizer(r'"foo\nbar"').nextToken(), equals([KdlToken.STRING, "foo\nbar"])); - expect(KdlTokenizer(r'"\u{10FFF}"').nextToken(), equals([KdlToken.STRING, "\u{10FFF}"])); + expect(KdlTokenizer('"foo"').nextToken(), + equals(KdlToken(KdlTerm.string, "foo"))); + expect(KdlTokenizer(r'"foo\nbar"').nextToken(), + equals(KdlToken(KdlTerm.string, "foo\nbar"))); + expect(KdlTokenizer(r'"\u{10FFF}"').nextToken(), + equals(KdlToken(KdlTerm.string, "\u{10FFF}"))); + expect(KdlTokenizer('"\\\n\n\nfoo"').nextToken(), + equals(KdlToken(KdlTerm.string, "foo"))); }); test('rawstring', () { - expect(KdlTokenizer('r"foo\\nbar"').nextToken(), equals([KdlToken.RAWSTRING, "foo\\nbar"])); - expect(KdlTokenizer('r#"foo"bar"#').nextToken(), equals([KdlToken.RAWSTRING, "foo\"bar"])); - expect(KdlTokenizer('r##"foo"#bar"##').nextToken(), equals([KdlToken.RAWSTRING, "foo\"#bar"])); - expect(KdlTokenizer('r#""foo""#').nextToken(), equals([KdlToken.RAWSTRING, "\"foo\""])); - - var tokenizer = KdlTokenizer('node r"C:\\Users\\zkat\\"'); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, "node"])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, " "])); - expect(tokenizer.nextToken(), equals([KdlToken.RAWSTRING, "C:\\Users\\zkat\\"])); - - tokenizer = KdlTokenizer('other-node r#"hello"world"#'); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, "other-node"])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, " "])); - expect(tokenizer.nextToken(), equals([KdlToken.RAWSTRING, "hello\"world"])); + expect(KdlTokenizer('#"foo\\nbar"#').nextToken(), + equals(KdlToken(KdlTerm.rawstring, "foo\\nbar"))); + expect(KdlTokenizer('#"foo"bar"#').nextToken(), + equals(KdlToken(KdlTerm.rawstring, "foo\"bar"))); + expect(KdlTokenizer('##"foo"#bar"##').nextToken(), + equals(KdlToken(KdlTerm.rawstring, "foo\"#bar"))); + expect(KdlTokenizer('#""foo""#').nextToken(), + equals(KdlToken(KdlTerm.rawstring, "\"foo\""))); + + var tokenizer = KdlTokenizer('node #"C:\\Users\\zkat\\"#'); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, " ", 1, 5))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.rawstring, "C:\\Users\\zkat\\", 1, 6))); + + tokenizer = KdlTokenizer('other-node #"hello"world"#'); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "other-node"))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, " ", 1, 11))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.rawstring, "hello\"world", 1, 12))); }); test('integer', () { - expect(KdlTokenizer("123").nextToken(), equals([KdlToken.INTEGER, 123])); - expect(KdlTokenizer("0x0123456789abcdef").nextToken(), equals([KdlToken.INTEGER, 0x0123456789abcdef])); - expect(KdlTokenizer("0o01234567").nextToken(), equals([KdlToken.INTEGER, 342391])); - expect(KdlTokenizer("0b101001").nextToken(), equals([KdlToken.INTEGER, 41])); - expect(KdlTokenizer("-0x0123456789abcdef").nextToken(), equals([KdlToken.INTEGER, -0x0123456789abcdef])); - expect(KdlTokenizer("-0o01234567").nextToken(), equals([KdlToken.INTEGER, -342391])); - expect(KdlTokenizer("-0b101001").nextToken(), equals([KdlToken.INTEGER, -41])); - expect(KdlTokenizer("+0x0123456789abcdef").nextToken(), equals([KdlToken.INTEGER, 0x0123456789abcdef])); - expect(KdlTokenizer("+0o01234567").nextToken(), equals([KdlToken.INTEGER, 342391])); - expect(KdlTokenizer("+0b101001").nextToken(), equals([KdlToken.INTEGER, 41])); + expect(KdlTokenizer("123").nextToken(), + equals(KdlToken(KdlTerm.integer, 123))); + expect(KdlTokenizer("0x0123456789abcdef").nextToken(), + equals(KdlToken(KdlTerm.integer, 0x0123456789abcdef))); + expect(KdlTokenizer("0o01234567").nextToken(), + equals(KdlToken(KdlTerm.integer, 342391))); + expect(KdlTokenizer("0b101001").nextToken(), + equals(KdlToken(KdlTerm.integer, 41))); + expect(KdlTokenizer("-0x0123456789abcdef").nextToken(), + equals(KdlToken(KdlTerm.integer, -0x0123456789abcdef))); + expect(KdlTokenizer("-0o01234567").nextToken(), + equals(KdlToken(KdlTerm.integer, -342391))); + expect(KdlTokenizer("-0b101001").nextToken(), + equals(KdlToken(KdlTerm.integer, -41))); + expect(KdlTokenizer("+0x0123456789abcdef").nextToken(), + equals(KdlToken(KdlTerm.integer, 0x0123456789abcdef))); + expect(KdlTokenizer("+0o01234567").nextToken(), + equals(KdlToken(KdlTerm.integer, 342391))); + expect(KdlTokenizer("+0b101001").nextToken(), + equals(KdlToken(KdlTerm.integer, 41))); }); test('float', () { - expect(KdlTokenizer("1.23").nextToken(), equals([KdlToken.FLOAT, BigDecimal.parse('1.23')])); + expect(KdlTokenizer("1.23").nextToken(), + equals(KdlToken(KdlTerm.decimal, BigDecimal.parse('1.23')))); + expect(KdlTokenizer("#inf").nextToken(), + equals(KdlToken(KdlTerm.double, double.infinity))); + expect(KdlTokenizer("#-inf").nextToken(), + equals(KdlToken(KdlTerm.double, -double.infinity))); + var nan = KdlTokenizer("#nan").nextToken(); + expect(nan.type, equals(KdlTerm.double)); + expect(nan.value, isNaN); }); test('boolean', () { - expect(KdlTokenizer("true").nextToken(), equals([KdlToken.TRUE, true])); - expect(KdlTokenizer("false").nextToken(), equals([KdlToken.FALSE, false])); + expect(KdlTokenizer("#true").nextToken(), + equals(KdlToken(KdlTerm.trueKeyword, true))); + expect(KdlTokenizer("#false").nextToken(), + equals(KdlToken(KdlTerm.falseKeyword, false))); }); test('null', () { - expect(KdlTokenizer("null").nextToken(), equals([KdlToken.NULL, null])); + expect(KdlTokenizer("#null").nextToken(), + equals(KdlToken(KdlTerm.nullKeyword, null))); }); test('symbols', () { - expect(KdlTokenizer("{").nextToken(), equals([KdlToken.LBRACE, '{'])); - expect(KdlTokenizer("}").nextToken(), equals([KdlToken.RBRACE, '}'])); - expect(KdlTokenizer("=").nextToken(), equals([KdlToken.EQUALS, '='])); + expect( + KdlTokenizer("{").nextToken(), equals(KdlToken(KdlTerm.lbrace, '{'))); + expect( + KdlTokenizer("}").nextToken(), equals(KdlToken(KdlTerm.rbrace, '}'))); }); - test('whitespace', () { - expect(KdlTokenizer(" ").nextToken(), equals([KdlToken.WS, ' '])); - expect(KdlTokenizer("\t").nextToken(), equals([KdlToken.WS, "\t"])); - expect(KdlTokenizer(" \t").nextToken(), equals([KdlToken.WS, " \t"])); + test('equals', () { + expect( + KdlTokenizer("=").nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + expect( + KdlTokenizer(" =").nextToken(), equals(KdlToken(KdlTerm.equals, ' ='))); + expect( + KdlTokenizer("= ").nextToken(), equals(KdlToken(KdlTerm.equals, '= '))); + expect(KdlTokenizer(" = ").nextToken(), + equals(KdlToken(KdlTerm.equals, ' = '))); + expect(KdlTokenizer(" =foo").nextToken(), + equals(KdlToken(KdlTerm.equals, ' ='))); }); - test('escline', () { - expect(KdlTokenizer("\\\n").nextToken(), equals([KdlToken.ESCLINE, "\\\n"])); - expect(KdlTokenizer("\\").nextToken(), equals([KdlToken.ESCLINE, "\\"])); - expect(KdlTokenizer("\\//some comment\n").nextToken(), equals([KdlToken.ESCLINE, "\\\n"])); - expect(KdlTokenizer("\\ //some comment\n").nextToken(), equals([KdlToken.ESCLINE, "\\ \n"])); - expect(KdlTokenizer("\\//some comment").nextToken(), equals([KdlToken.ESCLINE, "\\"])); + test('whitespace', () { + expect(KdlTokenizer(" ").nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(KdlTokenizer("\t").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\t"))); + expect(KdlTokenizer(" \t").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \t"))); + expect(KdlTokenizer("\\\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\\\n"))); + expect(KdlTokenizer("\\").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\\"))); + expect(KdlTokenizer("\\//some comment\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\\\n"))); + expect(KdlTokenizer("\\ //some comment\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\\ \n"))); + expect(KdlTokenizer("\\//some comment").nextToken(), + equals(KdlToken(KdlTerm.whitespace, "\\"))); + expect(KdlTokenizer(" \\\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\\n"))); + expect(KdlTokenizer(" \\//some comment\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\\n"))); + expect(KdlTokenizer(" \\ //some comment\n").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\ \n"))); + expect(KdlTokenizer(" \\//some comment").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\"))); + expect(KdlTokenizer(" \\\n \\\n ").nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\\n \\\n "))); }); test('multiple_tokens', () { var tokenizer = KdlTokenizer("node 1 \"two\" a=3"); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'node'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.INTEGER, 1])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.STRING, 'two'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'a'])); - expect(tokenizer.nextToken(), equals([KdlToken.EQUALS, '='])); - expect(tokenizer.nextToken(), equals([KdlToken.INTEGER, 3])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node', 1, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 1, 5))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 1, 1, 6))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 1, 7))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, 'two', 1, 8))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' ', 1, 13))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'a', 1, 14))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '=', 1, 15))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 3, 1, 16))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 1, 17))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 1, 17))); }); test('single_line_comment', () { - expect(KdlTokenizer("// comment").nextToken(), equals([KdlToken.EOF, ''])); + expect(KdlTokenizer("// comment").nextToken(), + equals(KdlToken(KdlTerm.eof, ''))); var tokenizer = KdlTokenizer(""" node1 // comment node2 - """.trim()); - - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'node1'])); - expect(tokenizer.nextToken(), equals([KdlToken.NEWLINE, "\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.NEWLINE, "\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'node2'])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + """ + .trim()); + + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node1', 1, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n", 1, 6))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n", 2, 11))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node2', 3, 1))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 3, 6))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 3, 6))); }); test('multiline_comment', () { var tokenizer = KdlTokenizer("foo /*bar=1*/ baz=2"); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'foo'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'baz'])); - expect(tokenizer.nextToken(), equals([KdlToken.EQUALS, '='])); - expect(tokenizer.nextToken(), equals([KdlToken.INTEGER, 2])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo', 1, 1))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' ', 1, 4))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'baz', 1, 15))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '=', 1, 18))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 2, 1, 19))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 1, 20))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 1, 20))); }); test('utf8', () { - expect(KdlTokenizer("😁").nextToken(), equals([KdlToken.IDENT, '😁'])); - expect(KdlTokenizer('"😁"').nextToken(), equals([KdlToken.STRING, '😁'])); - expect(KdlTokenizer('ノード').nextToken(), equals([KdlToken.IDENT, 'ノード'])); - expect(KdlTokenizer('お名前').nextToken(), equals([KdlToken.IDENT, 'お名前'])); - expect(KdlTokenizer('"☜(゚ヮ゚☜)"').nextToken(), equals([KdlToken.STRING, '☜(゚ヮ゚☜)'])); + expect( + KdlTokenizer("😁").nextToken(), equals(KdlToken(KdlTerm.ident, '😁'))); + expect(KdlTokenizer('"😁"').nextToken(), + equals(KdlToken(KdlTerm.string, '😁'))); + expect(KdlTokenizer('ノード').nextToken(), + equals(KdlToken(KdlTerm.ident, 'ノード'))); + expect(KdlTokenizer('お名前').nextToken(), + equals(KdlToken(KdlTerm.ident, 'お名前'))); + expect(KdlTokenizer('"☜(゚ヮ゚☜)"').nextToken(), + equals(KdlToken(KdlTerm.string, '☜(゚ヮ゚☜)'))); var tokenizer = KdlTokenizer(""" smile "😁" -ノード お名前="☜(゚ヮ゚☜)" - """.trim()); - - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'smile'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.STRING, '😁'])); - expect(tokenizer.nextToken(), equals([KdlToken.NEWLINE, "\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'ノード'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'お名前'])); - expect(tokenizer.nextToken(), equals([KdlToken.EQUALS, '='])); - expect(tokenizer.nextToken(), equals([KdlToken.STRING, '☜(゚ヮ゚☜)'])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); +ノード お名前="☜(゚ヮ゚☜)" + """ + .trim()); + + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'smile', 1, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 1, 6))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, '😁', 1, 7))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n", 1, 10))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'ノード', 2, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 2, 4))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'お名前', 2, 5))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '=', 2, 8))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.string, '☜(゚ヮ゚☜)', 2, 9))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 2, 18))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 2, 18))); }); test('semicolon', () { var tokenizer = KdlTokenizer('node1; node2'); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'node1'])); - expect(tokenizer.nextToken(), equals([KdlToken.SEMICOLON, ';'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'node2'])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node1', 1, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.semicolon, ';', 1, 6))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 1, 7))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node2', 1, 8))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 1, 13))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 1, 13))); }); test('slash_dash', () { @@ -174,61 +273,78 @@ smile "😁" /-mynode /-"foo" /-key=1 /-{ a } - """.trim()); - - expect(tokenizer.nextToken(), equals([KdlToken.SLASHDASH, '/-'])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'mynode'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.SLASHDASH, '/-'])); - expect(tokenizer.nextToken(), equals([KdlToken.STRING, 'foo'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.SLASHDASH, '/-'])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'key'])); - expect(tokenizer.nextToken(), equals([KdlToken.EQUALS, '='])); - expect(tokenizer.nextToken(), equals([KdlToken.INTEGER, 1])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.SLASHDASH, '/-'])); - expect(tokenizer.nextToken(), equals([KdlToken.LBRACE, '{'])); - expect(tokenizer.nextToken(), equals([KdlToken.NEWLINE, "\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'a'])); - expect(tokenizer.nextToken(), equals([KdlToken.NEWLINE, "\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.RBRACE, '}'])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + """ + .trim()); + + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.slashdash, '/-', 1, 1))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'mynode', 1, 3))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' ', 1, 9))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.slashdash, '/-', 1, 10))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, 'foo', 1, 12))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' ', 1, 17))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.slashdash, '/-', 1, 18))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'key', 1, 20))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '=', 1, 23))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 1, 1, 24))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' ', 1, 25))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.slashdash, '/-', 1, 26))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lbrace, '{', 1, 28))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n", 1, 29))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, ' ', 2, 1))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'a', 2, 3))); + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n", 2, 4))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rbrace, '}', 3, 1))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 3, 2))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 3, 2))); }); test('multiline_nodes', () { var tokenizer = KdlTokenizer(""" title \\ "Some title" - """.trim()); - - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'title'])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.ESCLINE, "\\\n"])); - expect(tokenizer.nextToken(), equals([KdlToken.WS, ' '])); - expect(tokenizer.nextToken(), equals([KdlToken.STRING, 'Some title'])); - expect(tokenizer.nextToken(), equals([KdlToken.EOF, ''])); - expect(tokenizer.nextToken(), equals([false, false])); + """ + .trim()); + + expect( + tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'title', 1, 1))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.whitespace, " \\\n ", 1, 6))); + expect(tokenizer.nextToken(), + equals(KdlToken(KdlTerm.string, 'Some title', 2, 3))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, '', 2, 15))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null, 2, 15))); }); test('types', () { var tokenizer = KdlTokenizer("(foo)bar"); - expect(tokenizer.nextToken(), equals([KdlToken.LPAREN, '('])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'foo'])); - expect(tokenizer.nextToken(), equals([KdlToken.RPAREN, ')'])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'bar'])); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rparen, ')'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'bar'))); tokenizer = KdlTokenizer("(foo)/*asdf*/bar"); - expect(tokenizer.nextToken(), equals([KdlToken.LPAREN, '('])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'foo'])); - expect(tokenizer.nextToken(), equals([KdlToken.RPAREN, ')'])); - expect(() => tokenizer.nextToken(), throwsA(anything)); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rparen, ')'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'bar'))); tokenizer = KdlTokenizer("(foo/*asdf*/)bar"); - expect(tokenizer.nextToken(), equals([KdlToken.LPAREN, '('])); - expect(tokenizer.nextToken(), equals([KdlToken.IDENT, 'foo'])); - expect(() => tokenizer.nextToken(), throwsA(anything)); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rparen, ')'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'bar'))); }); } diff --git a/test/types/base64_test.dart b/test/types/base64_test.dart index f0712c9..4997896 100644 --- a/test/types/base64_test.dart +++ b/test/types/base64_test.dart @@ -7,9 +7,9 @@ import 'package:kdl/src/types/base64.dart'; void main() { test('base64', () { - expect(KdlBase64.call(KdlString('U2VuZCByZWluZm9yY2VtZW50cw==')).value, + expect(KdlBase64.convert(KdlString('U2VuZCByZWluZm9yY2VtZW50cw=='))!.value, equals(utf8.encode('Send reinforcements'))); - expect(() => KdlBase64.call(KdlString('not base64')), throwsA(anything)); + expect(() => KdlBase64.convert(KdlString('not base64')), throwsA(anything)); }); } diff --git a/test/types/country_test.dart b/test/types/country_test.dart index 939a14e..b009fe9 100644 --- a/test/types/country_test.dart +++ b/test/types/country_test.dart @@ -8,24 +8,24 @@ void main() { var southAfrica = Country(alpha3: 'ZAF', alpha2: 'ZA', numericCode: 710, name: 'South Africa'); test('country3', () { - expect(KdlCountry3.call(KdlString('ZAF')).value, equals(southAfrica)); + expect(KdlCountry3.convert(KdlString('ZAF'))!.value, equals(southAfrica)); - expect(() => KdlCountry3.call(KdlString('ZZZ')), throwsA(anything)); + expect(() => KdlCountry3.convert(KdlString('ZZZ')), throwsA(anything)); }); test('country2', () { - expect(KdlCountry2.call(KdlString('ZA')).value, equals(southAfrica)); + expect(KdlCountry2.convert(KdlString('ZA'))!.value, equals(southAfrica)); - expect(() => KdlCountry2.call(KdlString('ZZ')), throwsA(anything)); + expect(() => KdlCountry2.convert(KdlString('ZZ')), throwsA(anything)); }); test('country subdivision', () { - var value = KdlCountrySubdivision.call(KdlString('ZA-GP')); + var value = KdlCountrySubdivision.convert(KdlString('ZA-GP'))!; expect(value.value, equals('ZA-GP')); expect(value.name, equals('Gauteng')); expect(value.country, equals(southAfrica)); - expect(() => KdlCountrySubdivision.call(KdlString('ZA-ZZ')), throwsA(anything)); - expect(() => KdlCountrySubdivision.call(KdlString('ZZ-GP')), throwsA(anything)); + expect(() => KdlCountrySubdivision.convert(KdlString('ZA-ZZ')), throwsA(anything)); + expect(() => KdlCountrySubdivision.convert(KdlString('ZZ-GP')), throwsA(anything)); }); } diff --git a/test/types/currency_test.dart b/test/types/currency_test.dart index ea1ffe4..0ee81da 100644 --- a/test/types/currency_test.dart +++ b/test/types/currency_test.dart @@ -6,9 +6,9 @@ import 'package:kdl/src/types/currency/iso4217_currencies.dart'; void main() { test('uuid', () { - expect(KdlCurrency.call(KdlString('ZAR')).value, + expect(KdlCurrency.convert(KdlString('ZAR'))!.value, equals(Currency(numericCode: 710, minorUnit: 2, name: 'South African rand'))); - expect(() => KdlCurrency.call(KdlString('ZZZ')), throwsA(anything)); + expect(() => KdlCurrency.convert(KdlString('ZZZ')), throwsA(anything)); }); } diff --git a/test/types/date_time_test.dart b/test/types/date_time_test.dart index 479701b..4b2fe02 100644 --- a/test/types/date_time_test.dart +++ b/test/types/date_time_test.dart @@ -5,30 +5,30 @@ import 'package:kdl/src/types/date_time.dart'; void main() { test('date time', () { - expect(KdlDateTime.call(KdlString('2011-10-05T22:26:12-04:00')).value, + expect(KdlDateTime.convert(KdlString('2011-10-05T22:26:12-04:00'))!.value, equals(DateTime.parse('2011-10-05T22:26:12-04:00'))); - expect(() => KdlDateTime.call(KdlString('not a date time')), throwsA(anything)); + expect(() => KdlDateTime.convert(KdlString('not a date time')), throwsA(anything)); }); test('time', () { var today = DateTime.now().toString().split(' ')[0]; - expect(KdlTime.call(KdlString('22:26:12')).value, + expect(KdlTime.convert(KdlString('22:26:12'))!.value, equals(DateTime.parse("${today}T22:26:12"))); - expect(KdlTime.call(KdlString('T22:26:12Z')).value, + expect(KdlTime.convert(KdlString('T22:26:12Z'))!.value, equals(DateTime.parse("${today}T22:26:12Z"))); - expect(KdlTime.call(KdlString('22:26:12.000Z')).value, + expect(KdlTime.convert(KdlString('22:26:12.000Z'))!.value, equals(DateTime.parse("${today}T22:26:12Z"))); - expect(KdlTime.call(KdlString('22:26:12-04:00')).value, + expect(KdlTime.convert(KdlString('22:26:12-04:00'))!.value, equals(DateTime.parse("${today}T22:26:12-04:00"))); - expect(() => KdlTime.call(KdlString('not a time')), throwsA(anything)); + expect(() => KdlTime.convert(KdlString('not a time')), throwsA(anything)); }); test('date', () { - expect(KdlDate.call(KdlString('2011-10-05')).value, + expect(KdlDate.convert(KdlString('2011-10-05'))!.value, equals(DateTime.parse('2011-10-05'))); - expect(() => KdlDate.call(KdlString('not a date')), throwsA(anything)); + expect(() => KdlDate.convert(KdlString('not a date')), throwsA(anything)); }); } diff --git a/test/types/decimal_test.dart b/test/types/decimal_test.dart index f4c0d80..03b45fe 100644 --- a/test/types/decimal_test.dart +++ b/test/types/decimal_test.dart @@ -6,9 +6,9 @@ import 'package:kdl/src/types/decimal.dart'; void main() { test('decimal', () { - expect(KdlDecimal.call(KdlString('10000000000000')).value, + expect(KdlDecimal.convert(KdlString('10000000000000'))!.value, equals(BigDecimal.parse('10000000000000'))); - expect(() => KdlDecimal.call(KdlString('not a decimal')), throwsA(anything)); + expect(() => KdlDecimal.convert(KdlString('not a decimal')), throwsA(anything)); }); } diff --git a/test/types/duration_test.dart b/test/types/duration_test.dart index 7f21bfc..dc30524 100644 --- a/test/types/duration_test.dart +++ b/test/types/duration_test.dart @@ -5,27 +5,27 @@ import 'package:kdl/src/types/duration.dart'; void main() { test('uuid', () { - var value = KdlDuration.call(KdlString('P3Y6M4DT12H30M5S')); - expect(value.value, equals(ISODuration(years: 3, months: 6, days: 4, hours: 12, minutes: 30, seconds: 5))); - value = KdlDuration.call(KdlString('P23DT23H')); - expect(value.value, equals(ISODuration(days: 23, hours: 23))); - value = KdlDuration.call(KdlString('P4Y')); - expect(value.value, equals(ISODuration(years: 4))); - value = KdlDuration.call(KdlString('PT0S')); - expect(value.value, equals(ISODuration(seconds: 0))); - value = KdlDuration.call(KdlString('P0D')); - expect(value.value, equals(ISODuration(days: 0))); - value = KdlDuration.call(KdlString('P0.5Y')); - expect(value.value, equals(ISODuration(years: 0.5))); - value = KdlDuration.call(KdlString('P0,5Y')); - expect(value.value, equals(ISODuration(years: 0.5))); - value = KdlDuration.call(KdlString('P1M')); - expect(value.value, equals(ISODuration(months: 1))); - value = KdlDuration.call(KdlString('PT1M')); - expect(value.value, equals(ISODuration(minutes: 1))); - value = KdlDuration.call(KdlString('P7W')); - expect(value.value, equals(ISODuration(weeks: 7))); + var value = KdlDuration.convert(KdlString('P3Y6M4DT12H30M5S'))!; + expect(value.value, equals(Duration(years: 3, months: 6, days: 4, hours: 12, minutes: 30, seconds: 5))); + value = KdlDuration.convert(KdlString('P23DT23H'))!; + expect(value.value, equals(Duration(days: 23, hours: 23))); + value = KdlDuration.convert(KdlString('P4Y'))!; + expect(value.value, equals(Duration(years: 4))); + value = KdlDuration.convert(KdlString('PT0S'))!; + expect(value.value, equals(Duration(seconds: 0))); + value = KdlDuration.convert(KdlString('P0D'))!; + expect(value.value, equals(Duration(days: 0))); + value = KdlDuration.convert(KdlString('P0.5Y'))!; + expect(value.value, equals(Duration(years: 0.5))); + value = KdlDuration.convert(KdlString('P0,5Y'))!; + expect(value.value, equals(Duration(years: 0.5))); + value = KdlDuration.convert(KdlString('P1M'))!; + expect(value.value, equals(Duration(months: 1))); + value = KdlDuration.convert(KdlString('PT1M'))!; + expect(value.value, equals(Duration(minutes: 1))); + value = KdlDuration.convert(KdlString('P7W'))!; + expect(value.value, equals(Duration(weeks: 7))); - expect(() => KdlDuration.call(KdlString('not a duration')), throwsA(anything)); + expect(() => KdlDuration.convert(KdlString('not a duration')), throwsA(anything)); }); } diff --git a/test/types/email_test.dart b/test/types/email_test.dart index cd76bff..317f49a 100644 --- a/test/types/email_test.dart +++ b/test/types/email_test.dart @@ -5,12 +5,12 @@ import 'package:kdl/src/types/email.dart'; void main() { test('email', () { - var value = KdlEmail.call(KdlString('danielle@example.com')); + var value = KdlEmail.convert(KdlString('danielle@example.com'))!; expect(value.value, equals('danielle@example.com')); expect(value.local, equals('danielle')); expect(value.domain, equals('example.com')); - expect(() => KdlEmail.call(KdlString('not an email')), throwsA(anything)); + expect(() => KdlEmail.convert(KdlString('not an email')), throwsA(anything)); }); var validEmails = [ @@ -34,7 +34,7 @@ void main() { test('valid emails', () { for (var testCase in validEmails) { - var value = KdlEmail.call(KdlString(testCase[0])); + var value = KdlEmail.convert(KdlString(testCase[0]))!; expect(value.value, equals(testCase[0])); expect(value.local, equals(testCase[1])); expect(value.domain, equals(testCase[2])); @@ -44,10 +44,10 @@ void main() { var invalidEmails = [ 'Abc.example.com', 'A@b@c@example.com', - 'a"b(c)d,e:f;gi[j\k]l@example.com', + 'a"b(c)d,e:f;gi[j\\k]l@example.com', 'just"not"right@example.com', - 'this is"not\allowed@example.com', - 'this\ still\"not\\allowed@example.com', + 'this is"not\\allowed@example.com', + 'this\\ still\\"not\\allowed@example.com', '1234567890123456789012345678901234567890123456789012345678901234+x@example.com', '-some-user-@-example-.com', 'QA🦄CHOCOLATE🌈@test.com', @@ -55,24 +55,24 @@ void main() { test('invalid emails', () { for (var email in invalidEmails) { - expect(() => KdlEmail.call(KdlString(email)), throwsA(anything)); + expect(() => KdlEmail.convert(KdlString(email)), throwsA(anything)); } }); test('idn email', () { - var value = KdlIDNEmail.call(KdlString('🌈@xn--9ckb.com')); + var value = KdlIdnEmail.convert(KdlString('🌈@xn--9ckb.com'))!; expect(value.value, equals('🌈@xn--9ckb.com')); expect(value.unicodeValue, equals('🌈@ツッ.com')); expect(value.local, equals('🌈')); expect(value.unicodeDomain, equals('ツッ.com')); expect(value.domain, equals('xn--9ckb.com')); - value = KdlIDNEmail.call(KdlString('🌈@ツッ.com')); + value = KdlIdnEmail.convert(KdlString('🌈@ツッ.com'))!; expect(value.value, equals('🌈@xn--9ckb.com')); expect(value.unicodeValue, equals('🌈@ツッ.com')); expect(value.local, equals('🌈')); expect(value.unicodeDomain, equals('ツッ.com')); expect(value.domain, equals('xn--9ckb.com')); - expect(() => KdlIDNEmail.call(KdlString('not an email')), throwsA(anything)); + expect(() => KdlIdnEmail.convert(KdlString('not an email')), throwsA(anything)); }); } diff --git a/test/types/hostname_test.dart b/test/types/hostname_test.dart index a3cc259..d1edc4b 100644 --- a/test/types/hostname_test.dart +++ b/test/types/hostname_test.dart @@ -5,34 +5,42 @@ import 'package:kdl/src/types/hostname.dart'; void main() { test('hostname', () { - expect(KdlHostname.call(KdlString('www.example.com')).value, - equals('www.example.com')); + expect(KdlHostname.convert(KdlString('www.example.com'))!.value, + equals('www.example.com')); // 63 a's - var maxPartLength = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com'; - expect(KdlHostname.call(KdlString(maxPartLength)).value, equals(maxPartLength)); - - var maxLength = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; - expect(KdlHostname.call(KdlString(maxLength)).value, equals(maxLength)); - - expect(() => KdlHostname.call(KdlString('not a hostname')), throwsA(anything)); - expect(() => KdlHostname.call(KdlString('-start-with-a-dash.com')), throwsA(anything)); - expect(() => KdlHostname.call(KdlString('a' + maxPartLength)), throwsA(anything)); - expect(() => KdlHostname.call(KdlString(maxLength + 'a')), throwsA(anything)); + var maxPartLength = + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com'; + expect(KdlHostname.convert(KdlString(maxPartLength))!.value, + equals(maxPartLength)); + + var maxLength = + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + expect(KdlHostname.convert(KdlString(maxLength))?.value, equals(maxLength)); + + expect( + () => KdlHostname.convert(KdlString('not a hostname')), throwsA(anything)); + expect(() => KdlHostname.convert(KdlString('-start-with-a-dash.com')), + throwsA(anything)); + expect(() => KdlHostname.convert(KdlString('a$maxPartLength')), + throwsA(anything)); + expect( + () => KdlHostname.convert(KdlString('${maxLength}a')), throwsA(anything)); }); test('idn hostname', () { - var value = KdlIDNHostname.call(KdlString('xn--bcher-kva.example')); + var value = KdlIdnHostname.convert(KdlString('xn--bcher-kva.example'))!; expect(value.value, equals('xn--bcher-kva.example')); expect(value.unicodeValue, equals('bücher.example')); - value = KdlIDNHostname.call(KdlString('bücher.example')); + value = KdlIdnHostname.convert(KdlString('bücher.example'))!; expect(value.value, equals('xn--bcher-kva.example')); expect(value.unicodeValue, equals('bücher.example')); - expect(() => KdlIDNHostname.call(KdlString('not a hostname')), throwsA(anything)); + expect(() => KdlIdnHostname.convert(KdlString('not a hostname')), + throwsA(anything)); }); } diff --git a/test/types/ip_test.dart b/test/types/ip_test.dart index 4b8828a..98a4036 100644 --- a/test/types/ip_test.dart +++ b/test/types/ip_test.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:test/test.dart'; import 'package:kdl/src/document.dart'; @@ -6,20 +5,40 @@ import 'package:kdl/src/types/ip.dart'; void main() { test('ipv4', () { - expect(KdlIPV4.call(KdlString('127.0.0.1')).value, - equals(InternetAddress('127.0.0.1'))); + expect(KdlIPV4.convert(KdlString('127.0.0.1'))!.value, equals('127.0.0.1')); + expect(KdlIPV4.convert(KdlString('192.168.42.255'))!.value, + equals('192.168.42.255')); - expect(() => KdlIPV4.call(KdlString('not an ipv4 address')), throwsA(anything)); - expect(() => KdlIPV4.call(KdlString('3ffe:505:2::1')), throwsA(anything)); + expect(() => KdlIPV4.convert(KdlString('not an ipv4 address')), + throwsA(anything)); + expect(() => KdlIPV4.convert(KdlString('3ffe:505:2::1')), throwsA(anything)); + expect(() => KdlIPV4.convert(KdlString('256.0.0.0')), throwsA(anything)); + expect(() => KdlIPV4.convert(KdlString('312.0.0.0')), throwsA(anything)); }); test('ipv6', () { - expect(KdlIPV6.call(KdlString('::')).value, - equals(InternetAddress('::'))); - expect(KdlIPV6.call(KdlString('3ffe:505:2::1')).value, - equals(InternetAddress('3ffe:505:2::1'))); + final addresses = [ + 'FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', + '1080:0:0:0:8:800:200C:417A', + '1080:0:0:0:8:800:200C:417A', + 'FF01:0:0:0:0:0:0:101', + '0:0:0:0:0:0:0:1', + '0:0:0:0:0:0:0:0', + '1080::8:800:200C:417A', + 'FF01::101', + '::1', + '::', + '0:0:0:0:0:0:13.1.68.3', + '0:0:0:0:0:FFFF:129.144.52.38', + '::13.1.68.3', + '::FFFF:129.144.52.38' + ]; + for (var addr in addresses) { + expect(KdlIPV6.convert(KdlString(addr))!.value, equals(addr)); + } - expect(() => KdlIPV6.call(KdlString('not an ipv4 address')), throwsA(anything)); - expect(() => KdlIPV6.call(KdlString('127.0.0.1')), throwsA(anything)); + expect(() => KdlIPV6.convert(KdlString('not an ipv6 address')), + throwsA(anything)); + expect(() => KdlIPV6.convert(KdlString('127.0.0.1')), throwsA(anything)); }); } diff --git a/test/types/irl_test.dart b/test/types/irl_test.dart index d59fd31..4e42fbb 100644 --- a/test/types/irl_test.dart +++ b/test/types/irl_test.dart @@ -5,37 +5,37 @@ import 'package:kdl/src/types/irl.dart'; void main() { test('irl', () { - var value = KdlIRL.call(KdlString('https://bücher.example/foo/Ῥόδος')); + var value = KdlIRL.convert(KdlString('https://bücher.example/foo/Ῥόδος'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος')); - value = KdlIRL.call(KdlString('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82')); + value = KdlIRL.convert(KdlString('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος')); - value = KdlIRL.call(KdlString('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄')); + value = KdlIRL.convert(KdlString('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82?%F0%9F%8C%88=%E2%9C%94%EF%B8%8F#%F0%9F%A6%84'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄')); - expect(() => KdlIRL.call(KdlString('not a url')), throwsA(anything)); - expect(() => KdlIRL.call(KdlString('/reference/to/something')), throwsA(anything)); + expect(() => KdlIRL.convert(KdlString('not a url')), throwsA(anything)); + expect(() => KdlIRL.convert(KdlString('/reference/to/something')), throwsA(anything)); }); test('irl reference', () { - var value = KdlIRLReference.call(KdlString('https://bücher.example/foo/Ῥόδος')); + var value = KdlIrlReference.convert(KdlString('https://bücher.example/foo/Ῥόδος'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος')); - value = KdlIRLReference.call(KdlString('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82')); + value = KdlIrlReference.convert(KdlString('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος')); - value = KdlIRLReference.call(KdlString('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄')); + value = KdlIrlReference.convert(KdlString('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄'))!; expect(value.value, equals(Uri.parse('https://xn--bcher-kva.example/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82?%F0%9F%8C%88=%E2%9C%94%EF%B8%8F#%F0%9F%A6%84'))); expect(value.unicodeValue, equals('https://bücher.example/foo/Ῥόδος?🌈=✔️#🦄')); - value = KdlIRLReference.call(KdlString('/foo/Ῥόδος')); + value = KdlIrlReference.convert(KdlString('/foo/Ῥόδος'))!; expect(value.value, equals(Uri.parse('/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('/foo/Ῥόδος')); - value = KdlIRLReference.call(KdlString('/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82')); + value = KdlIrlReference.convert(KdlString('/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))!; expect(value.value, equals(Uri.parse('/foo/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82'))); expect(value.unicodeValue, equals('/foo/Ῥόδος')); - expect(() => KdlIRLReference.call(KdlString('not a url')), throwsA(anything)); + expect(() => KdlIrlReference.convert(KdlString('not a url')), throwsA(anything)); }); } diff --git a/test/types/regex_test.dart b/test/types/regex_test.dart index 7c22d63..83867d5 100644 --- a/test/types/regex_test.dart +++ b/test/types/regex_test.dart @@ -5,9 +5,9 @@ import 'package:kdl/src/types/regex.dart'; void main() { test('regex', () { - expect(KdlRegex.call(KdlString('asdf')).value, + expect(KdlRegex.convert(KdlString('asdf'))!.value, equals(RegExp('asdf'))); - expect(() => KdlRegex.call(KdlString('invalid(regex]')), throwsA(anything)); + expect(() => KdlRegex.convert(KdlString('invalid(regex]')), throwsA(anything)); }); } diff --git a/test/types/url_template_test.dart b/test/types/url_template_test.dart index d9adb06..0a6368f 100644 --- a/test/types/url_template_test.dart +++ b/test/types/url_template_test.dart @@ -24,23 +24,23 @@ var variables = { }; void assertExpansionEqual(String template, String expected) { - var value = KdlURLTemplate.call(KdlString(template)); + var value = KdlUrlTemplate.convert(KdlString(template))!; expect(value.expand(variables), equals(Uri.parse(expected))); } void main() { test('no variables', () { - var value = KdlURLTemplate.call(KdlString('https://www.example.com/foo/bar')); + var value = KdlUrlTemplate.convert(KdlString('https://www.example.com/foo/bar'))!; expect(value.expand({}), equals(Uri.parse('https://www.example.com/foo/bar'))); }); test('one variable', () { - var value = KdlURLTemplate.call(KdlString('https://www.example.com/{foo}/bar')); + var value = KdlUrlTemplate.convert(KdlString('https://www.example.com/{foo}/bar'))!; expect(value.expand({ 'foo': 'lorem' }), equals(Uri.parse('https://www.example.com/lorem/bar'))); }); test('multiple_variables', () { - var value = KdlURLTemplate.call(KdlString('https://www.example.com/{foo}/{bar}')); + var value = KdlUrlTemplate.convert(KdlString('https://www.example.com/{foo}/{bar}'))!; expect(value.expand({ 'foo': 'lorem', 'bar': 'ipsum' }), equals(Uri.parse('https://www.example.com/lorem/ipsum'))); }); @@ -190,4 +190,3 @@ void main() { assertExpansionEqual('{&keys*}', '&semi=%3B&dot=.&comma=%2C'); }); } - diff --git a/test/types/url_test.dart b/test/types/url_test.dart index 379de7c..445ba27 100644 --- a/test/types/url_test.dart +++ b/test/types/url_test.dart @@ -5,16 +5,16 @@ import 'package:kdl/src/types/url.dart'; void main() { test('url', () { - expect(KdlURL.call(KdlString('https://www.example.com/foo/bar')).value, + expect(KdlUrl.convert(KdlString('https://www.example.com/foo/bar'))!.value, equals(Uri.parse('https://www.example.com/foo/bar'))); - expect(() => KdlURL.call(KdlString('/reference/to/something')), throwsA(anything)); + expect(() => KdlUrl.convert(KdlString('/reference/to/something')), throwsA(anything)); }); test('url reference', () { - expect(KdlURLReference.call(KdlString('https://www.example.com/foo/bar')).value, + expect(KdlUrlReference.convert(KdlString('https://www.example.com/foo/bar'))!.value, equals(Uri.parse('https://www.example.com/foo/bar'))); - expect(KdlURLReference.call(KdlString('/foo/bar')).value, + expect(KdlUrlReference.convert(KdlString('/foo/bar'))!.value, equals(Uri.parse('/foo/bar'))); }); } diff --git a/test/types/uuid_test.dart b/test/types/uuid_test.dart index 8a98546..bea5762 100644 --- a/test/types/uuid_test.dart +++ b/test/types/uuid_test.dart @@ -5,11 +5,11 @@ import 'package:kdl/src/types/uuid.dart'; void main() { test('uuid', () { - expect(KdlUUID.call(KdlString('f81d4fae-7dec-11d0-a765-00a0c91e6bf6')).value, + expect(KdlUuid.convert(KdlString('f81d4fae-7dec-11d0-a765-00a0c91e6bf6'))!.value, equals('f81d4fae-7dec-11d0-a765-00a0c91e6bf6')); - expect(KdlUUID.call(KdlString('F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6')).value, + expect(KdlUuid.convert(KdlString('F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6'))!.value, equals('f81d4fae-7dec-11d0-a765-00a0c91e6bf6')); - expect(() => KdlUUID.call(KdlString('not a uuid')), throwsA(anything)); + expect(() => KdlUuid.convert(KdlString('not a uuid')), throwsA(anything)); }); } diff --git a/test/types_test.dart b/test/types_test.dart index abc20db..7f2c7d3 100644 --- a/test/types_test.dart +++ b/test/types_test.dart @@ -1,36 +1,21 @@ - import 'package:test/test.dart'; import 'package:kdl/kdl.dart'; -import 'package:kdl/src/document.dart'; import 'package:kdl/src/parser.dart'; -import 'package:kdl/src/types/date_time.dart'; -import 'package:kdl/src/types/duration.dart'; -import 'package:kdl/src/types/decimal.dart'; -import 'package:kdl/src/types/currency.dart'; -import 'package:kdl/src/types/country.dart'; -import 'package:kdl/src/types/email.dart'; -import 'package:kdl/src/types/hostname.dart'; -import 'package:kdl/src/types/ip.dart'; -import 'package:kdl/src/types/url.dart'; -import 'package:kdl/src/types/irl.dart'; -import 'package:kdl/src/types/url_template.dart'; -import 'package:kdl/src/types/uuid.dart'; -import 'package:kdl/src/types/regex.dart'; -import 'package:kdl/src/types/base64.dart'; class Foo extends KdlValue { - Foo(String value, [String? type]) : super(value, type); + Foo(super.value, [super.type]); } class Bar extends KdlNode { - Bar(KdlNode node, [String? type]) : super( - node.name, - arguments: node.arguments, - properties: node.properties, - children: node.children, - type: type, - ); + Bar(KdlNode node, [String? type]) + : super( + node.name, + arguments: node.arguments, + properties: node.properties, + children: node.children, + type: type, + ); } void main() { @@ -66,7 +51,8 @@ node (date-time)"2021-01-01T12:12:12" \\ (uuid)"f81d4fae-7dec-11d0-a765-00a0c91e6bf6" \\ (regex)"asdf" \\ (base64)"U2VuZCByZWluZm9yY2VtZW50cw==" -""".trim()); +""" + .trim()); var i = 0; expect(doc.nodes[0].arguments[i++], isA()); @@ -79,36 +65,40 @@ node (date-time)"2021-01-01T12:12:12" \\ expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); - expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); + expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); expect(doc.nodes[0].arguments[i++], isA()); }); test('custom types', () { - var parsers = { - 'foo': (value, type) { - if (!(value is KdlValue)) return null; + var customValues = { + 'foo': (KdlValue value, String type) { return Foo(value.value, type); - }, - 'bar': (node, type) { - if (!(node is KdlNode)) return null; + } + }; + var customNodes = { + 'bar': (KdlNode node, type) { return Bar(node, type); }, }; - var doc = Kdl.parseDocument(""" + var doc = KdlDocument.parse( + """ (bar)barnode (foo)"foovalue" (foo)foonode (bar)"barvalue" -""".trim(), typeParsers: parsers); +""" + .trim(), + valueTypes: customValues, + nodeTypes: customNodes); expect(doc, isNotNull); expect(doc.nodes[0], isA()); @@ -118,9 +108,12 @@ node (date-time)"2021-01-01T12:12:12" \\ }); test('parse false', () { - var doc = Kdl.parseDocument(""" + var doc = KdlDocument.parse( + """ node (date-time)"2021-01-01T12:12:12" - """.trim(), parseTypes: false); + """ + .trim(), + parseTypes: false); expect(doc, isNotNull); expect(doc.nodes[0].arguments[0], isA()); diff --git a/test/v1/example_test.dart b/test/v1/example_test.dart new file mode 100644 index 0000000..b32d26f --- /dev/null +++ b/test/v1/example_test.dart @@ -0,0 +1,150 @@ +import 'package:kdl/src/document.dart'; +import 'package:test/test.dart'; +import 'dart:io'; + +import 'package:kdl/kdl.dart'; + +typedef VarArgsCallback = T Function(List args); + +class VarArgsFunction { + final VarArgsCallback callback; + + VarArgsFunction(this.callback); + + T call() => callback([]); + + @override + dynamic noSuchMethod(Invocation inv) { + return callback( + inv.positionalArguments, + ); + } +} + +main() { + KdlNode? currentNode; + KdlDocument? currentDocument; + + dynamic n = VarArgsFunction((args) { + var argv = List.from(args); + var block = () {}; + var kwargs = {}; + if (argv.last is Function) block = argv.removeLast(); + if (argv.last is Map) kwargs = argv.removeLast(); + var node = KdlNode( + argv.removeAt(0), + arguments: argv.map((e) => KdlValue.from(e)).toList(), + properties: kwargs.map((key, value) => MapEntry(key, KdlValue.from(value))) + ); + var previousNode = currentNode; + currentNode = node; + block.call(); + currentNode = previousNode; + if (currentNode != null) { + currentNode!.children.add(node); + } else { + currentDocument!.nodes.add(node); + } + }); + + nodes(block) { + var doc = KdlDocument([]); + currentDocument = doc; + block.call(); + currentDocument = null; + return doc; + } + + test('ci', () async { + var string = await File('./test/v1/kdl-org/examples/ci.kdl').readAsString(); + var doc = KdlDocument.parse(string, version: 1); + var expectedDoc = nodes(() { + n("name", "CI"); + n("on", "push", "pull_request"); + n("env", () { + n("RUSTFLAGS", "-Dwarnings"); + }); + n("jobs", () { + n("fmt_and_docs", "Check fmt & build docs", () { + n("runs-on", "ubuntu-latest"); + n("steps", () { + n("step", { "uses": "actions/checkout@v1" }); + n("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { + n("profile", "minimal"); + n("toolchain", "stable"); + n("components", "rustfmt"); + n("override", true); + }); + n("step", "rustfmt", { "run": "cargo fmt --all -- --check" }); + n("step", "docs", { "run": "cargo doc --no-deps" }); + }); + }); + n("build_and_test", "Build & Test", () { + n("runs-on", r"${{ matrix.os }}"); + n("strategy", () { + n("matrix", () { + n("rust", "1.46.0", "stable"); + n("os", "ubuntu-latest", "macOS-latest", "windows-latest"); + }); + }); + + n("steps", () { + n("step", { "uses": "actions/checkout@v1" }); + n("step", "Install Rust", { "uses": "actions-rs/toolchain@v1" }, () { + n("profile", "minimal"); + n("toolchain", r"${{ matrix.rust }}"); + n("components", "clippy"); + n("override", true); + }); + n("step", "Clippy", { "run": "cargo clippy --all -- -D warnings" }); + n("step", "Run tests", { "run": "cargo test --all --verbose" }); + }); + }); + }); + }); + + expect(doc, equals(expectedDoc)); + }); + + test('cargo', () async { + var string = await File('./test/v1/kdl-org/examples/Cargo.kdl').readAsString(); + var doc = KdlDocument.parse(string, version: 1); + var expectedDoc = nodes(() { + n("package", () { + n("name", "kdl"); + n("version", "0.0.0"); + n("description", "kat's document language"); + n("authors", "Kat Marchán "); + n("license-file", "LICENSE.md"); + n("edition", "2018"); + }); + n("dependencies", () { + n("nom", "6.0.1"); + n("thiserror", "1.0.22"); + }); + }); + + expect(doc, equals(expectedDoc)); + }); + + test('nuget', () async { + var string = await File('./test/v1/kdl-org/examples/nuget.kdl').readAsString(); + var doc = KdlDocument.parse(string, version: 1); + + expect(doc, isNotNull); + }); + + test('kdl-schema', () async { + var string = await File('./test/v1/kdl-org/examples/kdl-schema.kdl').readAsString(); + var doc = KdlDocument.parse(string, version: 1); + + expect(doc, isNotNull); + }); + + test('website', () async { + var string = await File('./test/v1/kdl-org/examples/website.kdl').readAsString(); + var doc = KdlDocument.parse(string, version: 1); + + expect(doc, isNotNull); + }); +} diff --git a/test/v1/kdl-org b/test/v1/kdl-org new file mode 160000 index 0000000..ef93a6b --- /dev/null +++ b/test/v1/kdl-org @@ -0,0 +1 @@ +Subproject commit ef93a6b10c4e16d94194280bb6687661d7024476 diff --git a/test/v1/parser_test.dart b/test/v1/parser_test.dart new file mode 100644 index 0000000..cbc2a44 --- /dev/null +++ b/test/v1/parser_test.dart @@ -0,0 +1,505 @@ +import 'package:test/test.dart'; + +import 'package:kdl/src/document.dart'; +import 'package:kdl/src/exception.dart'; +import 'package:kdl/src/parser.dart'; + +void main() { + late KdlV1Parser parser; + + setUp(() { + parser = KdlV1Parser(); + }); + + test('parse_empty_string', () { + expect(parser.parse(''), equals(KdlDocument([]))); + expect(parser.parse(' '), equals(KdlDocument([]))); + expect(parser.parse("\n"), equals(KdlDocument([]))); + }); + + test('nodes', () { + expect(parser.parse('node'), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node\n"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("\nnode\n"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node1\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + }); + + test('node', () { + expect(parser.parse('node;'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node 1'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse('node 1 2 "3" true false null'), + equals(KdlDocument([ + KdlNode('node', arguments: [ + KdlInt(1), + KdlInt(2), + KdlString("3"), + KdlBool(true), + KdlBool(false), + KdlNull() + ]) + ]))); + expect( + parser.parse("node {\n node2\n}"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + expect( + parser.parse("node { node2; }"), + equals(KdlDocument([ + KdlNode('node', children: [KdlNode('node2')]) + ]))); + }); + + test('node_slashdash_comment', () { + expect(parser.parse('/-node'), equals(KdlDocument([]))); + expect(parser.parse('/- node'), equals(KdlDocument([]))); + expect(parser.parse("/- node\n"), equals(KdlDocument([]))); + expect(parser.parse('/-node 1 2 3'), equals(KdlDocument([]))); + expect(parser.parse('/-node key=false'), equals(KdlDocument([]))); + expect(parser.parse("/-node{\nnode\n}"), equals(KdlDocument([]))); + expect(parser.parse("/-node 1 2 3 key=\"value\" \\\n{\nnode\n}"), + equals(KdlDocument([]))); + }); + + test('arg_slashdash_comment', () { + expect(parser.parse('node /-1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node /-1 2'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(2)]) + ]))); + expect( + parser.parse('node 1 /- 2 3'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1), KdlInt(3)]) + ]))); + expect(parser.parse('node /--1'), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse('node /- -1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse("node \\\n/- -1"), equals(KdlDocument([KdlNode('node')]))); + }); + + test('prop_slashdash_comment', () { + expect( + parser.parse('node /-key=1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node /- key=1'), equals(KdlDocument([KdlNode('node')]))); + expect( + parser.parse('node key=1 /-key2=2'), + equals(KdlDocument([ + KdlNode('node', properties: {'key': KdlInt(1)}) + ]))); + }); + + test('children_slashdash_comment', () { + expect(parser.parse('node /-{}'), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse('node /- {}'), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("node /-{\nnode2\n}"), + equals(KdlDocument([KdlNode('node')]))); + }); + + test('string', () { + expect( + parser.parse('node ""'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("")]) + ]))); + expect( + parser.parse('node "hello"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("hello")]) + ]))); + expect( + parser.parse(r'node "hello\nworld"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("hello\nworld")]) + ]))); + expect( + parser.parse(r'node "\u{10FFF}"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\u{10FFF}")]) + ]))); + expect( + parser.parse(r'node "\"\\\/\b\f\n\r\t"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\"\\/\u{08}\u{0C}\n\r\t")]) + ]))); + expect( + parser.parse(r'node "\u{10}"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString("\u{10}")]) + ]))); + expect(() { + parser.parse(r'node "\i"'); + }, throwsA(anything)); + expect(() { + parser.parse(r'node "\u{c0ffee}"'); + }, throwsA(anything)); + }); + + test('float', () { + expect( + parser.parse('node 1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0)]) + ]))); + expect( + parser.parse('node 0.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(0.0)]) + ]))); + expect( + parser.parse('node -1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(-1.0)]) + ]))); + expect( + parser.parse('node +1.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0)]) + ]))); + expect( + parser.parse('node 1.0e10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0e10)]) + ]))); + expect( + parser.parse('node 1.0e-10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(1.0e-10)]) + ]))); + expect( + parser.parse('node 123_456_789.0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(123456789.0)]) + ]))); + expect( + parser.parse('node 123_456_789.0_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBigDecimal.from(123456789.0)]) + ]))); + expect(() { + parser.parse('node ?1.0'); + }, throwsA(anything)); + expect(() { + parser.parse('node _1.0'); + }, throwsA(anything)); + expect(() { + parser.parse('node 1._0'); + }, throwsA(anything)); + expect(() { + parser.parse('node 1.'); + }, throwsA(anything)); + expect(() { + parser.parse('node .0'); + }, throwsA(anything)); + }); + + test('integer', () { + expect( + parser.parse('node 0'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0)]) + ]))); + expect( + parser.parse('node 0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node 0123_456_789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node 0123_456_789_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node +0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(123456789)]) + ]))); + expect( + parser.parse('node -0123456789'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(-123456789)]) + ]))); + expect(() { + parser.parse('node ?0123456789'); + }, throwsA(anything)); + expect(() { + parser.parse('node _0123456789'); + }, throwsA(anything)); + expect(() { + parser.parse('node a'); + }, throwsA(anything)); + expect(() { + parser.parse('node --'); + }, throwsA(anything)); + }); + + test('hexadecimal', () { + expect( + parser.parse('node 0x0123456789abcdef'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect( + parser.parse('node 0x01234567_89abcdef'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect( + parser.parse('node 0x01234567_89abcdef_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(0x0123456789abcdef)]) + ]))); + expect(() { + parser.parse('node 0x_123'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0xg'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0xx'); + }, throwsA(anything)); + }); + + test('octal', () { + expect( + parser.parse('node 0o01234567'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect( + parser.parse('node 0o0123_4567'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect( + parser.parse('node 0o01234567_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(342391)]) + ]))); + expect(() { + parser.parse('node 0o_123'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0o8'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0oo'); + }, throwsA(anything)); + }); + + test('binary', () { + expect( + parser.parse('node 0b0101'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(5)]) + ]))); + expect( + parser.parse('node 0b01_10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect( + parser.parse('node 0b01___10'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect( + parser.parse('node 0b0110_'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(6)]) + ]))); + expect(() { + parser.parse('node 0b_0110'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0b20'); + }, throwsA(anything)); + expect(() { + parser.parse('node 0bb'); + }, throwsA(anything)); + }); + + test('raw_string', () { + expect( + parser.parse(r'node r"foo"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node r"foo\nbar"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString(r'foo\nbar')]) + ]))); + expect( + parser.parse(r'node r#"foo"#'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node r##"foo"##'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString('foo')]) + ]))); + expect( + parser.parse(r'node r"\nfoo\r"'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlString(r'\nfoo\r')]) + ]))); + expect(() { + parser.parse('node r##"foo"#'); + }, throwsA(anything)); + }); + + test('boolean', () { + expect( + parser.parse('node true'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBool(true)]) + ]))); + expect( + parser.parse('node false'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlBool(false)]) + ]))); + }); + + test('node_space', () { + expect( + parser.parse('node 1'), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse("node\t1"), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + expect( + parser.parse("node\t \\ // hello\n 1"), + equals(KdlDocument([ + KdlNode('node', arguments: [KdlInt(1)]) + ]))); + }); + + test('single_line_comment', () { + expect(parser.parse('//hello'), equals(KdlDocument([]))); + expect(parser.parse("// \thello"), equals(KdlDocument([]))); + expect(parser.parse("//hello\n"), equals(KdlDocument([]))); + expect(parser.parse("//hello\r\n"), equals(KdlDocument([]))); + expect(parser.parse("//hello\n\r"), equals(KdlDocument([]))); + expect(parser.parse("//hello\rworld"), + equals(KdlDocument([KdlNode('world')]))); + expect(parser.parse("//hello\nworld\r\n"), + equals(KdlDocument([KdlNode('world')]))); + }); + + test('multi_line_comment', () { + expect(parser.parse("/*hello*/"), equals(KdlDocument([]))); + expect(parser.parse("/*hello*/\n"), equals(KdlDocument([]))); + expect(parser.parse("/*\nhello\r\n*/"), equals(KdlDocument([]))); + expect(parser.parse("/*\nhello** /\n*/"), equals(KdlDocument([]))); + expect(parser.parse("/**\nhello** /\n*/"), equals(KdlDocument([]))); + expect(parser.parse('/*hello*/world'), + equals(KdlDocument([KdlNode('world')]))); + }); + + test('escline', () { + expect( + parser.parse("foo\\\n 1"), + equals(KdlDocument([ + KdlNode('foo', arguments: [KdlInt(1)]) + ]))); + expect(() { + parser.parse("node\\\nnode2"); + }, throwsA(anything)); + }); + + test('whitespace', () { + expect(parser.parse(" node"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("\tnode"), equals(KdlDocument([KdlNode('node')]))); + expect(parser.parse("/* \nfoo\r\n */ etc"), + equals(KdlDocument([KdlNode('etc')]))); + }); + + test('newline', () { + expect(parser.parse("node1\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\rnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\r\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + expect(parser.parse("node1\n\nnode2"), + equals(KdlDocument([KdlNode('node1'), KdlNode('node2')]))); + }); + + test('comments', () { + var doc = parser.parse(""" + // C style + + /* + C style multiline + */ + + tag /*foo=true*/ bar=false + + /*/* + hello + */*/ + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('tag', properties: {'bar': KdlBool(false)}) + ]); + expect(doc, equals(nodes)); + }); + + test('utf8', () { + var doc = parser.parse(""" + smile "😁" + ノード お名前="☜(゚ヮ゚☜)" + """ + .trim()); + var nodes = KdlDocument([ + KdlNode('smile', arguments: [KdlString('😁')]), + KdlNode('ノード', properties: {'お名前': KdlString('☜(゚ヮ゚☜)')}) + ]); + expect(doc, equals(nodes)); + }); + + test('node_names', () { + var doc = parser.parse(r""" + "!@#$@$%Q#$%~@!40" "1.2.3" "!!!!!"=true + foo123~!@#$%^&*.:'|?+ "weeee" + """ + .trim()); + var nodes = KdlDocument([ + KdlNode(r"!@#$@$%Q#$%~@!40", + arguments: [KdlString("1.2.3")], + properties: {"!!!!!": KdlBool(true)}), + KdlNode(r"foo123~!@#$%^&*.:'|?+", arguments: [KdlString("weeee")]) + ]); + expect(doc, equals(nodes)); + }); + + test('version directive', () { + var doc = parser.parse('/- kdl-version 1\nnode "foo"'); + expect(doc, isNotNull); + + expect(() { + parser.parse('/- kdl-version 2\nnode foo'); + }, throwsA(isA())); + }); +} diff --git a/test/v1/spec_test.dart b/test/v1/spec_test.dart new file mode 100644 index 0000000..08ff987 --- /dev/null +++ b/test/v1/spec_test.dart @@ -0,0 +1,39 @@ +import 'package:test/test.dart'; +import 'dart:io'; +import 'package:path/path.dart' as p; + +import 'package:kdl/src/parser.dart'; +import 'package:kdl/src/exception.dart'; + +void main() { + KdlV1Parser parser = KdlV1Parser(); + + var excludedTests = ['escline_comment_node']; + + var testCasesPath = p.join( + Directory.current.path, 'test', 'v1', 'kdl-org', 'tests', 'test_cases'); + var inputsDir = Directory(p.join(testCasesPath, 'input')); + var expectedPath = p.join(testCasesPath, 'expected_kdl'); + for (var entry in inputsDir.listSync()) { + if (entry is Directory) continue; + var inputFile = (entry as File); + var inputName = p.basenameWithoutExtension(inputFile.path); + if (excludedTests.contains(inputName)) continue; + var expectedFile = File(p.join(expectedPath, "$inputName.kdl")); + if (expectedFile.existsSync()) { + test("$inputName matches expected output", () async { + var input = await inputFile.readAsString(); + var expected = await expectedFile.readAsString(); + expect(parser.parse(input).toString(), + equals(parser.parse(expected).toString())); + }); + } else { + test("$inputName does not parse", () async { + var input = await inputFile.readAsString(); + expect(() { + parser.parse(input); + }, throwsA(isA())); + }); + } + } +} diff --git a/test/v1/tokenizer_test.dart b/test/v1/tokenizer_test.dart new file mode 100644 index 0000000..7d785ce --- /dev/null +++ b/test/v1/tokenizer_test.dart @@ -0,0 +1,229 @@ +import 'package:test/test.dart'; +import 'package:big_decimal/big_decimal.dart'; + +import 'package:kdl/src/tokenizer.dart'; + +void main() { + test('peek and peek after next', () { + var tokenizer = KdlV1Tokenizer("node 1 2 3"); + + expect(tokenizer.peekToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect(tokenizer.peekTokenAfterNext(), equals(KdlToken(KdlTerm.whitespace, " "))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect(tokenizer.peekToken(), equals(KdlToken(KdlTerm.whitespace, " "))); + expect(tokenizer.peekTokenAfterNext(), equals(KdlToken(KdlTerm.integer, 1))); + }); + + test('identifier', () { + expect(KdlV1Tokenizer("foo").nextToken(), equals(KdlToken(KdlTerm.ident, "foo"))); + expect(KdlV1Tokenizer("foo-bar123").nextToken(), equals(KdlToken(KdlTerm.ident, "foo-bar123"))); + }); + + test('string', () { + expect(KdlV1Tokenizer('"foo"').nextToken(), equals(KdlToken(KdlTerm.string, "foo"))); + expect(KdlV1Tokenizer(r'"foo\nbar"').nextToken(), equals(KdlToken(KdlTerm.string, "foo\nbar"))); + expect(KdlV1Tokenizer(r'"\u{10FFF}"').nextToken(), equals(KdlToken(KdlTerm.string, "\u{10FFF}"))); + }); + + test('rawstring', () { + expect(KdlV1Tokenizer('r"foo\\nbar"').nextToken(), equals(KdlToken(KdlTerm.rawstring, "foo\\nbar"))); + expect(KdlV1Tokenizer('r#"foo"bar"#').nextToken(), equals(KdlToken(KdlTerm.rawstring, "foo\"bar"))); + expect(KdlV1Tokenizer('r##"foo"#bar"##').nextToken(), equals(KdlToken(KdlTerm.rawstring, "foo\"#bar"))); + expect(KdlV1Tokenizer('r#""foo""#').nextToken(), equals(KdlToken(KdlTerm.rawstring, "\"foo\""))); + + var tokenizer = KdlV1Tokenizer('node r"C:\\Users\\zkat\\"'); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "node"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, " "))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rawstring, "C:\\Users\\zkat\\"))); + + tokenizer = KdlV1Tokenizer('other-node r#"hello"world"#'); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, "other-node"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, " "))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rawstring, "hello\"world"))); + }); + + test('integer', () { + expect(KdlV1Tokenizer("123").nextToken(), equals(KdlToken(KdlTerm.integer, 123))); + expect(KdlV1Tokenizer("0x0123456789abcdef").nextToken(), equals(KdlToken(KdlTerm.integer, 0x0123456789abcdef))); + expect(KdlV1Tokenizer("0o01234567").nextToken(), equals(KdlToken(KdlTerm.integer, 342391))); + expect(KdlV1Tokenizer("0b101001").nextToken(), equals(KdlToken(KdlTerm.integer, 41))); + expect(KdlV1Tokenizer("-0x0123456789abcdef").nextToken(), equals(KdlToken(KdlTerm.integer, -0x0123456789abcdef))); + expect(KdlV1Tokenizer("-0o01234567").nextToken(), equals(KdlToken(KdlTerm.integer, -342391))); + expect(KdlV1Tokenizer("-0b101001").nextToken(), equals(KdlToken(KdlTerm.integer, -41))); + expect(KdlV1Tokenizer("+0x0123456789abcdef").nextToken(), equals(KdlToken(KdlTerm.integer, 0x0123456789abcdef))); + expect(KdlV1Tokenizer("+0o01234567").nextToken(), equals(KdlToken(KdlTerm.integer, 342391))); + expect(KdlV1Tokenizer("+0b101001").nextToken(), equals(KdlToken(KdlTerm.integer, 41))); + }); + + test('float', () { + expect(KdlV1Tokenizer("1.23").nextToken(), equals(KdlToken(KdlTerm.decimal, BigDecimal.parse('1.23')))); + }); + + test('boolean', () { + expect(KdlV1Tokenizer("true").nextToken(), equals(KdlToken(KdlTerm.trueKeyword, true))); + expect(KdlV1Tokenizer("false").nextToken(), equals(KdlToken(KdlTerm.falseKeyword, false))); + }); + + test('null', () { + expect(KdlV1Tokenizer("null").nextToken(), equals(KdlToken(KdlTerm.nullKeyword, null))); + }); + + test('symbols', () { + expect(KdlV1Tokenizer("{").nextToken(), equals(KdlToken(KdlTerm.lbrace, '{'))); + expect(KdlV1Tokenizer("}").nextToken(), equals(KdlToken(KdlTerm.rbrace, '}'))); + expect(KdlV1Tokenizer("=").nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + }); + + test('whitespace', () { + expect(KdlV1Tokenizer(" ").nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(KdlV1Tokenizer("\t").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\t"))); + expect(KdlV1Tokenizer(" \t").nextToken(), equals(KdlToken(KdlTerm.whitespace, " \t"))); + expect(KdlV1Tokenizer("\\\n").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\\\n"))); + expect(KdlV1Tokenizer("\\").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\\"))); + expect(KdlV1Tokenizer("\\//some comment\n").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\\\n"))); + expect(KdlV1Tokenizer("\\ //some comment\n").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\\ \n"))); + expect(KdlV1Tokenizer("\\//some comment").nextToken(), equals(KdlToken(KdlTerm.whitespace, "\\"))); + }); + + test('multiple_tokens', () { + var tokenizer = KdlV1Tokenizer("node 1 \"two\" a=3"); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 1))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, 'two'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'a'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 3))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('single_line_comment', () { + expect(KdlV1Tokenizer("// comment").nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + + var tokenizer = KdlV1Tokenizer(""" +node1 +// comment +node2 + """.trim()); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node1'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node2'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('multiline_comment', () { + var tokenizer = KdlV1Tokenizer("foo /*bar=1*/ baz=2"); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'baz'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 2))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('utf8', () { + expect(KdlV1Tokenizer("😁").nextToken(), equals(KdlToken(KdlTerm.ident, '😁'))); + expect(KdlV1Tokenizer('"😁"').nextToken(), equals(KdlToken(KdlTerm.string, '😁'))); + expect(KdlV1Tokenizer('ノード').nextToken(), equals(KdlToken(KdlTerm.ident, 'ノード'))); + expect(KdlV1Tokenizer('お名前').nextToken(), equals(KdlToken(KdlTerm.ident, 'お名前'))); + expect(KdlV1Tokenizer('"☜(゚ヮ゚☜)"').nextToken(), equals(KdlToken(KdlTerm.string, '☜(゚ヮ゚☜)'))); + + var tokenizer = KdlV1Tokenizer(""" +smile "😁" +ノード お名前="☜(゚ヮ゚☜)" + """.trim()); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'smile'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, '😁'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'ノード'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'お名前'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, '☜(゚ヮ゚☜)'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('semicolon', () { + var tokenizer = KdlV1Tokenizer('node1; node2'); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node1'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.semicolon, ';'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'node2'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('slash_dash', () { + var tokenizer = KdlV1Tokenizer(""" +/-mynode /-"foo" /-key=1 /-{ + a +} + """.trim()); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.slashdash, '/-'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'mynode'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.slashdash, '/-'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.slashdash, '/-'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'key'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.equals, '='))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.integer, 1))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.slashdash, '/-'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lbrace, '{'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'a'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.newline, "\n"))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rbrace, '}'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('multiline_nodes', () { + var tokenizer = KdlV1Tokenizer(""" +title \\ + "Some title" + """.trim()); + + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'title'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.whitespace, ' \\\n '))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.string, 'Some title'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, ''))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.eof, null))); + }); + + test('types', () { + var tokenizer = KdlV1Tokenizer("(foo)bar"); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rparen, ')'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'bar'))); + + tokenizer = KdlV1Tokenizer("(foo)/*asdf*/bar"); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.rparen, ')'))); + expect(() => tokenizer.nextToken(), throwsA(anything)); + + tokenizer = KdlV1Tokenizer("(foo/*asdf*/)bar"); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.lparen, '('))); + expect(tokenizer.nextToken(), equals(KdlToken(KdlTerm.ident, 'foo'))); + expect(() => tokenizer.nextToken(), throwsA(anything)); + }); +} diff --git a/test/value_test.dart b/test/value_test.dart new file mode 100644 index 0000000..9a9a78c --- /dev/null +++ b/test/value_test.dart @@ -0,0 +1,20 @@ +import 'package:test/test.dart'; + +import 'package:kdl/src/document.dart'; + +void main() { + test('equals', () { + expect(KdlInt(42), equals(KdlInt(42))); + expect(KdlDouble(3.14), equals(KdlDouble(3.14))); + expect(KdlDouble(double.nan), equals(KdlDouble(double.nan))); + expect(KdlBool(true), equals(KdlBool(true))); + expect(KdlNull, equals(KdlNull)); + expect(KdlString("lorem"), equals(KdlString("lorem"))); + + expect(KdlInt(42), isNot(equals(KdlInt(69)))); + expect(KdlDouble(3.14), isNot(equals(KdlDouble(6.28)))); + expect(KdlBool(true), isNot(equals(KdlBool(false)))); + expect(KdlNull, isNot(equals(7))); + expect(KdlString("lorem"), isNot(equals(KdlString("ipsum")))); + }); +}