the delegator pragma becomes a set of dot operators

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

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

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

View file

@ -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

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. - 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.