Skip to content

Commit

Permalink
fix last issue with escaped parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
make-github-pseudonymous-again committed Oct 17, 2018
1 parent d0f089d commit 0327d62
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 105 deletions.
2 changes: 1 addition & 1 deletion src/shaketape.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export default async function shaketape ( inputTape , outputStream ) {

const ctx = {
env : [ ] ,
args : [ ] ,
variables ,
parser ,
} ;

const transformed = await ast.transform( tree , shaker , ctx ) ;
Expand Down
231 changes: 129 additions & 102 deletions src/transform/shaker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StopIteration } from '@aureooms/js-itertools' ;
import { ast } from '@aureooms/js-grammar' ;
import tape from '@aureooms/js-tape' ;

import visitor from './visitor' ;

Expand Down Expand Up @@ -86,7 +87,7 @@ function extend ( transform, extension ) {
return result ;
}

export default extend( visitor , {
const optimizedVisitor = extend( visitor , {

"anything" : {
"end" : () => empty ,
Expand All @@ -96,6 +97,98 @@ export default extend( visitor , {
"end" : () => empty ,
} ,

"*" : {
"*" : tree => tree ,
} ,

"[" : {
"[" : tree => tree ,
} ,

"]" : {
"]" : tree => tree ,
} ,

"something-else" : {

"text" : tree => tree ,

"\n" : tree => tree ,

" " : tree => tree ,

"digit" : tree => tree ,

"$" : tree => tree ,

} ,

"cmdargs": {
"end" : () => empty ,
} ,

"cmdafter": {
"nothing" : () => empty ,
} ,

"cmdafter-but-not-]": {
"nothing" : () => empty ,
} ,

"endif": {
"fi" : tree => tree ,
} ,

} ) ;

const expandArguments = extend( optimizedVisitor , {
"something-else": {
"argument": async ( tree , match , { args } ) => {
const it = iter(tree.children) ;
await next(it); // #
const nonterminal = await next(it); // # or digit
const arg = await next(iter(nonterminal.children)) ;
if (nonterminal.production === '#') return hash ; // this and next line could be moved one line up
const i = parseInt(arg.buffer, 10) - 1; // #arg
if ( i >= args.length ) throw new Error(`Requesting ${arg.buffer} but only got ${args.length} arguments.`) ;
const subtree = args[i] ;
return subtree ;
} ,
} ,

"argument-subject" : {
"#" : err("argument-subject", "#") ,
"digit" : err("argument-subject", "digit") ,
} ,

} ) ;

function parseArguments ( args , dfltarg , type , name ) {

const cmdargs = [];
let arg_i = args
if ( arg_i.production === 'optional' ) {
if (dfltarg === null) throw new Error(`${type} ${name} is not defined with a default argument.`) ;
const [ optgroup , tail ] = arg_i.children ;
const [ _open , arg , _close ] = optgroup.children ;
cmdargs.push(arg);
arg_i = tail ;
}
else if (dfltarg !== null) cmdargs.push(dfltarg) ;
while ( arg_i.production === 'normal' ) {
const [ group , tail ] = arg_i.children ;
const [ _open , arg , _close ] = group.children ;
cmdargs.push(arg) ;
arg_i = tail ;
}
const complex = arg_i.production === 'optional' ;

return [ complex , cmdargs ] ;

}

export default extend( optimizedVisitor , {

"othercmd" : {

"othercmd": async ( tree , match , ctx ) => {
Expand All @@ -113,27 +206,18 @@ export default extend( visitor , {
if ( ctx.variables.get('cmd').has(cmd) ) {

const [ nargs , dfltarg , expandsto ] = ctx.variables.get('cmd').get(cmd) ;
const cmdargs = [];
let arg_i = args
if ( arg_i.production === 'optional' ) {
if (dfltarg === null) throw new Error(`Command ${cmd} is not defined with a default argument.`) ;
const [ optgroup , tail ] = arg_i.children ;
const [ _open , arg , _close ] = optgroup.children ;
cmdargs.push(arg);
arg_i = tail ;
}
else if (dfltarg !== null) cmdargs.push(dfltarg) ;
while ( arg_i.production === 'normal' ) {
const [ group , tail ] = arg_i.children ;
const [ _open , arg , _close ] = group.children ;
cmdargs.push(arg) ;
arg_i = tail ;
}
const complex = arg_i.production === 'optional' ;

const [ complex , cmdargs ] = parseArguments( args , dfltarg , 'Command' , cmd ) ;

if (!complex) {
// do not parse complex syntax
if (cmdargs.length !== nargs) throw new Error(`Command ${cmd} is defined with ${nargs} arguments but ${cmdargs.length} were given.`) ;
return t( expandsto , match , { env: ctx.env, variables: ctx.variables , args: [ ctx.args , cmdargs ] } ) ;

const withArguments = await t( expandsto , expandArguments , { args: cmdargs } )
const flat = ast.flatten(withArguments) ;
const tokensTape = tape.fromAsyncIterable(flat);
const subtree = (await ast.materialize(ctx.parser.parse(tokensTape))).children[0] ;
return t( subtree , match , ctx ) ;
}

}
Expand Down Expand Up @@ -170,31 +254,20 @@ export default extend( visitor , {

const [ nargs , dfltarg , begin , end ] = ctx.variables.get('env').get(env) ;

const cmdargs = [];
let arg_i = args
if ( arg_i.production === 'optional' ) {
if (dfltarg === null) throw new Error(`Environment ${env} is not defined with a default argument.`) ;
const [ optgroup , tail ] = arg_i.children ;
const [ _open , arg , _close ] = optgroup.children ;
cmdargs.push(arg);
arg_i = tail ;
}
else if (dfltarg !== null) cmdargs.push(dfltarg) ;
while ( arg_i.production === 'normal' ) {
const [ group , tail ] = arg_i.children ;
const [ _open , arg , _close ] = group.children ;
cmdargs.push(arg) ;
arg_i = tail ;
}
const [ complex , cmdargs ] = parseArguments( args , dfltarg , 'Environment' , env ) ;

const complex = arg_i.production === 'optional' ;
if (!complex) {
envStackEntry.expand = true ;
envStackEntry.args = cmdargs ;
// do not parse complex syntax
if (cmdargs.length !== nargs)
throw new Error(`Environment ${env} is defined with ${nargs} arguments but ${cmdargs.length} were given.`) ;
return t( begin , match , { env: envStackEntry.children , variables: ctx.variables , args: [ ctx.args , cmdargs ] } ) ;

const withArguments = await t( begin , expandArguments , { args: cmdargs } )
const flat = ast.flatten(withArguments) ;
const tokensTape = tape.fromAsyncIterable(flat);
const subtree = (await ast.materialize(ctx.parser.parse(tokensTape))).children[0] ;
return t( subtree , match , { env: envStackEntry.children , variables: ctx.variables , parser: ctx.parser } ) ;
}

}
Expand Down Expand Up @@ -226,14 +299,18 @@ export default extend( visitor , {
throw new Error(`Trying to end environment on an empty stack with \\end{${env}} (matching \\begin{${env}} is missing).`);
}

const { expand , env: currentEnv , children , args } = ctx.env.pop();
const { expand , env: currentEnv , children , args: cmdargs } = ctx.env.pop();

if ( currentEnv !== env ) {
throw new Error(`Trying to match \\begin{${currentEnv}} with \\end{${env}}.`);
}
else if (expand) {
const [ nargs , defaultarg , begin , end ] = ctx.variables.get('env').get(env) ;
return t( end , match , { env: children , variables: ctx.variables , args: [ ctx.args , args ] } ) ;
const withArguments = await t( end , expandArguments , { args: cmdargs } )
const flat = ast.flatten(withArguments) ;
const tokensTape = tape.fromAsyncIterable(flat);
const subtree = (await ast.materialize(ctx.parser.parse(tokensTape))).children[0] ;
return t( subtree , match , { env: children , variables: ctx.variables , parser: ctx.parser } ) ;
}
else {
return {
Expand All @@ -247,24 +324,16 @@ export default extend( visitor , {

} ,

"*" : {
"*" : tree => tree ,
} ,

"[" : {
"[" : tree => tree ,
} ,

"]" : {
"]" : tree => tree ,
} ,

"something-else" : {

"text" : tree => tree ,

"newif": () => empty ,

"comment": ( ) => ({
'type' : 'leaf' ,
'terminal' : 'comment' ,
'buffer' : '%' ,
}) ,

"ifcmd": async ( tree , match , ctx ) => {

const it = iter(tree.children) ;
Expand Down Expand Up @@ -311,12 +380,6 @@ export default extend( visitor , {
return empty;
} ,

"comment": ( ) => ({
'type' : 'leaf' ,
'terminal' : 'comment' ,
'buffer' : '%' ,
}) ,

"def": async ( tree , match , { variables } ) => {
const it = iter(tree.children) ;
await next(it) ; // \def
Expand Down Expand Up @@ -358,40 +421,16 @@ export default extend( visitor , {
return t( envdef , match , ctx ) ;
} ,

"\n" : tree => tree ,

" " : tree => tree ,

"digit" : tree => tree ,

"argument": async ( tree , match , { args , variables } ) => {
const it = iter(tree.children) ;
await next(it); // #
const nonterminal = await next(it); // # or digit
const arg = await next(iter(nonterminal.children)) ;
if ( args.length < 2 ) throw new Error(`Requesting ${arg.buffer} but got no arguments in context.`) ;
if (nonterminal.production === 'digit') {
const i = parseInt(arg.buffer, 10) - 1; // #arg
if ( i >= args[1].length ) throw new Error(`Requesting ${arg.buffer} but only got ${args[1].length} arguments.`) ;
const subtree = args[1][i] ; // arg
return t( subtree , match , { args: args[0] , variables } ) ;
}
else {
return hash ;
} ;
} ,

"$" : tree => tree ,

} ,

"argument-subject" : {
"#" : err("argument-subject", "#") ,
"digit" : err("argument-subject", "digit") ,
} ,

"endif": {
"fi" : tree => tree ,
"#" : () => {
throw new Error('Escaped hash (##) without argument context.') ;
} ,
"digit" : async tree => {
const digit = await next(iter(tree.children)) ;
throw new Error(`Requesting #${digit.buffer} without argument context.`) ;
} ,
} ,

"command-definition" : {
Expand Down Expand Up @@ -485,18 +524,6 @@ export default extend( visitor , {
"no" : err( "cmd*" , "no" ) ,
} ,

"cmdargs": {
"end" : () => empty ,
} ,

"cmdafter": {
"nothing" : () => empty ,
} ,

"cmdafter-but-not-]": {
"nothing" : () => empty ,
} ,

"ignore" : {
"starts-with-a-space" : err('ignore', 'starts-with-a-space') ,
"starts-with-a-newline" : err('ignore', 'starts-with-a-newline') ,
Expand Down
12 changes: 10 additions & 2 deletions test/src/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,15 @@ test( throws , '#' , /unexpected end of file/ ) ;
test( throws , '#x' , /1:2/ ) ;

// no arguments defined
test( throws , '#1' , /no arguments in context/ ) ;
test( throws , '#1' , /#1 without argument context/ ) ;
test( throws , '#2' , /#2 without argument context/ ) ;
test( throws , '#3' , /#3 without argument context/ ) ;
test( throws , '#4' , /#4 without argument context/ ) ;
test( throws , '#5' , /#5 without argument context/ ) ;
test( throws , '#6' , /#6 without argument context/ ) ;
test( throws , '#7' , /#7 without argument context/ ) ;
test( throws , '#8' , /#8 without argument context/ ) ;
test( throws , '#9' , /#9 without argument context/ ) ;

// escaped #
test( immutable , '\\#' ) ;
Expand Down Expand Up @@ -238,7 +246,7 @@ test( transformFile , `${transformedInputFiledir}/${filename}` , `${transformedO

// argument escaping
test( transform , '\\newcommand\\x[1]{\\newcommand\\y[1]{#1 ##1}}\\x{test}\\y{1212}' , 'test 1212' ) ;
test( throws , '\\def\\y{##1}\\newcommand\\x[1]{\\y}\\x{test}' , /no arguments in context/ ) ;
test( throws , '\\def\\y{##1}\\newcommand\\x[1]{\\y}\\x{test}' , /#1 without argument context/ ) ;

// default arguments with newcommand and renewcommand
test( transform , '\\newcommand{\\price}[2][17.5]{\\pounds #2 excl VAT @ #1\\%}\\price{100}' , '\\pounds 100 excl VAT @ 17.5\\%') ;
Expand Down

0 comments on commit 0327d62

Please sign in to comment.