Python script for generating random C code

Posted on

Problem

I’m writing a Python script to randomly generate C functions. This is my first time using classes in Python and I’m basically doing my best to pretend I’m using Java.

I’m looking for a review of my OOP, Python style, and in general whether or not I’m doing things in the “Pythonic” way, whatever that means.

import random
import sys
import string

# writes text to a stored outputString
# fetch the outputString later for outputting
class Output:
    def __init__(self):
        self.clear()

    def clear(self):
        self.outputString = ''
        self.__lineStart = True
        self.lineCount = 0
        self.nestingLevel = 0           

    def __write(self, str):
        self.outputString += str    

    def line(self):
        self.__write('n')
        self.__lineStart = True
        self.lineCount += 1

    def output(self, str):
        # start each line with tabs equal to nesting level      
        if(self.__lineStart == True):
            for q in range(0, self.nestingLevel):
                self.__write('t')
            self.__lineStart = False

        self.__write(str)

    def incNesting(self):
        self.nestingLevel += 1

    def decNesting(self):
        self.nestingLevel -= 1

class Variables:
    def __init__(self, varCount):
        if varCount > 26:
            raise Exception('Don't you think you're being a bit too aggressive?')
        self.varCount = varCount
        lowercase = list(string.ascii_lowercase)
        self.variables = lowercase[0:varCount]

    def Rand(self, count = 1):
        if count > self.varCount:
            raise Exception('Variables.Rand requested more variables than available')
        elif count == 1:
            return random.choice(self.variables)
        elif count > 1:
            return random.sample(self.variables, count)
        else:
            raise Exception('Variables.Rand invalid argument')

    def InitStatement(self, out):
        out.output('int ')
        vars = []
        for var in self.variables:
            vars.append('%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF)))
        out.output(', '.join(vars))
        out.output(';')
        out.line()      

class Action:
    def __init__(self, variables, output):
        self.variables = variables
        self.out = output
        self.labelCount = 0

class ControlSelector:
    @staticmethod
    def RandomControl(output, variables):
        controlType = random.choice(ControlType.implementedTypes)
        return ControlType.Make(controlType, output, variables)

class ControlType:
    STATEMENT = 1
    CONDITIONAL = 2
    GOTO = 3
    CALLGEN = 4
    CALLREAL = 5
    CALLAPI = 6
    STOP = 7
    allTypes = [STATEMENT, CONDITIONAL, GOTO, CALLGEN, CALLREAL, CALLAPI, STOP]
    implementedTypes = [STATEMENT, CONDITIONAL, STOP]

    @staticmethod
    def Make(controlType, output, variables): # factory pattern .. I think
        if(controlType ==  ControlType.STATEMENT):
            return StatementControl(output, variables)
        elif(controlType ==  ControlType.CONDITIONAL):
            return ConditionalControl(output, variables)
        elif(controlType ==  ControlType.STOP):
            return StopControl(output, variables)
        else:
            raise Exception('ControlType.Make bad argument')

    def __init__(self, output, variables):
        self.out = output
        self.variables = variables

    def Exec(self):
        raise NotImplementedError("")

    def PutCode(self):  
        control = ControlSelector.RandomControl(self.out, self.variables)

        control.Exec()

class PutCodeControl(ControlType):
    def Exec(self):
        self.PutCode()

class StatementControl(ControlType):
    def PutAssignment(self):
        dest, src = self.variables.Rand(2)
        self.out.output(src + ' = ' + dest + ';')
        self.out.line()     

    def Exec(self):
        self.PutAssignment()
        self.PutCode()

class ConditionalControl(ControlType):
    def PutConditional(self):
        first, second = self.variables.Rand(2)
        self.out.output('if(')
        self.out.output(first + ' == ' + second)
        self.out.output('){')
        self.out.line()     

    def PutCloseBrace(self):
        self.out.output('}')
        self.out.line()

    def PutIf(self):
        self.PutConditional()
        self.out.incNesting()
        self.PutCode()
        self.PutCloseBrace()
        self.out.decNesting()   

    def Exec(self):
        self.PutIf()
        self.PutCode()

class StopControl(ControlType):
    def Exec(self):
        return

class Function:
    totalFuncCounter = 0

    def __init__(self):
        self.funcNum = Function.totalFuncCounter
        Function.totalFuncCounter += 1

        self.variables = Variables(8)
        self.out = Output()
        self.name = 'RandFunc' + str(self.funcNum)

        self.action = Action(self.variables, self.out)

    def __FunctionHeader(self):
        self.out.output('int ' + self.name + '()')
        self.out.line()
        self.out.output('{')
        self.out.line()

    def __FunctionFooter(self):
        retval = self.variables.Rand()
        self.out.output('return ' + retval + ';')
        self.out.line()
        self.out.output('}')
        self.out.line()     

    def __Generate(self):       
        # int functionname()
        # {
        self.__FunctionHeader()

        # int a = 1, b = 2, etc
        self.variables.InitStatement(self.out)

        # [function body]
        PutCodeControl(self.out, self.variables).Exec()

        # return a;
        self.__FunctionFooter()

    def Generate(self, minLines = None):
        if minLines == None:
            minLines = 1 # < 1 will cause errors

        while self.out.lineCount < minLines:
            self.out.clear()
            self.__Generate()

    def OutputString(self):
        return self.out.outputString

def PrintLongFunction():
    func = Function()
    func.Generate(minLines = 50)
    sys.stdout.write(func.OutputString())

if __name__ == "__main__":
    PrintLongFunction()

Sample output is available here.

Solution

Welcome to python 🙂

pep8

If you want to go “pythonic” you may want to read pep8. It is the common coding guideline in the world of python.

Python Version

I assume you are writing python3 (your code works in 2.x and 3.x). If you are targeting 2.x you should add explicit inheritance from object. (keyword: new-style classes)

Doc-Strings

Doc-strings (the python equivalent to java-doc comments) are strings inside the block you comment:

class Output:
    """writes text to a stored outputString
       fetch the outputString later for outputting"""

underscores

def __write(self, str):
    self.outputString += str

In your use case there is no reason to prefix functions with __. It is not just a convention but does have a meaning for the interpreter as well.

shadowing built-in

Don’t use str as variable name. There is a built-in type called str.

ControlSelector.RandomControl

If you want a function you make a function – not a method. There is no reason for having the class ControlSelector.

def random_control(output, variables):
    controlType = random.choice(ControlType.implementedTypes)
    return ControlType.Make(controlType, output, variables)

minLines == None

The line

if minLines == None:

was suggested to be replaced with

if not minLines:

be aware that this changes the behavior in case of Generate(minLines=0) since if not 0 is True while 0 == None evaluates to False. If you want to check for None use is:

if minLine is None:

The __main__ pattern

if __name__ == "__main__":
    PrintLongFunction()

Awesome. Using the __name__ == "__main__" pattern is something you should get used to. Great start.

In class Output:

def output(self, str):
    # start each line with tabs equal to nesting level      
    if(self.__lineStart == True):
        for q in range(0, self.nestingLevel):
            self.__write('t')
        self.__lineStart = False

    self.__write(str)

can be rewritten as

def output(self, str):
    if self.__lineStart:
        self.__write('t' * self.nestingLevel)
        self.__lineStart = False

    self.__write(str)

You thus remove the useless parenthesis of if condition and make the code shorter and cleaner.

You also can rewrite

Exception('Don't you think you're being a bit too aggressive?') 

as

Exception("Don't you think you're being a bit too aggressive?")

Some list comprehension :

vars = []
for var in self.variables:
  vars.append('%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF)))

is equivalent to

vars = ['%s = %i' % (var, random.randint(-0xFFFFFFF, 0xFFFFFFF))
          for var in self.variables]

Replace

if minLines == None:

with

if not minLines:

Leave a Reply

Your email address will not be published. Required fields are marked *