class EVC_PARSE_ACTIONS

(source code)

description

Evolicity parse actions

note
	description: "Evolicity parse actions"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2025-03-28 8:54:57 GMT (Friday 28th March 2025)"
	revision: "17"

deferred class
	EVC_PARSE_ACTIONS

inherit
	EVC_SHARED_TOKEN_ENUM

	EL_TOKEN_TEXT_I
		export
			{EVC_VARIABLE_REFERENCE} all
		end

feature {NONE} -- Initialization

	make
		do
			create compound_directive.make
			create compound_directive_stack.make
			create loop_directive_stack.make
			create if_else_directive_stack.make
			create comparison_stack.make
			create boolean_expression_stack.make
			create number_stack.make
			create last_evaluate_directive.make
			create last_include_directive.make
		end

feature {NONE} -- Actions

	on_boolean_conjunction_expression (start_index, end_index: INTEGER)
			--
		require
			boolean_expression_stack_has_two_expressions: boolean_expression_stack.count >= 2
		local
			boolean_conjunction_expression: EVC_BOOLEAN_CONJUNCTION_EXPRESSION
		do
			if tokens_text.code (start_index) = Token.keyword_and then
				create {EVC_BOOLEAN_AND_EXPRESSION} boolean_conjunction_expression.make (boolean_expression_stack.item)
			else
				create {EVC_BOOLEAN_OR_EXPRESSION} boolean_conjunction_expression.make (boolean_expression_stack.item)
			end
			boolean_expression_stack.remove
			boolean_conjunction_expression.set_left_hand_expression (boolean_expression_stack.item)
			boolean_expression_stack.remove
			boolean_expression_stack.put (boolean_conjunction_expression)
		end

	on_boolean_not_expression (start_index, end_index: INTEGER)
			--
		require
			boolean_expression_stack_not_empty: not boolean_expression_stack.is_empty
		local
			boolean_not: EVC_BOOLEAN_NOT_EXPRESSION
		do
			create boolean_not.make (boolean_expression_stack.item)
			boolean_expression_stack.remove
			boolean_expression_stack.put (boolean_not)
		end

	on_boolean_variable (start_index, end_index: INTEGER)
			--
		local
			boolean_variable: EVC_BOOLEAN_REFERENCE_EXPRESSION
		do
			create boolean_variable.make (new_variable_reference (start_index, end_index))
			boolean_expression_stack.put (boolean_variable)
		end

	on_comparable (type: NATURAL; start_index, end_index: INTEGER)
			--
		require
			valid_comparison_text: valid_comparison_text (type, start_index, end_index)
		local
			comparable: EVC_COMPARABLE
		do
			if type = Token.literal_integer then
	  			create comparable.make (token_integer_64 (start_index).to_reference)

			elseif type = Token.literal_real then
	  			create comparable.make (token_real_64 (start_index).to_reference)

			elseif type = Token.literal_string then
	  			if is_token_text_latin_1 (start_index) then
		  			create comparable.make (shared_token_text_8 (start_index, True))
		  		else
		  			create comparable.make (unquoted_token_text (start_index))
	  			end
			else
				create {EVC_COMPARABLE_VARIABLE} comparable.make (new_variable_reference (start_index, end_index))
			end
			number_stack.put (comparable)
		end

	on_comparison_expression (start_index, end_index: INTEGER)
			--
		do
			comparison_stack.item.set_right_hand_expression (number_stack.item)
			number_stack.remove
			comparison_stack.item.set_left_hand_expression (number_stack.item)
			number_stack.remove
			boolean_expression_stack.put (comparison_stack.item)
			comparison_stack.remove
		end

	on_dollor_sign_escape (start_index, end_index: INTEGER)
			--
		do
			if not compound_directive.is_empty
				and then attached {EVC_FREE_TEXT_DIRECTIVE} compound_directive.last as free_text_directive
			then
				free_text_directive.text.append_character ('$')
			else
				compound_directive.extend (create {EVC_FREE_TEXT_DIRECTIVE}.make ("$"))
			end
		end

	on_evaluate (id: NATURAL; start_index, end_index: INTEGER)
			--
		do
			if id = Token.Keyword_evaluate then
				create last_evaluate_directive.make
				compound_directive.extend (last_evaluate_directive)

			elseif id = Token.White_text then
				--	leading space
				set_nested_directive_indent (last_evaluate_directive, start_index, end_index)

			elseif id = Token.Template_name_identifier then
				last_evaluate_directive.set_template_name (token_text (start_index))

			elseif id = Token.Literal_string then
				last_evaluate_directive.set_template_name (unquoted_token_text (start_index))

			elseif id = Token.operator_dot then
				last_evaluate_directive.set_template_name_variable_ref (new_variable_reference (start_index, end_index))
			else
				last_evaluate_directive.set_variable_ref (new_variable_reference (start_index, end_index))
			end
		end

	on_free_text (start_index, end_index: INTEGER)
			--
		local
			free_text_string: ZSTRING
		do
			free_text_string := token_text (start_index)
			compound_directive.extend (create {EVC_FREE_TEXT_DIRECTIVE}.make (free_text_string))
		end

	on_if (id: NATURAL; start_index, end_index: INTEGER)
			--
		do
			if id = Token.Keyword_if then
				compound_directive_stack.put (compound_directive)
				if_else_directive_stack.put (create {EVC_IF_ELSE_DIRECTIVE}.make)
				compound_directive := if_else_directive_stack.item

			elseif id = Token.Keyword_then then
	  			if_else_directive_stack.item.set_boolean_expression (boolean_expression_stack.item)
	  			boolean_expression_stack.remove

	  		elseif id = Token.Keyword_else then
				if_else_directive_stack.item.set_if_true_interval

			elseif id = Token.Keyword_end then
				compound_directive := compound_directive_stack.item
				compound_directive_stack.remove

				if_else_directive_stack.item.set_if_false_interval
				compound_directive.extend (if_else_directive_stack.item)
				if_else_directive_stack.remove
			end
		end

	on_include (id: NATURAL; start_index, end_index: INTEGER)
			--
		do
			if id = Token.Keyword_include then
				create last_include_directive.make
				compound_directive.extend (last_include_directive)

			elseif id = Token.White_text then
				-- leading space
				set_nested_directive_indent (last_include_directive, start_index, end_index)

			elseif id = Token.Literal_string then
				last_include_directive.set_template_name (unquoted_token_text (start_index))

			elseif id = Token.operator_dot then
				last_include_directive.set_variable_ref (new_variable_reference (start_index, end_index))
			end
		end

	on_loop (id: NATURAL; start_index, end_index: INTEGER)
			--
		do
			if Token.loop_keywords.has (id) then
				compound_directive_stack.put (compound_directive)
				loop_directive_stack.put (new_loop_directive (id))
				compound_directive := loop_directive_stack.item

			elseif id = Token.keyword_in then
				loop_directive_stack.item.set_iterable_variable_ref (new_variable_reference (start_index, end_index))

			elseif id = Token.keyword_as then
				loop_directive_stack.item.put_item_name (new_variable_reference (start_index, end_index) @ 1)

			elseif id  = Token.keyword_end then
				compound_directive := compound_directive_stack.item
				compound_directive_stack.remove

				compound_directive.extend (loop_directive_stack.item)
				loop_directive_stack.remove
			end
		end

	on_comparison (symbol: CHARACTER; start_index, end_index: INTEGER)
			--
		local
			comparison: EVC_COMPARISON
		do
			inspect symbol
				when '<' then
		  			create {EVC_LESS_THAN_COMPARISON} comparison.make
		  		when '>' then
		  			create {EVC_GREATER_THAN_COMPARISON} comparison.make
		  		when '=' then
					create {EVC_EQUAL_TO_COMPARISON} comparison.make
		  		when '/' then
					create {EVC_NOT_EQUAL_TO_COMPARISON} comparison.make
			else
				create {EVC_EQUAL_TO_COMPARISON} comparison.make
			end
			comparison_stack.put (comparison)
		end

	on_simple_comparison_expression (start_index, end_index: INTEGER)
			--
		do
		end

	on_value_reference (start_index, end_index: INTEGER)
		local
			substitution: EVC_VARIABLE_SUBST_DIRECTIVE
		do
			create substitution.make (new_variable_reference (start_index, end_index))
			compound_directive.extend (substitution)
		end

feature {NONE} -- Parse actions

	comparable_action (id: NATURAL): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_comparable (id, ?, ?)
		end

	evaluate_action (id: NATURAL): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_evaluate (id, ?, ?)
		end

	if_action (id: NATURAL): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_if (id, ?, ?)
		end

	include_action (id: NATURAL): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_include (id, ?, ?)
		end

	loop_action (id: NATURAL): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_loop (id, ?, ?)
		end

	comparison_action (symbol: CHARACTER): PROCEDURE [INTEGER, INTEGER]
		do
			Result := agent on_comparison (symbol, ?, ?)
		end

feature {NONE} -- Factory

	new_loop_directive (id: NATURAL): EVC_FOREACH_DIRECTIVE
		do
			if id = Token.Keyword_across then
				create {EVC_ACROSS_DIRECTIVE} Result.make
			else
				create Result.make
			end
		end

	new_variable_reference (start_index, end_index: INTEGER): EVC_VARIABLE_REFERENCE
			--
		do
			if tokens_text.code (start_index) = Token.at_sign then
				create {EVC_FUNCTION_REFERENCE} Result.make (Current, start_index, end_index)
			else
				create Result.make (Current, start_index, end_index)
			end
		ensure
			correct_capacity: Result.full
		end

feature {NONE} -- Implementation

	set_nested_directive_indent (nested_directive: EVC_NESTED_TEMPLATE_DIRECTIVE; start_index, end_index: INTEGER)
			--
		local
			leading_space: ZSTRING; space_count: INTEGER
		do
			leading_space := token_text (start_index)
			if leading_space.occurrences ('%T') = leading_space.count then
				nested_directive.set_tab_indent (leading_space.count)
			else
				space_count := leading_space.occurrences (' ') + leading_space.occurrences ('%T') * Spaces_per_tab
				nested_directive.set_tab_indent (space_count // Spaces_per_tab)
			end
		end

	tokens_out (start_index, end_index: INTEGER): SPECIAL [READABLE_STRING_GENERAL]
		-- For use as a watch expression in the Studio debugger
		local
			i: INTEGER; i_th_token: NATURAL_32
		do
			create Result.make_empty (end_index - start_index + 1)
			from i := start_index until i > end_index loop
				i_th_token := tokens_text.code (i)

				if Token.valid_value (i_th_token) then
					Result.extend (Token.name (i_th_token))

				elseif attached token_text (i) as text then
					if text.is_valid_as_string_8 then
						Result.extend (text.to_string_8)
					else
						Result.extend (text.to_string_32)
					end
				end
				i := i + 1
			end
		end

	valid_comparison_text (type: NATURAL; start_index, end_index: INTEGER): BOOLEAN
		do
			if type = Token.Literal_integer then
	  			Result := token_text (start_index).is_integer_64

			elseif type = Token.literal_real then
	  			Result := token_text (start_index).is_real_64

	  		else
	  			Result := True
			end
		end

feature {NONE} -- Internal attributes

	boolean_expression_stack: LINKED_STACK [EVC_BOOLEAN_EXPRESSION]

	compound_directive: EVC_COMPOUND_DIRECTIVE

	compound_directive_stack: LINKED_STACK [EVC_COMPOUND_DIRECTIVE]

	if_else_directive_stack: LINKED_STACK [EVC_IF_ELSE_DIRECTIVE]

	last_evaluate_directive: EVC_EVALUATE_DIRECTIVE

	last_include_directive: EVC_INCLUDE_DIRECTIVE

	loop_directive_stack: LINKED_STACK [EVC_FOREACH_DIRECTIVE]

	number_stack: LINKED_STACK [EVC_COMPARABLE]

	comparison_stack: LINKED_STACK [EVC_COMPARISON]

feature {NONE} -- Constants

	Spaces_per_tab: INTEGER = 4

end