Current section
Files
Jump to
Current section
Files
src/ephp_interpr.erl
-module(ephp_interpr).
-author('manuel@altenwald.com').
-export([process/2, process/3, run/2, run/3]).
-export_type([flow_status/0]).
-include("ephp.hrl").
-include("ephp_array.hrl").
-spec process(ephp:context_id(), Statements :: [main_statement()]) ->
{ok, binary() | flow_return() | false}.
process(Context, Statements) ->
Cover = ephp_cover:get_config(),
process(Context, Statements, Cover).
-spec process(ephp:context_id(), Statements :: [main_statement()], Cover :: boolean()) ->
{ok, binary() | flow_return() | false}.
process(_Context, [], _Cover) ->
{ok, false};
process(Context, Statements, false) ->
Value =
try
lists:foldl(fun (Statement, false) ->
run(Context, Statement, false);
(_Statement, Return) ->
Return
end,
false,
Statements)
catch
Exception when ?IS_OBJECT(Exception) ->
check_exception(Context, Exception),
false
end,
{ok, Value};
process(Context, Statements, true) ->
ok = ephp_cover:start_link(),
Value =
try
lists:foldl(fun (Statement, false) ->
run(Context, Statement, true);
(_Statement, Return) ->
Return
end,
false,
Statements)
catch
Exception when ?IS_OBJECT(Exception) ->
check_exception(Context, Exception),
false
end,
{ok, Value}.
-spec check_exception(ephp:context_id(), obj_ref()) -> ok.
check_exception(Context, Exception) ->
L = ephp_class_exception:exception_get_line(Context, Exception, undefined),
Line = {{line, L}, {column, 0}},
File = ephp_class_exception:exception_get_file(Context, Exception, Line),
Data = {File, L, Exception},
Error = {error, euncaught, Line, File, ?E_ERROR, Data},
ephp_error:handle_error(Context, Error),
ok.
-type break() :: break | {break, non_neg_integer()}.
-type continue() :: continue | {continue, non_neg_integer()}.
-type flow_return() :: {return, mixed()}.
-type flow_status() :: break() | continue() | flow_return() | false.
-spec run(ephp:context_id(), main_statement()) -> flow_status().
run(Context, Statement) ->
Cover = ephp_cover:get_config(),
run(Context, Statement, Cover).
-spec run(ephp:context_id(), main_statement(), Cover :: boolean()) -> flow_status().
run(Context, #print_text{text = Text, line = Line}, Cover) ->
ok = ephp_cover:store(Cover, print, Context, Line),
ephp_context:set_output(Context, Text),
false;
run(Context, #print{expression = Expr, line = Line}, Cover) ->
ok = ephp_cover:store(Cover, print, Context, Line),
Result = ephp_context:solve(Context, Expr),
ephp_context:set_output(Context, ephp_data:to_bin(Context, Line, Result)),
false;
run(Context, #eval{statements = Statements, line = Line}, Cover) ->
ok = ephp_cover:store(Cover, eval, Context, Line),
lists:foldl(fun (_Statement, {return, _} = State) ->
State;
(Statement, State) ->
run_depth(Context, Statement, State, Cover)
end,
false,
Statements).
-spec run_depth(ephp:context_id(), statement(), flow_status(), Cover :: boolean()) ->
flow_status().
run_depth(Context, #eval{} = Eval, false, Cover) ->
run(Context, Eval, Cover);
run_depth(Context, #assign{line = Line} = Assign, Return, Cover) ->
ok = ephp_cover:store(Cover, assign, Context, Line),
ephp_context:solve(Context, Assign),
Return;
run_depth(Context, #if_block{conditions = Cond, line = Line} = IfBlock, false, Cover) ->
ok = ephp_cover:store(Cover, if_block, Context, Line),
#if_block{true_block = TrueBlock, false_block = FalseBlock} = IfBlock,
case ephp_data:to_boolean(
ephp_context:solve(Context, Cond))
of
true when is_list(TrueBlock) ->
run(Context, #eval{statements = TrueBlock}, Cover);
true ->
ephp_context:solve(Context, TrueBlock),
false;
false when is_list(FalseBlock) ->
run(Context, #eval{statements = FalseBlock}, Cover);
false when FalseBlock =:= undefined ->
false;
false ->
ephp_context:solve(Context, FalseBlock),
false
end;
run_depth(Context,
#switch{condition = Cond,
cases = Cases,
line = Line},
false,
Cover) ->
ok = ephp_cover:store(Cover, switch, Context, Line),
case run_switch(Context, Cond, Cases, Cover) of
{seek, false} ->
{_, Return} = run_switch(Context, default, Cases, Cover),
Return;
{_, Return} ->
Return
end,
case Return of
false ->
false;
{return, R} ->
{return, R};
break ->
false;
{break, 0} ->
false;
{break, N} ->
{break, N - 1}
end;
run_depth(Context,
#foreach{kiter = Key,
iter = Var,
elements = RawElements,
loop_block = LB,
line = Line} =
FE,
false,
Cover) ->
ok = ephp_cover:store(Cover, foreach, Context, Line),
case ephp_context:solve(Context, RawElements) of
ProcElements when ?IS_ARRAY(ProcElements) ->
Elements = ephp_array:to_list(ProcElements),
run_foreach(Context, Key, Var, {RawElements, Elements}, LB, Cover);
Object when ?IS_OBJECT(Object) ->
case ephp_data:instance_of(Context, Object, <<"Iterator">>) of
true ->
ClassName = ephp_object:get_class_name(Object),
CallGen =
#call{type = object,
name = <<>>,
class = ClassName,
line = Line},
CallRewind = CallGen#call{name = <<"rewind">>},
CallValid = CallGen#call{name = <<"valid">>},
%% TODO check die or other breaks with these method calls
ephp_context:call_method(Context, Object, CallRewind),
case ephp_context:call_method(Context, Object, CallValid) of
true ->
run_foreach(Context, Key, Var, Object, LB, Cover);
false ->
false
end;
false ->
Line = FE#foreach.line,
File = ephp_context:get_active_file(Context),
Data = {<<"foreach">>},
Error = {error, eargsupplied, Line, File, ?E_WARNING, Data},
ephp_error:handle_error(Context, Error),
false
end;
_ ->
Line = FE#foreach.line,
File = ephp_context:get_active_file(Context),
Data = {<<"foreach">>},
Error = {error, eargsupplied, Line, File, ?E_WARNING, Data},
ephp_error:handle_error(Context, Error),
false
end;
run_depth(Context,
#while{type = Type,
conditions = Cond,
loop_block = LB,
line = Line},
false,
Cover) ->
ok = ephp_cover:store(Cover, while, Context, Line),
run_loop(Type, Context, Cond, LB, Cover);
run_depth(Context, #print_text{text = Text, line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, print, Context, Line),
ephp_context:set_output(Context, Text),
false;
run_depth(Context, #print{expression = Expr, line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, print, Context, Line),
Result = ephp_context:solve(Context, Expr),
ResText = ephp_data:to_bin(Context, Line, Result),
ephp_context:set_output(Context, ResText),
false;
run_depth(Context, #call{line = Line} = Call, false, Cover) ->
ok = ephp_cover:store(Cover, {call, Call#call.name}, Context, Line),
ephp_func:run(Context, Call);
run_depth(Context, {Op, _Var, Line} = MonoArith, false, Cover)
when Op =:= pre_incr
orelse Op =:= pre_decr
orelse Op =:= post_incr
orelse Op =:= post_decr ->
ok = ephp_cover:store(Cover, Op, Context, Line),
ephp_context:solve(Context, MonoArith),
false;
run_depth(Context, #operation{line = Line} = Op, false, Cover) ->
ok = ephp_cover:store(Cover, {op, Op#operation.type}, Context, Line),
ephp_context:solve(Context, Op),
false;
run_depth(Context, #class{type = interface, line = Line} = Interface, Return, Cover) ->
ok = ephp_cover:store(Cover, interface, Context, Line),
ephp_context:register_interface(Context, Interface),
Return;
run_depth(Context, #class{line = Line} = Class, Return, Cover) ->
ok = ephp_cover:store(Cover, class, Context, Line),
ephp_context:register_class(Context, Class),
Return;
run_depth(Context,
#function{name = Name,
args = Args,
code = Code,
namespace = NS,
line = Line},
Return,
Cover) ->
ok = ephp_cover:store(Cover, function, Context, Line),
ephp_context:register_func(Context, NS, Name, Args, Code, false, undefined),
Return;
run_depth(Context, {global, GlobalVar, Line}, Return, Cover) ->
ok = ephp_cover:store(Cover, global, Context, Line),
ephp_context:solve(Context, {global, GlobalVar, Line}),
Return;
run_depth(_Context, break, false, _Cover) ->
break;
run_depth(_Context, continue, false, _Cover) ->
continue;
run_depth(Context, #throw{value = Value, line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, throw, Context, Line),
case ephp_context:solve(Context, Value) of
Exception when ?IS_OBJECT(Exception) ->
throw(ephp_context:solve(Context, Value));
_ ->
ephp_error:error({error, enoobjectexception, Line, ?E_ERROR, {}})
end;
run_depth(Context, #return{value = Value, line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, return, Context, Line),
RealValue = ephp_context:solve(Context, Value),
if ?IS_OBJECT(RealValue) ->
ephp_object:add_link(RealValue);
?IS_MEM(RealValue) ->
ephp_mem:add_link(RealValue);
true ->
ok
end,
{return, RealValue};
run_depth(_Context, {return, Value}, false, _Cover) ->
{return, Value};
run_depth(_Context, {break, N}, false, _Cover) ->
{break, N - 1};
run_depth(_Context, {continue, N}, false, _Cover) ->
{continue, N - 1};
run_depth(_Context, Boolean, false, _Cover) when is_boolean(Boolean) ->
false;
run_depth(Context, #int{line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, int, Context, Line),
false;
run_depth(Context, #float{line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, float, Context, Line),
false;
run_depth(Context, #text{line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, text, Context, Line),
false;
run_depth(Context, #text_to_process{line = Line} = TP, false, Cover) ->
ok = ephp_cover:store(Cover, text, Context, Line),
ephp_context:solve(Context, TP),
false;
run_depth(Context, #variable{idx = [{object, #call{}, _}]} = Var, false, Cover) ->
ok = ephp_cover:store(Cover, object, Context, Var#variable.line),
ephp_context:solve(Context, Var),
false;
run_depth(Context,
#constant{type = define,
name = Name,
value = Expr,
namespace = NS,
line = Line},
false,
Cover) ->
ok = ephp_cover:store(Cover, define, Context, Line),
Value = ephp_context:solve(Context, Expr),
ephp_context:register_const(Context, NS, Name, Value),
false;
run_depth(Context, #constant{line = Line}, false, Cover) ->
ok = ephp_cover:store(Cover, constant, Context, Line),
false;
run_depth(Context, #instance{line = Line} = Instance, false, Cover) ->
ok = ephp_cover:store(Cover, new, Context, Line),
ObjRef = ephp_context:solve(Context, Instance),
ephp_object:remove(Context, ObjRef),
false;
run_depth(Context, {silent, Statement}, false, Cover) ->
ephp_error:run_quiet(
ephp_context:get_errors_id(Context),
fun() -> run_depth(Context, Statement, false, Cover) end);
run_depth(_Context, die, false, _Cover) ->
throw(die);
run_depth(Context,
#try_catch{code_block = Code,
catches = Catches,
finally = Finally,
line = Line},
false,
Cover) ->
ok = ephp_cover:store(Cover, try_catch, Context, Line),
try
run(Context, #eval{statements = Code}, Cover)
catch
Exception when ?IS_OBJECT(Exception) ->
Ret = lists:foldl(fun (#catch_block{} = CatchBlock, throw) ->
run_catch(Context, CatchBlock, Exception, Finally, Cover);
(#catch_block{}, Ret) ->
Ret
end,
throw,
Catches),
case Ret of
throw ->
run_finally(Context, Finally, Cover),
throw(Exception);
_ ->
Ret
end
end,
run_finally(Context, Finally, Cover);
run_depth(Context, #variable{type = static, name = VarName} = Var, false, _Cover) ->
ActiveFun = ephp_context:get_active_function(Context),
RealValue =
case ephp_context:get_active_real_class(Context) of
<<>> ->
NS = Var#variable.class_ns,
Funcs = ephp_context:get_funcs(Context),
ephp_func:init_static_value(Funcs, NS, ActiveFun, VarName, undefined);
ClassName ->
NS = ephp_context:get_active_real_class_ns(Context),
Classes = ephp_context:get_classes(Context),
ephp_class:init_static_value(Classes, NS, ClassName, ActiveFun, VarName, undefined)
end,
ephp_context:set(Context, Var, RealValue),
false;
run_depth(Context, #variable{} = Var, false, Cover) ->
ok = ephp_cover:store(Cover, variable, Context, Var#variable.line),
ephp_context:solve(Context, Var),
false;
run_depth(_Context, Statement, false, _Cover) ->
ephp_error:error({error, eunknownst, undefined, ?E_CORE_ERROR, Statement}),
break;
run_depth(_Context, _Statement, Break, _Cover) ->
Break.
-spec exit_cond(flow_status()) -> flow_status().
exit_cond({return, Ret}) ->
{return, Ret};
exit_cond({break, 0}) ->
false;
exit_cond({break, N}) ->
{break, N - 1};
exit_cond(break) ->
false.
-spec run_finally(ephp:context_id(), statements(), Cover :: boolean()) -> flow_status().
run_finally(_Context, [], _Cover) ->
false;
run_finally(Context, Finally, Cover) ->
run(Context, #eval{statements = Finally}, Cover).
-spec run_catch(ephp:context_id(),
catch_block(),
Exception :: obj_ref(),
Finally :: statements(),
Cover :: boolean()) ->
throw | flow_status().
run_catch(Context,
#catch_block{exception = #variable{data_type = Catch} = Var, code_block = CatchCode},
Exception,
Finally,
Cover) ->
case ephp_data:instance_of(Context, Exception, Catch) of
true ->
ephp_context:set(Context, Var, Exception),
try
run(Context, #eval{statements = CatchCode}, Cover)
catch
NewException when ?IS_OBJECT(NewException) ->
run_finally(Context, Finally, Cover),
throw(NewException)
end;
false ->
throw
end.
-spec run_loop(PrePost :: pre | post,
Context :: ephp:context_id(),
Cond :: condition(),
Statements :: [statement()],
Cover :: boolean()) ->
flow_return().
run_loop(pre, Context, Cond, Statements, Cover) ->
case ephp_data:to_bool(
ephp_context:solve(Context, Cond))
of
true ->
case run(Context, #eval{statements = Statements}, Cover) of
false ->
run_loop(pre, Context, Cond, Statements, Cover);
Return ->
exit_cond(Return)
end;
false ->
false
end;
run_loop(post, Context, Cond, Statements, Cover) ->
case run(Context, #eval{statements = Statements}, Cover) of
false ->
case ephp_data:to_bool(
ephp_context:solve(Context, Cond))
of
true ->
run_loop(post, Context, Cond, Statements, Cover);
false ->
false
end;
Return ->
exit_cond(Return)
end.
-spec run_foreach(Context :: ephp:context_id(),
Key :: variable() | ref(),
Var :: variable() | ref(),
Elements ::
variable() | expression() | {variable() | expression(), [{mixed(), mixed()}]},
Statements :: [statement()],
Cover :: boolean()) ->
break() | return() | false.
run_foreach(_Context, _Key, _Var, {_SupVar, []}, _Statements, _Cover) ->
false;
run_foreach(Context, Key, Var, Object, Statements, Cover) when ?IS_OBJECT(Object) ->
ClassName = ephp_object:get_class_name(Object),
CallGen =
#call{name = <<>>,
type = object,
class = ClassName},
CallValid = CallGen#call{name = <<"valid">>},
CallNext = CallGen#call{name = <<"next">>},
CallKey = CallGen#call{name = <<"key">>},
CallCurrent = CallGen#call{name = <<"current">>},
VarVal = ephp_context:call_method(Context, Object, CallCurrent),
%% TODO: error in case #ref{} instead of #variable{} for Var???
ephp_context:set(Context, Var, VarVal),
case Key of
undefined ->
ok;
_ ->
KeyVal = ephp_context:call_method(Context, Object, CallKey),
ephp_context:set(Context, Key, KeyVal)
end,
Break = run(Context, #eval{statements = Statements}, Cover),
if Break =/= break andalso not is_tuple(Break) ->
ephp_context:call_method(Context, Object, CallNext),
case ephp_context:call_method(Context, Object, CallValid) of
true ->
run_foreach(Context, Key, Var, Object, Statements, Cover);
false ->
false
end;
true ->
case Break of
{return, Ret} ->
{return, Ret};
{break, 0} ->
false;
{break, N} ->
{break, N - 1};
_ ->
false
end
end;
run_foreach(Context,
Key,
Var,
{SupVar, [{KeyVal, VarVal} | Elements]},
Statements,
Cover) ->
case Key of
undefined ->
ok;
_ ->
ephp_context:set(Context, Key, KeyVal)
end,
case Var of
#ref{var = #variable{} = SubVar} ->
Vars = ephp_context:get_vars(Context),
Idx = SupVar#variable.idx ++ [KeyVal],
NewSupVar = SupVar#variable{idx = Idx},
ephp_vars:ref(Vars, SubVar, Vars, NewSupVar, Context);
#variable{} ->
ephp_context:set(Context, Var, VarVal)
end,
Break = run(Context, #eval{statements = Statements}, Cover),
if Break =/= break andalso not is_tuple(Break) ->
run_foreach(Context, Key, Var, {SupVar, Elements}, Statements, Cover);
true ->
case Break of
{return, Ret} ->
{return, Ret};
{break, 0} ->
false;
{break, N} ->
{break, N - 1};
_ ->
false
end
end.
-type switch_flow() :: seek | run | exit.
-spec run_switch(ephp:context_id(),
condition() | default,
[switch_case()],
Cover :: boolean()) ->
{switch_flow(), break() | return() | false}.
run_switch(Context, default, Cases, Cover) ->
lists:foldl(fun (_SwitchCase, {exit, Return}) ->
{exit, Return};
(#switch_case{label = default,
code_block = Code,
line = Line},
{seek, false}) ->
ok = ephp_cover:store(Cover, switch_case, Context, Line),
case run(Context, #eval{statements = Code}, Cover) of
break ->
{exit, false};
{break, 0} ->
{exit, false};
{break, N} ->
{exit, {break, N - 1}};
{return, R} ->
{exit, {return, R}};
false ->
{run, false}
end;
(_Case, {seek, false}) ->
{seek, false};
(#switch_case{code_block = Code, line = Line}, {run, false}) ->
ok = ephp_cover:store(Cover, switch_case, Context, Line),
Break = run(Context, #eval{statements = Code}, Cover),
case Break of
break ->
{exit, false};
{break, 0} ->
{exit, false};
{break, N} ->
{exit, {break, N - 1}};
{return, R} ->
{exit, {return, R}};
false ->
{run, false}
end
end,
{seek, false},
Cases);
run_switch(Context, Cond, Cases, Cover) ->
MatchValue = ephp_context:solve(Context, Cond),
lists:foldl(fun (_SwitchCase, {exit, Return}) ->
{exit, Return};
(#switch_case{line = Line, code_block = Code}, {run, false}) ->
ok = ephp_cover:store(Cover, switch_case, Context, Line),
Break = run(Context, #eval{statements = Code}, Cover),
case Break of
break ->
{exit, false};
{break, 0} ->
{exit, false};
{break, N} ->
{exit, {break, N - 1}};
{return, R} ->
{exit, {return, R}};
false ->
{run, false}
end;
(#switch_case{label = default, line = Line}, {seek, false}) ->
ok = ephp_cover:store(Cover, switch_default, Context, Line),
{seek, false};
(#switch_case{label = LabelValue, line = Line} = Case, {seek, false}) ->
ok = ephp_cover:store(Cover, switch_case, Context, Line),
Op = #operation{type = <<"==">>,
expression_left = MatchValue,
expression_right = LabelValue},
case ephp_context:solve(Context, Op) of
true ->
Break =
run(Context,
#eval{statements = Case#switch_case.code_block},
Cover),
case Break of
break ->
{exit, false};
{break, 0} ->
{exit, false};
{break, N} ->
{exit, {break, N - 1}};
{return, R} ->
{exit, {return, R}};
false ->
{run, false}
end;
false ->
{seek, false}
end
end,
{seek, false},
Cases).