Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

> [!NOTE]
> Until the release of version 1.0, this project will be in development
> mode. In consequence, even minor versions might contain breaking changes.
> Starting with version 1.0, we'll follow [Semantic
> Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- The library was missing memory management. This release introduces the
Controlled Type `Document` that'll trigger a cleanup when it goes out of scope.
See [README](README.md) and the example programs for how this works.

## [0.1.0] - 2025-12-24

### Added

- First release of the library. It's going to be in development mode until
release 1.0. The practical consequence is that until then, even minor versions
might well contain _breaking changes_.
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ alr with tinyaml

```ada
with Ada.Text_IO;
with Tinyaml.Documents; use Tinyaml.Documents;
with Tinyaml.Parser;
with Tinyaml.Nodes; use Tinyaml.Nodes;
with Tinyaml.Nodes.Prelude; use Tinyaml.Nodes.Prelude;
Expand All @@ -29,10 +30,10 @@ procedure Example is
" host: localhost" & ASCII.LF &
" port: 5432";

Doc : Node_Access;
-- Document automatically frees memory when it goes out of scope
Doc : constant Document := Tinyaml.Parser.Parse_Document (Config);
begin
Doc := Tinyaml.Parser.Parse (Config);
Ada.Text_IO.Put_Line (Get_String (Doc, "database.host")); -- "localhost"
Ada.Text_IO.Put_Line (Get_String (Root (Doc), "database.host")); -- "localhost"
end Example;
```

Expand Down Expand Up @@ -96,7 +97,15 @@ These YAML features are intentionally rejected to keep configurations simple and
### Parsing

```ada
Doc := Tinyaml.Parser.Parse (Input : String) return Node_Access;
-- Recommended: automatic memory management with Document wrapper
Doc : Document := Tinyaml.Parser.Parse_Document (Input);
Root_Node : Node_Access := Root (Doc); -- Access the root node
-- Memory is freed when Doc goes out of scope

-- Alternative: manual memory management
Doc : Node_Access := Tinyaml.Parser.Parse (Input);
-- ... use Doc ...
Free_Node (Doc); -- Must call explicitly to avoid memory leak
```

### Node Types and Navigation
Expand Down Expand Up @@ -156,6 +165,30 @@ Schema.Seq ("tags", To_Seq_Item (Str_Schema'(null record)));
Schema.Map ("database", Db_Schema); -- Nested map
```

### Memory Management

TinyAML provides two approaches for memory management:

**Automatic (recommended):** Use `Document` which is a controlled type that automatically frees the entire node tree when it goes out of scope:

```ada
declare
Doc : constant Document := Tinyaml.Parser.Parse_Document (Config);
begin
Put_Line (Get_String (Root (Doc), "database.host"));
end; -- All nodes freed automatically here
```

**Manual:** Use `Parse` and call `Free_Node` explicitly:

```ada
Doc : Node_Access := Tinyaml.Parser.Parse (Config);
-- ... use Doc ...
Free_Node (Doc); -- Recursively frees all nodes; sets Doc to null
```

For schemas, `Free_Schema` is available for cleanup when schemas are created dynamically, though schemas are typically static and live for the program's lifetime.

## Building from Source

Requires Ada 2022 compiler (FSF GNAT 13+).
Expand Down
25 changes: 15 additions & 10 deletions examples/basic_parsing.adb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
--
-- Demonstrates parsing YAML without schema validation.
-- All values are strings until explicitly converted.
-- Uses Document wrapper for automatic memory management.

with Ada.Text_IO;
with Tinyaml.Documents;
with Tinyaml.Parser;
with Tinyaml.Nodes;
with Tinyaml.Nodes.Prelude;

procedure Basic_Parsing is
use Ada.Text_IO;
use Tinyaml.Documents;
use Tinyaml.Nodes;
use Tinyaml.Nodes.Prelude;

Expand All @@ -22,22 +25,22 @@ procedure Basic_Parsing is
" - logging" & ASCII.LF &
" - metrics";

Doc : Node_Access;
-- Document automatically frees memory when it goes out of scope
Doc : constant Document := Tinyaml.Parser.Parse_Document (Config);
begin
Put_Line ("Parsing configuration...");
New_Line;

Doc := Tinyaml.Parser.Parse (Config);

-- Access nested values using path notation
Put_Line ("Database host: " & Get_String (Doc, "database.host"));
Put_Line ("Database port: " & Get_String (Doc, "database.port"));
Put_Line ("Database name: " & Get_String (Doc, "database.name"));
-- Access nested values using path notation (Root returns the root node)
Put_Line ("Database host: " & Get_String (Root (Doc), "database.host"));
Put_Line ("Database port: " & Get_String (Root (Doc), "database.port"));
Put_Line ("Database name: " & Get_String (Root (Doc), "database.name"));
New_Line;

-- Check for optional fields using Navigate (returns null if missing)
declare
Timeout : constant Node_Access := Navigate (Doc, "database.timeout");
Timeout : constant Node_Access :=
Navigate (Root (Doc), "database.timeout");
begin
if Timeout /= null then
Put_Line ("Database timeout: " & Value (Timeout));
Expand All @@ -49,7 +52,7 @@ begin

-- Alternative: use Contains on Map_Node
declare
Database : constant Node_Access := Navigate (Doc, "database");
Database : constant Node_Access := Navigate (Root (Doc), "database");
begin
if Is_Map (Database.all) then
if Map_Node (Database.all).Contains ("port") then
Expand All @@ -65,7 +68,7 @@ begin

-- Access sequence items
declare
Features : constant Node_Access := Navigate (Doc, "features");
Features : constant Node_Access := Navigate (Root (Doc), "features");
Seq : Sequence_Node;
begin
Put_Line ("Features:");
Expand All @@ -76,4 +79,6 @@ begin
end loop;
end if;
end;

-- When Doc goes out of scope here, the entire tree is automatically freed
end Basic_Parsing;
34 changes: 21 additions & 13 deletions examples/error_handling.adb
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
-- Error Handling Example
--
-- Demonstrates handling parse errors and validation failures.
-- Shows both Document-based API and manual memory management patterns.

with Ada.Text_IO;
with Ada.Exceptions;
with Tinyaml;
with Tinyaml.Documents;
with Tinyaml.Parser;
with Tinyaml.Nodes;
with Tinyaml.Nodes.Prelude;

procedure Error_Handling is
use Ada.Text_IO;
use Tinyaml.Documents;
use Tinyaml.Nodes;
use Tinyaml.Nodes.Prelude;

Expand All @@ -21,15 +24,16 @@ procedure Error_Handling is

-- Invalid YAML: flow style (not supported)
Flow_Style : constant String := "{key: value}";

Doc : Node_Access;
pragma Unreferenced (Doc);
begin
-- Example 1: Duplicate key error
-- Example 1: Duplicate key error (exception handling with Document)
Put_Line ("Parsing YAML with duplicate keys...");
begin
Doc := Tinyaml.Parser.Parse (Bad_Yaml);
Put_Line (" Unexpected success!");
declare
Doc : constant Document := Tinyaml.Parser.Parse_Document (Bad_Yaml);
pragma Unreferenced (Doc);
begin
Put_Line (" Unexpected success!");
end;
exception
when E : Tinyaml.Parse_Error =>
Put_Line (" Parse error: " & Ada.Exceptions.Exception_Message (E));
Expand All @@ -39,33 +43,37 @@ begin
-- Example 2: Unsupported syntax
Put_Line ("Parsing flow-style YAML (not supported)...");
begin
Doc := Tinyaml.Parser.Parse (Flow_Style);
Put_Line (" Unexpected success!");
declare
Doc : constant Document := Tinyaml.Parser.Parse_Document (Flow_Style);
pragma Unreferenced (Doc);
begin
Put_Line (" Unexpected success!");
end;
exception
when E : Tinyaml.Parse_Error =>
Put_Line (" Parse error: " & Ada.Exceptions.Exception_Message (E));
end;
New_Line;

-- Example 3: Safe navigation with null checks
-- Document automatically frees memory when it goes out of scope
Put_Line ("Safe navigation with null checks...");
declare
Config : constant String := "database:" & ASCII.LF & " host: localhost";
Root : Node_Access;
Doc : constant Document := Tinyaml.Parser.Parse_Document (Config);
Found : Node_Access;
begin
Root := Tinyaml.Parser.Parse (Config);

-- This path exists
Found := Navigate (Root, "database.host");
Found := Navigate (Root (Doc), "database.host");
if Found /= null then
Put_Line (" Found: database.host = " & Value (Found));
end if;

-- This path doesn't exist
Found := Navigate (Root, "database.port");
Found := Navigate (Root (Doc), "database.port");
if Found = null then
Put_Line (" Not found: database.port");
end if;
end;
-- Doc is finalized here, freeing all nodes
end Error_Handling;
31 changes: 31 additions & 0 deletions src/tinyaml-documents.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package body Tinyaml.Documents is

use type Nodes.Node_Access;

overriding procedure Finalize (Doc : in out Document) is
begin
if Doc.Root_Node /= null then
Nodes.Free_Node (Doc.Root_Node);
end if;
end Finalize;

function Is_Empty (Doc : Document) return Boolean is
begin
return Doc.Root_Node = null;
end Is_Empty;

function Root (Doc : Document) return Nodes.Node_Access is
begin
return Doc.Root_Node;
end Root;

procedure Set_Root (Doc : in out Document; N : Nodes.Node_Access) is
begin
-- If there was a previous root, free it first
if Doc.Root_Node /= null then
Nodes.Free_Node (Doc.Root_Node);
end if;
Doc.Root_Node := N;
end Set_Root;

end Tinyaml.Documents;
39 changes: 39 additions & 0 deletions src/tinyaml-documents.ads
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- TinyAML Document - RAII wrapper for parsed YAML documents
--
-- Document owns the root node and automatically deallocates the entire
-- document tree when it goes out of scope.

with Ada.Finalization;

with Tinyaml.Nodes;

package Tinyaml.Documents is

---------------------------------------------------------------------------
-- Document Type (RAII wrapper)
---------------------------------------------------------------------------

type Document is new Ada.Finalization.Limited_Controlled with private;

-- Check if document has a valid root node
function Is_Empty (Doc : Document) return Boolean;

-- Get the root node for navigation (null if empty)
function Root (Doc : Document) return Nodes.Node_Access;

---------------------------------------------------------------------------
-- Internal: Used by Parser to construct Document
---------------------------------------------------------------------------

procedure Set_Root (Doc : in out Document; N : Nodes.Node_Access);

private

type Document is new Ada.Finalization.Limited_Controlled with record
Root_Node : Nodes.Node_Access := null;
end record;

overriding procedure Finalize (Doc : in out Document);
-- Recursively frees all nodes in the document tree

end Tinyaml.Documents;
Loading