-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Implement custom pragmas #6987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement custom pragmas #6987
Conversation
|
I'm really happy there's not only me who wants to implement it :D. However, there was a problem I hit in my impl. The problem is the custom pragmas are stripped away from object field definitions on the semantic phase, so you can't get those pragmas later with |
|
Hi yglukhov, could you please describe the problem in more details (example?). Yes, pragmas are not in types but they are in nodes. They can be retrieved with getTypeInst at least, so I don't see a problem here. |
|
yglukhov, I have extra test compared to your implementation for inner fields. getCustomPragmaVal(s.field.c, serializationKey) Is this what you mean? |
import macros
template serializationKey(k: string) {.pragma.}
type Foo = object
b1 {.serializationKey: "hi".}: int64
macro test(a: typed): untyped =
echo treeRepr(getTypeImpl(a)[1].getTypeImpl())
test(Foo)Outputs: I can't see a way to get the pragmas from within the macro. Or am i missing anything? |
|
@yglukhov outputs: You can access type def pragmas using I have considered patching |
That is an option I could bear with.
Now regarding the |
compiler/ast.nim
Outdated
| mNHint, mNWarning, mNError, | ||
| mInstantiationInfo, mGetTypeInfo, mNGenSym, | ||
| mNimvm, mIntDefine, mStrDefine, mRunnableExamples, | ||
| mCustomPragma, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, removed
compiler/ast.nim
Outdated
| sfOverriden, # proc is overriden | ||
| sfGenSym # symbol is 'gensym'ed; do not add to symbol table | ||
| sfGenSym, # symbol is 'gensym'ed; do not add to symbol table, | ||
| sfCustomPragma # symbol is custom pragma template |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would introduce an alias to an existing sfFlag here to keep the number of flags below 32.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to the alias of existing sfFlag. As alternative I can make a new SymKind, I know it is a bit of work, but if you find it to be better solution I can do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No new SymKind. Why? Because the symbol kinds should reflect the concrete syntax, the syntax is template foo {.pragma.} and so it should be an skTemplate with an sfPragma flag.
|
Hi Araq, I need this feature for my project in the next two weeks. Help is very much appreciated. |
compiler/pragmas.nim
Outdated
| elif n.kind == nkExprColonExpr and n[1].kind == nkBracket: | ||
| # pragma: [arg1, arg2] -> pragma(arg1, arg2) | ||
| result.add n[0] | ||
| result.sons &= n[1].sons |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like direct 'sons' accesses but have no better solution right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I have: Write a helper proc in ast.nim that does the required transformations.
|
The feature's implementation looks fine but it needs documentation. Please update the manual. |
|
Thanks Araq, I have updated the manual and added ast helpers for sons manipulation |
doc/manual/pragmas.txt
Outdated
| ------------------- | ||
| It is possible to define custom typed pragmas. Custom pragmas do not effect | ||
| code generation directly, but their presence can be detected by macros. | ||
| Custom pragmas are defined using ``pragma`` keyword: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick, but pragma isn't a keyword? Should be: "/.../ defined using the pragma pragma".
Great to see this feature implemented!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sense, updated the doc
doc/manual/pragmas.txt
Outdated
| In this example custom pragmas are used to describe how Nim objects are | ||
| mapped to the schema of the relational database. Custom pragmas can have | ||
| zero, one or more arguments. In order to pass multiple arguments enclose | ||
| them into brackets `[]` as for the rest of the pragmas. If you need to pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be done with () instead, other pragmas already use tuple syntax and it makes more sense than the array syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done it in the latest commit, however it required a minor parser change to make pass by name work. I was getting nkAsgn nodes instead of nkExprEqExpr. I hope your are ok with it
doc/manual/pragmas.txt
Outdated
| mapped to the schema of the relational database. Custom pragmas can have | ||
| zero, one or more arguments. In order to pass multiple arguments enclose | ||
| them into brackets `[]` as for the rest of the pragmas. If you need to pass | ||
| a single argument which is a const array, double brackets will be required. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See? That's not good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like () even more, I thought I have to comply with existing pragmas syntax. Good we have changed it to ()
compiler/pragmas.nim
Outdated
|
|
||
| if n.kind == nkIdent: | ||
| result = result[0] | ||
| elif n.kind == nkExprColonExpr and n[1].kind == nkBracket: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n[1].kind == nkPar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
doc/manual/pragmas.txt
Outdated
|
|
||
| type | ||
| User {.dbTable: ("users", tblspace).} = object | ||
| id {.dbKey: (primary_key = true).}: int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, no. Tuple constructors use ':'!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of disagree. Yes, tuple constructors are using ':', but it is not a tuple. It is more of call expression and I am passing the argument by name.
IMO, tuple constructors don't fit as they can't have a mix of unnamed and named arguments, while proc/template calls can.
Let me know if got everything wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clearly a tuple that is passed to the template here. That said, I think pragmas should just allow ordinary call syntax without the colons, importc"foo" that would solve this issue. I always wanted to allow this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All done now, it well worth a double shot of wiskey at FOSDEM
compiler/pragmas.nim
Outdated
| var y = a.sons[1] | ||
| if x.kind == nkExprColonExpr: x = x.sons[1] | ||
| if y.kind == nkExprColonExpr: y = y.sons[1] | ||
| if x.kind in nkPragmaCallKinds: x = x.sons[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good, but nkCall is not guaranteed to have 2 or more children, so you need a x.len check here too where there was none required before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for quick review, added len checks in the latest commit. Let's see how tests will go.
|
Superb work. I love it. |
|
Agree, len checks were required. Added them in the latest submit |
|
Many repetitions: if n.kind notin nkPragmaCallKinds or n.len != 2:Maybe to write a separate proc for this check? |
|
I don't see a problem in repetition, it generally follows compiler code convention. |
|
ready for the next review |
doc/manual/pragmas.txt
Outdated
|
|
||
| More examples with custom pragmas: | ||
| - Better serialization/deserialization control: | ||
| .. code-block:: nim |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline before this 'code-block'.
doc/manual/pragmas.txt
Outdated
| c {.serializationKey: "_c".}: string | ||
|
|
||
| - Adopting type for gui inspector in a game engine: | ||
| .. code-block:: nim |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline before this 'code-block'.
doc/manual/pragmas.txt
Outdated
| you cannot do yourself by walking AST object representation. | ||
|
|
||
| More examples with custom pragmas: | ||
| - Better serialization/deserialization control: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a heading.
doc/manual/pragmas.txt
Outdated
| b {.defaultDeserialize: 5.}: int | ||
| c {.serializationKey: "_c".}: string | ||
|
|
||
| - Adopting type for gui inspector in a game engine: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a heading.
| template dbIgnore {.pragma.} | ||
|
|
||
| Consider stylized example of possible Object Relation Mapping (ORM) implementation: | ||
| .. code-block:: nim |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline before this 'code-block'.
doc/manual/pragmas.txt
Outdated
| used. | ||
|
|
||
|
|
||
| User custom pragmas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of this feature shall be "Custom annotations"
|
Travis is red because of the illformed RST documentation. |
doc/manual/pragmas.txt
Outdated
| ------------------ | ||
| It is possible to define custom typed pragmas. Custom pragmas do not effect | ||
| code generation directly, but their presence can be detected by macros. | ||
| Custom pragmas are defined using pragma ``pragma``: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I read this first it seemed as if this "pragma" can only be named pragma, please explain that any arbitrary identifier is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| type MyComponent = object | ||
| position {.editable, animatable.}: Vector3 | ||
| alpha {.editRange: [0.0..1.0], animatable.}: float32 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An example implementation for one of these pragmas would be nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not really game or gui developer so I can't implement it in realistic way, I have taken this example from yglukhov first proposal as I thought the more examples the better. I can leave it as it is or remove it.
lib/core/macros.nim
Outdated
| ## | ||
| ## .. code-block:: nim | ||
| ## template myAttr() = discard | ||
| ## type MyObj = object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
convention is that type should be on its own line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/core/macros.nim
Outdated
| ## type MyObj = object | ||
| ## myField {.serializationKey: "mf".}: int | ||
| ## var o: MyObj | ||
| ## assert(o.myField.customPragmaNode(serializationKey) == "mf") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example uses a private proc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
| return newLit(true) | ||
| return newLit(false) | ||
|
|
||
| macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better API would be getCustomPragma and possibly an additional getValue procedure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm, so you don't think it's useful to be able to retrieve the CustomPragma itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think getCustomPragma can be a macro, what it is going to return? Invalid NimNode.
However, getCustomPragma can be a compile time proc that is used inside other user macros to do something with pragma node. Actually, I have such proc already it is called customPragmaNode, but it is not exported.
Should I rename it and make it public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I rename it and make it public?
We can do that later, I'm merging this now, I don't want to keep reviewing it.
lib/core/macros.nim
Outdated
| ## | ||
| ## .. code-block:: nim | ||
| ## template serializationKey(key: string) = discard | ||
| ## type MyObj = object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
| localError(it.info, "'experimental' pragma only valid as toplevel statement") | ||
| of wThis: | ||
| if it.kind == nkExprColonExpr: | ||
| if it.kind in nkPragmaCallKinds and it.len == 2: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @data-man here. You're using this expression many times.
But what's possibly error prone is that you're using the negation of this too (just above on line 988, and probably in other places too). It would be far clearer if this expression was in some nice proc and you negated it when necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bit unfair since the original code had the same issues. I planned to clean this one up after the merge. But ok, since other stuff needs to be reworked too, no big issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it was as severe previously: only the kind was checked, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree. Introduction of a new function for every pair of checks will turn compiler's code into hell, because it will contain 30000 one line helper procs that everybody will have to learn before reading compilers code.
It is pointless: it will not save a single line of code, because if statements can't be removed as error messages are different for different pragma.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to let @Araq decide. If this was my code though I wouldn't accept it. This is a common check.
|
Unrelated test failure. Merging. |
One more implementation of superb feature that we can't live without ;)
Previously attempted by yglukhov: #6070 and #6138
I hope this time we will get it done. I have addressed review comments in #6138