Skip to content

Conversation

@cellularmitosis
Copy link
Contributor

@cellularmitosis cellularmitosis commented Apr 4, 2025

This PR adds a hideemptyattrs option to Node.show(). Let me know what you think!

Given hello.c:

int main(int argc, char** argv) {
    printf("Hello, world!\n");
    return 0;
}

Here's a comparison of all of the options.

default show():

ast.show(attrnames=False, hideemptyattrs=False, nodenames=False, showcoord=False):
FileAST: 
  FuncDef: 
    Decl: main, [], [], [], []
      FuncDecl: 
        ParamList: 
          Decl: argc, [], [], [], []
            TypeDecl: argc, [], None
              IdentifierType: ['int']
          Decl: argv, [], [], [], []
            PtrDecl: []
              PtrDecl: []
                TypeDecl: argv, [], None
                  IdentifierType: ['char']
        TypeDecl: main, [], None
          IdentifierType: ['int']
    Compound: 
      FuncCall: 
        ID: printf
        ExprList: 
          Constant: string, "Hello, world!\n"
      Return: 
        Constant: int, 0

with hideemptyattrs:

ast.show(attrnames=False, hideemptyattrs=True, nodenames=False, showcoord=False):
FileAST: 
  FuncDef: 
    Decl: main
      FuncDecl: 
        ParamList: 
          Decl: argc
            TypeDecl: argc
              IdentifierType: ['int']
          Decl: argv
            PtrDecl: 
              PtrDecl: 
                TypeDecl: argv
                  IdentifierType: ['char']
        TypeDecl: main
          IdentifierType: ['int']
    Compound: 
      FuncCall: 
        ID: printf
        ExprList: 
          Constant: string, "Hello, world!\n"
      Return: 
        Constant: int, 0

attrnames=True:

ast.show(attrnames=True, hideemptyattrs=False, nodenames=False, showcoord=False):
FileAST: 
  FuncDef: 
    Decl: name=main, quals=[], align=[], storage=[], funcspec=[]
      FuncDecl: 
        ParamList: 
          Decl: name=argc, quals=[], align=[], storage=[], funcspec=[]
            TypeDecl: declname=argc, quals=[], align=None
              IdentifierType: names=['int']
          Decl: name=argv, quals=[], align=[], storage=[], funcspec=[]
            PtrDecl: quals=[]
              PtrDecl: quals=[]
                TypeDecl: declname=argv, quals=[], align=None
                  IdentifierType: names=['char']
        TypeDecl: declname=main, quals=[], align=None
          IdentifierType: names=['int']
    Compound: 
      FuncCall: 
        ID: name=printf
        ExprList: 
          Constant: type=string, value="Hello, world!\n"
      Return: 
        Constant: type=int, value=0

with hideemptyattrs:

ast.show(attrnames=True, hideemptyattrs=True, nodenames=False, showcoord=False):
FileAST: 
  FuncDef: 
    Decl: name=main
      FuncDecl: 
        ParamList: 
          Decl: name=argc
            TypeDecl: declname=argc
              IdentifierType: names=['int']
          Decl: name=argv
            PtrDecl: 
              PtrDecl: 
                TypeDecl: declname=argv
                  IdentifierType: names=['char']
        TypeDecl: declname=main
          IdentifierType: names=['int']
    Compound: 
      FuncCall: 
        ID: name=printf
        ExprList: 
          Constant: type=string, value="Hello, world!\n"
      Return: 
        Constant: type=int, value=0

nodenames=True:

ast.show(attrnames=True, hideemptyattrs=False, nodenames=True, showcoord=False):
FileAST: 
  FuncDef <ext[0]>: 
    Decl <decl>: name=main, quals=[], align=[], storage=[], funcspec=[]
      FuncDecl <type>: 
        ParamList <args>: 
          Decl <params[0]>: name=argc, quals=[], align=[], storage=[], funcspec=[]
            TypeDecl <type>: declname=argc, quals=[], align=None
              IdentifierType <type>: names=['int']
          Decl <params[1]>: name=argv, quals=[], align=[], storage=[], funcspec=[]
            PtrDecl <type>: quals=[]
              PtrDecl <type>: quals=[]
                TypeDecl <type>: declname=argv, quals=[], align=None
                  IdentifierType <type>: names=['char']
        TypeDecl <type>: declname=main, quals=[], align=None
          IdentifierType <type>: names=['int']
    Compound <body>: 
      FuncCall <block_items[0]>: 
        ID <name>: name=printf
        ExprList <args>: 
          Constant <exprs[0]>: type=string, value="Hello, world!\n"
      Return <block_items[1]>: 
        Constant <expr>: type=int, value=0

with hideemptyattrs:

ast.show(attrnames=True, hideemptyattrs=True, nodenames=True, showcoord=False):
FileAST: 
  FuncDef <ext[0]>: 
    Decl <decl>: name=main
      FuncDecl <type>: 
        ParamList <args>: 
          Decl <params[0]>: name=argc
            TypeDecl <type>: declname=argc
              IdentifierType <type>: names=['int']
          Decl <params[1]>: name=argv
            PtrDecl <type>: 
              PtrDecl <type>: 
                TypeDecl <type>: declname=argv
                  IdentifierType <type>: names=['char']
        TypeDecl <type>: declname=main
          IdentifierType <type>: names=['int']
    Compound <body>: 
      FuncCall <block_items[0]>: 
        ID <name>: name=printf
        ExprList <args>: 
          Constant <exprs[0]>: type=string, value="Hello, world!\n"
      Return <block_items[1]>: 
        Constant <expr>: type=int, value=0

showcoord=True:

ast.show(attrnames=True, hideemptyattrs=False, nodenames=True, showcoord=True):
FileAST:  (at None)
  FuncDef <ext[0]>:  (at :1:5)
    Decl <decl>: name=main, quals=[], align=[], storage=[], funcspec=[] (at :1:5)
      FuncDecl <type>:  (at :1:5)
        ParamList <args>:  (at :1:14)
          Decl <params[0]>: name=argc, quals=[], align=[], storage=[], funcspec=[] (at :1:14)
            TypeDecl <type>: declname=argc, quals=[], align=None (at :1:14)
              IdentifierType <type>: names=['int'] (at :1:10)
          Decl <params[1]>: name=argv, quals=[], align=[], storage=[], funcspec=[] (at :1:25)
            PtrDecl <type>: quals=[] (at :1:25)
              PtrDecl <type>: quals=[] (at :1:24)
                TypeDecl <type>: declname=argv, quals=[], align=None (at :1:27)
                  IdentifierType <type>: names=['char'] (at :1:20)
        TypeDecl <type>: declname=main, quals=[], align=None (at :1:5)
          IdentifierType <type>: names=['int'] (at :1:1)
    Compound <body>:  (at :1:1)
      FuncCall <block_items[0]>:  (at :2:5)
        ID <name>: name=printf (at :2:5)
        ExprList <args>:  (at :2:12)
          Constant <exprs[0]>: type=string, value="Hello, world!\n" (at :2:12)
      Return <block_items[1]>:  (at :3:5)
        Constant <expr>: type=int, value=0 (at :3:12)

with hideemptyattrs:

ast.show(attrnames=True, hideemptyattrs=True, nodenames=True, showcoord=True):
FileAST:  (at None)
  FuncDef <ext[0]>:  (at :1:5)
    Decl <decl>: name=main (at :1:5)
      FuncDecl <type>:  (at :1:5)
        ParamList <args>:  (at :1:14)
          Decl <params[0]>: name=argc (at :1:14)
            TypeDecl <type>: declname=argc (at :1:14)
              IdentifierType <type>: names=['int'] (at :1:10)
          Decl <params[1]>: name=argv (at :1:25)
            PtrDecl <type>:  (at :1:25)
              PtrDecl <type>:  (at :1:24)
                TypeDecl <type>: declname=argv (at :1:27)
                  IdentifierType <type>: names=['char'] (at :1:20)
        TypeDecl <type>: declname=main (at :1:5)
          IdentifierType <type>: names=['int'] (at :1:1)
    Compound <body>:  (at :1:1)
      FuncCall <block_items[0]>:  (at :2:5)
        ID <name>: name=printf (at :2:5)
        ExprList <args>:  (at :2:12)
          Constant <exprs[0]>: type=string, value="Hello, world!\n" (at :2:12)
      Return <block_items[1]>:  (at :3:5)
        Constant <expr>: type=int, value=0 (at :3:12)

pass

def show(self, buf=sys.stdout, offset=0, attrnames=False, nodenames=False, showcoord=False, _my_node_name=None):
def show(self, buf=sys.stdout, offset=0, attrnames=False, hideemptyattrs=False, nodenames=False, showcoord=False, _my_node_name=None):
Copy link
Owner

Choose a reason for hiding this comment

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

In general, I think it's preferable to have positive logic as much as possible, to avoid double-negation in code and reasoning about code.

Can you make it showemptyattrs=True?

Copy link
Contributor Author

@cellularmitosis cellularmitosis Apr 4, 2025

Choose a reason for hiding this comment

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

Hmm, yeah showemptyattrs does seem easier to reason about after extracting a helper.

if self.attr_names:
    is_empty = lambda v: v is None or (hasattr(v, '__len__') and len(v) == 0)
    nvlist = [(n, getattr(self,n)) for n in self.attr_names \
                if showemptyattrs or not is_empty(getattr(self,n))]

Do you think it is worth also extracting a filter, or just leave it inline?

if self.attr_names:
    is_empty = lambda v: v is None or (hasattr(v, '__len__') and len(v) == 0)
    attr_filter = lambda v: showemptyattrs or not is_empty(v)
    nvlist = [(n, getattr(self,n)) for n in self.attr_names if attr_filter(getattr(self,n))]

buf.write(lead + self.__class__.__name__+ ': ')

if self.attr_names:
nvlist = []
Copy link
Owner

Choose a reason for hiding this comment

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

If you factor out the emptiness check into a helper function, this loop can become a simple loop comprehension again:

nvlist = [(n, getattr(self, n)) for n in self.attr_names if ...]

Or something similar

@cellularmitosis
Copy link
Contributor Author

Ok, new commit pushed.

@cellularmitosis cellularmitosis changed the title hideemptyattrs option on Node.show() showemptyattrs option on Node.show() Apr 4, 2025
@eliben eliben merged commit f04fdcd into eliben:main Apr 5, 2025
15 checks passed
@cellularmitosis
Copy link
Contributor Author

cellularmitosis commented Apr 5, 2025

Thanks for merging!

Just to have it indexed by google, I'll drop an example here about how users can patch in their own custom Node.show() implementation without forking pycparser. Here's a different show() style I came up with:

#!/usr/bin/env python3

# Demonstration of patching in an alternative Node.show() method.

import sys
from pycparser import c_parser
from pycparser.c_ast import *

def show(self, buf=sys.stdout, indent=4, showcoord=True, _my_node_name=None, _lead='', _lastcoord=None, _depth=1):
    """ Pretty print the Node and all its attributes and
        children (recursively) to a buffer.

        buf:
            Open IO buffer into which the Node is printed.

        indent:
            The number of spaces to indent at each level.
        
        showcoord:
            Do you want the coordinates of each Node to be
            displayed.

        returns the number of parens which need to be closed by the parent.
    """
    # print the node type
    s = _lead
    if _my_node_name:
        s += "%s = " % _my_node_name
    s += "%s(" % (self.__class__.__name__)
    if showcoord and self.coord:
        # only print the line number if it has changed
        coord_did_change = _lastcoord is None \
            or self.coord.file != _lastcoord.file \
            or self.coord.line != _lastcoord.line
        if coord_did_change:
            s += '  // '
            if self.coord.file:
                s += '%s ' % self.coord.file
            s += 'line %s' % self.coord.line
            _lastcoord = self.coord
    buf.write(s + '\n')

    # use dots to give visual column cues
    lead2 = _lead + '.' + (' ' * (indent-1))

    # print the attributes
    if self.attr_names:
        for name, value in [(n, getattr(self,n)) for n in self.attr_names]:
            # suppress empty fields
            if value is None:
                continue
            if hasattr(value, '__len__') and len(value) == 0:
                continue
            buf.write(lead2 + "%s = %s\n" % (name, value))

    # print the children.
    unclosed_depth = 0
    for i, (child_name, child) in enumerate(self.children()):
        is_last = i+1 == len(self.children())
        unclosed_depth = child.show(
            buf,
            indent=indent,
            _my_node_name=child_name,
            _lead=lead2,
            _lastcoord=_lastcoord,
            _depth=_depth+1
        )
        if not is_last:
            dot_spaces = ('.' + ' '*(indent-1))
            spaces_cparen = ')' + ' ' * (indent-1)
            buf.write((dot_spaces * _depth) + (spaces_cparen * unclosed_depth) + '\n')
        else:
            # let the caller close out parens after the last child
            pass

    if _depth == 1:
        # this is the final paren closing
        unclosed_depth += 1
        spaces_cparen = ')' + ' ' * (indent-1)
        buf.write((spaces_cparen * unclosed_depth) + '\n')
    else:
        # let the caller close out parens after the last child
        return unclosed_depth + 1


# patch in our custom show
Node.show = show


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.stderr.write("Error: no filename given.\n")
        sys.exit(1)

    fname = sys.argv[1]
    with open(fname, 'r') as fd:
        text = fd.read()
    parser = c_parser.CParser()
    ast = parser.parse(text)
    ast.show()

given hello.c:

int main(int argc, char** argv) {
    printf("Hello, world!\n");
    return 0;
}

the above prints the following:

FileAST(
.   ext[0] = FuncDef(  // line 1
.   .   decl = Decl(
.   .   .   name = main
.   .   .   type = FuncDecl(
.   .   .   .   args = ParamList(
.   .   .   .   .   params[0] = Decl(
.   .   .   .   .   .   name = argc
.   .   .   .   .   .   type = TypeDecl(
.   .   .   .   .   .   .   declname = argc
.   .   .   .   .   .   .   type = IdentifierType(
.   .   .   .   .   .   .   .   names = ['int']
.   .   .   .   .   )   )   )   
.   .   .   .   .   params[1] = Decl(
.   .   .   .   .   .   name = argv
.   .   .   .   .   .   type = PtrDecl(
.   .   .   .   .   .   .   type = PtrDecl(
.   .   .   .   .   .   .   .   type = TypeDecl(
.   .   .   .   .   .   .   .   .   declname = argv
.   .   .   .   .   .   .   .   .   type = IdentifierType(
.   .   .   .   .   .   .   .   .   .   names = ['char']
.   .   .   .   )   )   )   )   )   )   
.   .   .   .   type = TypeDecl(
.   .   .   .   .   declname = main
.   .   .   .   .   type = IdentifierType(
.   .   .   .   .   .   names = ['int']
.   .   )   )   )   )   
.   .   body = Compound(
.   .   .   block_items[0] = FuncCall(  // line 2
.   .   .   .   name = ID(
.   .   .   .   .   name = printf
.   .   .   .   )   
.   .   .   .   args = ExprList(
.   .   .   .   .   exprs[0] = Constant(
.   .   .   .   .   .   type = string
.   .   .   .   .   .   value = "Hello, world!\n"
.   .   .   )   )   )   
.   .   .   block_items[1] = Return(  // line 3
.   .   .   .   expr = Constant(
.   .   .   .   .   type = int
.   .   .   .   .   value = 0
)   )   )   )   )   

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants