WPL-AI: Human and AI-friendly authoring language for wellness plans.
This module provides the public API for parsing, validating, and working with WPL-AI documents.
Overview
WPL-AI is a linear, indentation-based language designed to be easily written by both humans and LLMs. It compiles to canonical WPL JSON.
Architecture
LLM / Human
↓
WPL-AI text
↓
[Parser] → AST
↓
[Compiler] → WPL JSON
↓
Validation • Personalization • RenderingExample
iex> source = """
...> PLAN "Upper Body Beginner"
...> TYPE workout
...> DIFFICULTY beginner
...>
...> PHASES
...> PHASE "Foundation" (2 weeks):
...> WEEK 1:
...> DAY Monday training 45m "Upper Body":
...> warmup:
...> jumping_jacks 2m
...> main straight_sets:
...> push_up 3x8..12 target 10 rpe 7
...> """
iex> {:ok, ast} = WplAi.parse(source)
iex> ast.header.name
"Upper Body Beginner"
Summary
Functions
Count activities by type in a parsed document.
Get all activity types used in a parsed document.
Compile a WPL-AI AST document to WPL JSON format.
Compile a WPL-AI AST document to WPL JSON format, raising on error.
Decompile WPL JSON to WPL-AI text.
Decompile WPL JSON to WPL-AI text, raising on error.
Get all exercise references from a parsed document.
Format parse errors for display.
Parse WPL-AI source text into an AST.
Parse WPL-AI source text into an AST, raising on error.
Convert WPL-AI text to WPL JSON and back to WPL-AI (round-trip).
Parse and compile WPL-AI source text to WPL JSON in one step.
Parse and compile WPL-AI source text to WPL JSON, raising on error.
Tokenize WPL-AI source text.
Calculate total duration from phases in a parsed document.
Validate WPL-AI source text without fully parsing.
Run semantic validation on a parsed AST document, returning a list of warnings.
Get the WPL-AI language version.
Functions
@spec activity_counts(WplAi.AST.Document.t()) :: map()
Count activities by type in a parsed document.
Examples
iex> {:ok, doc} = WplAi.parse(source)
iex> WplAi.activity_counts(doc)
%{exercise: 15, cardio: 3, meditation: 2}
@spec activity_types(WplAi.AST.Document.t()) :: [atom()]
Get all activity types used in a parsed document.
Examples
iex> {:ok, doc} = WplAi.parse(source)
iex> WplAi.activity_types(doc)
[:exercise, :cardio, :meditation]
@spec compile(WplAi.AST.Document.t()) :: {:ok, map()} | {:error, list()}
Compile a WPL-AI AST document to WPL JSON format.
Returns {:ok, json_map} on success, or {:error, errors} on failure.
Examples
iex> {:ok, ast} = WplAi.parse("PLAN \"Test\"\nTYPE workout")
iex> {:ok, json} = WplAi.compile(ast)
iex> json["plan"]["name"]
"Test"
@spec compile!(WplAi.AST.Document.t()) :: map()
Compile a WPL-AI AST document to WPL JSON format, raising on error.
Decompile WPL JSON to WPL-AI text.
This enables editing existing plans in the LLM-friendly format and round-trip transformation.
Returns {:ok, text} on success, or {:error, reason} on failure.
Examples
iex> {:ok, text} = WplAi.decompile(json)
iex> String.starts_with?(text, "PLAN")
true
Decompile WPL JSON to WPL-AI text, raising on error.
@spec exercise_refs(WplAi.AST.Document.t()) :: [String.t()]
Get all exercise references from a parsed document.
Useful for validating that all exercises exist in the library.
Examples
iex> {:ok, doc} = WplAi.parse(source)
iex> WplAi.exercise_refs(doc)
["push_up", "squat", "plank"]
Format parse errors for display.
Examples
iex> {:error, errors} = WplAi.parse("INVALID")
iex> WplAi.format_errors(errors)
"1. [Parse Error] ..."
@spec parse(String.t()) :: {:ok, WplAi.AST.Document.t()} | {:error, list()}
Parse WPL-AI source text into an AST.
Returns {:ok, AST.Document.t()} on success, or {:error, errors} on failure.
Examples
iex> WplAi.parse("PLAN \"Test\"\nTYPE workout")
{:ok, %WplAi.AST.Document{header: %WplAi.AST.Header{name: "Test", type: :workout}}}
@spec parse!(String.t()) :: WplAi.AST.Document.t()
Parse WPL-AI source text into an AST, raising on error.
Examples
iex> WplAi.parse!("PLAN \"Test\"\nTYPE workout")
%WplAi.AST.Document{header: %WplAi.AST.Header{name: "Test", type: :workout}}
Convert WPL-AI text to WPL JSON and back to WPL-AI (round-trip).
Useful for normalizing WPL-AI text or testing round-trip correctness.
Examples
iex> {:ok, normalized} = WplAi.round_trip(source)
Parse and compile WPL-AI source text to WPL JSON in one step.
Returns {:ok, json_map} on success, or {:error, errors} on failure.
Examples
iex> {:ok, json} = WplAi.to_wpl("PLAN \"Test\"\nTYPE workout")
iex> json["plan"]["name"]
"Test"
Parse and compile WPL-AI source text to WPL JSON, raising on error.
@spec tokenize(String.t()) :: {:ok, [WplAi.Lexer.token()]} | {:error, list()}
Tokenize WPL-AI source text.
Returns {:ok, tokens} on success, or {:error, errors} on failure.
Useful for debugging or building syntax highlighters.
Examples
iex> {:ok, tokens} = WplAi.tokenize("PLAN \"Test\"")
iex> length(tokens)
3 # PLAN keyword, string, EOF
@spec total_duration_days(WplAi.AST.Document.t()) :: integer()
Calculate total duration from phases in a parsed document.
Returns duration in days.
Validate WPL-AI source text without fully parsing.
Returns :ok if valid, or {:error, errors} if invalid.
This is faster than full parsing when you only need validation.
Examples
iex> WplAi.validate("PLAN \"Test\"\nTYPE workout")
:ok
iex> WplAi.validate("INVALID")
{:error, _}
@spec validate_semantics(WplAi.AST.Document.t()) :: [WplAi.Validator.warning()]
Run semantic validation on a parsed AST document, returning a list of warnings.
Warnings (not errors) are emitted for vocabulary values that don't match known WPL schema enums — e.g. unknown measurement metrics or questionnaire identifiers. The plan is still valid and compilable; warnings are advisory only.
Examples
iex> {:ok, doc} = WplAi.parse(source)
iex> warnings = WplAi.validate_semantics(doc)
iex> Enum.filter(warnings, &String.contains?(&1.message, "measurement metric"))
[]
@spec version() :: String.t()
Get the WPL-AI language version.