<commit_before>(*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright (c) <NAME>
*)
unit proc_py;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Classes,
  PythonEngine,
  proc_str,
  proc_globdata,
  proc_appvariant;

type
  { TAppPython }

  TAppPython = class
  private const
    NamePrefix = 'xx'; //the same as in py/cudatext_reset_plugins.py
  private
    FInited: boolean;
    FEngine: TPythonEngine;
    FRunning: boolean;
    FLastCommandModule: string;
    FLastCommandMethod: string;
    FLastCommandParam: TAppVariant;
    EventTime: QWord;
    EventTimes: TStringList;
    LoadedLocals: TStringList;
    LoadedModuleCudatext: boolean;
    LoadedModules: TStringList;
    MainModule: PPyObject;
    Globals: PPyObject;
    procedure ImportCommand(const AObject, AModule: string);
    function ImportModuleCached(const AModule: string): PPyObject;
    function IsLoadedLocal(const S: string): boolean;
    function MethodEval(const AObject, AMethod, AParams: string): PPyObject;
    function MethodEvalEx(const AObject, AMethod, AParams: string): TAppPyEventResult;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Initialize;
    property Inited: boolean read FInited;
    property Engine: TPythonEngine read FEngine;
    property IsRunning: boolean read FRunning;
    property LastCommandModule: string read FLastCommandModule;
    property LastCommandMethod: string read FLastCommandMethod;
    property LastCommandParam: TAppVariant read FLastCommandParam;

    function Eval(const Command: string; UseFileMode: boolean=false): PPyObject;
    procedure Exec(const Command: string);
    function RunCommand(const AModule, AMethod: string; const AParams: TAppVariantArray): boolean;
    function RunEvent(const AModule, ACmd: string;
      AEd: TObject; const AParams: TAppVariantArray; ALazy: boolean): TAppPyEventResult;
    function RunModuleFunction(const AModule, AFunc: string; AParams: array of PPyObject; const AParamNames: array of string): PPyObject;
    function RunModuleFunction(const AModule, AFunc: string; AParams: array of PPyObject): PPyObject;

    function ValueFromString(const S: string): PPyObject;
    function ValueToString(Obj: PPyObject; QuoteStrings: boolean): string;

    procedure SetPath(const Dirs: array of string; DoAdd: boolean);
    procedure ClearCache;
    procedure DisableTiming;
    function GetTimingReport: string;
  end;

var
  AppPython: TAppPython;

implementation

{ TAppPython }

constructor TAppPython.Create;
begin
  inherited Create;
  LoadedLocals:= TStringList.Create;
  LoadedModules:= TStringList.Create;
  EventTimes:= TStringList.Create;
end;

destructor TAppPython.Destroy;
begin
  if Assigned(EventTimes) then
    FreeAndNil(EventTimes);
  FreeAndNil(LoadedModules);
  FreeAndNil(LoadedLocals);
  inherited Destroy;
end;

procedure TAppPython.Initialize;
begin
  FInited:= PythonOK;
  if FInited then
    FEngine:= GetPythonEngine;
end;

function TAppPython.IsLoadedLocal(const S: string): boolean; inline;
begin
  Result:= LoadedLocals.IndexOf(S)>=0;
end;

procedure TAppPython.DisableTiming;
begin
  FreeAndNil(EventTimes);
end;

function TAppPython.GetTimingReport: string;
var
  i: integer;
  tick: PtrInt;
begin
  Result:= IntToStr(EventTime div 10 * 10)+'ms (';
  for i:= 0 to EventTimes.Count-1 do
  begin
    tick:= PtrInt(EventTimes.Objects[i]);
    if i>0 then
      Result+= ', ';
    Result+=
      Copy(EventTimes[i], 6, MaxInt)+' '+
      IntToStr(tick)+'ms';
  end;
  Result+= ')';
end;

function TAppPython.Eval(const Command: string; UseFileMode: boolean=false): PPyObject;
var
  Mode: integer;
begin
  Result := nil;
  if not FInited then exit;

  if UseFileMode then
    Mode:= file_input
  else
    Mode:= eval_input;

  with FEngine do
  begin
    Traceback.Clear;
    CheckError(False);

    if MainModule=nil then
    begin
      MainModule:= GetMainModule;
      if MainModule=nil then
        raise EPythonError.Create('Python: can''t create __main__');
      //if _Locals=nil then
      //  _Locals:= PyModule_GetDict(MainModule);
      if Globals=nil then
        Globals:= PyModule_GetDict(MainModule);
    end;

    try
      //PythonEngine used PChar(CleanString(Command)) - is it needed?
      Result := PyRun_String(PChar(Command), Mode, Globals, Globals); //seems no need separate Locals
      if Result = nil then
        CheckError(False);
    except
      if PyErr_Occurred <> nil then
        CheckError(False)
      else
        raise;
    end;
  end;
end;

procedure TAppPython.Exec(const Command: string);
begin
  if not FInited then exit;
  with FEngine do
    Py_XDECREF(Eval(Command, true));
    //UseFileMode=True to allow running several statements with ";"
end;

function TAppPython.MethodEval(const AObject, AMethod, AParams: string): PPyObject;
begin
  Result:= Eval( Format('%s.%s(%s)', [AObject, AMethod, AParams]) );
end;

function TAppPython.MethodEvalEx(const AObject, AMethod, AParams: string): TAppPyEventResult;
var
  Obj: PPyObject;
begin
  Result.Val:= evrOther;
  Result.Str:= '';

  with FEngine do
  begin
    Obj:= MethodEval(AObject, AMethod, AParams);
    if Assigned(Obj) then
    try
      if Pointer(Obj)=Pointer(Py_True) then
        Result.Val:= evrTrue
      else
      if Pointer(Obj)=Pointer(Py_False) then
        Result.Val:= evrFalse
      {
      if Obj^.ob_type=PyBool_Type then<commit_msg>add func MethodEvalObjects, #2387<commit_after>(*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright (c) <NAME>
*)
unit proc_py;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Classes,
  PythonEngine,
  proc_str,
  proc_globdata,
  proc_appvariant;

type
  { TAppPython }

  TAppPython = class
  private const
    NamePrefix = 'xx'; //the same as in py/cudatext_reset_plugins.py
  private
    FInited: boolean;
    FEngine: TPythonEngine;
    FRunning: boolean;
    FLastCommandModule: string;
    FLastCommandMethod: string;
    FLastCommandParam: TAppVariant;
    EventTime: QWord;
    EventTimes: TStringList;
    LoadedLocals: TStringList;
    LoadedModuleCudatext: boolean;
    LoadedModules: TStringList;
    MainModule: PPyObject;
    Globals: PPyObject;
    procedure ImportCommand(const AObject, AModule: string);
    function ImportModuleCached(const AModule: string): PPyObject;
    function IsLoadedLocal(const S: string): boolean;
    function MethodEval(const AObject, AMethod, AParams: string): PPyObject;
    function MethodEvalEx(const AObject, AMethod, AParams: string): TAppPyEventResult;
    function MethodEvalObjects(const AModule, AObject, AFunc: string;
      AParams: array of PPyObject): PPyObject;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Initialize;
    property Inited: boolean read FInited;
    property Engine: TPythonEngine read FEngine;
    property IsRunning: boolean read FRunning;
    property LastCommandModule: string read FLastCommandModule;
    property LastCommandMethod: string read FLastCommandMethod;
    property LastCommandParam: TAppVariant read FLastCommandParam;

    function Eval(const Command: string; UseFileMode: boolean=false): PPyObject;
    procedure Exec(const Command: string);
    function RunCommand(const AModule, AMethod: string; const AParams: TAppVariantArray): boolean;
    function RunEvent(const AModule, ACmd: string;
      AEd: TObject; const AParams: TAppVariantArray; ALazy: boolean): TAppPyEventResult;
    function RunModuleFunction(const AModule, AFunc: string; AParams: array of PPyObject; const AParamNames: array of string): PPyObject;
    function RunModuleFunction(const AModule, AFunc: string; AParams: array of PPyObject): PPyObject;

    function ValueFromString(const S: string): PPyObject;
    function ValueToString(Obj: PPyObject; QuoteStrings: boolean): string;

    procedure SetPath(const Dirs: array of string; DoAdd: boolean);
    procedure ClearCache;
    procedure DisableTiming;
    function GetTimingReport: string;
  end;

var
  AppPython: TAppPython;

implementation

{ TAppPython }

constructor TAppPython.Create;
begin
  inherited Create;
  LoadedLocals:= TStringList.Create;
  LoadedModules:= TStringList.Create;
  EventTimes:= TStringList.Create;
end;

destructor TAppPython.Destroy;
begin
  if Assigned(EventTimes) then
    FreeAndNil(EventTimes);
  FreeAndNil(LoadedModules);
  FreeAndNil(LoadedLocals);
  inherited Destroy;
end;

procedure TAppPython.Initialize;
begin
  FInited:= PythonOK;
  if FInited then
    FEngine:= GetPythonEngine;
end;

function TAppPython.IsLoadedLocal(const S: string): boolean; inline;
begin
  Result:= LoadedLocals.IndexOf(S)>=0;
end;

procedure TAppPython.DisableTiming;
begin
  FreeAndNil(EventTimes);
end;

function TAppPython.GetTimingReport: string;
var
  i: integer;
  tick: PtrInt;
begin
  Result:= IntToStr(EventTime div 10 * 10)+'ms (';
  for i:= 0 to EventTimes.Count-1 do
  begin
    tick:= PtrInt(EventTimes.Objects[i]);
    if i>0 then
      Result+= ', ';
    Result+=
      Copy(EventTimes[i], 6, MaxInt)+' '+
      IntToStr(tick)+'ms';
  end;
  Result+= ')';
end;

function TAppPython.Eval(const Command: string; UseFileMode: boolean=false): PPyObject;
var
  Mode: integer;
begin
  Result := nil;
  if not FInited then exit;

  if UseFileMode then
    Mode:= file_input
  else
    Mode:= eval_input;

  with FEngine do
  begin
    Traceback.Clear;
    CheckError(False);

    if MainModule=nil then
    begin
      MainModule:= GetMainModule;
      if MainModule=nil then
        raise EPythonError.Create('Python: can''t create __main__');
      //if _Locals=nil then
      //  _Locals:= PyModule_GetDict(MainModule);
      if Globals=nil then
        Globals:= PyModule_GetDict(MainModule);
    end;

    try
      //PythonEngine used PChar(CleanString(Command)) - is it needed?
      Result := PyRun_String(PChar(Command), Mode, Globals, Globals); //seems no need separate Locals
      if Result = nil then
        CheckError(False);
    except
      if PyErr_Occurred <> nil then
        CheckError(False)
      else
        raise;
    end;
  end;
end;

procedure TAppPython.Exec(const Command: string);
begin
  if not FInited then exit;
  with FEngine do
    Py_XDECREF(Eval(Command, true));
    //UseFileMode=True to allow running several statements with ";"
end;

function TAppPython.MethodEvalObjects(const AModule, AObject, AFunc: string; AParams: array of PPyObject): PPyObject;
var
  Module,ModuleDic,CurrObject,Func,Params: PPyObject;
  i: integer;
begin
  Result:=nil;
  if not FInited then exit;
  with FEngine do
  begin
    Module:=ImportModuleCached(AModule);
    if Assigned(Module) then
    try
      ModuleDic:=PyModule_GetDict(Module);
      if Assigned(ModuleDic) then
      begin
        CurrObject:=PyDict_GetItemString(ModuleDic,PChar(AObject));
        if Assigned(CurrObject) then
        begin
          Func:=PyObject_GetAttrString(CurrObject,PChar(AFunc));
          if Assigned(Func) then
            try
              Params:=PyTuple_New(Length(AParams)+1);
              if Assigned(Params) then
              try
                PyTuple_SetItem(Params,0,CurrObject);
                for i:=1 to Length(AParams) do
                  if PyTuple_SetItem(Params,i,AParams[i-1])<>0 then
                    RaiseError;
                Result:=PyObject_Call(Func,Params,nil);
              finally
                Py_DECREF(Params);
              end;
            finally
              Py_DECREF(Func);
            end;
        end;
      end;
    finally
    end;
  end;
end;

function TAppPython.MethodEval(const AObject, AMethod, AParams: string): PPyObject;
begin
  Result:= Eval( Format('%s.%s(%s)', [AObject, AMethod, AParams]) );
end;

function TAppPython.MethodEvalEx(const AObject, AMethod, AParams: string): TAppPyEventResult;
var
  Obj: PPyObject;
begin
  Result.Val:= evrOther;
  Result.Str:= '';

  with FEngine do
  begin
    Obj:= MethodEval(AObject, AMethod, AParams);
    if Assigned(Obj) then
    try
      if Pointer(Obj)=Pointer(Py_True) then
        Result.Val:= evrTrue
      else
      if Pointer(Obj)=Pointer(Py_False) then
        Result.Val:= evrFalse
      {
      if Obj^.ob_type=PyBool_Type then
{'fraction_non_alphanumeric': 0.09092372063083361, 'fraction_numerical': 0.0028162214354682975, 'mean_word_length': 3.6955043445409896, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7270583', 'n_tokens_mistral': 3998, 'n_tokens_neox': 3889, 'n_words': 1088}