mirror of https://github.com/golang/go.git
- composites, receivers, various add. checks
R=r OCL=17295 CL=17295
This commit is contained in:
parent
082f116b2d
commit
b705ac6cf5
|
|
@ -68,38 +68,8 @@ export func NewList() *List {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Types
|
|
||||||
|
|
||||||
export const /* channel mode */ (
|
|
||||||
FULL = iota;
|
|
||||||
SEND;
|
|
||||||
RECV;
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
export type Type struct {
|
|
||||||
pos, tok int;
|
|
||||||
expr *Expr; // type name, array length
|
|
||||||
mode int; // channel mode
|
|
||||||
key *Type; // map key
|
|
||||||
elt *Type; // array element, map or channel value, or pointer base type
|
|
||||||
list *List; // struct fields, interface methods, function parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export func NewType(pos, tok int) *Type {
|
|
||||||
t := new(Type);
|
|
||||||
t.pos, t.tok = pos, tok;
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Expressions
|
// Expressions
|
||||||
//
|
|
||||||
// Expression pairs are represented as binary expressions with operator ":"
|
|
||||||
// Expression lists are represented as binary expressions with operator ","
|
|
||||||
|
|
||||||
export type Expr struct {
|
export type Expr struct {
|
||||||
pos, tok int;
|
pos, tok int;
|
||||||
|
|
@ -135,9 +105,53 @@ export func NewLit(pos, tok int, s string) *Expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export func NewTypeExpr(pos int, t *Type) *Expr {
|
// ----------------------------------------------------------------------------
|
||||||
|
// Types
|
||||||
|
|
||||||
|
export const /* channel mode */ (
|
||||||
|
FULL = iota;
|
||||||
|
SEND;
|
||||||
|
RECV;
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export type Type struct {
|
||||||
|
pos, tok int;
|
||||||
|
expr *Expr; // type name, array length
|
||||||
|
mode int; // channel mode
|
||||||
|
key *Type; // receiver type, map key
|
||||||
|
elt *Type; // array element, map or channel value, or pointer base type, result type
|
||||||
|
list *List; // struct fields, interface methods, function parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (t *Type) nfields() int {
|
||||||
|
nx, nt := 0, 0;
|
||||||
|
for i, n := 0, t.list.len(); i < n; i++ {
|
||||||
|
if t.list.at(i).(*Expr).tok == Scanner.TYPE {
|
||||||
|
nt++;
|
||||||
|
} else {
|
||||||
|
nx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nx == 0 {
|
||||||
|
return nt;
|
||||||
|
}
|
||||||
|
return nx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export func NewType(pos, tok int) *Type {
|
||||||
|
t := new(Type);
|
||||||
|
t.pos, t.tok = pos, tok;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// requires complete Type type
|
||||||
|
export func NewTypeExpr(t *Type) *Expr {
|
||||||
e := new(Expr);
|
e := new(Expr);
|
||||||
e.pos, e.tok, e.t = pos, Scanner.TYPE, t;
|
e.pos, e.tok, e.t = t.pos, Scanner.TYPE, t;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,18 +131,19 @@ func (P *Parser) ParseIdent() *Node.Expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (P *Parser) ParseIdentList() *Node.List {
|
func (P *Parser) ParseIdentList() *Node.Expr {
|
||||||
P.Trace("IdentList");
|
P.Trace("IdentList");
|
||||||
|
|
||||||
list := Node.NewList();
|
x := P.ParseIdent();
|
||||||
list.Add(P.ParseIdent());
|
if P.tok == Scanner.COMMA {
|
||||||
for P.tok == Scanner.COMMA {
|
pos := P.pos;
|
||||||
P.Next();
|
P.Next();
|
||||||
list.Add(P.ParseIdent());
|
y := P.ParseIdentList();
|
||||||
|
x := Node.NewExpr(pos, Scanner.COMMA, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
P.Ecart();
|
P.Ecart();
|
||||||
return list;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -241,6 +242,7 @@ func (P *Parser) ParseChannelType() *Node.Type {
|
||||||
func (P *Parser) ParseVarDeclList(list *Node.List) {
|
func (P *Parser) ParseVarDeclList(list *Node.List) {
|
||||||
P.Trace("VarDeclList");
|
P.Trace("VarDeclList");
|
||||||
|
|
||||||
|
// parse a list of types
|
||||||
i0 := list.len();
|
i0 := list.len();
|
||||||
list.Add(P.ParseType());
|
list.Add(P.ParseType());
|
||||||
for P.tok == Scanner.COMMA {
|
for P.tok == Scanner.COMMA {
|
||||||
|
|
@ -250,6 +252,7 @@ func (P *Parser) ParseVarDeclList(list *Node.List) {
|
||||||
|
|
||||||
typ := P.TryType();
|
typ := P.TryType();
|
||||||
|
|
||||||
|
// convert the list into a list of (type) expressions
|
||||||
if typ != nil {
|
if typ != nil {
|
||||||
// all list entries must be identifiers
|
// all list entries must be identifiers
|
||||||
// convert the type entries into identifiers
|
// convert the type entries into identifiers
|
||||||
|
|
@ -263,14 +266,14 @@ func (P *Parser) ParseVarDeclList(list *Node.List) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add type
|
// add type
|
||||||
list.Add(Node.NewTypeExpr(typ.pos, typ));
|
list.Add(Node.NewTypeExpr(typ));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// all list entries are types
|
// all list entries are types
|
||||||
// convert all type entries into type expressions
|
// convert all type entries into type expressions
|
||||||
for i, n := i0, list.len(); i < n; i++ {
|
for i, n := i0, list.len(); i < n; i++ {
|
||||||
t := list.at(i).(*Node.Type);
|
t := list.at(i).(*Node.Type);
|
||||||
list.set(i, Node.NewTypeExpr(t.pos, t));
|
list.set(i, Node.NewTypeExpr(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
if P.tok == Scanner.COMMA {
|
if P.tok == Scanner.COMMA {
|
||||||
|
|
@ -339,7 +342,7 @@ func (P *Parser) ParseResult() *Node.Type {
|
||||||
if typ != nil {
|
if typ != nil {
|
||||||
t = Node.NewType(P.pos, Scanner.STRUCT);
|
t = Node.NewType(P.pos, Scanner.STRUCT);
|
||||||
t.list = Node.NewList();
|
t.list = Node.NewList();
|
||||||
t.list.Add(Node.NewTypeExpr(typ.pos, typ));
|
t.list.Add(Node.NewTypeExpr(typ));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -572,7 +575,7 @@ func (P *Parser) ParseOperand() *Node.Expr {
|
||||||
default:
|
default:
|
||||||
t := P.TryType();
|
t := P.TryType();
|
||||||
if t != nil {
|
if t != nil {
|
||||||
x = Node.NewTypeExpr(t.pos, t);
|
x = Node.NewTypeExpr(t);
|
||||||
} else {
|
} else {
|
||||||
P.Error(P.pos, "operand expected");
|
P.Error(P.pos, "operand expected");
|
||||||
P.Next(); // make progress
|
P.Next(); // make progress
|
||||||
|
|
@ -653,31 +656,42 @@ func (P *Parser) ParseCall(x *Node.Expr) *Node.Expr {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (P *Parser) ParseExpressionPairList(mode int) *Node.Expr {
|
||||||
|
P.Trace("ExpressionPairList");
|
||||||
|
|
||||||
|
x := P.ParseExpressionPair(mode);
|
||||||
|
if mode == 0 {
|
||||||
|
// first expression determines mode
|
||||||
|
if x.tok == Scanner.COLON {
|
||||||
|
mode = 2;
|
||||||
|
} else {
|
||||||
|
mode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if P.tok == Scanner.COMMA {
|
||||||
|
pos := P.pos;
|
||||||
|
P.Next();
|
||||||
|
if P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
|
||||||
|
y := P.ParseExpressionPairList(mode);
|
||||||
|
x = Node.NewExpr(pos, Scanner.COMMA, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
P.Ecart();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (P *Parser) ParseCompositeLit(t *Node.Type) *Node.Expr {
|
func (P *Parser) ParseCompositeLit(t *Node.Type) *Node.Expr {
|
||||||
P.Trace("CompositeLit");
|
P.Trace("CompositeLit");
|
||||||
|
|
||||||
mode := 0;
|
pos := P.pos;
|
||||||
P.Expect(Scanner.LBRACE);
|
P.Expect(Scanner.LBRACE);
|
||||||
for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
|
x := P.ParseExpressionPairList(0);
|
||||||
x := P.ParseExpressionPair(mode);
|
|
||||||
if mode == 0 {
|
|
||||||
// first expression determines mode
|
|
||||||
if x.tok == Scanner.COLON {
|
|
||||||
mode = 2;
|
|
||||||
} else {
|
|
||||||
mode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if P.tok == Scanner.COMMA {
|
|
||||||
P.Next();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
P.Expect(Scanner.RBRACE);
|
P.Expect(Scanner.RBRACE);
|
||||||
|
|
||||||
P.Ecart();
|
P.Ecart();
|
||||||
return Node.NewLit(P.pos, Scanner.INT, "0"); // "null" expr
|
return Node.NewExpr(pos, Scanner.LBRACE, Node.NewTypeExpr(t), x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -780,16 +794,16 @@ func (P *Parser) ParseSimpleStat() *Node.Stat {
|
||||||
P.Trace("SimpleStat");
|
P.Trace("SimpleStat");
|
||||||
|
|
||||||
var s *Node.Stat;
|
var s *Node.Stat;
|
||||||
|
|
||||||
x := P.ParseExpressionList();
|
x := P.ParseExpressionList();
|
||||||
|
|
||||||
switch P.tok {
|
switch P.tok {
|
||||||
case Scanner.COLON:
|
case Scanner.COLON:
|
||||||
// label declaration
|
// label declaration
|
||||||
if x.len() == 1 {
|
s = Node.NewStat(P.pos, Scanner.COLON);
|
||||||
s = Node.NewStat(P.pos, Scanner.COLON);
|
s.expr = x;
|
||||||
s.expr = x;
|
if x.len() != 1 {
|
||||||
} else {
|
P.Error(x.pos, "illegal label declaration");
|
||||||
P.Error(P.pos, "illegal label declaration");
|
|
||||||
}
|
}
|
||||||
P.Next(); // consume ":"
|
P.Next(); // consume ":"
|
||||||
P.opt_semi = true;
|
P.opt_semi = true;
|
||||||
|
|
@ -803,23 +817,22 @@ func (P *Parser) ParseSimpleStat() *Node.Stat {
|
||||||
P.Next();
|
P.Next();
|
||||||
s.lhs = x;
|
s.lhs = x;
|
||||||
s.expr = P.ParseExpressionList();
|
s.expr = P.ParseExpressionList();
|
||||||
|
if l, r := x.len(), s.expr.len(); l > 1 && r > 1 && l != r {
|
||||||
|
P.Error(x.pos, "arity of lhs doesn't match rhs");
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
var pos, tok int;
|
||||||
if P.tok == Scanner.INC || P.tok == Scanner.DEC {
|
if P.tok == Scanner.INC || P.tok == Scanner.DEC {
|
||||||
s = Node.NewStat(P.pos, P.tok);
|
pos, tok = P.pos, P.tok;
|
||||||
if x.len() == 1 {
|
P.Next();
|
||||||
s.expr = x;
|
|
||||||
} else {
|
|
||||||
P.Error(P.pos, "more then one operand");
|
|
||||||
}
|
|
||||||
P.Next(); // consume "++" or "--"
|
|
||||||
} else {
|
} else {
|
||||||
s = Node.NewStat(P.pos, 0); // TODO give this a token value
|
pos, tok = x.pos, 0; // TODO give this a token value
|
||||||
if x.len() == 1 {
|
}
|
||||||
s.expr = x;
|
s = Node.NewStat(pos, tok);
|
||||||
} else {
|
s.expr = x;
|
||||||
P.Error(P.pos, "syntax error");
|
if x.len() != 1 {
|
||||||
}
|
P.Error(x.pos, "only one expression allowed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1173,15 +1186,15 @@ func (P *Parser) ParseVarSpec(exported bool) *Node.Decl {
|
||||||
P.Trace("VarSpec");
|
P.Trace("VarSpec");
|
||||||
|
|
||||||
d := Node.NewDecl(P.pos, Scanner.VAR, exported);
|
d := Node.NewDecl(P.pos, Scanner.VAR, exported);
|
||||||
P.ParseIdentList();
|
d.ident = P.ParseIdentList();
|
||||||
if P.tok == Scanner.ASSIGN {
|
if P.tok == Scanner.ASSIGN {
|
||||||
P.Next();
|
P.Next();
|
||||||
P.ParseExpressionList();
|
d.val = P.ParseExpressionList();
|
||||||
} else {
|
} else {
|
||||||
P.ParseVarType();
|
d.typ = P.ParseVarType();
|
||||||
if P.tok == Scanner.ASSIGN {
|
if P.tok == Scanner.ASSIGN {
|
||||||
P.Next();
|
P.Next();
|
||||||
P.ParseExpressionList();
|
d.val = P.ParseExpressionList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1246,20 +1259,20 @@ func (P *Parser) ParseFunctionDecl(exported bool) *Node.Decl {
|
||||||
|
|
||||||
d := Node.NewDecl(P.pos, Scanner.FUNC, exported);
|
d := Node.NewDecl(P.pos, Scanner.FUNC, exported);
|
||||||
P.Expect(Scanner.FUNC);
|
P.Expect(Scanner.FUNC);
|
||||||
|
|
||||||
|
var recv *Node.Type;
|
||||||
if P.tok == Scanner.LPAREN {
|
if P.tok == Scanner.LPAREN {
|
||||||
pos := P.pos;
|
pos := P.pos;
|
||||||
recv := P.ParseParameters();
|
recv = P.ParseParameters();
|
||||||
// TODO: fix this
|
if recv.nfields() != 1 {
|
||||||
/*
|
|
||||||
if recv.list.len() != 1 {
|
|
||||||
P.Error(pos, "must have exactly one receiver");
|
P.Error(pos, "must have exactly one receiver");
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.ident = P.ParseIdent();
|
d.ident = P.ParseIdent();
|
||||||
d.typ = P.ParseFunctionType();
|
d.typ = P.ParseFunctionType();
|
||||||
|
d.typ.key = recv;
|
||||||
|
|
||||||
if P.tok == Scanner.LBRACE {
|
if P.tok == Scanner.LBRACE {
|
||||||
P.scope_lev++;
|
P.scope_lev++;
|
||||||
d.list = P.ParseBlock();
|
d.list = P.ParseBlock();
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@ func (P *Printer) CloseScope(paren string) {
|
||||||
func (P *Printer) Type(t *Node.Type)
|
func (P *Printer) Type(t *Node.Type)
|
||||||
func (P *Printer) Expr(x *Node.Expr)
|
func (P *Printer) Expr(x *Node.Expr)
|
||||||
|
|
||||||
func (P *Printer) Parameters(list *Node.List) {
|
func (P *Printer) Parameters(pos int, list *Node.List) {
|
||||||
|
P.String(pos, "(");
|
||||||
var prev int;
|
var prev int;
|
||||||
for i, n := 0, list.len(); i < n; i++ {
|
for i, n := 0, list.len(); i < n; i++ {
|
||||||
x := list.at(i).(*Node.Expr);
|
x := list.at(i).(*Node.Expr);
|
||||||
|
|
@ -84,6 +85,7 @@ func (P *Printer) Parameters(list *Node.List) {
|
||||||
P.Expr(x);
|
P.Expr(x);
|
||||||
prev = x.tok;
|
prev = x.tok;
|
||||||
}
|
}
|
||||||
|
P.String(0, ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -123,7 +125,7 @@ func (P *Printer) Type(t *Node.Type) {
|
||||||
if t.expr != nil {
|
if t.expr != nil {
|
||||||
P.Expr(t.expr);
|
P.Expr(t.expr);
|
||||||
}
|
}
|
||||||
P.String(0, "] ");
|
P.String(0, "]");
|
||||||
P.Type(t.elt);
|
P.Type(t.elt);
|
||||||
|
|
||||||
case Scanner.STRUCT:
|
case Scanner.STRUCT:
|
||||||
|
|
@ -137,7 +139,7 @@ func (P *Printer) Type(t *Node.Type) {
|
||||||
case Scanner.MAP:
|
case Scanner.MAP:
|
||||||
P.String(t.pos, "[");
|
P.String(t.pos, "[");
|
||||||
P.Type(t.key);
|
P.Type(t.key);
|
||||||
P.String(0, "] ");
|
P.String(0, "]");
|
||||||
P.Type(t.elt);
|
P.Type(t.elt);
|
||||||
|
|
||||||
case Scanner.CHAN:
|
case Scanner.CHAN:
|
||||||
|
|
@ -168,13 +170,10 @@ func (P *Printer) Type(t *Node.Type) {
|
||||||
P.Type(t.elt);
|
P.Type(t.elt);
|
||||||
|
|
||||||
case Scanner.LPAREN:
|
case Scanner.LPAREN:
|
||||||
P.String(t.pos, "(");
|
P.Parameters(t.pos, t.list);
|
||||||
P.Parameters(t.list);
|
|
||||||
P.String(0, ")");
|
|
||||||
if t.elt != nil {
|
if t.elt != nil {
|
||||||
P.String(0, " (");
|
P.Blank();
|
||||||
P.Parameters(t.elt.list);
|
P.Parameters(0, t.elt.list);
|
||||||
P.String(0, ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -192,9 +191,6 @@ func (P *Printer) Expr1(x *Node.Expr, prec1 int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch x.tok {
|
switch x.tok {
|
||||||
case Scanner.VAR:
|
|
||||||
panic("UNIMPLEMENTED (VAR)");
|
|
||||||
|
|
||||||
case Scanner.TYPE:
|
case Scanner.TYPE:
|
||||||
P.Type(x.t);
|
P.Type(x.t);
|
||||||
|
|
||||||
|
|
@ -222,6 +218,12 @@ func (P *Printer) Expr1(x *Node.Expr, prec1 int) {
|
||||||
P.String(x.pos, "(");
|
P.String(x.pos, "(");
|
||||||
P.Expr1(x.y, 0);
|
P.Expr1(x.y, 0);
|
||||||
P.String(0, ")");
|
P.String(0, ")");
|
||||||
|
|
||||||
|
case Scanner.LBRACE:
|
||||||
|
P.Expr1(x.x, 8);
|
||||||
|
P.String(x.pos, "{");
|
||||||
|
P.Expr1(x.y, 0);
|
||||||
|
P.String(0, "}");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if x.x == nil {
|
if x.x == nil {
|
||||||
|
|
@ -396,35 +398,6 @@ func (P *Printer) Stat(s *Node.Stat) {
|
||||||
// Declarations
|
// Declarations
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (P *Printer) DoFuncDecl(x *AST.FuncDecl) {
|
|
||||||
P.String("func ");
|
|
||||||
if x.typ.recv != nil {
|
|
||||||
P.String("(");
|
|
||||||
P.DoVarDeclList(x.typ.recv);
|
|
||||||
P.String(") ");
|
|
||||||
}
|
|
||||||
P.DoIdent(x.ident);
|
|
||||||
P.DoFunctionType(x.typ);
|
|
||||||
if x.body != nil {
|
|
||||||
P.String(" ");
|
|
||||||
P.DoBlock(x.body);
|
|
||||||
} else {
|
|
||||||
P.String(" ;");
|
|
||||||
}
|
|
||||||
P.NewLine();
|
|
||||||
P.NewLine();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (P *Printer) DoMethodDecl(x *AST.MethodDecl) {
|
|
||||||
//P.DoIdent(x.ident);
|
|
||||||
//P.DoFunctionType(x.typ);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
func (P *Printer) Declaration(d *Node.Decl, parenthesized bool) {
|
func (P *Printer) Declaration(d *Node.Decl, parenthesized bool) {
|
||||||
if d == nil { // TODO remove this check
|
if d == nil { // TODO remove this check
|
||||||
P.String(0, "<nil decl>");
|
P.String(0, "<nil decl>");
|
||||||
|
|
@ -448,6 +421,11 @@ func (P *Printer) Declaration(d *Node.Decl, parenthesized bool) {
|
||||||
P.CloseScope(")");
|
P.CloseScope(")");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if d.tok == Scanner.FUNC && d.typ.key != nil {
|
||||||
|
P.Parameters(0, d.typ.key.list);
|
||||||
|
P.Blank();
|
||||||
|
}
|
||||||
|
|
||||||
P.Expr(d.ident);
|
P.Expr(d.ident);
|
||||||
|
|
||||||
if d.typ != nil {
|
if d.typ != nil {
|
||||||
|
|
@ -471,6 +449,10 @@ func (P *Printer) Declaration(d *Node.Decl, parenthesized bool) {
|
||||||
P.Blank();
|
P.Blank();
|
||||||
P.Block(d.list, true);
|
P.Block(d.list, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.tok != Scanner.TYPE {
|
||||||
|
P.semi = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
P.newl = 1;
|
P.newl = 1;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue