文章目錄
第九章 瘋狂Caché 宏和宏預處理器(三) - 使用系統提供的宏
- 可訪問系統提供的宏
- 系統支持的宏引用
- ADDSC(sc1, sc2)
- EMBEDSC(sc1, sc2)
- ERROR(errorcode, arg1, arg2, ...)
- FormatMessage(language,domain,id,default,arg1,arg2,...)
- FormatText(text, arg1, arg2, ...)
- FormatTextHTML(text, arg1, arg2, ...)
- FormatTextJS(text, arg1, arg2, ...)
- GETERRORCODE(sc)
- ISERR(sc)
- ISOK(sc)
- OK
- Text(text, domain, language)
- TextHTML(text, domain, language)
- TextJS(text, domain, language)
- ThrowOnError(sc)
- THROWONERROR(sc, expr)
- ThrowStatus(sc)
第九章 瘋狂Caché 宏和宏預處理器(三)
#SQLCompile Select
#SQLCompile Select
預處理器指令指定任何後續嵌入式SQL語句的數據格式模式。它的形式是:
#SQLCompile Select=value
-
Display-格式化用於屏幕和打印的數據。
-
Logical — 將數據保留在內存中的格式。並打印。
-
ODBC — 格式化數據以通過ODBC或JDBC表示。
-
Runtime — 支持根據執行時選擇模式值將輸入數據值從顯示格式(顯示或ODBC)自動轉換爲邏輯存儲格式。輸出值將轉換爲當前模式。可以使用
%SYSTEM.SQL
類的GetSelectMode
方法獲取執行時選擇模式值。可以使用%SYSTEM.SQL
類的SetSelectMode
方法設置執行時選擇模式值。 -
Text — 顯示的同義詞。
-
FDBMS — 允許嵌入式SQL以與FDBMS相同的方式格式化數據。
此宏的值確定SELECT OUTPUT主機變量的嵌入式SQL輸出數據格式,以及嵌入式SQL INSERT、UPDATE和SELECT INPUT主機變量所需的輸入數據格式。
下面的嵌入式sql示例使用不同的編譯模式從Sample.Person表返回三個字段,即Name(字符串字段)、DOB(日期字段)和Home(列表字段):
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILE()
ClassMethod TestSQLCOMPILE()
{
#SQLCOMPILE SELECT=Logical
&sql(SELECT Name,DOB,Home
INTO :n,:d,:h
FROM Sample.Person)
WRITE "name is: ",n,!
WRITE "birthdate is: ",d,!
WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILE()
name is: yaoxin
birthdate is: 54536
home is: 889 Clinton Drive
St LouisWI78672
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEDisplay()
ClassMethod TestSQLCOMPILEDisplay()
{
#SQLCOMPILE SELECT=Display
&sql(SELECT Name,DOB,Home
INTO :n,:d,:h
FROM Sample.Person)
WRITE "name is: ",n,!
WRITE "birthdate is: ",d,!
WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEDisplay()
name is: yaoxin
birthdate is: 04/25/1990
home is: 889 Clinton Drive
St LouisWI78672
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEODBC()
ClassMethod TestSQLCOMPILEODBC()
{
#SQLCOMPILE SELECT=ODBC
&sql(SELECT Name,DOB,Home
INTO :n,:d,:h
FROM Sample.Person)
WRITE "name is: ",n,!
WRITE "birthdate is: ",d,!
WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEODBC()
name is: yaoxin
birthdate is: 1990-04-25
home is: 889 Clinton Drive,St Louis,WI,7867
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILERuntime()
ClassMethod TestSQLCOMPILERuntime()
{
#SQLCOMPILE SELECT=Runtime
&sql(SELECT Name,DOB,Home
INTO :n,:d,:h
FROM Sample.Person)
WRITE "name is: ",n,!
WRITE "birthdate is: ",d,!
WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILERuntime()
name is: yaoxin
birthdate is: 54536
home is: 889 Clinton Drive
St LouisWI78672
#UnDef
#UnDef
預處理器指令刪除已定義的宏的定義。它的形式是:
#UnDef macro-name
其中宏名稱是已經定義的宏。
#UnDef
跟隨#Define
或#Def1Arg
的調用。它與#IfDef
及其相關的預處理器指令(#Else
、#EndIf
和#IfNDef
)協同工作。
下面的示例演示有條件的代碼,條件是先定義宏,然後再取消定義。
/// d ##class(PHA.TEST.ObjectScript).TestUnDef()
ClassMethod TestUnDef()
{
#Define TheSpecialPart
#IfDef TheSpecialPart
WRITE "We're in the special part of the program.",!
#EndIf
#UnDef TheSpecialPart
#IfDef TheSpecialPart
WRITE "We're in the special part of the program.",!
#Else
WRITE "We're no longer in the special part of the program.",!
#EndIf
#IfNDef TheSpecialPart
WRITE "We're still outside the special part of the program.",!
#Else
WRITE "We're back inside the special part of the program.",!
#EndIf
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestUnDef()
We're in the special part of the program.
We're no longer in the special part of the program.
We're still outside the special part of the program.
其中,用於此操作的.int代碼爲:
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestUnDef()
We're in the special part of the program.
We're no longer in the special part of the program.
We're still outside the special part of the program.
##;
##;
預處理指令使當前行的其餘部分成爲未出現在.int代碼中的註釋。註釋僅出現在.mac代碼或包含文件中。##;
註釋指示符應始終用於預處理器指令中的註釋:
#Define alphalen ##Function($LENGTH("abcdefghijklmnopqrstuvwxyz")) ##; + 100
WRITE $$$alphalen," is the length of the alphabet"
##
;註釋指示符可以出現在#DEFINE
、#Def1Arg
或#Dim
預處理器指令中。它不能在##CONTINUE
預處理器指令之後使用。在預處理器指令中應避免使用//
或;行內註釋。
##;
還可以在ObjectScript代碼行或嵌入式SQL代碼行的任何位置使用,以指定未出現在.int代碼中的註釋。當前行的其餘部分將繼續註釋。
##;
在嵌入式HTML或嵌入式JavaScript之前進行計算。
與#;
相比,#;
出現在第1列,並使整行成爲註釋。##;
使當前行的其餘部分成爲註釋。當##;
出現在該行的第一列中時,它在功能上與#;
預處理器指令相同。
##Continue
##CONTINUE
預處理器指令在下一行繼續宏定義,以支持多行宏定義。它出現在宏定義的行尾,表示繼續,格式爲:
#Define <beginning of macro definition> ##Continue
<continuation of macro definition>
一個宏定義可以使用多個##CONTINUE指令。
#Define Multiline(%a,%b,%c) ##Continue
SET v=" of Oz" ##Continue
SET line1="%a"_v ##Continue
SET line2="%b"_v ##Continue
SET line3="%c"_v
$$$Multiline(Scarecrow,Tin Woodman,Lion)
WRITE "Here is line 1: ",line1,!
WRITE "Here is line 2: ",line2,!
WRITE "Here is line 3: ",line3,!
##Continue
必須出現在宏定義行的末尾。因此,##CONTINUE
不能後跟##;
註釋或/註釋文本/註釋。#;
整行註釋也不能在##CONTINUE
多行指令中使用。可以按如下方式註釋##Continue
行:
#Define Multiline(%a,%b,%c) ##Continue
SET v=" of Oz" /* set a variable to a string */ ##Continue
SET line1="%a"_v ##Continue
SET line2="%b"_v ##Continue
SET line3="%c"_v
##Expression
##Expression預處理器函數在編譯時計算ObjectScript表達式。它的形式是:
##Expression(content)
其中,content是不包含任何帶引號的字符串或任何預處理器指令的有效ObjectScript代碼(嵌套的##Expression
除外,如下所述)。
預處理器在編譯時計算指令參數的值,並將##Expression
(內容)替換爲ObjectScript.int代碼中的計算結果。變量必須出現在##Expression
內的引號中;否則,將在編譯時計算它們。##Expression
在計算嵌入式HTML或嵌入式JavaScript之前會先計算表達式。
以下示例顯示了一些簡單的表達式:
/// d ##class(PHA.TEST.ObjectScript).TestExp()
ClassMethod TestExp()
{
#Define NumFunc ##Expression(1+2*3)
#Define StringFunc ##Expression("""This is"_" a concatenated string""")
WRITE $$$NumFunc,!
WRITE $$$StringFunc,!
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestExp()
9
This is a concatenated string
下面的示例定義一個包含當前例程的編譯時間戳的表達式:
#Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
WRITE $$$CompTS
Compiled: 03/26/2020 14:09:11
其中,##Expression
的參數被分成三個部分進行解析,並使用“_
”運算符進行連接:
- 初始字符串
"""Compiled: "
。它由雙引號分隔。其中,這對雙引號指定求值後出現的雙引號。 - 值
$ZDATETIME($HOROLOG)
。由$ZDATETIME
函數轉換和格式化的$HOROLOG
特殊變量在編譯時的值。 - 最後一個字符串,
""",!"
。這也是用雙引號分隔的。其中有一對雙引號(求值後會產生一個單雙引號)。由於正在定義的值將傳遞給寫入命令,因此最終字符串包括,!
,因此寫入命令包含回車符。
然後,例程的中間(.int)代碼將包括一行,如下所示:
WRITE "Compiled: 05/19/2014 07:49:30",!
##Expression
和文字字符串
使用##Expression
式進行分析不能識別文字字符串;引號內的括號字符不會被特殊處理。例如,在指令中
#Define MyMacro ##Expression(^abc(")",1))
帶引號的右括號被視爲用於指定參數的右括號。
##Expression
嵌套
Caché 支持表達式嵌套。可以定義包含展開到其他##Expression
的宏的##Expression
,只要可以在ObjectScript級別(即,它不包含預處理器指令)計算展開並將其存儲在ObjectScript變量中。對於嵌套的##Expression
,首先展開帶有##Expression
的宏,然後展開嵌套的##Expression
。不能在##Expression
中嵌套其他##預處理器函數。
##Expression
、子類、##SafeExpression
如果方法包含##Expression
,則在編譯類時會檢測到這一點。由於編譯器不解析##Expression
的內容,因此此##Expression
可能會在子類中生成不同的代碼。爲了避免這種情況,Caché使編譯器爲每個子類重新生成方法代碼。例如,##Expression
(%classname
)插入當前類名;當編譯子類時,代碼期望它插入子類classname。Caché強制在子類中重新生成此方法,以確保發生這種情況。
如果知道子類中的代碼永遠不會不同,則可以避免爲每個子類重新生成方法。爲此,請用##SafeExpression
預處理器指令替換##表達式。這兩個預處理器指令在其他方面是相同的。
##Expression
如何工作的
##Expression
的參數通過ObjectScript XECUTE
命令設置爲一個值:
SET value="Set value="_expression XECUTE value
其中,Expression是確定Value的值的ObjectScript表達式,不能包含宏或##Expression
指令。
但是,XECUTE
值的結果可能包含宏和/或另一個##Expression
。ObjectScript預處理器進一步擴展其中的任何一個,如本例所示。
假設例程A.mac的內容包括:
#Define BB ##Expression(10_"_"_$$aa^B())
SET CC = $$$BB
QUIT
例程B.mac包括:
aa()
QUIT "##Expression(10+10+10)"
然後,A.int包括以下內容:
SET CC = 10_30
QUIT
##Function
##Function
預處理器函數在編譯時計算ObjectScript函數。它有這樣的形式
##Function(content)
其中,Content是一個ObjectScript函數,可以由用戶定義。##Function
將##Function(Content)
替換爲該函數的返回值。
下面的示例從ObjectScript函數返回值:
#Define alphalen ##Function($LENGTH("abcdefghijklmnopqrstuvwxyz"))
WRITE $$$alphalen
```java
在下面的示例中,假設GetCurrentTime.mac文件中有一個用戶定義的函數:
Tag1()
KILL ^x
SET ^x = “”"" _ $Horolog _ “”""
QUIT ^x
然後,可以在名爲ShowTimeStamps.mac的單獨例程中調用此代碼,如下所示:
```java
Tag2
#Define CompiletimeTimeStamp ##function($$Tag1^GetCurrentTime())
#Define RuntimeTimeStamp $$Tag1^GetCurrentTime()
SET x=$$$CompiletimeTimeStamp
WRITE x,!
SET y=$$$RuntimeTimeStamp
WRITE y,!
```java
該命令在終端的輸出類似於:
USER>d ^ShowTimeStamps
60569,43570
“60569,53807”
其中,第一行輸出是編譯時`$Horolog`的值,第二行是運行時`$Horolog`的值。(輸出的第一行不帶引號,第二行帶引號,因爲x用帶引號的字符串代替其值,因此終端中不顯示引號,而y將帶引號的字符串直接打印到終端。)
注意:在給定調用上下文的情況下,應用程序編程人員有責任確保`##Function`調用的返回值具有語義和語法意義。
## `##Lit`
`##LIT`預處理器函數以文字形式保留其參數的內容:
```java
##Lit(content)
其中,Content是有效的ObjectScript表達式字符串。##LIT
預處理器指令確保它接收到的字符串不會被求值,而是被視爲文字文本。
#Define Macro1 "Row 1 Value"
#Define Macro2 "Row 2 Value"
##Lit(;;) Column 1 Header ##Lit(;) Column 2 Header
##Lit(;;) Row 1 Column 1 ##Lit(;) $$$Macro1
##Lit(;;) Row 2 Column 1 ##Lit(;) $$$Macro2
在.int代碼中創建組成表格的一組行:
;; Column 1 Header ; Column 2 Header
;; Row 1 Column 1 ; "Row 1 Value"
;; Row 2 Column 1 ; "Row 2 Value"
通過使用##LIT
指令,宏被求值,並由.int代碼中的分號分隔
##Quote
##Quote
預處理器函數接受單個參數並返回引用的參數。如果參數已經包含引號字符,則通過將它們加倍來轉義這些引號字符。它的形式是:
##Quote(value)
其中value是轉換爲帶引號的字符串的文字。在值中,必須成對使用括號字符或引號字符。例如,以下是有效值:
/// d ##class(PHA.TEST.ObjectScript).TestContinue()
ClassMethod TestContinue()
{
#Define qtest ##Quote(He said "Yes" after much debate)
ZZWRITE $$$qtest
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestContinue()
%val="He said ""Yes"" after much debate"
值字符串中的圓括號必須成對出現。以下是有效值:
#Define qtest2 ##Quote(After (a lot of) debate)
ZZWRITE $$$qtest2
以下示例顯示##Quote
的使用:
#Define AssertEquals(%e1,%e2) DO AssertEquals(%e1,%e2,##Quote(%e1)_" == "_##Quote(%e2))
Main ;
SET a="abstract"
WRITE "Test 1:",!
$$$AssertEquals(a,"abstract")
WRITE "Test 2:",!
$$$AssertEquals(a_"","abstract")
WRITE "Test 3:",!
$$$AssertEquals("abstract","abstract")
WRITE "All done"
QUIT
AssertEquals(e1,e2,desc) ;
WRITE desc_" is "_$SELECT(e1=e2:"true",1:"false"),!
QUIT
##SQL
##SQL
預處理器指令在編譯時調用指定的SQL語句。它的形式是:
##SQL(SQL-statement)
其中,sql-Statement是有效的SQL語句。##sql
預處理器指令類似於&sql
指令-##sql()
在編譯時調用該語句,而&sql()
在運行時調用該語句。
如果##SQL
指令包含無效的SQL
(如語法錯誤)或引用不存在的表或列,則宏預處理器將生成編譯錯誤。
例如,下面的代碼首先在編譯時運行查詢,然後在運行時再次運行:
/// d ##class(PHA.TEST.ObjectScript).TestSQL()
ClassMethod TestSQL()
{
##sql(SELECT COUNT(*) INTO :count1 FROM Sample.Person)
&sql(SELECT COUNT(*) INTO :count2 FROM Sample.Person)
WRITE "Number of instances of Sample.Person at compile time: ",count1,!
WRITE "Number of instances of Sample.Person at runtime: ",count2,!
}
##Unique
##UNIQUE
預處理器函數在宏定義中創建一個新的、唯一的局部變量,以在編譯時或運行時使用。此指令只能作爲#DEFINE
或#Def1Arg
調用的一部分使用。它的形式是:
##Unique(new)
##Unique(old)
其中NEW指定創建新的唯一變量,OLD指定對同一變量的引用。
SET ##UNIQUE(NEW)
創建的變量是名爲%mmmu1
的局部變量,隨後的SET ##UNIQUE(NEW)
操作將創建名爲%mmmu2
、%mmmu3
等的局部變量。這些局部變量與所有%LOCAL
變量遵循相同的作用域規則;%VARIABLE
始終是公共變量。與所有局部變量一樣,它們可以使用ZWRITE
顯示,也可以使用無參數KILL
終止。
用戶代碼可以引用##Unique(old)
變量,就像它可以引用任何其他ObjectScript變量一樣。可以無限次使用##UNIQUE(old)
語法來引用創建的變量。
對##Unique(New)
的後續調用將創建一個新變量;再次調用##Unique(New)
之後,對##Unique(Old)
的後續調用將引用隨後創建的變量。
例如,下面的代碼使用##UNIQUE(new)
和##UNIQUE(old)
在兩個變量之間交換值:
/// d ##class(PHA.TEST.ObjectScript).TestUn()
ClassMethod TestUn()
{
#Define Switch(%a,%b) SET ##Unique(new)=%a, %a=%b, %b=##Unique(old)
READ "First variable value? ",first,!
READ "Second variable value? ",second,!
$$$Switch(first,second)
WRITE "The first value is now ",first," and the second is now ",second,!
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestUn()
First variable value? a
Second variable value? b
The first value is now b and the second is now a
要保持這些變量的唯一性,請執行以下操作:
- 請勿嘗試在
#DEFINE
或#Def1Arg
預處理器指令之外設置##UNIQUE(NEW)
。 - 不要在方法或過程內的預處理器指令中設置
##UNIQUE(NEW)
。它們將生成方法(%mmmu1
)唯一的變量名;但是,因爲這是一個%變量,所以它的作用域是全局的。調用另一個設置##UNIQUE(NEW)
的方法也會創建%mmmu1
,覆蓋第一個方法創建的變量。 - 切勿直接設置
%mmmu1
變量。Caché保留所有%變量(%z
和%Z
變量除外)供系統使用;它們不應由用戶代碼設置。
使用系統提供的宏
可訪問系統提供的宏
這些宏可用於%RegisteredObject的所有子類。要使這些在未擴展%RegisteredObject的例程或類中可用,請包括相應的文件:
- 對於與狀態相關的宏,請包括%occStatus.inc。
- 對於與消息相關的宏,請包含%occMessages.inc
此類語句的語法爲:
#Include %occStatus
這些包含文件的名稱區分大小寫。
系統支持的宏引用
宏名稱區分大小寫。Caché附帶的宏包括:
ADDSC(sc1, sc2)
ADDSC宏將 %Status代碼(Sc2)附加到現有的 %Status代碼(Sc1)。此宏需要%occStatus.inc。
EMBEDSC(sc1, sc2)
EMBEDSC宏將%Status代碼(Sc2)嵌入到現有的%Status代碼(Sc1)中。此宏需要%occStatus.inc。
ERROR(errorcode, arg1, arg2, …)
ERROR宏使用對象錯誤代碼(ErrorCode)創建%STATUS對象,該對象的關聯文本可能接受某些形式爲%1、%2等的參數。然後,Error根據這些附加參數的順序,將這些參數替換爲errorcode後面的宏參數(arg1、arg2等)。此宏需要%occStatus.inc。
FormatMessage(language,domain,id,default,arg1,arg2,…)
FormatMessage宏使能夠在同一宏調用中從此命名空間的消息字典中檢索文本,並用文本替換消息參數。它返回%字符串。此宏需要%occMessages.inc。
參數 | 描述 |
---|---|
language | RFC1766語言代碼。在CSP上下文中,可以指定·%Response.Language·以使用默認區域設置。 |
domain | 消息域。在CSP上下文中,可以指定·%Response.Domain· |
id | 消息ID。 |
default | 找不到由語言、域和ID標識的消息時使用的字符串。 |
arg1, arg2, and so on | 消息參數的替換文本。所有這些都是可選的,因此即使消息沒有參數,也可以使用·$FormatMessage·。。 |
FormatText(text, arg1, arg2, …)
FormatText宏接受可能包含%1
、%2
等形式的參數的輸入文本消息(Text)。FormatText然後根據這些附加參數的順序將這些參數替換爲文本參數後面的宏參數(arg1、arg2等)。然後,它返回結果字符串。此宏需要%occMessages.inc。。
FormatTextHTML(text, arg1, arg2, …)
FormatTextHTML宏接受可能包含%1
、%2
等形式的參數的輸入文本消息(Text)。FormatTextHTML然後根據這些附加參數的順序將這些參數替換爲文本參數後面的宏參數(arg1、arg2等);然後宏應用HTML轉義。然後,它返回結果字符串。此宏需要%occMessages.inc。
FormatTextJS(text, arg1, arg2, …)
FormatTextJS宏接受可能包含%1
、%2
等形式的參數的輸入文本消息(TEXT)。然後,FormatTextJS根據這些附加參數的順序,將這些參數替換爲文本參數後面的宏參數(arg1、arg2等);然後,宏將應用JavaScript轉義。然後,它返回結果字符串。此宏需要%occMessages.inc。
GETERRORCODE(sc)
GETERRORCODE宏從提供的%狀態代碼(Sc)返回錯誤代碼值。此宏需要%occStatus.inc。
ISERR(sc)
如果提供%Status
代碼(Sc)是錯誤代碼,則ISERR宏將返回True。否則,它返回FALSE。此宏需要%occStatus.inc。
ISOK(sc)
如果提供的%Status
代碼(Sc)成功完成,則ISOK宏將返回True。否則,它返回FALSE。此宏需要%occStatus.inc。
OK
OK宏爲成功完成創建%Status代碼。此宏需要%occStatus.inc。
Text(text, domain, language)
文本宏用於本地化。它在編譯時生成一條新消息,並生成代碼以在運行時檢索該消息。此宏需要%occMessages.inc。
TextHTML(text, domain, language)
TextHTML宏用於本地化。它執行與文本宏相同的處理;然後額外應用HTML轉義。然後,它返回結果字符串。此宏需要%occMessages.inc。
TextJS(text, domain, language)
TextJS宏用於本地化。它執行與文本宏相同的處理;然後額外應用JavaScript轉義。然後,它返回結果字符串。此宏需要%occMessages.inc。
ThrowOnError(sc)
ThrowOnError宏計算指定的%狀態代碼(Sc)。如果sc表示錯誤狀態,ThrowOnError將執行拋出操作,向異常處理程序拋出%Exception.StatusException類型的異常。此宏需要%occStatus.inc。
THROWONERROR(sc, expr)
THROWONERROR宏計算表達式(EXPR),其中表達式的值被假定爲%狀態代碼;宏將%狀態代碼存儲在作爲sc傳遞的變量中。如果%Status代碼是錯誤,則THROWONERROR執行一個拋出操作,向異常處理程序拋出%Exception.StatusException類型的異常。此宏需要%occStatus.inc。
ThrowStatus(sc)
ThrowStatus宏使用指定的%狀態代碼(Sc)執行拋出操作,以向異常處理程序拋出%Exception.StatusException類型的異常。此宏需要%occStatus.inc。