the delegator pragma becomes a set of dot operators
This commit is contained in:
parent
a158053ae9
commit
492fa86638
7 changed files with 169 additions and 75 deletions
|
|
@ -409,7 +409,9 @@ type
|
||||||
# efficiency
|
# efficiency
|
||||||
nfTransf, # node has been transformed
|
nfTransf, # node has been transformed
|
||||||
nfSem # node has been checked for semantics
|
nfSem # node has been checked for semantics
|
||||||
nfDelegate # the call can use a delegator
|
nfDotField # the call can use a dot operator
|
||||||
|
nfDotSetter # the call can use a setter dot operarator
|
||||||
|
nfExplicitCall # x.y() was used instead of x.y
|
||||||
nfExprCall # this is an attempt to call a regular expression
|
nfExprCall # this is an attempt to call a regular expression
|
||||||
nfIsRef # this node is a 'ref' node; used for the VM
|
nfIsRef # this node is a 'ref' node; used for the VM
|
||||||
|
|
||||||
|
|
@ -843,7 +845,8 @@ const
|
||||||
ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator,
|
ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator,
|
||||||
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub}
|
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub}
|
||||||
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
|
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
|
||||||
nfAllConst, nfDelegate, nfIsRef}
|
nfDotSetter, nfDotField,
|
||||||
|
nfAllConst,nfIsRef}
|
||||||
namePos* = 0
|
namePos* = 0
|
||||||
patternPos* = 1 # empty except for term rewriting macros
|
patternPos* = 1 # empty except for term rewriting macros
|
||||||
genericParamsPos* = 2
|
genericParamsPos* = 2
|
||||||
|
|
@ -1044,6 +1047,10 @@ proc newStrNode(kind: TNodeKind, strVal: string): PNode =
|
||||||
result = newNode(kind)
|
result = newNode(kind)
|
||||||
result.strVal = strVal
|
result.strVal = strVal
|
||||||
|
|
||||||
|
proc withInfo*(n: PNode, info: TLineInfo): PNode =
|
||||||
|
n.info = info
|
||||||
|
return n
|
||||||
|
|
||||||
proc newIdentNode(ident: PIdent, info: TLineInfo): PNode =
|
proc newIdentNode(ident: PIdent, info: TLineInfo): PNode =
|
||||||
result = newNode(nkIdent)
|
result = newNode(nkIdent)
|
||||||
result.ident = ident
|
result.ident = ident
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ proc parseSymbol(p: var TParser): PNode =
|
||||||
add(result, newIdentNodeP(getIdent"{}", p))
|
add(result, newIdentNodeP(getIdent"{}", p))
|
||||||
getTok(p)
|
getTok(p)
|
||||||
eat(p, tkCurlyRi)
|
eat(p, tkCurlyRi)
|
||||||
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDotDot:
|
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDot, tkDotDot:
|
||||||
add(result, newIdentNodeP(p.tok.ident, p))
|
add(result, newIdentNodeP(p.tok.ident, p))
|
||||||
getTok(p)
|
getTok(p)
|
||||||
of tkIntLit..tkCharLit:
|
of tkIntLit..tkCharLit:
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: seq[string]) =
|
||||||
# fail fast:
|
# fail fast:
|
||||||
globalError(n.info, errTypeMismatch, "")
|
globalError(n.info, errTypeMismatch, "")
|
||||||
var result = msgKindToString(errTypeMismatch)
|
var result = msgKindToString(errTypeMismatch)
|
||||||
add(result, describeArgs(c, n, 1 + ord(nfDelegate in n.flags)))
|
add(result, describeArgs(c, n, 1 + ord(nfDotField in n.flags)))
|
||||||
add(result, ')')
|
add(result, ')')
|
||||||
|
|
||||||
var candidates = ""
|
var candidates = ""
|
||||||
|
|
@ -138,15 +138,33 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
|
||||||
|
|
||||||
let overloadsState = result.state
|
let overloadsState = result.state
|
||||||
if overloadsState != csMatch:
|
if overloadsState != csMatch:
|
||||||
if nfDelegate in n.flags:
|
if nfDotField in n.flags:
|
||||||
internalAssert f.kind == nkIdent
|
internalAssert f.kind == nkIdent and n.sonsLen >= 2
|
||||||
let calleeName = newStrNode(nkStrLit, f.ident.s)
|
let calleeName = newStrNode(nkStrLit, f.ident.s).withInfo(n.info)
|
||||||
calleeName.info = n.info
|
|
||||||
|
|
||||||
let callOp = newIdentNode(idDelegator, n.info)
|
# leave the op head symbol empty,
|
||||||
n.sons[0..0] = [callOp, calleeName]
|
# we are going to try multiple variants
|
||||||
orig.sons[0..0] = [callOp, calleeName]
|
n.sons[0..1] = [nil, n[1], calleeName]
|
||||||
|
orig.sons[0..1] = [nil, orig[1], calleeName]
|
||||||
|
|
||||||
|
template tryOp(x) =
|
||||||
|
let op = newIdentNode(getIdent(x), n.info)
|
||||||
|
n.sons[0] = op
|
||||||
|
orig.sons[0] = op
|
||||||
|
pickBest(op)
|
||||||
|
|
||||||
|
if nfExplicitCall in n.flags:
|
||||||
|
tryOp ".()"
|
||||||
|
|
||||||
|
if result.state in {csEmpty, csNoMatch}:
|
||||||
|
tryOp "."
|
||||||
|
|
||||||
|
elif nfDotSetter in n.flags:
|
||||||
|
internalAssert f.kind == nkIdent and n.sonsLen == 3
|
||||||
|
let calleeName = newStrNode(nkStrLit, f.ident.s[0.. -2]).withInfo(n.info)
|
||||||
|
let callOp = newIdentNode(getIdent".=", n.info)
|
||||||
|
n.sons[0..1] = [callOp, n[1], calleeName]
|
||||||
|
orig.sons[0..1] = [callOp, orig[1], calleeName]
|
||||||
pickBest(callOp)
|
pickBest(callOp)
|
||||||
|
|
||||||
if overloadsState == csEmpty and result.state == csEmpty:
|
if overloadsState == csEmpty and result.state == csEmpty:
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
# it is a static call!
|
# it is a static call!
|
||||||
result = n.sons[0]
|
result = n.sons[0]
|
||||||
result.kind = nkCall
|
result.kind = nkCall
|
||||||
|
result.flags.incl nfExplicitCall
|
||||||
for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i])
|
for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i])
|
||||||
return semExpr(c, result, flags)
|
return semExpr(c, result, flags)
|
||||||
else:
|
else:
|
||||||
|
|
@ -999,7 +1000,7 @@ proc dotTransformation(c: PContext, n: PNode): PNode =
|
||||||
else:
|
else:
|
||||||
var i = considerAcc(n.sons[1])
|
var i = considerAcc(n.sons[1])
|
||||||
result = newNodeI(nkDotCall, n.info)
|
result = newNodeI(nkDotCall, n.info)
|
||||||
result.flags.incl nfDelegate
|
result.flags.incl nfDotField
|
||||||
addSon(result, newIdentNode(i, n[1].info))
|
addSon(result, newIdentNode(i, n[1].info))
|
||||||
addSon(result, copyTree(n[0]))
|
addSon(result, copyTree(n[0]))
|
||||||
|
|
||||||
|
|
@ -1082,12 +1083,13 @@ proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
|
|
||||||
proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
|
proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
|
||||||
var id = considerAcc(a[1])
|
var id = considerAcc(a[1])
|
||||||
let setterId = newIdentNode(getIdent(id.s & '='), n.info)
|
var setterId = newIdentNode(getIdent(id.s & '='), n.info)
|
||||||
# a[0] is already checked for semantics, that does ``builtinFieldAccess``
|
# a[0] is already checked for semantics, that does ``builtinFieldAccess``
|
||||||
# this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
|
# this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
|
||||||
# nodes?
|
# nodes?
|
||||||
let aOrig = nOrig[0]
|
let aOrig = nOrig[0]
|
||||||
result = newNode(nkCall, n.info, sons = @[setterId, a[0], semExpr(c, n[1])])
|
result = newNode(nkCall, n.info, sons = @[setterId, a[0], semExpr(c, n[1])])
|
||||||
|
result.flags.incl nfDotSetter
|
||||||
let orig = newNode(nkCall, n.info, sons = @[setterId, aOrig[0], nOrig[1]])
|
let orig = newNode(nkCall, n.info, sons = @[setterId, aOrig[0], nOrig[1]])
|
||||||
result = semOverloadedCallAnalyseEffects(c, result, orig, {})
|
result = semOverloadedCallAnalyseEffects(c, result, orig, {})
|
||||||
|
|
||||||
|
|
@ -1777,22 +1779,6 @@ proc semBlock(c: PContext, n: PNode): PNode =
|
||||||
closeScope(c)
|
closeScope(c)
|
||||||
dec(c.p.nestedBlockCounter)
|
dec(c.p.nestedBlockCounter)
|
||||||
|
|
||||||
proc buildCall(n: PNode): PNode =
|
|
||||||
if n.kind == nkDotExpr and n.len == 2:
|
|
||||||
# x.y --> y(x)
|
|
||||||
result = newNodeI(nkCall, n.info, 2)
|
|
||||||
result.sons[0] = n.sons[1]
|
|
||||||
result.sons[1] = n.sons[0]
|
|
||||||
elif n.kind in nkCallKinds and n.sons[0].kind == nkDotExpr:
|
|
||||||
# x.y(a) -> y(x, a)
|
|
||||||
let a = n.sons[0]
|
|
||||||
result = newNodeI(nkCall, n.info, n.len+1)
|
|
||||||
result.sons[0] = a.sons[1]
|
|
||||||
result.sons[1] = a.sons[0]
|
|
||||||
for i in 1 .. <n.len: result.sons[i+1] = n.sons[i]
|
|
||||||
else:
|
|
||||||
result = n
|
|
||||||
|
|
||||||
proc doBlockIsStmtList(n: PNode): bool =
|
proc doBlockIsStmtList(n: PNode): bool =
|
||||||
result = n.kind == nkDo and
|
result = n.kind == nkDo and
|
||||||
n[paramsPos].sonsLen == 1 and
|
n[paramsPos].sonsLen == 1 and
|
||||||
|
|
@ -1901,7 +1887,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||||
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
|
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
|
||||||
# check if it is an expression macro:
|
# check if it is an expression macro:
|
||||||
checkMinSonsLen(n, 1)
|
checkMinSonsLen(n, 1)
|
||||||
let mode = if nfDelegate in n.flags: {} else: {checkUndeclared}
|
let mode = if nfDotField in n.flags: {} else: {checkUndeclared}
|
||||||
var s = qualifiedLookUp(c, n.sons[0], mode)
|
var s = qualifiedLookUp(c, n.sons[0], mode)
|
||||||
if s != nil:
|
if s != nil:
|
||||||
if gCmd == cmdPretty and n.sons[0].kind == nkDotExpr:
|
if gCmd == cmdPretty and n.sons[0].kind == nkDotExpr:
|
||||||
|
|
@ -1940,7 +1926,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
|
||||||
# the 'newSeq[T](x)' bug
|
# the 'newSeq[T](x)' bug
|
||||||
setGenericParams(c, n.sons[0])
|
setGenericParams(c, n.sons[0])
|
||||||
result = semDirectOp(c, n, flags)
|
result = semDirectOp(c, n, flags)
|
||||||
elif isSymChoice(n.sons[0]) or nfDelegate in n.flags:
|
elif isSymChoice(n.sons[0]) or nfDotField in n.flags:
|
||||||
result = semDirectOp(c, n, flags)
|
result = semDirectOp(c, n, flags)
|
||||||
else:
|
else:
|
||||||
result = semIndirectOp(c, n, flags)
|
result = semIndirectOp(c, n, flags)
|
||||||
|
|
|
||||||
|
|
@ -4106,6 +4106,59 @@ types that will match the typedesc param:
|
||||||
|
|
||||||
The constraint can be a concrete type or a type class.
|
The constraint can be a concrete type or a type class.
|
||||||
|
|
||||||
|
Special Operators
|
||||||
|
=================
|
||||||
|
|
||||||
|
dot operators
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Nimrod offers a special family of dot operators that can be used to
|
||||||
|
intercept and rewrite proc call and field access attempts, referring
|
||||||
|
to previously undeclared symbol names. They can be used to provide a
|
||||||
|
fluent interface to objects lying outside the static confines of the
|
||||||
|
Nimrod's type system such as values from dynamic scripting languages
|
||||||
|
or dynamic file formats such as JSON or XML.
|
||||||
|
|
||||||
|
When Nimrod encounters an expression that cannot be resolved by the
|
||||||
|
standard overload resolution rules, the current scope will be searched
|
||||||
|
for a dot operator that can be matched against a re-written form of
|
||||||
|
the expression, where the unknown field or proc name is converted to
|
||||||
|
an additional static string parameter:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
a.b # becomes `.`(a, "b")
|
||||||
|
a.b(c, d) # becomes `.`(a, "b", c, d)
|
||||||
|
|
||||||
|
The matched dot operators can be symbols of any callable kind (procs,
|
||||||
|
templates and macros), depending on the desired effect:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
proc `.` (js: PJsonNode, field: string): JSON = js[field]
|
||||||
|
|
||||||
|
var js = parseJson("{ x: 1, y: 2}")
|
||||||
|
echo js.x # outputs 1
|
||||||
|
echo js.y # outputs 2
|
||||||
|
|
||||||
|
The following dot operators are available:
|
||||||
|
|
||||||
|
operator `.`
|
||||||
|
------------
|
||||||
|
This operator will be matched against both field accesses and method calls.
|
||||||
|
|
||||||
|
operator `.()`
|
||||||
|
---------------
|
||||||
|
This operator will be matched exclusively against method calls. It has higher
|
||||||
|
precedence than the `.` operator and this allows you to handle expressions like
|
||||||
|
`x.y` and `x.y()` differently if you are interfacing with a scripting language
|
||||||
|
for example.
|
||||||
|
|
||||||
|
operator `.=`
|
||||||
|
-------------
|
||||||
|
This operator will be matched against assignments to missing fields.
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
a.b = c # becomes `.=`(a, "b", c)
|
||||||
|
|
||||||
|
|
||||||
Term rewriting macros
|
Term rewriting macros
|
||||||
=====================
|
=====================
|
||||||
|
|
@ -4758,42 +4811,6 @@ This may change in future versions of language, but for now use
|
||||||
the ``finalizer`` parameter to ``new``.
|
the ``finalizer`` parameter to ``new``.
|
||||||
|
|
||||||
|
|
||||||
delegator pragma
|
|
||||||
----------------
|
|
||||||
|
|
||||||
**Note**: The design of the delegator feature is subject to change.
|
|
||||||
|
|
||||||
The delegator pragma can be used to intercept and rewrite proc call and field
|
|
||||||
access attempts referring to previously undeclared symbol names. It can be used
|
|
||||||
to provide a fluent interface to objects lying outside the static confines of
|
|
||||||
the Nimrod's type system such as values from dynamic scripting languages or
|
|
||||||
dynamic file formats such as JSON or XML.
|
|
||||||
|
|
||||||
A delegator is a special form of the `()` operator marked with the delagator
|
|
||||||
pragma. When Nimrod encounters an expression that cannot be resolved by the
|
|
||||||
standard overload resolution, any delegators in the current scope will be
|
|
||||||
matched against a rewritten form of the expression following the standard
|
|
||||||
signature matching rules. In the rewritten expression, the name of the unknown
|
|
||||||
proc or field name is inserted as an additional static string parameter always
|
|
||||||
appearing in the leading position:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
a.b => delegator("b", a)
|
|
||||||
a.b(c, d) => delegator("b", a, c)
|
|
||||||
a b, c, d => delegator("a", b, c, d)
|
|
||||||
|
|
||||||
|
|
||||||
The delegators can be any callable symbol type (procs, templates, macros)
|
|
||||||
depending on the desired effect:
|
|
||||||
|
|
||||||
.. code-block:: nimrod
|
|
||||||
proc `()` (field: string, js: PJsonNode): JSON {.delegator.} = js[field]
|
|
||||||
|
|
||||||
var js = parseJson("{ x: 1, y: 2}")
|
|
||||||
echo js.x # outputs 1
|
|
||||||
echo js.y # outputs 2
|
|
||||||
|
|
||||||
|
|
||||||
procvar pragma
|
procvar pragma
|
||||||
--------------
|
--------------
|
||||||
The `procvar`:idx: pragma is used to mark a proc that it can be passed to a
|
The `procvar`:idx: pragma is used to mark a proc that it can be passed to a
|
||||||
|
|
|
||||||
66
tests/specialops/tdotops.nim
Normal file
66
tests/specialops/tdotops.nim
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
discard """
|
||||||
|
output: '''
|
||||||
|
10
|
||||||
|
assigning z = 20
|
||||||
|
reading field y
|
||||||
|
20
|
||||||
|
call to y
|
||||||
|
dot call
|
||||||
|
no params call to a
|
||||||
|
100
|
||||||
|
no params call to b
|
||||||
|
100
|
||||||
|
one param call to c with 10
|
||||||
|
100'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
T1 = object
|
||||||
|
x*: int
|
||||||
|
|
||||||
|
TD = distinct T1
|
||||||
|
|
||||||
|
T2 = object
|
||||||
|
x: int
|
||||||
|
|
||||||
|
proc `.`*(v: T1, f: string): int =
|
||||||
|
echo "reading field ", f
|
||||||
|
return v.x
|
||||||
|
|
||||||
|
proc `.=`(x: var T1, f: string{lit}, v: int) =
|
||||||
|
echo "assigning ", f, " = ", v
|
||||||
|
x.x = v
|
||||||
|
|
||||||
|
template `.()`(x: T1, f: string, args: varargs[expr]): string =
|
||||||
|
echo "call to ", f
|
||||||
|
"dot call"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
var t = T1(x: 10)
|
||||||
|
|
||||||
|
echo t.x
|
||||||
|
t.z = 20
|
||||||
|
echo t.y
|
||||||
|
echo t.y()
|
||||||
|
|
||||||
|
var d = TD(t)
|
||||||
|
assert(not compiles(d.y))
|
||||||
|
|
||||||
|
proc `.`(v: T2, f: string): int =
|
||||||
|
echo "no params call to ", f
|
||||||
|
return v.x
|
||||||
|
|
||||||
|
proc `.`*(v: T2, f: string, a: int): int =
|
||||||
|
echo "one param call to ", f, " with ", a
|
||||||
|
return v.x
|
||||||
|
|
||||||
|
var tt = T2(x: 100)
|
||||||
|
|
||||||
|
echo tt.a
|
||||||
|
echo tt.b()
|
||||||
|
echo tt.c(10)
|
||||||
|
|
||||||
|
assert(not compiles(tt.d("x")))
|
||||||
|
assert(not compiles(tt.d(1, 2)))
|
||||||
|
|
||||||
|
|
@ -68,8 +68,8 @@ Language Additions
|
||||||
- Exported templates are allowed to access hidden fields.
|
- Exported templates are allowed to access hidden fields.
|
||||||
- The ``using statement`` enables you to more easily author domain-specific
|
- The ``using statement`` enables you to more easily author domain-specific
|
||||||
languages and libraries providing OOP-like syntactic sugar.
|
languages and libraries providing OOP-like syntactic sugar.
|
||||||
- Added a new ``delegator pragma`` for handling calls to missing procs and
|
- Added the possibility to override various dot operators in order to handle
|
||||||
fields at compile-time.
|
calls to missing procs and reads from undeclared fields at compile-time.
|
||||||
- The overload resolution now supports ``static[T]`` params that must be
|
- The overload resolution now supports ``static[T]`` params that must be
|
||||||
evaluable at compile-time.
|
evaluable at compile-time.
|
||||||
- Support for user-defined type classes has been added.
|
- Support for user-defined type classes has been added.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue