Files
2026-10-18 00:06:55 +02:00

605 lines
26 KiB
PowerShell

<#
.SYNOPSIS
Build script for the more_math ANTLR grammar and its generated symbol tables.
.DESCRIPTION
1. Compiles more_math/Parser/MathExpr.g4 into more_math/Parser/grammer
(the parser antlr_router.py loads for current antlr4-python3-runtime
versions), using whatever ANTLR toolchain is available on this
machine (an 'antlr4' launcher on PATH, or 'java -jar <jar>').
2. Parses MathExpr.g4 itself (keywords, constants, function tokens and
their doc-comment signatures/descriptions) and regenerates:
- more_math/Parser/inbuilt_symbols.py
- web/inbuilt_symbols.js
so that the Python interpreter and the web autocomplete UI both read
an up-to-date, identical view of the language's built-in symbols.
.PARAMETER SkipAntlr
Skip step 1 (grammar compilation) and only regenerate the symbol tables.
.PARAMETER AntlrJar
Path to an antlr-*-complete.jar to use for step 1. Defaults to
$env:ANTLR_JAR, then tools\antlr.jar next to this script.
.PARAMETER AntlrLegacyJar
Optional path to an ANTLR 4.9.x complete jar to use for regenerating
more_math/Parser/legacy (the antlr4-python3-runtime==4.9.x compatibility
path used by antlr_router.py). Defaults to $env:ANTLR_LEGACY_JAR. When
not given, the 'antlr4' launcher (antlr4-tools) is tried instead, pinned
to -AntlrLegacyVersion, same as the main grammar target.
.PARAMETER AntlrVersion
ANTLR release to use when the 'antlr4' launcher comes from the
antlr4-tools pip package (it otherwise queries Maven Central for the
latest release on every run). Defaults to $env:ANTLR4_TOOLS_ANTLR_VERSION,
then '4.13.2' to match the version more_math/Parser/grammer was last
generated with. Ignored when using a plain jar/java invocation.
.PARAMETER AntlrLegacyVersion
Same as -AntlrVersion, but for the more_math/Parser/legacy (ANTLR 4.9.x)
target. Defaults to $env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION, then '4.9.3'
to match the version more_math/Parser/legacy was last generated with.
#>
[CmdletBinding()]
param(
[switch]$SkipAntlr,
[string]$AntlrJar = $env:ANTLR_JAR,
[string]$AntlrLegacyJar = $env:ANTLR_LEGACY_JAR,
[string]$AntlrVersion = $(if ($env:ANTLR4_TOOLS_ANTLR_VERSION) { $env:ANTLR4_TOOLS_ANTLR_VERSION } else { '4.13.2' }),
[string]$AntlrLegacyVersion = $(if ($env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION) { $env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION } else { '4.9.3' })
)
$ErrorActionPreference = 'Stop'
# This script lives at more_math/Parser/compile.ps1, two levels under the
# node root (more_math/Parser -> more_math -> node root).
$ParserDir = $PSScriptRoot
$NodeRoot = Split-Path (Split-Path $ParserDir -Parent) -Parent
$GrammarFile = Join-Path $ParserDir 'MathExpr.g4'
$GrammarOutDir = Join-Path $ParserDir 'grammer'
$LegacyOutDir = Join-Path $ParserDir 'legacy'
$WebDir = Join-Path $NodeRoot 'web'
$PySymbolsPath = Join-Path $ParserDir 'inbuilt_symbols.py'
$JsSymbolsPath = Join-Path $WebDir 'inbuilt_symbols.js'
if (-not (Test-Path $GrammarFile)) {
throw "Grammar file not found: $GrammarFile"
}
# ---------------------------------------------------------------------------
# Step 1: ANTLR compilation
# ---------------------------------------------------------------------------
function Invoke-AntlrCompile {
# Runs with cwd = $ParserDir and passes ANTLR just the grammar's leaf
# filename, so the "# Generated from MathExpr.g4 by ANTLR ..." header it
# writes into the output files stays a plain relative name instead of
# embedding this machine's local absolute path.
param(
[Parameter(Mandatory)][string]$Grammar,
[Parameter(Mandatory)][string]$OutDir,
[string]$Jar,
[string]$Version = $AntlrVersion
)
$grammarLeaf = Split-Path -Leaf $Grammar
$antlrCmd = Get-Command antlr4 -ErrorAction SilentlyContinue
if ($antlrCmd) {
# antlr4-tools (the pip-installed launcher) otherwise hits Maven
# Central on every invocation to resolve "latest"; pin it so the
# build is reproducible and doesn't need that network call.
$previousVersionEnv = $env:ANTLR4_TOOLS_ANTLR_VERSION
$env:ANTLR4_TOOLS_ANTLR_VERSION = $Version
Push-Location $ParserDir
try {
Write-Host "[antlr] antlr4 (v$Version) -Dlanguage=Python3 -visitor -o `"$OutDir`" `"$grammarLeaf`""
& $antlrCmd.Source -Dlanguage=Python3 -visitor -o $OutDir $grammarLeaf
if ($LASTEXITCODE -ne 0) { throw "antlr4 failed with exit code $LASTEXITCODE" }
} finally {
Pop-Location
$env:ANTLR4_TOOLS_ANTLR_VERSION = $previousVersionEnv
}
return $true
}
$javaCmd = Get-Command java -ErrorAction SilentlyContinue
if (-not $javaCmd) {
return $false
}
if (-not $Jar) {
$default = Join-Path $NodeRoot 'tools\antlr.jar'
if (Test-Path $default) { $Jar = $default }
}
if (-not $Jar -or -not (Test-Path $Jar)) {
return $false
}
Push-Location $ParserDir
try {
Write-Host "[antlr] java -jar `"$Jar`" -Dlanguage=Python3 -visitor -o `"$OutDir`" `"$grammarLeaf`""
& $javaCmd.Source -jar $Jar -Dlanguage=Python3 -visitor -o $OutDir $grammarLeaf
if ($LASTEXITCODE -ne 0) { throw "ANTLR jar invocation failed with exit code $LASTEXITCODE" }
} finally {
Pop-Location
}
return $true
}
if ($SkipAntlr) {
Write-Host "[antlr] Skipping grammar compilation (-SkipAntlr)."
} else {
$ok = Invoke-AntlrCompile -Grammar $GrammarFile -OutDir $GrammarOutDir -Jar $AntlrJar
if (-not $ok) {
Write-Warning ("ANTLR toolchain not found (no 'antlr4' on PATH and no usable jar). " +
"Skipping grammar compilation; '$GrammarOutDir' was left untouched. " +
"Install Java + antlr4-tools ('pip install antlr4-tools'), or set `$env:ANTLR_JAR " +
"to an antlr-*-complete.jar, then re-run.")
}
if ($AntlrLegacyJar -and -not (Test-Path $AntlrLegacyJar)) {
Write-Warning "AntlrLegacyJar '$AntlrLegacyJar' does not exist; falling back to the antlr4 launcher for the legacy target."
$AntlrLegacyJar = $null
}
$legacyOk = Invoke-AntlrCompile -Grammar $GrammarFile -OutDir $LegacyOutDir -Jar $AntlrLegacyJar -Version $AntlrLegacyVersion
if (-not $legacyOk) {
Write-Warning ("ANTLR toolchain not found for the legacy (ANTLR 4.9.x) target. " +
"Skipping legacy grammar compilation; '$LegacyOutDir' was left untouched. " +
"Install antlr4-tools, or set `$env:ANTLR_LEGACY_JAR to an antlr-4.9.x-complete.jar, then re-run.")
}
}
# ---------------------------------------------------------------------------
# Step 2: parse MathExpr.g4 for its built-in symbol table
# ---------------------------------------------------------------------------
function Get-GrammarSections {
param([Parameter(Mandatory)][string]$Text)
$funcStart = $Text.IndexOf('func0:')
$lexerMarker = '// LEXER RULES'
$lexerStart = $Text.IndexOf($lexerMarker)
if ($funcStart -lt 0 -or $lexerStart -lt 0 -or $lexerStart -le $funcStart) {
throw "Could not locate expected grammar sections ('func0:' / '$lexerMarker') - has MathExpr.g4's layout changed?"
}
[pscustomobject]@{
FuncSection = $Text.Substring($funcStart, $lexerStart - $funcStart)
LexerSection = $Text.Substring($lexerStart)
}
}
function Get-LexerTokens {
# Returns an ordered map of TOKEN_NAME -> raw string-literal alternatives,
# for every lexer rule that defines at least one word-like literal
# (i.e. skips punctuation/operator tokens and regex-only tokens like
# NUMBER/STRING/VARIABLE/WS/comments, which have no literal alternatives).
param([Parameter(Mandatory)][string]$LexerSection)
$tokenRegex = [regex]'(?ms)^[ \t]*([A-Z][A-Z0-9_]*)[ \t]*:[ \t]*(.*?);'
$literalRegex = [regex]"'([a-zA-Z_][a-zA-Z0-9_]*)'"
$tokens = [ordered]@{}
foreach ($m in $tokenRegex.Matches($LexerSection)) {
$name = $m.Groups[1].Value
$body = $m.Groups[2].Value
$literals = @($literalRegex.Matches($body) | ForEach-Object { $_.Groups[1].Value })
if ($literals.Count -gt 0) {
$tokens[$name] = $literals
}
}
return $tokens
}
function Get-FunctionTokenNames {
# A token counts as a "function" token when it is used as `TOKEN LPAREN`
# inside the func0..funcNoise rules (the atom productions for calls).
# This deliberately excludes structural keywords like IF/WHILE/FOR, which
# are also followed by LPAREN but in ifStmt/whileStmt/forStmt, outside
# this section.
param([Parameter(Mandatory)][string]$FuncSection)
$names = New-Object System.Collections.Generic.HashSet[string]
foreach ($m in [regex]::Matches($FuncSection, '\b([A-Z][A-Z0-9_]*)\s+LPAREN\b')) {
[void]$names.Add($m.Groups[1].Value)
}
return $names
}
function Get-UniqueWords {
# De-duplicates literal alternatives, preserving first-seen order. Case
# variants (e.g. 'pi'/'PI', 'none'/'None'/'null'/'NULL') are all kept,
# since the lexer is case-sensitive and the web highlighter must
# recognise every spelling the grammar accepts.
param([string[]]$Words)
$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
$result = New-Object System.Collections.Generic.List[string]
foreach ($w in $Words) {
if ($seen.Add($w)) { [void]$result.Add($w) }
}
return $result
}
function Get-FunctionDocEntries {
# Pairs each `/** ... */` doc comment in the func section with the
# lexer token of the call it documents (the `TOKEN LPAREN` that follows
# the comment, skipping over an optional leading `|` alternation).
param([Parameter(Mandatory)][string]$FuncSection)
$pattern = [regex]'(?s)/\*\*(?<doc>.*?)\*/\s*\|?\s*(?<token>[A-Z][A-Z0-9_]*)\s+LPAREN'
$entries = New-Object System.Collections.Generic.List[object]
foreach ($m in $pattern.Matches($FuncSection)) {
$lines = @($m.Groups['doc'].Value -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
if ($lines.Count -eq 0) { continue }
$entries.Add([pscustomobject]@{
Token = $m.Groups['token'].Value
FirstLine = $lines[0]
Details = @($lines | Select-Object -Skip 1)
})
}
return $entries
}
function Get-ProductionArity {
# Computes argument-count bounds from a call production's argument list,
# e.g. "expr COMMA expr (COMMA expr)?" -> 2..3. A `*`/`+` group makes the
# call variadic. This is what the parser actually accepts.
param([string]$ArgsText)
$stack = New-Object System.Collections.Generic.List[hashtable]
$stack.Add(@{ Min = 0; Max = 0; Variadic = $false })
foreach ($m in [regex]::Matches($ArgsText, '\(|\)[?*+]?|\b(?:expr|indexExpr)\b')) {
$t = $m.Value
if ($t -eq '(') {
$stack.Add(@{ Min = 0; Max = 0; Variadic = $false })
continue
}
$top = $stack[$stack.Count - 1]
if ($t.StartsWith(')')) {
$stack.RemoveAt($stack.Count - 1)
$parent = $stack[$stack.Count - 1]
$q = $t.Substring(1)
if ($q -eq '' -or $q -eq '+') { $parent.Min += $top.Min }
$parent.Max += $top.Max
$parent.Variadic = $parent.Variadic -or $top.Variadic -or $q -eq '*' -or $q -eq '+'
continue
}
$top.Min += 1
$top.Max += 1
}
$root = $stack[0]
[pscustomobject]@{ MinArgs = $root.Min; MaxArgs = $(if ($root.Variadic) { $null } else { $root.Max }) }
}
function Get-ProductionArities {
# Maps each function token to the arity of its `TOKEN LPAREN ... RPAREN` production.
param([Parameter(Mandatory)][string]$FuncSection)
$code = [regex]::Replace($FuncSection, '(?s)/\*.*?\*/', '')
$arities = @{}
foreach ($m in [regex]::Matches($code, '\b([A-Z][A-Z0-9_]*)\s+LPAREN(.*?)\bRPAREN\b')) {
$arities[$m.Groups[1].Value] = Get-ProductionArity -ArgsText $m.Groups[2].Value
}
return $arities
}
function Split-ArgSpec {
# Parses a documented arg list like "x, [dims]", "r, [g, b]" or
# "tensor, [kernel_sizes...], kernel" into min/max argument counts and
# display names (kept as written, brackets included). Args inside [...]
# are optional; an arg containing "..." makes the call variadic.
param([string]$ArgsText)
$min = 0
$max = 0
$variadic = $false
$depth = 0
$display = New-Object System.Collections.Generic.List[string]
foreach ($raw in ($ArgsText -split ',')) {
$p = $raw.Trim()
if ($p -eq '') { continue }
$isOptional = $depth -gt 0 -or $p.StartsWith('[')
$depth += ([regex]::Matches($p, '\[')).Count - ([regex]::Matches($p, '\]')).Count
[void]$display.Add($p)
if ($p.Contains('...')) { $variadic = $true; continue }
$max++
if (-not $isOptional) { $min++ }
}
[pscustomobject]@{ Min = $min; Max = $max; Variadic = $variadic; Display = $display.ToArray() }
}
function ConvertTo-FunctionSignature {
# Parses a doc-comment first line such as:
# "sum(x, [dims]) - computes the sum of elements of x, ..."
# "rgb_to_hsv(rgb, [degrees]) / rgb_to_hsv(r, g, b, [degrees]) - converts ..."
# into the signature text, the display arg names of every call form, the
# arg-count bounds covering all forms, and the trailing description text.
param([Parameter(Mandatory)][string]$Line)
$formPattern = [regex]'^\s*/?\s*[a-zA-Z_][a-zA-Z0-9_]*\((?<args>.*?)\)'
$rest = $Line
$forms = New-Object System.Collections.Generic.List[object]
$min = $null
$max = 0
$variadic = $false
while (($m = $formPattern.Match($rest)).Success -and ($forms.Count -eq 0 -or $m.Value.TrimStart().StartsWith('/'))) {
$spec = Split-ArgSpec -ArgsText $m.Groups['args'].Value
$forms.Add($spec.Display)
if ($null -eq $min -or $spec.Min -lt $min) { $min = $spec.Min }
if ($spec.Max -gt $max) { $max = $spec.Max }
if ($spec.Variadic) { $variadic = $true }
$rest = $rest.Substring($m.Length)
}
if ($forms.Count -eq 0) { return $null }
$signature = $Line.Substring(0, $Line.Length - $rest.Length).Trim()
$descMatch = [regex]::Match($rest, '^\s*-\s*(?<desc>.*)$')
$desc = if ($descMatch.Success) { $descMatch.Groups['desc'].Value.Trim() } else { $rest.Trim() }
[pscustomobject]@{
Signature = $signature
MinArgs = $min
MaxArgs = if ($variadic) { $null } else { $max }
Forms = $forms.ToArray()
Description = $desc
}
}
Write-Host "[symbols] Parsing $GrammarFile"
$grammarText = Get-Content -Raw -LiteralPath $GrammarFile
$sections = Get-GrammarSections -Text $grammarText
$allTokens = Get-LexerTokens -LexerSection $sections.LexerSection
$functionTokenNames = Get-FunctionTokenNames -FuncSection $sections.FuncSection
$constantWords = @()
$keywordWords = New-Object System.Collections.Generic.List[string]
$functionWords = New-Object System.Collections.Generic.List[string]
$functionTokenToWords = [ordered]@{}
$tokenNames = @($allTokens.psbase.Keys)
foreach ($name in $tokenNames) {
$words = Get-UniqueWords -Words $allTokens[$name]
if ($name -eq 'CONSTANT') {
$constantWords = $words
continue
}
if ($functionTokenNames.Contains($name)) {
$functionTokenToWords[$name] = $words
foreach ($w in $words) { [void]$functionWords.Add($w) }
} else {
foreach ($w in $words) { [void]$keywordWords.Add($w) }
}
}
$KEYWORDS = @($keywordWords | Sort-Object -Unique -CaseSensitive)
$CONSTANTS = @($constantWords | Sort-Object -Unique -CaseSensitive)
$FUNCTIONS = @($functionWords | Sort-Object -Unique -CaseSensitive)
$docEntries = Get-FunctionDocEntries -FuncSection $sections.FuncSection
$sigByToken = @{}
$detailsByToken = @{}
$paramsByToken = @{}
$paramTotal = 0
$paramDescribed = 0
foreach ($e in $docEntries) {
$sig = ConvertTo-FunctionSignature -Line $e.FirstLine
if (-not $sig) { continue }
$sigByToken[$e.Token] = $sig
# "- name: description" lines document one parameter; other lines are general details.
$argNames = @($sig.Forms | ForEach-Object { $_ } | ForEach-Object { $_.Trim('[', ']', ' ').Replace('...', '') } | Where-Object { $_ })
$params = [ordered]@{}
$details = New-Object System.Collections.Generic.List[string]
foreach ($line in $e.Details) {
$pm = [regex]::Match($line, '^-\s*(?<name>[a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(?<desc>.+)$')
if (-not $pm.Success) {
[void]$details.Add($line)
continue
}
$paramName = $pm.Groups['name'].Value
if ($argNames -notcontains $paramName) {
Write-Warning "Doc comment for $($e.Token) describes parameter '$paramName', which is not in '$($sig.Signature)'."
}
$params[$paramName] = $pm.Groups['desc'].Value.Trim()
}
$paramsByToken[$e.Token] = $params
$detailsByToken[$e.Token] = $details -join "`n"
$paramTotal += @($argNames | Select-Object -Unique).Count
$paramDescribed += @($argNames | Select-Object -Unique | Where-Object { $params.Contains($_) }).Count
}
Write-Host "[symbols] $paramDescribed of $paramTotal parameters have a description."
$functionTokens = @($functionTokenToWords.psbase.Keys)
$undocumented = @($functionTokens | Where-Object { -not $sigByToken.ContainsKey($_) })
if ($undocumented.Count -gt 0) {
Write-Warning "No parsable doc comment found for function token(s): $($undocumented -join ', '). They will get placeholder metadata."
}
# The grammar production bounds what parses; the doc comment may be stricter
# (e.g. conv accepts any count in the grammar but 3..5 in the visitor). Use the
# narrower range, and report docs that allow calls the grammar rejects.
$arityByToken = Get-ProductionArities -FuncSection $sections.FuncSection
function Format-Arity { param($Min, $Max) "$Min..$(if ($null -eq $Max) { 'n' } else { $Max })" }
function Test-ArityWithin {
param($Inner, $Outer)
$Inner.MinArgs -ge $Outer.MinArgs -and ($null -eq $Outer.MaxArgs -or ($null -ne $Inner.MaxArgs -and $Inner.MaxArgs -le $Outer.MaxArgs))
}
$FUNCTION_META = [ordered]@{}
foreach ($token in $functionTokens) {
$sig = $sigByToken[$token]
$arity = $arityByToken[$token]
if ($sig -and $arity) {
if (Test-ArityWithin $sig $arity) {
$arity = $sig
} else {
Write-Warning "Doc comment for $token ('$($sig.Signature)') documents $(Format-Arity $sig.MinArgs $sig.MaxArgs) args, grammar accepts $(Format-Arity $arity.MinArgs $arity.MaxArgs)."
}
}
# Assigned outside an if-expression so PowerShell doesn't unroll the nested arrays.
$forms = [object[]]@(, [string[]]@())
if ($sig) { $forms = $sig.Forms }
$words = $functionTokenToWords[$token]
foreach ($alias in $words) {
# Show the signature under the spelling being completed (e.g. conv vs convolution).
$signature = if ($sig) { [regex]::Replace($sig.Signature, '(^|/\s*)[a-zA-Z_][a-zA-Z0-9_]*(?=\()', "`${1}$alias") } else { "$alias()" }
$FUNCTION_META[$alias] = [pscustomobject]@{
MinArgs = if ($arity) { $arity.MinArgs } else { 0 }
MaxArgs = if ($arity) { $arity.MaxArgs } else { $null }
Signature = $signature
Forms = $forms
Description = if ($sig) { $sig.Description } else { '' }
Details = if ($sig) { $detailsByToken[$token] } else { '' }
Params = if ($sig) { $paramsByToken[$token] } else { [ordered]@{} }
Aliases = @($words | Where-Object { $_ -ne $alias })
}
}
}
# ---------------------------------------------------------------------------
# Step 3: emit the generated files
# ---------------------------------------------------------------------------
function Set-Utf8NoBom {
# Windows PowerShell 5.1's Set-Content has no utf8NoBOM encoding, so write
# the file directly via .NET to avoid a BOM in the generated .py/.js files.
param([string]$Path, [string]$Content)
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
}
function ConvertTo-JsString {
param([string]$Value)
$escaped = $Value.Replace('\', '\\').Replace('"', '\"').Replace("`n", '\n')
return '"' + $escaped + '"'
}
function ConvertTo-PyString {
param([string]$Value)
$escaped = $Value.Replace('\', '\\').Replace("'", "\'").Replace("`n", '\n')
return "'" + $escaped + "'"
}
function ConvertTo-JsArray {
param([string[]]$Values)
# An empty array arrives as $null after passing through an if-expression.
if (-not $Values) { return '[]' }
return '[' + (@($Values | ForEach-Object { ConvertTo-JsString $_ }) -join ', ') + ']'
}
function ConvertTo-PyList {
param([string[]]$Values)
# An empty array arrives as $null after passing through an if-expression.
if (-not $Values) { return '[]' }
return '[' + (@($Values | ForEach-Object { ConvertTo-PyString $_ }) -join ', ') + ']'
}
function ConvertTo-JsForms {
param([object[]]$Forms)
$parts = foreach ($form in $Forms) { ConvertTo-JsArray $form }
return '[' + (@($parts) -join ', ') + ']'
}
function ConvertTo-PyForms {
param([object[]]$Forms)
$parts = foreach ($form in $Forms) { ConvertTo-PyList $form }
return '[' + (@($parts) -join ', ') + ']'
}
function ConvertTo-JsObject {
param([System.Collections.IDictionary]$Map)
$parts = foreach ($key in @($Map.Keys)) { "$(ConvertTo-JsString $key): $(ConvertTo-JsString $Map[$key])" }
return '{' + (@($parts) -join ', ') + '}'
}
function ConvertTo-PyDict {
param([System.Collections.IDictionary]$Map)
$parts = foreach ($key in @($Map.Keys)) { "$(ConvertTo-PyString $key): $(ConvertTo-PyString $Map[$key])" }
return '{' + (@($parts) -join ', ') + '}'
}
function Write-JsSymbols {
param(
[string]$Path,
[string[]]$Keywords,
[string[]]$Constants,
[string[]]$Functions,
[System.Collections.Specialized.OrderedDictionary]$FunctionMeta
)
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine('// Generated automatically by compile.ps1. Do not edit.')
[void]$sb.AppendLine()
[void]$sb.AppendLine('export const KEYWORDS = new Set([')
[void]$sb.AppendLine(($Keywords | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
[void]$sb.AppendLine(']);')
[void]$sb.AppendLine()
[void]$sb.AppendLine('export const CONSTANTS = new Set([')
[void]$sb.AppendLine(($Constants | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
[void]$sb.AppendLine(']);')
[void]$sb.AppendLine()
[void]$sb.AppendLine('export const FUNCTIONS = new Set([')
[void]$sb.AppendLine(($Functions | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
[void]$sb.AppendLine(']);')
[void]$sb.AppendLine()
[void]$sb.AppendLine('export const FUNCTION_META = {')
foreach ($name in @($FunctionMeta.psbase.Keys)) {
$meta = $FunctionMeta[$name]
$maxArgsJs = if ($null -eq $meta.MaxArgs) { 'null' } else { $meta.MaxArgs }
[void]$sb.AppendLine(" $(ConvertTo-JsString $name): { minArgs: $($meta.MinArgs), maxArgs: $maxArgsJs, signature: $(ConvertTo-JsString $meta.Signature), forms: $(ConvertTo-JsForms $meta.Forms), description: $(ConvertTo-JsString $meta.Description), details: $(ConvertTo-JsString $meta.Details), params: $(ConvertTo-JsObject $meta.Params), aliases: $(ConvertTo-JsArray $meta.Aliases) },")
}
[void]$sb.AppendLine('};')
Set-Utf8NoBom -Path $Path -Content $sb.ToString()
}
function Write-PySymbols {
param(
[string]$Path,
[string[]]$Keywords,
[string[]]$Constants,
[string[]]$Functions,
[System.Collections.Specialized.OrderedDictionary]$FunctionMeta
)
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine('# Generated automatically by compile.ps1. Do not edit.')
[void]$sb.AppendLine('INBUILT_KEYWORDS = {')
[void]$sb.AppendLine(($Keywords | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
[void]$sb.AppendLine('}')
[void]$sb.AppendLine('INBUILT_CONSTANTS = {')
[void]$sb.AppendLine(($Constants | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
[void]$sb.AppendLine('}')
[void]$sb.AppendLine('INBUILT_FUNCTIONS = {')
[void]$sb.AppendLine(($Functions | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
[void]$sb.AppendLine('}')
[void]$sb.AppendLine()
[void]$sb.AppendLine('INBUILT_FUNCTION_META = {')
foreach ($name in @($FunctionMeta.psbase.Keys)) {
$meta = $FunctionMeta[$name]
$maxArgsPy = if ($null -eq $meta.MaxArgs) { 'None' } else { $meta.MaxArgs }
[void]$sb.AppendLine(" $(ConvertTo-PyString $name): {'min_args': $($meta.MinArgs), 'max_args': $maxArgsPy, 'signature': $(ConvertTo-PyString $meta.Signature), 'forms': $(ConvertTo-PyForms $meta.Forms), 'description': $(ConvertTo-PyString $meta.Description), 'details': $(ConvertTo-PyString $meta.Details), 'params': $(ConvertTo-PyDict $meta.Params), 'aliases': $(ConvertTo-PyList $meta.Aliases)},")
}
[void]$sb.AppendLine('}')
Set-Utf8NoBom -Path $Path -Content $sb.ToString()
}
Write-Host "[symbols] Writing $PySymbolsPath"
Write-PySymbols -Path $PySymbolsPath -Keywords $KEYWORDS -Constants $CONSTANTS -Functions $FUNCTIONS -FunctionMeta $FUNCTION_META
Write-Host "[symbols] Writing $JsSymbolsPath"
Write-JsSymbols -Path $JsSymbolsPath -Keywords $KEYWORDS -Constants $CONSTANTS -Functions $FUNCTIONS -FunctionMeta $FUNCTION_META
Write-Host "[symbols] $($FUNCTIONS.Count) functions, $($KEYWORDS.Count) keywords, $($CONSTANTS.Count) constants."
Write-Host 'Done.'