defmodule CodeGenerator do @moduledoc """ Returns the assembly code generated given an input source code. """ @doc """ Returns a string of the assembly code generated given a source code. ## Specs ```abstract_syntax_tree``` : abstract sytnax tree generated by the `Parser.parse/2`. ```verbose``` a boolean value indicating if the compiler should output all of its steps. """ def generate_code(abstract_syntax_tree, verbose) do {assembly_code, _result_my_context, _result_free_context, _result_uid} = generate_raw_string_code(abstract_syntax_tree) assembly_code |> cleanup() |> IO.Printer.check_for_verbose(Common.StringElements.code_generated(), verbose) end defp generate_raw_string_code(abstract_syntax_tree, incoming_free_context \\ get_available_registers(), sibling_number \\ 0, start_string \\ "", uid \\ 0) do children_list = abstract_syntax_tree.children {children_string, children_context, free_context, current_uid} = print_next_children( Enum.reverse(children_list), [], incoming_free_context, Enum.count(children_list)-1, start_string, uid ) {return_string, return_my_context, return_free_context} = contextualize_asm( abstract_syntax_tree.asm, children_context, free_context, sibling_number ) if abstract_syntax_tree.token == nil do { children_string <> String.replace( return_string, ":u", Integer.to_string(current_uid) ) <> "\n", return_my_context, return_free_context, current_uid+1 } else { children_string <> (String.replace( return_string, ":u", Integer.to_string(current_uid+1) ) |> String.replace(":t", abstract_syntax_tree.token.expression )) <> "\n", return_my_context, return_free_context, current_uid+1 } end end defp print_next_children([], incoming_siblings_context, incoming_free_context, _sibling_number, incoming_string, incoming_uid) do {incoming_string, incoming_siblings_context, incoming_free_context, incoming_uid} end defp print_next_children(children_list, incoming_siblings_context, incoming_free_context, sibling_number, incoming_string, incoming_uid) do [head | tail] = children_list {return_string, return_siblings_context, return_free_context, next_uid} = generate_raw_string_code( head, incoming_free_context, sibling_number, incoming_string, incoming_uid ) print_next_children(tail, return_siblings_context ++ incoming_siblings_context, return_free_context, sibling_number - 1, return_string, next_uid ) end defp get_available_registers() do ["r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"] end defp contextualize_asm(code, incoming_children_context, incoming_free_context, sibling_number) do #Find what registers I have to assign. register_candidates = Regex.scan(~r/:r|:v[0-9]+/, code) |> Enum.uniq |> List.flatten register_identities = fetch_registers( register_candidates, incoming_free_context ) #Create a replacement rule list for the equivalencies for placeholder-registry replacement_list = incoming_children_context ++ Enum.zip(register_candidates, register_identities) #Replace placeholders in string replaced_code = Enum.reduce_while( replacement_list, code, fn {k,v}, acc -> {:cont, String.replace(acc,k,v)} end ) #Free children registers result_registry = Enum.into(replacement_list, %{})[":r"] canonical_registers = get_available_registers() #Get legal registers new_free_registers = ( MapSet.intersection( canonical_registers |> MapSet.new, Enum.map(incoming_children_context, fn {_x,y} -> y end) |> MapSet.new ) |> MapSet.to_list ) ++ Enum.filter(incoming_free_context, fn x -> x != result_registry end) #Generate my return register equivalency my_register = [ { ":#{sibling_number}", if result_registry == nil do "" else result_registry end } ] #Return tuple {replaced_code, my_register, new_free_registers} end defp fetch_registers([], _) do [] end defp fetch_registers(candidates, free_registers) do candidates_count = candidates |> Enum.count free_registers_count = free_registers |> Enum.count Enum.slice(free_registers, 0..candidates_count-1) ++ generate_ram_variables(candidates_count-free_registers_count) end defp generate_ram_variables(_number_needed) do []#TODO Implement this or it will fail someday end defp cleanup(raw_asm_code) do s1 = Regex.replace(~r/\n+/, raw_asm_code, "\n") s2 = Regex.replace(~r/[\t\r ]{2,}/, s1, "") String.split(s2, "\n") |> Enum.map( fn line -> if String.contains?(line, ":") do line else " "<>line end end) |> Enum.join("\n") end end