Commit 0ea9dd81 authored by Robert Griesemer's avatar Robert Griesemer

gofmt the last outstanding files in src/pkg

- added a list of issues to printer/nodes.go

R=rsc
http://go/go-review/1024002
parent 39fd52d3
...@@ -232,13 +232,13 @@ type Formatter func(state *State, value interface{}, ruleName string) bool ...@@ -232,13 +232,13 @@ type Formatter func(state *State, value interface{}, ruleName string) bool
// A FormatterMap is a set of custom formatters. // A FormatterMap is a set of custom formatters.
// It maps a rule name to a formatter function. // It maps a rule name to a formatter function.
// //
type FormatterMap map [string] Formatter; type FormatterMap map[string]Formatter
// A parsed format expression is built from the following nodes. // A parsed format expression is built from the following nodes.
// //
type ( type (
expr interface {}; expr interface{};
alternatives []expr; // x | y | z alternatives []expr; // x | y | z
...@@ -265,7 +265,7 @@ type ( ...@@ -265,7 +265,7 @@ type (
custom struct { custom struct {
ruleName string; ruleName string;
fun Formatter fun Formatter;
}; };
) )
...@@ -273,7 +273,7 @@ type ( ...@@ -273,7 +273,7 @@ type (
// A Format is the result of parsing a format specification. // A Format is the result of parsing a format specification.
// The format may be applied repeatedly to format values. // The format may be applied repeatedly to format values.
// //
type Format map [string] expr; type Format map[string]expr
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
...@@ -290,7 +290,7 @@ type Format map [string] expr; ...@@ -290,7 +290,7 @@ type Format map [string] expr;
// the receiver, and thus can be very light-weight. // the receiver, and thus can be very light-weight.
// //
type Environment interface { type Environment interface {
Copy() Environment Copy() Environment;
} }
...@@ -365,14 +365,14 @@ func (s *State) Write(data []byte) (int, os.Error) { ...@@ -365,14 +365,14 @@ func (s *State) Write(data []byte) (int, os.Error) {
// write text segment and indentation // write text segment and indentation
n1, _ := s.output.Write(data[i0 : i+1]); n1, _ := s.output.Write(data[i0 : i+1]);
n2, _ := s.output.Write(s.indent.Bytes()); n2, _ := s.output.Write(s.indent.Bytes());
n += n1 + n2; n += n1+n2;
i0 = i + 1; i0 = i+1;
s.linePos.Offset = s.output.Len(); s.linePos.Offset = s.output.Len();
s.linePos.Line++; s.linePos.Line++;
} }
} }
n3, _ := s.output.Write(data[i0 : len(data)]); n3, _ := s.output.Write(data[i0:len(data)]);
return n + n3, nil; return n+n3, nil;
} }
...@@ -505,7 +505,7 @@ func (s *State) eval(fexpr expr, value reflect.Value, index int) bool { ...@@ -505,7 +505,7 @@ func (s *State) eval(fexpr expr, value reflect.Value, index int) bool {
// segment contains a %-format at the beginning // segment contains a %-format at the beginning
if lit[1] == '%' { if lit[1] == '%' {
// "%%" is printed as a single "%" // "%%" is printed as a single "%"
s.Write(lit[1 : len(lit)]); s.Write(lit[1:len(lit)]);
} else { } else {
// use s instead of s.output to get indentation right // use s instead of s.output to get indentation right
fmt.Fprintf(s, string(lit), value.Interface()); fmt.Fprintf(s, string(lit), value.Interface());
...@@ -580,7 +580,7 @@ func (s *State) eval(fexpr expr, value reflect.Value, index int) bool { ...@@ -580,7 +580,7 @@ func (s *State) eval(fexpr expr, value reflect.Value, index int) bool {
ruleName := t.ruleName; ruleName := t.ruleName;
if ruleName == "" { if ruleName == "" {
// no alternate rule name, value type determines rule // no alternate rule name, value type determines rule
ruleName = typename(value.Type()) ruleName = typename(value.Type());
} }
fexpr = s.getFormat(ruleName); fexpr = s.getFormat(ruleName);
...@@ -687,7 +687,7 @@ func (f Format) Eval(env Environment, args ...) ([]byte, os.Error) { ...@@ -687,7 +687,7 @@ func (f Format) Eval(env Environment, args ...) ([]byte, os.Error) {
errors <- nil; // no errors errors <- nil; // no errors
}(); }();
err := <- errors; err := <-errors;
return s.output.Bytes(), err; return s.output.Bytes(), err;
} }
......
...@@ -29,8 +29,7 @@ func verify(t *testing.T, f Format, expected string, args ...) { ...@@ -29,8 +29,7 @@ func verify(t *testing.T, f Format, expected string, args ...) {
if result != expected { if result != expected {
t.Errorf( t.Errorf(
"result : `%s`\nexpected: `%s`\n\n", "result : `%s`\nexpected: `%s`\n\n",
result, expected result, expected);
)
} }
} }
...@@ -62,9 +61,9 @@ func formatter(s *State, value interface{}, rule_name string) bool { ...@@ -62,9 +61,9 @@ func formatter(s *State, value interface{}, rule_name string) bool {
func TestCustomFormatters(t *testing.T) { func TestCustomFormatters(t *testing.T) {
fmap0 := FormatterMap{ "/": formatter }; fmap0 := FormatterMap{"/": formatter};
fmap1 := FormatterMap{ "int": formatter, "blank": formatter, "nil": formatter }; fmap1 := FormatterMap{"int": formatter, "blank": formatter, "nil": formatter};
fmap2 := FormatterMap{ "testing.T": formatter }; fmap2 := FormatterMap{"testing.T": formatter};
f := parse(t, `int=`, fmap0); f := parse(t, `int=`, fmap0);
verify(t, f, ``, 1, 2, 3); verify(t, f, ``, 1, 2, 3);
...@@ -103,8 +102,7 @@ func check(t *testing.T, form, expected string, args ...) { ...@@ -103,8 +102,7 @@ func check(t *testing.T, form, expected string, args ...) {
if result != expected { if result != expected {
t.Errorf( t.Errorf(
"format : %s\nresult : `%s`\nexpected: `%s`\n\n", "format : %s\nresult : `%s`\nexpected: `%s`\n\n",
form, result, expected form, result, expected);
)
} }
} }
...@@ -164,7 +162,7 @@ func TestChanTypes(t *testing.T) { ...@@ -164,7 +162,7 @@ func TestChanTypes(t *testing.T) {
check(t, `chan="chan"`, `chan`, c0); check(t, `chan="chan"`, `chan`, c0);
c1 := make(chan int); c1 := make(chan int);
go func(){ c1 <- 42 }(); go func() { c1 <- 42 }();
check(t, `chan="chan"`, `chan`, c1); check(t, `chan="chan"`, `chan`, c1);
// check(t, `chan=*`, `42`, c1); // reflection support for chans incomplete // check(t, `chan=*`, `42`, c1); // reflection support for chans incomplete
} }
...@@ -174,14 +172,14 @@ func TestFuncTypes(t *testing.T) { ...@@ -174,14 +172,14 @@ func TestFuncTypes(t *testing.T) {
var f0 func() int; var f0 func() int;
check(t, `func="func"`, `func`, f0); check(t, `func="func"`, `func`, f0);
f1 := func() int { return 42; }; f1 := func() int { return 42 };
check(t, `func="func"`, `func`, f1); check(t, `func="func"`, `func`, f1);
// check(t, `func=*`, `42`, f1); // reflection support for funcs incomplete // check(t, `func=*`, `42`, f1); // reflection support for funcs incomplete
} }
func TestInterfaceTypes(t *testing.T) { func TestInterfaceTypes(t *testing.T) {
var i0 interface{}; var i0 interface{}
check(t, `interface="interface"`, `interface`, i0); check(t, `interface="interface"`, `interface`, i0);
i0 = "foo"; i0 = "foo";
...@@ -234,8 +232,7 @@ type T1 struct { ...@@ -234,8 +232,7 @@ type T1 struct {
a int; a int;
} }
const F1 = const F1 = `datafmt "datafmt";`
`datafmt "datafmt";`
`int = "%d";` `int = "%d";`
`datafmt.T1 = "<" a ">";` `datafmt.T1 = "<" a ">";`
...@@ -252,17 +249,15 @@ type T2 struct { ...@@ -252,17 +249,15 @@ type T2 struct {
p *T1; p *T1;
} }
const F2a = const F2a = F1 +
F1 +
`string = "%s";` `string = "%s";`
`ptr = *;` `ptr = *;`
`datafmt.T2 = s ["-" p "-"];` `datafmt.T2 = s ["-" p "-"];`
const F2b = const F2b = F1 +
F1 +
`string = "%s";` `string = "%s";`
`ptr = *;` `ptr = *;`
`datafmt.T2 = s ("-" p "-" | "empty");`; `datafmt.T2 = s ("-" p "-" | "empty");`
func TestStruct2(t *testing.T) { func TestStruct2(t *testing.T) {
check(t, F2a, "foo", T2{"foo", nil}); check(t, F2a, "foo", T2{"foo", nil});
...@@ -279,14 +274,12 @@ type T3 struct { ...@@ -279,14 +274,12 @@ type T3 struct {
a []int; a []int;
} }
const F3a = const F3a = `datafmt "datafmt";`
`datafmt "datafmt";`
`default = "%v";` `default = "%v";`
`array = *;` `array = *;`
`datafmt.T3 = s {" " a a / ","};` `datafmt.T3 = s {" " a a / ","};`
const F3b = const F3b = `datafmt "datafmt";`
`datafmt "datafmt";`
`int = "%d";` `int = "%d";`
`string = "%s";` `string = "%s";`
`array = *;` `array = *;`
...@@ -310,8 +303,7 @@ type T4 struct { ...@@ -310,8 +303,7 @@ type T4 struct {
a []int; a []int;
} }
const F4a = const F4a = `datafmt "datafmt";`
`datafmt "datafmt";`
`int = "%d";` `int = "%d";`
`ptr = *;` `ptr = *;`
`array = *;` `array = *;`
...@@ -319,8 +311,7 @@ const F4a = ...@@ -319,8 +311,7 @@ const F4a =
`empty = *:nil;` `empty = *:nil;`
`datafmt.T4 = "<" (x:empty x | "-") ">" ` `datafmt.T4 = "<" (x:empty x | "-") ">" `
const F4b = const F4b = `datafmt "datafmt";`
`datafmt "datafmt";`
`int = "%d";` `int = "%d";`
`ptr = *;` `ptr = *;`
`array = *;` `array = *;`
...@@ -345,8 +336,7 @@ type Point struct { ...@@ -345,8 +336,7 @@ type Point struct {
x, y int; x, y int;
} }
const FPoint = const FPoint = `datafmt "datafmt";`
`datafmt "datafmt";`
`int = "%d";` `int = "%d";`
`hexInt = "0x%x";` `hexInt = "0x%x";`
`string = "---%s---";` `string = "---%s---";`
...@@ -361,8 +351,7 @@ func TestStructPoint(t *testing.T) { ...@@ -361,8 +351,7 @@ func TestStructPoint(t *testing.T) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Formatting a slice (documentation example) // Formatting a slice (documentation example)
const FSlice = const FSlice = `int = "%b";`
`int = "%b";`
`array = { * / ", " }` `array = { * / ", " }`
func TestSlice(t *testing.T) { func TestSlice(t *testing.T) {
......
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
// noPos is used when there is no corresponding source position for a token. // noPos is used when there is no corresponding source position for a token.
var noPos token.Position; var noPos token.Position
// The mode parameter to the Parse* functions is a set of flags (or 0). // The mode parameter to the Parse* functions is a set of flags (or 0).
...@@ -27,7 +27,7 @@ var noPos token.Position; ...@@ -27,7 +27,7 @@ var noPos token.Position;
// parser functionality. // parser functionality.
// //
const ( const (
PackageClauseOnly uint = 1 << iota; // parsing stops after package clause PackageClauseOnly uint = 1<<iota; // parsing stops after package clause
ImportsOnly; // parsing stops after import declarations ImportsOnly; // parsing stops after import declarations
ParseComments; // parse comments and add them to AST ParseComments; // parse comments and add them to AST
Trace; // print a trace of parsed productions Trace; // print a trace of parsed productions
...@@ -63,7 +63,7 @@ type parser struct { ...@@ -63,7 +63,7 @@ type parser struct {
pkgScope *ast.Scope; pkgScope *ast.Scope;
fileScope *ast.Scope; fileScope *ast.Scope;
topScope *ast.Scope; topScope *ast.Scope;
}; }
// scannerMode returns the scanner mode bits given the parser's mode bits. // scannerMode returns the scanner mode bits given the parser's mode bits.
...@@ -79,7 +79,7 @@ func (p *parser) init(filename string, src []byte, mode uint) { ...@@ -79,7 +79,7 @@ func (p *parser) init(filename string, src []byte, mode uint) {
p.ErrorVector.Init(); p.ErrorVector.Init();
p.scanner.Init(filename, src, p, scannerMode(mode)); p.scanner.Init(filename, src, p, scannerMode(mode));
p.mode = mode; p.mode = mode;
p.trace = mode & Trace != 0; // for convenience (p.trace is used frequently) p.trace = mode&Trace != 0; // for convenience (p.trace is used frequently)
p.next(); p.next();
} }
...@@ -88,16 +88,15 @@ func (p *parser) init(filename string, src []byte, mode uint) { ...@@ -88,16 +88,15 @@ func (p *parser) init(filename string, src []byte, mode uint) {
// Parsing support // Parsing support
func (p *parser) printTrace(a ...) { func (p *parser) printTrace(a ...) {
const dots = const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "; ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
const n = uint(len(dots)); const n = uint(len(dots));
fmt.Printf("%5d:%3d: ", p.pos.Line, p.pos.Column); fmt.Printf("%5d:%3d: ", p.pos.Line, p.pos.Column);
i := 2*p.indent; i := 2 * p.indent;
for ; i > n; i -= n { for ; i > n; i -= n {
fmt.Print(dots); fmt.Print(dots);
} }
fmt.Print(dots[0 : i]); fmt.Print(dots[0:i]);
fmt.Println(a); fmt.Println(a);
} }
...@@ -109,7 +108,8 @@ func trace(p *parser, msg string) *parser { ...@@ -109,7 +108,8 @@ func trace(p *parser, msg string) *parser {
} }
func un/*trace*/(p *parser) { // Usage pattern: defer un(trace(p, "..."));
func un(p *parser) {
p.indent--; p.indent--;
p.printTrace(")"); p.printTrace(")");
} }
...@@ -127,7 +127,7 @@ func (p *parser) next0() { ...@@ -127,7 +127,7 @@ func (p *parser) next0() {
case p.tok.IsLiteral(): case p.tok.IsLiteral():
p.printTrace(s, string(p.lit)); p.printTrace(s, string(p.lit));
case p.tok.IsOperator(), p.tok.IsKeyword(): case p.tok.IsOperator(), p.tok.IsKeyword():
p.printTrace("\"" + s + "\""); p.printTrace("\""+s+"\"");
default: default:
p.printTrace(s); p.printTrace(s);
} }
...@@ -246,7 +246,7 @@ func (p *parser) errorExpected(pos token.Position, msg string) { ...@@ -246,7 +246,7 @@ func (p *parser) errorExpected(pos token.Position, msg string) {
// make the error message more specific // make the error message more specific
msg += ", found '" + p.tok.String() + "'"; msg += ", found '" + p.tok.String() + "'";
if p.tok.IsLiteral() { if p.tok.IsLiteral() {
msg += " " + string(p.lit); msg += " "+string(p.lit);
} }
} }
p.Error(pos, msg); p.Error(pos, msg);
...@@ -272,7 +272,8 @@ func openScope(p *parser) *parser { ...@@ -272,7 +272,8 @@ func openScope(p *parser) *parser {
} }
func close/*Scope*/(p *parser) { // Usage pattern: defer close(openScope(p));
func close(p *parser) {
p.topScope = p.topScope.Outer; p.topScope = p.topScope.Outer;
} }
...@@ -757,14 +758,22 @@ func (p *parser) parseChanType() *ast.ChanType { ...@@ -757,14 +758,22 @@ func (p *parser) parseChanType() *ast.ChanType {
func (p *parser) tryRawType(ellipsisOk bool) ast.Expr { func (p *parser) tryRawType(ellipsisOk bool) ast.Expr {
switch p.tok { switch p.tok {
case token.IDENT: return p.parseTypeName(); case token.IDENT:
case token.LBRACK: return p.parseArrayType(ellipsisOk); return p.parseTypeName();
case token.STRUCT: return p.parseStructType(); case token.LBRACK:
case token.MUL: return p.parsePointerType(); return p.parseArrayType(ellipsisOk);
case token.FUNC: return p.parseFuncType(); case token.STRUCT:
case token.INTERFACE: return p.parseInterfaceType(); return p.parseStructType();
case token.MAP: return p.parseMapType(); case token.MUL:
case token.CHAN, token.ARROW: return p.parseChanType(); return p.parsePointerType();
case token.FUNC:
return p.parseFuncType();
case token.INTERFACE:
return p.parseInterfaceType();
case token.MAP:
return p.parseMapType();
case token.CHAN, token.ARROW:
return p.parseChanType();
case token.LPAREN: case token.LPAREN:
lparen := p.pos; lparen := p.pos;
p.next(); p.next();
...@@ -1097,9 +1106,12 @@ func isTypeName(x ast.Expr) bool { ...@@ -1097,9 +1106,12 @@ func isTypeName(x ast.Expr) bool {
switch t := x.(type) { switch t := x.(type) {
case *ast.BadExpr: case *ast.BadExpr:
case *ast.Ident: case *ast.Ident:
case *ast.ParenExpr: return isTypeName(t.X); // TODO(gri): should (TypeName) be illegal? case *ast.ParenExpr:
case *ast.SelectorExpr: return isTypeName(t.X); return isTypeName(t.X); // TODO(gri): should (TypeName) be illegal?
default: return false; // all other nodes are not type names case *ast.SelectorExpr:
return isTypeName(t.X);
default:
return false; // all other nodes are not type names
} }
return true; return true;
} }
...@@ -1111,12 +1123,15 @@ func isCompositeLitType(x ast.Expr) bool { ...@@ -1111,12 +1123,15 @@ func isCompositeLitType(x ast.Expr) bool {
switch t := x.(type) { switch t := x.(type) {
case *ast.BadExpr: case *ast.BadExpr:
case *ast.Ident: case *ast.Ident:
case *ast.ParenExpr: return isCompositeLitType(t.X); case *ast.ParenExpr:
case *ast.SelectorExpr: return isTypeName(t.X); return isCompositeLitType(t.X);
case *ast.SelectorExpr:
return isTypeName(t.X);
case *ast.ArrayType: case *ast.ArrayType:
case *ast.StructType: case *ast.StructType:
case *ast.MapType: case *ast.MapType:
default: return false; // all other nodes are not legal composite literal types default:
return false; // all other nodes are not legal composite literal types
} }
return true; return true;
} }
...@@ -1154,9 +1169,12 @@ func (p *parser) parsePrimaryExpr() ast.Expr { ...@@ -1154,9 +1169,12 @@ func (p *parser) parsePrimaryExpr() ast.Expr {
x := p.parseOperand(); x := p.parseOperand();
L: for { L: for {
switch p.tok { switch p.tok {
case token.PERIOD: x = p.parseSelectorOrTypeAssertion(p.checkExpr(x)); case token.PERIOD:
case token.LBRACK: x = p.parseIndex(p.checkExpr(x)); x = p.parseSelectorOrTypeAssertion(p.checkExpr(x));
case token.LPAREN: x = p.parseCallOrConversion(p.checkExprOrType(x)); case token.LBRACK:
x = p.parseIndex(p.checkExpr(x));
case token.LPAREN:
x = p.parseCallOrConversion(p.checkExprOrType(x));
case token.LBRACE: case token.LBRACE:
if isCompositeLitType(x) && (p.exprLev >= 0 || !isTypeName(x)) { if isCompositeLitType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
x = p.parseCompositeLit(x); x = p.parseCompositeLit(x);
...@@ -1206,7 +1224,7 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr { ...@@ -1206,7 +1224,7 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
for p.tok.Precedence() == prec { for p.tok.Precedence() == prec {
pos, op := p.pos, p.tok; pos, op := p.pos, p.tok;
p.next(); p.next();
y := p.parseBinaryExpr(prec + 1); y := p.parseBinaryExpr(prec+1);
x = &ast.BinaryExpr{p.checkExpr(x), pos, op, p.checkExpr(y)}; x = &ast.BinaryExpr{p.checkExpr(x), pos, op, p.checkExpr(y)};
} }
} }
...@@ -1637,7 +1655,7 @@ func (p *parser) parseForStmt() ast.Stmt { ...@@ -1637,7 +1655,7 @@ func (p *parser) parseForStmt() ast.Stmt {
} }
if rhs, isUnary := as.Rhs[0].(*ast.UnaryExpr); isUnary && rhs.Op == token.RANGE { if rhs, isUnary := as.Rhs[0].(*ast.UnaryExpr); isUnary && rhs.Op == token.RANGE {
// rhs is range expression; check lhs // rhs is range expression; check lhs
return &ast.RangeStmt{pos, key, value, as.TokPos, as.Tok, rhs.X, body} return &ast.RangeStmt{pos, key, value, as.TokPos, as.Tok, rhs.X, body};
} else { } else {
p.errorExpected(s2.Pos(), "range clause"); p.errorExpected(s2.Pos(), "range clause");
return &ast.BadStmt{pos}; return &ast.BadStmt{pos};
......
...@@ -24,6 +24,15 @@ const ( ...@@ -24,6 +24,15 @@ const (
) )
// Other outstanding formatting issues:
// - replacement of expression spacing algorithm with rsc's algorithm
// - support for one-line composite types (e.g. structs) as composite literals types
// - better comment formatting for /*-style comments at the end of a line (e.g. a declaration)
// when the comment spans multiple lines
// - formatting of expression lists; especially for string lists (stringListMode)
// - blank after { and before } in one-line composite literals probably looks better
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Common AST nodes. // Common AST nodes.
......
This diff is collapsed.
...@@ -16,7 +16,8 @@ const testInput = ` ...@@ -16,7 +16,8 @@ const testInput = `
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<body xmlns:foo="ns1" xmlns="ns2" xmlns:tag="ns3" ` "\r\n\t" ` > <body xmlns:foo="ns1" xmlns="ns2" xmlns:tag="ns3" `
"\r\n\t" ` >
<hello lang="en">World &lt;&gt;&apos;&quot; &#x767d;&#40300;翔</hello> <hello lang="en">World &lt;&gt;&apos;&quot; &#x767d;&#40300;翔</hello>
<goodbye /> <goodbye />
<outer foo:attr="value" xmlns:tag="ns4"> <outer foo:attr="value" xmlns:tag="ns4">
...@@ -116,7 +117,7 @@ func (r *stringReader) ReadByte() (b byte, err os.Error) { ...@@ -116,7 +117,7 @@ func (r *stringReader) ReadByte() (b byte, err os.Error) {
} }
func StringReader(s string) io.Reader { func StringReader(s string) io.Reader {
return &stringReader{s, 0} return &stringReader{s, 0};
} }
func TestRawToken(t *testing.T) { func TestRawToken(t *testing.T) {
...@@ -146,4 +147,3 @@ func TestToken(t *testing.T) { ...@@ -146,4 +147,3 @@ func TestToken(t *testing.T) {
} }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment