Skip to content

Commit

Permalink
[v1.1] typeset -f <functname>: generate script using sh_deparse()
Browse files Browse the repository at this point in the history
Problem reproducer:
- Locate or write a script with a function definition
- Source the script with '.' or 'source'
- Print the function definition with 'typeset -f'; looks good
- Edit the file to add or remove characters before or in the
  function definition, or rename or remove the file
- Print the function definition with 'typeset -f' again; the output
  is corrupted!

Cause: ksh remembers the offset[*1] and length[*2] of the function
definition in the source file, and prints function definitions by
simply dumping that amount of bytes from that offset in the source
file. This will obviously break if the file is edited or (re)moved,
so that approach is fundamentally flawed and needs to be replaced.

All other shells that are capable of printing function definitions
(bash, mksh, yash, zsh) regenerate the function definition from the
parse tree. ksh does include code for a deparser (in deparse.c) but
it is disused (so it was removed in the 1.0 branch). This commit
changes 'typeset -f' to use it, as well as 'typeset -T' when
printing type discipline functions.

It also applies lots of tweaks and fixes to the deparser. More
fixes might be needed. So, it's time to test this.

[*1] functloc in struct functnod --> hoffset in struct Ufunction
[*2] repurposed functline in struct functnod --> nvsize in
     struct Namval

src/cmd/ksh93/sh/deparse.c:
- Too many tweaks to mention for more correct script generation and
  better formatting; this is the result of intermittent work over
  about a year so I don't quite remember everything.
- sh_deparse(): Add argument for initial indentation level.
- p_keyword(): Convert the BEGIN/MIDDLE/END argument into a bitmask
  and add a NOTAB bit flag to avoid printing a tab after a keyword.

src/cmd/ksh93/sh/string.c: sh_fmtq():
- For assignments, avoid quoting variable names containing a dot,
  e.g. .namespace.foo=bar should not become '.namespace.foo=bar'.
- Avoid quoting the variable name in additive assignments, e.g.
  foo+=bar\ baz should become foo+='bar baz', not 'foo+=bar baz'.

src/cmd/ksh93/include/shnodes.h,
src/cmd/ksh93/sh/parse.c:
- funct(): Do not save function location, offset and length.

src/cmd/ksh93/bltins/typeset.c: print_namval():
- For 'typeset -f functionname', instead of retrieving the function
  definition from the saved position in its source file, call
  sh_deparse() to generate script from the parse tree. Start
  indentation at level 0.

src/cmd/ksh93/bltins/typeset.c: sh_outtype():
- For type discipline functions output by 'typeset -T typename',
  instead of retrieving the function definition from the saved
  position in its source file, call sh_deparse() to generate script
  from the parse tree. Start indentation at level 1.
  • Loading branch information
McDutchie committed Aug 30, 2022
1 parent 4810789 commit f21ace5
Show file tree
Hide file tree
Showing 15 changed files with 105 additions and 128 deletions.
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.

2022-08-30:

- When printing a function definition, 'typeset -f function_name' now
regenerates the code from the parse tree instead of dumping the literal
code from the source file. This avoids corrupted or missing output if the
source file is edited or (re)moved after loading the function. It also
makes 'typeset -f' function as a code reformatter (AT&T indentation style).

- In the vi line editor, 'C' and 'c$' at the start of a line now enter insert
mode instead of beeping, and 'I' jumps to the first non-blank character on
the line instead of the start of the line. These changes are consistent
Expand Down
21 changes: 4 additions & 17 deletions src/cmd/ksh93/bltins/typeset.c
Original file line number Diff line number Diff line change
Expand Up @@ -1477,34 +1477,21 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
sfputr(file,cp,-1);
if(nv_isattr(np,NV_FPOSIX))
sfwrite(file,"()",2);
if(np->nvalue.ip && np->nvalue.rp->hoffset>=0)
if(np->nvalue.rp && nv_funtree(np))
fname = np->nvalue.rp->fname;
else
flag = '\n';
if(flag)
{
if(tp->pflag && np->nvalue.ip && np->nvalue.rp->hoffset>=0)
if(tp->pflag && np->nvalue.rp && nv_funtree(np))
sfprintf(file," #line %d %s\n", np->nvalue.rp->lineno, fname ? sh_fmtq(fname) : Empty);
else
sfputc(file, '\n');
}
else
{
if(nv_isattr(np,NV_FTMP))
{
fname = 0;
iop = sh.heredocs;
}
else if(fname)
iop = sfopen(iop,fname,"r");
else if(sh.hist_ptr)
iop = (sh.hist_ptr)->histfp;
if(iop && sfseek(iop,(Sfoff_t)np->nvalue.rp->hoffset,SEEK_SET)>=0)
sfmove(iop,file, nv_size(np), -1);
else
flag = '\n';
if(fname)
sfclose(iop);
sfputc(file, '\n');
sh_deparse(file, (Shnode_t*)(nv_funtree(np)), 2 | nv_isattr(np,NV_FPOSIX), 0);
}
return(nv_size(np)+1);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/ksh93/include/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ extern void sh_assignok(Namval_t*,int);
extern struct dolnod *sh_arguse(void);
extern char *sh_checkid(char*,char*);
extern void sh_chktrap(void);
extern void sh_deparse(Sfio_t*,const Shnode_t*,int);
extern void sh_deparse(Sfio_t*,const Shnode_t*,int,int);
extern int sh_debug(const char*,const char*,const char*,char *const[],int);
extern char **sh_envgen(void);
extern void sh_envnolocal(Namval_t*,void*);
Expand Down
2 changes: 0 additions & 2 deletions src/cmd/ksh93/include/name.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ struct Ufunction
short argc; /* number of references */
short running; /* function is running */
char **argv; /* reference argument list */
off_t hoffset; /* offset into source or history file */
Namval_t *nspace; /* pointer to name space */
char *fname; /* file name where function defined */
char *help; /* help string */
Expand All @@ -124,7 +123,6 @@ struct Ufunction
#define NV_COMVAR 0x4000000
#define NV_FUNCTION (NV_RJUST|NV_FUNCT) /* value is shell function */
#define NV_FPOSIX NV_LJUST /* POSIX function semantics */
#define NV_FTMP NV_ZFILL /* function source in tmpfile */
#define NV_STATICF NV_INTEGER /* static class function */

#define NV_NOPRINT (NV_LTOU|NV_UTOL) /* do not print */
Expand Down
1 change: 0 additions & 1 deletion src/cmd/ksh93/include/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@ struct Shell_s
struct sh_scoped st; /* scoped information */
Stk_t *stk; /* stack pointer */
Sfio_t *heredocs; /* current here-doc temp file */
Sfio_t *funlog; /* for logging function definitions */
int **fdptrs; /* pointer to file numbers */
char *lastarg; /* $_ */
int path_err; /* last error on path search */
Expand Down
1 change: 0 additions & 1 deletion src/cmd/ksh93/include/shnodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ struct functnod
char *functnam;
Shnode_t *functtre;
int functline;
off_t functloc;
struct slnod *functstak;
struct comnod *functargs;
};
Expand Down
105 changes: 63 additions & 42 deletions src/cmd/ksh93/sh/deparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@
#include "shnodes.h"
#include "test.h"

/* flags that can be specified with p_keyword() */
#define BEGIN (1 << 0)
#define MIDDLE (1 << 1)
#define END (1 << 2)
#define NOTAB (1 << 3)

#define HUGE_INT (((unsigned)-1)>>1)
#define BEGIN 0
#define MIDDLE 1
#define END 2
/* options that can be specified with p_arg() */
#define PRE 1
#define POST 2


/* flags that can be specified with p_tree() */
#define NO_NEWLINE (1 << 0)
#define NEED_BRACE (1 << 1)
#define NO_BRACKET (1 << 2)
#define PROCSUBST (1 << 3)
#define PROC_SUBST (1 << 3)

static void p_comlist(const struct dolnod*,int);
static void p_arg(const struct argnod*, int endchar, int opts);
Expand All @@ -53,17 +54,25 @@ static void p_tree(const Shnode_t*,int);

static int level;
static int begin_line;
static int end_line;
static int end_line = '\n';
static char io_op[7];
static char un_op[3] = "-?";
static const struct ionod *here_doc;
static Sfio_t *outfile;
static const char *forinit = "";

void sh_deparse(Sfio_t *out, const Shnode_t *t,int tflags)
void sh_deparse(Sfio_t *out, const Shnode_t *t,int tflags, int initlevel)
{
int firstnodetype = t->tre.tretyp & COMMSK;
char needouterbrace = (tflags & NEED_BRACE) && firstnodetype != TLST && (!(tflags & NV_FPOSIX) || firstnodetype != TPAR);
outfile = out;
level = initlevel;
begin_line=1;
if(needouterbrace)
p_keyword("{",BEGIN);
p_tree(t,tflags);
if(needouterbrace)
p_keyword("}",(tflags & NO_NEWLINE) ? 0 : END);
}
/*
* print script corresponding to shell tree <t>
Expand All @@ -73,7 +82,7 @@ static void p_tree(register const Shnode_t *t,register int tflags)
register char *cp=0;
int save = end_line;
int needbrace = (tflags&NEED_BRACE);
int procsub = (tflags&PROCSUBST);
int procsub = (tflags&PROC_SUBST);
tflags &= ~NEED_BRACE;
if(tflags&NO_NEWLINE)
end_line = ' ';
Expand All @@ -85,12 +94,18 @@ static void p_tree(register const Shnode_t *t,register int tflags)
{
case TTIME:
if(t->tre.tretyp&COMSCAN)
p_keyword("!",BEGIN);
{
p_keyword("!",MIDDLE|NOTAB);
if(t->par.partre)
p_tree(t->par.partre,tflags);
}
else
{
p_keyword("time",BEGIN);
if(t->par.partre)
p_tree(t->par.partre,tflags);
level--;
if(t->par.partre)
p_tree(t->par.partre,tflags);
level--;
}
break;

case TCOM:
Expand Down Expand Up @@ -149,20 +164,21 @@ static void p_tree(register const Shnode_t *t,register int tflags)

case TWH:
if(t->wh.whinc)
cp = "for";
p_keyword("for",BEGIN|NOTAB);
else if(t->tre.tretyp&COMSCAN)
cp = "until";
p_keyword("until",BEGIN);
else
cp = "while";
p_keyword(cp,BEGIN);
p_keyword("while",BEGIN);
if(t->wh.whinc)
{
struct argnod *arg = (t->wh.whtre)->ar.arexpr;
sfprintf(outfile,"(( %s; ",forinit);
sfprintf(outfile,"((%s;",forinit);
forinit = "";
sfputr(outfile,arg->argval,';');
arg = (t->wh.whinc)->arexpr;
sfprintf(outfile," %s))\n",arg->argval);
if(level>1)
sfnputc(outfile,'\t',level-1);
}
else
p_tree(t->wh.whtre,0);
Expand Down Expand Up @@ -207,12 +223,13 @@ static void p_tree(register const Shnode_t *t,register int tflags)
tflags |= NO_NEWLINE;
if(!(tflags&NO_BRACKET))
{
p_keyword("[[",BEGIN);
p_keyword("[[",BEGIN|NOTAB);
tflags |= NO_BRACKET;
bracket=1;
}
}
p_tree(t->lst.lstlef,NEED_BRACE|NO_NEWLINE|(tflags&NO_BRACKET));
begin_line = 0;
if(tflags&FALTPIPE)
{
Shnode_t *tt = t->lst.lstrit;
Expand All @@ -237,25 +254,28 @@ static void p_tree(register const Shnode_t *t,register int tflags)
}

case TPAR:
p_keyword("(",BEGIN);
p_tree(t->par.partre,0);
{
char indented_block = (begin_line || !level);
p_keyword("(", indented_block ? BEGIN : BEGIN|NOTAB);
p_tree(t->par.partre, indented_block ? 0 : NO_NEWLINE);
p_keyword(")",END);
break;
}

case TARITH:
{
register struct argnod *ap = t->ar.arexpr;
if(begin_line && level)
sfnputc(outfile,'\t',level);
sfprintf(outfile,"(( %s ))%c",ap->argval,end_line);
sfprintf(outfile,"((%s))%c",ap->argval,end_line);
if(!(tflags&NO_NEWLINE))
begin_line=1;
break;
}

case TFOR:
cp = ((t->tre.tretyp&COMSCAN)?"select":"for");
p_keyword(cp,BEGIN);
p_keyword(cp,BEGIN|NOTAB);
sfputr(outfile,t->for_.fornam,' ');
if(t->for_.forlst)
{
Expand All @@ -276,7 +296,7 @@ static void p_tree(register const Shnode_t *t,register int tflags)
break;

case TSW:
p_keyword("case",BEGIN);
p_keyword("case",BEGIN|NOTAB);
p_arg(t->sw.swarg,' ',0);
if(t->sw.swlst)
{
Expand All @@ -298,7 +318,7 @@ static void p_tree(register const Shnode_t *t,register int tflags)
}
else
{
p_keyword("function",BEGIN);
p_keyword("function",BEGIN|NOTAB);
tflags = (t->funct.functargs?' ':'\n');
sfputr(outfile,t->funct.functnam,tflags);
if(t->funct.functargs)
Expand All @@ -318,10 +338,10 @@ static void p_tree(register const Shnode_t *t,register int tflags)
/* new test compound command */
case TTST:
if(!(tflags&NO_BRACKET))
p_keyword("[[",BEGIN);
p_keyword("[[",BEGIN|NOTAB);
if((t->tre.tretyp&TPAREN)==TPAREN)
{
p_keyword("(",BEGIN);
p_keyword("(",BEGIN|NOTAB);
p_tree(t->lst.lstlef,NO_BRACKET|NO_NEWLINE);
p_keyword(")",END);
}
Expand Down Expand Up @@ -358,19 +378,19 @@ static void p_tree(register const Shnode_t *t,register int tflags)

/*
* print a keyword
* increment indent level for flag==BEGIN
* decrement indent level for flag==END
* increment indent level for flag & BEGIN
* decrement indent level for flag & END
*/
static void p_keyword(const char *word,int flag)
{
register int sep;
if(flag==END)
if(flag & END)
sep = end_line;
else if(*word=='[' || *word=='(')
else if(flag & NOTAB)
sep = ' ';
else
sep = '\t';
if(flag!=BEGIN)
if(!(flag & BEGIN))
level--;
if(begin_line && level)
sfnputc(outfile,'\t',level);
Expand All @@ -379,7 +399,7 @@ static void p_keyword(const char *word,int flag)
begin_line=1;
else
begin_line=0;
if(flag!=END)
if(!(flag & END))
level++;
}

Expand All @@ -394,7 +414,8 @@ static void p_arg(register const struct argnod *arg,register int endchar,int opt
else if(opts&PRE)
{
/* case alternation lists in reverse order */
p_arg(arg->argnxt.ap,'|',opts);
p_arg(arg->argnxt.ap,-1,opts);
sfprintf(outfile," | ");
flag = endchar;
}
else if(opts)
Expand All @@ -406,7 +427,8 @@ static void p_arg(register const struct argnod *arg,register int endchar,int opt
int c = (arg->argflag&ARG_RAW)?'>':'<';
sfputc(outfile,c);
sfputc(outfile,'(');
p_tree((Shnode_t*)arg->argchn.ap,PROCSUBST);
begin_line = 0;
p_tree((Shnode_t*)arg->argchn.ap,PROC_SUBST);
}
else if(*cp==0 && opts==POST && arg->argchn.ap)
{
Expand Down Expand Up @@ -478,7 +500,7 @@ static void p_redirect(register const struct ionod *iop)
here_doc = iop;
io_op[2] = '<';
}
sfputr(outfile,cp,' ');
sfputr(outfile,cp,-1);
if(iop->ionxt)
iof = ' ';
else
Expand All @@ -491,11 +513,9 @@ static void p_redirect(register const struct ionod *iop)
if((iop->iofile & IOPROCSUB) && !(iop->iofile & IOLSEEK))
{
/* process substitution as argument to redirection */
if(iop->iofile & IOPUT)
sfwrite(outfile,">(",2);
else
sfwrite(outfile,"<(",2);
p_tree((Shnode_t*)iop->ioname,PROCSUBST);
sfprintf(outfile," %c(", (iop->iofile & IOPUT) ? '>' : '<');
begin_line = 0;
p_tree((Shnode_t*)iop->ioname,PROC_SUBST);
sfputc(outfile,iof);
}
else if(iop->iodelim)
Expand Down Expand Up @@ -569,7 +589,8 @@ static void p_switch(register const struct regnod *reg)
sfnputc(outfile,'\t',level-1);
p_arg(reg->regptr,')',PRE);
begin_line = 0;
sfputc(outfile,'\t');
sfputc(outfile,'\n');
sfnputc(outfile,'\t',level);
if(reg->regcom)
p_tree(reg->regcom,0);
level++;
Expand Down
Loading

5 comments on commit f21ace5

@JohnoKing
Copy link

@JohnoKing JohnoKing commented on f21ace5 Aug 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this commit appears to have fixed a rather old bug that caused truncated typeset -f output (att#25).
Output comparison:

$ ksh -c 'function f { g() uname; g; }; typeset -f f'   # ksh93u+, ksh2020 and ksh93u+m v1.0
function f { g() uname;$

$ arch/*/bin/ksh -c 'function f { g() uname; g; }; typeset -f f'   # dev branch
function f
{	g()
		{
			uname
	}
	g
}

The resulting output may not be perfect, but it is usable for reinput to the shell (certainly an improvement over truncated output).

@McDutchie
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that observation. Looks like deparse.c needs a cosmetic tweak for nested function definitions.

@JohnoKing
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For finding other deparser issues, shcomp could be a useful tool. The following patch adds shcomp --deparse and modifies shtests to test deparsed scripts:

diff --git a/src/cmd/ksh93/sh/shcomp.c b/src/cmd/ksh93/sh/shcomp.c
index 17f496744..a70695040 100644
--- a/src/cmd/ksh93/sh/shcomp.c
+++ b/src/cmd/ksh93/sh/shcomp.c
@@ -33,7 +33,7 @@ static const char usage[] =
 "[-license?https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html]"
 "[--catalog?" SH_DICT "]"
 "[+NAME?shcomp - compile a shell script]"
-"[+DESCRIPTION?Unless \b-D\b is specified, \b\f?\f\b takes a shell script, "
+"[+DESCRIPTION?Unless one of \b-d\b or \b-D\b is specified, \b\f?\f\b takes a shell script, "
 	"\ainfile\a, and creates a binary format file, \aoutfile\a, that "
 	"\bksh\b can read and execute with the same effect as the original "
 	"script.]"
@@ -50,6 +50,7 @@ static const char usage[] =
 	"a script from your keyboard unless \ainfile\a is given as "
 	"\b/dev/stdin\b, and will refuse to write binary data to a terminal "
 	"in any case.]"
+"[d:deparse?Dump a deparsed version of the shell script to \aoutfile\a.]"
 "[D:dictionary?Generate a list of strings that need to be placed in a message "
 	"catalog for internationalization.]"
 "[n:noexec?Displays warning messages for obsolete or non-conforming "
@@ -82,13 +83,16 @@ int main(int argc, char *argv[])
 	Namval_t *np;
 	Shnode_t *t;
 	char *cp, *shcomp_id, *script_id;
-	int n, nflag=0, vflag=0, dflag=0;
+	int n, nflag=0, vflag=0, dflag=0, deparse=0;
 	shcomp_id = error_info.id = path_basename(argv[0]);
 	while(n = optget(argv, usage )) switch(n)
 	{
 	    case 'D':
 		dflag=1;
 		break;
+	    case 'd':
+		deparse=1;
+		break;
 	    case 'v':
 		vflag=1;
 		break;
@@ -108,7 +112,7 @@ int main(int argc, char *argv[])
 	sh.shcomp = 1;
 	argv += opt_info.index;
 	argc -= opt_info.index;
-	if(argc==0 && tty_check(0))
+	if(argc==0 && tty_check(0) && !deparse)
 	{
 		errormsg(SH_DICT,ERROR_exit(0),"refusing to read script from terminal");
 		error_info.errors++;
@@ -141,7 +145,7 @@ int main(int argc, char *argv[])
 	}
 	else
 		out = sfstdout;
-	if(tty_check(sffileno(out)))
+	if(tty_check(sffileno(out)) && !deparse)
 	{
 		errormsg(SH_DICT,ERROR_exit(1),"refusing to write binary data to terminal",cp);
 		UNREACHABLE();
@@ -155,7 +159,7 @@ int main(int argc, char *argv[])
 		sh_onoption(SH_NOEXEC);
 	if(vflag)
 		sh_onoption(SH_VERBOSE);
-	if(!dflag)
+	if(!dflag && !deparse)
 		sfwrite(out,header,sizeof(header));  /* write binary shcomp header */
 #if SHOPT_ESH || SHOPT_VSH
 	sh_offoption(SH_MULTILINE);
@@ -173,7 +177,9 @@ int main(int argc, char *argv[])
 			if((t->tre.tretyp&(COMMSK|COMSCAN))==0 && t->com.comnamp && strcmp(nv_name((Namval_t*)t->com.comnamp),"alias")==0)
 				/* Create aliases found in the script to prevent syntax errors */
 				sh_exec(t,0);
-			if(!dflag && sh_tdump(out,t) < 0)
+			if(deparse)
+				sh_deparse(out,t,0,0);
+			else if(!dflag && sh_tdump(out,t) < 0)
 			{
 				error_info.id = shcomp_id;
 				errormsg(SH_DICT,ERROR_exit(1),"dump failed");
diff --git a/src/cmd/ksh93/tests/shtests b/src/cmd/ksh93/tests/shtests
index 43ef69fa6..e4b1a22c2 100755
--- a/src/cmd/ksh93/tests/shtests
+++ b/src/cmd/ksh93/tests/shtests
@@ -415,7 +415,7 @@ do	[[ $i == *.sh ]] || i+='.sh'
 		num_alias=$(grep -c '^alias [[:alnum:]_]\{2,\}=' _common)
 		{ grep '^alias [[:alnum:]_]\{2,\}=' _common; sed "1,$num_alias d" "$i"; } >$c.orig
 		cd "$tmp_s" || exit
-		if	"$SHCOMP" "$c.orig" > $c
+		if	"$SHCOMP" --deparse "$c.orig" > $c
 		then	if	tmp=$tmp_s $valgrind $SHELL $trace $c
 			then	echo test $o passed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} "[ $t $T 0 errors ]"
 			else	e=$?

Below is an attached log file containing the results of bin/shtests -c after applying the above patch. There are quite a lot of test failures.
log.txt

Additionally, comparing differences in a script after deparsing it can be done with a diff command like the one below:

diff -u src/cmd/ksh93/tests/arrays.sh <(arch/*/bin/shcomp --deparse src/cmd/ksh93/tests/arrays.sh)

@McDutchie
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of 0975086, nested function definitions should be correctly indented.

@McDutchie
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For finding other deparser issues, shcomp could be a useful tool. The following patch adds shcomp --deparse and modifies shtests to test deparsed scripts:

Very interesting idea, I will be trying that out. I can see at least one problem: aliases. The deparser output has all aliases resolved. But some aliases resolve to words that may be other aliases or even the same alias -- err_exit is one example of that. So the deparser output (assuming no bugs) is only guaranteed to be correct for reinput if no aliases are defined.

mksh deals with this issue by prefixing all words that are in a position where alias expansion may happen with a backslash. I think the effect of that is obnoxious when reading the output as a human, so I've no intention of following that. IMO, it is reasonable to require that no aliases are defined when reusing deparser output as input.

In the meantime I've already found a deparser bug that I don't know how to solve, so I'll create an issue.

Please sign in to comment.