Question
· Jun 12, 2018

Extending method keywords

Hi community!
Is there any way to create new method keywords?
For example:

ClassMethod TestOne() [ MyKeyword = MyValue ]
{
    // Implementation
}

Another doubt (maybe another topic)
How can I generate new methods at compilation time for the same class?
I tried method generators, but It only gets compiled when I compile for the second time.
I also read this comment but I could not find out how to generate the methods and get them compiled in the first compilation.

For example:

Class util.Test
{

ClassMethod TestOne()
{
    Write 1
}

ClassMethod TestTwo()
{
    Write 1
}

}

Expected:

Class util.Test
{

ClassMethod TestOne()
{
    Write 1
}

ClassMethod TestTwo()
{
    Write 1
}

ClassMethod TestOneDoSomethingElse()
{
    // My implementation
}

ClassMethod TestTwoDoSomethingElse()
{
    // My implementation
}

}

Thank you all

Discussion (13)1
Log in or sign up to continue
Class util.Test [ DependsOn = util.TestGenerator ]
{

ClassMethod TestOne()
{
    Write 1
}

ClassMethod TestTwo()
{
    Write 1
}

ClassMethod GenerateMethods() As %Status [ CodeMode = objectgenerator ]
{
    For i=1:1:%class.Methods.Count() {
        #dim method As %Dictionary.MethodDefinition = %class.Methods.GetAt(i)
        Continue:((method.Description["@AutoGenerated") || (method.CodeMode="objectgenerator"))
        Do %class.Methods.Insert(##class(util.TestGenerator).Generate(method.Name_"DoSomethingElse",%class.Name))
    }
    Do %class.%Save()
}

}



Class util.TestGenerator
{

ClassMethod Generate(
methodName,
parentName) As %Status
{
    #dim sc As %Status = $$$OK
    Set methodKey = parentName_"||"_methodName
    
    Set newMethod = ##class(%Dictionary.MethodDefinition).%OpenId(methodKey)
    If '$ISOBJECT(newMethod){
    Set newMethod = ##class(%Dictionary.MethodDefinition).%New()
    }
    
    Do newMethod.Implementation.WriteLine($CHAR(9)_"Write """_methodKey_"""")
    Set newMethod.Name = methodName
    Set newMethod.ClassMethod=1
    Do newMethod.parentSetObjectId(parentName)
    Set newMethod.Description = "@AutoGenerated"
    Return newMethod
}

}

When I compile the code, It generates  correctly.

/// @AutoGenerated
ClassMethod TestOneDoSomethingElse()
{
Write "util.Test||TestOneDoSomethingElse"
}

/// @AutoGenerated
ClassMethod TestTwoDoSomethingElse()
{
Write "util.Test||TestTwoDoSomethingElse"
}

but if you see the source,  It is not compiled

 ;util.Test.1
 ;(C)InterSystems, generated for class util.Test. Do NOT edit. 06/12/2018 02:01:03PM
 ;;7735656A;util.Test
 ;
zTestOne() public {
    Write 1 }
zTestTwo() public {
    Write 1 }

When I compile for the second time:

 ;util.Test.1
 ;(C)InterSystems, generated for class util.Test. Do NOT edit. 06/12/2018 02:04:31PM
 ;;4C617153;util.Test
 ;
zTestOne() public {
    Write 1 }
zTestOneDoSomethingElse() public {
Write "util.Test||TestOneDoSomethingElse" }
zTestTwo() public {
    Write 1 }
zTestTwoDoSomethingElse() public {
Write "util.Test||TestTwoDoSomethingElse" }

What flags are you compiling with? For me it works on a first compilation:

USER>do $system.OBJ.UnCompile("util.*")
 
Uncompiling class util.Test
Uncompiling class util.TestGenerator
USER>do ##class(util.Test).TestOneDoSomethingElse()
 
DO ##CLASS(util.Test).TestOneDoSomethingElse()
^
<CLASS DOES NOT EXIST> *util.Test
USER>do ##class(util.TestGenerator).Generate()
 
DO ##CLASS(util.TestGenerator).Generate()
^
<CLASS DOES NOT EXIST> *util.TestGenerator
USER>do $system.OBJ.Compile("util.*")
 
Compilation started on 06/12/2018 20:13:00 with qualifiers ''
Compiling 2 classes, using 2 worker jobs
Compiling class util.TestGenerator
Compiling routine util.TestGenerator.1
Compiling class util.Test
Compiling routine util.Test.1
Compilation finished successfully in 0.012s.
 
USER>do ##class(util.Test).TestOneDoSomethingElse()
util.Test||TestOneDoSomethingElse

As you see from the log both classes don't exist, but after one compilation I can call TestOneDoSomethingElse method and it works. What's your output for:

do $system.OBJ.UnCompile("util.*")
do ##class(util.Test).TestOneDoSomethingElse()
do ##class(util.TestGenerator).Generate()
do $system.OBJ.Compile("util.*")
do ##class(util.Test).TestOneDoSomethingElse()
USER>do $system.OBJ.UnCompile("util.*")

Uncompiling class util.Test
Uncompiling class util.TestGenerator
USER>do ##class(util.Test).TestOneDoSomethingElse()

DO ##CLASS(util.Test).TestOneDoSomethingElse()
^
<CLASS DOES NOT EXIST> *util.Test
USER>do ##class(util.TestGenerator).Generate()

DO ##CLASS(util.TestGenerator).Generate()
^
<CLASS DOES NOT EXIST> *util.TestGenerator
USER>do $system.OBJ.Compile("util.*")

Compilation started on 06/12/2018 14:31:01 with qualifiers ''
Compiling 2 classes, using 2 worker jobs
Compiling class util.TestGenerator
Compiling routine util.TestGenerator.1
Compiling class util.Test
Compiling routine util.Test.1
Compilação terminou com sucesso em 0.050s.

USER>do ##class(util.Test).TestOneDoSomethingElse()

DO ##CLASS(util.Test).TestOneDoSomethingElse()
^
<METHOD DOES NOT EXIST> *TestOneDoSomethingElse,util.Test

I did exactly as you!

When you first imported the class, did you compile it?

If so, maybe Caché had already written the new methods and when you compiled throught terminal It worked.

Try to remove the methods " TestOneDoSomethingElse" and " TestTwoDoSomethingElse" and compile the class again. Check it out if it generates the source correctly

Unrelated but it's better to return a status from a generator method:

ClassMethod GenerateMethods() As %Status [ CodeMode = objectgenerator ]
{
    For i=1:1:%class.Methods.Count() {
        #dim method As %Dictionary.MethodDefinition = %class.Methods.GetAt(i)
        Continue:((method.Description["@AutoGenerated") || (method.CodeMode="objectgenerator"))
        Do %class.Methods.Insert(##class(util.TestGenerator).Generate(method.Name_"DoSomethingElse",%class.Name))
    }
    Quit %class.%Save()
}

Re: extending method keywords, you can't do that at this time, but a useful approximation is structuring a comment - for example:

/// @MyKeyword MyValue
ClassMethod TestOne()
{
    // Implementation
}

And then looking at the %Dictionary.MethodDefinition / %Dictionary.CompiledMethod documentation in a generator method. (But it looks like you might already be on to that with @AutoGenerated.)

Re: making first compilation work, this works for me, by making the first compilation automatically trigger a second one when needed:

ClassMethod GenerateMethods() As %Status [ CodeMode = objectgenerator ]
{
    For i=1:1:%class.Methods.Count() {
        #dim method As %Dictionary.MethodDefinition = %class.Methods.GetAt(i)
        Continue:((method.Description["@AutoGenerated") || (method.CodeMode="objectgenerator"))
        Do %class.Methods.Insert(##class(util.TestGenerator).Generate(method.Name_"DoSomethingElse",%class.Name))
    }
    If (%class.Methods.%IsModified()) {
        Do ##class(%Projection.AbstractProjection).QueueClass(%class.Name)
    }
    Quit %class.%Save()
}

Test code:

Class util.Driver
{

ClassMethod Run()
{
    do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestOneDoSomethingElse")
    do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestTwoDoSomethingElse")
    do $system.OBJ.UnCompile("util.*")
    do $system.OBJ.Compile("util.*","ck")
}

}