A robust TOML (Tom's Obvious, Minimal Language) parser and serializer for Free Pascal, almost fully compliant with the TOML v1.0.0 specification.
Note
Our extensive test suite (59 tests) ensures that TOML-FP adheres to the TOML v1.0.0 specification, covering all essential data types, structures, and edge cases.
- TOML Parser for Free Pascal
TOML-FP provides a complete solution for working with TOML configuration files in Free Pascal applications. Whether you need to read configuration files or generate them, TOML-FP offers an intuitive API with comprehensive type support and robust error handling.
- Full TOML v1.0.0 Compliance: Supports all TOML data types and structures, including proper serialization of nested (dotted) tables.
- Type-Safe API: Strong typing with compile-time checks
- Memory Management: Automatic cleanup with proper object lifecycle management
- Error Handling: Detailed error messages and exception handling
- Serialization: Convert Pascal objects to TOML and back
- Documentation: Comprehensive examples and API documentation
- Test Suite: Comprehensive test suite (59 items)
- More tests for error handling: Robust testing of invalid inputs and ensuring appropriate error messages or handling mechanisms.
- Performance optimization for large TOML files
- Additional examples for common use cases
- Free Pascal Compiler 3.2.2 or later
- Lazarus IDE 4.0 (for running tests)
-
Clone the repository:
git clone https://github.com/ikelaiah/toml-fp.git
-
Add to your project:
- Copy the
src
directory to your project. - Add the
src
directory to your project's unit search path. - Add the main
TOML
unit to youruses
clause:uses TOML;
- For direct access to specific types like
TTOMLValueType
or if you prefer explicit qualification, you might also includeTOML.Types
:However, for most common operations (parsing, serializing, using helper functions likeuses TOML, TOML.Types; // If needing TTOMLValueType, etc.
TOMLTable
),uses TOML;
is sufficient.
- Copy the
program BasicParseTOML;
{$mode objfpc}{$H+}{$J-}
uses
TOML;
var
Config: TTOMLTable;
RevisionValue, ProjectValue, ProjectName: TTOMLValue;
ProjectTable: TTOMLTable;
begin
// Parse TOML from file
Config := ParseTOMLFromFile('config.toml');
try
// Access a string value
if (Config.TryGetValue('revision', RevisionValue)) then
WriteLn('The value of ''revision'' is ', RevisionValue.AsString);
// Access nested values safely
if Config.TryGetValue('project', ProjectValue) and
(ProjectValue is TTOMLTable) then
begin
ProjectTable := TTOMLTable(ProjectValue);
if ProjectTable.TryGetValue('name', ProjectName) then
WriteLn('Project Name: ', ProjectName.AsString)
else
WriteLn('Project name not found');
end
else
WriteLn('Project configuration not found');
finally
Config.Free;
end;
end.
Example config.toml
:
# config.toml
revision = "1.2.1af"
[project]
name = "My Amazing Project"
version = "1.0.0"
program BasicSerializeTOML;
{$mode objfpc}{$H+}{$J-}
uses
TOML;
var
Config: TTOMLTable;
Database: TTOMLTable;
begin
Config := TOMLTable;
try
Database := TOMLTable;
Database.Add('host', TOMLString('localhost'));
Database.Add('port', TOMLInteger(5432));
Config.Add('database', Database);
if SerializeTOMLToFile(Config, 'config.toml') then
WriteLn('Configuration saved successfully')
else
WriteLn('Error saving configuration');
finally
Config.Free;
end.
end.
var
Config: TTOMLTable;
Tags: TTOMLArray;
begin
Config := TOMLTable;
try
Tags := TOMLArray;
Tags.Add(TOMLString('pascal'));
Tags.Add(TOMLString('toml'));
Config.Add('tags', Tags);
WriteLn(SerializeTOML(Config));
finally
Config.Free;
end.
end.
var
Config: TTOMLTable;
Database, Server: TTOMLTable;
SerializedTOML: string;
begin
Config := TOMLTable;
try
Database := TOMLTable;
Database.Add('host', TOMLString('localhost'));
Database.Add('port', TOMLInteger(5432));
Server := TOMLTable;
Server.Add('database', Database); // Nest Database table under Server
Config.Add('server', Server); // Nest Server table under Config
SerializedTOML := SerializeTOML(Config);
WriteLn(SerializedTOML);
// Expected output:
// [server.database]
// host = "localhost"
// port = 5432
finally
Config.Free; // Frees Config, Server, and Database tables
end.
end.
TOML-FP correctly handles the distinction between hierarchical paths and literal dotted keys as per the TOML specification:
-
Hierarchical Table Paths:
// This creates a nested hierarchy: [server.database] ServerTable := TOMLTable; DatabaseTable := TOMLTable; DatabaseTable.Add('port', TOMLInteger(5432)); ServerTable.Add('database', DatabaseTable); Config.Add('server', ServerTable);
Serializes to:
[server.database] port = 5432
-
Literal Dotted Keys:
// This creates a literal dotted key: ["server.database"] LiteralTable := TOMLTable; LiteralTable.Add('port', TOMLInteger(5432)); Config.Add('server.database', LiteralTable);
Serializes to:
["server.database"] port = 5432
The serializer automatically determines whether to quote key components based on the TOML specification rules.
Tip
According to the TOML specification: "Quoted keys follow the exact same rules as either basic strings or literal strings and allow you to use a much broader set of key names. Best practice is to use bare keys except when absolutely necessary."
In practice, this means you should design your configuration to use hierarchical nesting with simple key names rather than relying on keys with dots or special characters.
program BasicSerializeTOML;
{$mode objfpc}{$H+}{$J-}
uses
TOML, SysUtils;
var
Config, ServerConfig: TTOMLTable;
Ports: TTOMLArray;
SerializedTOML: string;
begin
Config := TOMLTable;
try
// Create a nested table
ServerConfig := TOMLTable;
ServerConfig.Add('host', TOMLString('127.0.0.1'));
ServerConfig.Add('enabled', TOMLBoolean(True));
// Create and populate an array
Ports := TOMLArray;
Ports.Add(TOMLInteger(80));
Ports.Add(TOMLInteger(443));
ServerConfig.Add('ports', Ports);
// Add the server config to main config
Config.Add('server', ServerConfig);
// Add some basic metadata
Config.Add('version', TOMLFloat(1.0));
Config.Add('last_updated', TOMLDateTime(Now));
// Serialize to TOML format
SerializedTOML := SerializeTOML(Config);
WriteLn('Generated TOML:');
WriteLn(SerializedTOML);
// Save to file
if SerializeTOMLToFile(Config, 'config.toml') then
WriteLn('Successfully saved to file')
else
WriteLn('Error saving to file');
finally
Config.Free;
end.
end.
This will generate TOML like:
version = 1.0
last_updated = 2024-03-20T15:30:45Z
[server]
host = "127.0.0.1"
enabled = true
ports = [ 80, 443 ]
Note: All values are properly type-checked and memory-managed. The library ensures that:
- Each value has the correct TOML type
- Arrays maintain type consistency
- All objects are properly freed
- Type conversions are validated
Note
Only free the top-level owner table to avoid memory management issues.
- Proper memory management is essential to prevent memory leaks in your applications. TOML-FP follows a clear ownership hierarchy where parent tables own their nested tables and values. Here's how it works:
- When you create a
TTOMLTable
, it acts as an owner for any nested tables or values you add to it.
- Use the
Add
method to insert nested tables or values into a parent table. - Example:
var
Config: TTOMLTable;
Database: TTOMLTable;
begin
Config := TOMLTable;
Database := TOMLTable;
Config.Add('database', Database);
// ...
Config.Free; // This frees all nested tables and values
end.
- Only free the top-level owner table.
- Frees all nested tables and values automatically.
- Example:
var
Config: TTOMLTable;
begin
Config := TOMLTable;
// ... add nested tables and values ...
Config.Free; // This frees all nested tables and values
end.
- Do not manually free nested tables or values to prevent memory management issues.
TTOMLValue
- Base type for all TOML valuesTTOMLTable
- Represents a TOML tableTTOMLArray
- Represents a TOML arrayTTOMLString
- Represents a TOML string valueTTOMLInteger
- Represents a TOML integer valueTTOMLFloat
- Represents a TOML float valueTTOMLBoolean
- Represents a TOML boolean valueTTOMLDateTime
- Represents a TOML datetime value
TOMLString
function TOMLString(const AValue: string): TTOMLString;
Creates a TOML string value.
TOMLInteger
function TOMLInteger(const AValue: Int64): TTOMLInteger;
Creates a TOML integer value.
TOMLFloat
function TOMLFloat(const AValue: Double): TTOMLFloat;
Creates a TOML float value.
TOMLBoolean
function TOMLBoolean(const AValue: Boolean): TTOMLBoolean;
Creates a TOML boolean value.
TOMLDateTime
function TOMLDateTime(const AValue: TDateTime): TTOMLDateTime;
Creates a TOML datetime value.
TOMLArray
function TOMLArray: TTOMLArray;
Creates a TOML array.
TOMLTable
function TOMLTable: TTOMLTable;
Creates a TOML table.
ParseTOML
function ParseTOML(const ATOML: string): TTOMLTable;
Parses a TOML-formatted string into a TTOMLTable
object. Raises ETOMLParserException
on error.
ParseTOMLFromFile
function ParseTOMLFromFile(const AFileName: string): TTOMLTable;
Parses a TOML file into a TTOMLTable
object. Reads the file content and then parses it. Raises ETOMLParserException
on parsing errors or file system errors.
SerializeTOML
function SerializeTOML(const AValue: TTOMLValue): string;
Serializes a TTOMLValue
(typically a TTOMLTable
for a whole document) into a TOML-formatted string.
SerializeTOMLToFile
function SerializeTOMLToFile(const AValue: TTOMLValue; const AFileName: string): Boolean;
Serializes a TTOMLValue
and saves it to the specified file. Returns True
on success, False
on failure (e.g., file I/O error).
The library includes a comprehensive test suite (59 items).
To run the tests:
- Open
tests/TestRunner.lpi
in Lazarus - Build and run the project
See Test-Coverage-Overview.md for details.
- Basic Types:
- Strings, Integers, Floats, Booleans, DateTime
- Advanced Structures:
- Multiline and Literal Strings
- Homogeneous and Mixed-Type Arrays
- Nested and Inline Tables
- Serialization:
- Accurate conversion of Pascal objects to TOML format and vice versa
- Error Handling:
- Robust tests for invalid inputs, duplicate keys, and other edge cases
$ ./TestRunner.exe -a --format=plain
Time:00.008 N:56 E:0 F:0 I:0
TTOMLTestCase Time:00.008 N:56 E:0 F:0 I:0
Test01_StringValue
Test02_IntegerValue
...
Test70_ComplexKeys
Number of run tests: 56
Number of errors: 0
Number of failures: 0
Heap dump by heaptrc unit of path\to\TestRunner.exe
2991 memory blocks allocated : 195551/208600
2991 memory blocks freed : 195551/208600
0 unfreed memory blocks : 0
True heap size : 294912 (256 used in System startup)
True free heap : 294656
Check out the examples
directory and the test cases, tests/TestCaseTOML.pas
, for more detailed usage examples:
- Basic Configuration Reading
- Writing Complex Data Structures
- Error Handling Patterns
- Integration Examples
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Please ensure:
- Code follows the project's style guidelines
- All tests pass
- New features include appropriate tests
- Documentation is updated
This project is licensed under the MIT License - see the LICENSE.md file for details.
- TOML specification creators and maintainers
- Free Pascal and Lazarus communities
- All contributors to this project