defmodule Parser do @moduledoc """ Parses the Output Token List (OTL) from `Lexer.tokenize/1` into an Output Abstract Syntax Tree (OAST). """ @doc """ Returns a tuple containing a status token, the Output Abstract Syntax Tree (OAST), a token list and a possible error cause. ## Specs ```otl``` is the Output Token List which is a list of the tokens found in the source code string. This list is generated by the `Lexer` in `Lexer.tokenize/1`. ```gast``` is a General Abstract Syntax Tree which is list of `Structs.Node` that contains the source language grammar production rules. Returns the following elements: + ```status_atom``` : indicates if the parsing could be completed successfully. If not, then the following error tokens are returned: + ```:token_missing_error``` : thrown when the source code is missing something or the order of the tokens is not correct. For example: ``` int main() { 2; } ``` where the token ```return```is missing. + ```:token_not_absorbed``` : thrown when the structure is correct but it has an extra token. For example: ``` int main() { return 2; } main ``` where the token ```main```is unnecessary. + ```output_ast``` : abstract syntax tree representing the source code. + ```token_list``` : token list found in the source code. + ```error_cause``` : description of the error cause. """ def parse(otl, gast) do ps_m = generate_possible_structure_map(gast) root_AST = generate_root_ast() {result_token, oast, tl, error_cause} = my_structure_matches(root_AST, otl, ps_m) if result_token === :ok and tl === [] do {:ok,oast,tl,error_cause, otl} else if result_token === :error do {:token_missing_error,oast,tl,error_cause, otl} else {:token_not_absorbed_error,oast,tl,{nil, tl, nil}, otl} end end end defp my_structure_matches(cs,tl,ps_m) do {result_token_1, tl_1, absorbed_token} = my_token_matches(cs,tl) if result_token_1 === :ok do {result_token_2, tl_2, children_list,error_cause} = my_children_match(cs,tl_1,ps_m) if result_token_2 === :ok do d_cs = specifize_structure(cs,absorbed_token,children_list) {:ok,d_cs,tl_2,nil} else #there was no if {broken_structure,bad_tokens, breaker} = error_cause if broken_structure == nil do {:error,cs,tl,{cs,bad_tokens, breaker}}#Changed, it returned cs, now it returns cs and the token that caused the catastrophic failure. else {:error,cs,tl,error_cause} end #{:error,cs,tl,incoming_error} #:structure-does-not-match-expectation end else {:error,cs,tl,{nil,tl, cs}} #:Could not absorb token of structure end end defp my_children_match(cs,tl,ps_m) do children_list = cs.children next_child_match(children_list,tl,[],ps_m) end defp next_child_match([],tl,previous_children,_ps_m) do {:ok, tl, previous_children, nil} end defp next_child_match(cl,tl,previous_children,ps_m) do # IO.inspect(cl, label: "The list is: ") # Commented line to avoid printing [child | cl_1] = cl possible_structures = ps_m[child["class"]] {result_token, matched_structure, tl_1, incoming_error} = check_ps(possible_structures,tl,nil,ps_m) if result_token === :ok do next_child_match(cl_1,tl_1,previous_children++[matched_structure],ps_m) else {:error,tl,previous_children++[child],incoming_error} #if incoming_error === nil do # {:error,tl,previous_children++[child],child} #else # {:error,tl,previous_children++[child],incoming_error} #end end end defp check_ps([],tl,error_cause,_ps_m) do {:error,nil,tl,error_cause} end defp check_ps(ps,tl,_error_cause,ps_m) do [candidate_structure | ps_1] = ps {result_token,current_candidate,tl_1,error_cause_1} = my_structure_matches(candidate_structure,tl,ps_m) if result_token === :ok do {:ok,current_candidate,tl_1,nil} else check_ps(ps_1,tl,error_cause_1,ps_m) end end defp my_token_matches(cs,[]) do if cs.token === nil do {:ok,[],nil} else {:error,[],nil} end end defp my_token_matches(cs,tl) do [absorbed_token | tl_1] = tl if cs.token === nil do {:ok,tl,nil} else if cs.token === absorbed_token.tag do {:ok,tl_1,absorbed_token} else {:error,tl,absorbed_token} end end end defp specifize_structure(cs,absorbed_token,children_list) do %Structs.Node{ class: cs.class, token: absorbed_token, tag: cs.tag, asm: cs.asm, children: children_list } end defp generate_possible_structure_map(gast) do keys = (Enum.map(gast, fn x -> [x.class] end) |> List.flatten |> Enum.uniq) Enum.map( keys, fn k -> { k, Enum.filter( gast, fn y -> Enum.member?( List.flatten([y.class]), String.replace(k,"*","")) end) } end ) |> Enum.map( fn {k,value_list} -> if String.contains?(k, "*"), do: {k,value_list ++ [generate_null_ast(k)]}, else: {k,value_list} end ) |> Map.new end defp generate_root_ast() do %Structs.Node{ class: :root, token: nil, tag: :root, children: [%{"class"=>"program-root", "tag"=>"program-root"}], asm: "movq %:0, %rax" } end defp generate_null_ast(optional_class) do %Structs.Node{ class: String.replace(optional_class,"*",""), token: nil, tag: "no_match_for_optional_substructure", children: [], asm: "" } end end