Skip to content

Commit

Permalink
parser: ti manual: deal with sub-alternate syntax lines
Browse files Browse the repository at this point in the history
  • Loading branch information
adriweb committed Sep 22, 2024
1 parent 517d5f1 commit ad97c9f
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 133 deletions.
50 changes: 50 additions & 0 deletions TI-84_Plus_CE_catalog-tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -4777,6 +4777,28 @@
"2:Input"
]
},
{
"syntax": "Input [\"text\",variable]",
"arguments": [
[
"text",
"string",
true
],
[
"variable",
"",
true
]
],
"description": "Prompts for value to store to `variable`.",
"inEditorOnly": true,
"location": [
"【prgm】",
"I/O",
"2:Input"
]
},
{
"syntax": "Input [Strn,variable]",
"arguments": [
Expand Down Expand Up @@ -4976,6 +4998,34 @@
"DISTR",
"3:invNorm("
]
},
{
"syntax": "tail [catalog]: LEFT, CENTER, RIGHT",
"arguments": [
[
"tail catalog: LEFT",
"",
false
],
[
"CENTER",
"",
false
],
[
"RIGHT",
"",
false
]
],
"description": "Computes the inverse cumulative normal distribution function for a given area under the normal distribution curve specified by μ and s.. The optional argument tail can be LEFT (-∞,-a), CENTER [-a,a] or RIGHT (a, ∞) for Real a.\nThe tokens LEFT, CENTER and RIGHT can be found in [catalog].",
"inEditorOnly": false,
"location": [
"【2nd】",
"[distr]",
"DISTR",
"3:invNorm("
]
}
],
"localizations": {
Expand Down
268 changes: 135 additions & 133 deletions parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,162 +345,164 @@ for(let i = 0; i < 26; i++)
description = descRaw.map(el => el.textContent.trim()).join("\n").replace('``', '');
}

const syntaxLine = token.querySelector('tbody p.SyntaxLine');

let wholeSyntaxLine = (syntaxLine?.textContent ?? '').replace(',[,','[,').replace(/ +/g,' ').replace(/ +/g,' ').trim();
if (comment) {
if (/^\(.*\)$/.test(comment)) {
wholeSyntaxLine = wholeSyntaxLine.replace(new RegExp(escapeRegExp(comment) + '$'), ''); // remove potential comment at the end
comment = comment.substring(1, comment.length-1);
} else if (wholeSyntaxLine.endsWith(comment)) {
comment = undefined;
const syntaxLines = token.querySelectorAll('tbody p.SyntaxLine');
for (const syntaxLine of syntaxLines)
{
let wholeSyntaxLine = (syntaxLine?.textContent ?? '').replace(',[,','[,').replace(/ +/g,' ').replace(/ +/g,' ').trim();
if (comment) {
if (/^\(.*\)$/.test(comment)) {
wholeSyntaxLine = wholeSyntaxLine.replace(new RegExp(escapeRegExp(comment) + '$'), ''); // remove potential comment at the end
comment = comment.substring(1, comment.length-1);
} else if (wholeSyntaxLine.endsWith(comment)) {
comment = undefined;
}
}
}

let optionalSinceIdx = (wholeSyntaxLine.startsWith(`${name}[`) || wholeSyntaxLine.startsWith(`${name.trim()}[`)) ? 0 : null;
let _argIdx = 0;
let args = [...syntaxLine.childNodes]
.filter(el => el.nodeType !== 8 /*COMMENT_NODE*/ && el.textContent !== '→' && el.textContent.trim().length &&
(!el.classList?.contains('Function') || el.textContent === ',' || el.getAttribute("style")?.includes('font-weight: normal')))
.filter((el, idx) => {
const str = el.textContent.trim();
if (str === '►') { return false; }
const strOrFirstPart = str.split(' ')[0];
return (idx > 0) || (strOrFirstPart !== name.trim() && (strOrFirstPart + '(') !== name.trim());
})
.map((el,idx,arr) => {
const argStr = el.textContent.trim().replace(',[,','[,');
if (optionalSinceIdx === null && /^\[,?$/.test(argStr)) {
optionalSinceIdx = _argIdx
if (optionalSinceIdx === 0 && name.endsWith('(')) {
optionalSinceIdx = 1;
let optionalSinceIdx = (wholeSyntaxLine.startsWith(`${name}[`) || wholeSyntaxLine.startsWith(`${name.trim()}[`)) ? 0 : null;
let _argIdx = 0;
let args = [...syntaxLine.childNodes]
.filter(el => el.nodeType !== 8 /*COMMENT_NODE*/ && el.textContent !== '→' && el.textContent.trim().length &&
(!el.classList?.contains('Function') || el.textContent === ',' || el.getAttribute("style")?.includes('font-weight: normal')))
.filter((el, idx) => {
const str = el.textContent.trim();
if (str === '►') { return false; }
const strOrFirstPart = str.split(' ')[0];
return (idx > 0) || (strOrFirstPart !== name.trim() && (strOrFirstPart + '(') !== name.trim());
})
.map((el,idx,arr) => {
const argStr = el.textContent.trim().replace(',[,','[,');
if (optionalSinceIdx === null && /^\[,?$/.test(argStr)) {
optionalSinceIdx = _argIdx
if (optionalSinceIdx === 0 && name.endsWith('(')) {
optionalSinceIdx = 1;
}
// console.log(` optional args start now => ${optionalSinceIdx} ; ${wholeSyntaxLine}`)
}
// console.log(` optional args start now => ${optionalSinceIdx} ; ${wholeSyntaxLine}`)
}
if (el.textContent === ',' || el.classList?.contains('Variable')) {
_argIdx += 1 + (argStr.replace(/(^,|,$)/g, '').match(/,/g) || []).length
if (el.textContent === ',' || el.classList?.contains('Variable')) {
_argIdx += 1 + (argStr.replace(/(^,|,$)/g, '').match(/,/g) || []).length
}
return argStr;
})
.filter(el => el && el.textContent !== ',')
.map((el) => [ el.replace(/(^,*|[\[\]\(\)]|,*$)/g, ''), '', false ])
.map((el,idx,arr) => [ el[0], '', optionalSinceIdx !== null && idx >= optionalSinceIdx ])
.filter(n => n && n[0].length && !/^[\[\]\(\)]$/.test(n[0]));

{
let newArgs = [];
for (const [idx, [argName, argType, isOptional]] of Object.entries(args)) {
if (argName.includes(',')) {
// console.warn(` arg ${idx} wasn't split correctly: "${argName}". args was: ${JSON.stringify(args)}`);
for (const [idx2, arg] of Object.entries(argName.split(','))) {
newArgs.push([arg.replace(/^\(/, '').trim(), argType, isOptional])
}
return argStr;
})
.filter(el => el && el.textContent !== ',')
.map((el) => [ el.replace(/(^,*|[\[\]\(\)]|,*$)/g, ''), '', false ])
.map((el,idx,arr) => [ el[0], '', optionalSinceIdx !== null && idx >= optionalSinceIdx ])
.filter(n => n && n[0].length && !/^[\[\]\(\)]$/.test(n[0]));

{
let newArgs = [];
for (const [idx, [argName, argType, isOptional]] of Object.entries(args)) {
if (argName.includes(',')) {
// console.warn(` arg ${idx} wasn't split correctly: "${argName}". args was: ${JSON.stringify(args)}`);
for (const [idx2, arg] of Object.entries(argName.split(','))) {
newArgs.push([arg.replace(/^\(/, '').trim(), argType, isOptional])
} else {
newArgs.push([argName, argType, isOptional]);
}
} else {
newArgs.push([argName, argType, isOptional]);
}
args = newArgs;
}
args = newArgs;
}

// args misc. cleanup and type determination...
args = args.filter(el => el && el[0].length);
for (const [idx, [argName]] of Object.entries(args)) {
if (argName === "Plot#type") {
wholeSyntaxLine = wholeSyntaxLine.replace(new RegExp('^' + escapeRegExp(name)), '').trim();
args[idx] = [ 'type', `${name} token`, false ];
}
if (/^listname\d/i.test(argName)) {
args[idx][1] = 'listName'
} else if (argName.includes('list')) {
args[idx][1] = 'list'
} else if (argName.includes('matrix')) {
args[idx][1] = 'matrix'
} else if (argName.endsWith('string') || argName.startsWith('text')) {
args[idx][1] = 'string'
} else if (argName === 'color#') {
args[idx][1] = 'colorNum'
} else if (/^(length|rows|columns|linestyle#)$/.test(argName)) {
args[idx][1] = 'integer'
} else if (argName === 'expression') {
args[idx][1] = 'expression'
} else if (argName === 'complex value') {
args[idx][1] = 'complex'
}
// args misc. cleanup and type determination...
args = args.filter(el => el && el[0].length);
for (const [idx, [argName]] of Object.entries(args)) {
if (argName === "Plot#type") {
wholeSyntaxLine = wholeSyntaxLine.replace(new RegExp('^' + escapeRegExp(name)), '').trim();
args[idx] = [ 'type', `${name} token`, false ];
}
if (/^listname\d/i.test(argName)) {
args[idx][1] = 'listName'
} else if (argName.includes('list')) {
args[idx][1] = 'list'
} else if (argName.includes('matrix')) {
args[idx][1] = 'matrix'
} else if (argName.endsWith('string') || argName.startsWith('text')) {
args[idx][1] = 'string'
} else if (argName === 'color#') {
args[idx][1] = 'colorNum'
} else if (/^(length|rows|columns|linestyle#)$/.test(argName)) {
args[idx][1] = 'integer'
} else if (argName === 'expression') {
args[idx][1] = 'expression'
} else if (argName === 'complex value') {
args[idx][1] = 'complex'
}

// some overrides...
if (description.includes('`valueA` and `valueB`, which can be real numbers or lists') // gcd/lcm, expressions work fine
|| description.includes('`valueA` and `valueB` can be real numbers, expressions, or lists')) {
args[idx][1] = 'real|expression|real[]'
} else if (description.includes('of a real number, expression, or list')) {
args[idx][1] = 'real|expression|real[]'
} else if (description.includes('of a real number, expression, list, or matrix')) {
args[idx][1] = 'real|expression|real[]|matrix'
} else if (description.includes('of a complex number or list')) {
args[idx][1] = 'complex|complex[]'
// some overrides...
if (description.includes('`valueA` and `valueB`, which can be real numbers or lists') // gcd/lcm, expressions work fine
|| description.includes('`valueA` and `valueB` can be real numbers, expressions, or lists')) {
args[idx][1] = 'real|expression|real[]'
} else if (description.includes('of a real number, expression, or list')) {
args[idx][1] = 'real|expression|real[]'
} else if (description.includes('of a real number, expression, list, or matrix')) {
args[idx][1] = 'real|expression|real[]|matrix'
} else if (description.includes('of a complex number or list')) {
args[idx][1] = 'complex|complex[]'
}
//console.log(idx, argName, arguments[idx][1])
}
//console.log(idx, argName, arguments[idx][1])
}

// remove last arg if it's just a comment that got there somehow (not an actual arg)
if (comment && args.length && new RegExp('^\\(?' + comment + '$').test(args[args.length-1][0])) {
args.pop();
}
// remove last arg if it's just a comment that got there somehow (not an actual arg)
if (comment && args.length && new RegExp('^\\(?' + comment + '$').test(args[args.length-1][0])) {
args.pop();
}

const rawLocation = token.querySelector('tbody p.MenuName > span')?.parentElement;
const location = [ ...(rawLocation?.children ?? []) ]
.map(el => el.textContent.trim())
.filter(el => el)
.map(el => menuReplacements[el] ?? el);
if (location.length >= 2) {
if (name.includes('►')) {
const [ part1, part2 ] = name.split('►');
if (location[location.length-1] === part2 && (new RegExp('^([A-Z\d]:)?' + escapeRegExp(part1) + '$').test(location[location.length-2]))) {
location[location.length-2] += '►' + location.pop();
const rawLocation = token.querySelector('tbody p.MenuName > span')?.parentElement;
const location = [ ...(rawLocation?.children ?? []) ]
.map(el => el.textContent.trim())
.filter(el => el)
.map(el => menuReplacements[el] ?? el);
if (location.length >= 2) {
if (name.includes('►')) {
const [ part1, part2 ] = name.split('►');
if (location[location.length-1] === part2 && (new RegExp('^([A-Z\d]:)?' + escapeRegExp(part1) + '$').test(location[location.length-2]))) {
location[location.length-2] += '►' + location.pop();
}
}
if (/^[A-Z\d]:$/.test(location[location.length-2])) {
location[location.length-2] += location.pop();
}
}
if (/^[A-Z\d]:$/.test(location[location.length-2])) {
location[location.length-2] += location.pop();
}
}

let specificName = undefined;
if (!bytes) {
bytes = {
'If Then End': name2bytes['If '],
'If Then Else End': name2bytes['If '],
}[name];
specificName = name;
let specificName = undefined;
if (!bytes) {
throw `bytes not defined for token name: [${name}]`;
bytes = {
'If Then End': name2bytes['If '],
'If Then Else End': name2bytes['If '],
}[name];
specificName = name;
if (!bytes) {
throw `bytes not defined for token name: [${name}]`;
}
}
}

if (tkXML[bytes]) {
const lastTkData = tkXML[bytes][tkXML[bytes].length - 1];
if (lastTkData.enAccessible && tokenEntry.name !== lastTkData.enAccessible) {
tokenEntry.accessibleName = lastTkData.enAccessible;
if (tkXML[bytes]) {
const lastTkData = tkXML[bytes][tkXML[bytes].length - 1];
if (lastTkData.enAccessible && tokenEntry.name !== lastTkData.enAccessible) {
tokenEntry.accessibleName = lastTkData.enAccessible;
}
if (lastTkData.enVariants) {
tokenEntry.nameVariants = lastTkData.enVariants;
}
mergeSinceUntilFromTkXML(tokenEntry, tkXML[bytes], name);
}
if (lastTkData.enVariants) {
tokenEntry.nameVariants = lastTkData.enVariants;

if (comment && comment.startsWith('Alias')) {
tokenEntry.isAlias = true;
}
mergeSinceUntilFromTkXML(tokenEntry, tkXML[bytes], name);
}

if (comment && comment.startsWith('Alias')) {
tokenEntry.isAlias = true;
(json[bytes] ??= tokenEntry).syntaxes.push({
specificName: specificName,
syntax: wholeSyntaxLine,
comment: comment,
arguments: args,
description: description,
inEditorOnly: (token.querySelector('tbody p.MenuName')?.textContent ?? '').includes('†'),
location: location.length && location[0].length ? location : [`[${name.replace(/\($/,'')}]`],
specialCategory: specialCategory.length ? specialCategory : undefined,
});
}

(json[bytes] ??= tokenEntry).syntaxes.push({
specificName: specificName,
syntax: wholeSyntaxLine,
comment: comment,
arguments: args,
description: description,
inEditorOnly: (token.querySelector('tbody p.MenuName')?.textContent ?? '').includes('†'),
location: location.length && location[0].length ? location : [`[${name.replace(/\($/,'')}]`],
specialCategory: specialCategory.length ? specialCategory : undefined,
});

if (manualOverrides[bytes]) {
for (const [ what, override ] of Object.entries(manualOverrides[bytes])) {
if (typeof(json[bytes][what]) === 'undefined') {
Expand Down
26 changes: 26 additions & 0 deletions tokens/0xBB11.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ The tokens LEFT, CENTER and RIGHT can be found in [catalog].
<tt><kbd><b>2nd</b></kbd></tt>, <kbd>distr</kbd>, `DISTR`, `3:invNorm(`
<hr>

## Overview
Computes the inverse cumulative normal distribution function for a given area under the normal distribution curve specified by μ and s.. The optional argument tail can be LEFT (-∞,-a), CENTER [-a,a] or RIGHT (a, ∞) for Real a.
The tokens LEFT, CENTER and RIGHT can be found in [catalog].


<b>Availability</b>: Token available everywhere.

## Syntax
`tail [catalog]: LEFT, CENTER, RIGHT`

## Arguments
<table>
<tr><th>Name</th><th>Type</th><th>Optional</th></tr>

<tr><td><b>tail catalog: LEFT</b></td><td></td><td></td></tr>

<tr><td><b>CENTER</b></td><td></td><td></td></tr>

<tr><td><b>RIGHT</b></td><td></td><td></td></tr>

</table>

## Location
<tt><kbd><b>2nd</b></kbd></tt>, <kbd>distr</kbd>, `DISTR`, `3:invNorm(`
<hr>

## Description

<tt>invNorm(</tt> is the inverse of the cumulative normal distribution function: given a probability, it will give you a z-score with that tail probability. The probability argument of <tt>invNorm(</tt> is between 0 and 1; 0 will give <tt>-1<span style="font-size:75%;">E</span>99</tt> instead of negative infinity, and 1 will give <tt>1<span style="font-size:75%;">E</span>99</tt> instead of positive infinity
Expand Down
Loading

0 comments on commit ad97c9f

Please sign in to comment.