Predicator.Conformance.AnalysisGenerator (predicator v9.5.0)

Copy Markdown View Source

The pure half of the corpus's analysis surface.

generate/1 takes decoded authored analysis cases - the maps in conformance/analysis/cases.json - and completes each one from Predicator.Analysis: the instructions a source compiles to (through Predicator.compile_program/1), and the references, calls and assignments the analysis lists for it. mix corpus.generate is the I/O shell that writes the result to conformance/analysis/corpus.json.

The expected values are always computed, never authored: a case names a program, and the reference implementation says what it reads, calls and stores. A case authored as source is analysed in both forms - the source string and its compiled instructions - and generation fails, naming the case, when the two forms disagree on anything but positions, since the instruction form carries none. That keeps one expected set per case honest for both forms a runner may read.

Only cases the analysis answers {:ok, list} for are shipped: a source that does not parse, or an instruction the analysis cannot read, fails generation rather than becoming a case.

The JSON spelling of an answer (conformance/README.md, "The analysis surface"): a segment is a string, an integer, or {"$type": "dynamic"}; a position is [line, column] or null.

Examples

iex> Predicator.Conformance.AnalysisGenerator.generate([
...>   %{"id" => "analysis/example", "source" => "items[k] > 1"}
...> ])
{:ok,
 [
   %{
     "id" => "analysis/example",
     "source" => "items[k] > 1",
     "instructions" => [
       ["load", "items"],
       ["load", "k"],
       ["bracket_access"],
       ["lit", 1],
       ["compare", "GT"],
       ["pop"]
     ],
     "references" => [
       %{"path" => ["items", %{"$type" => "dynamic"}], "position" => [1, 1]},
       %{"path" => ["k"], "position" => [1, 7]}
     ],
     "calls" => [],
     "assignments" => []
   }
 ]}

Summary

Types

An authored analysis case, already JSON-decoded.

One per-case generation failure, in Predicator.Conformance.Generator's shape.

A completed analysis case, as one line of conformance/analysis/corpus.json.

Functions

Completes every authored analysis case, sorted by id.

Types

authored_case()

@type authored_case() :: %{optional(String.t()) => term()}

An authored analysis case, already JSON-decoded.

case_error()

@type case_error() :: %{id: String.t() | nil, problem: String.t()}

One per-case generation failure, in Predicator.Conformance.Generator's shape.

completed_case()

@type completed_case() :: %{optional(String.t()) => term()}

A completed analysis case, as one line of conformance/analysis/corpus.json.

Functions

generate(cases)

@spec generate([authored_case()]) ::
  {:ok, [completed_case()]} | {:error, [case_error()]}

Completes every authored analysis case, sorted by id.

Returns {:ok, cases} when every case generates cleanly, or {:error, errors} naming every failing case, not just the first.

Examples

iex> Predicator.Conformance.AnalysisGenerator.generate([%{"id" => "analysis/bad", "source" => "limit >"}])
{:error, [%{id: "analysis/bad", problem: "source \"limit >\" has no analysis: Expected number, string, boolean, date, datetime, identifier, function call, list, object, or '(' but found end of input"}]}