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

  • 0
  • 136
  • 11
  • 2

Answers

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")
}

}

I tried method generators, but It only gets compiled when I compile for the second time.

Please show a sample. Compiling a class with method generators should work on a first compilation.

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

My bad, I've done the UnCompile  but forgot to remove already generated methods.

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()
}

Comments

What you probably need is some kind of OnCompile callback in which you would be able to change the actual class definition based on meta data.

Adding methods and properties.

This is something I've asking for since the incarnation of Cache in 1997.

It would be really nice.

This is not the first time I needed something to be triggered before the compilation...

They are executed after the compiler loads the definitions.

Any changes we do in class definitions is not going to be caught by the compiler since it has already built the dependencies/inheritances.

I.e. you need Callback methods for Studio, Atelier, etc.?

  • for Studio: Extending Studio (OnBeforeCompile, OnBeforeClassCompile, OnBeforeAllClassCompile, etc.)
  • for Atelier: %Atelier.SourceControl (OnBeforeCompile, OnBeforeClassCompile, OnBeforeAllClassCompile, etc.)