Skip to content

Embedding Nexus Script

An application can compile a document, validate it, and consume the native Live graph. The graph retains real reference targets and metadata, so the application does not need to reconstruct relationships from JSON or parse Nexus Script again.

This chapter is for Object Pascal developers using the Nexus source packages. The earlier learning chapters do not require this API knowledge.

A complete target-aware reader

Save Regional.nxscript from Targets, then save this program:

InspectCatalog.lpr
program InspectCatalog;
{$mode delphi}{$H+}

uses
  SysUtils, obNexusScriptModel, obNexusScriptSession,
  obNexusScriptValidator, obNexusScriptLive;

procedure Run;
var
  lTargets: TNexusScriptTargetSelection;
  lSession: TNexusScriptCompilationSession;
  lValidator: TNexusScriptValidator;
  lLive: TNexusScriptLiveDocument;
  lShop: TNexusScriptLiveDefinition;
  lFeatured: TNexusScriptLiveDefinition;
begin
  if ParamCount <> 2 then
    raise Exception.Create('Usage: InspectCatalog <document> <region>');
  lLive := nil;
  lTargets := TNexusScriptTargetSelection.Create;
  try
    lTargets.Add('Region', ParamStr(2));
    lSession := TNexusScriptCompilationSession.Create(lTargets);
    try
      if not lSession.CompileFile(ParamStr(1)) then
        raise Exception.Create(lSession.LastError);
      if lSession.EntryCompiler.CompiledDocument.DialectDocument <> nil then
      begin
        lValidator := TNexusScriptValidator.Create;
        try
          if not lValidator.Validate(lSession.EntryCompiler.CompiledDocument,
            lSession.EntryCompiler.CompiledDocument.DialectDocument) then
            raise Exception.Create(lValidator.Diagnostics[0].MessageText);
        finally
          lValidator.Free;
        end;
      end;
      lLive := TNexusScriptLiveEmitter.Emit(
        lSession.EntryCompiler.CompiledDocument);
    finally
      lSession.Free;
    end;
  finally
    lTargets.Free;
  end;
  try
    lShop := lLive.FindDefinition('Shop');
    if lShop = nil then
      raise Exception.Create('Expected a Shop root');
    if lShop.FindProperty('Featured') = nil then
      raise Exception.Create('Expected Shop.Featured');
    lFeatured := lShop.FindProperty('Featured').Value.AsDefinition;
    WriteLn(lFeatured.FindProperty('Title').Value.AsText);
    WriteLn(lFeatured.FindProperty('Currency').Value.AsText);
    WriteLn(lFeatured.FindProperty('PriceCents').Value.AsText);
  finally
    lLive.Free;
  end;
end;

begin
  try
    Run;
  except
    on E: Exception do
    begin
      WriteLn(StdErr, E.Message);
      Halt(1);
    end;
  end;
end.

The program expects the Featured product shape shown in Regional.nxscript. For an application accepting other input, supply a dialect for its required shape before navigating those members.

Build with the source package catalog

Save this project beside the program. Replace <Nexus source> with the absolute Nexus source-checkout path, using forward slashes. Its package checkouts and configured Nexus Pascal environment must be present.

InspectCatalog.nxproject
dialect "projects/nxbuild/language/nxbuild.Language.nxscript";
module Projects "<Nexus source>/projects/nxbuild/Projects.nxscript";
module Tools "NXBuild.Environment.nxscript";

Project InspectCatalog {
    Id: "Example.InspectCatalog";
    Requires: [
        @Projects.NexusRTL, @Projects.RTLSystem, @Projects.RTLObjPas,
        @Projects.RTLGenerics, @Projects.NexusScript, @Projects.SQLite,
        @Projects.JSON, @Projects.FCLJSON, @Projects.JSONRPC, @Projects.Core,
        @Projects.Mustache, @Projects.Serialization, @Projects.DelimitedText
    ];
    NXPC Application (Tools.Defaults.Pascal) {
        SourceFiles: ["InspectCatalog.lpr"];
        OutputPath: "build";
    }
}

Build and run on Windows:

nxbuild /action=build /project=InspectCatalog.nxproject /targetos=win64 /targetcpu=x86_64
./build/InspectCatalog.exe Regional.nxscript US
./build/InspectCatalog.exe Regional.nxscript EU

On Linux select linux and run ./build/InspectCatalog. US prints Pocket notebook, USD, 450; EU prints Pocket notebook, EUR, 500. No language changes or invented CLI switches are needed to supply these selections.

Ownership and lifetime

The compilation session owns its compilers and loaded dependency context. Keep it alive while validating compiled documents or using their borrowed views. The session copies the target selection supplied to its constructor.

Live emission creates an independently owned graph. The example deliberately frees the session before navigating Live. Free the Live document when finished; its definitions, properties, array entries, and navigation links are borrowed. Do not free those nodes individually.

Repeated references to a definition share its Live target. Value.Reference describes the immediate reference; AsDefinition follows aliases to the effective definition. AsText reads effective text and evaluates external expressions when necessary. Calling an accessor for the wrong value kind raises an error. Items retains array order, and FindItem uses effective entry names.

Roots and Declarations describe public contributions. Definitions also contains private targets reachable through references. Do not expose every reachable dependency as public output by iterating the wrong collection.

External value handlers

For External.nxscript from External values, the host can supply Brand.Prefix through this class. This is a class fragment to add to an application using the Live API:

type
  TCatalogValues = class
  private
    FPrefix: string;
  public
    procedure ReadExternal(const APath: string;
      out AValue: string; out AHandled: Boolean);
    property Prefix: string read FPrefix write FPrefix;
  end;

procedure TCatalogValues.ReadExternal(const APath: string;
  out AValue: string; out AHandled: Boolean);
begin
  AHandled := APath = 'Brand.Prefix';
  if AHandled then
    AValue := FPrefix
  else
    AValue := '';
end;

After emitting that document, attach the event and read Heading:

lValues := TCatalogValues.Create;
try
  lValues.Prefix := 'Welcome to';
  lLive.OnReadExternal := lValues.ReadExternal;
  WriteLn(lLive.FindDefinition('Shop').FindProperty('Heading').Value.AsText);
finally
  lLive.OnReadExternal := nil;
  lValues.Free;
end;

Declare lValues: TCatalogValues in the calling routine. The result is Welcome to Corner Stationery. A later read can observe a changed Prefix. Detach events before freeing their receiver; the Live document does not own it. Use AHandled = True for an intentionally empty value.

Desired external results

EvaluateExternalAssignments returns a caller-owned result list containing paths, evaluated text, and source ranges. It lets the application inspect a proposed change without applying it.

PublishExternalAssignments first evaluates the complete batch and then calls OnExternalResult for each result. No callbacks occur if evaluation fails. The callback has (const APath, AValue: string; out AHandled: Boolean). Missing/unhandled results are errors. Physical changes and transaction policy belong to the host; successful callbacks are not automatically rolled back if a later callback fails.

Metadata, emitters, and diagnostics

Nodes retain source ranges; properties retain contributor ranges; reference uses retain their own ranges. These let an editor or diagnostic explain where an effective value came from without creating another parser or resolver.

Use session diagnostics for loading/compilation failures and validator diagnostics for dialect failures. Preserve code, message, primary range, and related ranges when presenting them to users.

TNexusScriptJSONEmitter.AddLiveDocument renders a Live snapshot with its external read handlers. TNexusScriptEmitterFactory.CreateEmitter obtains a registered stream emitter; built-in registrations are json and sqlite. An application must link the corresponding implementation unit to register it. Live itself has no WriteArtifact stream format.

The public APIs live in obNexusScriptSession, obNexusScriptLive, and obNexusScriptValidator.

Validation and emitted-model ownership

Dialect normalization and document validation are separate operations. TNexusScriptLanguageDefinition is read-only after normalization. Normalization diagnostics use NSV1xxx; document checks use NSV2xxx/NSV3xxx. The validator consumes the compiled document; it does not redo reference resolution or modify the compiler's model.

External-value checks which cannot run at compile time are retained for Live access. Revalidation replaces the validating document's checks without discarding checks owned by imported documents. External namespace identities are case-sensitive.

Whole-array aliases share entries in the compiled graph. A Live emission owns its own projection; separate emissions can be released independently. Borrowed reference targets and contributors are not newly owned child definitions. Use the real node/reference kinds when inspecting Value.Reference.Target; do not infer identity from a rendered string or duplicate resolution in a consumer.