|
|
|
import defineFunction from "../defineFunction"; |
|
import ParseError from "../ParseError"; |
|
import {assertNodeType} from "../parseNode"; |
|
|
|
const globalMap = { |
|
"\\global": "\\global", |
|
"\\long": "\\\\globallong", |
|
"\\\\globallong": "\\\\globallong", |
|
"\\def": "\\gdef", |
|
"\\gdef": "\\gdef", |
|
"\\edef": "\\xdef", |
|
"\\xdef": "\\xdef", |
|
"\\let": "\\\\globallet", |
|
"\\futurelet": "\\\\globalfuture", |
|
}; |
|
|
|
const checkControlSequence = (tok) => { |
|
const name = tok.text; |
|
if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { |
|
throw new ParseError("Expected a control sequence", tok); |
|
} |
|
return name; |
|
}; |
|
|
|
const getRHS = (parser) => { |
|
let tok = parser.gullet.popToken(); |
|
if (tok.text === "=") { |
|
tok = parser.gullet.popToken(); |
|
if (tok.text === " ") { |
|
tok = parser.gullet.popToken(); |
|
} |
|
} |
|
return tok; |
|
}; |
|
|
|
const letCommand = (parser, name, tok, global) => { |
|
let macro = parser.gullet.macros.get(tok.text); |
|
if (macro == null) { |
|
|
|
|
|
tok.noexpand = true; |
|
macro = { |
|
tokens: [tok], |
|
numArgs: 0, |
|
|
|
unexpandable: !parser.gullet.isExpandable(tok.text), |
|
}; |
|
} |
|
parser.gullet.macros.set(name, macro, global); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
defineFunction({ |
|
type: "internal", |
|
names: [ |
|
"\\global", "\\long", |
|
"\\\\globallong", |
|
], |
|
props: { |
|
numArgs: 0, |
|
allowedInText: true, |
|
}, |
|
handler({parser, funcName}) { |
|
parser.consumeSpaces(); |
|
const token = parser.fetch(); |
|
if (globalMap[token.text]) { |
|
|
|
if (funcName === "\\global" || funcName === "\\\\globallong") { |
|
token.text = globalMap[token.text]; |
|
} |
|
return assertNodeType(parser.parseFunction(), "internal"); |
|
} |
|
throw new ParseError(`Invalid token after macro prefix`, token); |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
defineFunction({ |
|
type: "internal", |
|
names: ["\\def", "\\gdef", "\\edef", "\\xdef"], |
|
props: { |
|
numArgs: 0, |
|
allowedInText: true, |
|
primitive: true, |
|
}, |
|
handler({parser, funcName}) { |
|
let tok = parser.gullet.popToken(); |
|
const name = tok.text; |
|
if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) { |
|
throw new ParseError("Expected a control sequence", tok); |
|
} |
|
|
|
let numArgs = 0; |
|
let insert; |
|
const delimiters = [[]]; |
|
|
|
while (parser.gullet.future().text !== "{") { |
|
tok = parser.gullet.popToken(); |
|
if (tok.text === "#") { |
|
|
|
|
|
|
|
|
|
if (parser.gullet.future().text === "{") { |
|
insert = parser.gullet.future(); |
|
delimiters[numArgs].push("{"); |
|
break; |
|
} |
|
|
|
|
|
|
|
tok = parser.gullet.popToken(); |
|
if (!(/^[1-9]$/.test(tok.text))) { |
|
throw new ParseError(`Invalid argument number "${tok.text}"`); |
|
} |
|
if (parseInt(tok.text) !== numArgs + 1) { |
|
throw new ParseError( |
|
`Argument number "${tok.text}" out of order`); |
|
} |
|
numArgs++; |
|
delimiters.push([]); |
|
} else if (tok.text === "EOF") { |
|
throw new ParseError("Expected a macro definition"); |
|
} else { |
|
delimiters[numArgs].push(tok.text); |
|
} |
|
} |
|
|
|
let {tokens} = parser.gullet.consumeArg(); |
|
if (insert) { |
|
tokens.unshift(insert); |
|
} |
|
|
|
if (funcName === "\\edef" || funcName === "\\xdef") { |
|
tokens = parser.gullet.expandTokens(tokens); |
|
tokens.reverse(); |
|
} |
|
|
|
parser.gullet.macros.set(name, { |
|
tokens, |
|
numArgs, |
|
delimiters, |
|
}, funcName === globalMap[funcName]); |
|
|
|
return { |
|
type: "internal", |
|
mode: parser.mode, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
defineFunction({ |
|
type: "internal", |
|
names: [ |
|
"\\let", |
|
"\\\\globallet", |
|
], |
|
props: { |
|
numArgs: 0, |
|
allowedInText: true, |
|
primitive: true, |
|
}, |
|
handler({parser, funcName}) { |
|
const name = checkControlSequence(parser.gullet.popToken()); |
|
parser.gullet.consumeSpaces(); |
|
const tok = getRHS(parser); |
|
letCommand(parser, name, tok, funcName === "\\\\globallet"); |
|
return { |
|
type: "internal", |
|
mode: parser.mode, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
defineFunction({ |
|
type: "internal", |
|
names: [ |
|
"\\futurelet", |
|
"\\\\globalfuture", |
|
], |
|
props: { |
|
numArgs: 0, |
|
allowedInText: true, |
|
primitive: true, |
|
}, |
|
handler({parser, funcName}) { |
|
const name = checkControlSequence(parser.gullet.popToken()); |
|
const middle = parser.gullet.popToken(); |
|
const tok = parser.gullet.popToken(); |
|
letCommand(parser, name, tok, funcName === "\\\\globalfuture"); |
|
parser.gullet.pushToken(tok); |
|
parser.gullet.pushToken(middle); |
|
return { |
|
type: "internal", |
|
mode: parser.mode, |
|
}; |
|
}, |
|
}); |
|
|