diff options
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/ast.nim | 4 | ||||
-rw-r--r-- | compiler/nim.ini | 2 | ||||
-rw-r--r-- | compiler/nim.nimrod.cfg | 2 | ||||
-rw-r--r-- | compiler/sem.nim | 20 | ||||
-rw-r--r-- | compiler/semexprs.nim | 20 | ||||
-rw-r--r-- | compiler/semgnrc.nim | 11 | ||||
-rw-r--r-- | compiler/seminst.nim | 4 | ||||
-rw-r--r-- | compiler/semstmts.nim | 3 | ||||
-rw-r--r-- | compiler/semtypes.nim | 74 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 36 | ||||
-rw-r--r-- | compiler/suggest.nim | 3 | ||||
-rw-r--r-- | compiler/types.nim | 20 | ||||
-rw-r--r-- | compiler/vmdef.nim | 3 | ||||
-rw-r--r-- | compiler/vmgen.nim | 13 |
14 files changed, 141 insertions, 74 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 565bb4353..d85dbf42c 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1517,8 +1517,8 @@ proc getStr*(a: PNode): string = proc getStrOrChar*(a: PNode): string = case a.kind of nkStrLit..nkTripleStrLit: result = a.strVal - of nkCharLit: result = $chr(int(a.intVal)) - else: + of nkCharLit..nkUInt64Lit: result = $chr(int(a.intVal)) + else: internalError(a.info, "getStrOrChar") result = "" diff --git a/compiler/nim.ini b/compiler/nim.ini index 576b6d2bb..1f14eb21b 100644 --- a/compiler/nim.ini +++ b/compiler/nim.ini @@ -125,7 +125,7 @@ Files: "start.bat" BinPath: r"bin;dist\mingw\bin;dist" ; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry -Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|doc\overview.html" +Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|overview.html" Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip" Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.1.3.zip|aporia\bin\aporia.exe" ; for now only NSIS supports optional downloads diff --git a/compiler/nim.nimrod.cfg b/compiler/nim.nimrod.cfg index ba7697c4c..f4d8b9dcb 100644 --- a/compiler/nim.nimrod.cfg +++ b/compiler/nim.nimrod.cfg @@ -1,7 +1,5 @@ # Special configuration file for the Nim project -# gc:markAndSweep - hint[XDeclaredButNotUsed]:off path:"llvm" path:"$projectPath/.." diff --git a/compiler/sem.nim b/compiler/sem.nim index 91adcac5e..9ac7ad139 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -67,12 +67,25 @@ proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = # error correction: result = copyTree(arg) result.typ = formal + else: + let x = result.skipConv + if x.kind == nkPar and formal.kind != tyExpr: + changeType(x, formal, check=true) proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode var commonTypeBegin = PType(kind: tyExpr) +proc isEmptyContainer(t: PType): bool = + case t.kind + of tyExpr, tyNil: result = true + of tyArray, tyArrayConstr: result = t.sons[1].kind == tyEmpty + of tySet, tySequence, tyOpenArray, tyVarargs: + result = t.sons[0].kind == tyEmpty + of tyGenericInst: result = isEmptyContainer(t.lastSon) + else: result = false + proc commonType*(x, y: PType): PType = # new type relation that is used for array constructors, # if expressions, etc.: @@ -96,6 +109,13 @@ proc commonType*(x, y: PType): PType = # check for seq[empty] vs. seq[int] let idx = ord(b.kind in {tyArray, tyArrayConstr}) if a.sons[idx].kind == tyEmpty: return y + elif a.kind == tyTuple and b.kind == tyTuple and a.len == b.len: + var nt: PType + for i in 0.. <a.len: + if isEmptyContainer(a.sons[i]) and not isEmptyContainer(b.sons[i]): + if nt.isNil: nt = copyType(a, a.owner, false) + nt.sons[i] = b.sons[i] + if not nt.isNil: result = nt #elif b.sons[idx].kind == tyEmpty: return x elif a.kind == tyRange and b.kind == tyRange: # consider: (range[0..3], range[0..4]) here. We should make that diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 25113aa5a..89110a479 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -219,7 +219,7 @@ proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) = # gnrc. params, then it won't be necessary to open a new scope here openScope(c) var lifted = liftParamType(c, skType, newNodeI(nkArgList, info), - t, ":anon", info) + t, ":anon", info) closeScope(c) if lifted != nil: t = lifted @@ -429,7 +429,8 @@ proc changeType(n: PNode, newType: PType, check: bool) = for i in countup(0, sonsLen(n) - 1): changeType(n.sons[i], elemType(newType), check) of nkPar: - if newType.kind != tyTuple: + let tup = newType.skipTypes({tyGenericInst}) + if tup.kind != tyTuple: internalError(n.info, "changeType: no tuple type for constructor") else: for i in countup(0, sonsLen(n) - 1): @@ -437,7 +438,7 @@ proc changeType(n: PNode, newType: PType, check: bool) = if m.kind == nkExprColonExpr: m = m.sons[1] n.sons[i] = m - changeType(m, newType.sons[i], check) + changeType(m, tup.sons[i], check) of nkCharLit..nkUInt64Lit: if check: let value = n.intVal @@ -925,18 +926,20 @@ const proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = let ty = if typ.kind == tyGenericInst: typ.skipGenericAlias - else: (internalAssert(typ.kind == tyCompositeTypeClass); typ.sons[1]) - + else: (internalAssert(typ.kind == tyCompositeTypeClass); + typ.sons[1].skipGenericAlias) + #debug ty let tbody = ty.sons[0] for s in countup(0, tbody.len-2): let tParam = tbody.sons[s] - if tParam.sym.name == paramName: + if tParam.sym.name.id == paramName.id: let rawTyp = ty.sons[s + 1] if rawTyp.kind == tyStatic: return rawTyp.n else: let foundTyp = makeTypeDesc(c, rawTyp) return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) + #echo "came here: returned nil" proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access @@ -963,6 +966,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = var ty = n.sons[0].typ var f: PSym = nil result = nil + if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone): if ty.kind == tyTypeDesc: ty = ty.base ty = ty.skipTypes(tyDotOpTransparent) @@ -971,7 +975,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = # look up if the identifier belongs to the enum: while ty != nil: f = getSymFromList(ty.n, i) - if f != nil: break + if f != nil: break ty = ty.sons[0] # enum inheritance if f != nil: result = newSymNode(f) @@ -983,7 +987,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = of tyTypeParamsHolders: return readTypeParameter(c, ty, i, n.info) of tyObject, tyTuple: - if ty.n.kind == nkRecList: + if ty.n != nil and ty.n.kind == nkRecList: for field in ty.n: if field.sym.name == i: n.typ = newTypeWithSons(c, tyFieldAccessor, @[ty, field.sym.typ]) diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 7c32b0051..1ab4f5989 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -60,13 +60,20 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, else: result = symChoice(c, n, s, scOpen) of skGenericParam: - result = newSymNodeTypeDesc(s, n.info) + if s.typ != nil and s.typ.kind == tyStatic: + if s.typ.n != nil: + result = s.typ.n + else: + result = n + else: + result = newSymNodeTypeDesc(s, n.info) styleCheckUse(n.info, s) of skParam: result = n styleCheckUse(n.info, s) of skType: - if (s.typ != nil) and (s.typ.kind != tyGenericParam): + if (s.typ != nil) and + (s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}): result = newSymNodeTypeDesc(s, n.info) else: result = n diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 2decb5d0b..81a4465c5 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -21,7 +21,8 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable, var q = a.sym if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic, tyIter}+tyTypeClasses: continue - var s = newSym(skType, q.name, getCurrOwner(), q.info) + let symKind = if q.typ.kind == tyStatic: skConst else: skType + var s = newSym(symKind, q.name, getCurrOwner(), q.info) s.flags = s.flags + {sfUsed, sfFromGeneric} var t = PType(idTableGet(pt, q.typ)) if t == nil: @@ -40,6 +41,7 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable, t = generateTypeInstance(c, pt, a, t) #t = ReplaceTypeVarsT(cl, t) s.typ = t + if t.kind == tyStatic: s.ast = t.n addDecl(c, s) entry.concreteTypes[i] = t diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a7603147c..3b0332939 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -653,7 +653,8 @@ proc checkForMetaFields(n: PNode) = template checkMeta(t) = if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags: localError(n.info, errTIsNotAConcreteType, t.typeToString) - + + if n.isNil: return case n.kind of nkRecList, nkRecCase: for s in n: checkForMetaFields(s) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 4305a48e1..eb15c3809 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -214,44 +214,48 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType = localError(n.info, errXExpectsOneTypeParam, "range") result = newOrPrevType(tyError, prev, c) +proc semArrayIndex(c: PContext, n: PNode): PType = + if isRange(n): result = semRangeAux(c, n, nil) + else: + let e = semExprWithType(c, n, {efDetermineType}) + if e.typ.kind == tyFromExpr: + result = makeRangeWithStaticExpr(c, e.typ.n) + elif e.kind in {nkIntLit..nkUInt64Lit}: + result = makeRangeType(c, 0, e.intVal-1, n.info, e.typ) + elif e.kind == nkSym and e.typ.kind == tyStatic: + if e.sym.ast != nil: + return semArrayIndex(c, e.sym.ast) + internalAssert c.inGenericContext > 0 + if not isOrdinalType(e.typ.lastSon): + localError(n[1].info, errOrdinalTypeExpected) + result = makeRangeWithStaticExpr(c, e) + result.flags.incl tfUnresolved + elif e.kind in nkCallKinds and hasGenericArguments(e): + if not isOrdinalType(e.typ): + localError(n[1].info, errOrdinalTypeExpected) + # This is an int returning call, depending on an + # yet unknown generic param (see tgenericshardcases). + # We are going to construct a range type that will be + # properly filled-out in semtypinst (see how tyStaticExpr + # is handled there). + result = makeRangeWithStaticExpr(c, e) + elif e.kind == nkIdent: + result = e.typ.skipTypes({tyTypeDesc}) + else: + let x = semConstExpr(c, e) + if x.kind in {nkIntLit..nkUInt64Lit}: + result = makeRangeType(c, 0, x.intVal-1, n.info, + x.typ.skipTypes({tyTypeDesc})) + else: + result = x.typ.skipTypes({tyTypeDesc}) + #localError(n[1].info, errConstExprExpected) + proc semArray(c: PContext, n: PNode, prev: PType): PType = - var indx, base: PType + var base: PType result = newOrPrevType(tyArray, prev, c) - if sonsLen(n) == 3: + if sonsLen(n) == 3: # 3 = length(array indx base) - if isRange(n[1]): indx = semRangeAux(c, n[1], nil) - else: - let e = semExprWithType(c, n.sons[1], {efDetermineType}) - if e.typ.kind == tyFromExpr: - indx = makeRangeWithStaticExpr(c, e.typ.n) - elif e.kind in {nkIntLit..nkUInt64Lit}: - indx = makeRangeType(c, 0, e.intVal-1, n.info, e.typ) - elif e.kind == nkSym and e.typ.kind == tyStatic: - if e.sym.ast != nil: return semArray(c, e.sym.ast, nil) - internalAssert c.inGenericContext > 0 - if not isOrdinalType(e.typ.lastSon): - localError(n[1].info, errOrdinalTypeExpected) - indx = makeRangeWithStaticExpr(c, e) - indx.flags.incl tfUnresolved - elif e.kind in nkCallKinds and hasGenericArguments(e): - if not isOrdinalType(e.typ): - localError(n[1].info, errOrdinalTypeExpected) - # This is an int returning call, depending on an - # yet unknown generic param (see tgenericshardcases). - # We are going to construct a range type that will be - # properly filled-out in semtypinst (see how tyStaticExpr - # is handled there). - indx = makeRangeWithStaticExpr(c, e) - elif e.kind == nkIdent: - indx = e.typ.skipTypes({tyTypeDesc}) - else: - let x = semConstExpr(c, e) - if x.kind in {nkIntLit..nkUInt64Lit}: - indx = makeRangeType(c, 0, x.intVal-1, n.info, - x.typ.skipTypes({tyTypeDesc})) - else: - indx = x.typ.skipTypes({tyTypeDesc}) - #localError(n[1].info, errConstExprExpected) + var indx = semArrayIndex(c, n[1]) addSonSkipIntLit(result, indx) if indx.kind == tyGenericInst: indx = lastSon(indx) if indx.kind notin {tyGenericParam, tyStatic, tyFromExpr}: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 1ec48bd0e..b58818a29 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -517,6 +517,16 @@ proc maybeSkipDistinct(t: PType, callee: PSym): PType = else: result = t +proc tryResolvingStaticExpr(c: var TCandidate, n: PNode): PNode = + # Consider this example: + # type Value[N: static[int]] = object + # proc foo[N](a: Value[N], r: range[0..(N-1)]) + # Here, N-1 will be initially nkStaticExpr that can be evaluated only after + # N is bound to a concrete value during the matching of the first param. + # This proc is used to evaluate such static expressions. + let instantiated = replaceTypesInBody(c.c, c.bindings, n) + result = c.c.semExpr(c.c, instantiated) + proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # typeRel can be used to establish various relationships between types: # @@ -620,6 +630,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # bugfix: accept integer conversions here #if result < isGeneric: result = isNone if result notin {isNone, isGeneric}: + # resolve any late-bound static expressions + # that may appear in the range: + for i in 0..1: + if f.n[i].kind == nkStaticExpr: + f.n.sons[i] = tryResolvingStaticExpr(c, f.n[i]) result = typeRangeRel(f, a) else: if skipTypes(f, {tyRange}).kind == a.kind: @@ -964,6 +979,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyStatic: if aOrig.kind == tyStatic: result = typeRel(c, f.lastSon, a) + if result != isNone and f.n != nil: + if not exprStructuralEquivalent(f.n, a.n): + result = isNone if result != isNone: put(c.bindings, f, aOrig) else: result = isNone @@ -1006,15 +1024,14 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyFromExpr: # fix the expression, so it contains the already instantiated types - let instantiated = replaceTypesInBody(c.c, c.bindings, f.n) - let reevaluted = c.c.semExpr(c.c, instantiated) - case reevaluted.typ.kind + let reevaluated = tryResolvingStaticExpr(c, f.n) + case reevaluated.typ.kind of tyTypeDesc: - result = typeRel(c, a, reevaluted.typ.base) + result = typeRel(c, a, reevaluated.typ.base) of tyStatic: - result = typeRel(c, a, reevaluted.typ.base) - if result != isNone and reevaluted.typ.n != nil: - if not exprStructuralEquivalent(aOrig.n, reevaluted.typ.n): + result = typeRel(c, a, reevaluated.typ.base) + if result != isNone and reevaluated.typ.n != nil: + if not exprStructuralEquivalent(aOrig.n, reevaluated.typ.n): result = isNone else: localError(f.n.info, errTypeExpected) @@ -1083,6 +1100,11 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, arg: PNode): PNode = # arg.typ can be nil in 'suggest': if isNil(arg.typ): return nil + + # sem'checking for 'echo' needs to be re-entrant: + # XXX we will revisit this issue after 0.10.2 is released + if f == arg.typ and arg.kind == nkHiddenStdConv: return arg + var call = newNodeI(nkCall, arg.info) call.add(f.n.copyTree) call.add(arg.copyTree) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index c700db323..f7b00c8f8 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -74,9 +74,6 @@ proc suggestField(c: PContext, s: PSym, outputs: var int) = suggestWriteln(symToStr(s, isLocal=true, sectionSuggest)) inc outputs -when not defined(nimhygiene): - {.pragma: inject.} - template wholeSymTab(cond, section: expr) {.immediate.} = var isLocal = true for scope in walkScopes(c.currentScope): diff --git a/compiler/types.nim b/compiler/types.nim index f67cd239e..4a77773e6 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -17,7 +17,7 @@ proc lastOrd*(t: PType): BiggestInt proc lengthOrd*(t: PType): BiggestInt type TPreferedDesc* = enum - preferName, preferDesc, preferExported, preferModuleInfo + preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string proc base*(t: PType): PType @@ -411,11 +411,13 @@ const "UserTypeClassInst", "CompositeTypeClass", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor"] +const preferToResolveSymbols = {preferName, preferModuleInfo, preferGenericArg} + proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = var t = typ result = "" if t == nil: return - if prefer in {preferName, preferModuleInfo} and t.sym != nil and + if prefer in preferToResolveSymbols and t.sym != nil and sfAnon notin t.sym.flags: if t.kind == tyInt and isIntLit(t): return t.sym.name.s & " literal(" & $t.n.intVal & ")" @@ -428,20 +430,26 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = if not isIntLit(t) or prefer == preferExported: result = typeToStr[t.kind] else: - result = "int literal(" & $t.n.intVal & ")" + if prefer == preferGenericArg: + result = $t.n.intVal + else: + result = "int literal(" & $t.n.intVal & ")" of tyGenericBody, tyGenericInst, tyGenericInvokation: result = typeToString(t.sons[0]) & '[' for i in countup(1, sonsLen(t) -1 -ord(t.kind != tyGenericInvokation)): if i > 1: add(result, ", ") - add(result, typeToString(t.sons[i])) + add(result, typeToString(t.sons[i], preferGenericArg)) add(result, ']') of tyTypeDesc: if t.base.kind == tyNone: result = "typedesc" else: result = "typedesc[" & typeToString(t.base) & "]" of tyStatic: internalAssert t.len > 0 - result = "static[" & typeToString(t.sons[0]) & "]" - if t.n != nil: result.add "(" & renderTree(t.n) & ")" + if prefer == preferGenericArg and t.n != nil: + result = t.n.renderTree + else: + result = "static[" & typeToString(t.sons[0]) & "]" + if t.n != nil: result.add "(" & renderTree(t.n) & ")" of tyUserTypeClass: internalAssert t.sym != nil and t.sym.owner != nil return t.sym.owner.name.s diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index c06606318..3d49cb130 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -164,7 +164,8 @@ type slotTempInt, # some temporary int slotTempFloat, # some temporary float slotTempStr, # some temporary string - slotTempComplex # some complex temporary (s.node field is used) + slotTempComplex, # some complex temporary (s.node field is used) + slotTempPerm # slot is temporary but permanent (hack) PProc* = ref object blocks*: seq[TBlock] # blocks; temp data structure diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index c4101874c..9a3fc260a 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -16,7 +16,7 @@ # types that use the 'node' field; the reason is that slots are # re-used in a register based VM. Example: # -# .. code-block:: nimrod +# .. code-block:: nim # let s = a & b # no matter what, create fresh node # s = a & b # no matter what, keep the node # @@ -28,7 +28,7 @@ # this copy depends on the involved types. import - unsigned, strutils, ast, astalgo, types, msgs, renderer, vmdef, + unsigned, strutils, ast, astalgo, types, msgs, renderer, vmdef, trees, intsets, rodread, magicsys, options, lowerings from os import splitFile @@ -58,6 +58,7 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = if i in jumpTargets: result.addf("L$1:\n", i) let x = c.code[i] + result.add($i) let opc = opcode(x) if opc in {opcConv, opcCast}: let y = c.code[i+1] @@ -188,7 +189,7 @@ proc getTemp(c: PCtx; typ: PType): TRegister = proc freeTemp(c: PCtx; r: TRegister) = let c = c.prc - if c.slots[r].kind >= slotSomeTemp: c.slots[r].inUse = false + if c.slots[r].kind in {slotSomeTemp..slotTempComplex}: c.slots[r].inUse = false proc getTempRange(c: PCtx; n: int; kind: TSlotKind): TRegister = # if register pressure is high, we re-use more aggressively: @@ -1074,8 +1075,10 @@ proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; c.gABC(n, opcNodeToReg, dest, dest) elif c.prc.slots[tmp].kind >= slotTempUnknown: gABC(c, n, opcAddrNode, dest, tmp) - # XXX this can still be wrong sometimes; hopefully it's only generated - # in call contexts, where it is safe + # hack ahead; in order to fix bug #1781 we mark the temporary as + # permanent, so that it's not used for anything else: + c.prc.slots[tmp].kind = slotTempPerm + # XXX this is still a hack #message(n.info, warnUser, "suspicious opcode used") else: gABC(c, n, opcAddrReg, dest, tmp) |