tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

reference.py (10368B)


      1 #!/usr/bin/env python3
      2 """Build static agent references and picker metadata from published interfaces."""
      3 import argparse
      4 import hashlib
      5 import json
      6 from pathlib import Path
      7 import re
      8 import sys
      9 
     10 
     11 # These paths belong to immutable releases predating canonical IO naming.
     12 # Current implementation context is published only when its source exists.
     13 HISTORICAL_SOURCE_PATHS = {
     14     'lib/io-v2.tri', 'lib/io-v2/artifacts.tri', 'lib/io-v2/http-transport.tri',
     15     'lib/io-v2/objects.tri', 'lib/io-v2/storage.tri', 'lib/io-v2/text.tri',
     16     'lib/source/io-v2.tri', 'lib/arboricx/streaming-v2.tri', 'lib/packages/io-v2.tri',
     17 }
     18 
     19 
     20 def source_reference(root, title):
     21     source = (root / title).resolve()
     22     if not source.is_relative_to(root / 'lib'):
     23         raise ValueError('library source escapes the source root')
     24     if not source.is_file():
     25         if title not in HISTORICAL_SOURCE_PATHS:
     26             raise ValueError('missing current library source: ' + title)
     27         return None, f'Historical source path: `{title}`. Its source is retained in Git history. '
     28     relative = source.relative_to(root).as_posix()
     29     return source, (f'[Repository source](../source/{relative}.txt) from this site build. '
     30         f'Source SHA-256: `{hashlib.sha256(source.read_bytes()).hexdigest()}`. ')
     31 
     32 
     33 def example_instructions(example):
     34     name = example['expression']
     35     if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name):
     36         raise ValueError('Playground examples require a named source definition: ' + name)
     37     interpretation = dict(number='Natural number', string='Text')[example['output']]
     38     if example['mode'] == 'pure':
     39         return f'Click the underlined `{name}` definition. Its card displays the result; choose **{interpretation}** to request that interpretation explicitly.'
     40     return (f'Click the underlined `{name}` definition to open its IO card. '
     41             f'In **IO details**, set **Returned value (next run)** to **{interpretation}**, '
     42             'then click **Run IO**. The card shows terminal output and the returned value.')
     43 
     44 
     45 def generate(root, output):
     46     site = root / 'ext/demo-site'
     47     sys.path.insert(0, str(root / 'test'))
     48     from bundle_dag import read
     49     reference = output / 'reference'
     50     reference.mkdir(parents=True, exist_ok=True)
     51     def write(name, text):
     52         path = reference / name
     53         path.parent.mkdir(parents=True, exist_ok=True)
     54         path.write_text(text)
     55     write('LICENSE.txt', (root / 'LICENSE').read_text())
     56     notes = json.loads((site / 'reference-input/libraries.json').read_text())
     57     releases = json.loads((site / 'packages.json').read_text())['releases']
     58     rows = []
     59     # Keep all releases. Browser-side SemVer ordering chooses the initial view.
     60     for release in releases:
     61         if not release['name'].startswith('tricu-lib-'):
     62             continue
     63         short = release['name'].removeprefix('tricu-lib-')
     64         bundle = site / 'bundles' / (release['hash'] + '.arboricx')
     65         assert hashlib.sha256(bundle.read_bytes()).hexdigest() == release['hash']
     66         names = list(read(bundle)[3])
     67         note = notes.get(short + '@' + release['version'], notes.get(short, {}))
     68         note = note if release['version'] == note.get('version', '0.1.0') else {}
     69         alias = note.get('alias', ''.join(word.capitalize() for word in short.split('-')))
     70         description = note.get('description', f'{short.replace("-", " ").capitalize()} library; inspect its complete interface and source reference below.')
     71         filename = release['name'] + '-' + release['version'] + '.md'
     72         rows.append(dict(release, title=short, description=description, alias=alias,
     73                          names=names, reference='libraries/' + filename, **({'usage': note['usage']} if 'usage' in note else {})))
     74         source, source_note = source_reference(root, release['title'])
     75         # Export membership comes from the immutable published bundle; current
     76         # checkout source is separately labelled implementation context.
     77         if source is not None:
     78             write('source/' + source.relative_to(root).as_posix() + '.txt', source.read_text())
     79         write('libraries/' + filename,
     80               f'# {short} · {release["version"]}\n\n{description}\n\n'
     81               f'```tricu\n!import {release["manifest"]} as {alias}\n```\n\n'
     82               f'Package: `{release["name"]}`\n\nManifest: `{release["manifest"]}`\n\n'
     83               f'Bundle SHA-256: `{release["hash"]}`\n\n'
     84               f'[Download bundle](https://arbori.cx/_arboricx/bundle/hash/{release["hash"]})\n\n'
     85               '## Calling conventions\n\n' + note.get('usage', 'Consult the source and protocol guides for argument order, encoding and effects.') + '\n\n'
     86               '[Complete examples](../examples.md) · [Language features](../language-features.md) · [Contracts](../contracts.md) · [Host ABI](../host-abi.md) · [Parser protocol](../parser.md)\n\n'
     87               f'## Complete published interface ({len(names)} exports)\n\n```text\n' + '\n'.join(names) + '\n```\n\n'
     88               '## Source reference\n\n' + source_note +
     89               'The export list above describes the selected published bundle; repository source is provided for implementation context. '
     90               'Workspace import names in source resolve through the [library directory](../libraries.md) when adapting code for the playground.\n')
     91     write('libraries.md', '# Published tricu libraries\n\nChoose a release to get its complete exports, manifest import and calling conventions. '
     92           'The playground picker initially shows the newest version of each library; all releases are listed here. '
     93           '[Start with the playground guide](getting-started.md).\n\n' + '\n'.join(
     94               f'- [{r["title"]} {r["version"]}]({r["reference"]}) — {r["description"]}' for r in rows) + '\n')
     95     # Publish readable source and language/protocol contracts at ordinary HTTP URLs.
     96     for path in (root / 'lib').rglob('*.tri'):
     97         write('source/' + path.relative_to(root).as_posix() + '.txt', path.read_text())
     98     for path in (root / 'docs').glob('*.md'):
     99         text = path.read_text().replace('.md.html', '.md')
    100         text = re.sub(r'\]\(\.\./lib/([^)]*?\.tri)(#[^)]*)?\)', lambda m: '](source/lib/' + m[1] + '.txt' + (m[2] or '') + ')', text)
    101         text = re.sub(r'\]\(\.\./([^)]*)\)', r'](https://git.eversole.co/James/tricu/file/\1)', text)
    102         write(path.name, text)
    103     write('getting-started.md', (site / 'reference-input/getting-started.md').read_text())
    104     # Examples pin explicit release versions; adding a publication cannot silently
    105     # change their program meaning or independent expected results.
    106     pins = {r['name'].removeprefix('tricu-lib-'): r['manifest'] for r in rows if r['version'] == '0.11.0'}
    107     pins.update({r['name'].removeprefix('tricu-lib-') + '@' + r['version']: r['manifest'] for r in rows})
    108     examples = json.loads((site / 'reference-input/examples.json').read_text())
    109     for example in examples:
    110         example['source'] = re.sub(r'\{\{([^}]+)\}\}', lambda m: pins[m[1]], example['source'])
    111     for row in rows:
    112         example = next((e for e in examples if row['manifest'] in e['source']), None)
    113         if example:
    114             row['example'] = example['source'] + '\n' + example_instructions(example) + '\nExpected result: ' + example['expected']
    115     write('libraries.json', json.dumps(dict(schema=1, releases=rows), indent=2) + '\n')
    116     write('examples.json', json.dumps(examples, indent=2) + '\n')
    117     write('examples.md', '# Playground examples\n\nReplace Source with a complete module below and wait for **Compiled**. '
    118           'Click the underlined definition name to open its card. Pure examples display their result there; '
    119           'IO examples require an explicit **Run IO** click. Write a new source definition to try another application. '
    120           'The interpretation selector belongs to the card; IO returned-value interpretation is in **IO details**.\n\n' + '\n'.join(
    121         f'## {e["title"]}\n\n```tricu\n{e["source"]}```\n\n' + example_instructions(e) + '\n\n'
    122         f'Expected result: `{e["expected"]}`.\n\n' + (f'Terminal output:\n\n```text\n{e["terminal"]}```\n\n' if e['terminal'] else '') for e in examples))
    123     (output / 'llms.txt').write_text('''# Arboricx and tricu
    124 
    125 > tricu is a programming language built on Tree Calculus. Arboricx carries compiled programs between hosts. This site is the language homepage and browser playground.
    126 
    127 ## Write programs
    128 
    129 - [Playground guide](https://arbori.cx/reference/getting-started.md): edit source, click definition names, inspect cards, run IO, import libraries and handle diagnostics.
    130 - [Library directory](https://arbori.cx/reference/libraries.md): complete published interfaces, versions, exact manifest imports and calling conventions.
    131 - [Machine-readable library index](https://arbori.cx/reference/libraries.json): the same metadata used by Explore libraries.
    132 - [Language features](https://arbori.cx/reference/language-features.md): patterns, recursion, local bindings, sequencing and library protocols.
    133 - [Language contract](https://arbori.cx/reference/language.md): source syntax and semantics.
    134 - [Worked examples](https://arbori.cx/reference/examples.md): complete source, definition-card instructions and expected results.
    135 - [Modules and packages](https://arbori.cx/reference/modules.md): source modules and portable bundle identities.
    136 
    137 ## Protocols and boundaries
    138 
    139 - [Contracts](https://arbori.cx/reference/contracts.md): input validation and Result protocols.
    140 - [Parser combinators](https://arbori.cx/reference/parser.md): replies, progress and rollback.
    141 - [Host ABI](https://arbori.cx/reference/host-abi.md): IO actions and host capabilities.
    142 - [Native CLI](https://arbori.cx/reference/build-and-run.md): run and package programs outside the browser.
    143 - [License](https://arbori.cx/reference/LICENSE.txt): source and documentation licensing.
    144 
    145 Start with the playground guide, then fetch the relevant library release pages. Static references are available without executing WebAssembly.
    146 ''')
    147 
    148 
    149 if __name__ == '__main__':
    150     parser = argparse.ArgumentParser(description=__doc__)
    151     parser.add_argument('--root', type=Path, required=True)
    152     parser.add_argument('--output', type=Path, required=True)
    153     args = parser.parse_args()
    154     generate(args.root.resolve(), args.output.resolve())