2023-11-18 10:59:00 -07:00
import string
2023-11-24 09:31:18 -07:00
import warnings
2023-11-17 16:57:53 -07:00
2023-11-21 15:38:39 -07:00
from english_words import get_english_words_set
2023-11-17 16:57:53 -07:00
2023-11-25 11:40:46 -07:00
from ast_generator . tiny_py_unparse import TinyPyUnparser
2023-11-23 12:51:13 -07:00
from ast_generator . utils import *
2023-11-23 13:21:43 -07:00
from ast_generator . utils import filter_options , _choose_option
2023-11-18 12:21:52 -07:00
from constants import *
2023-11-17 16:57:53 -07:00
2023-11-22 13:50:56 -07:00
import keyword
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
class AstGenerator :
"""
Generates an AST from a grammar based on given settings
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
Originally the intention was to use the ISLa library to generate
the AST , however I found that ISLa is like taking a buldozer to
a sledgehammer ' s job, so I decided to write a procedural generator
instead .
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
The way we select elements is we take all the settings in their
category and assign them a range on a number line . Then we
pick a random number in that range and whichever category it
falls into will be selected .
"""
2023-11-17 16:57:53 -07:00
2023-11-23 13:01:13 -07:00
### INITIALIZATION ###
2023-11-18 10:59:00 -07:00
def __init__ ( self , settings : dict ) :
"""
This class is designed to get the settings from some wrapper class that
better defines the precise constraints of the language being generated
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
the necessary settings are in the . yaml file and #TODO this is not generalizable yet
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
@param settings : settings for weights and probabilities and lengths
"""
self . settings = settings
2023-11-17 16:57:53 -07:00
2023-11-22 13:50:56 -07:00
self . symbol_table = [ ]
2023-11-18 10:59:00 -07:00
global_scope = Scope ( None , None )
self . symbol_table . append ( global_scope ) # NOTE for debug
self . current_scope = global_scope
2023-11-17 16:57:53 -07:00
2023-11-23 12:51:13 -07:00
self . _init_names ( )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . ast : ET . Element or None = None
self . current_ast_element : ET . Element or None = None
self . current_nesting_depth = 0
self . current_control_flow_nesting_depth = 0
2023-11-17 16:57:53 -07:00
2023-11-23 12:51:13 -07:00
self . _init_numlines ( )
def _init_numlines ( self ) :
2023-11-21 20:40:50 -07:00
# Numberlines - For computing probabilities
self . int_op_options , self . int_op_cutoffs , self . int_op_numline = (
2023-11-23 12:51:13 -07:00
get_numberlines ( ' expression-weights ' , [ ' brackets ' , ' arithmetic ' , ' unary ' ] , [ [ ] , [ ] , [ ' not ' ] ] ,
self . settings ) )
2023-11-21 20:40:50 -07:00
self . int_unary = [ ' negation ' , ' noop ' ]
2023-11-21 21:39:04 -07:00
self . bool_op_options , self . bool_op_cutoffs , self . bool_op_numline = (
2023-11-23 12:51:13 -07:00
get_numberlines ( ' expression-weights ' , [ ' brackets ' , ' comparison ' , ' logical ' , ' unary ' ] ,
excluded_values = [ [ ] , [ ' less-than-or-equal ' , ' greater-than-or-equal ' , ' less-than ' ,
2023-11-23 13:01:13 -07:00
' greater-than ' ] , [ ] , [ ' noop ' , ' negation ' ] ] ,
2023-11-23 12:51:13 -07:00
settings = self . settings ) )
2023-11-21 21:39:04 -07:00
self . bool_unary = [ ' not ' ]
self . float_op_options , self . float_op_cutoffs , self . float_op_numline = (
2023-11-23 12:51:13 -07:00
get_numberlines ( ' expression-weights ' , [ ' brackets ' , ' arithmetic ' , ' unary ' ] , [ [ ] , [ ] , [ ' not ' ] ] ,
self . settings ) )
2023-11-21 21:39:04 -07:00
self . float_unary = [ ' negation ' , ' noop ' ]
self . char_op_options , self . char_op_cutoffs , self . char_op_numline = (
2023-11-23 12:51:13 -07:00
get_numberlines ( ' expression-weights ' , [ ' brackets ' , ' comparison ' ] ,
[ [ ] , [ ' less-than ' , ' greater-than ' , ' less-than-or-equal ' , ' greater-than-or-equal ' ] ] ,
self . settings ) )
2023-11-21 21:39:04 -07:00
self . comp_op_options , self . comp_op_cutoffs , self . comp_op_numline = (
2023-11-23 12:51:13 -07:00
get_numberlines ( ' expression-weights ' , [ ' brackets ' , ' comparison ' ] , [ [ ] , [ ] ] , self . settings ) )
2023-11-24 07:33:30 -07:00
self . type_options , self . type_cutoffs , self . type_numline = (
get_numberlines ( ' type-weights ' , [ ' composite ' , ' atomic ' ] , [ [ ] , [ ] ] , self . settings ) )
self . atomic_type_options , self . atomic_type_cutoffs , self . atomic_type_numline = (
get_numberlines ( ' type-weights ' , [ ' atomic-types ' ] , [ [ ] ] , self . settings ) )
self . composite_type_options , self . composite_type_cutoffs , self . composite_type_numline = (
get_numberlines ( ' type-weights ' , [ ' composite-types ' ] , [ [ ] ] , self . settings ) )
2023-11-23 12:51:13 -07:00
def _init_names ( self ) :
names = get_english_words_set ( [ ' web2 ' ] , alpha = True )
2023-11-24 09:31:18 -07:00
possible_names = filter ( lambda x : ( self . settings [ ' properties ' ] [ ' id-length ' ] [ ' max ' ] < = len ( x ) < =
self . settings [ ' properties ' ] [ ' id-length ' ] [ ' max ' ] ) and not keyword . iskeyword ( x ) ,
2023-11-23 12:51:13 -07:00
names )
var_name_list = list ( possible_names )
var_name_len = len ( var_name_list )
self . variable_names = var_name_list [ 0 : var_name_len / / 2 ]
self . routine_names = var_name_list [ var_name_len / / 2 : var_name_len ]
2023-11-21 20:40:50 -07:00
2023-11-23 13:01:13 -07:00
### GENERATION ###
2023-11-18 10:59:00 -07:00
def generate_ast ( self ) :
"""
@brief generates an AST from a grammar
"""
self . generate_top_level_block ( )
2023-11-23 12:51:13 -07:00
def generate_top_level_block ( self ) :
"""
@brief creates the top - level block containing the whole program
"""
element = self . make_element ( GAZ_BLOCK_TAG , [ ] )
2023-11-18 10:59:00 -07:00
self . ast = element
2023-11-23 12:51:13 -07:00
for i in range ( random . randint ( 0 , self . settings [ ' generation-options ' ] [ ' max-globals ' ] ) ) :
self . generate_global ( )
for i in range ( self . settings [ ' generation-options ' ] [ ' max-number-of-routines ' ] ) :
2023-11-18 10:59:00 -07:00
if random . random ( ) < self . settings [ ' block-termination-probability ' ] :
break
self . generate_routine ( )
2023-11-23 08:36:02 -07:00
self . generate_main ( )
2023-11-23 12:51:13 -07:00
pass
2023-11-23 08:36:02 -07:00
2023-11-18 10:59:00 -07:00
def generate_main ( self ) :
main_args = [ # TODO refactor these into constants
2023-11-23 12:51:13 -07:00
( GAZ_NAME_KEY , " main " ) ,
( GAZ_RETURN_KEY , GAZ_INT_KEY ) ,
2023-11-18 10:59:00 -07:00
]
2023-11-23 12:51:13 -07:00
parent = self . make_scoped_element ( GAZ_PROCEDURE_TAG , main_args )
2023-11-23 08:36:02 -07:00
self . generate_block ( return_stmt = True , return_value = " 0 " , return_type = GAZ_INT_KEY , block_type = GAZ_PROCEDURE_TAG )
2023-11-23 12:51:13 -07:00
self . exit_scoped_element ( parent )
2023-11-18 10:59:00 -07:00
2023-11-22 13:50:56 -07:00
def generate_block ( self , tag = None , return_stmt = False , return_value = None , return_type = None , block_type = None ,
loop_var = None ) :
# TODO this should be broken into many functions depending on the block requirements
2023-11-18 10:59:00 -07:00
if tag is None :
tag = [ ]
parent = self . current_ast_element
self . push_scope ( )
element = build_xml_element ( tag , name = GAZ_BLOCK_TAG )
self . current_ast_element . append ( element )
self . current_ast_element = element
2023-11-22 13:50:56 -07:00
# Generate the loop condition increment if we are in a loop
if block_type == GAZ_LOOP_TAG :
self . generate_loop_condition_check ( loop_var )
self . generate_loop_condition_increment ( loop_var )
2023-11-25 14:15:07 -07:00
if block_type not in [ GAZ_PROCEDURE_TAG , GAZ_FUNCTION_TAG ] :
self . generate_statements ( )
else :
self . generate_statements ( include = ' declaration ' )
self . generate_statements ( exclude = ' declaration ' )
2023-11-18 10:59:00 -07:00
if return_stmt :
self . generate_return ( return_type = return_type , return_value = return_value )
if self . settings [ ' generation-options ' ] [ ' generate-dead-code ' ] :
2023-11-23 08:36:02 -07:00
self . generate_statements ( exclude = ' declaration ' )
2023-11-24 15:45:38 -07:00
self . print_all_current_scope_vars ( )
2023-11-18 10:59:00 -07:00
self . pop_scope ( )
self . current_ast_element = parent
def generate_return ( self , return_type = None , return_value = None ) :
2023-11-23 13:11:46 -07:00
"""
@brief generates a return statement
@param return_type : the type to be returned ( if None - > any )
@param return_value : value to be returned ( if None - > expr [ return_type ] )
"""
2023-11-18 10:59:00 -07:00
if return_type is None or return_type == GAZ_VOID_TYPE :
2023-11-23 13:01:13 -07:00
self . current_ast_element . append ( self . make_element ( GAZ_RETURN_TAG , [ ] ) )
2023-11-18 10:59:00 -07:00
else :
2023-11-23 13:11:46 -07:00
# store the parent
2023-11-23 13:01:13 -07:00
parent = self . current_ast_element
2023-11-23 13:11:46 -07:00
# initialize element
keys = [ ( " type " , return_type ) ]
self . make_element ( GAZ_RETURN_TAG , keys )
# make either a literal or a random expression based on choice
2023-11-18 10:59:00 -07:00
if return_value is None :
self . generate_expression ( return_type )
else :
2023-11-24 06:55:12 -07:00
self . generate_literal ( return_type , return_value )
2023-11-23 13:01:13 -07:00
2023-11-23 13:11:46 -07:00
# return to the parent
2023-11-23 13:01:13 -07:00
self . current_ast_element = parent
2023-11-18 10:59:00 -07:00
def generate_routine ( self , routine_type = None ) :
2023-11-23 13:11:46 -07:00
"""
@brief generate a random routine
@param return_type : the type to be returned ( if None - > any ( including void ) )
"""
2023-11-18 10:59:00 -07:00
if routine_type is None :
2023-11-23 13:11:46 -07:00
routine_type = self . get_routine_type ( ) # get a random type
2023-11-18 10:59:00 -07:00
else :
2023-11-23 13:11:46 -07:00
pass
2023-11-18 10:59:00 -07:00
2023-11-23 13:11:46 -07:00
# initialize random variables
2023-11-18 10:59:00 -07:00
args = self . generate_routine_args ( )
name = self . get_name ( routine_type )
return_type = self . get_type ( routine_type )
2023-11-23 13:11:46 -07:00
# initialize the routine
2023-11-18 10:59:00 -07:00
routine = Routine ( name , routine_type , return_type , args )
routine_args = [
( " name " , routine . name ) ,
( " return_type " , routine . return_type ) ,
]
2023-11-23 13:11:46 -07:00
# Generation
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-23 13:11:46 -07:00
self . make_scoped_element ( routine . type , routine_args )
2023-11-18 10:59:00 -07:00
self . define_args ( routine . arguments )
self . generate_block ( return_stmt = True , return_type = routine . return_type )
2023-11-23 13:11:46 -07:00
self . exit_scoped_element ( parent )
2023-11-18 10:59:00 -07:00
def define_args ( self , args ) :
2023-11-23 13:11:46 -07:00
"""
@brief Generate the argument tags in a routine
@param args : a list of arguments
"""
2023-11-18 10:59:00 -07:00
for arg in args :
self . current_ast_element . append ( arg . xml )
self . current_scope . append ( arg . name , arg )
2023-11-23 08:36:02 -07:00
def generate_statements ( self , include = None , exclude = None ) :
opts = [ ' declaration ' , ' routine_call ' , ' conditional ' , ' loop ' , ' assignment ' , ' out_stream ' , ' in_stream ' ]
2023-11-18 10:59:00 -07:00
# Number line
2023-11-22 13:50:56 -07:00
number_line = 180 # TODO fix the numberline stuff to reflect the settings
2023-11-18 20:13:15 -07:00
cutoffs = [ 10 , 30 , 50 , 80 , 100 , 140 , 180 ]
2023-11-18 10:59:00 -07:00
options = {
0 : self . generate_declaration ,
1 : self . generate_routine_call ,
2 : self . generate_conditional ,
3 : self . generate_loop ,
4 : self . generate_assignment ,
5 : self . generate_out_stream ,
6 : self . generate_in_stream ,
}
2023-11-23 13:21:43 -07:00
# Filter unwanted options
filter_options ( exclude , include , options , opts )
2023-11-23 08:36:02 -07:00
2023-11-23 13:21:43 -07:00
# Generate the statements
self . _generate_from_options ( cutoffs , number_line , options )
2023-11-18 10:59:00 -07:00
2023-11-21 21:39:04 -07:00
def _generate_expression ( self , expr_type : list [ str ] , number_line ,
cutoffs , options , unary = None , comparison : bool = False ) :
2023-11-23 13:40:20 -07:00
"""
@brief Generate an expression
@param expr_type : a list of types to be used
@param number_line : number line for probability computation
@param cutoffs : cutoffs to be used
@param options : options to be used
@param unary : a list of unary operators in options
"""
2023-11-18 10:59:00 -07:00
if unary is None :
unary = [ ]
parent = self . current_ast_element
self . current_nesting_depth + = 1
2023-11-23 13:40:20 -07:00
# Check the expression depth against settings
2023-11-18 10:59:00 -07:00
if self . current_nesting_depth > self . settings [ ' generation-options ' ] [ ' max-nesting-depth ' ] or random . random ( ) < \
self . settings [ ' block-termination-probability ' ] :
2023-11-21 21:39:04 -07:00
self . generate_literal ( random . choice ( expr_type ) )
2023-11-18 10:59:00 -07:00
self . current_nesting_depth - = 1
return
2023-11-23 13:40:20 -07:00
# Generate
2023-11-23 13:21:43 -07:00
op = _choose_option ( cutoffs , number_line , options )
self . _generate_expr ( comparison , expr_type , op , unary )
2023-11-18 10:59:00 -07:00
2023-11-23 13:40:20 -07:00
# Return to parent
2023-11-18 10:59:00 -07:00
self . current_nesting_depth - = 1
self . current_ast_element = parent
2023-11-23 13:40:20 -07:00
def generate_declaration ( self , mut = None ) : # TODO change this to a bool
"""
@brief Generate a declaration
@param mut : the mutability of the variable ( ' const ' or ' var ' )
"""
# Initialize the variable
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
decl_type = self . get_type ( GAZ_VAR_TAG )
decl_args = [
( " type " , decl_type ) ,
]
2023-11-23 13:40:20 -07:00
self . make_element ( GAZ_DECLARATION_TAG , decl_args )
2023-11-18 10:59:00 -07:00
2023-11-23 13:40:20 -07:00
# Generate the variable
2023-11-19 16:52:14 -07:00
variable = self . generate_variable ( decl_type , mut = mut )
2023-11-18 10:59:00 -07:00
self . current_ast_element . append ( variable . xml )
2023-11-23 13:40:20 -07:00
self . current_scope . append ( variable . name , variable ) # make sure the variable is in scope
2023-11-18 10:59:00 -07:00
2023-11-23 13:40:20 -07:00
# Generate the initialization of the variable
self . generate_xhs ( GAZ_RHS_TAG , decl_type )
# Return to parent
2023-11-18 10:59:00 -07:00
self . current_ast_element = parent
def generate_binary ( self , op , op_type ) :
2023-11-23 13:40:20 -07:00
"""
@brief Generate a binary operation
@param op : the operator
@param op_type : the type of the expression
"""
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-23 13:40:20 -07:00
# Check if the operator is valid
2023-11-19 16:13:08 -07:00
if op == " " :
raise ValueError ( " op is empty! " )
2023-11-18 10:59:00 -07:00
args = [
( " op " , op ) ,
( " type " , op_type ) ,
]
2023-11-23 13:40:20 -07:00
self . make_element ( GAZ_OPERATOR_TAG , args )
2023-11-18 10:59:00 -07:00
2023-11-23 13:40:20 -07:00
# Gnereate lhs and rhs
2023-11-18 10:59:00 -07:00
self . generate_xhs ( GAZ_LHS_TAG , op_type )
self . generate_xhs ( GAZ_RHS_TAG , op_type )
2023-11-23 13:40:20 -07:00
# Return to parent
2023-11-18 10:59:00 -07:00
self . current_ast_element = parent
2023-11-21 20:40:50 -07:00
def generate_bracket ( self , op_type ) :
2023-11-23 13:40:20 -07:00
"""
@brief Generate a bracket operation
@param op_type : the type of the expression
"""
2023-11-21 20:40:50 -07:00
parent = self . current_ast_element
2023-11-23 13:40:20 -07:00
args = [ ( " type " , op_type ) ]
self . make_element ( GAZ_BRACKET_TAG , args )
# Generate the expression in the brackets
2023-11-21 20:40:50 -07:00
self . generate_xhs ( GAZ_RHS_TAG , op_type )
2023-11-23 13:40:20 -07:00
# Return to parent
2023-11-21 20:40:50 -07:00
self . current_ast_element = parent
2023-11-22 13:50:56 -07:00
def generate_xhs ( self , handedness , op_type , is_zero = False ) :
2023-11-23 13:40:20 -07:00
"""
@brief generate a lhs or a rhs depending on handedness
@param handedness : the handedness
@param op_type : the type of the expression
@param is_zero : if the expression is zero
"""
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-23 13:40:20 -07:00
self . make_element ( handedness , [ ] )
2023-11-18 10:59:00 -07:00
2023-11-22 13:50:56 -07:00
self . generate_expression ( op_type , is_zero = is_zero )
2023-11-18 10:59:00 -07:00
self . current_ast_element = parent
def generate_unary ( self , op , op_type = ANY_TYPE ) :
2023-11-24 06:31:39 -07:00
"""
@brief Generate a unary operation
@param op_type : the type of the expression
"""
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
args = [
( " op " , op ) ,
( " type " , op_type ) ,
]
2023-11-24 06:31:39 -07:00
self . make_element ( GAZ_UNARY_OPERATOR_TAG , args )
2023-11-18 10:59:00 -07:00
self . generate_xhs ( GAZ_RHS_TAG , op_type )
self . current_ast_element = parent
2023-11-24 06:55:12 -07:00
def generate_routine_call ( self ) : # we should generate a test case with arbitrary number of args
2023-11-18 10:59:00 -07:00
pass
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
def generate_conditional ( self ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a conditional statement
@effects : modifies the current_ast_element
@return : None
"""
2023-11-18 10:59:00 -07:00
if self . current_control_flow_nesting_depth > = self . settings [ ' generation-options ' ] [ ' max-nesting-depth ' ] :
return
2023-11-17 16:57:53 -07:00
2023-11-22 13:50:56 -07:00
if self . current_control_flow_nesting_depth > 0 and random . random ( ) < self . settings [
' block-termination-probability ' ] :
2023-11-18 10:59:00 -07:00
return
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
self . make_scoped_element ( GAZ_IF_TAG , [ ] )
2023-11-18 10:59:00 -07:00
self . current_control_flow_nesting_depth + = 1
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . generate_expression ( GAZ_BOOL_KEY )
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
self . generate_block ( tag = [ ( " type " , GAZ_TRUE_BLOCK_TAG ) ] ) # FIXME this inhibits elif blocks
2023-11-18 10:59:00 -07:00
self . generate_block ( tag = [ ( " type " , GAZ_FALSE_BLOCK_TAG ) ] )
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
self . current_control_flow_nesting_depth - = 1
self . exit_scoped_element ( parent )
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
def generate_loop ( self ) :
"""
@brief generate a loop
@return : None
"""
2023-11-22 13:50:56 -07:00
# FIXME make sure that loop conditions are evaluated at least once (assert true or make a config param)
2023-11-18 10:59:00 -07:00
if self . current_control_flow_nesting_depth > = self . settings [ ' generation-options ' ] [ ' max-nesting-depth ' ] :
return
2023-11-17 16:57:53 -07:00
2023-11-22 13:50:56 -07:00
if self . current_control_flow_nesting_depth > 0 and random . random ( ) < self . settings [
' block-termination-probability ' ] :
2023-11-18 10:59:00 -07:00
return
2023-11-17 16:57:53 -07:00
2023-11-22 13:50:56 -07:00
init_var = self . generate_zero_declaration ( )
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
self . make_scoped_element ( GAZ_LOOP_TAG , [ ] )
2023-11-18 10:59:00 -07:00
self . current_control_flow_nesting_depth + = 1
2023-11-24 06:55:12 -07:00
2023-11-25 11:40:46 -07:00
truthiness = self . get_truthiness ( )
while True :
self . generate_expression ( GAZ_BOOL_KEY ) # the loop entry condition #TODO force true, if false, generate a simple block and return, no need to nest code we won't use
iterator = self . current_ast_element . iter ( )
next ( iterator )
conditional = next ( iterator )
if not self . truth_check ( conditional , truthiness ) :
self . current_ast_element . remove ( conditional )
else :
break
2023-11-22 13:50:56 -07:00
self . generate_block ( block_type = GAZ_LOOP_TAG ,
loop_var = init_var ) # append a variable increment and prepend a break statement if var is > max loop iterations
2023-11-24 06:55:12 -07:00
self . current_control_flow_nesting_depth - = 1
self . exit_scoped_element ( parent )
2023-11-17 16:57:53 -07:00
2023-11-25 11:40:46 -07:00
def get_truthiness ( self ) :
return random . random ( ) < self . settings [ ' misc-weights ' ] [ ' truthiness ' ]
def truth_check ( self , element : ET . Element , truthiness : bool ) :
# evaluate the element
uncompiled = self . unparse_conditional_statement ( element )
substr1 = uncompiled . find ( " ** " )
substr2 = uncompiled . find ( " ** " , substr1 + 1 )
if - 1 not in [ substr1 , substr2 ] :
return False
# print(uncompiled)
try :
res = eval ( uncompiled )
except ( OverflowError , ZeroDivisionError ) :
res = False
# check it against truthiness and return the comparison
return res == truthiness
def unparse_conditional_statement ( self , element : ET . Element ) :
py_unparse = TinyPyUnparser ( element , True )
unparsed = " "
if element . tag == GAZ_LIT_TAG :
py_unparse . unparse_literal ( element )
unparsed = py_unparse . source
elif element . tag == GAZ_OPERATOR_TAG :
py_unparse . unparse_operator ( element )
unparsed = py_unparse . source
elif element . tag == GAZ_VAR_TAG :
xml = self . current_scope . resolve ( element . get ( " name " ) )
py_unparse . unparse_variable ( xml )
unparsed = py_unparse . source
elif element . tag == GAZ_BRACKET_TAG :
py_unparse . unparse_brackets ( element )
unparsed = py_unparse . source
else :
raise ValueError ( " Unknown element type for conditional argument: " + element . tag )
return unparsed
2023-11-22 13:50:56 -07:00
def generate_zero_declaration ( self ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a declaration int a = 0 for some a
@return : None
"""
2023-11-22 13:50:56 -07:00
parent = self . current_ast_element
2023-11-24 06:55:12 -07:00
self . make_element ( GAZ_DECLARATION_TAG , [ ] )
2023-11-22 13:50:56 -07:00
2023-11-24 06:55:12 -07:00
# Initialize variable
2023-11-22 13:50:56 -07:00
variable = self . generate_variable ( GAZ_INT_KEY , ' var ' )
self . current_ast_element . append ( variable . xml )
self . current_scope . append ( variable . name , variable )
self . generate_xhs ( GAZ_RHS_TAG , variable . type , is_zero = True )
2023-11-24 06:55:12 -07:00
2023-11-22 13:50:56 -07:00
self . current_ast_element = parent
return variable
2023-11-18 10:59:00 -07:00
def generate_assignment ( self ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate an assignment
@return : None
"""
2023-11-19 16:52:14 -07:00
possible_vars = self . current_scope . get_all_defined_mutable_vars ( )
if len ( possible_vars ) == 0 :
raise ValueError ( " No possible variables to assign to! " )
2023-11-18 10:59:00 -07:00
# same structure as a declaration
parent = self . current_ast_element
2023-11-17 16:57:53 -07:00
2023-11-24 06:55:12 -07:00
self . make_element ( GAZ_ASSIGNMENT_TAG , [ ] )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
variable = random . choice ( possible_vars )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . current_ast_element . append ( variable . xml )
self . generate_xhs ( GAZ_RHS_TAG , variable . type )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . current_ast_element = parent
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
def generate_out_stream ( self ) :
self . generate_stream ( GAZ_OUT_STREAM )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
def generate_in_stream ( self ) :
self . generate_stream ( GAZ_IN_STREAM )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
def generate_stream ( self , stream_type ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a stream statment from a stream type
@param stream_type : whether the stream is an input or output
@return :
"""
2023-11-18 10:59:00 -07:00
parent = self . current_ast_element
2023-11-24 06:55:12 -07:00
2023-11-18 10:59:00 -07:00
args = [
( " type " , stream_type ) ,
]
2023-11-24 06:55:12 -07:00
self . make_element ( GAZ_STREAM_TAG , args )
2023-11-18 10:59:00 -07:00
self . generate_expression ( ANY_TYPE )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . current_ast_element = parent
2023-11-17 16:57:53 -07:00
2023-11-19 16:52:14 -07:00
def generate_variable ( self , var_type : str , mut = None ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a variable
@param var_type : they type of the variable
@param mut : mutability of the variable
@return : None
"""
2023-11-19 16:52:14 -07:00
if mut is None :
return Variable ( self . get_name ( GAZ_VAR_TAG ) , var_type , self . get_qualifier ( ) )
else :
return Variable ( self . get_name ( GAZ_VAR_TAG ) , var_type , mut )
2023-11-22 13:50:56 -07:00
def generate_literal ( self , var_type : str , value = None ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a literal
@param var_type : Type of the literal
@param value : optional value of the literal
@return : None
"""
2023-11-22 13:50:56 -07:00
if value is None :
value = self . get_value ( var_type )
else :
value = value
2023-11-24 06:55:12 -07:00
2023-11-18 10:59:00 -07:00
args = [
( " type " , var_type ) ,
2023-11-22 13:50:56 -07:00
( " value " , str ( value ) ) ,
2023-11-18 10:59:00 -07:00
]
element = build_xml_element ( args , name = GAZ_LIT_TAG )
self . current_ast_element . append ( element )
2023-11-24 06:55:12 -07:00
def make_literal ( self , type , value ) : # TODO eliminate this function
2023-11-18 10:59:00 -07:00
args = [
( " type " , type ) ,
( " value " , value ) ,
]
element = build_xml_element ( args , name = GAZ_LIT_TAG )
return element
def generate_global ( self ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate a global const declaration
@return : None
"""
2023-11-18 10:59:00 -07:00
current_scope = self . current_scope
current_element = self . current_ast_element
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . current_scope = self . current_scope . get_top_scope ( )
self . current_ast_element = self . ast
2023-11-17 16:57:53 -07:00
2023-11-20 20:28:55 -07:00
self . generate_declaration ( mut = ' const ' )
2023-11-17 16:57:53 -07:00
2023-11-18 10:59:00 -07:00
self . current_scope = current_scope
self . current_ast_element = current_element
2023-11-22 13:50:56 -07:00
def generate_expression ( self , expr_type : str , is_zero = False ) :
2023-11-24 06:55:12 -07:00
"""
@brief generate an expression
@param expr_type : the type of the expression
@param is_zero : if the expression should eval to 0
@return : None
"""
2023-11-22 13:50:56 -07:00
if is_zero :
self . generate_literal ( expr_type , value = 0 )
return
elif expr_type == GAZ_INT_KEY or expr_type == GAZ_FLOAT_KEY :
2023-11-21 21:39:04 -07:00
self . generate_int_expr ( )
2023-11-18 10:59:00 -07:00
elif expr_type == GAZ_BOOL_KEY :
2023-11-21 23:03:12 -07:00
if random . random ( ) < 0.5 :
self . generate_bool_expr ( )
else :
self . generate_comp_expr ( )
elif expr_type == GAZ_CHAR_KEY :
self . generate_char_expr ( )
2023-11-24 07:33:30 -07:00
elif expr_type == GAZ_FLOAT_KEY :
self . generate_float_expr ( )
2023-11-18 10:59:00 -07:00
elif expr_type == ANY_TYPE : # TODO implement the choice of any type
2023-11-24 06:55:12 -07:00
ty = self . get_type ( GAZ_RHS_TAG )
self . generate_expression ( ty )
2023-11-18 10:59:00 -07:00
else :
raise NotImplementedError ( f " Expression type { expr_type } not implemented " )
2023-11-24 06:55:12 -07:00
def generate_routine_args ( self ) - > list [ Argument ] :
"""
@brief generate a list of arguments for a routine
@return : a list of arguments
"""
2023-11-18 10:59:00 -07:00
number = random . randint ( self . settings [ ' properties ' ] [ ' number-of-arguments ' ] [ ' min ' ] ,
self . settings [ ' properties ' ] [ ' number-of-arguments ' ] [ ' max ' ] )
args = [ ]
for i in range ( number ) :
arg = self . generate_arg ( )
args . append ( arg )
self . current_scope . append ( arg . name , arg )
return args
def generate_arg ( self ) :
return Argument ( self . get_name ( GAZ_VAR_TAG ) , self . get_type ( GAZ_VAR_TAG ) )
2023-11-23 13:40:20 -07:00
def generate_int_expr ( self ) :
self . _generate_expression ( [ GAZ_INT_KEY ] ,
self . int_op_numline ,
self . int_op_cutoffs ,
self . int_op_options ,
self . int_unary )
def generate_float_expr ( self ) :
self . _generate_expression ( [ GAZ_FLOAT_KEY , GAZ_INT_KEY ] ,
self . float_op_numline ,
self . float_op_cutoffs ,
self . float_op_options ,
self . float_unary )
def generate_bool_expr ( self ) :
self . _generate_expression ( [ GAZ_BOOL_KEY ] ,
self . bool_op_numline ,
self . bool_op_cutoffs ,
self . bool_op_options ,
self . bool_unary )
def generate_char_expr ( self ) :
self . _generate_expression ( [ GAZ_CHAR_KEY ] ,
self . char_op_numline ,
self . char_op_cutoffs ,
self . char_op_options )
def generate_comp_expr ( self ) :
self . _generate_expression ( [ GAZ_BOOL_KEY ] ,
self . comp_op_numline ,
self . comp_op_cutoffs ,
self . comp_op_options ,
2023-11-24 15:45:38 -07:00
comparison = True ) #, evals=self.get_truth())
2023-11-23 13:40:20 -07:00
2023-11-18 10:59:00 -07:00
def push_scope ( self , xml_element : ET . Element = None ) :
scope = Scope ( self . current_scope )
self . symbol_table . append ( scope )
self . current_scope = scope
def pop_scope ( self ) :
self . current_scope = self . current_scope . enclosing_scope
# TODO revamp the random value generations
def get_qualifier ( self ) :
2023-11-17 16:57:53 -07:00
"""
2023-11-18 10:59:00 -07:00
@brief get a random qualifier from the list of possible qualifiers
@return a qualifier as a string
2023-11-17 16:57:53 -07:00
"""
2023-11-18 10:59:00 -07:00
number_line = ( self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " const " ] +
2023-11-22 13:50:56 -07:00
self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " var " ] - 1 )
2023-11-18 10:59:00 -07:00
res = random . randint ( 0 , number_line )
if res in range ( 0 , self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " const " ] ) :
return ' const '
elif res in range ( self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " const " ] ,
self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " const " ] +
self . settings [ " misc-weights " ] [ " type-qualifier-weights " ] [ " var " ] ) :
return ' var '
2023-11-17 16:57:53 -07:00
else :
2023-11-18 10:59:00 -07:00
raise ValueError ( " Internal Error, please report the stack trace to me " )
def get_routine_type ( self ) :
cutoffs = [ ]
values = [ ]
ops = [ ]
for key , value in self . settings [ " routine-weights " ] . items ( ) :
cutoffs . append ( value + sum ( cutoffs ) )
values . append ( value )
ops . append ( key )
res = random . randint ( 0 , sum ( values ) )
for i in range ( len ( cutoffs ) ) :
if res < cutoffs [ i ] :
return ops [ i ] # TODO everything should be fast faied
def get_value ( self , type ) :
if type == GAZ_INT_KEY :
if self . settings [ " properties " ] [ " generate-max-int " ] :
return random . randint ( - 2147483648 , 2147483647 )
else :
return random . randint ( - 1000 , 1000 )
elif type == GAZ_FLOAT_KEY :
return random . uniform ( - 1000 , 1000 )
elif type == GAZ_BOOL_KEY :
return random . choice ( [ True , False ] )
2023-11-21 23:03:12 -07:00
elif type == GAZ_CHAR_KEY :
return " ' " + random . choice ( string . ascii_letters ) + " ' "
2023-11-18 10:59:00 -07:00
else :
raise TypeError ( " Unimplemented generator for type: " + type )
def get_name ( self , name_type ) :
"""
@brief get a random name from the list of possible names and add it to the current scope
@param name_type :
@return :
"""
2023-11-21 15:38:39 -07:00
if not self . settings [ ' properties ' ] [ ' use-english-words ' ] :
length = random . randint ( self . settings [ ' properties ' ] [ ' id-length ' ] [ ' min ' ] ,
self . settings [ ' properties ' ] [ ' id-length ' ] [ ' max ' ] )
name = ' ' . join ( random . choices ( string . ascii_letters , k = length ) )
return name
else :
2023-11-24 07:33:30 -07:00
try :
if name_type == GAZ_VAR_TAG :
choice = random . choice ( self . variable_names )
self . variable_names . remove ( choice )
return choice
else :
choice = random . choice ( self . routine_names )
self . routine_names . remove ( choice )
return choice
except IndexError : # if we run out of variable names
length = random . randint ( self . settings [ ' properties ' ] [ ' id-length ' ] [ ' min ' ] ,
self . settings [ ' properties ' ] [ ' id-length ' ] [ ' max ' ] )
name = ' ' . join ( random . choices ( string . ascii_letters , k = length ) )
return name
def get_type ( self , tag ) - > str : # TODO Add support for composite types
"""
@brief get a random type from the list of possible types
@param tag :
@return : a type as a string
"""
comp_atom = self . get_choice ( self . type_options , self . type_numline , self . type_cutoffs )
choice = " "
if comp_atom == GAZ_ATOMIC_TYPE_KEY :
choice = self . get_choice ( self . atomic_type_options , self . atomic_type_numline , self . atomic_type_cutoffs )
elif comp_atom == GAZ_COMPOSITE_TYPE_KEY :
choice = self . get_choice ( self . composite_type_options , self . composite_type_numline , self . composite_type_cutoffs )
else :
raise NotImplementedError ( f " Unimplemented generator for type: { comp_atom } " )
if tag not in [ GAZ_PROCEDURE_TAG ] :
if choice != GAZ_VOID_TYPE :
2023-11-22 13:50:56 -07:00
return choice
else :
2023-11-24 07:33:30 -07:00
return self . get_type ( tag )
else :
return choice
2023-11-18 10:59:00 -07:00
2023-11-24 07:33:30 -07:00
def get_choice ( self , options , numline , cutoffs ) :
res = random . randint ( 0 , numline - 1 )
for i in range ( len ( cutoffs ) ) :
if res < cutoffs [ i ] :
try :
return options [ i ]
except Exception as e :
raise ValueError ( str ( e ) + " Internal Error, please report the stack trace to me " )
2023-11-23 13:01:13 -07:00
### LOOP HELPERS ###
def generate_loop_condition_check ( self , loop_var : Variable ) :
"""
@brief generates the loop condition check
Ensures that the loop does not iterate more than max - loop - iterations times
@param loop_var :
@return :
"""
# loop var is always an int
assert loop_var . type == GAZ_INT_KEY
# create a conditional xml tag
if_stmt = build_xml_element ( [ ] , name = GAZ_IF_TAG )
self . current_ast_element . append ( if_stmt )
parent = self . current_ast_element
self . current_ast_element = if_stmt
# add the check 'if loop_var >= self.settings['generation_options']['max-loop-iterations']: break'
operation = build_xml_element ( [ ( " op " , " >= " ) ] , name = GAZ_OPERATOR_TAG )
rhs = self . _loop_heloper ( loop_var , operation )
2023-11-24 06:55:12 -07:00
rhs . append ( # TODO refactor this to use generate_literal instead of make_literal
2023-11-23 13:01:13 -07:00
self . make_literal ( GAZ_INT_KEY , " ' " + str ( self . settings [ ' generation-options ' ] [ ' max-loop-iterations ' ] ) + " ' " ) )
true_block = build_xml_element ( [ ] , name = GAZ_BLOCK_TAG )
if_stmt . append ( true_block )
self . current_ast_element = true_block
break_stmt = build_xml_element ( [ ] , name = GAZ_BREAK_TAG )
true_block . append ( break_stmt )
# return everything to normalcy
self . current_ast_element = parent
def _loop_heloper ( self , loop_var , operation ) :
self . current_ast_element . append ( operation )
self . current_ast_element = operation
lhs = build_xml_element ( [ ] , name = GAZ_LHS_TAG )
operation . append ( lhs )
var = build_xml_element ( [ ( " name " , loop_var . name ) , ( " type " , loop_var . type ) ] , name = GAZ_VAR_TAG )
lhs . append ( var )
rhs = build_xml_element ( [ ] , name = GAZ_RHS_TAG )
operation . append ( rhs )
return rhs
def generate_loop_condition_increment ( self , loop_var ) :
assert loop_var . type == GAZ_INT_KEY
parent = self . current_ast_element
assignment = build_xml_element ( [ ] , name = GAZ_ASSIGNMENT_TAG )
self . current_ast_element . append ( assignment )
self . current_ast_element = assignment
# append the variable
self . current_ast_element . append ( loop_var . xml )
# add the increment 'loop_var += 1'
assn_rhs = build_xml_element ( [ ] , name = GAZ_RHS_TAG )
self . current_ast_element . append ( assn_rhs )
self . current_ast_element = assn_rhs
operation = build_xml_element ( [ ( " op " , " + " ) ] , name = GAZ_OPERATOR_TAG )
rhs = self . _loop_heloper ( loop_var , operation )
2023-11-24 06:55:12 -07:00
rhs . append ( self . make_literal ( GAZ_INT_KEY , ' 1 ' ) ) # TODO refactor this to use generate_literal instead of make_literal
2023-11-23 13:01:13 -07:00
# return everything to normalcy
self . current_ast_element = parent
### HELPER FUNCTIONS ###
def make_element ( self , name : str , keys : list [ tuple [ str , any ] ] ) - > ET . Element :
"""
@brief make an xml element for the ast
@effects modifies self . current_ast_element
@param name : the tag for the element
@param keys : a list of tuple containing keys for the element
"""
element = build_xml_element ( keys , name = name )
if self . current_ast_element is not None :
self . current_ast_element . append ( element )
self . current_ast_element = element
return element
def make_scoped_element ( self , name , keys ) - > ET . Element :
"""
@brief make an xml element for the ast with a scope
@param name : the tag for the element
@param keys : a list of tuple containing keys for the element
"""
parent = self . current_ast_element
self . push_scope ( )
self . make_element ( name , keys )
return parent
def exit_scoped_element ( self , parent ) :
"""
@brief leave the current element and return to parent
@param parent : the enclosing element to return to
"""
self . pop_scope ( )
2023-11-23 13:21:43 -07:00
self . current_ast_element = parent
def _generate_from_options ( self , cutoffs , number_line , options ) :
while True :
if random . random ( ) < self . settings [ ' block-termination-probability ' ] :
break
a = random . randint ( 0 , number_line )
i = 0
for i in range ( len ( cutoffs ) - 1 ) :
if cutoffs [ i ] < a < cutoffs [ i + 1 ] :
try :
options [ i ] ( )
2023-11-24 07:33:30 -07:00
except KeyError : # if the key is not in the options (due to exclusion)
2023-11-23 13:21:43 -07:00
continue
except ValueError :
break
break
def _generate_expr ( self , comparison , expr_type , op , unary ) :
if op in unary :
self . generate_unary ( op , random . choice ( expr_type ) )
elif op == GAZ_BRACKET_TAG :
self . generate_bracket ( random . choice ( expr_type ) )
elif comparison :
if op in [ ' equality ' , ' inequality ' ] :
self . generate_binary ( op , random . choice ( [ GAZ_INT_KEY , GAZ_FLOAT_KEY , GAZ_CHAR_KEY ] ) )
else :
self . generate_binary ( op , random . choice ( [ GAZ_INT_KEY , GAZ_FLOAT_KEY ] ) )
else :
self . generate_binary ( op , random . choice ( expr_type ) )
2023-11-24 15:45:38 -07:00
def print_all_current_scope_vars ( self ) :
for key , value in self . current_scope . symbols . items ( ) :
if isinstance ( value , Variable ) :
self . print_variable ( value )
def print_variable ( self , var : Variable ) :
"""
@brief generate an outstream for a variable
@param var : the variable to print
@return :
"""
parent = self . current_ast_element
args = [
( " type " , GAZ_OUT_STREAM ) ,
]
self . make_element ( GAZ_STREAM_TAG , args )
self . current_ast_element . append ( var . xml )
self . current_ast_element = parent