WplAi (WPL-AI v1.7.0)

Copy Markdown View Source

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  Rendering

Example

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

activity_counts(doc)

@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}

activity_types(doc)

@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]

compile(doc)

@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"

compile!(doc)

@spec compile!(WplAi.AST.Document.t()) :: map()

Compile a WPL-AI AST document to WPL JSON format, raising on error.

decompile(json)

@spec decompile(map()) :: {:ok, String.t()} | {:error, term()}

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!(json)

@spec decompile!(map()) :: String.t()

Decompile WPL JSON to WPL-AI text, raising on error.

exercise_refs(doc)

@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_errors(errors, source \\ nil)

@spec format_errors(list(), String.t() | nil) :: String.t()

Format parse errors for display.

Examples

iex> {:error, errors} = WplAi.parse("INVALID")
iex> WplAi.format_errors(errors)
"1. [Parse Error] ..."

parse(source)

@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}}}

parse!(source)

@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}}

round_trip(source)

@spec round_trip(String.t()) :: {:ok, String.t()} | {:error, term()}

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)

to_wpl(source)

@spec to_wpl(String.t()) :: {:ok, map()} | {:error, list()}

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"

to_wpl!(source)

@spec to_wpl!(String.t()) :: map()

Parse and compile WPL-AI source text to WPL JSON, raising on error.

tokenize(source)

@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

total_duration_days(doc)

@spec total_duration_days(WplAi.AST.Document.t()) :: integer()

Calculate total duration from phases in a parsed document.

Returns duration in days.

validate(source)

@spec validate(String.t()) :: :ok | {:error, list()}

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, _}

validate_semantics(doc)

@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"))
[]

version()

@spec version() :: String.t()

Get the WPL-AI language version.