Browse Source

* Add YAML parser & converter to JSON

Michaël Van Canneyt 7 months ago
parent
commit
52d1a5da94

+ 2 - 0
packages/fcl-yaml/Makefile

@@ -0,0 +1,2 @@
+PACKAGE_NAME=fcl-yaml
+include ../build/Makefile.pkg

+ 90 - 0
packages/fcl-yaml/examples/dumpyaml.lpi

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="dumpyaml"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="dumpyaml.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.data.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.json.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.parser.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.reader.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.scanner.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.strings.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.types.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="dumpyaml"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="../src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 151 - 0
packages/fcl-yaml/examples/dumpyaml.pp

@@ -0,0 +1,151 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML Parser & Dumping data demo
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+program dumpyaml;
+
+uses sysutils, fpyaml.types, fpyaml.data, fpyaml.parser;
+
+var
+  minimal : Boolean;
+
+Procedure DumpYAMLValue(Y : TYAMLData; aPrefix : String); forward;
+
+function YAMLToString(Y : TYAMLData) : String;
+
+begin
+  if Minimal then
+    begin
+    if (Y is TYAMLScalar) then
+      Result:=Y.AsString
+    else
+      Result:=Y.ClassName;
+    if TYAMLTagType.FromString(Y.Tag)=yttString then
+      Result:='"'+Result+'"';
+    end
+  else
+    if (Y is TYAMLScalar) then
+      Result:=Format('scalar<%s> (%s)',[Y.AsString,Y.Tag])
+    else
+      Result:=Format('%s<%s>',[Y.ClassName,Y.Tag]);
+end;
+
+
+Procedure DumpYAMLMapping(Y : TYAMLMapping; aPrefix : String);
+
+Var
+  I : Integer;
+  lKey : String;
+begin
+  if Minimal then
+    Writeln('{')
+  else
+    Writeln('Mapping {');
+  For I:=0 to Y.Count-1 do
+    begin
+    lKey:=YamlToString(Y.Key[I]);
+    if not Minimal then
+      lKey:=format('Key[%d] %s',[i,lKey]);
+    Write(aPrefix+'  ',lKey+': ');
+    DumpYAMLValue(Y[i],'  '+aPrefix);
+    end;
+  Writeln(aPrefix,'}');
+end;
+
+Procedure DumpYAMLSequence(Y : TYAMLSequence; aPrefix : String);
+
+Var
+  I : Integer;
+
+begin
+  if Minimal then
+    Writeln('[')
+  else
+    Writeln('Sequence [');
+  For I:=0 to Y.Count-1 do
+    begin
+    Write(aPrefix+'  ');
+    if Minimal then
+      Write('- ' )
+    else
+      Write(format('Item[%d] :',[i]));
+    DumpYAMLValue(Y[i],'  '+aPrefix);
+    end;
+  Writeln(aPrefix,']');
+end;
+
+
+Procedure DumpYAMLValue(Y : TYAMLData; aPrefix : String);
+
+begin
+  if (Y is TYAMLMapping) then
+    DumpYAMLMapping(TYAMLMapping(Y),aPrefix)
+  else if (Y is TYAMLSequence) then
+    DumpYAMLSequence(TYAMLSequence(Y),aPrefix)
+  else
+    Writeln(YAMLToString(Y));
+end;
+
+Procedure DumpDocument(Y : TYAMLDocument);
+var
+  I : Integer;
+
+begin
+  For I:=0 to Y.Count-1 do
+    DumpYAMLValue(Y[i],'');
+end;
+
+Procedure DumpYAML(Yaml : TYAMLStream);
+
+var
+  Y : TYAMLData;
+  I : Integer;
+
+begin
+  Writeln('YAML Stream with ',YAML.Count,' items');
+  For I:=0 to YAML.Count-1 do
+    begin
+    Y:=YAML[I];
+    if Y is TYAMLDocument then
+      begin
+      Writeln('Document ',I,':');
+      DumpDocument(TYAMLDocument(Y));
+      end
+    else
+      Writeln('Item ',I,' : ',Y.ClassName,' : ',YAMLToString(Y));
+    end;
+end;
+
+var
+  YAML: TYAMLStream;
+  P : TYAMLParser;
+
+begin
+  MiniMal:=ParamStr(1)='-m';
+  if (ParamStr(1+Ord(Minimal))='') or (ParamStr(1)='-h') then
+    begin
+    Writeln('Usage : ',ExtractFileName(ParamStr(0)),' [-m] inputfile');
+    Writeln('-m : output minimal structural info');
+    Halt(Ord(ParamStr(1)<>'-h'));
+    end;
+  YAML:=Nil;
+  P:=TYAMLParser.Create(Paramstr(1+Ord(Minimal)));
+  try
+    YAML:=P.Parse;
+    DumpYaml(YAML);
+  finally
+    YAML.Free;
+    P.Free;
+  end;
+end.
+

+ 84 - 0
packages/fcl-yaml/examples/yaml2json.lpi

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="yaml2json"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="yaml2json.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.data.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.json.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.parser.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.reader.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.scanner.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.strings.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.types.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="yaml2json"/>
+    </Target>
+    <SearchPaths>
+      <OtherUnitFiles Value="../src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 53 - 0
packages/fcl-yaml/examples/yaml2json.pp

@@ -0,0 +1,53 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML-To-JSON conversion demo.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+{$mode objfpc}
+{$h+}
+program yaml2json;
+
+uses sysutils, fpyaml.data, fpyaml.parser, fpyaml.json, fpjson;
+
+var
+  YAML : TYAMLStream;
+  JSON : TJSONData;
+  MiniMal : Boolean;
+
+begin
+  JSON:=Nil;
+  YAML:=nil;
+  Minimal:=ParamStr(1)='-m';
+  if (Paramstr(1+Ord(Minimal))='') or (ParamStr(1)='-h') then
+    begin
+    Writeln('Usage: ',ExtractFilePath(ParamStr(0)),' [-m] inputfile');
+    Writeln('-m : minimal output. Default is to format output.');
+    Halt(Ord(ParamStr(1)<>'-h'));
+    end;
+  With TYAMLParser.Create(Paramstr(1+Ord(Minimal))) do
+    try
+      YAML:=Parse;
+    finally
+      Free;
+    end;
+  try
+    JSON:=YAMLToJSON(YAML);
+    if Minimal then
+      Writeln(JSON.AsJSON)
+    else
+      Writeln(JSON.FormatJSON());
+  finally
+    YAML.Free;
+    JSON.Free;
+  end;
+end.

+ 82 - 0
packages/fcl-yaml/fpmake.pp

@@ -0,0 +1,82 @@
+{$ifndef ALLPACKAGES}
+{$mode objfpc}{$H+}
+program fpmake;
+
+uses {$ifdef unix}cthreads,{$endif} fpmkunit;
+
+Var
+  T : TTarget;
+  P : TPackage;
+begin
+  With Installer do
+    begin
+{$endif ALLPACKAGES}
+
+    P:=AddPackage('fcl-yaml');
+    P.ShortName:='openapi';
+{$ifdef ALLPACKAGES}
+    P.Directory:=ADirectory;
+{$endif ALLPACKAGES}
+    P.Version:='3.3.1';
+    P.Dependencies.Add('fcl-base');
+    P.Dependencies.Add('rtl-objpas');
+    P.Dependencies.Add('fcl-fpcunit');
+    P.Dependencies.Add('fcl-json');
+    P.Author := 'Michael van Canneyt';
+    P.License := 'LGPL with modification, ';
+    P.HomepageURL := 'www.freepascal.org';
+    P.Email := '';
+    P.Description := 'YAML file reader and data structures. YAML to JSON converter.';
+    P.NeedLibC:= false;
+    P.OSes:=AllOSes-[embedded,msdos,win16,macosclassic,palmos,zxspectrum,msxdos,amstradcpc,sinclairql,human68k,ps1];
+    if Defaults.CPU=jvm then
+      P.OSes := P.OSes - [java,android];
+
+    P.SourcePath.Add('src');
+
+    T:=P.Targets.AddUnit('fpyaml.strings.pp');
+//    T.ResourceStrings:=true;
+    
+    T:=P.Targets.AddUnit('fpyaml.types.pp');
+    with T.Dependencies do
+      AddUnit('fpyaml.strings');
+
+    T:=P.Targets.AddUnit('fpyaml.data.pp');
+    with T.Dependencies do
+      begin
+      AddUnit('fpyaml.strings');
+      AddUnit('fpyaml.types');
+      end;
+
+    T:=P.Targets.AddUnit('fpyaml.scanner.pp');
+    with T.Dependencies do
+      begin
+      AddUnit('fpyaml.strings');
+      AddUnit('fpyaml.types');
+      AddUnit('fpyaml.data');
+      end;
+    T:=P.Targets.AddUnit('fpyaml.parser.pp');
+    with T.Dependencies do
+      begin
+      AddUnit('fpyaml.strings');
+      AddUnit('fpyaml.types');
+      AddUnit('fpyaml.data');
+      AddUnit('fpyaml.scanner');
+      end;
+
+   T:=P.Targets.AddUnit('fpyaml.json.pp');
+   with T.Dependencies do
+     begin
+     AddUnit('fpyaml.strings');
+     AddUnit('fpyaml.types');
+     AddUnit('fpyaml.data');
+     end;
+
+{$ifndef ALLPACKAGES}
+    Run;
+    end;
+end.
+{$endif ALLPACKAGES}
+
+
+

+ 1271 - 0
packages/fcl-yaml/src/fpyaml.data.pp

@@ -0,0 +1,1271 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML data classes
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpyaml.data;
+
+{$mode ObjFPC}{$H+}
+{$modeswitch advancedrecords}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses System.SysUtils, System.DateUtils, System.Classes, System.Contnrs, fpyaml.types;
+{$ELSE}
+uses SysUtils, DateUtils, Classes, Contnrs, fpyaml.types, fpjson;
+{$ENDIF}
+
+
+Type
+
+  TYAMLMapItem = Class;
+
+  TYAMLEnumerator = Class
+    function GetCurrent: TYAMLMapItem; virtual; abstract;
+    function MoveNext : Boolean; virtual; abstract;
+    property Current: TYAMLMapItem read GetCurrent;
+  end;
+
+  { TYAMLData }
+
+  TYAMLData = class
+  private
+    FAnchor: TYAMLString;
+    FTag: TYAMLString;
+  Protected
+    function GetItem(aIndex : Integer): TYAMLData; virtual;
+    function GetItemCount: Integer; virtual;
+    function GetAsBoolean: Boolean; virtual;
+    function GetAsDouble: Double; virtual;
+    function GetAsInt64: Int64; virtual;
+    function GetAsInteger: Integer; virtual;
+    function GetIsNull: Boolean; virtual;
+    function GetAsString: TYAMLString; virtual;
+    function GetAsDateTime: TDateTime; virtual;
+    class function allowAnchor : Boolean; virtual;
+    class function allowTag : Boolean; virtual;
+    class function DefaultTag : TYAMLString; virtual;
+  Public
+    constructor create(); virtual;
+    constructor create(const aAnchor : TYAMLString; const aTag : TYAMLString = '');
+    Function Clone : TYAMLData; virtual; abstract;
+    Function Equals(aData : TYAMLData; aStrict : Boolean = False) : Boolean; overload;virtual;
+    Function GetEnumerator: TYAMLEnumerator; virtual; abstract;
+    Property Anchor : TYAMLString Read FAnchor;
+    Property Tag : TYAMLString Read FTag;
+    Property Items[aIndex : Integer] : TYAMLData Read GetItem; default;
+    Property Count : Integer Read GetItemCount;
+    Property AsString : TYAMLString Read GetAsString;
+    Property AsDouble : Double Read GetAsDouble;
+    Property AsInteger : Integer Read GetAsInteger;
+    Property AsInt64 : Int64 Read GetAsInt64;
+    Property AsBoolean : Boolean Read GetAsBoolean;
+    Property AsDateTime : TDateTime Read GetAsDateTime;
+    Property IsNull : Boolean Read GetIsNull;
+  end;
+  TYAMLDataClass = Class of TYAMLData;
+
+  { TYAMLTagData }
+
+  TYAMLTagData = class(TYAMLData)
+  private
+    FData: TYAMLTag;
+  type
+
+    { TYAMLTagDataEnumerator }
+
+    TYAMLTagDataEnumerator = Class(TYAMLEnumerator)
+      FOld,
+      FCurrent : TYAMLMapItem;
+      Destructor Destroy; override;
+      function GetCurrent: TYAMLMapItem; override;
+      function MoveNext : Boolean; override;
+      Constructor Create(aScalar : TYAMLTagData);
+    end;
+  Public
+    function Clone : TYAMLData; override;
+    function GetEnumerator: TYAMLEnumerator; override;
+    Property Data : TYAMLTag Read FData Write FData;
+  end;
+
+  { TYAMLScalar }
+
+  TYAMLScalar = Class(TYAMLData)
+  protected
+    function GetAsBoolean: Boolean; override;
+    function GetAsDouble: Double; override;
+    function GetAsInt64: Int64; override;
+    function GetAsInteger: Integer; override;
+    function GetAsString: TYAMLString; override;
+    function GetIsNull: Boolean; override;
+    function GetAsDateTime : TDateTime; override;
+    Type
+       TYAMLScalarEnumerator = Class(TYAMLEnumerator)
+         FOld,
+         FCurrent : TYAMLMapItem;
+         Destructor Destroy; override;
+         function GetCurrent: TYAMLMapItem; override;
+         function MoveNext : Boolean; override;
+         Constructor Create(aScalar : TYAMLScalar);
+       end;
+  private
+    FKind: TYAMLScalarKind;
+    FValue: TYAMLString;
+  Public
+    constructor Create(const aValue: TYAMLString; const aTag: TYAMLTagType = yttString; aKind: TYAMLScalarKind = yskPlain);
+    Constructor Create(aValue : Integer; aStyle : TYAMLScalarKind = yskPlain);
+    Constructor Create(aValue : Double; aStyle : TYAMLScalarKind = yskPlain);
+    Constructor Create(aValue : Int64; aStyle : TYAMLScalarKind = yskPlain);
+    Constructor Create(aValue : Boolean; aStyle : TYAMLScalarKind = yskPlain);
+    Constructor CreateDateTime(aValue : TDateTime; aStyle : TYAMLScalarKind = yskPlain);
+    Constructor Create(aStyle : TYAMLScalarKind = yskPlain);
+    Function Clone : TYAMLData; override;
+    Function GetEnumerator: TYAMLEnumerator; override;
+    function Equals(aData: TYAMLData; aStrict: Boolean=False): Boolean; override;
+    Property Value : TYAMLString Read FValue Write FValue;
+    Property Kind : TYAMLScalarKind Read FKind Write FKind;
+  end;
+
+  { TYAMLDataList }
+
+  TYAMLDataList = class(TYAMLData)
+  Private
+    FList : TFPObjectList;
+  Protected
+    Type
+       TYAMLListEnumerator = Class(TYAMLEnumerator)
+         FIndex : Integer;
+         FKey : TYAMLScalar;
+         FList : TYAMLDataList;
+         FItem : TYAMLMapItem;
+         function GetCurrent: TYAMLMapItem; override;
+         function MoveNext : Boolean; override;
+         Constructor Create(aList : TYAMLDataList);
+         destructor destroy; override;
+       end;
+  Protected
+    function GetItem(aIndex : Integer): TYAMLData; override;
+    function GetItemCount: Integer; override;
+  Public
+    constructor Create; override; overload;
+    destructor destroy; override;
+    Function Clone : TYAMLData; override;
+    Function GetEnumerator: TYAMLEnumerator; override;
+    Function Equals(aData : TYAMLData; aStrict : Boolean = False) : Boolean; override;
+    Function Extract(aIndex : Integer): TYAMLData;
+    procedure Delete(aIndex : Integer);
+    Function Add(aData : TYAMLData) : TYAMLData;
+    Function Add(const aData : TYAMLString) : TYAMLData;
+    Function Add(aData : Integer) : TYAMLData;
+    Function Add(aData : Double) : TYAMLData;
+    Function Add(aData : Boolean) : TYAMLData;
+    Function Add(aData : Int64) : TYAMLData;
+  end;
+
+  { TYAMLSequence }
+  TYAMLSequence = Class(TYAMLDataList)
+  private
+    FKind: TYAMLCollectionKind;
+  Protected
+    Class Function DefaultTag: TYAMLString; override;
+  Public
+    Function Clone : TYAMLData; override;
+    Property Kind : TYAMLCollectionKind Read FKind Write FKind;
+  end;
+
+
+  TYAMLMapItem = class
+  Private
+    Fkey,Fvalue : TYAMLData;
+  Public
+    constructor create(aKey,aValue : TYAMLData);
+    destructor destroy; override;
+    Property Key : TYAMLData Read FKey;
+    Property Value : TYAMLData Read FValue;
+  end;
+
+  { TYAMLMapping }
+
+  TYAMLMapping = Class(TYAMLData)
+  Protected
+  type
+      TYAMLMappingEnumerator = Class(TYAMLEnumerator)
+        FIndex : Integer;
+        FList : TYAMLMapping;
+        function GetCurrent: TYAMLMapItem; override;
+        function MoveNext : Boolean; override;
+        Constructor Create(aList : TYAMLMapping);
+      end;
+
+      { TYAMLMappingKeyEnumerator }
+
+      TYAMLMappingKeyEnumerator = Class
+        FIndex : Integer;
+        FList : TYAMLMApping;
+        function GetCurrent: TYAMLData;
+        function MoveNext : Boolean;
+        Constructor Create(aList : TYAMLMapping);
+      end;
+  Private
+    FKind: TYAMLCollectionKind;
+    FList : TFPObjectList;
+    Function IndexOfKey(aKey : TYAMLData): Integer;
+    Function IndexOfKeyValue(aKey : TYAMLData): Integer;
+    function GetByKey(aKey : TYAMLData): TYAMLData;
+    function GetByKeyValue(aKey : TYAMLData): TYAMLData;
+    function GetKey(aIndex : Integer): TYAMLData;
+    function GetMapping(aIndex : Integer): TYAMLMapItem;
+  Protected
+    function GetItem(aIndex : Integer): TYAMLData; override;
+    function GetItemCount: Integer; override;
+    class function DefaultTag : TYAMLString; override;
+    property Mapping[aIndex : Integer] : TYAMLMapItem Read GetMapping;
+  Public
+    constructor Create; override;
+    destructor destroy; override;
+    Procedure Add(aKey,aValue : TYAMLData);
+    Function Equals(aData : TYAMLData; aStrict : Boolean = False) : Boolean; override;
+    Function GetEnumerator: TYAMLEnumerator; override;
+    Procedure Delete(aIndex : Integer);
+    Procedure Delete(aKey : TYAMLData);
+    Procedure Delete(const aKey : TYAMLString);
+    Function Extract(aIndex : Integer) : TYAMLData;
+    Function Extract(aKey : TYAMLData) : TYAMLData;
+    function Extract(const aKey : TYAMLString) : TYAMLData;
+    Function Keys: TYamlMappingKeyEnumerator;
+    Function Clone : TYAMLData; override;
+    Function Add(const aKey : TYAMLString; aData : TYAMLData) : TYAMLData;
+    Function Add(const aKey : TYAMLString; const aData : TYAMLString) : TYAMLData;
+    Function Add(const aKey : TYAMLString; aData : Integer) : TYAMLData;
+    Function Add(const aKey : TYAMLString; aData : Double) : TYAMLData;
+    Function Add(const aKey : TYAMLString; aData : Boolean) : TYAMLData;
+    Function Add(const aKey : TYAMLString; aData : Int64) : TYAMLData;
+    Property Kind : TYAMLCollectionKind Read FKind Write FKind;
+    property Key[aIndex : Integer] : TYAMLData Read GetKey;
+    // Find by pointer
+    property ValueByKey[aKey : TYAMLData] : TYAMLData Read GetByKey;
+    // Find by value.
+    property ValueByKeyValue[aKey : TYAMLData] : TYAMLData Read GetByKeyValue;
+  end;
+
+  { TYAMLGlobalDataList }
+
+  TYAMLGlobalDataList = class(TYAMLDataList)
+    class function allowAnchor : Boolean; override;
+    class function allowTag : Boolean; override;
+  end;
+
+  { TYAMLDocument }
+
+  TYAMLDocument = Class(TYAMLGlobalDataList)
+  Private
+    FVersion: TYAMLVersion;
+  Public
+    function Clone : TYAMLData; override;
+    Property Version : TYAMLVersion read FVersion Write FVersion;
+  end;
+  TYAMLDocumentArray = Array of TYAMLDocument;
+
+  { TYAMLStream }
+
+  TYAMLStream = class(TYAMLGlobalDataList)
+  private
+    function GetDocuments: TYAMLDocumentArray;
+    function GetDocumentCount: Integer;
+  Public
+    Property Documents : TYAMLDocumentArray Read GetDocuments;
+    Property DocumentCount : Integer Read GetDocumentCount;
+  end;
+
+
+var
+  YAMLFormatSettings : TFormatSettings;
+
+implementation
+
+uses fpyaml.strings;
+
+{ TYAMLData }
+
+function TYAMLData.GetAsBoolean: Boolean;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'boolean']);
+  Result:=False; // Avoid compiler warning;
+end;
+
+
+function TYAMLData.GetAsDouble: Double;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'double']);
+  Result:=0.0; // Avoid compiler warning;
+end;
+
+
+function TYAMLData.GetAsInt64: Int64;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'int64']);
+  Result:=0; // Avoid compiler warning;
+end;
+
+
+function TYAMLData.GetAsInteger: Integer;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'integer']);
+  Result:=0; // Avoid compiler warning;
+end;
+
+
+function TYAMLData.GetAsString: TYAMLString;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'TYAMLString']);
+  Result:=''; // Avoid compiler warning;
+end;
+
+
+function TYAMLData.GetAsDateTime: TDateTime;
+
+begin
+  Raise EYAML.CreateFmt(SErrIsNotA,[ClassName,'TDateTime']);
+  Result:=0; // Avoid compiler warning
+end;
+
+
+function TYAMLData.GetIsNull: Boolean;
+
+begin
+  Result:=False;
+end;
+
+
+function TYAMLData.GetItem(aIndex : Integer): TYAMLData;
+
+begin
+  Raise EListError.CreateFmt(SErrIndexOutOfBounds ,[aIndex]);
+  Result:=Nil; // Avoid compiler warning
+end;
+
+
+function TYAMLData.GetItemCount: Integer;
+
+begin
+  Result:=0;
+end;
+
+
+class function TYAMLData.allowAnchor: Boolean;
+
+begin
+  Result:=True;
+end;
+
+
+class function TYAMLData.allowTag: Boolean;
+
+begin
+  Result:=True;
+end;
+
+
+class function TYAMLData.DefaultTag: TYAMLString;
+
+begin
+  Result:='';
+end;
+
+
+constructor TYAMLData.create();
+
+begin
+  if AllowTag then
+    FTag:=DefaultTag;
+end;
+
+
+constructor TYAMLData.create(const aAnchor: TYAMLString; const aTag: TYAMLString);
+
+begin
+  Create;
+  if allowAnchor then
+    FAnchor:=aAnchor;
+  if allowTag then
+    FTag:=aTag;
+end;
+
+
+function TYAMLData.Equals(aData: TYAMLData; aStrict: Boolean): Boolean;
+
+begin
+  Result:=(aData.Anchor=Anchor) and (aData.Tag=Tag);
+  if aStrict then; // Avoid compiler warning
+end;
+
+{ TYAMLTagData }
+
+function TYAMLTagData.Clone: TYAMLData;
+
+begin
+  Result:=TYAMLTagData(Self.ClassType).create('',Self.Tag);
+  TYAMLTagData(Result).Data:=Self.Data;
+end;
+
+
+function TYAMLTagData.GetEnumerator: TYAMLEnumerator;
+
+begin
+  Result:=TYAMLTagDataEnumerator.Create(Self);
+end;
+
+
+{ TYAMLTagData.TYAMLTagDataEnumerator }
+
+destructor TYAMLTagData.TYAMLTagDataEnumerator.Destroy;
+
+begin
+  MoveNext; // Make sure value is in old
+  FOld.FValue:=nil;
+  FreeAndNil(FOld);
+end;
+
+
+function TYAMLTagData.TYAMLTagDataEnumerator.GetCurrent: TYAMLMapItem;
+
+begin
+  Result:=FOld;
+end;
+
+
+function TYAMLTagData.TYAMLTagDataEnumerator.MoveNext: Boolean;
+
+begin
+  Result:=Assigned(FCurrent);
+  if Result then
+    begin
+    FOld:=FCurrent;
+    FCurrent:=Nil;
+    end;
+end;
+
+
+constructor TYAMLTagData.TYAMLTagDataEnumerator.Create(aScalar: TYAMLTagData);
+
+begin
+  FCurrent:=TYAMLMapItem.Create(TYAMLScalar.Create('',yttCustom,yskPlain),aScalar);
+end;
+
+
+{ TYAMLScalar }
+
+function TYAMLScalar.GetAsBoolean: Boolean;
+
+begin
+  Result:=StrToBool(Value);
+end;
+
+
+function TYAMLScalar.GetAsDouble: Double;
+
+begin
+  Result:=StrToFloat(Value,YAMLFormatSettings);
+end;
+
+
+function TYAMLScalar.GetAsInt64: Int64;
+
+begin
+  Result:=StrToInt64(Value);
+end;
+
+
+function TYAMLScalar.GetAsInteger: Integer;
+
+begin
+  Result:=StrToInt(Value);
+end;
+
+
+function TYAMLScalar.GetAsString: TYAMLString;
+
+begin
+  Result:=Value;
+end;
+
+
+function TYAMLScalar.GetIsNull: Boolean;
+
+begin
+  Result:=(Value='') and (Tag=YAMLTagNames[yttNull]);
+end;
+
+
+function TYAMLScalar.GetAsDateTime: TDateTime;
+
+begin
+  Result:=ISO8601ToDate(Value);
+end;
+
+
+constructor TYAMLScalar.Create(const aValue: TYAMLString; const aTag: TYAMLTagType; aKind: TYAMLScalarKind);
+
+begin
+  Inherited Create('',YAMLTagNames[aTag]);
+  FValue:=aValue;
+  FKind:=aKind;
+end;
+
+
+constructor TYAMLScalar.Create(aValue: Integer; aStyle: TYAMLScalarKind);
+
+begin
+  Create(IntToStr(aValue),yttInteger,aStyle);
+end;
+
+
+constructor TYAMLScalar.Create(aValue: Double; aStyle: TYAMLScalarKind);
+
+begin
+  Create(FloatToStr(aValue,YAMLFormatSettings),yttFloat,aStyle);
+end;
+
+
+constructor TYAMLScalar.Create(aValue: Int64; aStyle: TYAMLScalarKind);
+
+begin
+  Create(IntToStr(aValue),yttInteger,aStyle);
+end;
+
+
+constructor TYAMLScalar.Create(aValue: Boolean; aStyle: TYAMLScalarKind);
+
+const
+  BoolStrs : Array[Boolean] of TYAMLString = ('false','true');
+
+begin
+  Create(BoolStrs[aValue],yttBoolean,aStyle);
+end;
+
+
+constructor TYAMLScalar.CreateDateTime(aValue: TDateTime; aStyle: TYAMLScalarKind);
+
+begin
+  Create(DateToISO8601(aValue),yttTimeStamp,AStyle)
+end;
+
+
+constructor TYAMLScalar.Create(aStyle: TYAMLScalarKind);
+
+begin
+  Create('',yttNull,aStyle);
+end;
+
+
+function TYAMLScalar.Clone: TYAMLData;
+
+var
+  aScalar : TYAMLScalar absolute result;
+
+begin
+  Result:=TYAMLDataClass(Self.ClassType).Create('',Self.Tag);
+  aScalar.Value:=Self.Value;
+  aScalar.Kind:=Self.Kind;
+end;
+
+
+function TYAMLScalar.GetEnumerator: TYAMLEnumerator;
+
+begin
+  Result:=TYAMLScalarEnumerator.Create(Self);
+end;
+
+
+function TYAMLScalar.Equals(aData: TYAMLData; aStrict: Boolean): Boolean;
+
+begin
+  if aStrict then
+    Result:=inherited Equals(aData, aStrict)
+  else
+    Result:=True;
+  Result:=Result and (aData is TYAMLScalar) and (FValue=TYAMLScalar(aData).FValue)
+end;
+
+
+{ TYAMLScalar.TYAMLScalarEnumerator }
+
+destructor TYAMLScalar.TYAMLScalarEnumerator.Destroy;
+
+begin
+  MoveNext; // Make sure value is in old
+  FOld.FValue:=nil;
+  FreeAndNil(FOld);
+end;
+
+
+function TYAMLScalar.TYAMLScalarEnumerator.GetCurrent: TYAMLMapItem;
+
+begin
+  Result:=FOld;
+end;
+
+
+function TYAMLScalar.TYAMLScalarEnumerator.MoveNext: Boolean;
+
+begin
+  Result:=Assigned(FCurrent);
+  if Result then
+    begin
+    FOld:=FCurrent;
+    FCurrent:=Nil;
+    end;
+end;
+
+
+constructor TYAMLScalar.TYAMLScalarEnumerator.Create(aScalar: TYAMLScalar);
+
+begin
+  FCurrent:=TYAMLMapItem.Create(TYAMLScalar.Create('',yttCustom,yskPlain),aScalar);
+end;
+
+
+{ TYAMLDataList }
+
+function TYAMLDataList.GetItem(aIndex: Integer): TYAMLData;
+
+begin
+  Result:=TYAMLData(FList[aIndex]);
+end;
+
+
+function TYAMLDataList.GetItemCount: Integer;
+
+begin
+  Result:=FList.Count;
+end;
+
+
+function TYAMLDataList.Equals(aData: TYAMLData; aStrict: Boolean): Boolean;
+
+var
+  aList : TYAMLDataList absolute aData;
+  I : Integer;
+
+begin
+  Result:=(aData=Self);
+  if Result then exit;
+  Result:=Inherited Equals(aData,aStrict);
+  if not Result then exit;
+  Result:=(aData.ClassType=Self.ClassType);
+  if not Result then exit;
+  Result:=(aList.Count=Self.Count);
+  if not Result then exit;
+  I:=0;
+  While Result and (I<Count) do
+    begin
+    Result:=Items[i].Equals(aList.Items[i]);
+    Inc(I);
+    end;
+end;
+
+function TYAMLDataList.Extract(aIndex: Integer): TYAMLData;
+
+begin
+  Result:=TYAMLData(FList.Extract(FList[aIndex]));
+end;
+
+
+procedure TYAMLDataList.Delete(aIndex: Integer);
+
+begin
+  FList.Delete(aIndex);
+end;
+
+
+constructor TYAMLDataList.Create;
+
+begin
+  inherited Create;
+  FList:=TFPObjectList.Create(True);
+end;
+
+
+destructor TYAMLDataList.destroy;
+
+begin
+  FreeAndNil(FList);
+  Inherited;
+end;
+
+
+function TYAMLDataList.Clone: TYAMLData;
+
+var
+  aList : TYAMLDataList absolute Result;
+  I : Integer;
+
+begin
+  Result:=TYAMLDataClass(Self.ClassType).create('',Self.Tag);
+  For I:=0 to Count-1 do
+    aList.Add(GetItem(I).Clone);
+end;
+
+
+function TYAMLDataList.GetEnumerator: TYAMLEnumerator;
+
+begin
+  Result:=TYAMLListEnumerator.Create(Self);
+end;
+
+
+function TYAMLDataList.Add(aData: TYAMLData): TYAMLData;
+
+begin
+  Result:=aData;
+  FList.Add(Result);
+end;
+
+
+function TYAMLDataList.Add(const aData: TYAMLString): TYAMLData;
+
+begin
+  Result:=Add(TYamlScalar.Create(aData))
+end;
+
+
+function TYAMLDataList.Add(aData: Integer): TYAMLData;
+
+begin
+  Result:=Add(TYamlScalar.Create(aData))
+end;
+
+
+function TYAMLDataList.Add(aData: Double): TYAMLData;
+
+begin
+  Result:=Add(TYamlScalar.Create(aData))
+end;
+
+
+function TYAMLDataList.Add(aData: Boolean): TYAMLData;
+
+begin
+  Result:=Add(TYamlScalar.Create(aData))
+end;
+
+
+function TYAMLDataList.Add(aData: Int64): TYAMLData;
+
+begin
+  Result:=Add(TYamlScalar.Create(aData))
+end;
+
+
+{ TYAMLDataList.TYAMLListEnumerator }
+
+function TYAMLDataList.TYAMLListEnumerator.GetCurrent: TYAMLMapItem;
+
+begin
+  FItem.FValue:=FList.Items[FIndex];
+  Result:=FItem;
+end;
+
+
+function TYAMLDataList.TYAMLListEnumerator.MoveNext: Boolean;
+
+begin
+  Inc(FIndex);
+  Result:=(FIndex<FList.Count)
+end;
+
+
+constructor TYAMLDataList.TYAMLListEnumerator.Create(aList: TYAMLDataList);
+
+begin
+  Findex:=-1;
+  FList:=aList;
+  FKey:=TYAMLScalar.Create('',yttCustom,yskPlain);
+  FItem:=TYAMLMapItem.create(FKey,Nil);
+end;
+
+
+destructor TYAMLDataList.TYAMLListEnumerator.destroy;
+
+begin
+  FItem.FValue:=Nil;
+  FKey:=Nil;
+  FreeAndNil(FItem); // will free ey
+  inherited destroy;
+end;
+
+{ TYAMLSequence }
+
+class function TYAMLSequence.DefaultTag: TYAMLString;
+
+begin
+  Result:=YAMLTagNames[yttSequence];
+end;
+
+
+function TYAMLSequence.Clone: TYAMLData;
+
+begin
+  Result:=inherited Clone;
+  TYAMLSequence(Result).Kind:=Self.Kind;
+end;
+
+
+{ TYAMLMapping }
+
+function TYAMLMapping.GetMapping(aIndex : Integer): TYAMLMapItem;
+
+begin
+  Result:=TYAMLMapItem(FList[aIndex]);
+end;
+
+
+function TYAMLMapping.GetKey(aIndex : Integer): TYAMLData;
+
+begin
+  Result:=Mapping[aIndex].Key;
+end;
+
+
+function TYAMLMapping.IndexOfKey(aKey: TYAMLData): Integer;
+
+begin
+  Result:=0;
+  While (Result<Count) and (Key[Result]<>aKey) do
+    Inc(Result);
+  if Result>=Count then
+    Result:=-1;
+end;
+
+
+function TYAMLMapping.IndexOfKeyValue(aKey: TYAMLData): Integer;
+
+begin
+  Result:=IndexOfKey(aKey);
+  if Result<>-1 then
+    Exit;
+  Result:=0;
+  While (Result<Count) and not Key[Result].Equals(aKey) do
+    Inc(Result);
+  if Result>=Count then
+    Result:=-1;
+end;
+
+function TYAMLMapping.GetByKey(aKey : TYAMLData): TYAMLData;
+
+var
+  Idx : Integer;
+
+begin
+  Idx:=IndexOfKey(aKey);
+  if Idx=-1 then
+    Result:=Nil
+  else
+    Result:=Items[Idx];
+end;
+
+
+function TYAMLMapping.GetByKeyValue(aKey : TYAMLData): TYAMLData;
+
+var
+  I : Integer;
+
+begin
+  if aKey=nil then
+    Exit(Nil);
+  Result:=ValueByKey[aKey];
+  if Result=Nil then
+    For I:=0 to Count-1 do
+      if Mapping[i].Key.Equals(aKey) then
+        Exit(Mapping[i].Value);
+end;
+
+
+function TYAMLMapping.GetItem(aIndex: Integer): TYAMLData;
+
+begin
+  Result:=Mapping[aIndex].Value;
+end;
+
+
+function TYAMLMapping.GetItemCount: Integer;
+
+begin
+  Result:=FList.Count;
+end;
+
+
+class function TYAMLMapping.DefaultTag: TYAMLString;
+begin
+  Result:=YAMLTagNames[yttMap];
+end;
+
+
+constructor TYAMLMapping.Create;
+
+begin
+  inherited Create;
+  FList:=TFPObjectList.Create(True);
+end;
+
+
+destructor TYAMLMapping.destroy;
+
+begin
+  FreeAndNil(FList);
+  inherited destroy;
+end;
+
+
+procedure TYAMLMapping.Add(aKey, aValue: TYAMLData);
+
+var
+  lMap : TYAMLMapItem;
+
+begin
+  lMap:=TYAMLMapItem.Create(aKey,aValue);
+  FList.Add(lMap);
+end;
+
+
+function TYAMLMapping.Equals(aData: TYAMLData; aStrict: Boolean): Boolean;
+
+var
+  aMapping : TYAMLMapping absolute aData;
+  aValue : TYAMLData;
+  I : Integer;
+
+begin
+  Result:=(aData=Self);
+  if Result then exit;
+  if aStrict then
+    begin
+    Result:=Inherited Equals(aData,aStrict);
+    if not Result then
+      exit;
+    end;
+  Result:=(aData.ClassType=Self.ClassType);
+  if not Result then
+    exit;
+  Result:=(aMapping.Count=Self.Count);
+  if not Result then
+    exit;
+  I:=0;
+  While Result and (I<Count) do
+    begin
+    aValue:=aMapping.ValueByKeyValue[Key[i]];
+    Result:=Assigned(aValue) and Items[i].Equals(aValue,aStrict);
+    Inc(I);
+    end;
+end;
+
+
+function TYAMLMapping.GetEnumerator: TYAMLEnumerator;
+
+begin
+  Result:=TYAMLMappingEnumerator.Create(Self);
+end;
+
+
+procedure TYAMLMapping.Delete(aIndex: Integer);
+
+begin
+  FList.Delete(aIndex);
+end;
+
+
+procedure TYAMLMapping.Delete(aKey: TYAMLData);
+
+var
+  Idx : Integer;
+
+begin
+  Idx:=IndexOfKeyValue(aKey);
+  if (Idx<>-1) then
+    FList.Delete(Idx);
+end;
+
+
+procedure TYAMLMapping.Delete(const aKey: TYAMLString);
+
+var
+  lKey : TYAMLScalar;
+
+begin
+  lKey:=TYAMLScalar.Create(aKey);
+  try
+    Delete(lKey);
+  finally
+    lKey.Free;
+  end;
+end;
+
+
+function TYAMLMapping.Extract(aIndex: Integer): TYAMLData;
+
+var
+  M : TYAMLMapItem;
+begin
+  Result:=Nil;
+  M:=Mapping[aIndex];
+  M:=TYAMLMapItem(FList.Extract(M));
+  if Assigned(M) then
+    begin
+    Result:=M.Value;
+    M.Fvalue:=Nil;
+    M.Free;
+    end;
+end;
+
+
+function TYAMLMapping.Extract(aKey: TYAMLData): TYAMLData;
+
+var
+  Idx : Integer;
+begin
+  Idx:=IndexOfKeyValue(aKey);
+  If Idx=-1 then
+    Result:=Nil
+  else
+    Result:=Extract(Idx);
+end;
+
+
+function TYAMLMapping.Extract(const aKey: TYAMLString): TYAMLData;
+
+var
+  lKey : TYAMLScalar;
+
+begin
+  lKey:=TYAMLScalar.Create(aKey);
+  try
+    Result:=Extract(lKey);
+  finally
+    lKey.Free;
+  end;
+end;
+
+
+function TYAMLMapping.Keys: TYamlMappingKeyEnumerator;
+
+begin
+  Result:=TYAMLMappingKeyEnumerator.Create(Self);
+end;
+
+
+function TYAMLMapping.Clone: TYAMLData;
+
+var
+  I : Integer;
+  lKey,lValue : TYAMLData;
+  aMap : TYAMLMapping absolute Result;
+
+begin
+  Result:=TYAMLDataClass(Self.ClassType).create('',Self.tag);
+  aMap.Kind:=Self.Kind;
+  For I:=0 to Count-1 do
+    With Mapping[i] do
+      begin
+      lKey:=Key.Clone;
+      lValue:=Value.Clone;
+      aMap.Add(lKey,lValue);
+      end;
+end;
+
+function TYAMLMapping.Add(const aKey: TYAMLString; aData: TYAMLData): TYAMLData;
+
+begin
+  Result:=aData;
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+
+function TYAMLMapping.Add(const aKey: TYAMLString; const aData: TYAMLString): TYAMLData;
+
+begin
+  Result:=TYAMLScalar.Create(aData);
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+
+function TYAMLMapping.Add(const aKey: TYAMLString; aData: Integer): TYAMLData;
+
+begin
+  Result:=TYAMLScalar.Create(aData);
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+
+function TYAMLMapping.Add(const aKey: TYAMLString; aData: Double): TYAMLData;
+
+begin
+  Result:=TYAMLScalar.Create(aData);
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+
+function TYAMLMapping.Add(const aKey: TYAMLString; aData: Boolean): TYAMLData;
+
+begin
+  Result:=TYAMLScalar.Create(aData);
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+
+function TYAMLMapping.Add(const aKey: TYAMLString; aData: Int64): TYAMLData;
+
+begin
+  Result:=TYAMLScalar.Create(aData);
+  Add(TYAMLScalar.Create(aKey),Result);
+end;
+
+{ TYAMLMapping.TYAMLMappingtEnumerator }
+
+function TYAMLMapping.TYAMLMappingEnumerator.GetCurrent: TYAMLMapItem;
+
+begin
+  Result:=FList.Mapping[FIndex];
+end;
+
+
+function TYAMLMapping.TYAMLMappingEnumerator.MoveNext: Boolean;
+
+begin
+  Inc(FIndex);
+  Result:=FIndex<FList.Count;
+end;
+
+
+constructor TYAMLMapping.TYAMLMappingEnumerator.Create(aList: TYAMLMapping);
+
+begin
+  FIndex:=-1;
+  FList:=aList;
+end;
+
+{ TYAMLMapping.TYAMLMappingKeyEnumerator }
+
+function TYAMLMapping.TYAMLMappingKeyEnumerator.GetCurrent: TYAMLData;
+
+begin
+  Result:=FList.Mapping[FIndex].Value;
+end;
+
+
+function TYAMLMapping.TYAMLMappingKeyEnumerator.MoveNext: Boolean;
+
+begin
+  Inc(FIndex);
+  Result:=FIndex<FList.Count;
+end;
+
+
+constructor TYAMLMapping.TYAMLMappingKeyEnumerator.Create(aList: TYAMLMapping);
+
+begin
+  FIndex:=-1;
+  FList:=aList;
+end;
+
+
+{ TYAMLGlobalDataList }
+
+class function TYAMLGlobalDataList.allowAnchor: Boolean;
+
+begin
+  Result:=False;
+end;
+
+
+class function TYAMLGlobalDataList.allowTag: Boolean;
+
+begin
+  Result:=False;
+end;
+
+{ TYAMLDocument }
+
+function TYAMLDocument.Clone: TYAMLData;
+
+begin
+  Result:=inherited Clone;
+  TYAMLDocument(Result).Version:=Self.Version;
+end;
+
+
+{ TYAMLMapping.TMapItem }
+
+constructor TYAMLMapItem.create(aKey, aValue: TYAMLData);
+
+begin
+  FKey:=aKey;
+  FValue:=aValue;
+end;
+
+
+destructor TYAMLMapItem.destroy;
+
+begin
+  FreeAndNil(FKey);
+  FreeAndNil(FValue);
+  inherited destroy;
+end;
+
+
+{ TYAMLStream }
+
+function TYAMLStream.GetDocuments: TYAMLDocumentArray;
+
+var
+  I, lCount : Integer;
+
+begin
+  Result:=[];
+  SetLength(Result,GetDocumentCount);
+  lCount:=0;
+  I:=0;
+  While (I<Count) do
+    begin
+    If (Items[i] is TYAMLDocument) then
+      begin
+      Result[lCount]:=TYAMLDocument(Items[i]);
+      inc(lCount);
+      end;
+    Inc(I);
+    end;
+end;
+
+
+function TYAMLStream.GetDocumentCount: Integer;
+
+var
+  I : Integer;
+
+begin
+  Result:=0;
+  for I:=0 to Count-1 do
+    If Items[i] is TYAMLDocument then
+      Inc(Result);
+end;
+
+
+initialization
+  YAMLFormatSettings:=DefaultFormatSettings;
+  YAMLFormatSettings.DecimalSeparator:='.';
+end.
+

+ 139 - 0
packages/fcl-yaml/src/fpyaml.json.pp

@@ -0,0 +1,139 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML to JSON converter
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpyaml.json;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.SysUtils, FpJson.Data,
+{$ELSE}
+  SysUtils, fpjson,
+{$ENDIF}
+  fpyaml.data, fpyaml.types;
+
+function YamlToJSON(aYaml : TYAMLData) : TJSONData;
+
+implementation
+
+uses fpyaml.strings;
+
+function YAMLValueToJSONValue(aYaml : TYAMLData) : TJSONData; forward;
+
+Function YAMLScalarToJSON(aYAML : TYAMLScalar) : TJSONData;
+
+var
+  TT : TYAMLTagType;
+
+begin
+  TT:=TYAMLTagType.FromString(aYAML.Tag);
+  case TT of
+    yttNull :
+      Result:=TJSONNull.Create;
+    yttInteger :
+      if aYAML.AsInt64>MaxInt then
+        Result:=TJSONInt64Number.Create(aYAML.AsInt64)
+      else
+        Result:=TJSONIntegerNumber.Create(aYAML.AsInteger);
+    yttFloat :
+      Result:=TJSONFloatNumber.Create(aYAML.AsDouble);
+    yttBoolean :
+      Result:=TJSONBoolean.Create(aYAML.AsBoolean);
+  else
+    Result:=TJSONString.Create(aYAML.AsString);
+  end;
+end;
+
+Function YAMLMappingToJSONObject(aYAML : TYAMLMapping) : TJSONObject;
+
+var
+  I : Integer;
+  lKey : String;
+
+begin
+  Result:=TJSONObject.Create;
+  try
+    For I:=0 to aYAML.Count-1 do
+      begin
+      if Not (aYAML.Key[i] is TYAMLScalar) then
+        Raise EConvertError.Create(SErrOnlyScalarKeys);
+      if (TYAMLSCalar(aYAML.Key[i]).Tag=yttNull.ToString) then
+        Raise EConvertError.Create(SErrOnlyScalarKeys);
+      lKey:=TYAMLScalar(aYAML.Key[i]).Value;
+      Result.Add(lKey,YAMLValueToJSONValue(aYAML[I]));
+      end;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+Function YAMLSequenceToJSONArray(aYAML : TYAMLSequence) : TJSONArray;
+
+var
+  I : Integer;
+
+begin
+  Result:=TJSONArray.Create;
+  try
+    For I:=0 to aYAML.Count-1 do
+      Result.Add(YAMLValueToJSONValue(aYAML[I]));
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function YAMLValueToJSONValue(aYaml : TYAMLData) : TJSONData;
+
+begin
+  if aYAML is TYAMLMapping then
+    Result:=YAMLMappingToJSONObject(TYAMLMapping(aYAML))
+  else if aYAML is TYAMLSequence then
+    Result:=YAMLSequenceToJSONArray(TYAMLSequence(aYAML))
+  else if (aYAML is TYAMLScalar) then
+    Result:=YAMLScalarToJSON(TYAMLScalar(aYAML))
+  else
+    begin
+    if assigned(aYAML) then
+      Raise EConvertError.CreateFmt(SErrUnknownYAMLtype,[aYAML.ClassName])
+    else
+      Raise EConvertError.CreateFmt(SErrUnknownYAMLtype,['<NIL>']);
+    end;
+end;
+
+function YamlToJSON(aYaml : TYAMLData) : TJSONData;
+
+begin
+  if aYAML is TYAMLStream then
+    begin
+    if TYAMLStream(aYAML).DocumentCount<>1 then
+      Raise EConvertError.Create(SErrOnlySingleDocument);
+    aYAML:=TYAMLStream(aYAML).Documents[0];
+    end;
+  if aYAML is TYAMLDocument then
+    begin
+    if TYAMLDocument(aYAML).Count<>1 then
+      Raise EConvertError.Create(SErrOnlySingleValue);
+    aYAML:=TYAMLDocument(aYAML).Items[0];
+    end;
+  Result:=YAMLValueToJSONValue(aYAML);
+end;
+
+end.
+

+ 623 - 0
packages/fcl-yaml/src/fpyaml.parser.pp

@@ -0,0 +1,623 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML Parser
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpyaml.parser;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  {$IFDEF FPC_DOTTEDUNITS}
+  System.SysUtils, System.StrUtils, System.DateUtils, System.TypInfo, System.Classes, System.Contnrs,
+  {$ELSE}
+  sysutils, strutils, dateutils, typinfo, classes, contnrs,
+  {$ENDIF}
+  fpyaml.scanner, fpyaml.data, fpyaml.types;
+
+Type
+  EYAMLParser = class(EYAML)
+    Pos : TYAMLPos;
+  end;
+
+  { TYAMLParser }
+
+  TYAMLParser = class(TObject)
+  private
+    FLastVersion : TYAMLVersion;
+    FLastAnchor : TYAMLString;
+    FScanner : TYAMLScanner;
+    FOwnsScanner : Boolean;
+    FStream : TStream;
+    FMap : TFPObjectHashTable;
+  protected
+    procedure Error(const aMsg : String);
+    procedure Error(const aFmt : String; const aArgs: array of const);
+    Function Peek : TYAMLTokenData;
+    Procedure ConsumeToken;
+    function ConsumeAnchor: TYAMLString;
+    function ConsumeVersion: TYAMLVersion;
+    class function TokenToScalarKind(aToken: TYAMLToken): TYAMLScalarKind;
+    function GuessTagFromScalarValue(aValue: TYAMLString; aKind: TYAMLScalarKind): TYAMLTagType; virtual;
+    Function CreateScanner(aInput : TStream) : TYAMLScanner; virtual;
+    function CreateYAMLData(aClass: TYAMLDataClass; aTag : TYAMLTagType = yttCustom): TYAMLData; virtual;
+    function CreateDocument : TYAMLDocument;
+    function CreateTagData : TYAMLTagData;
+    function CreateStream: TYAMLStream;
+    function CreateSequence(aKind : TYAMLCollectionKind) : TYAMLSequence;
+    function CreateMapping(aKind : TYAMLCollectionKind) : TYAMLMapping;
+    function CreateScalar(const aValue : TYAMLString; aTag : TYAMLTagType): TYAMLScalar; virtual;
+    procedure ParseAnchor; virtual;
+    procedure ParseVersion; virtual;
+    function ParseAlias: TYAMLData; virtual;
+    function ParseBlockMapping: TYAMLMapping; virtual;
+    function ParseFlowMapping: TYAMLMapping; virtual;
+    function ParseScalar: TYAMLScalar; virtual;
+    function ParseBlockSequence(SkipStart: boolean=false): TYAMLSequence; virtual;
+    function ParseFlowSequence: TYAMLSequence; virtual;
+    function ParseValue(aAllowBlockEntry: Boolean=false): TYAMLData; virtual;
+    function ParseDocument: TYAMLDocument; virtual;
+    function ParseTagDirective: TYAMLTagData; virtual;
+    Property Scanner : TYAMLScanner Read FScanner;
+  public
+    Constructor create(aScanner : TYAMLScanner; aOwnsScanner : Boolean = False);
+    Constructor create(aStream: TStream);
+    Constructor create(aInput: TStrings);
+    Constructor create(aInput : array of string);
+    Constructor create(aFileName : string);
+
+    Function Parse : TYAMLStream;
+    Destructor Destroy; override;
+  end;
+
+implementation
+
+uses fpyaml.strings;
+
+{ TYAMLParser }
+
+function TYAMLParser.ConsumeAnchor : TYAMLString;
+
+begin
+  Result:=FLastAnchor;
+  FLastAnchor:='';
+end;
+
+
+function TYAMLParser.ConsumeVersion: TYAMLVersion;
+
+begin
+  Result:=FLastVersion;
+  FLastVersion:=Default(TYAMLVersion);
+end;
+
+
+function TYAMLParser.CreateYAMLData(aClass : TYAMLDataClass; aTag : TYAMLTagType) : TYAMLData;
+
+var
+  lAnchor:String;
+
+begin
+  lAnchor:=ConsumeAnchor;
+  Result:=aClass.Create(lAnchor,YAMLTagNames[aTag]);
+  if lAnchor<>'' then
+    begin
+    if (Result.Anchor=lAnchor) then
+      FMap.Add(lAnchor,Result)
+    else
+      FLastAnchor:=lAnchor;
+    end;
+end;
+
+
+function TYAMLParser.CreateScanner(aInput: TStream): TYAMLScanner;
+
+begin
+  Result:=TYAMLScanner.Create(aInput);
+end;
+
+
+constructor TYAMLParser.create(aScanner: TYAMLScanner; aOwnsScanner: Boolean);
+
+begin
+  FScanner:=aScanner;
+  FOwnsScanner:=aOwnsScanner;
+  FMap:=TFPObjectHashTable.Create(False);
+end;
+
+
+constructor TYAMLParser.create(aStream: TStream);
+
+begin
+  Create(CreateScanner(aStream),True);
+end;
+
+
+constructor TYAMLParser.create(aInput: TStrings);
+
+begin
+  FStream:=TMemoryStream.Create;
+  aInput.SaveToStream(FStream);
+  FStream.Position:=0;
+  Create(FStream);
+end;
+
+
+constructor TYAMLParser.create(aInput: array of string);
+
+var
+  L : TStrings;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.AddStrings(aInput);
+    Create(L);
+  finally
+    L.Free;
+  end;
+end;
+
+
+constructor TYAMLParser.create(aFileName: string);
+
+begin
+  FStream:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  Create(FStream);
+end;
+
+
+function TYAMLParser.GuessTagFromScalarValue(aValue : TYAMLString; aKind : TYAMLScalarKind): TYAMLTagType;
+
+var
+  i64 : Int64;
+  D : Double;
+
+begin
+  if (aValue='') then
+    begin
+    if aKind=yskPlain then
+      Exit(yttNull)
+    else
+      Exit(yttString);
+    end;
+  if aValue[1] in ['0'..'9'] then
+    begin
+    if TryStrToInt64(aValue,i64) then
+      Exit(yttInteger)
+    else if TryISOStrToDate(aValue,D) then
+      Exit(yttTimeStamp)
+    else if TryStrToFloat(aValue,D,YAMLFormatSettings) then
+      Exit(yttFloat)
+    end;
+  if IndexStr(aValue,['true','false'])<>-1 then
+    Exit(yttBoolean);
+  Result:=yttString;
+end;
+
+
+class function TYAMLParser.TokenToScalarKind(aToken : TYAMLToken) : TYAMLScalarKind;
+
+begin
+  Case aToken of
+    ytScalarPlain : Result:=yskPlain;
+    ytScalarSingle : Result:=yskSingle;
+    ytScalarDouble : Result:=yskDouble;
+    ytScalarFolded : Result:=yskFolded;
+    ytScalarLiteral : Result:=yskLiteral;
+  end;
+end;
+
+
+function TYAMLParser.ParseScalar : TYAMLScalar;
+
+var
+  lToken : TYAMLTokenData;
+  lValue : TYAMLString;
+  lTag : TYAMLTagType;
+  lKind : TYAMLScalarKind;
+
+begin
+  lToken:=Peek;
+  lValue:=lToken.value;
+  lKind:=TokenToScalarKind(lToken.Token);
+  lTag:=GuessTagFromScalarValue(lValue,lKind);
+  Result:=TYAMLScalar(CreateYAMLData(TYAMLScalar,lTag));
+  Result.Kind:=lKind;
+  Result.Value:=lValue;
+  ConsumeToken;
+end;
+
+
+function TYAMLParser.ParseValue(aAllowBlockEntry : Boolean = false) : TYAMLData;
+// On entry, we're on the first token of the value
+// On exit, were on EOF or the first token after the value
+var
+  lToken : TYAMLTokenData;
+
+begin
+  lToken:=Peek;
+  case lToken.Token of
+    ytAnchor : ParseAnchor;
+    ytAlias : Result:=ParseAlias;
+    ytBlockEntry :
+      {
+        key:
+        - item 1
+        - item 2
+      }
+      if aAllowBlockEntry then
+        Result:=ParseBlockSequence(True)
+      else
+        Error(SErrUnexpectedToken,[lToken.Token.ToString,lToken.Value]);
+    ytBlockSequenceStart : Result:=ParseBlockSequence;
+    ytFlowSequenceStart : Result:=ParseFlowSequence;
+    ytBlockMappingStart : Result:=ParseBlockMapping;
+    ytFlowMappingStart : Result:=ParseFlowMapping;
+    ytScalarPlain,
+    ytScalarSingle,
+    ytScalarDouble,
+    ytScalarFolded,
+    ytScalarLiteral : Result:=ParseScalar;
+  else
+    Error(SErrUnexpectedToken,[lToken.Token.ToString,lToken.Value])
+  end;
+end;
+
+
+function TYAMLParser.ParseFlowSequence : TYAMLSequence;
+// On entry, we're on the first token of the sequence: ytFlowSequenceStart.
+// On exit, were on EOF or the first token after the sequence end: ytFlowSequenceEnd;
+var
+  lToken : TYAMLTokenData;
+
+begin
+  Result:=CreateSequence(yckFlow);
+  try
+    ConsumeToken;
+    lToken:=Peek;
+    While not (Ltoken.token in [ytEOF,ytFlowSequenceEnd]) do
+      begin
+      Result.Add(ParseValue);
+      lToken:=Peek;
+      if lToken.token=ytFlowEntry then
+        begin
+        ConsumeToken;
+        lToken:=Peek;
+        end;
+      end;
+    If Ltoken.token=ytFlowSequenceEnd then
+      ConsumeToken;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function TYAMLParser.ParseBlockSequence(SkipStart : boolean = false) : TYAMLSequence;
+// On entry, we're on the first token of the sequence: ytBlockSequenceStart.
+// On exit, were on EOF or the first token after the sequence end: yt(Block|Flow)SequenceEnd;
+
+var
+  lToken : TYAMLTokenData;
+
+begin
+  Result:=CreateSequence(yckBlock);
+  try
+    if not SkipStart then
+      ConsumeToken;
+    lToken:=Peek;
+    While (Ltoken.token=ytBlockEntry) do
+      begin
+      ConsumeToken;
+      Result.Add(ParseValue);
+      lToken:=Peek;
+      end;
+    // When we're indentless, encountering a block end means we finish the enclosing block, so do not consume it...
+    If (Ltoken.token=ytBlockEnd) and Not SkipStart then
+      ConsumeToken;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function TYAMLParser.ParseBlockMapping: TYAMLMapping;
+// On entry, we're on the first token of the mapping: ytBlockMappingStart.
+// On exit, were on EOF or the first token after the mapping end: ytBlockEnd;
+
+var
+  lToken : TYAMLTokenData;
+  lKey,lValue: TYAMLData;
+  lCount : Integer;
+
+begin
+  lCount:=0;
+  Result:=CreateMapping(yckBlock);
+  try
+    ConsumeToken;
+    lToken:=Peek;
+    While (Ltoken.token=ytKey) do
+      begin
+      ConsumeToken;
+      lKey:=ParseValue;
+      lToken:=Peek;
+      if lToken.Token<>ytValue then
+        Error(SErrUnexpectedToken,[lToken.token.ToString,lToken.Value]);
+      consumeToken;
+      lValue:=ParseValue(true);
+      Result.Add(lKey,lValue);
+      lToken:=Peek;
+      Inc(lCount);
+      end;
+    If Ltoken.token=ytBlockEnd then
+      ConsumeToken
+    else If Ltoken.token<>ytEOF then
+      Error(SErrUnexpectedToken,[lToken.token.ToString,lToken.Value]);
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function TYAMLParser.ParseFlowMapping: TYAMLMapping;
+// On entry, we're on the first token of the mapping: ytBlockMappingStart.
+// On exit, were on EOF or the first token after the mapping end: ytFlowMappingEnd
+
+var
+  lToken : TYAMLTokenData;
+  lKey,lValue: TYAMLData;
+
+begin
+  Result:=CreateMapping(yckFlow);
+  try
+    ConsumeToken;
+    lToken:=Peek;
+    While (Ltoken.token=ytKey) do
+      begin
+      ConsumeToken;
+      lKey:=ParseValue;
+      lToken:=Peek;
+      if lToken.Token<>ytValue then
+        Error(SErrUnexpectedToken,[lToken.token.ToString,lToken.Value]);
+      consumeToken;
+      lValue:=ParseValue;
+      Result.Add(lKey,lValue);
+      lToken:=Peek;
+      if lToken.Token=ytFlowEntry then
+        begin
+        ConsumeToken;
+        lToken:=Peek;
+        end;
+      end;
+    If Ltoken.token=ytFlowMappingEnd then
+      ConsumeToken
+    else If Ltoken.token<>ytEOF then
+      Error(SErrUnexpectedToken,[lToken.token.ToString,lToken.Value]);
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function TYAMLParser.ParseAlias : TYAMLData;
+
+begin
+  //
+end;
+
+
+function TYAMLParser.ParseDocument : TYAMLDocument;
+
+// On entry, we're on the first token of the document: this may be the document start token or a value.
+// On exit, were on EOF or the first token after the document end.
+var
+  lToken : TYAMLTokenData;
+
+begin
+  Result:=CreateDocument;
+  try
+    lToken:=Peek;
+    if lToken.Token=ytDocumentStart then
+      begin
+      ConsumeToken;
+      lToken:=Peek;
+      end;
+    While not (lToken.token in [ytEOF,ytDocumentEnd]) do
+      begin
+      case lToken.Token of
+        ytAnchor : ParseAnchor;
+        ytAlias : Result.Add(ParseAlias);
+      else
+        Result.Add(ParseValue);
+      end;
+      lToken:=Peek;
+      end;
+    if lToken.token=ytDocumentEnd then
+      ConsumeToken;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+function TYAMLParser.CreateDocument : TYAMLDocument;
+
+begin
+  Result:=TYAMLDocument(CreateYAMLData(TYAMLDocument));
+  Result.Version:=ConsumeVersion;
+end;
+
+
+function TYAMLParser.CreateTagData: TYAMLTagData;
+begin
+  Result:=TYAMLTagData(CreateYAMLData(TYAMLTagData));
+end;
+
+
+function TYAMLParser.CreateStream : TYAMLStream;
+
+begin
+  Result:=TYAMLStream(CreateYAMLData(TYAMLStream));
+end;
+
+
+function TYAMLParser.CreateSequence(aKind: TYAMLCollectionKind): TYAMLSequence;
+
+begin
+  Result:=TYAMLSequence(CreateYAMLData(TYAMLSequence));
+  Result.Kind:=aKind;
+end;
+
+
+function TYAMLParser.CreateMapping(aKind: TYAMLCollectionKind): TYAMLMapping;
+
+begin
+  Result:=TYAMLMapping(CreateYAMLData(TYAMLMapping));
+  Result.Kind:=aKind;
+end;
+
+
+function TYAMLParser.CreateScalar(const aValue: TYAMLString; aTag: TYAMLTagType): TYAMLScalar;
+
+begin
+  Result:=TYAMLScalar(CreateYAMLData(TYAMLScalar,aTag));
+  Result.Value:=aValue;
+end;
+
+
+procedure TYAMLParser.ParseAnchor;
+// On entry, we're on the anchor token.
+// On exit, we're on the token after the anchor
+begin
+  if FLastAnchor<>'' then
+    Error(SErrDoubleAnchor,[Peek.value,FLastAnchor]);
+  FLastAnchor:=Peek.value;
+  ConsumeToken;
+end;
+
+
+procedure TYAMLParser.ParseVersion;
+// On entry, we're on the version token.
+// On exit, we're on the token after the directive
+var
+  lNew : TYAMLVersion;
+
+begin
+  lNew.Major:=StrToIntDef(Peek.value,0);
+  lNew.Minor:=StrToIntDef(Peek.value2,0);
+  if (FLastVersion.ToString<>'0.0') then
+    Error(SErrDoubleVersion,[lNew.ToString,FLastVersion.ToString]);
+  FLastVersion:=lNew;
+  ConsumeToken;
+end;
+
+
+function TYAMLParser.ParseTagDirective : TYAMLTagData;
+// On entry, we're on the directive token.
+// On exit, we're on the token after the directive
+begin
+  Result:=TYAMLTagData(CreateYAMLData(TYAMLTagData));
+  ConsumeToken;
+end;
+
+
+procedure TYAMLParser.Error(const aMsg: String);
+
+var
+  Err : EYAMLParser;
+
+begin
+  Err:=EYAMLParser.Create(aMsg);
+  Err.Pos:=Peek.beginpos;
+  Raise Err;
+end;
+
+
+procedure TYAMLParser.Error(const aFmt: String; const aArgs: array of const);
+
+begin
+  Error(Format(aFmt,aArgs));
+end;
+
+
+function TYAMLParser.Peek: TYAMLTokenData;
+
+begin
+  Result:=Scanner.Peek;
+end;
+
+
+procedure TYAMLParser.ConsumeToken;
+begin
+  Scanner.ConsumeToken;
+end;
+
+
+function TYAMLParser.Parse: TYAMLStream;
+
+var
+  lToken : TYAMLTokenData;
+  lDone : Boolean;
+
+begin
+  lDone:=False;
+  Result:=CreateStream;
+  try
+    Repeat
+      lToken:=Peek;
+      Case lToken.token of
+        ytAnchor : ParseAnchor;
+        ytAlias : Error(SErrAliasNotAllowed);
+        ytScalarDouble,
+        ytScalarSingle,
+        ytScalarFolded,
+        ytScalarLiteral,
+        ytScalarPlain,
+        ytBlockMappingStart,
+        ytBlockSequenceStart,
+        ytFlowSequenceStart,
+        ytFlowMappingStart,
+        ytDocumentStart : Result.Add(ParseDocument);
+        ytVersionDirective : ParseVersion;
+        ytTagDirective : Result.Add(ParseTagDirective);
+        ytEOF: lDone:=True;
+      else
+        Error(SErrUnexpectedToken,[lToken.Token.ToString,lToken.value]);
+      end;
+    until lDone;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+destructor TYAMLParser.Destroy;
+
+begin
+  FreeAndNil(FMap);
+  FreeAndNil(FStream);
+  if FOwnsScanner then
+    FreeAndNil(FScanner);
+  inherited Destroy;
+end;
+
+end.
+

+ 1854 - 0
packages/fcl-yaml/src/fpyaml.scanner.pp

@@ -0,0 +1,1854 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML scanner
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{
+  This unit takes some ideas from the libYAML C scanner:
+  https://github.com/yaml/libyaml/
+}
+unit fpyaml.scanner;
+
+{$mode ObjFPC}
+{$H+}
+{$modeswitch advancedrecords}
+{$modeswitch typehelpers}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses System.SysUtils, System.TypInfo, System.Classes, Fcl.Streams.Extra, System.Types, fpyaml.types;
+{$ELSE}
+uses sysutils, TypInfo, classes, streamex, types, fpyaml.types;
+{$ENDIF}
+
+
+
+type
+  TYAMLString = AnsiString;
+
+  TYAMLPos = record
+    Line: Cardinal;
+    Column: Cardinal;
+    Constructor create(const aLine, aColumn: Cardinal);
+  end;
+  { TYAMLKey }
+
+  TYAMLKey = record
+    Possible: Boolean;
+    Required: Boolean;
+    TokenNumber: Cardinal;
+    Position : TYAMLPos;
+    constructor Create (aPossible, aRequired : Boolean; aTokenNumber : Cardinal);
+    function CheckPossibleAt(at : TYAMLPos) : Boolean;
+  end;
+  PYAMLKey = ^TYAMLKey;
+
+  TYAMLKeyArray = Array of TYAMLKey;
+
+  TYAMLToken = (
+    ytNoToken,
+    ytEOF,
+
+    ytVersionDirective,
+    ytTagDirective,
+    ytDocumentStart,
+    ytDocumentEnd,
+
+    ytAlias,
+    ytAnchor,
+    ytTag,
+
+    ytScalarPlain,
+    ytScalarLiteral,
+    ytScalarFolded,
+    ytScalarSingle,
+    ytScalarDouble,
+
+    ytBlockSequenceStart,
+    ytBlockMappingStart,
+    ytBlockEntry,
+    ytBlockEnd,
+
+    ytFlowSequenceStart,
+    ytFlowSequenceEnd,
+    ytFlowMappingStart,
+    ytFlowMappingEnd,
+    ytFlowEntry,
+
+    ytKey,
+    ytValue);
+
+  { TYAMLTokenHelper }
+
+  TYAMLTokenHelper = type helper for TYAMLToken
+    function ToString : String;
+  end;
+
+  { TYAMLTokenData }
+
+  TYAMLTokenData = record
+    token : TYAMLToken;
+    beginpos,
+    endpos : TYAMLPos;
+    value : TYAMLString;
+    value2 : TYAMLString;
+    constructor Create(aToken : TYAMLToken; aBegin,aEnd : TYAMLPos; aValue : TYAMLString=''; aValue2 : TYAMLString='');
+    constructor Create(aToken : TYAMLToken; aPos : TYAMLPos);
+  end;
+  TYAMLTokenDataArray = Array of TYAMLTokenData;
+
+  { EYAMLScannerError }
+
+  EYAMLScanner = class(EYAML)
+    FPos: TYAMLPos;
+  public
+    constructor Create(const aPos: TYAMLPos; const aMessage: TYAMLString); overload;
+    property Position : TYAMLPos Read FPos;
+  end;
+
+
+  { TYAMLScanner }
+
+  TYAMLScanner = class
+  private
+    FCurrRow : Integer;
+    FCurrCol : Integer;
+    FCurrLine : TYAMLString;
+    FCurrLineLen : Integer;
+    FCurrIndent: Integer;
+    FAllowKey: Boolean;
+    FSkipAllowKeyCheck : Boolean;
+    FFlowLevel: Integer;
+    FConsumedTokens: Integer;
+    FReader: TStreamReader;
+    // Queue
+    FTokens: TYAMLTokenDataArray;
+    FTokenCount : Integer;
+    // Stack
+    FKeys: TYAMLKeyArray;
+    FKeyCount : Integer;
+    // Stack
+    FIndents: TIntegerDynArray;
+    FIndentCount: Integer;
+  Protected
+    // Error handling
+    procedure Error(const aPos: TYAMLPos; const aMessage: TYAMLString); virtual;
+    procedure Error(const aPos: TYAMLPos; const aFmt: TYAMLString; const aArgs : Array of const); virtual;
+    // Character stream handling
+    function GetEscapeChar: TYAMLString;
+    function ReadLine : Boolean;
+    function SkipWhiteSpace : boolean;
+    Function CurrPos : TYAMLPos;
+    function GetCurrChar: AnsiChar;
+    function GetNextChar: AnsiChar;
+    function IsDocumentStart: Boolean;
+    function IsDocumentEnd: Boolean;
+    function IsCurrAlpha: Boolean;
+    function IsCurrDigit: Boolean;
+    function IsCurrBlank(AllowEOF: boolean=True): Boolean;
+    function IsCurrSpace: Boolean;
+    function IsNextBlank: Boolean;
+    Function IsEOL : Boolean;
+    // Actual sacanning
+    function ScanDirective: TYAMLTokenData;
+    function ScanAnchorOrAlias(ATokenType: TYAMLToken): TYAMLTokenData;
+    function ScanTag: TYAMLTokenData;
+    function ScanDirectiveName(aPos: TYAMLPos): TYAMLString;
+    procedure ScanVersionDirectiveValue(aPos: TYAMLPos; out AMajor: Integer; out AMinor: Integer);
+    procedure ScanTagDirectiveValue(aPos: TYAMLPos; out AHandle: TYAMLString; out APrefix: TYAMLString);
+    function ScanTagHandle(aIsDirective: Boolean; aPos: TYAMLPos): TYAMLString;
+    function ScanTagUri(AIsVerbatim: Boolean; AIsDirective: Boolean; AHead: TYAMLString; aPos: TYAMLPos): TYAMLString;
+    function ScanUriEscapes(aIsDirective: Boolean; aPos: TYAMLPos): TYAMLString;
+    function ScanBlockScalar(aFolded: Boolean): TYAMLTokenData;
+    function ScanFlowScalar(AIsSingle: Boolean): TYAMLTokenData;
+    function ScanPlainScalar: TYAMLTokenData;
+    procedure HandleBlockScalarWhiteSpace(var AIndent: Integer; var ABreaks: TYAMLString; aPos: TYAMLPos; out aPos2: TYAMLPos);
+    procedure DoFold(var aValue, aLeading, aTrailing: TYAMLString);
+    // Indent stack
+    function PopIndent: Integer;
+    procedure PushIndent(aIndent: Integer);
+    procedure Indent(AColumn: Integer; ANumber: Integer; ATokenType: TYAMLToken; aPos: TYAMLPos);
+    procedure Undent(AColumn: Integer);
+    // Key stack
+    procedure PushKey(aKey: TYAMLKey);
+    function PeekKey: PYAMLKey;
+    procedure PopKey;
+    function CheckMoreTokensNeeded: Boolean;
+    procedure CheckPossibleKeys;
+    procedure RemoveSimpleKey;
+    procedure SaveSimpleKey;
+    // Token handling
+    procedure MaybeGrowTokens;
+    function FirstToken: TYAMLTokenData;
+    procedure QueueToken(aToken: TYAMLTokenData);
+    procedure InsertToken(aAtPos : Integer; aToken: TYAMLTokenData);
+    procedure FetchMoreTokens;
+    function FetchNextToken : Boolean;
+    function RemoveFirstToken: TYAMLTokenData;
+    procedure IncreaseFlowLevel;
+    procedure DecreaseFlowLevel;
+    procedure HandleStreamStart;
+    procedure HandleStreamEnd;
+    procedure HandleDirective;
+    procedure HandleDocumentIndicator(ATokenType: TYAMLToken);
+    procedure HandleFlowCollectionStart(ATokenType: TYAMLToken);
+    procedure HandleFlowCollectionEnd(ATokenType: TYAMLToken);
+    procedure HandleFlowEntry;
+    procedure HandleBlockEntry;
+    procedure HandleKey;
+    procedure HandleValue;
+    procedure HandleAnchorOrAlias(ATokenType: TYAMLToken);
+    procedure HandleTag;
+    procedure HandleBlockScalar(aFolded: Boolean);
+    procedure HandleFlowScalar(aSingle: Boolean);
+    procedure HandlePlainScalar;
+  public
+    constructor Create(aInput: TStream);
+    destructor Destroy; override;
+    procedure ConsumeToken;
+    function Peek: TYAMLTokenData;
+    Function GetNextToken : TYAMLTokenData;
+    function InFlowContext : Boolean;
+  end;
+
+implementation
+
+uses fpyaml.strings;
+
+const
+  MaxVersionDigitLength = 9;
+  TabChar = #9;
+  WhiteSpace = [#32, #9];
+  AlphaChars = ['A'..'Z', 'a'..'z', '0'..'9'];
+  DigitChars = ['0'..'9'];
+  NotScalarStartChars = ['-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '''', '"', '%', '@', '`' ];
+  TagURIChars = [';', '/', '?', ':', '@', '&', '=', '+', '$', '.', '%', '!', '~', '*', '''', '(', ')'];
+  HexChars = ['A'..'F','a'..'f','0'..'9'];
+  FlowIndicatorChars = [',', '[', ']', '{', '}'];
+  DirectiveNames : Array[boolean] of string = ('tag','directive');
+
+{ TYAMLPos }
+
+constructor TYAMLPos.create(const aLine, aColumn : Cardinal);
+
+begin
+  Line:=aLine;
+  Column:=aColumn;
+end;
+
+{ TYAMLKey }
+
+constructor TYAMLKey.Create(aPossible, aRequired: Boolean; aTokenNumber: Cardinal);
+
+begin
+  Possible:=aPossible;
+  Required:=aRequired;
+  TokenNumber:=aTokenNumber;
+end;
+
+
+function TYAMLKey.CheckPossibleAt(at: TYAMLPos): Boolean;
+
+begin
+  Result:=(Position.Line=at.Line) and ((at.Column-Position.Column) <= 1024);
+end;
+
+{ TYAMLTokenHelper }
+
+function TYAMLTokenHelper.ToString: String;
+begin
+  Result:=GetEnumName(TypeInfo(TYAMLToken),Ord(Self))
+end;
+
+
+{ TYAMLTokenData }
+
+constructor TYAMLTokenData.Create(aToken: TYAMLToken; aBegin, aEnd: TYAMLPos; aValue: TYAMLString; aValue2 : TYAMLString='');
+
+begin
+  Token:=aToken;
+  BeginPos:=aBegin;
+  EndPos:=aEnd;
+  Value:=aValue;
+  Value2:=aValue2;
+end;
+
+
+constructor TYAMLTokenData.Create(aToken: TYAMLToken; aPos: TYAMLPos);
+
+begin
+  Token:=aToken;
+  BeginPos:=aPos;
+  EndPos:=aPos;
+  Value:='';
+end;
+
+{ EYAMLScannerError }
+
+constructor EYAMLScanner.Create(const aPos: TYAMLPos; const aMessage: TYAMLString);
+
+begin
+  inherited Create(AMessage);
+  FPos:=aPos;
+end;
+
+
+{ TYAMLScanner }
+
+constructor TYAMLScanner.Create(aInput: TStream);
+
+begin
+  FReader:=TStreamReader.Create(aInput,4096,False);
+  SetLength(FTokens,100);
+  SetLength(FKeys,100);
+  SetLength(FIndents,100);
+
+  FTokenCount:=0;
+  FConsumedTokens:=0;
+  FFlowLevel:=0;
+  FCurrIndent:=0;
+  HandleStreamStart;
+end;
+
+destructor TYAMLScanner.Destroy;
+
+begin
+  FReader.Free;
+  inherited Destroy;
+end;
+
+
+function TYAMLScanner.CurrPos: TYAMLPos;
+
+begin
+  Result:=TYAMLPos.Create(FCurrRow,FCurrCol);
+end;
+
+
+function TYAMLScanner.IsNextBlank : Boolean;
+
+begin
+  Result:=(FCurrCol=FCurrLineLen) or (FCurrLine[FCurrCol+1] in WhiteSpace);
+end;
+
+
+function TYAMLScanner.IsEOL: Boolean;
+
+begin
+  Result:=FCurrCol>FCurrLineLen;
+end;
+
+
+function TYAMLScanner.IsCurrBlank(AllowEOF: boolean=True) : Boolean;
+
+begin
+  if AllowEOF and (FCurrCol>FCurrLineLen) then
+    Result:=True
+  else
+    Result:=(FCurrCol<=FCurrLineLen) and (FCurrLine[FCurrCol] in WhiteSpace);
+end;
+
+
+function TYAMLScanner.IsCurrSpace: Boolean;
+
+begin
+  Result:=(FCurrCol<=FCurrLineLen) and (FCurrLine[FCurrCol]=' ');
+end;
+
+
+function TYAMLScanner.IsDocumentStart : Boolean;
+
+begin
+  Result:=(FCurrLineLen>=3)
+          and (FCurrLine[1]='-')
+          and (FCurrLine[2]='-')
+          and (FCurrLine[3]='-')
+          And ((FCurrLineLen=3) or (FCurrLine[4]=' '));
+end;
+
+
+function TYAMLScanner.IsDocumentEnd : Boolean;
+
+begin
+  Result:=(FCurrLineLen>=3)
+          and (FCurrLine[1]='.')
+          and (FCurrLine[2]='.')
+          and (FCurrLine[3]='.')
+          and ((FCurrLineLen=3) or (FCurrLine[4]=' '));
+end;
+
+
+procedure TYAMLScanner.IncreaseFlowLevel;
+
+var
+  lKey: TYAMLKey;
+
+begin
+  lKey:=Default(TYAMLKey);
+  PushKey(lKey);
+  Inc(FFlowLevel);
+end;
+
+
+procedure TYAMLScanner.DecreaseFlowLevel;
+
+begin
+  if (FFlowLevel > 0) then
+    begin
+    Dec(FFlowLevel);
+    PopKey;
+    end;
+end;
+
+
+procedure TYAMLScanner.Error(const aPos: TYAMLPos; const aMessage: TYAMLString);
+
+begin
+  raise EYAMLScanner.Create(aPos,aMessage);
+end;
+
+
+procedure TYAMLScanner.Error(const aPos: TYAMLPos; const aFmt: TYAMLString; const aArgs: array of const);
+
+begin
+  Error(aPos,Format(aFmt,aArgs));
+end;
+
+
+function TYAMLScanner.RemoveFirstToken: TYAMLTokenData;
+var
+  i : integer;
+begin
+  Result:=FTokens[0];
+  Dec(FTokenCount);
+  For I:=0 to FTokenCount-1 do
+   FTokens[i]:=FTokens[I+1];
+end;
+
+
+function TYAMLScanner.FirstToken: TYAMLTokenData;
+
+begin
+  if FTokenCount=0 then
+    Result:=TYAMLTokenData.Create(ytEOF,CurrPos)
+  else
+    Result:=FTokens[0];
+end;
+
+
+function TYAMLScanner.Peek: TYAMLTokenData;
+
+begin
+  FetchMoreTokens;
+  Result:=FirstToken;
+end;
+
+
+function TYAMLScanner.GetNextToken: TYAMLTokenData;
+
+begin
+  Result:=Peek;
+  ConsumeToken;
+end;
+
+function TYAMLScanner.InFlowContext: Boolean;
+begin
+  Result:=(FFlowLevel>0);
+end;
+
+
+procedure TYAMLScanner.ConsumeToken;
+
+begin
+  Inc(FConsumedTokens);
+  RemoveFirstToken;
+end;
+
+
+procedure TYAMLScanner.FetchMoreTokens;
+
+var
+  lMore: Boolean;
+  L : Integer;
+begin
+  l:=0;
+  Repeat
+    inc(l);
+    // Check if we really need to get more tokens.
+    lMore:=FTokenCount=0;
+    if not lMore then
+      begin
+      CheckPossibleKeys;
+      lMore:=CheckMoreTokensNeeded;
+      end;
+    if lMore then
+      lMore:=FetchNextToken;
+  until not lMore;
+end;
+
+function TYAMLScanner.CheckMoreTokensNeeded: Boolean;
+// Check if we need more tokens to determine a key
+var
+  lKey: PYAMLKey;
+  I : Integer;
+
+begin
+  Result:=False;
+  I:=0;
+  While (Not Result) and (i<FKeyCount) do
+    begin
+    lKey:=@FKeys[i];
+    With lKey^ do
+      begin
+      Result:=(Possible and (TokenNumber=FConsumedTokens));
+      end;
+    inc(I);
+    end;
+end;
+
+procedure TYAMLScanner.CheckPossibleKeys;
+
+var
+  i : Integer;
+  lKey: PYAMLKey;
+
+begin
+  For I:=0 to FKeyCount-1 do
+    begin
+    lKey:=@FKeys[i];
+    { a simple key is limited to a single line and has at most 1024 chars }
+    if lKey^.Possible and Not lKey^.CheckPossibleAt(CurrPos) then
+      begin
+      if (lKey^.Required) then
+        Error(lkey^.Position,SErrMissingColonInKey);
+      lKey^.Possible:=False;
+      end;
+    end;
+end;
+
+
+function TYAMLScanner.FetchNextToken: Boolean;
+
+var
+  lDone : Boolean;
+  C : AnsiChar;
+
+begin
+  Result:=True;
+  if not ReadLine then
+    begin
+    HandleStreamEnd;
+    exit(False);
+    end;
+  if not SkipWhiteSpace then
+    begin
+    HandleStreamEnd;
+    exit;
+    end;
+  CheckPossibleKeys;
+  Undent(FCurrCol);
+  lDone:=FCurrCol=1;
+  if lDone then
+    begin
+    C:=FCurrLine[FCurrCol];
+    Case C of
+    '%' : HandleDirective;
+    '-' : begin
+          lDone:=IsDocumentStart;
+          if lDone then
+            HandleDocumentIndicator(ytDocumentStart)
+          end;
+    '.' : begin
+          lDone:=IsDocumentEnd;
+          if lDone then
+            HandleDocumentIndicator(ytDocumentEnd);
+          end;
+    else
+      lDone:=False;
+    end;
+    end;
+  if lDone then
+    exit;
+  lDone:=True;
+  C:=FCurrLine[FCurrCol];
+  Case C of
+  '[': HandleFlowCollectionStart(ytFlowSequenceStart);
+  '{': HandleFlowCollectionStart(ytFlowMappingStart);
+  ']': HandleFlowCollectionEnd(ytFlowSequenceEnd);
+  '}': HandleFlowCollectionEnd(ytFlowMappingEnd);
+  ',': HandleFlowEntry;
+  '*': HandleAnchorOrAlias(ytAlias);
+  '&': HandleAnchorOrAlias(ytAnchor);
+  '!': HandleTag;
+  '''': HandleFlowScalar(True);
+  '"' : HandleFlowScalar(False);
+  '-': begin
+       if IsNextBlank then
+         HandleBlockEntry
+       else
+         HandlePlainScalar
+       end;
+  '?': begin
+       if (FFlowLevel>0) or IsNextBlank then
+         HandleKey
+       else
+         HandlePlainScalar;
+       end;
+  ':': begin
+       if (FFlowLevel>0) or IsNextBlank then
+         HandleValue
+       else
+         HandlePlainScalar;
+       end;
+  '|': begin
+       lDone:=(FFlowLevel=0);
+       if lDone then
+          HandleBlockScalar(False);
+       end;
+  '>': begin
+       lDone:=(FFlowLevel=0);
+       if lDone then
+         HandleBlockScalar(True);
+       end;
+  else
+    lDone:=False;
+  end;
+  if lDone then
+    exit;
+  if (not IsCurrBlank) and not (FCurrLine[FCurrCol] in NotScalarStartChars) then
+    begin
+    HandlePlainScalar;
+    exit;
+    end;
+  Error(CurrPos, SErrUnknownCharacter);
+end;
+
+
+function TYAMLScanner.SkipWhiteSpace : boolean;
+
+begin
+  // Skip whitespace.
+  // Handle tabs: they are not allowed in block mode at start of line or after '-' '?' ':'
+  Result:=False;
+  // if we're past the end of the line, it means we start a new line, and a simple key is allowed.
+  if (FFlowLevel=0) and (FCurrCol>FCurrLineLen) then
+    FAllowKey:=True;
+  while ReadLine do
+    begin
+    While (FCurrCol<=FCurrLineLen) do
+      Case FCurrLine[FCurrCol] of
+      ' ':
+         Inc(FCurrCol);
+      #9:
+        if (FFlowLevel > 0) or (not FAllowKey) then
+          Inc(FCurrCol)
+        else
+          Exit(True);
+      '#':
+        FCurrCol:=FCurrLineLen+1;
+      else
+        Exit(True);
+      end;
+     // If we get here, we treated the whole line. In block mode, we can have a key now.
+    if (FFlowLevel=0) then
+      FAllowKey:=True;
+    end;
+end;
+
+
+procedure TYAMLScanner.MaybeGrowTokens;
+
+Var
+  lLen: Integer;
+
+begin
+  lLen:=Length(FTokens);
+  if (FTokenCount=lLen) then
+    SetLength(FTokens,lLen+10);
+end;
+
+
+procedure TYAMLScanner.QueueToken(aToken : TYAMLTokenData);
+
+begin
+  MaybeGrowTokens;
+  FTokens[FTokenCount]:=aToken;
+  Inc(FTokenCount);
+end;
+
+
+procedure TYAMLScanner.InsertToken(aAtPos: Integer; aToken: TYAMLTokenData);
+
+var
+  I : Integer;
+
+begin
+  MaybeGrowTokens;
+  For I:=FTokenCount downto aAtpos+1 do
+    FTokens[i]:=FTokens[i-1];
+  FTokens[aAtPos]:=aToken;
+  Inc(FTokenCount);
+end;
+
+
+procedure TYAMLScanner.Undent(AColumn: Integer);
+
+begin
+  if (FFlowLevel > 0) then
+    Exit;
+  while {(FCurrIndent<>-1) and} (FCurrIndent > AColumn-1) do
+    begin
+    QueueToken(TYAMLTokenData.Create(ytBlockEnd,CurrPos));
+    FCurrIndent:=PopIndent;
+    end;
+end;
+
+
+function TYAMLScanner.PopIndent: Integer;
+
+begin
+  if FIndentCount=0 then
+    Result:=-1
+  else
+    begin
+    Dec(FIndentCount);
+    Result:=FIndents[FIndentCount];
+    end;
+end;
+
+procedure TYAMLScanner.PushIndent(aIndent : Integer);
+
+var
+  lLen : Integer;
+
+begin
+  lLen:=Length(FIndents);
+  if (FIndentCount=lLen) then
+    SetLength(FIndents,lLen+10);
+  FIndents[FIndentCount]:=aIndent;
+  Inc(FIndentCount);
+end;
+
+
+procedure TYAMLScanner.Indent(AColumn: Integer; ANumber: Integer; ATokenType: TYAMLToken; aPos: TYAMLPos);
+
+var
+  lToken: TYAMLTokenData;
+
+begin
+  if (FFlowLevel > 0) then
+    Exit;
+  if (FCurrIndent>=AColumn-1) then
+    Exit;
+  PushIndent(FCurrIndent);
+  FCurrIndent:=aColumn-1;
+  //* Create a token and insert it into the queue. */
+  Case ATokenType of
+  ytBlockMappingStart,
+  ytBlockSequenceStart:
+     lToken:=TYAMLTokenData.Create(aTokenType, aPos);
+  else
+    raise EYAMLScanner.Create(aPos,'Unexpected token for indent');
+  end;
+  if (ANumber=-1) then
+    QueueToken(lToken)
+  else
+    InsertToken(ANumber-FConsumedTokens,lToken);
+end;
+
+
+function TYAMLScanner.PeekKey: PYAMLKey;
+
+begin
+  if FKeyCount>0 then
+    Result:=@FKeys[FKeyCount-1]
+  else
+    raise EYAMLScanner.Create(SErrNoSimpleKeyAvailable);
+end;
+
+
+procedure TYAMLScanner.RemoveSimpleKey;
+
+var
+  lKey: PYAMLKey;
+
+begin
+  lKey:=PeekKey;
+  if (lKey^.Possible) and (lKey^.Required) then
+    Error(lKey^.Position,SErrMissingColonInKey);
+  lKey^.Possible:= False;
+end;
+
+
+procedure TYAMLScanner.SaveSimpleKey;
+
+var
+  lKey: PYAMLKey;
+  lIsRequired: Boolean;
+
+begin
+  // in blockcontext, a key is required if the curr column is the indent level.
+  // Note that indent is 0 based, CurrCol is 1 based.
+  lIsRequired:=(FFlowLevel=0) and (FCurrIndent=(FCurrCol-1));
+  if (FAllowKey) then
+    begin
+    RemoveSimpleKey;
+    lKey:=PeekKey;
+    lKey^.Possible:=True;
+    lKey^.Required:=lIsRequired;
+    lKey^.TokenNumber:=FConsumedTokens + FTokenCount;
+    lKey^.Position:=CurrPos;
+    end;
+end;
+
+
+procedure TYAMLScanner.PushKey(aKey : TYAMLKey);
+
+var
+  lLen : Integer;
+
+begin
+  lLen:=Length(FKeys);
+  if FKeyCount=lLen then
+    SetLength(FKeys,lLen+10);
+  FKeys[FKeyCount]:=aKey;
+  Inc(FKeyCount);
+end;
+
+
+procedure TYAMLScanner.PopKey;
+
+begin
+  If FKeyCount>0 then
+    Dec(FKeyCount);
+end;
+
+
+procedure TYAMLScanner.HandleStreamStart;
+
+var
+  lKey: TYAMLKey;
+
+begin
+  lKey:=Default(TYAMLKey);
+  FCurrIndent:=-1;
+  PushKey(lKey);
+  FAllowKey:=True;
+end;
+
+
+procedure TYAMLScanner.HandleStreamEnd;
+
+begin
+  Undent(0);
+  RemoveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(TYAMLTokenData.Create(ytEOF,CurrPos));
+end;
+
+
+procedure TYAMLScanner.HandleDirective;
+
+begin
+  Undent(0);
+  RemoveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(ScanDirective);
+end;
+
+
+procedure TYAMLScanner.HandleDocumentIndicator(ATokenType: TYAMLToken);
+
+var
+  lPos1,lPos2: TYAMLPos;
+
+begin
+  Undent(0);
+  RemoveSimpleKey;
+  FAllowKey:=False;
+  lPos1:=CurrPos;
+  Inc(FCurrCol,3);
+  lPos2:=CurrPos;
+  Case aTokenType of
+    ytDocumentStart,
+    ytDocumentEnd:
+      QueueToken(TYAMLtokenData.Create(aTokenType,lPos1,lPos2));
+  else
+    raise EYAMLScanner.Create(lPos1,'unexpected token type');
+  end;
+end;
+
+
+procedure TYAMLScanner.HandleFlowCollectionStart(ATokenType: TYAMLToken);
+
+var
+  lPos1,lPos2: TYAMLPos;
+
+begin
+  SaveSimpleKey;
+  IncreaseFlowLevel;
+  FAllowKey:=True;
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  Case ATokenType of
+  ytFlowSequenceStart,
+  ytFlowMappingStart:
+    QueueToken(TYAMLTokenData.Create(aTokenType,lPos1,lPos2))
+  else
+    raise EYAMLScanner.Create(lPos1,'unexpected token type');
+  end;
+end;
+
+
+procedure TYAMLScanner.HandleFlowCollectionEnd(ATokenType: TYAMLToken);
+
+var
+  lPos1,lPos2: TYAMLPos;
+
+begin
+  RemoveSimpleKey;
+  DecreaseFlowLevel;
+  FAllowKey:=False;
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  Case ATokenType of
+  ytFlowSequenceEnd,
+  ytFlowMappingEnd:
+    QueueToken(TYAMLTokenData.Create(aTokenType,lPos1,lPos2));
+  else
+    raise EYAMLScanner.Create(lPos1,'unexpected token type');
+  end;
+end;
+
+
+procedure TYAMLScanner.HandleFlowEntry;
+
+var
+  lPos1,lPos2: TYAMLPos;
+
+begin
+  RemoveSimpleKey;
+  FAllowKey:=True;
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  QueueToken(TYAMLTokenData.Create(ytFlowEntry,lPos1,lPos2));
+end;
+
+
+procedure TYAMLScanner.HandleBlockEntry;
+
+var
+  lPos1,lPos2 : TYAMLPos;
+
+begin
+  if (FFlowLevel > 0) then
+    Error(CurrPos,SErrUnexpectedBlockEntry);
+  if (not FAllowKey) then
+    Error(CurrPos,SErrUnexpectedBlockEntry);
+  Indent(FCurrCol,-1,ytBlockSequenceStart,CurrPos);
+  RemoveSimpleKey;
+  FAllowKey:=True;
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  QueueToken(TYAMLTokenData.Create(ytBlockEntry,lPos1,lpos2));
+end;
+
+
+procedure TYAMLScanner.HandleKey;
+
+var
+  lPos1,lPos2: TYAMLPos;
+
+begin
+  if (FFlowLevel=0) then
+    begin
+    //* Check if we are allowed to start a new key (not necessary simple). */
+    if (not FAllowKey) then
+      Error(CurrPos, SErrMappingKeysAreNotAllowedInBlockContext);
+    Indent(FCurrCol, -1, ytBlockMappingStart, CurrPos);
+    end;
+  RemoveSimpleKey;
+  FAllowKey:=(FFlowLevel=0);
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  QueueToken(TYAMLTokenData.Create(ytKey,lPos1,lPos2));
+end;
+
+
+procedure TYAMLScanner.HandleValue;
+
+var
+  lPos1,lPos2 : TYAMLPos;
+  lKey: PYAMLKey;
+
+begin
+  lKey:=PeekKey;
+  if (lKey^.Possible) then
+    begin
+    With lKey^ do
+      begin
+      InsertToken(TokenNumber - FConsumedTokens, TYAMLTokenData.Create(ytKey,Position));
+      Indent(Position.Column, TokenNumber,ytBlockMappingStart, Position);
+      end;
+    lKey^.Possible:=False;
+    FAllowKey:=(FFlowLevel=0) and (FCurrCol>=FCurrLineLen);
+    end
+  else
+    begin
+    if (FFlowLevel=0) then
+      begin
+      if (not FAllowKey) then
+        Error(CurrPos, SErrMappingValuesNotAllowedInThisContext);
+      Indent(FCurrCol, -1, ytBlockMappingStart,CurrPos);
+      end;
+    FAllowKey:=(FFlowLevel=0);
+    end;
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lPos2:=CurrPos;
+  QueueToken(TYAMLTokenData.Create(ytValue,lPos1,lPos2));
+end;
+
+
+procedure TYAMLScanner.HandleAnchorOrAlias(ATokenType: TYAMLToken);
+
+begin
+  SaveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(ScanAnchorOrAlias(ATokenType));
+end;
+
+
+procedure TYAMLScanner.HandleTag;
+
+begin
+  SaveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(ScanTag);
+end;
+
+
+procedure TYAMLScanner.HandleBlockScalar(aFolded: Boolean);
+
+begin
+  RemoveSimpleKey;
+  FAllowKey:=True;
+  QueueToken(ScanBlockScalar(aFolded));
+end;
+
+
+procedure TYAMLScanner.HandleFlowScalar(aSingle: Boolean);
+
+begin
+  SaveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(ScanFlowScalar(aSingle));
+end;
+
+
+procedure TYAMLScanner.HandlePlainScalar;
+
+begin
+  SaveSimpleKey;
+  FAllowKey:=False;
+  QueueToken(ScanPlainScalar);
+end;
+
+
+function TYAMLScanner.ReadLine: Boolean;
+
+begin
+  if FCurrCol>FCurrLineLen then
+    FCurrCol:=0;
+  Result:=(FCurrCol>0);
+  if not (Result or FReader.EOF) then
+    begin
+    FCurrLine:=FReader.ReadLine;
+    FCurrLineLen:=Length(FCurrLine);
+    Inc(FCurrRow);
+    Result:=True;
+    if not (FSkipAllowKeyCheck or InFlowContext) then
+      FAllowKey:=True;
+    FCurrCol:=1
+    end;
+end;
+
+
+function TYAMLScanner.ScanDirective: TYAMLTokenData;
+
+var
+  lPos1,lPos2 : TYAMLPos;
+  lmajor, lMinor: Integer;
+  lName,lPrefix, lHandle : TYAMLString;
+
+begin
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  lName:=ScanDirectiveName(lPos1);
+  Case lName of
+  'YAML':
+    begin
+    ScanVersionDirectiveValue(lPos1, lMajor, lMinor);
+    lPos2:=CurrPos;
+    Result:=TYAMLTokenData.Create(ytVersionDirective,lPos1,lPos2,IntToStr(lMajor),IntToStr(lMinor));
+    end;
+  'TAG':
+    begin
+    ScanTagDirectiveValue(lPos1, lHandle, lPrefix);
+    lPos2:=CurrPos;
+    Result:=TYAMLTokenData.Create(ytTagDirective, lPos1,lPos2, lHandle, lPrefix);
+    end
+  else
+    Error(CurrPos, SErrUnknownDirective, [lName]);
+  end;
+  Inc(FCurrCol);
+  while (FCurrCol<=FCurrLineLen) and IsCurrBlank do
+    Inc(FCurrCol);
+
+  if (FCurrLine[FCurrCol]='#') then
+    FCurrCol:=FCurrLineLen+1;
+  if (FCurrCol<=FCurrLineLen) then
+    Error(CurrPos, SErrUnexpectedTrailingData);
+  ReadLine;
+end;
+
+
+function TYAMLScanner.IsCurrAlpha: Boolean;
+
+begin
+  Result:=(FCurrCol<=FCurrLineLen) and (FCurrLine[FCurrCol] in AlphaChars);
+end;
+
+
+function TYAMLScanner.IsCurrDigit: Boolean;
+
+begin
+  Result:=(FCurrCol<=FCurrLineLen) and (FCurrLine[FCurrCol] in DigitChars);
+end;
+
+
+function TYAMLScanner.ScanDirectiveName(aPos: TYAMLPos): TYAMLString;
+
+var
+  lStart : Integer;
+
+begin
+  Result:='';
+  lStart:=FCurrCol;
+  Inc(FCurrCol);
+  while IsCurrAlpha do
+    Inc(FCurrCol);
+  Result:=Copy(FCurrLine,lStart,FCurrCol-lStart);
+  if (Result='') then
+    Error(aPos, SErrMissingDirectiveName);
+  if Not (IsCurrBlank) then
+    Error(aPos,SErrUnexpectedCharacter, [FCurrLine[FCurrCol]]);
+end;
+
+
+procedure TYAMLScanner.ScanVersionDirectiveValue(aPos: TYAMLPos; out AMajor: Integer; out AMinor: Integer);
+
+  Function ScanDigit : Integer;
+
+  var
+    lStart: Integer;
+    lNr: TYAMLString;
+
+  begin
+    Result:=0;
+    lStart:=FCurrCol;
+    while IsCurrDigit do
+      Inc(FCurrCol);
+    if (FCurrCol-lStart>MaxVersionDigitLength) then
+        Error(CurrPos, SErrVersionNumberTooLong);
+    lNr:=Copy(FCurrLine,lStart,FCurrCol-lStart);
+    Result:=StrToIntDef(lNr, -1);
+    if (Result=-1) then
+      Error(aPos, SErrInvalidVersionNumber, [lNr]);
+  end;
+
+begin
+  While (FCurrCol<=FCurrLineLen) and IsCurrBlank do
+    Inc(FCurrCol);
+  AMajor:=ScanDigit;
+  if ((FCurrCol>FCurrLineLen) or (FCurrLine[FCurrCol]<>'.')) then
+    Error(CurrPos, SErrInvalidCharacterInVersion, [FCurrLine[FCurrCol]]);
+  Inc(FCurrCol);
+  AMinor:=ScanDigit;
+end;
+
+
+procedure TYAMLScanner.ScanTagDirectiveValue(aPos: TYAMLPos; out AHandle: TYAMLString; out APrefix: TYAMLString);
+
+begin
+  While IsCurrBlank(False) do
+    Inc(FCurrCol);
+  aHandle:=ScanTagHandle(True,aPos);
+  if not IsCurrBlank(False) then
+    Error(CurrPos, SErrUnexpectedCharInDirectiveValue);
+  While IsCurrBlank(False) do
+    Inc(FCurrCol);
+  aPrefix:=ScanTagUri(True,True,'', CurrPos);
+  if (not IsCurrBlank) then
+    Error(CurrPos, SErrNoWhiteSpaceAtDirectiveEnd);
+end;
+
+
+function TYAMLScanner.GetCurrChar: AnsiChar;
+
+begin
+  if FCurrCol>FCurrLineLen then
+    Result:=#0
+  else
+    Result:=FCurrLine[FCurrCol];
+end;
+
+
+function TYAMLScanner.GetNextChar: AnsiChar;
+
+begin
+  if FCurrCol>=FCurrLineLen then
+    Result:=#0
+  else
+    Result:=FCurrLine[FCurrCol+1];
+end;
+
+
+function TYAMLScanner.ScanTagHandle(aIsDirective: Boolean; aPos: TYAMLPos): TYAMLString;
+
+var
+  lStart : Integer;
+
+begin
+  if GetCurrChar <> '!' then
+    Error(CurrPos, SErrUnexpectedCharacterInTag, [DirectiveNames[aIsDirective], GetCurrChar]);
+  LStart:=FCurrCol;
+  Inc(FCurrCol);
+  while (IsCurrAlpha) do
+    Inc(FCurrCol);
+  if (GetCurrChar<>'!') then
+    Error(CurrPos,SErrUnexpectedCharacterInTag, [DirectiveNames[aIsDirective], GetCurrChar]);
+  inc(FCurrCol);
+  Result:=Copy(FCurrLine,lStart,FCurrCol-lStart);
+end;
+
+
+function TYAMLScanner.ScanTagUri(AIsVerbatim: Boolean; AIsDirective: Boolean; AHead: TYAMLString; aPos: TYAMLPos): TYAMLString;
+
+var
+  C : AnsiChar;
+
+begin
+  if Length(AHead)> 1 then
+    Result:=Copy(AHead,2,Length(AHead) - 1)
+  else
+    Result:='';
+  C:=GetCurrChar;
+  while IsCurrAlpha
+        or (C in TagURIChars)
+        or (AIsVerbatim and (C in [',','[',']'])) do
+    begin
+    if (C='%') then
+      Result:= Result+ScanUriEscapes(AIsDirective, CurrPos)
+    else
+      Result:=Result + C;
+    Inc(FCurrCol);
+    C:=GetCurrChar;
+    end;
+  if (Result='') then
+    Error(CurrPos, SErrMissingTagURI);
+end;
+
+
+function TYAMLScanner.ScanUriEscapes(aIsDirective: Boolean; aPos: TYAMLPos): TYAMLString;
+
+var
+  lWidth: Integer;
+  lByte: Byte;
+  C1,C2 : AnsiChar;
+
+begin
+  Result:='';
+  lWidth:=0;
+  repeat
+    lByte:=0;
+    Inc(FCurrCol); // Skip %
+    C1:=GetCurrChar;
+    Inc(FCurrCol);
+    C2:=GetCurrChar;
+    if Not ((C1 in HexChars) and (C2 in HexChars)) then
+      Error(CurrPos, SErrInvalidURIEscapeChar, [DirectiveNames[aIsDirective]]);
+    lByte:=StrToInt('$'+C1+C2);
+    if (lWidth=0) then
+      begin
+      if (lByte and $80)=$00 then
+        lWidth:=1
+      else if (lByte and $E0)=$C0 then
+        lWidth:=2
+      else if (lByte and $F0)=$E0 then
+        lWidth:=3
+      else if (lByte and $F8)=$F0 then
+        lWidth:=4
+      else
+        Error(aPos,SErrInvalidUTF8Char,[DirectiveNames[aIsDirective]]);
+      end
+    else if ((lByte and $C0) <> $80) then
+      Error(aPos, Format(SErrInvalidUTF8Char, [DirectiveNames[aIsDirective]]));
+    Result:=Result+AnsiChar(lByte);
+    Dec(lWidth);
+  until (lWidth=0);
+end;
+
+
+function TYAMLScanner.ScanAnchorOrAlias(ATokenType: TYAMLToken): TYAMLTokenData;
+
+const
+  AnchorAliasNames : Array[Boolean] of string = ('alias','anchor');
+
+var
+  lPos1,lPos2: TYAMLPos;
+  lValue: TYAMLString;
+
+begin
+  lValue:='';
+  inc(FCurrCol);
+  lPos1:=CurrPos;
+  while IsCurrAlpha do
+    Inc(FCurrCol);
+  lPos2:=CurrPos;
+  if (LPos2.Column-LPos1.Column)=0 then
+    Error(CurrPos, SErrInvalidAnchorAliasName,[AnchorAliasNames[aTokenType=ytAnchor]]);
+  if not (IsCurrBlank(False) or (GetCurrChar in ['?', ':', ',', ']', '}', '%', '@', '`'])) then
+    Error(CurrPos, SErrInvalidCharacterInAnchor, [GetCurrChar]);
+  lValue:=Copy(FCurrLine,LPos1.Column,LPos2.Column-LPos1.Column);
+  Result:=TYAMLTokenData.Create(aTokenType,lPos1,lPos2,lValue);
+end;
+
+
+function TYAMLScanner.ScanTag: TYAMLTokenData;
+
+var
+  lPos1,lPos2: TYAMLPos;
+  lSuffix,lHandle: TYAMLString;
+
+begin
+  lHandle:='';
+  lSuffix:='';
+  lPos1:=CurrPos;
+  inc(FCurrCol);
+  if (GetCurrChar='<') then
+    begin
+    Inc(FCurrCol);
+    lSuffix:=ScanTagUri(True, False,'',lPos1);
+    if (GetCurrChar <> '>') then
+      Error(CurrPos, SErrInvalidTagEnd);
+    Inc(FCurrCol);
+    end
+  else
+    begin
+    lhandle:=ScanTagHandle(False,lPos1);
+    if (Length(lHandle)>1) and (lHandle[1]='!') and (lHandle[Length(LHandle)]='!') then
+      lSuffix:=ScanTagUri(False, False, '', lPos1)
+    else
+      begin
+      lSuffix:=ScanTagUri(False, False,lHandle, lPos1);
+      lHandle:='!';
+      if (lSuffix='') then
+        begin
+        lHandle:='';
+        lSuffix:='!';
+        end;
+      end;
+    end;
+  if (not IsCurrBlank) then
+    if (FFlowLevel=0) or (GetCurrChar<>',') then
+      Error(CurrPos, SErrInvalidWhitespace, [GetCurrChar]);
+  lPos2:=CurrPos;
+  Result:=TYAMLTokenData.Create(ytTag,lPos1,lPos2,lHandle,lSuffix);
+end;
+
+
+procedure TYAMLScanner.HandleBlockScalarWhiteSpace(var AIndent: Integer; var ABreaks: TYAMLString; aPos: TYAMLPos; out  aPos2: TYAMLPos);
+
+var
+  maxIndent: Integer;
+
+begin
+  maxIndent:=0;
+  aPos2:=CurrPos;
+  while (True) do
+    begin
+    // IsCurrBlank will return false if CurrCol>CurrLineLen, no need to check again
+    while IsCurrBlank(False) and ((aIndent=0) or (FCurrCol < (aIndent+1))) do
+      Inc(FCurrCol);
+    if (FCurrCol-1>maxIndent) then
+      maxIndent:=FCurrCol-1;
+    if ((AIndent=0) or (FCurrCol < (AIndent+1))) and (GetCurrChar=TabChar) then
+      Error(CurrPos, SErrInvalidIndentChar);
+    // have chars ?
+    if FCurrCol<=FCurrLineLen then
+      Break;
+    ReadLine;
+    aBreaks:= aBreaks + #10;
+    aPos2:=CurrPos;
+    end;
+  if (AIndent>0) then
+    Exit;
+  AIndent:=maxIndent;
+  if (AIndent<=FCurrIndent) then
+    AIndent:=FCurrIndent+1;
+  if (AIndent<=0) then
+    AIndent:=1;
+end;
+
+
+function TYAMLScanner.ScanBlockScalar(aFolded: Boolean): TYAMLTokenData;
+
+Type
+  TBlockStart=(bsNeutral,bsPlus,bsMinus);
+
+Const
+  Starts : Array[Boolean] of TBlockStart=(bsMinus,bsPlus);
+  Scalars : array[Boolean] of TYAMLToken=(ytScalarLiteral,ytScalarFolded);
+
+var
+  lPos1,lPos2: TYAMLPos;
+  lStart : TBlockStart;
+  lLeading: TYAMLString;
+  lTrailing: TYAMLString;
+  lIncrement: Integer;
+  lindent: Integer;
+  lBlank: Boolean;
+  lTrailingBlank: Boolean;
+  lValue: TYAMLString;
+  C : AnsiChar;
+
+  procedure AppendValue(const s : string); inline;
+
+  begin
+    lValue:=lValue+S;
+  end;
+
+
+begin
+  lLeading:='';
+  lTrailing:='';
+  lStart:=bsNeutral;
+  lIncrement:=0;
+  lindent:=0;
+  lBlank:=False;
+  lTrailingBlank:=False;
+  lValue:='';
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  C:=GetCurrChar;
+  if (C in ['+', '-']) then
+    begin
+    lStart:=Starts[C='+'];
+    inc(FCurrCol);
+    if (IsCurrDigit) then
+      begin
+      if (GetCurrChar='0') then
+        Error(CurrPos, SErrInvalidIndentationChar);
+      lIncrement:=Ord(GetCurrChar)- Ord('0');
+      end;
+    end
+  //* Do the same as above, but in the opposite order. */
+  else if (C in DigitChars) then
+    begin
+    if (GetCurrChar='0') then
+      Error(CurrPos,SErrInvalidIndentationChar);
+    lIncrement:=Ord(C)- Ord('0');
+    inc(FCurrCol);
+    C:=GetCurrChar;
+    if (C in ['+', '-']) then
+      begin
+      lStart:=Starts[C='+'];
+      Inc(FCurrCol);
+      end;
+    end;
+  while IsCurrBlank(False) do
+    Inc(FCurrCol);
+  if GetCurrChar='#' then
+    FCurrCol:=FCurrLineLen+1;
+  if FCurrCol<=FCurrLineLen then
+    Error(CurrPos, SErrUnexpectedEndOfLine);
+  if FCurrCol>FCurrLineLen then
+    ReadLine;
+  lPos2:=CurrPos;
+
+  if (lIncrement <> 0) then
+    begin
+    if FCurrIndent >= 0 then
+      lindent:=FCurrIndent + lIncrement
+    else
+      lindent:=lIncrement;
+  end;
+  HandleBlockScalarWhiteSpace(lIndent,lTrailing,lPos1,lPos2);
+  while ((FCurrCol-1)=lIndent) and ReadLine do
+    begin
+    lTrailingBlank:=IsCurrBlank(False);
+    //* Check if we need to fold the leading line break. */
+    if aFolded
+        and ((lLeading<>'') and (lLeading[1]=#10))
+        and (not lBlank) and (not lTrailingBlank) then
+      begin
+      if (lTrailing='') then
+        AppendValue(' ');
+      lLeading:='';
+      end
+    else
+      begin
+      AppendValue(lLeading);
+      lLeading:='';
+      end;
+    AppendValue(lTrailing);
+    lTrailing:='';
+    lBlank:=IsCurrBlank(False);
+    AppendValue(Copy(FCurrLine,FCurrCol,FCurrLineLen-FCurrCol+1));
+    FCurrCol:=FCurrLineLen+1;
+    ReadLine;
+    lLeading:=lLeading+#10;
+    HandleBlockScalarWhiteSpace(lIndent,lTrailing,lPos1,lPos2);
+    end;
+  if (lStart<>bsMinus) then
+    AppendValue(lLeading);
+  if (lStart=bsPlus) then
+    AppendValue(lTrailing);
+  Result:=TYAMLTokenData.Create(Scalars[aFolded],lPos1,lPos2,lValue);
+end;
+
+
+function TYAMLScanner.GetEscapeChar : TYAMLString;
+
+var
+  lSuff : TYAMLString;
+  i,lLen : Integer;
+  lPoint : Cardinal;
+  C : AnsiChar;
+
+begin
+  Result:='';
+  lLen:=0;
+  inc(FCurrCol);
+  C:=GetCurrChar;
+  //* Check the escape character. */
+  case C of
+    '0': lSuff:=#00;
+    'a': lSuff:=#07;
+    'b': lSuff:=#08;
+    't': lSuff:=#09;
+    'n': lSuff:=#10;
+    'v': lSuff:=#11;
+    'f': lSuff:=#12;
+    'r': lSuff:=#13;
+    'e': lSuff:=#$1B;
+    ' ': lSuff:=#32;
+    '"': lSuff:='"';
+    '/': lSuff:='/';
+    '\': lSuff:='\';
+    'N': lSuff:=#$C2#$85;
+    '_': lSuff:=#$C2#$A0;
+    'L': lSuff:=#$E2#$80#$A8;
+    'P': lSuff:=#$E2#$80#$A9;
+    'x': lLen:=2;
+    'u': lLen:=4;
+    'U': lLen:=8;
+  else
+     Error(CurrPos, SErrInvalidEscapeChar,[C]);
+  end;
+  if (lLen=0) then
+    Exit(lSuff);
+  lPoint:=0;
+  for i:=1 to lLen do
+    begin
+    inc(FCurrCol);
+    C:=GetCurrChar;
+    if not (C in HexChars) then
+      Error(CurrPos, SErrInvalidHexChar, [C]);
+    lPoint:=(lPoint shl 4) + StrToInt('$'+C);
+    end;
+  if (((lPoint >= $D800) and (lPoint <= $DFFF)) or (lPoint > $10FFFF)) then
+    Error(CurrPos, SErrInvalidUnicodePoint, [HexStr(Pointer(lPoint))]);
+  if (lPoint <= $7F) then
+    Result:=AnsiChar(lPoint)
+  else if (lPoint <= $07FF) then
+    Result:=AnsiChar($C0 + (lPoint shr 6)) + AnsiChar($80 + (lpoint and $3F))
+  else if (lPoint <= $FFFF) then
+    Result:=AnsiChar($E0 + (lPoint shr 12))
+            + AnsiChar($80 + ((lPoint shr 6) and $3F))
+            + AnsiChar($80 + (lPoint and $3F))
+  else
+    Result:=AnsiChar($F0 + (lPoint shr 18))
+            + AnsiChar($80 + ((lPoint shr 12) and $3F))
+            + AnsiChar($80 + ((lPoint shr 6) and $3F))
+            + AnsiChar($80 + (lPoint and $3F));
+end;
+
+
+procedure TYAMLScanner.DoFold(var aValue, aLeading, aTrailing: TYAMLString);
+
+begin
+  if (aLeading<>'') and (aLeading[1]=#10) then
+    begin
+    if (aTrailing='') then
+      aValue:=aValue + ' '
+    else
+      begin
+      aValue:=aValue + aTrailing;
+      aTrailing:='';
+      end;
+    aLeading:='';
+    end
+  else
+    begin
+    aValue:=aValue + aLeading;
+    aValue:=aValue + aTrailing;
+    aLeading:='';
+    aTrailing:='';
+    end;
+end;
+
+
+function TYAMLScanner.ScanFlowScalar(AIsSingle: Boolean): TYAMLTokenData;
+
+const
+  Scalars : array[Boolean] of TYAMLToken=(ytScalarDouble,ytScalarSingle);
+  QuoteChars : Array[Boolean] of Char=('"','''');
+
+var
+  lBlanks: Boolean;
+  lPos1,lPos2: TYAMLPos;
+  lValue: TYAMLString;
+  lLeading: TYAMLString;
+  lTrailing: TYAMLString;
+  whitespaces: TYAMLString;
+  c : ansichar;
+
+  procedure AppendValue(const s : string); inline;
+
+  begin
+    lValue:=lValue+S;
+  end;
+
+begin
+  lBlanks :=False;
+  lLeading:='';
+  lTrailing:='';
+  lValue:='';
+  whitespaces:='';
+  lPos1:=CurrPos;
+  Inc(FCurrCol);
+  while (True) do
+    begin
+    if (FCurrCol=1) and (IsDocumentStart or IsDocumentEnd) then
+      Error(CurrPos, SErrInvalidDocumentIndicator);
+    //* Check for EOF. */
+    if (FCurrCol>FCurrLineLen) and not ReadLine then
+      Error(CurrPos, SErrUnexpectedEOS);
+    lBlanks:=False;
+    while not IsCurrBlank do
+      begin
+      C:=GetCurrChar;
+      // Single quote
+      if aIsSingle then
+        begin
+        if (C<> '''') then
+          begin
+          AppendValue(C);
+          Inc(FCurrCol);
+          end
+        else if (GetNextChar='''') then
+          begin
+          AppendValue('''');
+          Inc(FCurrCol,2);
+          end
+        else if (C= '''') then
+          Break
+        end
+      else
+        // Double quote
+        begin
+        if (C='"') then
+          Break
+        else if (C='\') then
+          begin
+          if (FCurrCol<FCurrLineLen) then
+            begin
+            AppendValue(GetEscapeChar);
+            Inc(FCurrCol);
+            end
+          else
+            begin
+            FCurrCol:=FCurrLineLen+1;
+            ReadLine;
+            lBlanks:=True;
+            Break;
+            end
+          end
+        else
+          begin
+          AppendValue(C);
+          Inc(FCurrCol);
+          end;
+        end;
+      end;
+    if C=QuoteChars[aIsSingle] then
+      Break;
+    while IsCurrBlank(True) do
+      begin
+      if IsCurrBlank(False) then
+        begin
+        if not lBlanks then
+          whitespaces:=whitespaces + GetCurrChar;
+        inc(FCurrCol);
+        end
+      else
+        begin
+        if not Readline then
+          Error(CurrPos,SErrUnexpectedEOS);
+        if lBlanks then
+          lTrailing:=lTrailing + #10
+        else
+          begin
+          whitespaces:='';
+          Lleading:=lLeading+#10;
+          lBlanks:=True;
+          end
+        end;
+      end;
+
+    if (lBlanks) then
+      DoFold(lValue,lLeading,lTrailing)
+    else
+      begin
+      AppendValue(whitespaces);
+      whitespaces:='';
+      end;
+    end; // While
+  Inc(FCurrCol);
+  if (FFlowLevel=0) and (FCurrCol>FCurrLineLen) then
+    FAllowKey:=True;
+  lPos2:=CurrPos;
+  Result:=TYAMLTokenData.Create(Scalars[aIsSingle],lPos1,lPos2,lValue)
+end;
+
+
+function TYAMLScanner.ScanPlainScalar: TYAMLTokenData;
+
+var
+  lBlanks: Boolean;
+  lPos1, lPos2: TYAMLPos;
+  lValue : TYAMLString;
+  lLeading: TYAMLString;
+  lTrailing: TYAMLString;
+  whitespaces: TYAMLString;
+  lindent: Integer;
+  C,C2 : AnsiChar;
+
+  procedure AppendValue(const s : string); inline;
+
+  begin
+    lValue:=lValue+S;
+  end;
+
+begin
+  lLeading:='';
+  lTrailing:='';
+  lValue:='';
+  whitespaces:='';
+  lBlanks:=False;
+  lindent:=FCurrIndent + 1;
+  lPos1:=CurrPos;
+  lPos2:=lPos1;
+  FSkipAllowKeyCheck:=True;
+  try
+    while ReadLine do
+      begin
+      if (FCurrCol=1) and (IsDocumentStart or isDocumentEnd) then
+        Break;
+      if (GetCurrChar='#') then
+        Break;
+      while not IsCurrBlank(True) do
+        begin
+        C:=GetCurrChar;
+        C2:=GetNextChar;
+        if (FFlowLevel > 0) and (C=':') and (C2 in FlowIndicatorChars) then
+            Error(CurrPos, SErrUnexpectedColon);
+        if ((C=':') and IsNextBlank)
+           or ((FFlowLevel>0) and (C in FlowIndicatorChars)) then
+          Break;
+        if lBlanks or (whitespaces <> '') then
+          begin
+          if (lBlanks) then
+            begin
+            DoFold(lValue,lLeading,lTrailing);
+            lBlanks:=False;
+            end
+          else
+            begin
+            AppendValue(whitespaces);
+            whitespaces:='';
+            end;
+          end;
+        AppendValue(C);
+        Inc(FCurrCol);
+        end;
+      if not IsCurrBlank(True)  then
+        Break;
+      while IsCurrBlank(True) do
+        begin
+        if IsCurrBlank(False) then
+          begin
+          if lBlanks and ((FCurrCol-1)<lIndent) and (GetCurrChar=TabChar) then
+            Error(CurrPos, SErrUnexpectedTab);
+          if not lBlanks then
+            begin
+            lBlanks:=FCurrCol=1;
+            whitespaces:=whitespaces + GetCurrChar;
+            end;
+          Inc(FCurrCol);
+          end
+        else
+          begin
+          if lBlanks then
+            lTrailing:=lTrailing+#10
+          else
+            begin
+            whitespaces:='';
+            lLeading:=lLeading+#10;
+            lBlanks:=True;
+            end;
+          if not ReadLine then break;
+          end;
+        end;
+
+      if (FFlowLevel=0) and (FCurrCol-1 < lIndent) then
+        Break;
+      end;
+  finally
+    FSkipAllowKeyCheck:=False
+  end;
+  Result:=TYAMLTokenData.Create(ytScalarPlain, lPos1, lPos2,lValue);
+  if (lBlanks) or ((FFlowLevel=0) and (FCurrCol>FCurrLineLen))  then
+    FAllowKey:=True;
+end;
+
+
+end.
+

+ 75 - 0
packages/fcl-yaml/src/fpyaml.strings.pp

@@ -0,0 +1,75 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML string constants
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpyaml.strings;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+resourcestring
+  // Data
+  SErrIsNotA = '%s is not a valid %s';
+  SErrIndexOutOfBounds = 'Index out of bounds: %d';
+  // Scanner
+  SErrInvalidAnchorAliasName = 'An empty %s name is not allowed.';
+  SErrInvalidDocumentIndicator = 'Unexpected document indicator.';
+  SErrInvalidCharacterInAnchor = 'Invalid character in anchor or alias: "%s".';
+  SErrInvalidCharacterInVersion = 'Invalid character in version: "%s".';
+  SErrInvalidEscapeChar = 'Invalid escape character "%s".';
+  SErrInvalidHexChar = 'Invalid hexadecimal character: "%s".';
+  SErrInvalidIndentationChar = 'Indentation specifier cannot be 0.';
+  SErrInvalidIndentChar = 'Invalid tab character, expected indentation space.';
+  SErrInvalidTagEnd = 'Tag did not end on ">".';
+  SErrInvalidUnicodePoint = 'Invalid Unicode character escape code.';
+  SErrInvalidURIEscapeChar = 'Invalid URI escaped octet in "%s".';
+  SErrInvalidUTF8Char = 'Invalid UTF-8 character in "%s".';
+  SErrInvalidVersionNumber = 'Invalid version number: "%s".';
+  SErrInvalidWhitespace = 'Invalid character: Expected whitespace or line break, got: "%s".';
+  SErrMappingKeysAreNotAllowedInBlockContext = 'Mapping keys are not allowed in block context.';
+  SErrMappingValuesNotAllowedInThisContext = 'Mapping values are not allowed in this context.';
+  SErrMissingColonInKey = 'Missing ":" while scanning for a simple key.';
+  SErrMissingDirectiveName = 'Missing directive name.';
+  SErrMissingTagURI = 'Missing tag URI.';
+  SErrNoSimpleKeyAvailable = 'No simple key available.';
+  SErrNoWhiteSpaceAtDirectiveEnd = 'Tag directive must end with whitespace or line break.';
+  SErrUnexpectedBlockEntry = 'block entries are not allowed in flow context.';
+  SErrUnexpectedCharacter = 'Unexpected character found: "%s".';
+  SErrUnexpectedCharacterInTag = 'Unexpected character in "%s". Expected "!", got "%s" instead.';
+  SErrUnexpectedCharInDirectiveValue = 'Unexpected character in directive value.';
+  SErrUnexpectedColon = 'Unexpected ":".';
+  SErrUnexpectedEndOfLine = 'Did not find expected line break or comment.';
+  SErrUnexpectedEOS = 'Unexpected end of stream.';
+  SErrUnexpectedTab = 'Tab character encountered that violates indentation.';
+  SErrUnexpectedTrailingData = 'Unexpected trailing data after directive.';
+  SErrUnknownCharacter = 'Unknown character encountered.';
+  SErrUnknownDirective = 'Unknown directive name: "%s".';
+  SErrVersionNumberTooLong = 'Version number too long.';
+
+  // Parser
+  SErrDoubleAnchor    = 'Double anchor: encountered new anchor "%s", current is "%s".';
+  SErrDoubleVersion   = 'Double version directive: encountered new version "%s", current is "%s".';
+  SErrAliasNotAllowed = 'Alias not allowed at stream level.';
+  SErrUnexpectedToken = 'Unexpected token %s with value: "%s".';
+
+  // Convert to JSON
+  SErrOnlyScalarKeys = 'Only scalar keys can be converted to JSON keys.';
+  SErrUnknownYAMLtype = 'Unknown YAML data type : "%s".';
+  SErrOnlySingleDocument = 'Can only convert YAML stream with 1 document.';
+  SErrOnlySingleValue = 'Can only convert YAML document with 1 value.';
+
+implementation
+
+end.
+

+ 147 - 0
packages/fcl-yaml/src/fpyaml.types.pp

@@ -0,0 +1,147 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML basic types
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpyaml.types;
+
+{$mode ObjFPC}
+{$H+}
+{$modeswitch advancedrecords}
+{$modeswitch typehelpers}
+
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses System.SysUtils, fpyaml.strings;
+{$ELSE}
+uses SysUtils, fpyaml.strings;
+{$ENDIF}
+
+Type
+  EYAML = class(Exception);
+
+  TYAMLString = AnsiString;
+
+  { TYAMLVersion }
+
+  TYAMLVersion = record
+    Major: Integer;
+    Minor: Integer;
+    Constructor Create(const aMajor, aMinor : Integer);
+    function ToString : String;
+    function IsEmpty : Boolean;
+  end;
+
+  { TYAMLTag }
+
+  TYAMLTag = record
+    Handle: TYAMLString;
+    Prefix: TYAMLString;
+    constructor Create(const aHandle, aPrefix: TYAMLString);
+  end;
+  TYAMLTagArray = Array of TYamlTag;
+
+  TYAMLTagType = (yttCustom,yttNull,yttBoolean,yttString,yttInteger,yttFloat,yttTimeStamp,yttSequence,yttMap);
+
+  { TYAMLTagTypeHelper }
+
+  TYAMLTagTypeHelper = Type helper for TYAMLTagType
+  private
+    procedure SetAsString(AValue: String);
+  public
+    class function FromString(aString : String) : TYAMLTagType; static;
+    function ToString: String;
+    Property AsString : String Read ToString Write SetAsString;
+  end;
+
+  TYAMLScalarKind = (
+    yskPlain,
+    yskSingle,
+    yskDouble,
+    yskLiteral,
+    yskFolded);
+
+  TYAMLCollectionKind = (
+    yckBlock,
+    yckFlow);
+
+Const
+  YAMLTagNames : Array[TYAMLTagType] of string = (
+  '',
+  'tag:yaml.org,2002:null',
+  'tag:yaml.org,2002:bool',
+  'tag:yaml.org,2002:str',
+  'tag:yaml.org,2002:int',
+  'tag:yaml.org,2002:float',
+  'tag:yaml.org,2002:timestamp',
+  'tag:yaml.org,2002:seq',
+  'tag:yaml.org,2002:map');
+
+implementation
+
+
+{ TYAMLVersion }
+
+constructor TYAMLVersion.Create(const aMajor, aMinor: Integer);
+begin
+  Major:=aMajor;
+  Minor:=aMinor;
+end;
+
+function TYAMLVersion.ToString: String;
+begin
+  Result:=Format('%d.%d',[Major,Minor]);
+end;
+
+function TYAMLVersion.IsEmpty: Boolean;
+begin
+  Result:=(Major=0) and (Minor=0);
+end;
+
+{ TYAMLTag }
+
+constructor TYAMLTag.Create(const aHandle, aPrefix: TYAMLString);
+begin
+  Handle:=aHandle;
+  Prefix:=aPrefix;
+end;
+
+{ TYAMLTagTypeHelper }
+
+procedure TYAMLTagTypeHelper.SetAsString(AValue: String);
+var
+  T : TYAMLTagType;
+
+begin
+  Self:=yttCustom;
+  for T in TYAMLTagType do
+    if T.ToString=aValue then
+      begin
+      Self:=T;
+      exit;
+      end;
+end;
+
+class function TYAMLTagTypeHelper.FromString(aString: String): TYAMLTagType;
+begin
+  Result:=Default(TYAMLTagType);
+  Result.AsString:=aString;
+end;
+
+function TYAMLTagTypeHelper.ToString: String;
+begin
+  Result:=YAMLTagNames[Self];
+end;
+
+end.
+

+ 109 - 0
packages/fcl-yaml/test/testyaml.lpi

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="testyaml"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+      <Icon Value="0"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <RequiredPackages>
+      <Item>
+        <PackageName Value="FCL"/>
+      </Item>
+    </RequiredPackages>
+    <Units>
+      <Unit>
+        <Filename Value="testyaml.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="utyamlparser.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.data.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.scanner.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="utyamlscanner.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.types.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="utyamldata.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.parser.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.json.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="../src/fpyaml.strings.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="testyaml"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 21 - 0
packages/fcl-yaml/test/testyaml.lpr

@@ -0,0 +1,21 @@
+program testyaml;
+
+{$mode objfpc}{$H+}
+
+uses
+  ConsoleTestRunner, utyamlparser, fpyaml.data, fpyaml.scanner, utyamlscanner, fpyaml.types, utyamldata, fpyaml.parser, fpyaml.json,
+  fpyaml.strings;
+
+{$R *.res}
+
+var
+  Application : TTestRunner;
+begin
+  DefaultFormat:=fPlain;
+  DefaultRunAllTests:=True;
+  Application:=TTestRunner.Create(nil);
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+

+ 75 - 0
packages/fcl-yaml/test/utcyamlparser.pp

@@ -0,0 +1,75 @@
+unit utcyamlparser;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, fpyaml.types, fpyaml.parser, fpyaml.data, utyamldata;
+
+type
+
+  { TCYamlParser }
+
+  TCYamlParser= class(TTestYAMLData)
+  private
+    FParser: TYAMLParser;
+    FYAMLDocument: TYAMLDocument;
+    FYAMLStream: TYAMLStream;
+    function GetDocument: TYAMLDocument;
+    function GetStream: TYAMLStream;
+    function GetValue: TYAMLData;
+  protected
+    procedure Parse(aContent : Array of string);
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property Parser : TYAMLParser Read FParser;
+    property YAML : TYAMLStream Read GetStream;
+    property Document: TYAMLDocument Read GetDocument;
+    property Value: TYAMLData Read GetValue;
+  published
+    procedure TestHookUp;
+  end;
+
+implementation
+
+procedure TCYamlParser.TestHookUp;
+begin
+//  Ignore('Write your own test');
+end;
+
+function TCYamlParser.GetDocument: TYAMLDocument;
+begin
+  Stream.Count
+end;
+
+function TCYamlParser.GetStream: TYAMLStream;
+begin
+
+end;
+
+function TCYamlParser.GetValue: TYAMLData;
+begin
+
+end;
+
+procedure TCYamlParser.Parse(aContent: array of string);
+begin
+  FParser:=TYAMLParser.Create(aContent);
+  SetData(FParser.
+end;
+
+procedure TCYamlParser.SetUp;
+begin
+end;
+
+procedure TCYamlParser.TearDown;
+begin
+
+end;
+
+initialization
+
+  RegisterTest(TCYamlParser);
+end.
+

+ 886 - 0
packages/fcl-yaml/test/utyamldata.pp

@@ -0,0 +1,886 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML data & converted unit tests
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit utyamldata;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  DateUtils, Classes, SysUtils, fpcunit, testregistry, fpyaml.types, fpyaml.data, fpjson, fpyaml.json;
+
+Type
+
+  { TTestYAMLData }
+
+  TTestYAMLData = class(TTestCase)
+  private
+    FData: TYAMLData;
+  Public
+    procedure TearDown; override;
+    procedure SetData(aData : TYAMLData);
+    procedure AssertScalar(const Msg : string; aData : TYAMLData; aType : TYAMLTagType; const aValue : String);
+    Property Data : TYAMLData Read FData Write SetData;
+  Published
+    Procedure TestHookup;
+  end;
+
+  { TTestYAMLScalar }
+
+  TTestYAMLScalar = class(TTestYAMLData)
+  private
+    FScalar2 : TYAMLScalar;
+    function GetScalar: TYAMLScalar;
+  Public
+    procedure TearDown; override;
+    property Scalar : TYAMLScalar Read GetScalar;
+    property Scalar2 : TYAMLScalar Read FScalar2 Write FScalar2;
+  Published
+    Procedure TestString;
+    Procedure TestInteger;
+    Procedure TestDouble;
+    Procedure TestBoolean;
+    Procedure TestInt64;
+    Procedure TestDateTime;
+    Procedure TestNull;
+    Procedure TestEqualsBoolean;
+    Procedure TestEqualsBooleanNotStrict;
+    Procedure TestEqualsString;
+    Procedure TestEqualsInteger;
+    Procedure TestClone;
+  end;
+
+  { TTestYAMLSequence }
+
+  TTestYAMLSequence = class(TTestYAMLData)
+  private
+    function GetFirst: TYAMLData;
+    function GetSequence: TYAMLSequence;
+  Public
+    procedure Init(aElement: TYAMLData);
+    property Sequence : TYAMLSequence Read GetSequence;
+    property First : TYAMLData Read GetFirst;
+  published
+    procedure TestCreate;
+    Procedure TestString;
+    Procedure TestAddString;
+    Procedure TestAddInteger;
+    Procedure TestAddBoolean;
+    Procedure TestAddFloat;
+    Procedure TestDelete;
+    Procedure TestExtract;
+    Procedure TestEquals;
+    Procedure TestClone;
+  end;
+
+  { TTestYAMLMapping }
+
+  TTestYAMLMapping = class(TTestYAMLData)
+  private
+    function GetMapping: TYAMLMapping;
+  Public
+    procedure Init(aKey,aValue: TYAMLData);
+    property Mapping : TYAMLMapping  Read GetMapping;
+  Published
+    Procedure TestCreate;
+    Procedure TestCreate2;
+    Procedure TestAddString;
+    Procedure TestAddInteger;
+    Procedure TestAddBoolean;
+    Procedure TestAddFloat;
+    Procedure TestDelete;
+    Procedure TestExtract;
+    Procedure TestEquals;
+    Procedure TestEqualsOrder;
+    Procedure TestEqualsStrict;
+    Procedure TestClone;
+  end;
+
+  { TTestConvertToJSON }
+
+  TTestConvertToJSON = Class(TTestYAMLData)
+  private
+    FJSON: TJSONData;
+  Public
+    Procedure DoConvert;
+    Procedure TearDown; override;
+    procedure AssertJSON(const msg: string; aType: TJSONtype; aValue: string);
+    Property JSON : TJSONData Read FJSON;
+  Published
+    Procedure TestNull;
+    Procedure TestInteger;
+    Procedure TestInt64;
+    Procedure TestFloat;
+    Procedure TestBoolean;
+    Procedure TestString;
+    Procedure TestTimeStamp;
+    Procedure TestSequence;
+    Procedure TestMapping;
+    Procedure TestMappingNoCompositeKey;
+    Procedure TestMappingNoNullKey;
+    Procedure TestDocument;
+    Procedure TestDocumentOnly1;
+    Procedure TestDocumentNotEmpty;
+    Procedure TestStream;
+    Procedure TestStreamNotEmpty;
+    Procedure TestStreamOnly1;
+    Procedure TestVersionOK;
+  end;
+
+implementation
+
+{ TTestYAMLData }
+
+procedure TTestYAMLData.TearDown;
+begin
+  Data:=Nil;
+  inherited TearDown;
+end;
+
+procedure TTestYAMLData.SetData(aData: TYAMLData);
+begin
+  FreeAndNil(FData);
+  FData:=aData;
+end;
+
+procedure TTestYAMLData.AssertScalar(const Msg: string; aData: TYAMLData; aType: TYAMLTagType; const aValue: String);
+begin
+  AssertNotNull(Msg+': not null',aData);
+  AssertEquals(Msg+': scalar',TYAMLScalar,aData.ClassType);
+  AssertEquals(Msg+': tag',YAMLTagNames[aType],aData.Tag);
+  AssertEquals(Msg+': value',aValue,TYAMLScalar(aData).Value);
+end;
+
+procedure TTestYAMLData.TestHookup;
+begin
+  AssertNull('Data',Data);
+end;
+
+{ TTestYAMLScalar }
+
+function TTestYAMLScalar.GetScalar: TYAMLScalar;
+begin
+  Result:=Data as TYAMLScalar;
+end;
+
+procedure TTestYAMLScalar.TearDown;
+begin
+  FreeAndNil(FScalar2);
+  inherited TearDown;
+end;
+
+procedure TTestYAMLScalar.TestString;
+begin
+  Data:=TYAMLScalar.Create('one');
+  AssertEquals('Value','one',Scalar.Value);
+  AssertEquals('Value typed','one',Scalar.AsString);
+  AssertEquals('Tag',YAMLTagNames[yttString],Scalar.Tag);
+  AssertFalse('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestInteger;
+begin
+  Data:=TYAMLScalar.Create(1);
+  AssertEquals('Value','1',Scalar.Value);
+  AssertEquals('Value typed',1,Scalar.AsInteger);
+  AssertEquals('Tag',YAMLTagNames[yttInteger],Scalar.Tag);
+  AssertFalse('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestDouble;
+begin
+  Data:=TYAMLScalar.Create(1.2);
+  AssertEquals('Value','1.2',Scalar.Value);
+  AssertEquals('Typed',1.2,Scalar.AsDouble);
+  AssertEquals('Tag',YAMLTagNames[yttFloat],Scalar.Tag);
+end;
+
+procedure TTestYAMLScalar.TestBoolean;
+begin
+  Data:=TYAMLScalar.Create(True);
+  AssertEquals('Value','true',Scalar.Value);
+  AssertEquals('Typed',true,Scalar.AsBoolean);
+  AssertEquals('Tag',YAMLTagNames[yttBoolean],Scalar.Tag);
+  AssertFalse('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestInt64;
+begin
+  Data:=TYAMLScalar.Create(Int64(1234567891011));
+  AssertEquals('Value','1234567891011',Scalar.Value);
+  AssertEquals('Typed',1234567891011,Scalar.AsInt64);
+  AssertEquals('Tag',YAMLTagNames[yttInteger],Scalar.Tag);
+  AssertFalse('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestDateTime;
+begin
+  Data:=TYAMLScalar.CreateDateTime(Date);
+  AssertEquals('Value',DateToISO8601(Date),Scalar.Value);
+  AssertEquals('Typed',Date,Scalar.AsDateTime);
+  AssertEquals('Tag',YAMLTagNames[yttTimeStamp],Scalar.Tag);
+  AssertFalse('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestNull;
+begin
+  Data:=TYAMLScalar.Create(yskPlain);
+  AssertEquals('Value','',Scalar.Value);
+  AssertTrue('Null',Scalar.IsNull);
+end;
+
+procedure TTestYAMLScalar.TestEqualsBoolean;
+begin
+  Data:=TYAMLScalar.Create(True);
+  Scalar2:=TYAMLScalar.Create(True);
+  AssertTrue('Equals',Data.Equals(Scalar2,True));
+end;
+
+procedure TTestYAMLScalar.TestEqualsBooleanNotStrict;
+begin
+  Data:=TYAMLScalar.Create(True);
+  Scalar2:=TYAMLScalar.Create('true'); // string but with same value
+  AssertTrue('Equals',Data.Equals(Scalar2,False));
+end;
+
+procedure TTestYAMLScalar.TestEqualsString;
+begin
+  Data:=TYAMLScalar.Create('a');
+  Scalar2:=TYAMLScalar.Create('a');
+  AssertTrue('Equals',Data.Equals(Scalar2,True));
+  Scalar2.Value:='b';
+  AssertFalse('Equals',Data.Equals(Scalar2,True));
+end;
+
+procedure TTestYAMLScalar.TestEqualsInteger;
+begin
+  Data:=TYAMLScalar.Create(1);
+  Scalar2:=TYAMLScalar.Create(1);
+  AssertTrue('Equals',Data.Equals(Scalar2,True));
+  Scalar2.Free;
+  Scalar2:=TYAMLScalar.Create('1');
+  AssertFalse('Equals (tag diff)',Data.Equals(Scalar2,True));
+  AssertTrue('Equals (tag diff, not strict',Data.Equals(Scalar2,False));
+end;
+
+procedure TTestYAMLScalar.TestClone;
+begin
+  Data:=TYAMLScalar.Create(1);
+  Scalar2:=Data.Clone as TYAMLScalar;
+  AssertEquals('Tag',Data.Tag,Scalar2.Tag);
+  AssertEquals('Anchor','',Scalar2.Anchor);
+  AssertEquals('Value',Data.AsString,Scalar2.AsString);
+  AssertTrue('Kind',Scalar.Kind=Scalar2.Kind);
+end;
+
+{ TTestYAMLSequence }
+
+
+function TTestYAMLSequence.GetFirst: TYAMLData;
+begin
+  AssertTrue('Have one element',Data.Count>0);
+  Result:=Data.Items[0];
+end;
+
+function TTestYAMLSequence.GetSequence: TYAMLSequence;
+begin
+  Result:=Data as TYAMLSequence;
+end;
+
+procedure TTestYAMLSequence.Init(aElement: TYAMLData);
+begin
+  Data:=TYAMLSequence.Create;
+  if (aElement<>Nil) then
+    Sequence.Add(aElement);
+end;
+
+procedure TTestYAMLSequence.TestCreate;
+begin
+  Init(Nil);
+  AssertEquals('Tag',YAMLTagNames[yttSequence],Data.Tag);
+end;
+
+procedure TTestYAMLSequence.TestString;
+var
+  El : TYAMLData;
+begin
+  Init(TYAMLScalar.Create('one'));
+  AssertEquals('Count',1,Sequence.Count);
+  El:=Sequence[0];
+  AssertNotNull('have Element',El);
+  AssertEquals('Element type',TYAMLScalar,El.ClassType);
+  AssertEquals('Element value','one',El.AsString);
+end;
+
+procedure TTestYAMLSequence.TestAddString;
+var
+  El : TYAMLData;
+begin
+  Init(Nil);
+  El:=Sequence.Add('abc');
+  AssertEquals('Count',1,Sequence.Count);
+  AssertNotNull('Have result',EL);
+  AssertEquals('Element type',TYAMLScalar,El.ClassType);
+  AssertEquals('Element value','abc',El.AsString);
+end;
+
+procedure TTestYAMLSequence.TestAddInteger;
+var
+  El : TYAMLData;
+begin
+  Init(Nil);
+  El:=Sequence.Add(123);
+  AssertEquals('Count',1,Sequence.Count);
+  AssertNotNull('Have result',EL);
+  AssertEquals('Element type',TYAMLScalar,El.ClassType);
+  AssertEquals('Element value',123,El.AsInteger);
+end;
+
+procedure TTestYAMLSequence.TestAddBoolean;
+var
+  El : TYAMLData;
+begin
+  Init(Nil);
+  El:=Sequence.Add(True);
+  AssertEquals('Count',1,Sequence.Count);
+  AssertNotNull('Have result',EL);
+  AssertEquals('Element type',TYAMLScalar,El.ClassType);
+  AssertEquals('Element value',true,El.AsBoolean);
+end;
+
+procedure TTestYAMLSequence.TestAddFloat;
+var
+  El : TYAMLData;
+begin
+  Init(Nil);
+  El:=Sequence.Add(1.23);
+  AssertEquals('Count',1,Sequence.Count);
+  AssertNotNull('Have result',EL);
+  AssertEquals('Element type',TYAMLScalar,El.ClassType);
+  AssertEquals('Element value',1.23,El.AsDouble);
+end;
+
+procedure TTestYAMLSequence.TestDelete;
+begin
+  Init(TYAMLScalar.Create('one'));
+  Sequence.Add('two');
+  AssertEquals('Count',2,Sequence.Count);
+  Sequence.Delete(0);
+  AssertEquals('Count',1,Sequence.Count);
+  AssertEquals('Remaining','two',Sequence.Items[0].AsString);
+end;
+
+procedure TTestYAMLSequence.TestExtract;
+var
+  El: TYAMLData;
+begin
+  Init(TYAMLScalar.Create('one'));
+  Sequence.Add('two');
+  AssertEquals('Count',2,Sequence.Count);
+  El:=Sequence.Extract(0);
+  try
+    AssertEquals('Count',1,Sequence.Count);
+    AssertEquals('El','one',EL.AsString);
+    AssertEquals('Remaining','two',Sequence.Items[0].AsString);
+  finally
+    El.Free;
+  end;
+end;
+
+procedure TTestYAMLSequence.TestEquals;
+var
+  S2 : TYAMLSequence;
+
+begin
+  Init(Nil);
+  Sequence.Add('one');
+  Sequence.Add('two');
+  AssertTrue('Self',Sequence.Equals(Sequence,True));
+  S2:=TYAMLSequence.Create;
+  try
+    S2.Add('one');
+    S2.Add('two');
+    AssertTrue('S2 - 1 ',Sequence.Equals(S2,True));
+    TYAMLScalar(S2[0]).Value:='1';
+    AssertFalse('S2 - 2',Sequence.Equals(S2,True));
+    TYAMLScalar(S2[0]).Value:='one';
+  finally
+    S2.Free;
+  end;
+end;
+
+procedure TTestYAMLSequence.TestClone;
+var
+  D: TYAMLData;
+  S2 : TYAMLSequence absolute d;
+begin
+  Init(Nil);
+  Sequence.Kind:=yckFlow;
+  Sequence.Add('one');
+  Sequence.Add('two');
+  D:=Sequence.Clone;
+  try
+    AssertEquals('Class',TYAMLSequence,D.ClassType);
+    AssertTrue('kind',Sequence.Kind=S2.Kind);
+    AssertEquals('Count',Sequence.Count,S2.Count);
+    AssertTrue('Item 1',Sequence.Items[0].Equals(S2.Items[0],False));
+    AssertEquals('Item 1 tag',Sequence.Items[0].Tag,S2.Items[0].Tag);
+    AssertTrue('Item 2',Sequence.Items[1].Equals(S2.Items[1],False));
+    AssertEquals('Item 2 tag',Sequence.Items[1].Tag,S2.Items[1].Tag);
+  finally
+    D.Free;
+  end;
+end;
+
+{ TTestYAMLMapping }
+
+function TTestYAMLMapping.GetMapping: TYAMLMapping;
+begin
+  Result:=Data as TYAMLMapping;
+end;
+
+procedure TTestYAMLMapping.Init(aKey, aValue: TYAMLData);
+
+begin
+  Data:=TYAMLMapping.Create;
+  if Assigned(aKey) and Assigned(aValue) then
+    Mapping.Add(aKey,aValue);
+end;
+
+procedure TTestYAMLMapping.TestCreate;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  AssertEquals('Tag',YAMLTagNames[yttMap],Mapping.Tag);
+  AssertEquals('Count',0,Mapping.Count);
+end;
+
+procedure TTestYAMLMapping.TestCreate2;
+
+begin
+  Init(TYAMLScalar.Create(1),TYAMLScalar.Create(2));
+  AssertNotNull('Mapping',Mapping);
+  AssertEquals('Count',1,Mapping.Count);
+  AssertEquals('Count',1,Mapping.Count);
+end;
+
+
+procedure TTestYAMLMapping.TestAddString;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a','b');
+  AssertEquals('Count',1,Mapping.Count);
+  AssertScalar('Key',Mapping.Key[0],yttString,'a');
+  AssertScalar('Value',Mapping.Items[0],yttString,'b');
+end;
+
+procedure TTestYAMLMapping.TestAddInteger;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a',1);
+  AssertEquals('Count',1,Mapping.Count);
+  AssertScalar('Key',Mapping.Key[0],yttString,'a');
+  AssertScalar('Value',Mapping.Items[0],yttInteger,'1');
+end;
+
+procedure TTestYAMLMapping.TestAddBoolean;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a',true);
+  AssertEquals('Count',1,Mapping.Count);
+  AssertScalar('Key',Mapping.Key[0],yttString,'a');
+  AssertScalar('Value',Mapping.Items[0],yttBoolean,'true');
+end;
+
+procedure TTestYAMLMapping.TestAddFloat;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a',1.2);
+  AssertEquals('Count',1,Mapping.Count);
+  AssertScalar('Key',Mapping.Key[0],yttString,'a');
+  AssertScalar('Value',Mapping.Items[0],yttFloat,'1.2');
+end;
+
+procedure TTestYAMLMapping.TestDelete;
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a',1.2);
+  Mapping.Add('b',1.3);
+  Mapping.Add('c',1.4);
+  AssertEquals('Count 0',3,Mapping.Count);
+  Mapping.Delete(1);
+  AssertEquals('Count 1',2,Mapping.Count);
+  AssertScalar('Remaining 1 - 0',Mapping.Items[0],yttFloat,'1.2');
+  AssertScalar('Remaining 1 - 1',Mapping.Items[1],yttFloat,'1.4');
+  Mapping.Delete('c');
+  AssertEquals('Count 2',1,Mapping.Count);
+  AssertScalar('Remaining 2 - 0',Mapping.Items[0],yttFloat,'1.2');
+  Mapping.Delete(Mapping.Key[0]);
+  AssertEquals('Count 3',0,Mapping.Count);
+end;
+
+procedure TTestYAMLMapping.TestExtract;
+
+var
+  Itm: TYAMLData;
+
+begin
+  Init(Nil,Nil);
+  AssertNotNull('Mapping',Mapping);
+  Mapping.Add('a',1.2);
+  Mapping.Add('b',1.3);
+  Mapping.Add('c',1.4);
+  AssertEquals('Count 0',3,Mapping.Count);
+  Itm:=Mapping.Extract(1);
+  try
+    AssertEquals('Count 1',2,Mapping.Count);
+    AssertScalar('Remaining 1 - 0',Mapping.Items[0],yttFloat,'1.2');
+    AssertScalar('Remaining 1 - 4',Mapping.Items[1],yttFloat,'1.4');
+    AssertScalar('Extracted 1', Itm,yttFloat,'1.3');
+  finally
+    Itm.Free;
+  end;
+  Itm:=Mapping.Extract('c');
+  try
+    AssertEquals('Count 2',1,Mapping.Count);
+    AssertScalar('Remaining 2 - 0',Mapping.Items[0],yttFloat,'1.2');
+    AssertScalar('Extracted 2', Itm,yttFloat,'1.4');
+  finally
+    Itm.Free;
+  end;
+  Itm:=Mapping.Extract(Mapping.Key[0]);
+  try
+    AssertEquals('Count',0,Mapping.Count);
+    AssertScalar('Extracted 2', Itm,yttFloat,'1.2');
+  finally
+    Itm.Free;
+  end;
+end;
+
+procedure TTestYAMLMapping.TestEquals;
+
+var
+  M2 : TYAMLMapping;
+
+begin
+  Init(nil,nil);
+  Mapping.Add('a','1');
+  Mapping.Add('b','2');
+  AssertTrue('Self',Mapping.Equals(Mapping,True));
+  M2:=TYAMLMapping.Create;
+  try
+    M2.Add('a','1');
+    M2.Add('b','2');
+    AssertTrue('M2-1',Mapping.Equals(M2,True));
+  finally
+    M2.Free;
+  end;
+end;
+
+procedure TTestYAMLMapping.TestEqualsOrder;
+var
+  M2 : TYAMLMapping;
+
+begin
+  Init(nil,nil);
+  Mapping.Add('a','1');
+  Mapping.Add('b','2');
+  AssertTrue('Self',Mapping.Equals(Mapping,True));
+  M2:=TYAMLMapping.Create;
+  try
+    M2.Add('b','2');
+    M2.Add('a','1');
+    AssertTrue('M2-1',Mapping.Equals(M2,True));
+  finally
+    M2.Free;
+  end;
+end;
+
+procedure TTestYAMLMapping.TestEqualsStrict;
+var
+  M2 : TYAMLMapping;
+
+begin
+  Init(nil,nil);
+  Mapping.Add('a','1');
+  Mapping.Add('b','2');
+  AssertTrue('Self',Mapping.Equals(Mapping,True));
+  M2:=TYAMLMapping.Create;
+  try
+    M2.Add('a','1');
+    M2.Add('b',2);
+    AssertFalse('M2-1',Mapping.Equals(M2,True));
+    AssertTrue('M2-2',Mapping.Equals(M2,False));
+  finally
+    M2.Free;
+  end;
+end;
+
+procedure TTestYAMLMapping.TestClone;
+var
+  D: TYAMLData;
+  M2 : TYAMLMapping absolute d;
+begin
+  Init(Nil,Nil);
+  Mapping.Kind:=yckFlow;
+  Mapping.Add('one','1');
+  Mapping.Add('two','2');
+  D:=Mapping.Clone;
+  try
+    AssertEquals('Class',TYAMLMapping,D.ClassType);
+    AssertTrue('kind',Mapping.Kind=M2.Kind);
+    AssertEquals('Count',Mapping.Count,M2.Count);
+    AssertTrue('Key 1',Mapping.Key[0].Equals(M2.Key[0],False));
+    AssertEquals('Key 1 tag',Mapping.Key[0].Tag,M2.Key[0].Tag);
+    AssertTrue('Item 1',Mapping.Items[0].Equals(M2.Items[0],False));
+    AssertEquals('Item 1 tag',Mapping.Items[0].Tag,M2.Items[0].Tag);
+    AssertTrue('Key 2',Mapping.Key[1].Equals(M2.Key[1],False));
+    AssertEquals('Key 2 tag',Mapping.Key[1].Tag,M2.Key[1].Tag);
+    AssertTrue('Item 2',Mapping.Items[1].Equals(M2.Items[1],False));
+    AssertEquals('Item 2 tag',Mapping.Items[1].Tag,M2.Items[1].Tag);
+  finally
+    D.Free;
+  end;
+end;
+
+{ TTestConvertToJSON }
+
+procedure TTestConvertToJSON.DoConvert;
+begin
+  FJSON:=YAMLToJSON(Data);
+end;
+
+procedure TTestConvertToJSON.TearDown;
+begin
+  FreeAndNil(FJSON);
+  inherited TearDown;
+end;
+
+procedure TTestConvertToJSON.AssertJSON(const msg : string; aType: TJSONtype; aValue: string);
+begin
+  AssertTrue(Msg+': Correct type',aType=JSON.JSONType);
+  if aValue<>'' then
+    if aType in StructuredJSONTypes then
+      AssertEquals('JSON value : ',aValue,JSON.AsJSON)
+    else
+      AssertEquals('String value : ',aValue,JSON.AsString);
+end;
+
+procedure TTestConvertToJSON.TestNull;
+begin
+  Data:=TYAMLScalar.Create('',yttNull);
+  DoConvert;
+  AssertJSON('Null',jtNull,'');
+end;
+
+procedure TTestConvertToJSON.TestInteger;
+begin
+  Data:=TYAMLScalar.Create(123);
+  DoConvert;
+  AssertJSON('Number',jtNumber,'123');
+  AssertTrue('32-bit-Integer',ntInteger=TJSONNumber(JSON).NumberType);
+end;
+
+procedure TTestConvertToJSON.TestInt64;
+begin
+  Data:=TYAMLScalar.Create(123456789101112);
+  DoConvert;
+  AssertJSON('Number',jtNumber,'123456789101112');
+  AssertTrue('64-bit-Integer',ntInt64=TJSONNumber(JSON).NumberType);
+end;
+
+procedure TTestConvertToJSON.TestFloat;
+begin
+  Data:=TYAMLScalar.Create(12.34);
+  DoConvert;
+  AssertJSON('Number',jtNumber,'');
+  AssertEquals('Float',12.34,JSON.AsFloat);
+end;
+
+procedure TTestConvertToJSON.TestBoolean;
+begin
+  Data:=TYAMLScalar.Create(True);
+  DoConvert;
+  AssertJSON('Boolean',jtBoolean,'True');
+end;
+
+procedure TTestConvertToJSON.TestString;
+begin
+  Data:=TYAMLScalar.Create('abc');
+  DoConvert;
+  AssertJSON('String',jtString,'abc');
+end;
+
+procedure TTestConvertToJSON.TestTimeStamp;
+
+var
+  D : TDateTime;
+begin
+  D:=EncodeDateTime(2024,12,20,21,11,0,0);
+  Data:=TYAMLScalar.CreateDateTime(D);
+  DoConvert;
+  AssertJSON('String',jtString,DateToISO8601(D));
+end;
+
+procedure TTestConvertToJSON.TestSequence;
+
+Var
+  Seq : TYAMLSequence;
+
+begin
+  Seq:=TYAMLSequence.Create;
+  Data:=Seq;
+  Seq.Add('a');
+  Seq.Add(2);
+  DoConvert;
+  AssertJSON('Array',jtArray,'["a", 2]');
+end;
+
+procedure TTestConvertToJSON.TestMapping;
+Var
+  Map : TYAMLMapping;
+
+begin
+  Map:=TYAMLMapping.Create;
+  Data:=Map;
+  Map.Add('a','1');
+  Map.Add('b',2);
+  DoConvert;
+  AssertJSON('Object',jtObject,'{ "a" : "1", "b" : 2 }');
+end;
+
+procedure TTestConvertToJSON.TestMappingNoCompositeKey;
+Var
+  Map : TYAMLMapping;
+
+begin
+  Map:=TYAMLMapping.Create;
+  Data:=Map;
+  Map.Add(TYAMLSequence.Create(),TYAMLScalar.Create('1'));
+  AssertException('No sequence',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestMappingNoNullKey;
+Var
+  Map : TYAMLMapping;
+
+begin
+  Map:=TYAMLMapping.Create;
+  Data:=Map;
+  Map.Add(TYAMLScalar.Create('',yttNull),TYAMLScalar.Create('1'));
+  AssertException('No null scalar',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestDocument;
+Var
+  Doc : TYAMLDocument;
+
+begin
+  Doc:=TYAMLDocument.Create;
+  Data:=Doc;
+  Doc.Add('a');
+  DoConvert;
+  AssertJSON('string',jtString,'a');
+end;
+
+procedure TTestConvertToJSON.TestDocumentOnly1;
+Var
+  Doc : TYAMLDocument;
+
+begin
+  Doc:=TYAMLDocument.Create;
+  Data:=Doc;
+  Doc.Add('a');
+  Doc.Add('b');
+  AssertException('Only one element',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestDocumentNotEmpty;
+Var
+  Doc : TYAMLDocument;
+
+begin
+  Doc:=TYAMLDocument.Create;
+  Data:=Doc;
+  AssertException('Only one element',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestStream;
+Var
+  Stream : TYAMLStream;
+  Doc : TYAMLDocument;
+
+begin
+  Stream:=TYAMLStream.Create;
+  Data:=Stream;
+  Doc:=TYAMLDocument.Create;
+  Stream.Add(Doc);
+  Doc.Add('a');
+  DoConvert;
+  AssertJSON('string',jtString,'a');
+end;
+
+procedure TTestConvertToJSON.TestStreamNotEmpty;
+Var
+  Stream : TYAMLStream;
+
+begin
+  Stream:=TYAMLStream.Create;
+  Data:=Stream;
+  AssertException('Must have one document',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestStreamOnly1;
+Var
+  Stream : TYAMLStream;
+  Doc : TYAMLDocument;
+
+begin
+  Stream:=TYAMLStream.Create;
+  Data:=Stream;
+  Doc:=TYAMLDocument.Create;
+  Doc.Add('a');
+  Stream.Add(Doc);
+  Doc:=TYAMLDocument.Create;
+  Stream.Add(Doc);
+  Doc.Add('B');
+  AssertException('Must have only one document',EConvertError,@DoConvert);
+end;
+
+procedure TTestConvertToJSON.TestVersionOK;
+Var
+  Stream : TYAMLStream;
+  Doc : TYAMLDocument;
+begin
+  Stream:=TYAMLStream.Create;
+  Data:=Stream;
+  Stream.Add(TYAMLTagData.Create('','soso'));
+  Doc:=TYAMLDocument.Create;
+  Doc.Add('a');
+  Stream.Add(Doc);
+  DoConvert;
+  AssertJSON('string',jtString,'a');
+end;
+
+initialization
+  RegisterTests([TTestYAMLScalar,TTestYAMLSequence,TTestYAMLMapping,TTestConvertToJSON]);
+end.
+

+ 553 - 0
packages/fcl-yaml/test/utyamlparser.pp

@@ -0,0 +1,553 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML parser unit tests
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit utyamlparser;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, fpyaml.types, fpyaml.parser, fpyaml.data, utyamldata;
+
+type
+
+  { TTestYamlParser }
+
+  TTestYamlParser= class(TTestYAMLData)
+  private
+    FParser: TYAMLParser;
+    function AssertMapping(Msg: String; Y: TYAMLData): TYAMLMapping;
+    function AssertSequence(Msg: String; Y: TYAMLData): TYAMLSequence;
+    function AssertValue(aClass: TYAMLDataClass): TYAMLData;
+    function GetDocument: TYAMLDocument;
+    function GetStream: TYAMLStream;
+    function GetValue: TYAMLData;
+  public
+    procedure Parse(aContent : Array of string);
+
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property Parser : TYAMLParser Read FParser;
+    property YAML : TYAMLStream Read GetStream;
+    property Document: TYAMLDocument Read GetDocument;
+    property Value: TYAMLData Read GetValue;
+  published
+    procedure TestCreate;
+    procedure TestEmptyDocument;
+    procedure TestVersionEmptyDocument;
+    procedure TestScalar;
+    procedure TestAnchoredScalar;
+    procedure TestBlockSequence;
+    procedure TestBlockSequenceTwo;
+    procedure TestBlockSequenceThree;
+    procedure TestBlockSequenceNested;
+    procedure TestBlockSequenceNestedBetween;
+    procedure TestFlowSequence;
+    procedure TestFlowSequenceTwo;
+    procedure TestFlowSequenceThree;
+    procedure TestFlowSequenceNestedBetween;
+    procedure TestBlockMapping;
+    procedure TestBlockMappingTwo;
+    procedure TestBlockMappingThree;
+    procedure TestBlockMappingNested;
+    procedure TestBlockMappingNestedDouble;
+    procedure TestBlockMappingUnindentedSequence;
+    procedure TestBlockMappingUnindentedSequenceWithIndent;
+    procedure TestBlockMappingFlowSequence;
+    procedure TestFlowMapping;
+    procedure TestFlowMappingOne;
+    procedure TestFlowMappingTwo;
+    procedure TestFlowMappingNested;
+  end;
+
+implementation
+
+procedure TTestYamlParser.TestCreate;
+begin
+  Parse(['one']);
+  AssertNotNull('Parser',Parser);
+  AssertNotNull('Data',Data);
+  AssertNotNull('YAML',YAML);
+  AssertNotNull('Document',Document);
+  AssertNotNUll('Value');
+end;
+
+procedure TTestYamlParser.TestScalar;
+begin
+  Parse(['one']);
+  AssertNotNull('Parser',Parser);
+  AssertNotNull('Data',Data);
+  AssertEquals('YAML Stream',TYAMLStream,Data.ClassType);
+  AssertEquals('YAML Stream item count',1,YAML.Count);
+  AssertNotNull('Document',Document);
+  AssertNotNUll('Value');
+  AssertEquals('Value ',TYAMLScalar,Value.ClassType);
+  AssertEquals('Value ','one',Value.AsString);
+end;
+
+procedure TTestYamlParser.TestAnchoredScalar;
+begin
+  Parse(['&anchor one']);
+  AssertNotNull('Parser',Parser);
+  AssertNotNull('Data',Data);
+  AssertEquals('YAML Stream',TYAMLStream,Data.ClassType);
+  AssertEquals('YAML Stream item count',1,YAML.Count);
+  AssertNotNull('Document',Document);
+  AssertNotNUll('Value');
+  AssertEquals('Value ',TYAMLScalar,Value.ClassType);
+  AssertEquals('Value ','one',Value.AsString);
+  AssertEquals('Value ','anchor',Value.Anchor);
+end;
+
+function TTestYamlParser.AssertValue(aClass : TYAMLDataClass) : TYAMLData;
+
+begin
+  AssertNotNull('Data',Data);
+  AssertEquals('YAML Stream',TYAMLStream,Data.ClassType);
+  AssertEquals('YAML Stream item count',1,YAML.Count);
+  AssertNotNull('Document',Document);
+  AssertTrue('Document not empty',Document.Count>0);
+  AssertEquals('Value class',aClass,Document.Items[0].ClassType);
+  Result:=Value;
+end;
+
+procedure TTestYamlParser.TestBlockSequence;
+
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['- one']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckBlock=seq.Kind);
+  AssertEquals('element count',1,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+end;
+
+procedure TTestYamlParser.TestBlockSequenceTwo;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['- one','- two']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckBlock=seq.Kind);
+  AssertEquals('Element count',2,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Second',Seq.Items[1],yttString,'two');
+end;
+
+procedure TTestYamlParser.TestBlockSequenceThree;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['- one','- two','- three']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertEquals('Element count',3,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Second',Seq.Items[1],yttString,'two');
+  AssertScalar('Third',Seq.Items[2],yttString,'three');
+end;
+
+procedure TTestYamlParser.TestBlockSequenceNested;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['- one','-','  - a','  - b']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckBlock=seq.Kind);
+  AssertEquals('Element count',2,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertEquals('Sequence',TYAMLSequence,Seq.Items[1].ClassType);
+  Seq:=TYAMLSequence(Seq.Items[1]);
+  AssertEquals('Element count',2,Seq.Count);
+  AssertScalar('2 - 1',Seq.Items[0],yttString,'a');
+  AssertScalar('2 - 2',Seq.Items[1],yttString,'b');
+end;
+
+procedure TTestYamlParser.TestBlockSequenceNestedBetween;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['- one','-','  - a','  - b','- two']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckBlock=seq.Kind);
+  AssertEquals('Element count',3,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Third',Seq.Items[2],yttString,'two');
+  AssertEquals('Sequence',TYAMLSequence,Seq.Items[1].ClassType);
+  Seq:=TYAMLSequence(Seq.Items[1]);
+  AssertEquals('Element count',2,Seq.Count);
+  AssertScalar('2 - 1',Seq.Items[0],yttString,'a');
+  AssertScalar('2 - 2',Seq.Items[1],yttString,'b');
+end;
+
+procedure TTestYamlParser.TestFlowSequence;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['[one]']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckFlow=seq.Kind);
+  AssertEquals('element count',1,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+end;
+
+procedure TTestYamlParser.TestFlowSequenceTwo;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['[one,two]']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertEquals('element count',2,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Second',Seq.Items[1],yttString,'two');
+end;
+
+procedure TTestYamlParser.TestFlowSequenceThree;
+var
+  Seq : TYAMLSequence;
+
+begin
+  Parse(['[one,two,','three]']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertTrue('Correct sequence kind',yckFlow=seq.Kind);
+  AssertEquals('element count',3,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Second',Seq.Items[1],yttString,'two');
+  AssertScalar('Third',Seq.Items[2],yttString,'three');
+end;
+
+procedure TTestYamlParser.TestFlowSequenceNestedBetween;
+var
+  Seq : TYAMLSequence;
+begin
+  Parse(['[one ','[a,b]',',two]']);
+  Seq:=TYAMLSequence(AssertValue(TYAMLSequence));
+  AssertEquals('Element count',3,Seq.Count);
+  AssertScalar('First',Seq.Items[0],yttString,'one');
+  AssertScalar('Third',Seq.Items[2],yttString,'two');
+  AssertEquals('Sequence',TYAMLSequence,Seq.Items[1].ClassType);
+  Seq:=TYAMLSequence(Seq.Items[1]);
+  AssertEquals('Element count',2,Seq.Count);
+  AssertScalar('2 - 1',Seq.Items[0],yttString,'a');
+  AssertScalar('2 - 2',Seq.Items[1],yttString,'b');
+end;
+
+procedure TTestYamlParser.TestBlockMapping;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['one: two']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',1,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+end;
+
+procedure TTestYamlParser.TestBlockMappingTwo;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['one: two','three: four']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+  AssertScalar('Second key',Map.Key[1],yttString,'three');
+  AssertScalar('Second value',Map.Items[1],yttString,'four');
+end;
+
+procedure TTestYamlParser.TestBlockMappingThree;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['one: two','three: four','five: six']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertEquals('Element count',3,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+  AssertScalar('Second key',Map.Key[1],yttString,'three');
+  AssertScalar('Second value',Map.Items[1],yttString,'four');
+  AssertScalar('third key',Map.Key[2],yttString,'five');
+  AssertScalar('third value',Map.Items[2],yttString,'six');
+end;
+
+procedure TTestYamlParser.TestBlockMappingNested;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['one: two','three:','  five: six','  seven: eight']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+  AssertScalar('Second key',Map.Key[1],yttString,'three');
+  AssertEquals('Map',TYAMLMapping,Map.Items[1].ClassType);
+  Map:=TYAMLMapping(Map.Items[1]);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('2 - First key',Map.Key[0],yttString,'five');
+  AssertScalar('2 - first value',Map.Items[0],yttString,'six');
+  AssertScalar('2 - Second key',Map.Key[1],yttString,'seven');
+  AssertScalar('2 - second value',Map.Items[1],yttString,'eight');
+end;
+
+procedure TTestYamlParser.TestBlockMappingNestedDouble;
+
+var
+  Map,Map2 : TYAMLMapping;
+begin
+  Parse(['one: 1',
+         'two:',
+         '  three:',
+         '    four: 4',
+         '    five: 5',
+         '  six: 6']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttInteger,'1');
+  AssertScalar('Second key',Map.Key[1],yttString,'two');
+  AssertEquals('Map',TYAMLMapping,Map.Items[1].ClassType);
+  Map:=TYAMLMapping(Map.Items[1]);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('2 - First key',Map.Key[0],yttString,'three');
+  AssertEquals('2 - first value is Map',TYAMLMapping,Map.Items[0].ClassType);
+
+  Map2:=TYAMLMapping(Map.Items[0]);
+  AssertEquals('Element count',2,Map2.Count);
+  AssertScalar('3 - First key',Map2.Key[0],yttString,'four');
+  AssertScalar('3 - First value',Map2.items[0],yttInteger,'4');
+  AssertScalar('3 - second key',Map2.Key[1],yttString,'five');
+  AssertScalar('3 - second value',Map2.items[1],yttInteger,'5');
+
+  AssertScalar('2 - Second key',Map.Key[1],yttString,'six');
+  AssertScalar('2 - second value',Map.Items[1],yttInteger,'6');
+end;
+
+procedure TTestYamlParser.TestBlockMappingUnindentedSequence;
+var
+  seq : TYAMLSequence;
+  map : TYAMLMapping;
+begin
+  Parse(['one:',
+         '- a',
+         '- b',
+         'two: c']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('1 - key',Map.Key[0],yttString,'one');
+  AssertEquals('1 - item is Map',TYAMLSequence,Map.Items[0].ClassType);
+  seq:=TYAMLSequence(Map.Items[0]);
+  AssertEquals('> Element count',2,seq.Count);
+  AssertScalar('> element - 1',seq.items[0],yttString,'a');
+  AssertScalar('> element - 2',seq.items[1],yttString,'b');
+  AssertScalar('2 - key',map.key[1],yttString,'two');
+  AssertScalar('2 - item',map.items[1],yttString,'c');
+end;
+
+Function TTestYamlParser.AssertSequence(Msg : String; Y : TYAMLData) : TYAMLSequence;
+begin
+  AssertNotNull(Msg+': Have data',Y);
+  AssertEquals(Msg+': Have sequence',TYAMLSequence,Y.ClassType);
+  Result:=TYAMLSequence(Y);
+end;
+Function TTestYamlParser.AssertMapping(Msg : String; Y : TYAMLData) : TYAMLMapping;
+
+begin
+  AssertNotNull(Msg+': Have data',Y);
+  AssertEquals(Msg+': Have mapping',TYAMLMapping,Y.ClassType);
+  Result:=TYAMLMapping(Y);
+
+end;
+
+procedure TTestYamlParser.TestBlockMappingUnindentedSequenceWithIndent;
+var
+  seq : TYAMLSequence;
+  map1,map2,map3 : TYAMLMapping;
+begin
+  Parse(['two:', // map1
+         '  three:', // map 2
+         '    four:', // map 3
+         '    - enum', // enum ,
+         '  five: a' // Map 2
+         ]);
+  Map1:=AssertMapping('1',AssertValue(TYAMLMapping));
+  Map2:=AssertMapping('2',Map1.Items[0]);
+  Map3:=AssertMapping('3',Map2.Items[0]);
+  Seq:=AssertSequence('seq',Map3.Items[0]);
+  AssertEquals('Seq count',1,Seq.Count);
+  AssertEquals('map3 count',1,Map3.Count);
+  AssertEquals('map2 count',2,Map2.Count);
+  AssertScalar('map2 key 2',Map2.Key[1],yttString,'five');
+  AssertScalar('map2 item 2',Map2[1],yttString,'a');
+end;
+
+procedure TTestYamlParser.TestBlockMappingFlowSequence;
+
+var
+  seq : TYAMLSequence;
+  map : TYAMLMapping;
+
+begin
+  Parse(['one: []',
+         'two: c']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckBlock);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('1 - key',Map.Key[0],yttString,'one');
+  AssertEquals('1 - item is Map',TYAMLSequence,Map.Items[0].ClassType);
+  seq:=TYAMLSequence(Map.Items[0]);
+  AssertEquals('> Element count',0,seq.Count);
+//  AssertScalar('> element - 1',seq.items[0],yttString,'a');
+//  AssertScalar('> element - 2',seq.items[1],yttString,'b');
+//  AssertScalar('2 - key',map.key[1],yttString,'two');
+  AssertScalar('2 - key',map.key[1],yttString,'two');
+  AssertScalar('2 - item',map.items[1],yttString,'c');
+end;
+
+procedure TTestYamlParser.TestFlowMapping;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['{ }']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckFlow);
+  AssertEquals('Element count',0,Map.Count);
+end;
+
+procedure TTestYamlParser.TestFlowMappingOne;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['{ one : two }']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckFlow);
+  AssertEquals('Element count',1,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+end;
+
+procedure TTestYamlParser.TestFlowMappingTwo;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['{ one : two, three: four }']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckFlow);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+  AssertScalar('Second key',Map.Key[1],yttString,'three');
+  AssertScalar('Second value',Map.Items[1],yttString,'four');
+end;
+
+procedure TTestYamlParser.TestFlowMappingNested;
+var
+  Map : TYAMLMapping;
+begin
+  Parse(['{ one: two ,three: { five: six , seven: eight} }']);
+  Map:=TYAMLMapping(AssertValue(TYAMLMapping));
+  AssertTrue('Correct kind',Map.Kind=yckFlow);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('First key',Map.Key[0],yttString,'one');
+  AssertScalar('First value',Map.Items[0],yttString,'two');
+  AssertScalar('Second key',Map.Key[1],yttString,'three');
+  AssertEquals('Map',TYAMLMapping,Map.Items[1].ClassType);
+  Map:=TYAMLMapping(Map.Items[1]);
+  AssertEquals('Element count',2,Map.Count);
+  AssertScalar('2 - First key',Map.Key[0],yttString,'five');
+  AssertScalar('2 - first value',Map.Items[0],yttString,'six');
+  AssertScalar('2 - Second key',Map.Key[1],yttString,'seven');
+  AssertScalar('2 - second value',Map.Items[1],yttString,'eight');
+end;
+
+procedure TTestYamlParser.TestEmptyDocument;
+begin
+  Parse(['---','...']);
+  AssertNotNull('Data',Data);
+  AssertEquals('YAML Stream',TYAMLStream,Data.ClassType);
+  AssertEquals('YAML Stream item count',1,YAML.Count);
+  AssertNotNull('Document',Document);
+  AssertEquals('Document empty',0,Document.Count);
+end;
+
+procedure TTestYamlParser.TestVersionEmptyDocument;
+begin
+  Parse(['%YAML 1.2','---','...']);
+  AssertNotNull('Data',Data);
+  AssertEquals('YAML Stream',TYAMLStream,Data.ClassType);
+  AssertEquals('YAML Stream item count',1,YAML.Count);
+  AssertNotNull('Document',Document);
+  AssertEquals('Major',1,Document.Version.Major);
+  AssertEquals('Minor',2,Document.Version.Minor);
+  AssertEquals('Document empty',0,Document.Count);
+end;
+
+function TTestYamlParser.GetDocument: TYAMLDocument;
+
+begin
+  AssertTrue('Have documents',YAML.DocumentCount>0);
+  Result:=YAML.Documents[0];
+end;
+
+function TTestYamlParser.GetStream: TYAMLStream;
+begin
+  AssertTrue('Have stream',Data is  TYAMLStream);
+  Result:=Data as TYAMLStream;
+end;
+
+function TTestYamlParser.GetValue: TYAMLData;
+var
+  Doc : TYAMLDocument;
+begin
+  Doc:=GetDocument;
+  AssertTrue('Have data',Doc.Count>0);
+  Result:=Doc[0];
+end;
+
+procedure TTestYamlParser.Parse(aContent: array of string);
+begin
+  FParser:=TYAMLParser.Create(aContent);
+  SetData(FParser.Parse);
+end;
+
+procedure TTestYamlParser.SetUp;
+begin
+  Inherited;
+  FParser:=Nil;
+end;
+
+procedure TTestYamlParser.TearDown;
+begin
+  FreeAndNil(FParser);
+  Inherited;
+end;
+
+initialization
+
+  RegisterTest(TTestYamlParser);
+end.
+

+ 592 - 0
packages/fcl-yaml/test/utyamlscanner.pp

@@ -0,0 +1,592 @@
+{
+    This file is part of the Free Component Library
+    Copyright (c) 2024 by Michael Van Canneyt [email protected]
+
+    YAML scanner unit tests
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit utyamlscanner;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, fpyaml.scanner;
+
+
+Type
+
+  { TTestYAMLScanner }
+
+  TTestYAMLScanner= class(TTestCase)
+  private
+    FDocument : String;
+    FInput : TStream;
+    FLineBreak : String;
+    FScanner: TYAMLScanner;
+    FLastToken : TYAMLTokenData;
+    procedure AssertEscape(aEscape, aResult: TYAMLString);
+  protected
+    procedure StartDocument(const aDoc: array of AnsiString);
+    procedure SetUp; override;
+    procedure TearDown; override;
+    Procedure StartDocument(aDoc : AnsiString);
+    procedure ReadToken;
+  Public
+    class procedure AssertEquals(const msg: string; aExpected, aActual: TYAMLToken); overload;
+    procedure AssertToken(const msg: string; aTokenData: TYAMLTokenData; aToken: TYAMLToken; aRow: Integer; aCol: Integer);
+    function AssertToken(const msg: string; aToken: TYAMLToken; aRow: Integer; aCol: Integer): TYAMLTokenData;
+    function AssertToken(const msg: string; aToken: TYAMLToken; const aValue: string; aRow: Integer; aCol: Integer): TYAMLTokenData;
+    procedure AssertEOF;
+
+    Property Document : String read FDocument;
+    Property Scanner : TYAMLScanner Read FScanner;
+    Property ScannerInput : TStream Read FInput;
+    Property LineBreak : String Read FLineBreak;
+
+  published
+    procedure TestHookUp;
+    procedure TestEmpty;
+    procedure TestDocumentStart;
+    procedure TestDocumentEnd;
+    procedure TestComment;
+    procedure TestAnchor;
+    procedure TestAnchorFailNoSpace;
+    procedure TestAlias;
+    procedure TestAliasFailNoSpace;
+    procedure TestScalar;
+    procedure TestNumScalar;
+    procedure TestSingleQuotedScalar;
+    procedure TestSingleQuotedScalarSpaces;
+    procedure TestDoubleQuotedScalar;
+    procedure TestDoubleQuotedScalarSpacesNewline;
+    procedure TestDoubleQuotedScalarError;
+    procedure TestLiteralScalar;
+    procedure TestLiteralScalar2;
+    procedure TestFoldedScalar;
+    procedure TestFoldedScalar2;
+    procedure TestPlainMultilineScalar;
+    procedure TestYAMLDirective;
+    procedure TestTAGDirectiveLocal;
+    procedure TestTAGDirectiveGlobal;
+    procedure TestSequence;
+    procedure TestFlowSequence;
+    procedure TestFlowSequenceEnd;
+    procedure TestSimplekey;
+    procedure TestSimpleLongkey;
+    procedure TestExplicitkey;
+    procedure TestEscape0;
+    procedure TestEscapeA;
+    procedure TestEscapeB;
+    procedure TestEscapeT;
+    procedure TestEscapeNMin;
+    procedure TestEscapeV;
+    procedure TestEscapeF;
+    procedure TestEscapeR;
+    procedure TestEscapeE;
+    procedure TestEscapeSpace;
+    procedure TestEscapeQuote;
+    procedure TestEscapeSlash;
+    procedure TestEscapeBackSlash;
+    procedure TestEscapeNMaj;
+    procedure TestEscapeUnderscore;
+    procedure TestEscapeL;
+    procedure TestEscapeP;
+    procedure TestEscapeX;
+    procedure TestEscapeUMin;
+    procedure TestEscapeUMAj;
+  end;
+
+implementation
+
+uses typinfo;
+
+{ TTestYAMLScanner }
+
+procedure TTestYAMLScanner.SetUp;
+begin
+  inherited SetUp;
+  FLineBreak:=#10;
+  FDocument:='';
+  FInput:=Nil;
+  FScanner:=Nil;
+
+end;
+
+procedure TTestYAMLScanner.TearDown;
+begin
+  FDocument:='';
+  FreeAndNil(FScanner);
+  FreeAndNil(FInput);
+  inherited TearDown;
+end;
+
+
+
+
+procedure TTestYAMLScanner.StartDocument(const aDoc: array of AnsiString);
+
+var
+  lDoc,S : String;
+
+begin
+  lDoc:='';
+  for S in aDoc do
+    begin
+    if lDoc<>'' then
+      lDoc:=lDoc+FLineBreak;
+    lDoc:=lDoc+S;
+    end;
+  StartDocument(lDoc);
+end;
+
+procedure TTestYAMLScanner.StartDocument(aDoc: AnsiString);
+begin
+  FDocument:=aDoc;
+  FInput:=TStringStream.Create(aDoc);
+  FScanner:=TYAMLScanner.Create(FInput);
+end;
+
+procedure TTestYAMLScanner.ReadToken;
+begin
+  AssertNotNull('have scanner',Scanner);
+  FLastToken:=FScanner.GetNextToken;
+end;
+
+procedure TTestYAMLScanner.TestHookUp;
+begin
+  AssertNull('No scanner',Scanner);
+  AssertNull('No input',ScannerInput);
+  AssertEquals('LineBreak',#10,LineBreak);
+  AssertEquals('No document','',Document);
+end;
+
+class procedure TTestYAMLScanner.AssertEquals(const msg: string; aExpected,aActual: TYAMLToken);
+
+begin
+  AssertEquals(Msg,GetEnumName(TypeInfo(TYAMLToken),ord(aExpected)),
+                   GetEnumName(TypeInfo(TYAMLToken),ord(aActual)));
+end;
+
+function TTestYAMLScanner.AssertToken(const msg: string; aToken: TYAMLToken; aRow: Integer; aCol: Integer) : TYAMLTokenData;
+
+var
+  lToken : TYAMLTokenData;
+
+begin
+  AssertNotNull(Scanner);
+  lToken:=Scanner.GetNextToken;
+  AssertToken(Msg,lToken,aToken,aRow,aCol);
+  FLastToken:=lToken;
+  Result:=lToken;
+end;
+
+procedure TTestYAMLScanner.AssertToken(const msg: string; aTokenData : TYAMLTokenData; aToken: TYAMLToken; aRow: Integer; aCol: Integer);
+
+begin
+  AssertEquals(msg+': Correct token',aToken,aTokenData.token);
+  if aRow<>-1 then
+    AssertEquals(msg+': Correct row',aRow,aTokenData.beginpos.Line);
+  if aCol<>-1 then
+    AssertEquals(msg+': Correct col',aCol,aTokenData.beginpos.Column);
+end;
+
+function TTestYAMLScanner.AssertToken(const msg: string; aToken: TYAMLToken; const aValue: string; aRow: Integer; aCol: Integer) : TYAMLTokenData;
+var
+  lToken : TYAMLTokenData;
+
+begin
+  AssertNotNull(Scanner);
+  lToken:=Scanner.GetNextToken;
+  AssertToken(Msg,lToken,aToken,aRow,aCol);
+  AssertEquals(Msg+' token value',aValue,lToken.value);
+  FLastToken:=lToken;
+  Result:=lToken;
+end;
+
+procedure TTestYAMLScanner.AssertEOF;
+begin
+  AssertToken('Empty',ytEOF,-1,-1);
+end;
+
+procedure TTestYAMLScanner.TestEmpty;
+begin
+  StartDocument('');
+  AssertToken('Empty',ytEOF,0,0);
+end;
+
+procedure TTestYAMLScanner.TestDocumentStart;
+begin
+  StartDocument('---');
+  AssertToken('Document start',ytDocumentStart,1,1);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestDocumentEnd;
+begin
+  StartDocument('...');
+  AssertToken('Document start',ytDocumentEnd,1,1);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestComment;
+begin
+  StartDocument('#');
+  AssertToken('Empty',ytEOF,1,0);
+end;
+
+procedure TTestYAMLScanner.TestAnchor;
+begin
+  StartDocument('&one ');
+  AssertToken('anchor',ytAnchor,'one',1,2);
+end;
+
+procedure TTestYAMLScanner.TestAnchorFailNoSpace;
+begin
+  StartDocument('&one');
+  AssertException('Need space',EYAMLScanner,@ReadToken);
+end;
+
+procedure TTestYAMLScanner.TestAlias;
+begin
+  StartDocument('*one ');
+  AssertToken('Alias',ytAlias,'one',1,2);
+end;
+
+procedure TTestYAMLScanner.TestAliasFailNoSpace;
+begin
+  StartDocument('*alias');
+  AssertException('Need space',EYAMLScanner,@ReadToken);
+end;
+
+procedure TTestYAMLScanner.TestScalar;
+begin
+  StartDocument('one');
+  AssertToken('scalar',ytScalarPlain,'one',1,1);
+end;
+
+procedure TTestYAMLScanner.TestNumScalar;
+begin
+  StartDocument('123');
+  AssertToken('scalar',ytScalarPlain,'123',1,1);
+end;
+
+procedure TTestYAMLScanner.TestSingleQuotedScalar;
+begin
+  StartDocument('''123''');
+  AssertToken('scalar',ytScalarSingle,'123',1,1);
+end;
+
+procedure TTestYAMLScanner.TestSingleQuotedScalarSpaces;
+begin
+  StartDocument('''123 456''');
+  AssertToken('scalar',ytScalarSingle,'123 456',1,1);
+end;
+
+procedure TTestYAMLScanner.TestDoubleQuotedScalarSpacesNewline;
+begin
+  StartDocument('"123 456\'#10'  \ abc def"');
+  AssertToken('scalar',ytScalarDouble,'123 456 abc def',1,1);
+end;
+
+procedure TTestYAMLScanner.TestDoubleQuotedScalar;
+begin
+  StartDocument('"123"');
+  AssertToken('scalar',ytScalarDouble,'123',1,1);
+end;
+
+procedure TTestYAMLScanner.TestDoubleQuotedScalarError;
+begin
+  StartDocument('"\"');
+  AssertException('End of stream',EYAMLScanner,@ReadToken);
+end;
+
+procedure TTestYAMLScanner.TestLiteralScalar;
+begin
+  // Example 8.7
+  StartDocument(['|',
+                 ' literal',
+                 ' '#9'text',
+                 '']);
+  AssertToken('Literal scalar',ytScalarLiteral,'literal'#10#9'text'#10,1,1);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestLiteralScalar2;
+begin
+  // Example 8.8
+  StartDocument(['|',
+                 ' ',
+                 '  ',
+                 '  literal',
+                 '   ',
+                 '  ',
+                 '  text',
+                 '',
+                 ' # Comment']);
+  AssertToken('Literal scalar',ytScalarLiteral,#10#10'literal'#10' '#10#10'text'#10,1,1);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestFoldedScalar;
+begin
+  // Example 8.9
+  StartDocument(['>',
+                 ' folded',
+                 ' text',
+                 '']);
+  AssertToken('Literal scalar',ytScalarFolded,'folded text'#10,1,1);
+  AssertEOF;
+end;
+
+
+procedure TTestYAMLScanner.TestFoldedScalar2;
+begin
+  // Example 8.10
+  // The example seems wrong in the sense that none of the scanners I tried
+  // returns a space for the empty line between bullet and list.
+  // Adapted the test accordingly
+  StartDocument(['>',
+                 '',
+                 ' folded',
+                 ' line',
+                 '',
+                 ' next',
+                 ' line',
+                 '   * bullet',
+                 '',
+                 '   * list',
+                 '   * lines',
+                 '',
+                 ' last',
+                 ' line',
+                 '',
+                 '# comment']);
+  AssertToken('Literal scalar',ytScalarFolded,#10'folded line'#10+
+                                             'next line'#10+
+                                             '  * bullet'#10+
+                                             ''#10+
+                                             '  * list'#10+
+                                             '  * lines'#10+
+                                             #10+
+                                             'last line'#10,1,1);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestPlainMultilineScalar;
+begin
+  StartDocument(['5',
+                 '  00']);
+  AssertToken('Plain scalar',ytScalarPlain,'5 00',1,1);
+end;
+
+
+procedure TTestYAMLScanner.TestYAMLDirective;
+var
+  lToken : TYAMLTokenData;
+begin
+  StartDocument('%YAML 1.2');
+  lToken:=AssertToken('Directive',ytVersionDirective,'1',1,1);
+  AssertEquals('minor','2',lToken.Value2);
+end;
+
+procedure TTestYAMLScanner.TestTAGDirectiveLocal;
+
+var
+  lToken : TYAMLTokenData;
+
+begin
+  StartDocument('%TAG !me! !you');
+  lToken:=AssertToken('Tag directive',ytTagDirective,'!me!',1,1);
+  AssertEquals('local prefix','!you',lToken.Value2);
+end;
+
+procedure TTestYAMLScanner.TestTAGDirectiveGlobal;
+var
+  lToken : TYAMLTokenData;
+
+begin
+  StartDocument('%TAG !me! tag:example.com,2000:app/');
+  lToken:=AssertToken('Tag directive',ytTagDirective,'!me!',1,1);
+  AssertEquals('local prefix','tag:example.com,2000:app/',lToken.Value2);
+end;
+
+procedure TTestYAMLScanner.TestSequence;
+begin
+  StartDocument('- ');
+  AssertToken('Sequence start',ytBlockSequenceStart,'',1,1);
+  AssertFalse('Block context',scanner.InFlowContext);
+end;
+
+procedure TTestYAMLScanner.TestFlowSequence;
+begin
+  StartDocument('[');
+  AssertToken('Flow sequence start',ytFlowSequenceStart,'',1,1);
+  AssertTrue('Flow context',scanner.InFlowContext);
+end;
+
+procedure TTestYAMLScanner.TestFlowSequenceEnd;
+begin
+  StartDocument(']');
+  AssertToken('Flow sequence end',ytFlowSequenceEnd,'',1,1);
+  AssertFalse('Block context',scanner.InFlowContext);
+end;
+
+procedure TTestYAMLScanner.TestSimplekey;
+begin
+  StartDocument('key: ');
+  AssertToken('Start mapping',ytBlockMappingStart,'',1,1);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertToken('Key directive',ytKey,'',1,1);
+  AssertToken('scalar key value',ytScalarPlain,'key',1,1);
+  AssertToken('value directive',ytValue,'',1,4);
+  AssertToken('end mapping',ytBlockEnd,'',1,0);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestSimpleLongkey;
+begin
+  StartDocument('long key: ');
+  AssertToken('Start mapping',ytBlockMappingStart,'',1,1);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertToken('Key directive',ytKey,'',1,1);
+  AssertToken('scalar key value',ytScalarPlain,'long key',1,1);
+  AssertToken('value directive',ytValue,'',1,9);
+  AssertToken('end mapping',ytBlockEnd,'',1,0);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertEOF;
+end;
+
+procedure TTestYAMLScanner.TestExplicitkey;
+begin
+  StartDocument('? key');
+  AssertToken('Start mapping',ytBlockMappingStart,'',1,1);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertToken('Key directive',ytKey,'',1,1);
+  AssertToken('scalar key value',ytScalarPlain,'key',1,3);
+  AssertToken('end mapping',ytBlockEnd,'',1,0);
+  AssertFalse('Block context',scanner.InFlowContext);
+  AssertToken('end',ytEOF,'',1,0);
+end;
+
+procedure TTestYAMLScanner.AssertEscape(aEscape,aResult : TYAMLString);
+begin
+  StartDocument('"a\'+aEscape+'"');
+  AssertToken('Token',ytScalarDouble,'a'+aResult,1,1);
+end;
+
+procedure TTestYAMLScanner.TestEscape0;
+begin
+  AssertEscape('0',#0);
+end;
+
+procedure TTestYAMLScanner.TestEscapeA;
+begin
+  AssertEscape('a',#7);
+end;
+
+procedure TTestYAMLScanner.TestEscapeB;
+begin
+  AssertEscape('b',#8);
+end;
+
+procedure TTestYAMLScanner.TestEscapeT;
+begin
+  AssertEscape('t',#9);
+end;
+
+procedure TTestYAMLScanner.TestEscapeNMin;
+begin
+  AssertEscape('n',#10);
+end;
+
+procedure TTestYAMLScanner.TestEscapeV;
+begin
+  AssertEscape('v',#11);
+end;
+
+procedure TTestYAMLScanner.TestEscapeF;
+begin
+  AssertEscape('f',#12);
+end;
+
+procedure TTestYAMLScanner.TestEscapeR;
+begin
+  AssertEscape('r',#13);
+end;
+
+procedure TTestYAMLScanner.TestEscapeE;
+begin
+  AssertEscape('e',#$1B);
+end;
+
+procedure TTestYAMLScanner.TestEscapeSpace;
+begin
+  AssertEscape(' ',' ');
+end;
+
+procedure TTestYAMLScanner.TestEscapeQuote;
+begin
+  AssertEscape('"','"');
+end;
+
+procedure TTestYAMLScanner.TestEscapeSlash;
+begin
+  AssertEscape('/','/');
+end;
+
+procedure TTestYAMLScanner.TestEscapeBackSlash;
+begin
+  AssertEscape('\','\');
+end;
+
+procedure TTestYAMLScanner.TestEscapeNMaj;
+begin
+  AssertEscape('N',#$C2#$85);
+end;
+
+procedure TTestYAMLScanner.TestEscapeUnderscore;
+begin
+  AssertEscape('_',#$C2#$A0);
+end;
+
+procedure TTestYAMLScanner.TestEscapeL;
+begin
+  AssertEscape('L',#$E2#$80#$A8);
+end;
+
+procedure TTestYAMLScanner.TestEscapeP;
+begin
+  AssertEscape('P',#$E2#$80#$A9);
+end;
+
+procedure TTestYAMLScanner.TestEscapeX;
+begin
+  AssertEscape('xA0',#$C2#$A0);
+end;
+
+procedure TTestYAMLScanner.TestEscapeUMin;
+begin
+  AssertEscape('u2029',#$E2#$80#$A9);
+end;
+
+procedure TTestYAMLScanner.TestEscapeUMAj;
+begin
+  AssertEscape('U000E0030',#$F3#$A0#$80#$B0);
+end;
+
+
+
+initialization
+  RegisterTest(TTestYAMLScanner);
+end.
+

+ 1 - 0
packages/fpmake_add.inc

@@ -161,5 +161,6 @@
   add_wasm_oi(ADirectory+IncludeTrailingPathDelimiter('wasm-oi'));
   add_wasm_oi(ADirectory+IncludeTrailingPathDelimiter('wasm-oi'));
   add_fcl_jsonschema(ADirectory+IncludeTrailingPathDelimiter('fcl-jsonschema'));
   add_fcl_jsonschema(ADirectory+IncludeTrailingPathDelimiter('fcl-jsonschema'));
   add_fcl_openapi(ADirectory+IncludeTrailingPathDelimiter('fcl-openapi'));
   add_fcl_openapi(ADirectory+IncludeTrailingPathDelimiter('fcl-openapi'));
+  add_fcl_yaml(ADirectory+IncludeTrailingPathDelimiter('fcl-yaml'));
   add_ptckvm(ADirectory+IncludeTrailingPathDelimiter('ptckvm'));
   add_ptckvm(ADirectory+IncludeTrailingPathDelimiter('ptckvm'));
   add_fcl_fpterm(ADirectory+IncludeTrailingPathDelimiter('fcl-fpterm'));
   add_fcl_fpterm(ADirectory+IncludeTrailingPathDelimiter('fcl-fpterm'));

+ 6 - 0
packages/fpmake_proc.inc

@@ -916,6 +916,12 @@ begin
 {$include fcl-openapi/fpmake.pp}
 {$include fcl-openapi/fpmake.pp}
 end;
 end;
 
 
+procedure add_fcl_yaml(const ADirectory: string);
+begin
+  with Installer do
+{$include fcl-yaml/fpmake.pp}
+end;
+
 procedure add_ptckvm(const ADirectory: string);
 procedure add_ptckvm(const ADirectory: string);
 begin
 begin
   with Installer do
   with Installer do