There are two documents available: the Cyan Manual and the description of the Cyan Metaobject Protocol (MOP), the greatest innovation of the language (the code used in the text is here). For a simple way of starting using metaobjects, read section 3.3 of the last document. This section describes metaobject action_afti_dsa and similar ones.
Most of the Cyan MOP is also described in the article The Cyan Language Metaobject Protocol (34 pages, submitted to a journal). There is a primer on how to build metaobjects in Java in the Eclipse IDE (it can be easily adapted to other IDEs ou no IDE).
The table below shows some metaobjects that are ready to use, with examples (download all the examples). For a more direct feeling of metaobjects, see action_afti_dsa and eval. For Codegs, the visual metaobjects, see this page.
Name (Clique to Example) | kind | Description | Usage example |
---|---|---|---|
doc | document code | add the attached DSL to the list of documents of the declaration | @doc{* This is the documentation of the 'run' method *} func run { } |
addBeforeMethod | add code | add code before a method. A demonstration metaobject. | @addBeforeMethod(Proto, "run", "\"run called\" println;") |
addCodeFromMetaobject | add code | when attached to a generic prototype, an annotation addCodeFromMetaobject can ask to an annotation addCodeToGenericPrototype attached to a generic parameter to supply code to be inserted in the generic prototype. | @addCodeFromMetaobject |
addCodeToGenericPrototype | add code | Used with addCodeFromMetaobject. See The Cyan Language Metaobject Protocol for more information. | @addCodeToGenericPrototype(String){* code *} @addCodeToGenericPrototype(Id){* code to be added*} |
addMethodTo | add code | add a method to a prototype. A demonstration metaobject. | @addMethodTo(Proto, "run", "func run { }") |
addToSet | add info | add a pair (varName, value) to a map associated to the project. The pair can be accessed by other metaobjects | @addToSet(secure, "yes") |
annot | add info | add information to instance variable, method, prototype, package, or program | @annot("correct this") |
assert | check | check if the boolean expression is true. If not, a warning message is output. | assert ok; assert n == 0; |
range | check | check if variable hold only values in the range | Int@range(1, 12) Char@range('a', 'z') |
regex | check | check if a String variable only holds strings that match a regular expression | String@regex("[A-Z]+") |
tainted and untainted | check | check if a value of type tainted is assigned to a variable of type untainted. See the Checker Framework for more details. See also prototype TaintedToUntainted and the project file. | String@tainted(sql) String@untainted(sql) |
type | check | similar C-language typedef, strong types for any type | Int@type(inBytes) |
callTestMethods | add code | add code to call all methods of the current prototype ending with 'Test' | @callTestMethods |
callUnaryMethods | add code | add code to call unary methods of the current prototype whose name match the pattern that is parameter | @callUnaryMethods(".*Test") |
changeFunctionForMethod | replace message passing, use in Cyan libraries only | use only in prototype Any. Create a anonymous function that represents a method | @changeFunctionForMethod func functionForMethod: String |
checkCatchParameter | check | whether each parameter to a catch: selector has at least one 'eval:' method, each of them accepting one parameter whose type is subprototype of CyException | @checkCatchParameter catch: CyException e |
action_afti_dsa | add Code | add methods, fields,and statements | @action_afterResTypes_semAn{* .... *} |
addFieldInfo | add Code | add fields with information on fields and methods | @addFieldInfo object Program ... end |
letter | check | check whether a letter is assigned to a Char variable | var Char@letter ch; ch = 'a'; |
restrictTo | check | check the values assigned to a variable | var Int@restrictTo{* self >= 0 *} age; |
checkIsA | check | check if the argument to a call to method isA: of Any is a prototype | @checkIsA final func isA: (Any proto) -> Boolean |
checkMethodEqualEqual | check | check if the argument to method == of Any is legal. It is illegal to compare values of types that will always result in false, like an Int and a Char. | @checkMethodEqualEqual func == (Dyn other) -> Boolean |
checkStyle | check | a demonstration metaobject that checks the style of variables, fields, methods, and prototypes | @checkStyle object Person ... end |
codeAfter | check | a demonstration metaobject. It adds the code attached to the annotation after the local variable declaration the annotation is attached to. | @codeAfter{* sum = 1; *} var Int sum = 0; assert sum == 1; |
fsmDSLMethods | add code/check | check if method calls obey a Finite State Machine | @fsmDSLMethods{* ... *} object FSMTest ... end |
@columnNumber | add code | replaced by the column number of '@' of the annotation | var cn = @columnNumber; |
compilationInfo | add code | give information according to the argument | var lineNumber = @compilationInfo("lineNumber"); |
concept See also GroupWork and IntGroupPlus | check | check restrictions of generic prototype parameters. See The Cyan Language Metaobject Protocol for more information. | @concept{* T has [ func Boolean ] *} |
countFieldAccess | add code | count how many times a field was accessed | @countFieldAccess var String s = "0"; |
countNew | add code | count how many objects of a prototype were created (if attached to all init or init: methods) | @countNew func init { ... } |
createArrayMethods | add code | add methods 'sort' and 'sortDescending' to a generic array. Used only in prototype cyan.lang.Array | @createArrayMethods object Array ... end |
createCatchExit | add code | Used only in cyan.lang.CatchExit | @createCatchExitobject CatchExit end |
createCatchIgnore | add code | Used only in cyan.lang.CatchIgnore | object CatchIgnore @createCatchIgnore end |
createCatchMethodsForFunctionNil | add code | Used only in cyan.lang.Function | abstract object Function @createCatchMethodsForFunctionNil ... end |
createCatchWarning | add code | Used only in cyan.lang.CatchWarning | object CatchWarning @createCatchWarning end |
createExceptionConverter | add code | Used only in cyan.lang.ExceptionConverter | object ExceptionConverter @createExceptionConverter end |
createExceptionEncapsulator | add code | Used only in cyan.lang.ExceptionEncapsulator | object ExceptionEncapsulator @createExceptionEncapsulator end |
createFieldIfAccessed | intercept field access | create a field in a hash table | @createFieldIfAccessed object Test ... end |
createFunction | add code | Used only in cyan.lang.Function | abstract object Function @createFunction end |
createPrototype | create prototype | create one or two prototypes. A demonstration metaobject | @createPrototype( "Dragon", "package main\nobject Dragon\n func get -> Int { return 0 }\n\nend", "Elf", "package main\nobject Elf\n func get -> String { return \"Elf !\" }\n\nend" ) |
createTuple | add code | Used only in cyan.lang.createTuple | object Tuple @createTuple end |
deprecated | check | Issue an error message whenever the attached method can be called | @deprecated("Method 'outdatedMethod:' was replaced by 'newMethod:'") func outdatedMethod: String s { } |
eval | add code | evaluate and return the value of the attached interpreted Cyan code | var s = @eval("cyan.lang", "Int"){* var k = 0; for n in 1..10 { k = k + n } return k *}; assert s == 55; |
extract | add code | transform an identifier into a string. It allows to pretend that generic prototype instantiations accept Int values | object MyVector<T> var Array end |
feature | add information | add information to a declaration that can be retrieved by other metaobjects, the compiler, or at runtime. | @feature(test "Test always") object MyTest end |
genericPrototypeInstantiationInfo | add information | This metaobject is used only by the Cyan compiler. It adds information on every compiler-created generic prototype instantiation. Because of this, error messages on generic prototype instantiations are very clear in Cyan. | @genericPrototypeInstantiationInfo("main", "Program", 538, 13) object G1 ... end |
getPackageValueFromKey. See also getProgramValueFromKey and setVariable | get information | get the value associated to a package key, transformed into a string | var String value = @getPackageValueFromKey(test); |
getProgramValueFromKey. See also getPackageValueFromKey and setVariable | get information | get the value associated to a program key, transformed into a string | var String value = @getProgramValueFromKey(debug); |
grammarMethod | create DSL | create a DSL using regular operators and message keywords. One of the biggest metaobjects. See The Cyan Language Metaobject Protocol for more information. | @grammarMethod{* (name: String (at: String)? (with: String)* )+ *} func meet: Array ... } |
immutable | check | check if a prototype is immutable | @immutable object Person ... end |
init | add code | create constructors based on field names | object Init @init(max) @init(name, lastLetter, max) ... let String name = "init"; var Char lastLetter = 'z'; var Int max end |
insertCode | add code | add fields, methods, and statements. The code is produced by interpreted Cyan at compile-time | var Int fat12; @insertCode{* var p = 2; for n in 3..12 { p = p*n } insert: " fat12 = " ++ p ++ ";" ++ '\n'; *} "The factorial of 12 is $fat12" println; |
error | cause error | issue a compilation error with the message that is parameter | @error("This code is not working yet. Fix it") |
keepValue | add code | save the last five values of a variable in a file. A demonstration metaobject | 7 times: { var Int r = Math randomInt; // record the last five random numbers in the file @keepValue(r); } |
lineNumber | add code | the annotation is an expression with the value of the current line number | var nextLine = @lineNumber; |
Literal string 'r' or 'R' | add code | r"a*b+" is replaced by an object of RegExpr, that represents a literal expression | var ok = "aabc" ~= r"a*b*c"; assert ok; |
Literal string 'n' or 'N' | add code | the string is unescaped | assert n"\n" == 2; |
log | add code | log if the annotated method or methods of the annotated prototype were called | @log(mylog) object Test ... end |
Number_base | add code | a literal number in base 2 till 36 | assert 0FF_FF_BASE16 == 65535; assert 11_base_8 == 9; |
Number_bin | add code | a literal number in base 2 | assert 101bin == 5; assert 10BIN == 2; assert 111_111Bin == 63; |
OnDeclaration_afterSemAn | check | check any declaration like method, prototype, and field | @onDeclaration_afsa{* var Int size = compiler getMethodDecList size; if size < 10 { metaobject addError: "This prototype should have at least ten methods"; } *} object Test ... end |
OnDeclaration_afterResTypes_semAn_afterSemAn | add code and check | add code to the current prototype and check a declaration like a prototype, method, or field | @onDeclaration_afti_dsa_afsa{* func afti_codeToAdd { var annotation = metaobject getMetaobjectAnnotation; var String name = annotation getDeclaration getName; var String typeName = annotation getDeclaration getType getName; return [. "func get_" ++ name ++ " -> " ++ typeName ++ " = " ++ name ++ ";", "func get_" ++ name ++ " -> Int" .] } *} var Int age = 0; |
onFieldAccess | add code and check | add fields and methods to the current prototype and check accesses to a field | /* Even if this field is set to another value, its value will always be 0 */ @onFieldAccess{* func dsa_replaceGetField { return "0"; } *} var Int alwaysZero = 9; |
onFieldMissing | add code and check | add fields and methods to the current prototype, can introduce virtual fields in a prototype | @onFieldMissing{* func dsa_replaceGetMissingField { // replace 'read' accesses to 'cyan' by 0 var String name = fieldToGet asString; if name startsWith: "self." { name = name substring: 5 } var Int n = -1; if name == "self.cyan" { return [. "cyan.lang", "Int", "0" .]; } // otherwise, return null and there will be // a compilation error "field not found" } *} object Test ... end |
OnMessageSend_afterSemAn | check | check a message passing | @onMessageSend_afsa{* func afsa_checkKeywordMessageSend { ... } *} func at: Int n, String s { } |
OnMessageSend_semAn | add code | replace a message passing by any code | /* message passings x mult: n, other are replaced by, simply, 0. */ @onMessageSend_dsa{* func dsa_analyzeReplaceKeywordMessage { return [. "0", "cyan.lang", "Int" .] } *} func mult: Int n to: Int other -> Int { return n*other } |
onMethodMissing | add code | replace a message passing for which no method is adequate by any code | @onMethodMissing{* // any unary message for which there // is no method is replaced by "" func dsa_analyzeReplaceUnaryMessage { return [. "", "cyan.lang", "String" .]; } *} object Test ... end |
OnOverride_afterResTypes_semAn_afterSemAn | add code and check | add fields, methods, and statements. Check when a method is overridden. | @onOverride_afti_dsa_afsa{* // only prototypes whose names start with 'Sub' can inherit // this prototype. A message is printed, but no error is issued func afsa_checkOverride { let String subProtoName = overridedMethod getDeclaringObject getName; let String methodName = overridedMethod getName if !(subProtoName startsWith: "Sub") { Out println: "I am really sorry, but method " ++ methodName ++ " can only be overridden in prototypes whose name " ++ "start with 'Sub'"; } } *} func draw { } |
onOverride | check | Cyan statements are executed whenever the attached method is overridden | @onOverride{* // the overridden method should call // this method in its first statement // shouldCallSuperMethod is an action function // of package cyan.lang call: #shouldCallSuperMethod; *} func draw { } |
OnSubprototype_afterResTypes_semAn_afterSemAn | add code and check | add fields, methods, and statements. Check when a prototype is inherited | @onSubprototype_afti_dsa_afsa{* func afsa_checkSubprototype { let WrEnv env = compiler getEnv; let WrProgramUnit attachedProgramUnit = metaobject getAttachedDeclaration; if attachedProgramUnit getPackageName: env != subPrototype getPackageName: env { Out println: "Prototype " ++ attachedProgramUnit getFullName ++ " can only be inherited in its own package. It is " ++ "being inherited by " ++ subPrototype getFullName; } } *} open object Test ... end |
onSubprototype | check | Cyan statements are executed whenever the attached prototype is inherited | @onSubprototype{* Out println: "Do not forget of defining a 'subtest' method" *} open object Test ... end |
onVariableDeclaration | add code and check | add fields and methods to the current prototype, add statements after a variable declaration | @onVariableDeclaration{* func dsa_codeToAddAfter { return """ webpage = complexInitialization;""" } *} var Int webpage; |
overrideTest. See also SubOverrideTest | create test | create a test prototype whenever a method is overridden in a subprototype | @overrideTest{* func test { var (%SUBPROTOTYPE%) subproto = (%SUBPROTOTYPE%) new; var localTotal = subproto getTotal; subproto add: 100; subproto withdraw: 100; assert localTotal == subproto getTotal; } *} func withdraw: Int amount { total = total - amount } |
parametersToString | add code | A demonstration metaobject that converts all of the annotation parameters to a string | Out println: @parametersToString( 0, 'a', "Newton", Newton, // identifier, counts as string [ 0, 1, 2 ], [ "zero" -> 0, "one" -> 1 ], [. "Newton", 1643 .] *}; |
property | add code | create get and set methods for 'var' fields and get methods for 'let' fields | @property var Int total = 0; |
readOnly | check | demands that no field, shared or non-shared, is assigned to in the attached method | @readOnly func at: Int n { ... } |
renameMethod | rename method and add code | A demonstration metaobject. It renames a method and add another method with the old name. | see the example |
replaceCallBy | add code | replace a message passing by code | @replaceCallBy{* 3*n *} func threeTimes: Int n -> Int = n; |
restrictImplementation. See also ImplementRestrictImplementation | check | Restrict the prototypes allowed to implement the annotated interface | @restrictImplementation("ImplementRestrictImplementation") interface RestrictImplementation func myprint end |
restrictOverrideTo. See also SubRestrictOverrideTo | check | Restrict the prototypes allowed to override the annotated method | @restrictOverrideTo(main.SubRestrictOverrideTo, main.SubOtherRestrictOverrideTo) func myprint { } |
rpn | add code | At compile-time, evaluates a RPN expression | let value = @rpn{* 1 2 3 * + *}; |
runFile. See also afti_dsa_test(MetSig,UMS,Ret).myan | add code and check | run file with myan methods that is in a file. Similar to action_afti_dsa | @runFile("afti_dsa_test", "with:1 do:1", "unary", 10) |
runPastCode | add code | The attached Cyan code can use objects of the last time the program run. | var RunPastCode past2 = self; @runPastCode(true, past2){* var Int fatorial5 = past2 fat: 5; return "f5 = " ++ fatorial5 ++ ";"; *} |
setVariable. See also getProgramValueFromKey and getPackageValueFromKey | associate a key to a value | associate a key to a value in the program or in a package. Used inside the project file. | @setVariable(debug, "yes") @setVariable(author, "Jose") // elided program // elided @setVariable(test, create) @setVariable(goal, "Test all metaobjects") package metaobjectTest // elided |
shouldCallSuperMethod | check | This is an action metaobject. It checks whether a method calls the superprototype method as its first statement | @onOverride{* call: #shouldCallSuperMethod; *} func run { } |
shout | add code | A demonstration metaobject that changes all alphabetic characters in literal strings of a method to uppercase letters | @shout func shoutTest { ... } |
symbolToString | add code | convert the parameter to the annotation into a string. Used mainly in generic prototypes | var String s = @symbolToString(Int); |
cep | check | Warn the compiler that it should expect a compilation error 'n' lines ahead with the message 'mss' if the annotation is @cep(n, mss) | @cep(1, "Expression expected) var k = 1 + ; |
printexpr | add code | prints the expression that follows the macro, as a string, and its value | printexpr n + 1; |