翻譯這篇文章源於我的一個通用工資計算平台的想法,在工資的計算中,不可避免的需要使用到自定義公式,然而對於自定義公式的實現,我自己想了一些,也在網上搜索了很多,解決辦法大致有以下幾種:
1. 自己寫代碼去解析公式。這種方法的缺點是,解析的代碼很難實現,如果公式的功能比較完整,如增加條件判斷或自定義函數。不亞於實現了一個簡單的語言編譯囂或解釋囂。所以,只能實現一些諸如加減乘除之類的簡單公式。
2. 打包成SQL傳給數據庫去執行。這顯然不是一種好辦法。而且需要與特定的數據庫和表結構進行適應。
3. 我想到在foxpro中有宏替換功能&,那不如就借用它的這個功能,即利用foxpro寫一個dll,在這個dll中實現了將字符串轉換成指令執行的功能,然後在delphi中加載這個dll,將公式傳入dll中的函數執行。這應該是一個辦法,但我還沒有去實現它。
4. 內嵌腳本語言。
也只有第四種辦法比較理想的,於是我就找到了RemObjects Pascal Script,安裝,並翻譯了這篇使用說明。
再把應用范圍擴大一點,其實在編譯型程序中嵌入腳本語言可以解決很多應用程序自動化的問題,在了解並實際寫了幾個RemObjects Pascal Script的實從程序後。內心還是蠻興奮的。
PS01 - Using the RemObjects Pascal Script
使用RemObjects Pascal Script
This article provides an overview of the new RemObjects Pascal Script and explains how to create some simple scripts.
這篇文章提供了RemObjects Pascal Script的一個概覽,以及說明了如何去創建一些簡單的腳本。
Pascal Script comprises two different parts:
Compiler (uPSCompiler.pas)
Runtime (uPSRuntime.pas)
Pascal Script由兩個部分組成:
編譯囂(UPSCompiler.pas)
運行時(uPSRuntime.pas)
The two parts have no interdependencies on each other. You can use them directly, or you can use them in the TPSScript component, which can be found in the uPSComponent.pas unit, and wraps them both in one easy to use class.
這兩部分之間是沒有相互依賴的。你可以直接使用她們,或才你可以透過TPSSCript組件來使用她們,TPSSCript組件在uPSComponent.pas單元中,她對上述兩個部分進行一些包裝以便我們可以很容易的使用。
To use the component version of Pascal Script, you must first place it on your form or data module, set or assign the script property, call the Compile method, and call the Execute method. Compile errors, warnings or hints can be found in the CompilerMessages array property, while runtime errors can be found by reading the ExecErrorToString property.
要使用Pascal Script組件,你首先要將它從組件面板中拖置窗體或module中,然後設置它的script屬性,然後調用它的Compile方法進行編譯,再然後調用它的Execute方法來執行腳本。編譯的errors,hints,warnings可以通過其屬性CompilerMessages取得,這個屬性是一個數組。如果是運行時的錯誤,則可以通過屬性ExecErrorToString取得。
The following example will compile and execute an empty script ("begin end."):
下面的例子將編譯並執行一個空腳本("begin end."):
var
Messages: string;
compiled: boolean;
begin
ce.Script.Text := 'begin end.';
Compiled := Ce.Compile;
for i := 0 to ce.CompilerMessageCount -1 do
Messages := Messages +
ce.CompilerMessages[i].MessageToString +
#13#10;
if Compiled then
Messages := Messages + 'Succesfully compiled'#13#10;
ShowMessage('Compiled Script: '#13#10+Messages);
if Compiled then begin
if Ce.Execute then
ShowMessage('Succesfully Executed')
else
ShowMessage('Error while executing script: '+
Ce.ExecErrorToString);
end;
end;
By default, the component only adds a few standard functions to the scripting engine (the exact list can be found at the top of uPSComponents.pas).
缺省情況下,組件只加入一少部分標准的functions到腳本引擎中(具體可以在uPSComponents.pas單元頭中找到)
Besides the standard functions, there are a few libraries included with Pascal Script:
TPSDllPlugin Allow scripts to use dll functions, the syntax is like:
function FindWindow(C1, C2: PChar): Longint; external 'FindWindowA@user32.dll stdcall';
TPSImport_Classes Import library for TObject and the Classes unit.
TPSImport_DateUtils Import library for date/time related functions.
TPSImport_ComObj Access COM Objects from your scripts.
TPSImport_DB Import library for db.pas.
TPSImport_Forms Import library for the Forms & Menus units.
TPSImport_Controls Import library to Controls.pas and Graphics.pas.
TPSImport_StdCtrls Import library for ExtCtrls and Buttons.
除了這些標准的functions之外,Pascal Script還包含了一少部分程式庫:
TPSDllPlugin 允許腳本可以使用外部DLL函數,其調用語法類似下例:
function FindWindow(C1, C2: PChar): Longint; external 'FindWindowA@user32.dll stdcall';
TPSImport_Classes 導入對應於TObject和Classes單元的libraries;
TPSImport_DateUtils 導入日期時間相關的libraries;
TPSImport_ComObj 在腳本中訪問COM對象;
TPSImport_DB 導入對應於db.pas單元的libraries;
TPSImport_Forms 導入對應於Forms和Menus單元的libraries;
TPSImport_Controls 導入對應於Controls.pas和Graphics.pas單元的libraries;
TPSImport_StdCtrls 導入對應於ExtCtrls和Buttons的libraries.
To use these libraries, add them to your form or data module, select the [...] button next to the plugins property of the TPSCompiler component, add a new item and set the Plugin property to the plugin component.
要使用這些libraries,將它們從組件面板中拖至窗體數據data module中,然後設置TPSCompiler的plugins屬性,在其中增加條目,並將條目指向這些plugin組件。
Besides the standard libraries, you can easily add new functions to the scripting engine. In order to do that, create a new method you would like to expose to the scripting engine, for example:
除了這些標准的libraries之外,你還可以很方便地向腳本引擎中添加新的函數。要做到這一點,創建一個你要加入到腳本中的method,如下例:
procedure TForm1.ShowNewMessage(const Message: string);
begin
ShowMessage('ShowNewMessage invoked:'#13#10+Message);
end;
Then, assign an event handler to the OnCompile event and use the AddMethod method of TPSCompiler to add the actual method:
然後,在TPSCompiler 的OnCompile事件中將該方法添加入腳本中:
procedure TForm1.CECompile(Sender: TPSScript);
begin
Sender.AddMethod(Self, @TForm1.ShowNewMessage,
'procedure ShowNewMessage
(const Message: string);');
end;
A sample script that uses this function could look like this:
這樣, 你就可以在腳本中使用這個函數,如下:
begin
ShowNewMessage('Show This !');
end.
Advanced Features
高級功能
Pascal Script includes a preprocessor that allows you to use defines ({$IFDEF}, {$ELSE}, {$ENDIF}) and include other files in your script ({$I filename.inc}). To enable these features, you must set the UsePreprocessor property to true and the MainFileName property to match the name of the script in the Script property. The Defines property specifies which defines are set by default, and the OnNeedFile event is called when an include file is needed.
Pascal Script包含了一個預處理程序,以便你可以在腳本中使用編譯預定義(defines)({$IFDEF}, {$ELSE}, {$ENDIF}) 以及在腳本中包含其它腳本 ({$I filename.inc})。要達到這個功能,你需要設置UsePreprocessor屬性為true,and the MainFileName property to match the name of the script in the Script property. 。Defines屬性指定要缺省時定義哪些defines;OnNeedFile事件代碼在需要包含的文件時被執行。
function TForm1.ceNeedFile(Sender: TObject;
const OrginFileName: String;
var FileName, Output: String): Boolean;
var
path: string;
f: TFileStream;
begin
Path := ExtractFilePath(ParamStr(0)) + FileName;
try
F := TFileStream.Create(Path, fmOpenRead or fmShareDenyWrite);
except
Result := false;
exit;
end;
try
SetLength(Output, f.Size);
f.Read(Output[1], Length(Output));
finally
f.Free;
end;
Result := True;
end;
When these properties are set, the CompilerMessages array property will include the file name these messages occur in.
當這些屬性被設置以後,CompilerMessages屬性可就將可能包含這些文件名。
Additionally, you can call scripted functions from Delphi. The following sample will be used as a script:
另外,你可以在Delphi中調用腳本裡的函數。如下函數被定義在腳本中,後面將會在delphi中被調用:
function TestFunction(Param1: Double; Data: String): Longint;
begin
ShowNewMessage('Param1: '+FloatToString(param1)
+#13#10+'Data: '+Data);
Result := 1234567;
end;
begin
end.
Before this scripted function can be used, it has to be checked to match its parameter and result types, which can be done in the OnVerifyProc event.
在使用調用這個函數之前,必須對其進行一個校驗,校驗其參數和返回值類型,在OnVerifyProc執行這個校驗。
procedure TForm1.CEVerifyProc(Sender: TPSScript;
Proc: TPSInternalProcedure;
const Decl: String;
var Error: Boolean);
begin
if Proc.Name = 'TESTFUNCTION' then begin
if not ExportCheck(Sender.Comp, Proc,
[btS32, btDouble, btString], [pmIn, pmIn]) then begin
Sender.Comp.MakeError('', ecCustomError, 'Function header for
TestFunction does not match.');
Error := True;
end
else begin
Error := False;
end;
end
else
Error := False;
end;
The ExportCheck function checks if the parameters match. In this case, btu8 is a boolean (the result type), btdouble is the first parameter, and btString the second parameter. [pmIn, pmIn] specifies that both parameters are IN parameters. To call this scripted function, you have to create an event declaration for this function and call that.
ExportCheck函數檢查參數的匹配情況。在這個例子中,btu8是一個布爾型(返回值類型),btdouble是第一個參數,btString是第二個參數。[pmIn, pmIn]表示兩個參數都是輸入參數。要調用這個腳本函數,你需要為它創建一個函數類型聲明。
type
TTestFunction = function (Param1: Double;
Data: String): Longint of object;
//...
var
Meth: TTestFunction;
Meth := TTestFunction(ce.GetProcMethod('TESTFUNCTION'));
if @Meth = nil then
raise Exception.Create('Unable to call TestFunction');
ShowMessage('Result: '+IntToStr(Meth(pi, DateTimeToStr(Now))));
It's also possible to add variables to the script engine, which can be used from within the script. To do this, you have to use the AddRegisteredVariable function. You can set this in the OnExecute event :
還可以向腳本引擎中添加變量,然後就可以在腳本中使用這些變量 。要做到這一點,你需要使用AddRegisteredVariable函數。可以在OnExecute設置它:
procedure TForm1.ceExecute(Sender: TPSScript);
begin
CE.SetVarToInstance('SELF', Self);
// ^^^ For class variables
VSetInt(CE.GetVariable('MYVAR'), 1234567);
end;
To read this variable back, after the script has finished executing, you can use the OnAfterExecute event:
若要再去讀取這個變量的值,在腳本執行完成後,在OnAfterExecute事件中訪問:
VGetInt(CE.GetVariable('MYVAR')).
Registering external variables to the script engine is also possible. It's a two step process, first, in the OnCompile event, add the types to the script using the AddRegisteredPTRVariable function.
注冊一個外部變量到腳本引擎中也是可以的。這需要兩個步驟,首先在OnCompile事件中使用AddRegisteredPTRVariable函數將變量類型添加到腳本中。
procedure TMyForm.PSScriptCompile(Sender: TPSScript);
begin
Sender.AddRegisteredPTRVariable('MyClass', 'TButton');
Sender.AddRegisteredPTRVariable('MyVar', 'Longint');
end;
This will register the external MyClass and MyVar variables. Second, attach a pointer to these variables in the OnExecute event:
這樣就注冊了MyClass 和 MyVar這兩個變量。第二步,在OnExecute中通過將變量值的地址指針傳給變量來實現給變量賦值:
procedure TMyForm.PSScriptExecute(Sender: TPSScript);
begin
PSScript.SetPointerToData('MyVar', @MyVar, PSScript.FindBaseType(bts32));
PSScript.SetPointerToData('Memo1', @Memo1, PSScript.FindNamedType('TMemo'));
end;
There are two types of variables in Pascal Script, base types, which are simple types (see the table below), and class types. Base types are registered in the uPSUtils.pas unit and can be located using the FindBaseType function. Class types have to be located by name, using the FindNamedType. Changes to these variables have a direct effect on the actual variable.
在Pascal Script中有兩種類型的變量,一種是基本類型,包含一些簡單的類型,下面會列出;另一種是類類型。基本類型是在uPSUtils.pas被注冊進去的,可以通過FindBaseType函數找到。類類型需要使用FindNamedType函數通過名稱找到。改變這些變量將直接地影響到實際的變量。
Base types:
btU8 Byte
btS8 Shortint
btU16 Word
btS16 Smallint
btU32 Longword
btS32 Longint
btS64 Int64
btSingle Single
btDouble Double
btExtended Extended
btVariant Variant
btString String
btWideString WideString
btChar Char
btWideChar WideChar
The component version of Pascal Script also supports execution of scripted functions. This works by using the ExecuteFunction method.
Pascal Script組件同樣也支持腳本函數。這通過ExecuteFunction來調用。
ShowMessage(CompExec.ExecuteFunction([1234.5678, 4321,
'test'],
'TestFunction'));
This will execute the function called 'TestFunction' with 3 parameters, a float, an integer and a string. The result will be passed back to ShowMessage.
這個例子將執行一個名為TestFunction的函數,這個函數包含3個參數,一個float,一個integer和一個string。函數的返回值傳回給ShowMessage。
Notes:
For some functions and constants, it might be necessary to add: uPSCompiler.pas, uPSRuntime.pas and/or uPSUtils.pas to your uses list.
The script engine never calls Application.ProcessMessages by itself, so your application might hang, while the script is running. To avoid this, you can add Application.ProcessMessages to the TPSScript.OnLine event.
It's possible to import your own classes in the script engine. Pascal Script includes a tool to create import libraries in the /Unit-Importing/ directory.
It's possible to import your own classes in the script engine. Pascal Script includes a tool to create import libraries in the Bin directory.
For examples on how to use the compiler and runtime separately, see the Import and Kylix samples.
The Debug requires SynEdit http://synedit.sourceforge.net/.
注意:
一些必要的函數和常量應該被加入到uses 列表中:uPSCompiler.pas, uPSRuntime.pas, uPSUtils.pas;
腳本引擎不會自行調用Application.ProcessMessages,因此在腳本執行時你的應用程序可能會終止。要避免這一點,你可以將Application.ProcessMessages加入到TPSScript的OnLine事件中;
可能需要在腳本中引入你自己的類,Pascal Script包含一個工具以便創建引入的庫,這個工具在Unit-Importing目錄中;
可能需要在腳本中引入你自己的類,Pascal Script包含一個工具以便創建引入的庫,這個工具在Bin目錄中;
安裝目錄中可以找到單獨使用Comiler和Runtim的實例;
腳本調試需要SynEdit http://synedit.sourceforge.net/.
--------------------
fey
2007/2/27夜
- FPGA开发中的脚本语言
多数FPGA开发者都习惯图形化界面(GUI).GUI方式简单易学,为小项目提供了一键式流程.然而,随着FPGA项目越来越复杂,在很多情况下GUI工具就阻碍了工作效率.因为GUI工具不能对整个开发过程提 ...
- 使用RemObjects Pascal Script (转)
http://www.cnblogs.com/MaxWoods/p/3304954.html 摘自RemObjects Wiki 本文提供RemObjects Pascal Script的整体概要并演 ...
- 使用RemObjects Pascal Script
摘自RemObjects Wiki 本文提供RemObjects Pascal Script的整体概要并演示如何创建一些简单的脚本. Pascal Script包括两个不同部分: 编译器 (uPSCo ...
- 第七节 JBPM 中的脚本语言
1.JPDL表达式 2.动作:数据库操作例子 3.路由:transaction一个流程之间的指向 4.BeanShell脚本语言 例子: 发布到数据库中才能做一个测试类
- 基于 Go 的可嵌入脚本语言 zygomys
zygomys zygomys 是一种可嵌入的脚本语言. 它是一个具有面向对象风格的现代化 Lisp,提供了一个解释器和 REPL(Read-Eval-Print-Loop:也就是说,它带有一个命令行 ...
- JS脚本语言(全称java script:网页里使用的脚本语言:非常强大的语言):基础语法
一.注释语法 1.单行注释// 2.多行注释/**/ 二.语法输出 1.alert(信息):弹出信息 2.confirm(信息):弹出一个和用户交互的对话框 3.prompt(信息):弹出一个可以让用 ...
- 测试RemObjects Pascal Script
unit Unit1; interface usesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, ...
- 关于.net中的脚本语言使用
基于.net中drl框架的脚本现在有很多,最近也由于工作的需要,目前有lua.python.ruby.javascript的.net实现,对ruby不怎么了解,python.lua.js就成了试验的对 ...
- 在HTML网页中嵌入脚本的方式
在HTML标记的事件属性中直接添加脚本 <!doctype html> <html> <head> <meta charset="utf-8&quo ...
随机推荐
- springboot中url地址重写(urlwrite)
在日常网站访问中,会把动态地址改造成伪静态地址. 例如: 访问新闻栏目 /col/1/,这是原有地址,如果这样访问,不利于搜索引擎检索收录,同时安全性也不是很好. 改造之后: /col/1.html. ...
- pyqt5-组件
组件(widgets)是构建一个应用的基础模块.PyQt5有广泛的各式各样的组件,包含:复选按钮(QCheckBox),切换按钮(ToggleButton),滑块条(QSlider),进度条(Prog ...
- HttpClient 详解一《C#高级编程(第9版)》
1.异步调用 Web 服务 static void Main(string[] args) { Console.WriteLine("In main before call to GetDa ...
- Java编程的逻辑 (14) - 类的组合
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Intellij IDEA Debug调试技巧
1.这里以一个web工程为例,点击图中按钮开始运行web工程. 2.设置断点 3.使用postman发送http请求 4.请求发送之后会自动跳到断点处,并且在断点之前会有数据结果显示 5.按F8 在 ...
- Swagger+IdentityServer4测试授权验证
1.Bearer授权操作,添加如下代码 services.AddSwaggerGen(options => { options.AddSecurityDefinition("Beare ...
- Angular 快速学习笔记(1) -- 官方示例要点
创建组件 ng generate component heroes {{ hero.name }} {{}}语法绑定数据 管道pipe 格式化数据 <h2>{{ hero.name | u ...
- 利用yum安装时,报错 [Errno 256] No more mirrors to try.
问题: [root@gg ~]# yum install -y perl-DBD-MySQL Loaded plugins: product-id, refresh-packagekit, secu ...
- 深入理解指针—>指针函数与函数指针的区别
一. 在学习过程中发现这"指针函数"与"函数指针"容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1.指针函数是指带指针的函数, ...
- 解决Mac java.net Local host name unknown error的方法
解决这个问题的方法: scutil --set HostName "localhost" 解决Mac java.net Local host name unknown error ...