the delegator pragma becomes a set of dot operators

This commit is contained in:
Zahary Karadjov
2014-02-15 17:41:35 +02:00
parent a158053ae9
commit 492fa86638
7 changed files with 169 additions and 75 deletions

View File

@@ -409,7 +409,9 @@ type
# efficiency
nfTransf, # node has been transformed
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
nfIsRef # this node is a 'ref' node; used for the VM
@@ -843,7 +845,8 @@ const
ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator,
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub}
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
nfAllConst, nfDelegate, nfIsRef}
nfDotSetter, nfDotField,
nfAllConst,nfIsRef}
namePos* = 0
patternPos* = 1 # empty except for term rewriting macros
genericParamsPos* = 2
@@ -1044,6 +1047,10 @@ proc newStrNode(kind: TNodeKind, strVal: string): PNode =
result = newNode(kind)
result.strVal = strVal
proc withInfo*(n: PNode, info: TLineInfo): PNode =
n.info = info
return n
proc newIdentNode(ident: PIdent, info: TLineInfo): PNode =
result = newNode(nkIdent)
result.ident = ident

View File

@@ -281,7 +281,7 @@ proc parseSymbol(p: var TParser): PNode =
add(result, newIdentNodeP(getIdent"{}", p))
getTok(p)
eat(p, tkCurlyRi)
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDotDot:
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDot, tkDotDot:
add(result, newIdentNodeP(p.tok.ident, p))
getTok(p)
of tkIntLit..tkCharLit:

View File

@@ -82,7 +82,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: seq[string]) =
# fail fast:
globalError(n.info, 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, ')')
var candidates = ""
@@ -138,17 +138,35 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
let overloadsState = result.state
if overloadsState != csMatch:
if nfDelegate in n.flags:
internalAssert f.kind == nkIdent
let calleeName = newStrNode(nkStrLit, f.ident.s)
calleeName.info = n.info
if nfDotField in n.flags:
internalAssert f.kind == nkIdent and n.sonsLen >= 2
let calleeName = newStrNode(nkStrLit, f.ident.s).withInfo(n.info)
let callOp = newIdentNode(idDelegator, n.info)
n.sons[0..0] = [callOp, calleeName]
orig.sons[0..0] = [callOp, calleeName]
# leave the op head symbol empty,
# we are going to try multiple variants
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)
if overloadsState == csEmpty and result.state == csEmpty:
localError(n.info, errUndeclaredIdentifier, considerAcc(f).s)
return

View File

@@ -683,20 +683,21 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
incl(c.p.owner.flags, sfSideEffect)
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode
proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
result = nil
checkMinSonsLen(n, 1)
var prc = n.sons[0]
if n.sons[0].kind == nkDotExpr:
if n.sons[0].kind == nkDotExpr:
checkSonsLen(n.sons[0], 2)
n.sons[0] = semFieldAccess(c, n.sons[0])
if n.sons[0].kind == nkDotCall:
if n.sons[0].kind == nkDotCall:
# it is a static call!
result = n.sons[0]
result.kind = nkCall
result.flags.incl nfExplicitCall
for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i])
return semExpr(c, result, flags)
else:
else:
n.sons[0] = semExpr(c, n.sons[0])
let nOrig = n.copyTree
semOpAux(c, n)
@@ -999,7 +1000,7 @@ proc dotTransformation(c: PContext, n: PNode): PNode =
else:
var i = considerAcc(n.sons[1])
result = newNodeI(nkDotCall, n.info)
result.flags.incl nfDelegate
result.flags.incl nfDotField
addSon(result, newIdentNode(i, n[1].info))
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 =
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``
# this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
# nodes?
let aOrig = nOrig[0]
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]])
result = semOverloadedCallAnalyseEffects(c, result, orig, {})
@@ -1777,22 +1779,6 @@ proc semBlock(c: PContext, n: PNode): PNode =
closeScope(c)
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 =
result = n.kind == nkDo 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:
# check if it is an expression macro:
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)
if s != nil:
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
setGenericParams(c, n.sons[0])
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)
else:
result = semIndirectOp(c, n, flags)

View File

@@ -4106,6 +4106,59 @@ types that will match the typedesc param:
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
=====================
@@ -4758,42 +4811,6 @@ This may change in future versions of language, but for now use
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
--------------
The `procvar`:idx: pragma is used to mark a proc that it can be passed to a

View 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)))

View File

@@ -68,8 +68,8 @@ Language Additions
- Exported templates are allowed to access hidden fields.
- The ``using statement`` enables you to more easily author domain-specific
languages and libraries providing OOP-like syntactic sugar.
- Added a new ``delegator pragma`` for handling calls to missing procs and
fields at compile-time.
- Added the possibility to override various dot operators in order to handle
calls to missing procs and reads from undeclared fields at compile-time.
- The overload resolution now supports ``static[T]`` params that must be
evaluable at compile-time.
- Support for user-defined type classes has been added.