| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gc |
| |
| import "cmd/internal/obj" |
| |
| /* |
| * truncate float literal fv to 32-bit or 64-bit precision |
| * according to type; return truncated value. |
| */ |
| func truncfltlit(oldv *Mpflt, t *Type) *Mpflt { |
| if t == nil { |
| return oldv |
| } |
| |
| v := Val{} |
| v.Ctype = CTFLT |
| v.U.Fval = oldv |
| overflow(v, t) |
| |
| fv := new(Mpflt) |
| *fv = *oldv |
| |
| // convert large precision literal floating |
| // into limited precision (float64 or float32) |
| switch t.Etype { |
| case TFLOAT64: |
| d := mpgetflt(fv) |
| Mpmovecflt(fv, d) |
| |
| case TFLOAT32: |
| d := mpgetflt32(fv) |
| Mpmovecflt(fv, d) |
| } |
| |
| return fv |
| } |
| |
| /* |
| * convert n, if literal, to type t. |
| * implicit conversion. |
| */ |
| func Convlit(np **Node, t *Type) { |
| convlit1(np, t, false) |
| } |
| |
| /* |
| * convert n, if literal, to type t. |
| * return a new node if necessary |
| * (if n is a named constant, can't edit n->type directly). |
| */ |
| func convlit1(np **Node, t *Type, explicit bool) { |
| n := *np |
| if n == nil || t == nil || n.Type == nil || isideal(t) || n.Type == t { |
| return |
| } |
| if !explicit && !isideal(n.Type) { |
| return |
| } |
| |
| if n.Op == OLITERAL { |
| nn := Nod(OXXX, nil, nil) |
| *nn = *n |
| n = nn |
| *np = n |
| } |
| |
| switch n.Op { |
| default: |
| if n.Type == idealbool { |
| if t.Etype == TBOOL { |
| n.Type = t |
| } else { |
| n.Type = Types[TBOOL] |
| } |
| } |
| |
| if n.Type.Etype == TIDEAL { |
| Convlit(&n.Left, t) |
| Convlit(&n.Right, t) |
| n.Type = t |
| } |
| |
| return |
| |
| // target is invalid type for a constant? leave alone. |
| case OLITERAL: |
| if okforconst[t.Etype] == 0 && n.Type.Etype != TNIL { |
| defaultlit(&n, nil) |
| *np = n |
| return |
| } |
| |
| case OLSH, |
| ORSH: |
| convlit1(&n.Left, t, explicit && isideal(n.Left.Type)) |
| t = n.Left.Type |
| if t != nil && t.Etype == TIDEAL && n.Val.Ctype != CTINT { |
| n.Val = toint(n.Val) |
| } |
| if t != nil && Isint[t.Etype] == 0 { |
| Yyerror("invalid operation: %v (shift of type %v)", Nconv(n, 0), Tconv(t, 0)) |
| t = nil |
| } |
| |
| n.Type = t |
| return |
| |
| case OCOMPLEX: |
| if n.Type.Etype == TIDEAL { |
| switch t.Etype { |
| // If trying to convert to non-complex type, |
| // leave as complex128 and let typechecker complain. |
| default: |
| t = Types[TCOMPLEX128] |
| fallthrough |
| |
| //fallthrough |
| case TCOMPLEX128: |
| n.Type = t |
| |
| Convlit(&n.Left, Types[TFLOAT64]) |
| Convlit(&n.Right, Types[TFLOAT64]) |
| |
| case TCOMPLEX64: |
| n.Type = t |
| Convlit(&n.Left, Types[TFLOAT32]) |
| Convlit(&n.Right, Types[TFLOAT32]) |
| } |
| } |
| |
| return |
| } |
| |
| // avoided repeated calculations, errors |
| if Eqtype(n.Type, t) { |
| return |
| } |
| |
| ct := consttype(n) |
| var et int |
| if ct < 0 { |
| goto bad |
| } |
| |
| et = int(t.Etype) |
| if et == TINTER { |
| if ct == CTNIL && n.Type == Types[TNIL] { |
| n.Type = t |
| return |
| } |
| |
| defaultlit(np, nil) |
| return |
| } |
| |
| switch ct { |
| default: |
| goto bad |
| |
| case CTNIL: |
| switch et { |
| default: |
| n.Type = nil |
| goto bad |
| |
| // let normal conversion code handle it |
| case TSTRING: |
| return |
| |
| case TARRAY: |
| if !Isslice(t) { |
| goto bad |
| } |
| |
| case TPTR32, |
| TPTR64, |
| TINTER, |
| TMAP, |
| TCHAN, |
| TFUNC, |
| TUNSAFEPTR: |
| break |
| |
| // A nil literal may be converted to uintptr |
| // if it is an unsafe.Pointer |
| case TUINTPTR: |
| if n.Type.Etype == TUNSAFEPTR { |
| n.Val.U.Xval = new(Mpint) |
| Mpmovecfix(n.Val.U.Xval, 0) |
| n.Val.Ctype = CTINT |
| } else { |
| goto bad |
| } |
| } |
| |
| case CTSTR, |
| CTBOOL: |
| if et != int(n.Type.Etype) { |
| goto bad |
| } |
| |
| case CTINT, |
| CTRUNE, |
| CTFLT, |
| CTCPLX: |
| ct := int(n.Val.Ctype) |
| if Isint[et] != 0 { |
| switch ct { |
| default: |
| goto bad |
| |
| case CTCPLX, |
| CTFLT, |
| CTRUNE: |
| n.Val = toint(n.Val) |
| fallthrough |
| |
| // flowthrough |
| case CTINT: |
| overflow(n.Val, t) |
| } |
| } else if Isfloat[et] != 0 { |
| switch ct { |
| default: |
| goto bad |
| |
| case CTCPLX, |
| CTINT, |
| CTRUNE: |
| n.Val = toflt(n.Val) |
| fallthrough |
| |
| // flowthrough |
| case CTFLT: |
| n.Val.U.Fval = truncfltlit(n.Val.U.Fval, t) |
| } |
| } else if Iscomplex[et] != 0 { |
| switch ct { |
| default: |
| goto bad |
| |
| case CTFLT, |
| CTINT, |
| CTRUNE: |
| n.Val = tocplx(n.Val) |
| |
| case CTCPLX: |
| overflow(n.Val, t) |
| } |
| } else if et == TSTRING && (ct == CTINT || ct == CTRUNE) && explicit { |
| n.Val = tostr(n.Val) |
| } else { |
| goto bad |
| } |
| } |
| |
| n.Type = t |
| return |
| |
| bad: |
| if n.Diag == 0 { |
| if t.Broke == 0 { |
| Yyerror("cannot convert %v to type %v", Nconv(n, 0), Tconv(t, 0)) |
| } |
| n.Diag = 1 |
| } |
| |
| if isideal(n.Type) { |
| defaultlit(&n, nil) |
| *np = n |
| } |
| |
| return |
| } |
| |
| func copyval(v Val) Val { |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| i := new(Mpint) |
| mpmovefixfix(i, v.U.Xval) |
| v.U.Xval = i |
| |
| case CTFLT: |
| f := new(Mpflt) |
| mpmovefltflt(f, v.U.Fval) |
| v.U.Fval = f |
| |
| case CTCPLX: |
| c := new(Mpcplx) |
| mpmovefltflt(&c.Real, &v.U.Cval.Real) |
| mpmovefltflt(&c.Imag, &v.U.Cval.Imag) |
| v.U.Cval = c |
| } |
| |
| return v |
| } |
| |
| func tocplx(v Val) Val { |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| c := new(Mpcplx) |
| Mpmovefixflt(&c.Real, v.U.Xval) |
| Mpmovecflt(&c.Imag, 0.0) |
| v.Ctype = CTCPLX |
| v.U.Cval = c |
| |
| case CTFLT: |
| c := new(Mpcplx) |
| mpmovefltflt(&c.Real, v.U.Fval) |
| Mpmovecflt(&c.Imag, 0.0) |
| v.Ctype = CTCPLX |
| v.U.Cval = c |
| } |
| |
| return v |
| } |
| |
| func toflt(v Val) Val { |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| f := new(Mpflt) |
| Mpmovefixflt(f, v.U.Xval) |
| v.Ctype = CTFLT |
| v.U.Fval = f |
| |
| case CTCPLX: |
| f := new(Mpflt) |
| mpmovefltflt(f, &v.U.Cval.Real) |
| if mpcmpfltc(&v.U.Cval.Imag, 0) != 0 { |
| Yyerror("constant %v%vi truncated to real", Fconv(&v.U.Cval.Real, obj.FmtSharp), Fconv(&v.U.Cval.Imag, obj.FmtSharp|obj.FmtSign)) |
| } |
| v.Ctype = CTFLT |
| v.U.Fval = f |
| } |
| |
| return v |
| } |
| |
| func toint(v Val) Val { |
| switch v.Ctype { |
| case CTRUNE: |
| v.Ctype = CTINT |
| |
| case CTFLT: |
| i := new(Mpint) |
| if mpmovefltfix(i, v.U.Fval) < 0 { |
| Yyerror("constant %v truncated to integer", Fconv(v.U.Fval, obj.FmtSharp)) |
| } |
| v.Ctype = CTINT |
| v.U.Xval = i |
| |
| case CTCPLX: |
| i := new(Mpint) |
| if mpmovefltfix(i, &v.U.Cval.Real) < 0 { |
| Yyerror("constant %v%vi truncated to integer", Fconv(&v.U.Cval.Real, obj.FmtSharp), Fconv(&v.U.Cval.Imag, obj.FmtSharp|obj.FmtSign)) |
| } |
| if mpcmpfltc(&v.U.Cval.Imag, 0) != 0 { |
| Yyerror("constant %v%vi truncated to real", Fconv(&v.U.Cval.Real, obj.FmtSharp), Fconv(&v.U.Cval.Imag, obj.FmtSharp|obj.FmtSign)) |
| } |
| v.Ctype = CTINT |
| v.U.Xval = i |
| } |
| |
| return v |
| } |
| |
| func doesoverflow(v Val, t *Type) bool { |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| if Isint[t.Etype] == 0 { |
| Fatal("overflow: %v integer constant", Tconv(t, 0)) |
| } |
| if Mpcmpfixfix(v.U.Xval, Minintval[t.Etype]) < 0 || Mpcmpfixfix(v.U.Xval, Maxintval[t.Etype]) > 0 { |
| return true |
| } |
| |
| case CTFLT: |
| if Isfloat[t.Etype] == 0 { |
| Fatal("overflow: %v floating-point constant", Tconv(t, 0)) |
| } |
| if mpcmpfltflt(v.U.Fval, minfltval[t.Etype]) <= 0 || mpcmpfltflt(v.U.Fval, maxfltval[t.Etype]) >= 0 { |
| return true |
| } |
| |
| case CTCPLX: |
| if Iscomplex[t.Etype] == 0 { |
| Fatal("overflow: %v complex constant", Tconv(t, 0)) |
| } |
| if mpcmpfltflt(&v.U.Cval.Real, minfltval[t.Etype]) <= 0 || mpcmpfltflt(&v.U.Cval.Real, maxfltval[t.Etype]) >= 0 || mpcmpfltflt(&v.U.Cval.Imag, minfltval[t.Etype]) <= 0 || mpcmpfltflt(&v.U.Cval.Imag, maxfltval[t.Etype]) >= 0 { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| func overflow(v Val, t *Type) { |
| // v has already been converted |
| // to appropriate form for t. |
| if t == nil || t.Etype == TIDEAL { |
| return |
| } |
| |
| if !doesoverflow(v, t) { |
| return |
| } |
| |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| Yyerror("constant %v overflows %v", Bconv(v.U.Xval, 0), Tconv(t, 0)) |
| |
| case CTFLT: |
| Yyerror("constant %v overflows %v", Fconv(v.U.Fval, obj.FmtSharp), Tconv(t, 0)) |
| |
| case CTCPLX: |
| Yyerror("constant %v overflows %v", Fconv(v.U.Fval, obj.FmtSharp), Tconv(t, 0)) |
| } |
| } |
| |
| func tostr(v Val) Val { |
| switch v.Ctype { |
| case CTINT, |
| CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, Minintval[TINT]) < 0 || Mpcmpfixfix(v.U.Xval, Maxintval[TINT]) > 0 { |
| Yyerror("overflow in int -> string") |
| } |
| rune_ := uint(Mpgetfix(v.U.Xval)) |
| s := &Strlit{S: string(rune_)} |
| v = Val{} |
| v.Ctype = CTSTR |
| v.U.Sval = s |
| |
| case CTFLT: |
| Yyerror("no float -> string") |
| fallthrough |
| |
| case CTNIL: |
| v = Val{} |
| v.Ctype = CTSTR |
| v.U.Sval = new(Strlit) |
| } |
| |
| return v |
| } |
| |
| func consttype(n *Node) int { |
| if n == nil || n.Op != OLITERAL { |
| return -1 |
| } |
| return int(n.Val.Ctype) |
| } |
| |
| func Isconst(n *Node, ct int) bool { |
| t := consttype(n) |
| |
| // If the caller is asking for CTINT, allow CTRUNE too. |
| // Makes life easier for back ends. |
| return t == ct || (ct == CTINT && t == CTRUNE) |
| } |
| |
| func saveorig(n *Node) *Node { |
| if n == n.Orig { |
| // duplicate node for n->orig. |
| n1 := Nod(OLITERAL, nil, nil) |
| |
| n.Orig = n1 |
| *n1 = *n |
| } |
| |
| return n.Orig |
| } |
| |
| /* |
| * if n is constant, rewrite as OLITERAL node. |
| */ |
| func evconst(n *Node) { |
| // pick off just the opcodes that can be |
| // constant evaluated. |
| switch n.Op { |
| default: |
| return |
| |
| case OADD, |
| OAND, |
| OANDAND, |
| OANDNOT, |
| OARRAYBYTESTR, |
| OCOM, |
| ODIV, |
| OEQ, |
| OGE, |
| OGT, |
| OLE, |
| OLSH, |
| OLT, |
| OMINUS, |
| OMOD, |
| OMUL, |
| ONE, |
| ONOT, |
| OOR, |
| OOROR, |
| OPLUS, |
| ORSH, |
| OSUB, |
| OXOR: |
| break |
| |
| case OCONV: |
| if n.Type == nil { |
| return |
| } |
| if okforconst[n.Type.Etype] == 0 && n.Type.Etype != TNIL { |
| return |
| } |
| |
| // merge adjacent constants in the argument list. |
| case OADDSTR: |
| var nr *Node |
| var nl *Node |
| var str *Strlit |
| var l2 *NodeList |
| for l1 := n.List; l1 != nil; l1 = l1.Next { |
| if Isconst(l1.N, CTSTR) && l1.Next != nil && Isconst(l1.Next.N, CTSTR) { |
| // merge from l1 up to but not including l2 |
| str = new(Strlit) |
| l2 = l1 |
| for l2 != nil && Isconst(l2.N, CTSTR) { |
| nr = l2.N |
| str.S += nr.Val.U.Sval.S |
| l2 = l2.Next |
| } |
| |
| nl = Nod(OXXX, nil, nil) |
| *nl = *l1.N |
| nl.Orig = nl |
| nl.Val.Ctype = CTSTR |
| nl.Val.U.Sval = str |
| l1.N = nl |
| l1.Next = l2 |
| } |
| } |
| |
| // fix list end pointer. |
| for l2 := n.List; l2 != nil; l2 = l2.Next { |
| n.List.End = l2 |
| } |
| |
| // collapse single-constant list to single constant. |
| if count(n.List) == 1 && Isconst(n.List.N, CTSTR) { |
| n.Op = OLITERAL |
| n.Val = n.List.N.Val |
| } |
| |
| return |
| } |
| |
| nl := n.Left |
| if nl == nil || nl.Type == nil { |
| return |
| } |
| if consttype(nl) < 0 { |
| return |
| } |
| wl := int(nl.Type.Etype) |
| if Isint[wl] != 0 || Isfloat[wl] != 0 || Iscomplex[wl] != 0 { |
| wl = TIDEAL |
| } |
| |
| nr := n.Right |
| var rv Val |
| var lno int |
| var wr int |
| var v Val |
| var norig *Node |
| if nr == nil { |
| goto unary |
| } |
| if nr.Type == nil { |
| return |
| } |
| if consttype(nr) < 0 { |
| return |
| } |
| wr = int(nr.Type.Etype) |
| if Isint[wr] != 0 || Isfloat[wr] != 0 || Iscomplex[wr] != 0 { |
| wr = TIDEAL |
| } |
| |
| // check for compatible general types (numeric, string, etc) |
| if wl != wr { |
| goto illegal |
| } |
| |
| // check for compatible types. |
| switch n.Op { |
| // ideal const mixes with anything but otherwise must match. |
| default: |
| if nl.Type.Etype != TIDEAL { |
| defaultlit(&nr, nl.Type) |
| n.Right = nr |
| } |
| |
| if nr.Type.Etype != TIDEAL { |
| defaultlit(&nl, nr.Type) |
| n.Left = nl |
| } |
| |
| if nl.Type.Etype != nr.Type.Etype { |
| goto illegal |
| } |
| |
| // right must be unsigned. |
| // left can be ideal. |
| case OLSH, |
| ORSH: |
| defaultlit(&nr, Types[TUINT]) |
| |
| n.Right = nr |
| if nr.Type != nil && (Issigned[nr.Type.Etype] != 0 || Isint[nr.Type.Etype] == 0) { |
| goto illegal |
| } |
| if nl.Val.Ctype != CTRUNE { |
| nl.Val = toint(nl.Val) |
| } |
| nr.Val = toint(nr.Val) |
| } |
| |
| // copy numeric value to avoid modifying |
| // n->left, in case someone still refers to it (e.g. iota). |
| v = nl.Val |
| |
| if wl == TIDEAL { |
| v = copyval(v) |
| } |
| |
| rv = nr.Val |
| |
| // convert to common ideal |
| if v.Ctype == CTCPLX || rv.Ctype == CTCPLX { |
| v = tocplx(v) |
| rv = tocplx(rv) |
| } |
| |
| if v.Ctype == CTFLT || rv.Ctype == CTFLT { |
| v = toflt(v) |
| rv = toflt(rv) |
| } |
| |
| // Rune and int turns into rune. |
| if v.Ctype == CTRUNE && rv.Ctype == CTINT { |
| rv.Ctype = CTRUNE |
| } |
| if v.Ctype == CTINT && rv.Ctype == CTRUNE { |
| if n.Op == OLSH || n.Op == ORSH { |
| rv.Ctype = CTINT |
| } else { |
| v.Ctype = CTRUNE |
| } |
| } |
| |
| if v.Ctype != rv.Ctype { |
| // Use of undefined name as constant? |
| if (v.Ctype == 0 || rv.Ctype == 0) && nerrors > 0 { |
| return |
| } |
| Fatal("constant type mismatch %v(%d) %v(%d)", Tconv(nl.Type, 0), v.Ctype, Tconv(nr.Type, 0), rv.Ctype) |
| } |
| |
| // run op |
| switch uint32(n.Op)<<16 | uint32(v.Ctype) { |
| default: |
| goto illegal |
| |
| case OADD<<16 | CTINT, |
| OADD<<16 | CTRUNE: |
| mpaddfixfix(v.U.Xval, rv.U.Xval, 0) |
| |
| case OSUB<<16 | CTINT, |
| OSUB<<16 | CTRUNE: |
| mpsubfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OMUL<<16 | CTINT, |
| OMUL<<16 | CTRUNE: |
| mpmulfixfix(v.U.Xval, rv.U.Xval) |
| |
| case ODIV<<16 | CTINT, |
| ODIV<<16 | CTRUNE: |
| if mpcmpfixc(rv.U.Xval, 0) == 0 { |
| Yyerror("division by zero") |
| Mpmovecfix(v.U.Xval, 1) |
| break |
| } |
| |
| mpdivfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OMOD<<16 | CTINT, |
| OMOD<<16 | CTRUNE: |
| if mpcmpfixc(rv.U.Xval, 0) == 0 { |
| Yyerror("division by zero") |
| Mpmovecfix(v.U.Xval, 1) |
| break |
| } |
| |
| mpmodfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OLSH<<16 | CTINT, |
| OLSH<<16 | CTRUNE: |
| mplshfixfix(v.U.Xval, rv.U.Xval) |
| |
| case ORSH<<16 | CTINT, |
| ORSH<<16 | CTRUNE: |
| mprshfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OOR<<16 | CTINT, |
| OOR<<16 | CTRUNE: |
| mporfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OAND<<16 | CTINT, |
| OAND<<16 | CTRUNE: |
| mpandfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OANDNOT<<16 | CTINT, |
| OANDNOT<<16 | CTRUNE: |
| mpandnotfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OXOR<<16 | CTINT, |
| OXOR<<16 | CTRUNE: |
| mpxorfixfix(v.U.Xval, rv.U.Xval) |
| |
| case OADD<<16 | CTFLT: |
| mpaddfltflt(v.U.Fval, rv.U.Fval) |
| |
| case OSUB<<16 | CTFLT: |
| mpsubfltflt(v.U.Fval, rv.U.Fval) |
| |
| case OMUL<<16 | CTFLT: |
| mpmulfltflt(v.U.Fval, rv.U.Fval) |
| |
| case ODIV<<16 | CTFLT: |
| if mpcmpfltc(rv.U.Fval, 0) == 0 { |
| Yyerror("division by zero") |
| Mpmovecflt(v.U.Fval, 1.0) |
| break |
| } |
| |
| mpdivfltflt(v.U.Fval, rv.U.Fval) |
| |
| // The default case above would print 'ideal % ideal', |
| // which is not quite an ideal error. |
| case OMOD<<16 | CTFLT: |
| if n.Diag == 0 { |
| Yyerror("illegal constant expression: floating-point %% operation") |
| n.Diag = 1 |
| } |
| |
| return |
| |
| case OADD<<16 | CTCPLX: |
| mpaddfltflt(&v.U.Cval.Real, &rv.U.Cval.Real) |
| mpaddfltflt(&v.U.Cval.Imag, &rv.U.Cval.Imag) |
| |
| case OSUB<<16 | CTCPLX: |
| mpsubfltflt(&v.U.Cval.Real, &rv.U.Cval.Real) |
| mpsubfltflt(&v.U.Cval.Imag, &rv.U.Cval.Imag) |
| |
| case OMUL<<16 | CTCPLX: |
| cmplxmpy(v.U.Cval, rv.U.Cval) |
| |
| case ODIV<<16 | CTCPLX: |
| if mpcmpfltc(&rv.U.Cval.Real, 0) == 0 && mpcmpfltc(&rv.U.Cval.Imag, 0) == 0 { |
| Yyerror("complex division by zero") |
| Mpmovecflt(&rv.U.Cval.Real, 1.0) |
| Mpmovecflt(&rv.U.Cval.Imag, 0.0) |
| break |
| } |
| |
| cmplxdiv(v.U.Cval, rv.U.Cval) |
| |
| case OEQ<<16 | CTNIL: |
| goto settrue |
| |
| case ONE<<16 | CTNIL: |
| goto setfalse |
| |
| case OEQ<<16 | CTINT, |
| OEQ<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) == 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case ONE<<16 | CTINT, |
| ONE<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLT<<16 | CTINT, |
| OLT<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) < 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLE<<16 | CTINT, |
| OLE<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) <= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGE<<16 | CTINT, |
| OGE<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) >= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGT<<16 | CTINT, |
| OGT<<16 | CTRUNE: |
| if Mpcmpfixfix(v.U.Xval, rv.U.Xval) > 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OEQ<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) == 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case ONE<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLT<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) < 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLE<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) <= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGE<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) >= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGT<<16 | CTFLT: |
| if mpcmpfltflt(v.U.Fval, rv.U.Fval) > 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OEQ<<16 | CTCPLX: |
| if mpcmpfltflt(&v.U.Cval.Real, &rv.U.Cval.Real) == 0 && mpcmpfltflt(&v.U.Cval.Imag, &rv.U.Cval.Imag) == 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case ONE<<16 | CTCPLX: |
| if mpcmpfltflt(&v.U.Cval.Real, &rv.U.Cval.Real) != 0 || mpcmpfltflt(&v.U.Cval.Imag, &rv.U.Cval.Imag) != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OEQ<<16 | CTSTR: |
| if cmpslit(nl, nr) == 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case ONE<<16 | CTSTR: |
| if cmpslit(nl, nr) != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLT<<16 | CTSTR: |
| if cmpslit(nl, nr) < 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OLE<<16 | CTSTR: |
| if cmpslit(nl, nr) <= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGE<<16 | CTSTR: |
| if cmpslit(nl, nr) >= 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OGT<<16 | CTSTR: |
| if cmpslit(nl, nr) > 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OOROR<<16 | CTBOOL: |
| if v.U.Bval != 0 || rv.U.Bval != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OANDAND<<16 | CTBOOL: |
| if v.U.Bval != 0 && rv.U.Bval != 0 { |
| goto settrue |
| } |
| goto setfalse |
| |
| case OEQ<<16 | CTBOOL: |
| if v.U.Bval == rv.U.Bval { |
| goto settrue |
| } |
| goto setfalse |
| |
| case ONE<<16 | CTBOOL: |
| if v.U.Bval != rv.U.Bval { |
| goto settrue |
| } |
| goto setfalse |
| } |
| |
| goto ret |
| |
| // copy numeric value to avoid modifying |
| // nl, in case someone still refers to it (e.g. iota). |
| unary: |
| v = nl.Val |
| |
| if wl == TIDEAL { |
| v = copyval(v) |
| } |
| |
| switch uint32(n.Op)<<16 | uint32(v.Ctype) { |
| default: |
| if n.Diag == 0 { |
| Yyerror("illegal constant expression %v %v", Oconv(int(n.Op), 0), Tconv(nl.Type, 0)) |
| n.Diag = 1 |
| } |
| |
| return |
| |
| case OCONV<<16 | CTNIL, |
| OARRAYBYTESTR<<16 | CTNIL: |
| if n.Type.Etype == TSTRING { |
| v = tostr(v) |
| nl.Type = n.Type |
| break |
| } |
| fallthrough |
| |
| // fall through |
| case OCONV<<16 | CTINT, |
| OCONV<<16 | CTRUNE, |
| OCONV<<16 | CTFLT, |
| OCONV<<16 | CTSTR: |
| convlit1(&nl, n.Type, true) |
| |
| v = nl.Val |
| |
| case OPLUS<<16 | CTINT, |
| OPLUS<<16 | CTRUNE: |
| break |
| |
| case OMINUS<<16 | CTINT, |
| OMINUS<<16 | CTRUNE: |
| mpnegfix(v.U.Xval) |
| |
| case OCOM<<16 | CTINT, |
| OCOM<<16 | CTRUNE: |
| et := Txxx |
| if nl.Type != nil { |
| et = int(nl.Type.Etype) |
| } |
| |
| // calculate the mask in b |
| // result will be (a ^ mask) |
| var b Mpint |
| switch et { |
| // signed guys change sign |
| default: |
| Mpmovecfix(&b, -1) |
| |
| // unsigned guys invert their bits |
| case TUINT8, |
| TUINT16, |
| TUINT32, |
| TUINT64, |
| TUINT, |
| TUINTPTR: |
| mpmovefixfix(&b, Maxintval[et]) |
| } |
| |
| mpxorfixfix(v.U.Xval, &b) |
| |
| case OPLUS<<16 | CTFLT: |
| break |
| |
| case OMINUS<<16 | CTFLT: |
| mpnegflt(v.U.Fval) |
| |
| case OPLUS<<16 | CTCPLX: |
| break |
| |
| case OMINUS<<16 | CTCPLX: |
| mpnegflt(&v.U.Cval.Real) |
| mpnegflt(&v.U.Cval.Imag) |
| |
| case ONOT<<16 | CTBOOL: |
| if v.U.Bval == 0 { |
| goto settrue |
| } |
| goto setfalse |
| } |
| |
| ret: |
| norig = saveorig(n) |
| *n = *nl |
| |
| // restore value of n->orig. |
| n.Orig = norig |
| |
| n.Val = v |
| |
| // check range. |
| lno = int(setlineno(n)) |
| |
| overflow(v, n.Type) |
| lineno = int32(lno) |
| |
| // truncate precision for non-ideal float. |
| if v.Ctype == CTFLT && n.Type.Etype != TIDEAL { |
| n.Val.U.Fval = truncfltlit(v.U.Fval, n.Type) |
| } |
| return |
| |
| settrue: |
| norig = saveorig(n) |
| *n = *Nodbool(true) |
| n.Orig = norig |
| return |
| |
| setfalse: |
| norig = saveorig(n) |
| *n = *Nodbool(false) |
| n.Orig = norig |
| return |
| |
| illegal: |
| if n.Diag == 0 { |
| Yyerror("illegal constant expression: %v %v %v", Tconv(nl.Type, 0), Oconv(int(n.Op), 0), Tconv(nr.Type, 0)) |
| n.Diag = 1 |
| } |
| |
| return |
| } |
| |
| func nodlit(v Val) *Node { |
| n := Nod(OLITERAL, nil, nil) |
| n.Val = v |
| switch v.Ctype { |
| default: |
| Fatal("nodlit ctype %d", v.Ctype) |
| |
| case CTSTR: |
| n.Type = idealstring |
| |
| case CTBOOL: |
| n.Type = idealbool |
| |
| case CTINT, |
| CTRUNE, |
| CTFLT, |
| CTCPLX: |
| n.Type = Types[TIDEAL] |
| |
| case CTNIL: |
| n.Type = Types[TNIL] |
| } |
| |
| return n |
| } |
| |
| func nodcplxlit(r Val, i Val) *Node { |
| r = toflt(r) |
| i = toflt(i) |
| |
| c := new(Mpcplx) |
| n := Nod(OLITERAL, nil, nil) |
| n.Type = Types[TIDEAL] |
| n.Val.U.Cval = c |
| n.Val.Ctype = CTCPLX |
| |
| if r.Ctype != CTFLT || i.Ctype != CTFLT { |
| Fatal("nodcplxlit ctype %d/%d", r.Ctype, i.Ctype) |
| } |
| |
| mpmovefltflt(&c.Real, r.U.Fval) |
| mpmovefltflt(&c.Imag, i.U.Fval) |
| return n |
| } |
| |
| // idealkind returns a constant kind like consttype |
| // but for an arbitrary "ideal" (untyped constant) expression. |
| func idealkind(n *Node) int { |
| if n == nil || !isideal(n.Type) { |
| return CTxxx |
| } |
| |
| switch n.Op { |
| default: |
| return CTxxx |
| |
| case OLITERAL: |
| return int(n.Val.Ctype) |
| |
| // numeric kinds. |
| case OADD, |
| OAND, |
| OANDNOT, |
| OCOM, |
| ODIV, |
| OMINUS, |
| OMOD, |
| OMUL, |
| OSUB, |
| OXOR, |
| OOR, |
| OPLUS: |
| k1 := idealkind(n.Left) |
| |
| k2 := idealkind(n.Right) |
| if k1 > k2 { |
| return k1 |
| } else { |
| return k2 |
| } |
| fallthrough |
| |
| case OREAL, |
| OIMAG: |
| return CTFLT |
| |
| case OCOMPLEX: |
| return CTCPLX |
| |
| case OADDSTR: |
| return CTSTR |
| |
| case OANDAND, |
| OEQ, |
| OGE, |
| OGT, |
| OLE, |
| OLT, |
| ONE, |
| ONOT, |
| OOROR, |
| OCMPSTR, |
| OCMPIFACE: |
| return CTBOOL |
| |
| // shifts (beware!). |
| case OLSH, |
| ORSH: |
| return idealkind(n.Left) |
| } |
| } |
| |
| func defaultlit(np **Node, t *Type) { |
| n := *np |
| if n == nil || !isideal(n.Type) { |
| return |
| } |
| |
| if n.Op == OLITERAL { |
| nn := Nod(OXXX, nil, nil) |
| *nn = *n |
| n = nn |
| *np = n |
| } |
| |
| lno := int(setlineno(n)) |
| ctype := idealkind(n) |
| var t1 *Type |
| switch ctype { |
| default: |
| if t != nil { |
| Convlit(np, t) |
| return |
| } |
| |
| if n.Val.Ctype == CTNIL { |
| lineno = int32(lno) |
| if n.Diag == 0 { |
| Yyerror("use of untyped nil") |
| n.Diag = 1 |
| } |
| |
| n.Type = nil |
| break |
| } |
| |
| if n.Val.Ctype == CTSTR { |
| t1 := Types[TSTRING] |
| Convlit(np, t1) |
| break |
| } |
| |
| Yyerror("defaultlit: unknown literal: %v", Nconv(n, 0)) |
| |
| case CTxxx: |
| Fatal("defaultlit: idealkind is CTxxx: %v", Nconv(n, obj.FmtSign)) |
| |
| case CTBOOL: |
| t1 := Types[TBOOL] |
| if t != nil && t.Etype == TBOOL { |
| t1 = t |
| } |
| Convlit(np, t1) |
| |
| case CTINT: |
| t1 = Types[TINT] |
| goto num |
| |
| case CTRUNE: |
| t1 = runetype |
| goto num |
| |
| case CTFLT: |
| t1 = Types[TFLOAT64] |
| goto num |
| |
| case CTCPLX: |
| t1 = Types[TCOMPLEX128] |
| goto num |
| } |
| |
| lineno = int32(lno) |
| return |
| |
| num: |
| if t != nil { |
| if Isint[t.Etype] != 0 { |
| t1 = t |
| n.Val = toint(n.Val) |
| } else if Isfloat[t.Etype] != 0 { |
| t1 = t |
| n.Val = toflt(n.Val) |
| } else if Iscomplex[t.Etype] != 0 { |
| t1 = t |
| n.Val = tocplx(n.Val) |
| } |
| } |
| |
| overflow(n.Val, t1) |
| Convlit(np, t1) |
| lineno = int32(lno) |
| return |
| } |
| |
| /* |
| * defaultlit on both nodes simultaneously; |
| * if they're both ideal going in they better |
| * get the same type going out. |
| * force means must assign concrete (non-ideal) type. |
| */ |
| func defaultlit2(lp **Node, rp **Node, force int) { |
| l := *lp |
| r := *rp |
| if l.Type == nil || r.Type == nil { |
| return |
| } |
| if !isideal(l.Type) { |
| Convlit(rp, l.Type) |
| return |
| } |
| |
| if !isideal(r.Type) { |
| Convlit(lp, r.Type) |
| return |
| } |
| |
| if force == 0 { |
| return |
| } |
| if l.Type.Etype == TBOOL { |
| Convlit(lp, Types[TBOOL]) |
| Convlit(rp, Types[TBOOL]) |
| } |
| |
| lkind := idealkind(l) |
| rkind := idealkind(r) |
| if lkind == CTCPLX || rkind == CTCPLX { |
| Convlit(lp, Types[TCOMPLEX128]) |
| Convlit(rp, Types[TCOMPLEX128]) |
| return |
| } |
| |
| if lkind == CTFLT || rkind == CTFLT { |
| Convlit(lp, Types[TFLOAT64]) |
| Convlit(rp, Types[TFLOAT64]) |
| return |
| } |
| |
| if lkind == CTRUNE || rkind == CTRUNE { |
| Convlit(lp, runetype) |
| Convlit(rp, runetype) |
| return |
| } |
| |
| Convlit(lp, Types[TINT]) |
| Convlit(rp, Types[TINT]) |
| } |
| |
| func cmpslit(l, r *Node) int { |
| return stringsCompare(l.Val.U.Sval.S, r.Val.U.Sval.S) |
| } |
| |
| func Smallintconst(n *Node) bool { |
| if n.Op == OLITERAL && Isconst(n, CTINT) && n.Type != nil { |
| switch Simtype[n.Type.Etype] { |
| case TINT8, |
| TUINT8, |
| TINT16, |
| TUINT16, |
| TINT32, |
| TUINT32, |
| TBOOL, |
| TPTR32: |
| return true |
| |
| case TIDEAL, |
| TINT64, |
| TUINT64, |
| TPTR64: |
| if Mpcmpfixfix(n.Val.U.Xval, Minintval[TINT32]) < 0 || Mpcmpfixfix(n.Val.U.Xval, Maxintval[TINT32]) > 0 { |
| break |
| } |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| func nonnegconst(n *Node) int { |
| if n.Op == OLITERAL && n.Type != nil { |
| switch Simtype[n.Type.Etype] { |
| // check negative and 2^31 |
| case TINT8, |
| TUINT8, |
| TINT16, |
| TUINT16, |
| TINT32, |
| TUINT32, |
| TINT64, |
| TUINT64, |
| TIDEAL: |
| if Mpcmpfixfix(n.Val.U.Xval, Minintval[TUINT32]) < 0 || Mpcmpfixfix(n.Val.U.Xval, Maxintval[TINT32]) > 0 { |
| break |
| } |
| return int(Mpgetfix(n.Val.U.Xval)) |
| } |
| } |
| |
| return -1 |
| } |
| |
| /* |
| * convert x to type et and back to int64 |
| * for sign extension and truncation. |
| */ |
| func iconv(x int64, et int) int64 { |
| switch et { |
| case TINT8: |
| x = int64(int8(x)) |
| |
| case TUINT8: |
| x = int64(uint8(x)) |
| |
| case TINT16: |
| x = int64(int16(x)) |
| |
| case TUINT16: |
| x = int64(uint64(x)) |
| |
| case TINT32: |
| x = int64(int32(x)) |
| |
| case TUINT32: |
| x = int64(uint32(x)) |
| |
| case TINT64, |
| TUINT64: |
| break |
| } |
| |
| return x |
| } |
| |
| /* |
| * convert constant val to type t; leave in con. |
| * for back end. |
| */ |
| func Convconst(con *Node, t *Type, val *Val) { |
| tt := Simsimtype(t) |
| |
| // copy the constant for conversion |
| Nodconst(con, Types[TINT8], 0) |
| |
| con.Type = t |
| con.Val = *val |
| |
| if Isint[tt] != 0 { |
| con.Val.Ctype = CTINT |
| con.Val.U.Xval = new(Mpint) |
| var i int64 |
| switch val.Ctype { |
| default: |
| Fatal("convconst ctype=%d %v", val.Ctype, Tconv(t, obj.FmtLong)) |
| |
| case CTINT, |
| CTRUNE: |
| i = Mpgetfix(val.U.Xval) |
| |
| case CTBOOL: |
| i = int64(val.U.Bval) |
| |
| case CTNIL: |
| i = 0 |
| } |
| |
| i = iconv(i, tt) |
| Mpmovecfix(con.Val.U.Xval, i) |
| return |
| } |
| |
| if Isfloat[tt] != 0 { |
| con.Val = toflt(con.Val) |
| if con.Val.Ctype != CTFLT { |
| Fatal("convconst ctype=%d %v", con.Val.Ctype, Tconv(t, 0)) |
| } |
| if tt == TFLOAT32 { |
| con.Val.U.Fval = truncfltlit(con.Val.U.Fval, t) |
| } |
| return |
| } |
| |
| if Iscomplex[tt] != 0 { |
| con.Val = tocplx(con.Val) |
| if tt == TCOMPLEX64 { |
| con.Val.U.Cval.Real = *truncfltlit(&con.Val.U.Cval.Real, Types[TFLOAT32]) |
| con.Val.U.Cval.Imag = *truncfltlit(&con.Val.U.Cval.Imag, Types[TFLOAT32]) |
| } |
| |
| return |
| } |
| |
| Fatal("convconst %v constant", Tconv(t, obj.FmtLong)) |
| } |
| |
| // complex multiply v *= rv |
| // (a, b) * (c, d) = (a*c - b*d, b*c + a*d) |
| func cmplxmpy(v *Mpcplx, rv *Mpcplx) { |
| var ac Mpflt |
| var bd Mpflt |
| var bc Mpflt |
| var ad Mpflt |
| |
| mpmovefltflt(&ac, &v.Real) |
| mpmulfltflt(&ac, &rv.Real) // ac |
| |
| mpmovefltflt(&bd, &v.Imag) |
| |
| mpmulfltflt(&bd, &rv.Imag) // bd |
| |
| mpmovefltflt(&bc, &v.Imag) |
| |
| mpmulfltflt(&bc, &rv.Real) // bc |
| |
| mpmovefltflt(&ad, &v.Real) |
| |
| mpmulfltflt(&ad, &rv.Imag) // ad |
| |
| mpmovefltflt(&v.Real, &ac) |
| |
| mpsubfltflt(&v.Real, &bd) // ac-bd |
| |
| mpmovefltflt(&v.Imag, &bc) |
| |
| mpaddfltflt(&v.Imag, &ad) // bc+ad |
| } |
| |
| // complex divide v /= rv |
| // (a, b) / (c, d) = ((a*c + b*d), (b*c - a*d))/(c*c + d*d) |
| func cmplxdiv(v *Mpcplx, rv *Mpcplx) { |
| var ac Mpflt |
| var bd Mpflt |
| var bc Mpflt |
| var ad Mpflt |
| var cc_plus_dd Mpflt |
| |
| mpmovefltflt(&cc_plus_dd, &rv.Real) |
| mpmulfltflt(&cc_plus_dd, &rv.Real) // cc |
| |
| mpmovefltflt(&ac, &rv.Imag) |
| |
| mpmulfltflt(&ac, &rv.Imag) // dd |
| |
| mpaddfltflt(&cc_plus_dd, &ac) // cc+dd |
| |
| mpmovefltflt(&ac, &v.Real) |
| |
| mpmulfltflt(&ac, &rv.Real) // ac |
| |
| mpmovefltflt(&bd, &v.Imag) |
| |
| mpmulfltflt(&bd, &rv.Imag) // bd |
| |
| mpmovefltflt(&bc, &v.Imag) |
| |
| mpmulfltflt(&bc, &rv.Real) // bc |
| |
| mpmovefltflt(&ad, &v.Real) |
| |
| mpmulfltflt(&ad, &rv.Imag) // ad |
| |
| mpmovefltflt(&v.Real, &ac) |
| |
| mpaddfltflt(&v.Real, &bd) // ac+bd |
| mpdivfltflt(&v.Real, &cc_plus_dd) // (ac+bd)/(cc+dd) |
| |
| mpmovefltflt(&v.Imag, &bc) |
| |
| mpsubfltflt(&v.Imag, &ad) // bc-ad |
| mpdivfltflt(&v.Imag, &cc_plus_dd) // (bc+ad)/(cc+dd) |
| } |
| |
| // Is n a Go language constant (as opposed to a compile-time constant)? |
| // Expressions derived from nil, like string([]byte(nil)), while they |
| // may be known at compile time, are not Go language constants. |
| // Only called for expressions known to evaluated to compile-time |
| // constants. |
| func isgoconst(n *Node) bool { |
| if n.Orig != nil { |
| n = n.Orig |
| } |
| |
| switch n.Op { |
| case OADD, |
| OADDSTR, |
| OAND, |
| OANDAND, |
| OANDNOT, |
| OCOM, |
| ODIV, |
| OEQ, |
| OGE, |
| OGT, |
| OLE, |
| OLSH, |
| OLT, |
| OMINUS, |
| OMOD, |
| OMUL, |
| ONE, |
| ONOT, |
| OOR, |
| OOROR, |
| OPLUS, |
| ORSH, |
| OSUB, |
| OXOR, |
| OIOTA, |
| OCOMPLEX, |
| OREAL, |
| OIMAG: |
| if isgoconst(n.Left) && (n.Right == nil || isgoconst(n.Right)) { |
| return true |
| } |
| |
| case OCONV: |
| if okforconst[n.Type.Etype] != 0 && isgoconst(n.Left) { |
| return true |
| } |
| |
| case OLEN, |
| OCAP: |
| l := n.Left |
| if isgoconst(l) { |
| return true |
| } |
| |
| // Special case: len/cap is constant when applied to array or |
| // pointer to array when the expression does not contain |
| // function calls or channel receive operations. |
| t := l.Type |
| |
| if t != nil && Isptr[t.Etype] != 0 { |
| t = t.Type |
| } |
| if Isfixedarray(t) && !hascallchan(l) { |
| return true |
| } |
| |
| case OLITERAL: |
| if n.Val.Ctype != CTNIL { |
| return true |
| } |
| |
| case ONAME: |
| l := n.Sym.Def |
| if l != nil && l.Op == OLITERAL && n.Val.Ctype != CTNIL { |
| return true |
| } |
| |
| case ONONAME: |
| if n.Sym.Def != nil && n.Sym.Def.Op == OIOTA { |
| return true |
| } |
| |
| // Only constant calls are unsafe.Alignof, Offsetof, and Sizeof. |
| case OCALL: |
| l := n.Left |
| |
| for l.Op == OPAREN { |
| l = l.Left |
| } |
| if l.Op != ONAME || l.Sym.Pkg != unsafepkg { |
| break |
| } |
| if l.Sym.Name == "Alignof" || l.Sym.Name == "Offsetof" || l.Sym.Name == "Sizeof" { |
| return true |
| } |
| } |
| |
| //dump("nonconst", n); |
| return false |
| } |
| |
| func hascallchan(n *Node) bool { |
| if n == nil { |
| return false |
| } |
| switch n.Op { |
| case OAPPEND, |
| OCALL, |
| OCALLFUNC, |
| OCALLINTER, |
| OCALLMETH, |
| OCAP, |
| OCLOSE, |
| OCOMPLEX, |
| OCOPY, |
| ODELETE, |
| OIMAG, |
| OLEN, |
| OMAKE, |
| ONEW, |
| OPANIC, |
| OPRINT, |
| OPRINTN, |
| OREAL, |
| ORECOVER, |
| ORECV: |
| return true |
| } |
| |
| if hascallchan(n.Left) || hascallchan(n.Right) { |
| return true |
| } |
| |
| for l := n.List; l != nil; l = l.Next { |
| if hascallchan(l.N) { |
| return true |
| } |
| } |
| for l := n.Rlist; l != nil; l = l.Next { |
| if hascallchan(l.N) { |
| return true |
| } |
| } |
| |
| return false |
| } |