mirror of
https://github.com/golang/go
synced 2024-11-26 20:01:19 -07:00
b90b213e61
- clarified constants and constant expressions - clarified type of array composite literals (fixed vs open arrays) - clarified type of map composite literals (need to use '&' to get a map pointer) - added proposal for "if-else" (TBD) - added TODO w/ respect to "x, ok = <-ch" (and for arrays) R=r DELTA=51 (35 added, 0 deleted, 16 changed) OCL=15573 CL=15575
2886 lines
84 KiB
Plaintext
2886 lines
84 KiB
Plaintext
The Go Programming Language Specification (DRAFT)
|
|
----
|
|
|
|
Robert Griesemer, Rob Pike, Ken Thompson
|
|
|
|
----
|
|
(September 19, 2008)
|
|
|
|
|
|
This document is a semi-formal specification of the Go systems
|
|
programming language.
|
|
|
|
<font color=red>
|
|
This document is not ready for external review, it is under active development.
|
|
Any part may change substantially as design progresses.
|
|
</font>
|
|
|
|
|
|
<!--
|
|
Open issues according to gri:
|
|
[ ] clarification on interface types, rules
|
|
[ ] methods for all types
|
|
[x] remove "any"
|
|
[ ] convert should not be used for composite literals anymore,
|
|
in fact, convert() should go away
|
|
[ ] syntax for var args
|
|
[ ] reflection support in the language
|
|
[ ] partial export of structs, methods
|
|
[ ] if statement: else syntax must be fixed
|
|
[ ] range statement: to be defined more reasonably
|
|
[ ] packages of multiple files: dealing with it is convoluted
|
|
[ ] should we have a shorter list of alias types? (byte, int, uint, float)
|
|
[ ] old-style export decls (still needed, but ideally should go away)
|
|
[ ] new(arraytype, n1, n2): spec only talks about length, not capacity
|
|
(should only use new(arraytype, n) - this will allow later
|
|
extension to multi-dim arrays w/o breaking the language)
|
|
[x] & needed to get a function pointer from a function? (NO - there is the "func" keyword - 9/19/08)
|
|
[ ] comparison operators: can we compare interfaces?
|
|
[ ] optional semicolons: too complicated and unclear
|
|
[ ] like to have assert() in the language, w/ option to disable code gen for it
|
|
[ ] composite types should uniformly create an instance instead of a pointer
|
|
[x] func literal like a composite type - should probably require the '&' to get
|
|
address
|
|
[ ] meaning of nil
|
|
[ ] clarify slice rules
|
|
[ ] something on tuples?
|
|
[ ] semantics of statements
|
|
[ ] need for type switch? (or use type guard with ok in tuple assignment?)
|
|
[ ] can we add methods to types defined in another package?
|
|
[ ] do we need anything on package vs file names?
|
|
[ ] need to talk about precise int/floats clearly
|
|
[ ] iant suggests to use abstract/precise int for len(), cap() - good idea
|
|
(issue: what happens in len() + const - what is the type?)
|
|
[ ] Do composite literals create a new literal each time (gri thinks yes)
|
|
[x] should binary <- be at lowest precedence level? when is a send/receive non-blocking? (NO - 9/19/08)
|
|
[ ] consider syntactic notation for composite literals to make them parseable w/o type information
|
|
|
|
|
|
Decisions in need of integration into the doc:
|
|
[ ] pair assignment is required to get map, and receive ok.
|
|
-->
|
|
|
|
Contents
|
|
----
|
|
|
|
Introduction
|
|
|
|
Notation
|
|
|
|
Source code representation
|
|
Characters
|
|
Letters and digits
|
|
|
|
Vocabulary
|
|
Identifiers
|
|
Numeric literals
|
|
Character and string literals
|
|
Operators and delimitors
|
|
Reserved words
|
|
|
|
Declarations and scope rules
|
|
Const declarations
|
|
Type declarations
|
|
Variable declarations
|
|
Export declarations
|
|
|
|
Types
|
|
Basic types
|
|
Arithmetic types
|
|
Booleans
|
|
Strings
|
|
|
|
Array types
|
|
Struct types
|
|
Pointer types
|
|
Map types
|
|
Channel types
|
|
Function types
|
|
Interface types
|
|
|
|
Expressions
|
|
Operands
|
|
Constants
|
|
Qualified identifiers
|
|
Iota
|
|
Composite Literals
|
|
Function Literals
|
|
|
|
Primary expressions
|
|
Selectors
|
|
Indexes
|
|
Slices
|
|
Type guards
|
|
Calls
|
|
|
|
Operators
|
|
Arithmetic operators
|
|
Comparison operators
|
|
Logical operators
|
|
Address operators
|
|
Communication operators
|
|
|
|
Constant expressions
|
|
|
|
Statements
|
|
Label declarations
|
|
Expression statements
|
|
IncDec statements
|
|
Assignments
|
|
If statements
|
|
Switch statements
|
|
For statements
|
|
Range statements
|
|
Go statements
|
|
Select statements
|
|
Return statements
|
|
Break statements
|
|
Continue statements
|
|
Label declaration
|
|
Goto statements
|
|
|
|
Function declarations
|
|
Methods (type-bound functions)
|
|
Predeclared functions
|
|
Length and capacity
|
|
Conversions
|
|
Allocation
|
|
|
|
Packages
|
|
|
|
Program initialization and execution
|
|
|
|
|
|
----
|
|
|
|
Introduction
|
|
----
|
|
|
|
|
|
Notation
|
|
----
|
|
|
|
The syntax is specified using Extended Backus-Naur Form (EBNF).
|
|
In particular:
|
|
|
|
- | separates alternatives (least binding strength)
|
|
- () groups
|
|
- [] specifies an option (0 or 1 times)
|
|
- {} specifies repetition (0 to n times)
|
|
|
|
Lexical symbols are enclosed in double quotes '''' (the
|
|
double quote symbol is written as ''"'').
|
|
|
|
The form "a ... b" represents the set of characters from "a" through "b" as
|
|
alternatives.
|
|
|
|
A production may be referenced from various places in this document
|
|
but is usually defined close to its first use. Productions and code
|
|
examples are indented.
|
|
|
|
Lower-case production names are used to identify productions that cannot
|
|
be broken by white space or comments; they are usually tokens. Other
|
|
productions are in CamelCase.
|
|
|
|
|
|
Source code representation
|
|
----
|
|
|
|
Source code is Unicode text encoded in UTF-8.
|
|
|
|
Tokenization follows the usual rules. Source text is case-sensitive.
|
|
|
|
White space is blanks, newlines, carriage returns, or tabs.
|
|
|
|
Comments are // to end of line or /* */ without nesting and are treated as white space.
|
|
|
|
Some Unicode characters (e.g., the character U+00E4) may be representable in
|
|
two forms, as a single code point or as two code points. For simplicity of
|
|
implementation, Go treats these as distinct characters.
|
|
|
|
|
|
Characters
|
|
----
|
|
|
|
In the grammar we use the notation
|
|
|
|
utf8_char
|
|
|
|
to refer to an arbitrary Unicode code point encoded in UTF-8. We use
|
|
|
|
non_ascii
|
|
|
|
to refer to the subset of "utf8_char" code points with values >= 128.
|
|
|
|
|
|
Letters and digits
|
|
----
|
|
|
|
letter = "A" ... "Z" | "a" ... "z" | "_" | non_ascii.
|
|
decimal_digit = "0" ... "9" .
|
|
octal_digit = "0" ... "7" .
|
|
hex_digit = "0" ... "9" | "A" ... "F" | "a" ... "f" .
|
|
|
|
All non-ASCII code points are considered letters; digits are always ASCII.
|
|
|
|
|
|
Vocabulary
|
|
----
|
|
|
|
Tokens make up the vocabulary of the Go language. They consist of
|
|
identifiers, numbers, strings, operators, and delimitors.
|
|
|
|
|
|
Identifiers
|
|
----
|
|
|
|
An identifier is a name for a program entity such as a variable, a
|
|
type, a function, etc.
|
|
|
|
identifier = letter { letter | decimal_digit } .
|
|
|
|
a
|
|
_x
|
|
ThisIsVariable9
|
|
αβ
|
|
|
|
Some identifiers are predeclared (§Declarations).
|
|
|
|
|
|
Numeric literals
|
|
----
|
|
|
|
An integer literal represents a mathematically ideal integer constant
|
|
of arbitrary precision, or 'ideal int'.
|
|
|
|
int_lit = decimal_int | octal_int | hex_int .
|
|
decimal_int = ( "1" ... "9" ) { decimal_digit } .
|
|
octal_int = "0" { octal_digit } .
|
|
hex_int = "0" ( "x" | "X" ) hex_digit { hex_digit } .
|
|
|
|
42
|
|
0600
|
|
0xBadFace
|
|
170141183460469231731687303715884105727
|
|
|
|
A floating point literal represents a mathematically ideal floating point
|
|
constant of arbitrary precision, or 'ideal float'.
|
|
|
|
float_lit =
|
|
decimals "." [ decimals ] [exponent ] |
|
|
decimals exponent |
|
|
"." decimals [ exponent ] .
|
|
decimals = decimal_digit { decimal_digit } .
|
|
exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
|
|
|
|
0.
|
|
2.71828
|
|
1.e+0
|
|
6.67428e-11
|
|
1E6
|
|
.25
|
|
.12345E+5
|
|
|
|
Numeric literals are unsigned. A negative constant is formed by
|
|
applying the unary prefix operator "-" (§Arithmetic operators).
|
|
|
|
An 'ideal number' is either an 'ideal int' or an 'ideal float'.
|
|
|
|
Only when an ideal number (or an arithmetic expression formed
|
|
solely from ideal numbers) is bound to a variable or used in an expression
|
|
or constant of fixed-size integers or floats it is required to fit
|
|
a particular size. In other words, ideal numbers and arithmetic
|
|
upon them are not subject to overflow; only use of them in assignments
|
|
or expressions involving fixed-size numbers may cause overflow, and thus
|
|
an error (§Expressions).
|
|
|
|
Implementation restriction: A compiler may implement ideal numbers
|
|
by choosing a "sufficiently large" internal representation of such
|
|
numbers.
|
|
|
|
|
|
Character and string literals
|
|
----
|
|
|
|
Character and string literals are almost the same as in C, with the
|
|
following differences:
|
|
|
|
- The encoding is UTF-8
|
|
- `` strings exist; they do not interpret backslashes
|
|
- Octal character escapes are always 3 digits ("\077" not "\77")
|
|
- Hexadecimal character escapes are always 2 digits ("\x07" not "\x7")
|
|
|
|
The rules are:
|
|
|
|
char_lit = "'" ( unicode_value | byte_value ) "'" .
|
|
unicode_value = utf8_char | little_u_value | big_u_value | escaped_char .
|
|
byte_value = octal_byte_value | hex_byte_value .
|
|
octal_byte_value = "\" octal_digit octal_digit octal_digit .
|
|
hex_byte_value = "\" "x" hex_digit hex_digit .
|
|
little_u_value = "\" "u" hex_digit hex_digit hex_digit hex_digit .
|
|
big_u_value =
|
|
"\" "U" hex_digit hex_digit hex_digit hex_digit
|
|
hex_digit hex_digit hex_digit hex_digit .
|
|
escaped_char = "\" ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | "\" | "'" | """ ) .
|
|
|
|
A unicode_value takes one of four forms:
|
|
|
|
* The UTF-8 encoding of a Unicode code point. Since Go source
|
|
text is in UTF-8, this is the obvious translation from input
|
|
text into Unicode characters.
|
|
* The usual list of C backslash escapes: "\n", "\t", etc.
|
|
Within a character or string literal, only the corresponding quote character
|
|
is a legal escape (this is not explicitly reflected in the above syntax).
|
|
* A `little u' value, such as "\u12AB". This represents the Unicode
|
|
code point with the corresponding hexadecimal value. It always
|
|
has exactly 4 hexadecimal digits.
|
|
* A `big U' value, such as "\U00101234". This represents the
|
|
Unicode code point with the corresponding hexadecimal value.
|
|
It always has exactly 8 hexadecimal digits.
|
|
|
|
Some values that can be represented this way are illegal because they
|
|
are not valid Unicode code points. These include values above
|
|
0x10FFFF and surrogate halves.
|
|
|
|
An octal_byte_value contains three octal digits. A hex_byte_value
|
|
contains two hexadecimal digits. (Note: This differs from C but is
|
|
simpler.)
|
|
|
|
It is erroneous for an octal_byte_value to represent a value larger than 255.
|
|
(By construction, a hex_byte_value cannot.)
|
|
|
|
A character literal is a form of unsigned integer constant. Its value
|
|
is that of the Unicode code point represented by the text between the
|
|
quotes.
|
|
|
|
'a'
|
|
'ä'
|
|
'本'
|
|
'\t'
|
|
'\000'
|
|
'\007'
|
|
'\377'
|
|
'\x07'
|
|
'\xff'
|
|
'\u12e4'
|
|
'\U00101234'
|
|
|
|
String literals come in two forms: double-quoted and back-quoted.
|
|
Double-quoted strings have the usual properties; back-quoted strings
|
|
do not interpret backslashes at all.
|
|
|
|
string_lit = raw_string_lit | interpreted_string_lit .
|
|
raw_string_lit = "`" { utf8_char } "`" .
|
|
interpreted_string_lit = """ { unicode_value | byte_value } """ .
|
|
|
|
A string literal has type "string". Its value is constructed by
|
|
taking the byte values formed by the successive elements of the
|
|
literal. For byte_values, these are the literal bytes; for
|
|
unicode_values, these are the bytes of the UTF-8 encoding of the
|
|
corresponding Unicode code points. Note that
|
|
"\u00FF"
|
|
and
|
|
"\xFF"
|
|
are
|
|
different strings: the first contains the two-byte UTF-8 expansion of
|
|
the value 255, while the second contains a single byte of value 255.
|
|
The same rules apply to raw string literals, except the contents are
|
|
uninterpreted UTF-8.
|
|
|
|
`abc`
|
|
`\n`
|
|
"hello, world\n"
|
|
"\n"
|
|
""
|
|
"Hello, world!\n"
|
|
"日本語"
|
|
"\u65e5本\U00008a9e"
|
|
"\xff\u00FF"
|
|
|
|
These examples all represent the same string:
|
|
|
|
"日本語" // UTF-8 input text
|
|
`日本語` // UTF-8 input text as a raw literal
|
|
"\u65e5\u672c\u8a9e" // The explicit Unicode code points
|
|
"\U000065e5\U0000672c\U00008a9e" // The explicit Unicode code points
|
|
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // The explicit UTF-8 bytes
|
|
|
|
The language does not canonicalize Unicode text or evaluate combining
|
|
forms. The text of source code is passed uninterpreted.
|
|
|
|
If the source code represents a character as two code points, such as
|
|
a combining form involving an accent and a letter, the result will be
|
|
an error if placed in a character literal (it is not a single code
|
|
point), and will appear as two code points if placed in a string
|
|
literal.
|
|
|
|
|
|
Operators and delimitors
|
|
----
|
|
|
|
The following special character sequences serve as operators or delimitors:
|
|
|
|
+ & += &= && == != ( )
|
|
- | -= |= || < >= [ ]
|
|
* ^ *= ^= <- > <= { }
|
|
/ << /= <<= ++ -- = :=
|
|
% >> %= >>= ! . , ; :
|
|
|
|
|
|
Reserved words
|
|
----
|
|
|
|
The following words are reserved and must not be used as identifiers:
|
|
|
|
break default func interface select
|
|
case else go map struct
|
|
const export goto package switch
|
|
chan fallthrough if range type
|
|
continue for import return var
|
|
|
|
|
|
Declarations and scope rules
|
|
----
|
|
|
|
Every identifier in a program must be declared; some identifiers, such as "int"
|
|
and "true", are predeclared. A declaration associates an identifier
|
|
with a language entity (package, constant, type, variable, function, or method)
|
|
and may specify properties of that entity such as its type.
|
|
|
|
Declaration = [ "export" ] ( ConstDecl | TypeDecl | VarDecl | FunctionDecl | MethodDecl ) .
|
|
|
|
The ``scope'' of a language entity named 'x' extends textually from the point
|
|
immediately after the identifier 'x' in the declaration to the end of the
|
|
surrounding block (package, function, struct, or interface), excluding any
|
|
nested scopes that redeclare 'x'. The entity is said to be local to its scope.
|
|
Declarations in the package scope are ``global'' declarations.
|
|
|
|
The following scope rules apply:
|
|
|
|
1. No identifier may be declared twice in a single scope.
|
|
2. A language entity may only be referred to within its scope.
|
|
3. Field and method identifiers may be used only to select elements
|
|
from the corresponding types, and only after those types are fully
|
|
declared. In effect, the field selector operator
|
|
"." temporarily re-opens the scope of such identifiers (§Expressions).
|
|
4. Forward declaration: A type of the form "*T" may be mentioned at a point
|
|
where "T" is not yet declared. The full declaration of "T" must be within a
|
|
block containing the forward declaration, and the forward declaration
|
|
refers to the innermost such full declaration.
|
|
|
|
Global declarations optionally may be marked for export with the reserved word
|
|
"export". Local declarations can never be exported.
|
|
Identifiers declared in exported declarations (and no other identifiers)
|
|
are made visible to clients of this package, that is, other packages that import
|
|
this package.
|
|
|
|
If the declaration defines a type, the type structure is exported as well. In
|
|
particular, if the declaration defines a new "struct" or "interface" type,
|
|
all structure fields and all structure and interface methods are exported also.
|
|
|
|
export const pi float = 3.14159265
|
|
export func Parse(source string);
|
|
|
|
The scope of a label 'x' is the entire block of the surrounding function excluding
|
|
any nested function. Thus, each function has its own private label scope, and
|
|
identifiers for labels never conflict with any non-label identifier. Within a
|
|
function a label 'x' may only be declared once (§Label declarations).
|
|
|
|
Note that at the moment the old-style export via ExportDecl is still supported.
|
|
|
|
TODO: Eventually we need to be able to restrict visibility of fields and methods.
|
|
(gri) The default should be no struct fields and methods are automatically exported.
|
|
Export should be identifier-based: an identifier is either exported or not, and thus
|
|
visible or not in importing package.
|
|
|
|
TODO: Need some text with respect to QualifiedIdents.
|
|
|
|
QualifiedIdent = [ PackageName "." ] identifier .
|
|
PackageName = identifier .
|
|
|
|
|
|
The following identifiers are predeclared:
|
|
|
|
- all basic types:
|
|
|
|
bool, uint8, uint16, uint32, uint64, int8, int16, int32, int64,
|
|
float32, float64, float80, string
|
|
|
|
- and their alias types:
|
|
|
|
byte, ushort, uint, ulong, short, int, long, float, double, ptrint
|
|
|
|
- the predeclared constants:
|
|
|
|
true, false, iota, nil
|
|
|
|
- the predeclared functions (note: this list is likely to change):
|
|
|
|
cap(), convert(), len(), new(), panic(), print(), typeof(), ...
|
|
|
|
|
|
TODO(gri) We should think hard about reducing the alias type list to:
|
|
byte, uint, int, float, ptrint (note that for instance the C++ style
|
|
guide is explicit about not using short, long, etc. because their sizes
|
|
are unknown in general).
|
|
|
|
|
|
Const declarations
|
|
----
|
|
|
|
A constant declaration gives a name to the value of a constant expression
|
|
(§Constant expressions).
|
|
|
|
ConstDecl = "const" ( ConstSpec | "(" ConstSpecList [ ";" ] ")" ).
|
|
ConstSpec = identifier [ Type ] "=" Expression .
|
|
ConstSpecList = ConstSpec { ";" ConstSpecOptExpr }.
|
|
ConstSpecOptExpr = identifier [ Type ] [ "=" Expression ] .
|
|
|
|
const pi float = 3.14159265
|
|
const e = 2.718281828
|
|
const (
|
|
one int = 1;
|
|
two = 3
|
|
)
|
|
|
|
The constant expression may be omitted, in which case the expression is
|
|
the last expression used after the reserved word "const". If no such expression
|
|
exists, the constant expression cannot be omitted.
|
|
|
|
Together with the "iota" constant generator (described later),
|
|
implicit repetition permits light-weight declaration of enumerated
|
|
values:
|
|
|
|
const (
|
|
Sunday = iota;
|
|
Monday;
|
|
Tuesday;
|
|
Wednesday;
|
|
Thursday;
|
|
Friday;
|
|
Partyday;
|
|
)
|
|
|
|
The initializing expression of a constant may contain only other
|
|
constants. This is illegal:
|
|
|
|
var i int = 10;
|
|
const c = i; // error
|
|
|
|
The initializing expression for a numeric constant is evaluated
|
|
using the principles described in the section on numeric literals:
|
|
constants are mathematical values given a size only upon assignment
|
|
to a variable. Intermediate values, and the constants themselves,
|
|
may require precision significantly larger than any concrete type
|
|
in the language. Thus the following is legal:
|
|
|
|
const Huge = 1 << 100;
|
|
var Four int8 = Huge >> 98;
|
|
|
|
A given numeric constant expression is, however, defined to be
|
|
either an integer or a floating point value, depending on the syntax
|
|
of the literals it comprises (123 vs. 1.0e4). This is because the
|
|
nature of the arithmetic operations depends on the type of the
|
|
values; for example, 3/2 is an integer division yielding 1, while
|
|
3./2. is a floating point division yielding 1.5. Thus
|
|
|
|
const x = 3./2. + 3/2;
|
|
|
|
yields a floating point constant of value 2.5 (1.5 + 1); its
|
|
constituent expressions are evaluated using different rules for
|
|
division.
|
|
|
|
If the type is specified, the resulting constant has the named type.
|
|
|
|
If the type is missing from the constant declaration, the constant
|
|
represents a value of abitrary precision, either integer or floating
|
|
point, determined by the type of the initializing expression. Such
|
|
a constant may be assigned to any variable that can represent its
|
|
value accurately, regardless of type. For instance, 3 can be
|
|
assigned to any int variable but also to any floating point variable,
|
|
while 1e12 can be assigned to a float32, float64, or even int64.
|
|
It is erroneous to assign a value with a non-zero fractional
|
|
part to an integer, or if the assignment would overflow or
|
|
underflow.
|
|
|
|
|
|
Type declarations
|
|
----
|
|
|
|
A type declaration introduces a name for a type.
|
|
|
|
TypeDecl = "type" ( TypeSpec | "(" TypeSpecList [ ";" ] ")" ).
|
|
TypeSpec = identifier Type .
|
|
TypeSpecList = TypeSpec { ";" TypeSpec }.
|
|
|
|
The name refers to an incomplete type until the type specification is complete.
|
|
Incomplete types can be referred to only by pointer types. Consequently, in a
|
|
type declaration a type may not refer to itself unless it does so with a pointer
|
|
type.
|
|
|
|
type IntArray [16] int
|
|
|
|
type (
|
|
Point struct { x, y float };
|
|
Polar Point
|
|
)
|
|
|
|
type TreeNode struct {
|
|
left, right *TreeNode;
|
|
value Point;
|
|
}
|
|
|
|
|
|
Variable declarations
|
|
----
|
|
|
|
A variable declaration creates a variable and gives it a type and a name.
|
|
It may optionally give the variable an initial value; in some forms of
|
|
declaration the type of the initial value defines the type of the variable.
|
|
|
|
VarDecl = "var" ( VarSpec | "(" VarSpecList [ ";" ] ")" ) .
|
|
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
|
|
VarSpecList = VarSpec { ";" VarSpec } .
|
|
|
|
IdentifierList = identifier { "," identifier } .
|
|
ExpressionList = Expression { "," Expression } .
|
|
|
|
var i int
|
|
var u, v, w float
|
|
var k = 0
|
|
var x, y float = -1.0, -2.0
|
|
var (
|
|
i int;
|
|
u, v = 2.0, 3.0
|
|
)
|
|
|
|
If the expression list is present, it must have the same number of elements
|
|
as there are variables in the variable specification.
|
|
|
|
If the variable type is omitted, an initialization expression (or expression
|
|
list) must be present, and the variable type is the type of the expression
|
|
value (in case of a list of variables, the variables assume the types of the
|
|
corresponding expression values).
|
|
|
|
If the variable type is omitted, and the corresponding initialization expression
|
|
is a constant expression of abstract int or floating point type, the type
|
|
of the variable is "int" or "float" respectively:
|
|
|
|
var i = 0 // i has int type
|
|
var f = 3.1415 // f has float type
|
|
|
|
The syntax
|
|
|
|
SimpleVarDecl = identifier ":=" Expression .
|
|
|
|
is shorthand for
|
|
|
|
var identifier = Expression.
|
|
|
|
i := 0
|
|
f := func() int { return 7; }
|
|
ch := new(chan int);
|
|
|
|
Also, in some contexts such as "if", "for", or "switch" statements,
|
|
this construct can be used to declare local temporary variables.
|
|
|
|
|
|
Export declarations
|
|
----
|
|
|
|
Global identifiers may be exported, thus making the
|
|
exported identifier visible outside the package. Another package may
|
|
then import the identifier to use it.
|
|
|
|
Export declarations must only appear at the global level of a
|
|
source file and can name only globally-visible identifiers.
|
|
That is, one can export global functions, types, and so on but not
|
|
local variables or structure fields.
|
|
|
|
Exporting an identifier makes the identifier visible externally to the
|
|
package. If the identifier represents a type, the type structure is
|
|
exported as well. The exported identifiers may appear later in the
|
|
source than the export directive itself, but it is an error to specify
|
|
an identifier not declared anywhere in the source file containing the
|
|
export directive.
|
|
|
|
ExportDecl = "export" ExportIdentifier { "," ExportIdentifier } .
|
|
ExportIdentifier = QualifiedIdent .
|
|
|
|
export sin, cos
|
|
export math.abs
|
|
|
|
TODO: complete this section
|
|
|
|
TODO: export as a mechanism for public and private struct fields?
|
|
|
|
|
|
Types
|
|
----
|
|
|
|
A type specifies the set of values that variables of that type may
|
|
assume, and the operators that are applicable.
|
|
|
|
There are basic types and composite types. Basic types are predeclared.
|
|
Composite types are arrays, maps, channels, structures, functions, pointers,
|
|
and interfaces. They are constructed from other (basic or composite) types.
|
|
|
|
The 'static type' (or simply 'type') of a variable is the type defined by
|
|
the variable's declaration. The 'dynamic type' of a variable is the actual
|
|
type of the value stored in a variable at runtime. Except for variables of
|
|
interface type, the static and dynamic type of variables is always the same.
|
|
|
|
Variables of interface type may hold values of different types during
|
|
execution. However, the dynamic type of the variable is always compatible
|
|
with the static type of the variable.
|
|
|
|
Type =
|
|
TypeName | ArrayType | ChannelType | InterfaceType |
|
|
FunctionType | MapType | StructType | PointerType .
|
|
TypeName = QualifiedIdent.
|
|
|
|
|
|
Basic types
|
|
----
|
|
|
|
Go defines a number of basic types, referred to by their predeclared
|
|
type names. These include traditional arithmetic types, booleans,
|
|
and strings.
|
|
|
|
|
|
Arithmetic types
|
|
----
|
|
|
|
uint8 the set of all unsigned 8-bit integers
|
|
uint16 the set of all unsigned 16-bit integers
|
|
uint32 the set of all unsigned 32-bit integers
|
|
uint64 the set of all unsigned 64-bit integers
|
|
|
|
int8 the set of all signed 8-bit integers, in 2's complement
|
|
int16 the set of all signed 16-bit integers, in 2's complement
|
|
int32 the set of all signed 32-bit integers, in 2's complement
|
|
int64 the set of all signed 64-bit integers, in 2's complement
|
|
|
|
float32 the set of all valid IEEE-754 32-bit floating point numbers
|
|
float64 the set of all valid IEEE-754 64-bit floating point numbers
|
|
float80 the set of all valid IEEE-754 80-bit floating point numbers
|
|
|
|
Additionally, Go declares several platform-specific type aliases; the
|
|
bit width of these types is ``natural'' for the respective types for the
|
|
given platform. For instance, int is usually the same as int32 on a
|
|
32-bit architecture, or int64 on a 64-bit architecture.
|
|
|
|
The integer sizes are defined such that short is at least 16 bits, int
|
|
is at least 32 bits, and long is at least 64 bits (and ditto for the
|
|
unsigned equivalents). Also, the sizes are such that short <= int <=
|
|
long. Similarly, float is at least 32 bits, double is at least 64
|
|
bits, and the sizes have float <= double.
|
|
|
|
byte alias for uint8
|
|
ushort uint16 <= ushort <= uint
|
|
uint uint32 <= uint <= ulong
|
|
ulong uint64 <= ulong
|
|
|
|
short int16 <= short <= int
|
|
int int32 <= int <= long
|
|
long int64 <= long
|
|
|
|
float float32 <= float <= double
|
|
double float64 <= double
|
|
|
|
An arithmetic type ``ptrint'' is also defined. It is an unsigned
|
|
integer type that is the smallest natural integer type of the machine
|
|
large enough to store the uninterpreted bits of a pointer value.
|
|
|
|
Generally, programmers should use these types rather than the explicitly
|
|
sized types to maximize portability.
|
|
|
|
|
|
Booleans
|
|
----
|
|
|
|
The type "bool" comprises the truth values true and false, which are
|
|
available through the two predeclared constants, "true" and "false".
|
|
|
|
|
|
Strings
|
|
----
|
|
|
|
The string type represents the set of string values (strings).
|
|
Strings behave like arrays of bytes, with the following properties:
|
|
|
|
- They are immutable: after creation, it is not possible to change the
|
|
contents of a string.
|
|
- No internal pointers: it is illegal to create a pointer to an inner
|
|
element of a string.
|
|
- They can be indexed: given string "s1", "s1[i]" is a byte value.
|
|
- They can be concatenated: given strings "s1" and "s2", "s1 + s2" is a value
|
|
combining the elements of "s1" and "s2" in sequence.
|
|
- Known length: the length of a string "s1" can be obtained by calling
|
|
"len(s1)". The length of a string is the number
|
|
of bytes within. Unlike in C, there is no terminal NUL byte.
|
|
- Creation 1: a string can be created from an integer value by a conversion;
|
|
the result is a string containing the UTF-8 encoding of that code point.
|
|
"string('x')" yields "x"; "string(0x1234)" yields the equivalent of "\u1234"
|
|
|
|
- Creation 2: a string can by created from an array of integer values (maybe
|
|
just array of bytes) by a conversion:
|
|
|
|
a [3]byte; a[0] = 'a'; a[1] = 'b'; a[2] = 'c'; string(a) == "abc";
|
|
|
|
|
|
Array types
|
|
----
|
|
|
|
An array is a composite type consisting of a number of elements all of the same
|
|
type, called the element type. The number of elements of an array is called its
|
|
length; it is always positive (including zero). The elements of an array are
|
|
designated by indices which are integers between 0 and the length - 1.
|
|
|
|
An array type specifies the array element type and an optional array
|
|
length which must be a compile-time constant expression of a (signed or
|
|
unsigned) int type. If present, the array length and its value is part of
|
|
the array type.
|
|
|
|
If the length is present in the declaration, the array is called
|
|
``fixed array''; if the length is absent, the array is called ``open array''.
|
|
|
|
ArrayType = "[" [ ArrayLength ] "]" ElementType .
|
|
ArrayLength = Expression .
|
|
ElementType = Type .
|
|
|
|
Type equality: Two array types are equal only if both have the same element
|
|
type and if both are either fixed arrays with the same array length, or both
|
|
are open arrays.
|
|
|
|
The length of an array "a" can be discovered using the built-in function
|
|
|
|
len(a)
|
|
|
|
If "a" is a fixed array, the length is known at compile-time and "len(a)" can
|
|
be evaluated to a compile-time constant. If "a" is an open array, then "len(a)"
|
|
will only be known at run-time.
|
|
|
|
The amount of space actually allocated to hold the array data may be larger
|
|
then the current array length; this maximum array length is called the array
|
|
capacity. The capacity of an array "a" can be discovered using the built-in
|
|
function
|
|
|
|
cap(a)
|
|
|
|
and the following relationship between "len()" and "cap()" holds:
|
|
|
|
0 <= len(a) <= cap(a)
|
|
|
|
Allocation: An open array may only be used as a function parameter type, or
|
|
as element type of a pointer type. There are no other variables
|
|
(besides parameters), struct or map fields of open array type; they must be
|
|
pointers to open arrays. For instance, an open array may have a fixed array
|
|
element type, but a fixed array must not have an open array element type
|
|
(though it may have a pointer to an open array). Thus, for now, there are
|
|
only ``one-dimensional'' open arrays.
|
|
|
|
The following are legal array types:
|
|
|
|
[32] byte
|
|
[2*N] struct { x, y int32 }
|
|
[1000]*[] float64
|
|
[] int
|
|
[][1024] byte
|
|
|
|
Variables of fixed arrays may be declared statically:
|
|
|
|
var a [32] byte
|
|
var m [1000]*[] float64
|
|
|
|
Static and dynamic arrays may be allocated dynamically via the built-in function
|
|
"new()" which takes an array type and zero or one array lengths as parameters,
|
|
depending on the number of open arrays in the type:
|
|
|
|
new([32] byte) // *[32] byte
|
|
new([]int, 100); // *[100] int
|
|
new([][1024] byte, 4); // *[4][1024] byte
|
|
|
|
Assignment compatibility: Fixed arrays are assignment compatible to variables
|
|
of the same type, or to open arrays with the same element type. Open arrays
|
|
may only be assigned to other open arrays with the same element type.
|
|
|
|
For the variables:
|
|
|
|
var fa, fb [32] int
|
|
var fc [64] int
|
|
var pa, pb *[] int
|
|
var pc *[][32] int
|
|
|
|
the following assignments are legal, and cause the respective array elements
|
|
to be copied:
|
|
|
|
fa = fb;
|
|
pa = pb;
|
|
*pa = *pb;
|
|
fa = *pc[7];
|
|
*pa = fa;
|
|
*pb = fc;
|
|
*pa = *pc[11];
|
|
|
|
The following assignments are illegal:
|
|
|
|
fa = *pa; // cannot assign open array to fixed array
|
|
*pc[7] = *pa; // cannot assign open array to fixed array
|
|
fa = fc; // different fixed array types
|
|
*pa = *pc; // different element types of open arrays
|
|
|
|
|
|
Array indexing: Given a (pointer to an) array variable "a", an array element
|
|
is specified with an array index operation:
|
|
|
|
a[i]
|
|
|
|
This selects the array element at index "i". "i" must be within array bounds,
|
|
that is "0 <= i < len(a)".
|
|
|
|
Array slicing: Given a (pointer to an) array variable "a", a sub-array is
|
|
specified with an array slice operation:
|
|
|
|
a[i : j]
|
|
|
|
This selects the sub-array consisting of the elements "a[i]" through "a[j - 1]"
|
|
(exclusive "a[j]"). "i" must be within array bounds, and "j" must satisfy
|
|
"i <= j <= cap(a)". The length of the new slice is "j - i". The capacity of
|
|
the slice is "cap(a) - i"; thus if "i" is 0, the array capacity does not change
|
|
as a result of a slice operation. An array slice is always an open array.
|
|
|
|
Note that a slice operation does not ``crop'' the underlying array, it only
|
|
provides a new ``view'' to an array. If the capacity of an array is larger
|
|
then its length, slicing can be used to ``grow'' an array:
|
|
|
|
// allocate an open array of bytes with length i and capacity 100
|
|
i := 10;
|
|
a := new([] byte, 100) [0 : i];
|
|
// grow the array by n bytes, with i + n <= 100
|
|
a = a[0 : i + n];
|
|
|
|
|
|
TODO: Expand on details of slicing and assignment, especially between pointers
|
|
to arrays and arrays.
|
|
|
|
|
|
Struct types
|
|
----
|
|
|
|
A struct is a composite type consisting of a fixed number of elements,
|
|
called fields, with possibly different types. The struct type declaration
|
|
specifies the name and type for each field. The scope of each field identifier
|
|
extends from the point of the declaration to the end of the struct type, but
|
|
it is also visible within field selectors (§Primary Expressions).
|
|
|
|
StructType = "struct" "{" [ FieldDeclList [ ";" ] ] "}" .
|
|
FieldDeclList = FieldDecl { ";" FieldDecl } .
|
|
FieldDecl = IdentifierList FieldType .
|
|
FieldType = Type .
|
|
|
|
Type equality: Two struct types are equal only if both have the same number
|
|
of fields in the same order and and the field types are equal
|
|
(note that the field names do not have to match).
|
|
|
|
// An empty struct.
|
|
struct {}
|
|
|
|
// A struct with 5 fields.
|
|
struct {
|
|
x, y int;
|
|
u float;
|
|
a *[]int;
|
|
f *();
|
|
}
|
|
|
|
Assignment compatibility: Structs are assignment compatible to variables of
|
|
equal type only.
|
|
|
|
|
|
Pointer types
|
|
----
|
|
|
|
A pointer type denotes the set of all pointers to variables of a given
|
|
type, called the ``base type'' of the pointer, and the value "nil".
|
|
|
|
PointerType = "*" BaseType .
|
|
BaseType = Type .
|
|
|
|
*int
|
|
*map[string] *chan
|
|
|
|
For pointer types (only), the pointer base type may be an
|
|
identifier referring to an incomplete (not yet fully defined) or undeclared
|
|
type. This allows the construction of recursive and mutually recursive types
|
|
such as:
|
|
|
|
type S struct { s *S }
|
|
|
|
type S1 struct { s2 *S2 }
|
|
type S2 struct { s1 *S1 }
|
|
|
|
If the base type is an undeclared identifier, the declaration implicitly
|
|
forward-declares an (incomplete) type with the respective name. Any such
|
|
forward-declared type must be completely declared in the same or an outer
|
|
scope.
|
|
|
|
Type equality: Two pointer types are equal only if both have equal
|
|
base types.
|
|
|
|
Assignment compatibility: A pointer is assignment compatible to a variable
|
|
of pointer type, only if both types are equal.
|
|
|
|
Pointer arithmetic of any kind is not permitted.
|
|
|
|
|
|
Map types
|
|
----
|
|
|
|
A map is a composite type consisting of a variable number of entries
|
|
called (key, value) pairs. For a given map, the keys and values must
|
|
each be of a specific type called the key and value type, respectively.
|
|
Upon creation, a map is empty and values may be added and removed
|
|
during execution. The number of entries in a map is called its length.
|
|
|
|
MapType = "map" "[" KeyType "]" ValueType .
|
|
KeyType = Type .
|
|
ValueType = Type .
|
|
|
|
map [string] int
|
|
map [struct { pid int; name string }] *chan Buffer
|
|
map [string] any
|
|
|
|
The length of a map "m" can be discovered using the built-in function
|
|
|
|
len(m)
|
|
|
|
Allocation: A map may only be used as a base type of a pointer type.
|
|
There are no variables, parameters, array, struct, or map fields of
|
|
map type, only of pointers to maps.
|
|
|
|
Type equivalence: Two map types are equal only if both have equal
|
|
key and value types.
|
|
|
|
Assignment compatibility: A pointer to a map type is assignment
|
|
compatible to a variable of pointer to map type only if both types
|
|
are equal.
|
|
|
|
|
|
Channel types
|
|
----
|
|
|
|
A channel provides a mechanism for two concurrently executing functions
|
|
to synchronize execution and exchange values of a specified type.
|
|
|
|
Upon creation, a channel can be used both to send and to receive.
|
|
By conversion or assignment, a 'full' channel may be constrained only to send or
|
|
to receive. Such a restricted channel is called a 'send channel' or a 'receive channel'.
|
|
|
|
ChannelType = FullChannel | SendChannel | RecvChannel .
|
|
FullChannel = "chan" ValueType .
|
|
SendChannel = "chan" "<-" ValueType .
|
|
RecvChannel = "<-" "chan" ValueType .
|
|
|
|
chan T // a channel that can exchange values of type T
|
|
chan <- float // a channel that can only be used to send floats
|
|
<-chan int // a channel that can receive only ints
|
|
|
|
Channel variables always have type pointer to channel.
|
|
It is an error to attempt to use a channel value and in
|
|
particular to dereference a channel pointer.
|
|
|
|
var ch *chan int;
|
|
ch = new(chan int); // new returns type *chan int
|
|
|
|
|
|
Function types
|
|
----
|
|
|
|
A function type denotes the set of all functions with the same parameter
|
|
list and result.
|
|
|
|
FunctionType = "(" [ ParameterList ] ")" [ Result ] .
|
|
ParameterList = ParameterSection { "," ParameterSection } .
|
|
ParameterSection = IdentifierList Type .
|
|
Result = Type | "(" ParameterList ")" .
|
|
|
|
Functions can return multiple values simultaneously.
|
|
|
|
// Function types
|
|
()
|
|
() int
|
|
(s string)
|
|
(a, b int, z float) bool
|
|
(a, b int, z float) (success bool)
|
|
(a, b int, z float) (success bool, result float)
|
|
|
|
A variable can hold only a pointer to a function, not a function value.
|
|
In particular, v := func() {} creates a variable of type *(). To call the
|
|
function referenced by v, one writes v(). It is illegal to dereference a
|
|
function pointer.
|
|
|
|
TODO: For consistency, we should require the use of & to get the pointer to
|
|
a function: &func() {}.
|
|
|
|
|
|
Interface types
|
|
----
|
|
|
|
An interface type denotes a set of methods.
|
|
|
|
InterfaceType = "interface" "{" [ MethodList [ ";" ] ] "}" .
|
|
MethodList = Method { ";" Method } .
|
|
Method = identifier FunctionType .
|
|
|
|
// A basic file interface.
|
|
type File interface {
|
|
Read(b Buffer) bool;
|
|
Write(b Buffer) bool;
|
|
Close();
|
|
}
|
|
|
|
Any type whose interface has, possibly as a subset, the complete
|
|
set of methods of an interface I is said to implement interface I.
|
|
For instance, if two types S1 and S2 have the methods
|
|
|
|
func (p T) Read(b Buffer) bool { return ... }
|
|
func (p T) Write(b Buffer) bool { return ... }
|
|
func (p T) Close() { ... }
|
|
|
|
(where T stands for either S1 or S2) then the File interface is
|
|
implemented by both S1 and S2, regardless of what other methods
|
|
S1 and S2 may have or share.
|
|
|
|
All types implement the empty interface:
|
|
|
|
interface {}
|
|
|
|
In general, a type implements an arbitrary number of interfaces.
|
|
For instance, if we have
|
|
|
|
type Lock interface {
|
|
lock();
|
|
unlock();
|
|
}
|
|
|
|
and S1 and S2 also implement
|
|
|
|
func (p T) lock() { ... }
|
|
func (p T) unlock() { ... }
|
|
|
|
they implement the Lock interface as well as the File interface.
|
|
|
|
|
|
Expressions
|
|
----
|
|
|
|
An expression specifies the computation of a value via the application of
|
|
operators and function invocations on operands. An expression has a value and
|
|
a type.
|
|
|
|
The type of a constant expression may be an ideal number. The type of such expressions
|
|
is implicitly converted into the 'expected type' required for the expression.
|
|
The conversion is legal if the (ideal) expression value is a member of the
|
|
set represented by the expected type. Otherwise the expression is erroneous.
|
|
|
|
For instance, if the expected type is int32, any ideal number
|
|
which fits into an int32 without loss of precision can be legally converted.
|
|
Along the same lines, a negative ideal integer cannot be converted into a uint
|
|
without loss of the sign; such a conversion is illegal.
|
|
|
|
TODO(gri) This may be overly constraining. What about "len(a) + c" where
|
|
c is an ideal number? Is len(a) of type int, or of an ideal number? Probably
|
|
should be ideal number, because for fixed arrays, it is a constant.
|
|
|
|
|
|
Operands
|
|
----
|
|
|
|
Operands denote the elementary values in an expression.
|
|
|
|
Operand = Literal | QualifiedIdent | "(" Expression ")" .
|
|
Literal = BasicLit | CompositeLit | FunctionLit .
|
|
BasicLit = int_lit | float_lit | char_lit | string_lit .
|
|
|
|
|
|
Constants
|
|
----
|
|
|
|
An operand is called ``constant'' if it is a literal of a basic type
|
|
(including the predeclared constants "true" and "false"), the predeclared
|
|
constant "nil", or a parenthesized constant expression (§Constant expressions).
|
|
Constants have values that are known at compile-time.
|
|
|
|
|
|
Qualified identifiers
|
|
----
|
|
|
|
TODO(gri) write this section
|
|
|
|
|
|
Iota
|
|
----
|
|
|
|
Within a declaration, the predeclared operand "iota"
|
|
represents successive elements of an integer sequence.
|
|
It is reset to zero whenever the reserved word "const"
|
|
introduces a new declaration and increments as each identifier
|
|
is declared. For instance, "iota" can be used to construct
|
|
a set of related constants:
|
|
|
|
const (
|
|
enum0 = iota; // sets enum0 to 0, etc.
|
|
enum1 = iota;
|
|
enum2 = iota
|
|
)
|
|
|
|
const (
|
|
a = 1 << iota; // sets a to 1 (iota has been reset)
|
|
b = 1 << iota; // sets b to 2
|
|
c = 1 << iota; // sets c to 4
|
|
)
|
|
|
|
const x = iota; // sets x to 0
|
|
const y = iota; // sets y to 0
|
|
|
|
Since the expression in constant declarations repeats implicitly
|
|
if omitted, the first two examples above can be abbreviated:
|
|
|
|
const (
|
|
enum0 = iota; // sets enum0 to 0, etc.
|
|
enum1;
|
|
enum2
|
|
)
|
|
|
|
const (
|
|
a = 1 << iota; // sets a to 1 (iota has been reset)
|
|
b; // sets b to 2
|
|
c; // sets c to 4
|
|
)
|
|
|
|
|
|
Composite Literals
|
|
----
|
|
|
|
Literals for composite data structures consist of the type of the value
|
|
followed by a braced expression list for array and structure literals,
|
|
or a list of expression pairs for map literals.
|
|
|
|
CompositeLit = LiteralType "{" [ ( ExpressionList | ExprPairList ) [ "," ] ] "}" .
|
|
LiteralType = TypeName | ArrayType | MapType | StructType .
|
|
ExprPairList = ExprPair { "," ExprPair } .
|
|
ExprPair = Expression ":" Expression .
|
|
|
|
If LiteralType is a TypeName, the denoted type must be an array, map, or
|
|
structure. The types of the expressions must match the respective key, element,
|
|
and field types of the literal type; there is no automatic type conversion.
|
|
LiteralType is the type of the literal: To get a pointer to the literal, the
|
|
address operator "&" must be used.
|
|
|
|
Given
|
|
|
|
type Rat struct { num, den int };
|
|
type Num struct { r Rat; f float; s string };
|
|
|
|
we can write
|
|
|
|
pi := Num{Rat{22, 7}, 3.14159, "pi"};
|
|
|
|
|
|
The length of a fixed array literal is the length specified in LiteralType.
|
|
If fewer elements are specified in the composite literal, the missing elements
|
|
are set to the approprate zero value for the array element type. It is an error
|
|
to provide more elements then specified in LiteralType.
|
|
|
|
The length of an open array literal is the number of elements specified in the
|
|
composite literal.
|
|
|
|
primes := [6]int{2, 3, 5, 7, 9, 11};
|
|
weekdays := &[]string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"};
|
|
|
|
Map literals are similar except the elements of the expression list are
|
|
key-value pairs separated by a colon:
|
|
|
|
m := &map[string]int{"good": 0, "bad": 1, "indifferent": 7};
|
|
|
|
TODO: Consider adding helper syntax for nested composites
|
|
(avoids repeating types but complicates the spec needlessly.)
|
|
|
|
|
|
Function Literals
|
|
----
|
|
|
|
Function literals represent anonymous functions.
|
|
|
|
FunctionLit = "func" FunctionType Block .
|
|
Block = "{" [ StatementList [ ";" ] ] "}" .
|
|
|
|
The type of a function literal is a pointer to the function type.
|
|
|
|
func (a, b int, z float) bool { return a*b < int(z); }
|
|
|
|
A function literal can be assigned to a variable of the
|
|
corresponding function pointer type, or invoked directly.
|
|
|
|
f := func(x, y int) int { return x + y; }
|
|
func(ch *chan int) { ch <- ACK; } (reply_chan)
|
|
|
|
Implementation restriction: A function literal can reference only
|
|
its parameters, global variables, and variables declared within the
|
|
function literal.
|
|
|
|
TODO: Should a function literal return a value of the function type
|
|
instead of the pointer to the function? Seems more consistent with
|
|
the other uses and composite literals.
|
|
|
|
|
|
Primary expressions
|
|
----
|
|
|
|
PrimaryExpr = Operand { Selector | Index | Slice | TypeGuard | Call } .
|
|
Selector = "." identifier .
|
|
Index = "[" Expression "]" .
|
|
Slice = "[" Expression ":" Expression "]" .
|
|
TypeGuard = "." "(" QualifiedIdent ")" .
|
|
Call = "(" [ ExpressionList ] ")" .
|
|
|
|
|
|
x
|
|
2
|
|
(s + ".txt")
|
|
f(3.1415, true)
|
|
Point(1, 2)
|
|
new([]int, 100)
|
|
m["foo"]
|
|
s[i : j + 1]
|
|
obj.color
|
|
Math.sin
|
|
f.p[i].x()
|
|
|
|
|
|
Selectors
|
|
----
|
|
|
|
Given a pointer p to a struct, one writes
|
|
p.f
|
|
to access field f of the struct.
|
|
|
|
|
|
Indexes
|
|
----
|
|
|
|
Given an array or map pointer, one writes
|
|
p[i]
|
|
to access an element.
|
|
|
|
|
|
Slices
|
|
----
|
|
|
|
Strings and arrays can be ``sliced'' to construct substrings or subarrays.
|
|
The index expressions in the slice select which elements appear in the
|
|
result. The result has indexes starting at 0 and length equal to the difference
|
|
in the index values in the slice. After
|
|
|
|
a := []int(1,2,3,4)
|
|
slice := a[1:3]
|
|
|
|
The array ``slice'' has length two and elements
|
|
|
|
slice[0] == 2
|
|
slice[1] == 3
|
|
|
|
The index values in the slice must be in bounds for the original
|
|
array (or string) and the slice length must be non-negative.
|
|
|
|
Slices are new arrays (or strings) storing copies of the elements, so
|
|
changes to the elements of the slice do not affect the original.
|
|
In the example, a subsequent assignment to element 0,
|
|
|
|
slice[0] = 5
|
|
|
|
would have no effect on ``a''.
|
|
|
|
|
|
Type guards
|
|
----
|
|
|
|
TODO: write this section
|
|
|
|
|
|
Calls
|
|
----
|
|
|
|
Given a function pointer, one writes
|
|
|
|
p()
|
|
|
|
to call the function.
|
|
|
|
A method is called using the notation
|
|
|
|
receiver.method()
|
|
|
|
where receiver is a value of the receive type of the method.
|
|
|
|
For instance, given a *Point variable pt, one may call
|
|
|
|
pt.Scale(3.5)
|
|
|
|
The type of a method is the type of a function with the receiver as first
|
|
argument. For instance, the method "Scale" has type
|
|
|
|
(p *Point, factor float)
|
|
|
|
However, a function declared this way is not a method.
|
|
|
|
There is no distinct method type and there are no method literals.
|
|
|
|
|
|
Operators
|
|
----
|
|
|
|
Operators combine operands into expressions.
|
|
|
|
Expression = UnaryExpr { binary_op Expression } .
|
|
UnaryExpr = unary_op UnaryExpr | PrimaryExpr .
|
|
|
|
binary_op = log_op | com_op | rel_op | add_op | mul_op .
|
|
log_op = "||" | "&&" .
|
|
com_op = "<-" .
|
|
rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" .
|
|
add_op = "+" | "-" | "|" | "^" .
|
|
mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" .
|
|
|
|
unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
|
|
|
|
TODO: If we allow non-blocking sends only in the form "ok = ch <- x", it doesn't
|
|
make sense to give binary "<-" precedence 3. It should be at the lowest level. TBD.
|
|
|
|
The operand types in binary operations must be equal, with the following exceptions:
|
|
|
|
- The right operand in a shift operation must be
|
|
an unsigned int (§Arithmetic operators).
|
|
|
|
- Otherwise, ideal number operands are
|
|
converted to match the type of the other operand (§Expression).
|
|
|
|
- If both operands are ideal numbers, the conversion is to ideal floats
|
|
if one of the operands is an ideal float (relevant for "/" and "%").
|
|
|
|
Unary operators have the highest precedence.
|
|
There are six precedence levels for binary operators:
|
|
multiplication operators bind strongest, followed by addition
|
|
operators, comparison operators, communication operators,
|
|
"&&" (logical and), and finally "||" (logical or) with the
|
|
lowest precedence:
|
|
|
|
Precedence Operator
|
|
6 * / % << >> &
|
|
5 + - | ^
|
|
4 == != < <= > >=
|
|
3 <-
|
|
2 &&
|
|
1 ||
|
|
|
|
Operators of the same precedence associate from left to right.
|
|
For instance, "x / y / z" stands for "(x / y) / z".
|
|
|
|
Examples
|
|
|
|
+x
|
|
23 + 3*x[i]
|
|
x <= f()
|
|
^a >> b
|
|
f() || g()
|
|
x == y + 1 && <-chan_ptr > 0
|
|
|
|
|
|
Arithmetic operators
|
|
----
|
|
|
|
Arithmetic operators apply to numeric types and yield a result of the same
|
|
type as the first operand. The four standard arithmetic operators ("+", "-",
|
|
"*", "/") apply to both integer and floating point types, while "+" also applies
|
|
to strings and arrays; all other arithmetic operators apply to integer types only.
|
|
|
|
+ sum integers, floats, strings, arrays
|
|
- difference integers, floats
|
|
* product integers, floats
|
|
/ quotient integers, floats
|
|
% remainder integers
|
|
|
|
& bitwise and integers
|
|
| bitwise or integers
|
|
^ bitwise xor integers
|
|
|
|
<< left shift integer << unsigned integer
|
|
>> right shift integer >> unsigned integer
|
|
|
|
Strings and arrays can be concatenated using the "+" operator
|
|
(or via the "+=" assignment):
|
|
|
|
s := "hi" + string(c)
|
|
a += []int{5, 6, 7}
|
|
|
|
String and array addition creates a new array or string by copying the
|
|
elements.
|
|
|
|
For integer values, "/" and "%" satisfy the following relationship:
|
|
|
|
(a / b) * b + a % b == a
|
|
|
|
and
|
|
|
|
(a / b) is "truncated towards zero".
|
|
|
|
Examples:
|
|
|
|
x y x / y x % y
|
|
5 3 1 2
|
|
-5 3 -1 -2
|
|
5 -3 -1 2
|
|
-5 -3 1 -2
|
|
|
|
Note that if the dividend is positive and the divisor is a constant power of 2,
|
|
the division may be replaced by a left shift, and computing the remainder may
|
|
be replaced by a bitwise "and" operation:
|
|
|
|
x x / 4 x % 4 x >> 2 x & 3
|
|
11 2 3 2 3
|
|
-11 -2 -3 -3 1
|
|
|
|
The shift operators shift the left operand by the shift count specified by the
|
|
right operand. They implement arithmetic shifts if the left operand is a signed
|
|
integer, and logical shifts if it is an unsigned integer. The shift count must
|
|
be an unsigned integer. There is no upper limit on the shift count. It is
|
|
as if the left operand is shifted "n" times by 1 for a shift count of "n".
|
|
|
|
The unary operators "+", "-", and "^" are defined as follows:
|
|
|
|
+x is 0 + x
|
|
-x negation is 0 - x
|
|
^x bitwise complement is -1 ^ x
|
|
|
|
|
|
Comparison operators
|
|
----
|
|
|
|
Comparison operators yield a boolean result. All comparison operators apply
|
|
to strings and numeric types. The operators "==" and "!=" also apply to
|
|
boolean values and to pointer types (including the value "nil").
|
|
|
|
== equal
|
|
!= not equal
|
|
< less
|
|
<= less or equal
|
|
> greater
|
|
>= greater or equal
|
|
|
|
TODO: Can we/should we be able to compare interfaces?
|
|
|
|
|
|
Logical operators
|
|
----
|
|
|
|
Logical operators apply to boolean operands and yield a boolean result.
|
|
The right operand is evaluated conditionally.
|
|
|
|
&& conditional and p && q is "if p then q else false"
|
|
|| conditional or p || q is "if p then true else q"
|
|
! not !p is "not p"
|
|
|
|
|
|
Address operators
|
|
----
|
|
|
|
TODO: Need to talk about unary "*", clean up section below.
|
|
|
|
Given a function f, declared as
|
|
|
|
func f(a int) int;
|
|
|
|
taking the address of f with the expression
|
|
|
|
&f
|
|
|
|
creates a pointer to the function that may be stored in a value of type pointer
|
|
to function:
|
|
|
|
var fp *(a int) int = &f;
|
|
|
|
The function pointer may be invoked with the usual syntax; no explicit
|
|
indirection is required:
|
|
|
|
fp(7)
|
|
|
|
Methods are a form of function, and the address of a method has the type
|
|
pointer to function. Consider the type T with method M:
|
|
|
|
type T struct {
|
|
a int;
|
|
}
|
|
func (tp *T) M(a int) int;
|
|
var t *T;
|
|
|
|
To construct the address of method M, we write
|
|
|
|
&t.M
|
|
|
|
using the variable t (not the type T). The expression is a pointer to a
|
|
function, with type
|
|
|
|
*(t *T, a int) int
|
|
|
|
and may be invoked only as a function, not a method:
|
|
|
|
var f *(t *T, a int) int;
|
|
f = &t.M;
|
|
x := f(t, 7);
|
|
|
|
Note that one does not write t.f(7); taking the address of a method demotes
|
|
it to a function.
|
|
|
|
In general, given type T with method M and variable t of type *T,
|
|
the method invocation
|
|
|
|
t.M(args)
|
|
|
|
is equivalent to the function call
|
|
|
|
(&t.M)(t, args)
|
|
|
|
If T is an interface type, the expression &t.M does not determine which
|
|
underlying type's M is called until the point of the call itself. Thus given
|
|
T1 and T2, both implementing interface I with interface M, the sequence
|
|
|
|
var t1 *T1;
|
|
var t2 *T2;
|
|
var i I = t1;
|
|
m := &i.M;
|
|
m(t2);
|
|
|
|
will invoke t2.M() even though m was constructed with an expression involving
|
|
t1.
|
|
|
|
|
|
Communication operators
|
|
----
|
|
|
|
The syntax presented above covers communication operations. This
|
|
section describes their form and function.
|
|
|
|
Here the term "channel" means "variable of type *chan".
|
|
|
|
A channel is created by allocating it:
|
|
|
|
ch := new(chan int)
|
|
|
|
An optional argument to new() specifies a buffer size for an
|
|
asynchronous channel; if absent or zero, the channel is synchronous:
|
|
|
|
sync_chan := new(chan int)
|
|
buffered_chan := new(chan int, 10)
|
|
|
|
The send operation uses the binary operator "<-", which operates on
|
|
a channel and a value (expression):
|
|
|
|
ch <- 3
|
|
|
|
In this form, the send operation is an (expression) statement that
|
|
blocks until the send can proceed, at which point the value is
|
|
transmitted on the channel.
|
|
|
|
If the send operation appears in an expression context, the value
|
|
of the expression is a boolean and the operation is non-blocking.
|
|
The value of the boolean reports true if the communication succeeded,
|
|
false if it did not. These two examples are equivalent:
|
|
|
|
ok := ch <- 3;
|
|
if ok { print("sent") } else { print("not sent") }
|
|
|
|
if ch <- 3 { print("sent") } else { print("not sent") }
|
|
|
|
In other words, if the program tests the value of a send operation,
|
|
the send is non-blocking and the value of the expression is the
|
|
success of the operation. If the program does not test the value,
|
|
the operation blocks until it succeeds.
|
|
|
|
TODO: Adjust the above depending on how we rule on the ok semantics.
|
|
|
|
The receive operation uses the prefix unary operator "<-".
|
|
The value of the expression is the value received:
|
|
|
|
<-ch
|
|
|
|
The expression blocks until a value is available, which then can
|
|
be assigned to a variable or used like any other expression:
|
|
|
|
v1 := <-ch
|
|
v2 = <-ch
|
|
f(<-ch)
|
|
|
|
If the receive expression does not save the value, the value is
|
|
discarded:
|
|
|
|
<-strobe // wait until clock pulse
|
|
|
|
If a receive expression is used in a tuple assignment of the form
|
|
|
|
x, ok = <-ch; // or: x, ok := <-ch
|
|
|
|
the receive operation becomes non-blocking, and the boolean variable
|
|
"ok" will be set to "true" if the receive operation succeeded, and set
|
|
to "false" otherwise.
|
|
|
|
|
|
Constant expressions
|
|
----
|
|
|
|
A constant expression is an expression whose operands are all constants
|
|
(§Constants). Additionally, the result of the predeclared functions
|
|
below (with appropriate arguments) is also constant:
|
|
|
|
len(a) if a is a fixed array
|
|
|
|
TODO: Complete this list as needed.
|
|
|
|
Constant expressions can be evaluated at compile time.
|
|
|
|
|
|
Statements
|
|
----
|
|
|
|
Statements control execution.
|
|
|
|
Statement =
|
|
Declaration | LabelDecl |
|
|
SimpleStat | GoStat | ReturnStat | BreakStat | ContinueStat | GotoStat |
|
|
Block | IfStat | SwitchStat | SelectStat | ForStat | RangeStat |
|
|
|
|
SimpleStat =
|
|
ExpressionStat | IncDecStat | Assignment | SimpleVarDecl .
|
|
|
|
Semicolons are used to separate individual statements of a statement list.
|
|
They are optional immediately before or after a closing curly brace "}",
|
|
immediately after "++" or "--", and immediately before a reserved word.
|
|
|
|
StatementList = Statement { [ ";" ] Statement } .
|
|
|
|
|
|
TODO: This still seems to be more complicated then necessary.
|
|
|
|
|
|
Label declarations
|
|
----
|
|
|
|
TODO write this section
|
|
|
|
|
|
Expression statements
|
|
----
|
|
|
|
ExpressionStat = Expression .
|
|
|
|
f(x+y)
|
|
|
|
|
|
IncDec statements
|
|
----
|
|
|
|
IncDecStat = Expression ( "++" | "--" ) .
|
|
|
|
a[i]++
|
|
|
|
Note that ++ and -- are not operators for expressions.
|
|
|
|
|
|
Assignments
|
|
----
|
|
|
|
Assignment = SingleAssignment | TupleAssignment .
|
|
SingleAssignment = PrimaryExpr assign_op Expression .
|
|
TupleAssignment = PrimaryExprList assign_op ExpressionList .
|
|
PrimaryExprList = PrimaryExpr { "," PrimaryExpr } .
|
|
|
|
assign_op = [ add_op | mul_op ] "=" .
|
|
|
|
The left-hand side must be an l-value such as a variable, pointer indirection,
|
|
or an array index.
|
|
|
|
x = 1
|
|
*p = f()
|
|
a[i] = 23
|
|
k = <-ch
|
|
|
|
As in C, arithmetic binary operators can be combined with assignments:
|
|
|
|
j <<= 2
|
|
|
|
A tuple assignment assigns the individual elements of a multi-valued operation,
|
|
such as function evaluation or some channel and map operations, into individual
|
|
variables. For instance, a tuple assignment such as
|
|
|
|
v1, v2, v3 = e1, e2, e3
|
|
|
|
assigns the expressions e1, e2, e3 to temporaries and then assigns the temporaries
|
|
to the variables v1, v2, v3. Thus
|
|
|
|
a, b = b, a
|
|
|
|
exchanges the values of a and b. The tuple assignment
|
|
|
|
x, y = f()
|
|
|
|
calls the function f, which must return two values, and assigns them to x and y.
|
|
As a special case, retrieving a value from a map, when written as a two-element
|
|
tuple assignment, assign a value and a boolean. If the value is present in the map,
|
|
the value is assigned and the second, boolean variable is set to true. Otherwise,
|
|
the variable is unchanged, and the boolean value is set to false.
|
|
|
|
value, present = map_var[key]
|
|
|
|
To delete a value from a map, use a tuple assignment with the map on the left
|
|
and a false boolean expression as the second expression on the right, such
|
|
as:
|
|
|
|
map_var[key] = value, false
|
|
|
|
In assignments, the type of the expression must match the type of the left-hand side.
|
|
|
|
|
|
If statements
|
|
----
|
|
|
|
If statements have the traditional form except that the
|
|
condition need not be parenthesized and the "then" statement
|
|
must be in brace brackets. The condition may be omitted, in which
|
|
case it is assumed to have the value "true".
|
|
|
|
IfStat = "if" [ [ Simplestat ] ";" ] [ Condition ] Block [ "else" Statement ] .
|
|
|
|
if x > 0 {
|
|
return true;
|
|
}
|
|
|
|
An "if" statement may include the declaration of a single temporary variable.
|
|
The scope of the declared variable extends to the end of the if statement, and
|
|
the variable is initialized once before the statement is entered.
|
|
|
|
if x := f(); x < y {
|
|
return x;
|
|
} else if x > z {
|
|
return z;
|
|
} else {
|
|
return y;
|
|
}
|
|
|
|
|
|
TODO: We should fix this and move to:
|
|
|
|
IfStat =
|
|
"if" [ [ Simplestat ] ";" ] [ Condition ] Block
|
|
{ "else" "if" Condition Block }
|
|
[ "else" Block ] .
|
|
|
|
|
|
Switch statements
|
|
----
|
|
|
|
Switches provide multi-way execution.
|
|
|
|
SwitchStat = "switch" [ [ Simplestat ] ";" ] [ Expression ] "{" { CaseClause } "}" .
|
|
CaseClause = Case [ StatementList [ ";" ] ] [ "fallthrough" [ ";" ] ] .
|
|
Case = ( "case" ExpressionList | "default" ) ":" .
|
|
|
|
There can be at most one default case in a switch statement.
|
|
|
|
The reserved word "fallthrough" indicates that the control should flow from
|
|
the end of this case clause to the first statement of the next clause.
|
|
|
|
The expressions do not need to be constants. They will
|
|
be evaluated top to bottom until the first successful non-default case is reached.
|
|
If none matches and there is a default case, the statements of the default
|
|
case are executed.
|
|
|
|
switch tag {
|
|
default: s3()
|
|
case 0, 1: s1()
|
|
case 2: s2()
|
|
}
|
|
|
|
A switch statement may include the declaration of a single temporary variable.
|
|
The scope of the declared variable extends to the end of the switch statement, and
|
|
the variable is initialized once before the switch is entered.
|
|
|
|
switch x := f(); true {
|
|
case x < 0: return -x
|
|
default: return x
|
|
}
|
|
|
|
Cases do not fall through unless explicitly marked with a "fallthrough" statement.
|
|
|
|
switch a {
|
|
case 1:
|
|
b();
|
|
fallthrough
|
|
case 2:
|
|
c();
|
|
}
|
|
|
|
If the expression is omitted, it is equivalent to "true".
|
|
|
|
switch {
|
|
case x < y: f1();
|
|
case x < z: f2();
|
|
case x == 4: f3();
|
|
}
|
|
|
|
|
|
For statements
|
|
----
|
|
|
|
For statements are a combination of the "for" and "while" loops of C.
|
|
|
|
ForStat = "for" [ Condition | ForClause ] Block .
|
|
ForClause = [ InitStat ] ";" [ Condition ] ";" [ PostStat ] .
|
|
|
|
InitStat = SimpleStat .
|
|
Condition = Expression .
|
|
PostStat = SimpleStat .
|
|
|
|
A SimpleStat is a simple statement such as an assignment, a SimpleVarDecl,
|
|
or an increment or decrement statement. Therefore one may declare a loop
|
|
variable in the init statement.
|
|
|
|
for i := 0; i < 10; i++ {
|
|
print(i, "\n")
|
|
}
|
|
|
|
A for statement with just a condition executes until the condition becomes
|
|
false. Thus it is the same as C's while statement.
|
|
|
|
for a < b {
|
|
a *= 2
|
|
}
|
|
|
|
If the condition is absent, it is equivalent to "true".
|
|
|
|
for {
|
|
f()
|
|
}
|
|
|
|
|
|
Range statements
|
|
----
|
|
|
|
Range statements are a special control structure for iterating over
|
|
the contents of arrays and maps.
|
|
|
|
RangeStat = "range" IdentifierList ":=" RangeExpression Block .
|
|
RangeExpression = Expression .
|
|
|
|
A range expression must evaluate to an array, map or string. The identifier list must contain
|
|
either one or two identifiers. If the range expression is a map, a single identifier is declared
|
|
to range over the keys of the map; two identifiers range over the keys and corresponding
|
|
values. For arrays and strings, the behavior is analogous for integer indices (the keys) and
|
|
array elements (the values).
|
|
|
|
a := []int(1, 2, 3);
|
|
m := [string]map int("fo",2, "foo",3, "fooo",4)
|
|
|
|
range i := a {
|
|
f(a[i]);
|
|
}
|
|
|
|
range i, v := a {
|
|
f(v);
|
|
}
|
|
|
|
range k, v := m {
|
|
assert(len(k) == v);
|
|
}
|
|
|
|
TODO: is this right?
|
|
|
|
|
|
Go statements
|
|
----
|
|
|
|
A go statement starts the execution of a function as an independent
|
|
concurrent thread of control within the same address space. Unlike
|
|
with a function, the next line of the program does not wait for the
|
|
function to complete.
|
|
|
|
GoStat = "go" Call .
|
|
|
|
|
|
go Server()
|
|
go func(ch chan <- bool) { for { sleep(10); ch <- true; }} (c)
|
|
|
|
|
|
Select statements
|
|
----
|
|
|
|
A select statement chooses which of a set of possible communications
|
|
will proceed. It looks similar to a switch statement but with the
|
|
cases all referring to communication operations.
|
|
|
|
SelectStat = "select" "{" { CommClause } "}" .
|
|
CommClause = CommCase [ StatementList [ ";" ] ] .
|
|
CommCase = ( "default" | ( "case" ( SendCase | RecvCase) ) ) ":" .
|
|
SendCase = SendExpr .
|
|
RecvCase = RecvExpr .
|
|
SendExpr = Expression "<-" Expression .
|
|
RecvExpr = [ PrimaryExpr ( "=" | ":=" ) ] "<-" Expression .
|
|
|
|
The select statement evaluates all the channel (pointers) involved.
|
|
If any of the channels can proceed, the corresponding communication
|
|
and statements are evaluated. Otherwise, if there is a default case,
|
|
that executes; if not, the statement blocks until one of the
|
|
communications can complete. A channel pointer may be nil, which is
|
|
equivalent to that case not being present in the select statement.
|
|
|
|
If the channel sends or receives an interface type, its
|
|
communication can proceed only if the type of the communication
|
|
clause matches that of the dynamic value to be exchanged.
|
|
|
|
If multiple cases can proceed, a uniform fair choice is made regarding
|
|
which single communication will execute.
|
|
|
|
The receive case may declare a new variable (via a ":=" assignment). The
|
|
scope of such variables begins immediately after the variable identifier
|
|
and ends at the end of the respective "select" case (that is, before the
|
|
next "case", "default", or closing brace).
|
|
|
|
var c, c1, c2 *chan int;
|
|
var i1, i2 int;
|
|
select {
|
|
case i1 = <-c1:
|
|
print("received ", i1, " from c1\n");
|
|
case c2 <- i2:
|
|
print("sent ", i2, " to c2\n");
|
|
default:
|
|
print("no communication\n");
|
|
}
|
|
|
|
for { // send random sequence of bits to c
|
|
select {
|
|
case c <- 0: // note: no statement, no fallthrough, no folding of cases
|
|
case c <- 1:
|
|
}
|
|
}
|
|
|
|
var ca *chan interface {};
|
|
var i int;
|
|
var f float;
|
|
select {
|
|
case i = <-ca:
|
|
print("received int ", i, " from ca\n");
|
|
case f = <-ca:
|
|
print("received float ", f, " from ca\n");
|
|
}
|
|
|
|
TODO: Make semantics more precise.
|
|
|
|
|
|
Return statements
|
|
----
|
|
|
|
A return statement terminates execution of the containing function
|
|
and optionally provides a result value or values to the caller.
|
|
|
|
ReturnStat = "return" [ ExpressionList ] .
|
|
|
|
|
|
There are two ways to return values from a function. The first is to
|
|
explicitly list the return value or values in the return statement:
|
|
|
|
func simple_f() int {
|
|
return 2;
|
|
}
|
|
|
|
A function may return multiple values.
|
|
The syntax of the return clause in that case is the same as
|
|
that of a parameter list; in particular, names must be provided for
|
|
the elements of the return value.
|
|
|
|
func complex_f1() (re float, im float) {
|
|
return -7.0, -4.0;
|
|
}
|
|
|
|
The second method to return values
|
|
is to use those names within the function as variables
|
|
to be assigned explicitly; the return statement will then provide no
|
|
values:
|
|
|
|
func complex_f2() (re float, im float) {
|
|
re = 7.0;
|
|
im = 4.0;
|
|
return;
|
|
}
|
|
|
|
|
|
Break statements
|
|
----
|
|
|
|
Within a for or switch statement, a break statement terminates execution of
|
|
the innermost for or switch statement.
|
|
|
|
BreakStat = "break" [ identifier ].
|
|
|
|
If there is an identifier, it must be the label name of an enclosing
|
|
for or switch
|
|
statement, and that is the one whose execution terminates.
|
|
|
|
L: for i < n {
|
|
switch i {
|
|
case 5: break L
|
|
}
|
|
}
|
|
|
|
|
|
Continue statements
|
|
----
|
|
|
|
Within a for loop a continue statement begins the next iteration of the
|
|
loop at the post statement.
|
|
|
|
ContinueStat = "continue" [ identifier ].
|
|
|
|
The optional identifier is analogous to that of a break statement.
|
|
|
|
|
|
Label declaration
|
|
----
|
|
|
|
A label declaration serves as the target of a goto, break or continue statement.
|
|
|
|
LabelDecl = identifier ":" .
|
|
|
|
Error:
|
|
|
|
|
|
Goto statements
|
|
----
|
|
|
|
A goto statement transfers control to the corresponding label statement.
|
|
|
|
GotoStat = "goto" identifier .
|
|
|
|
goto Error
|
|
|
|
Executing the goto statement must not cause any variables to come into
|
|
scope that were not already in scope at the point of the goto. For
|
|
instance, this example:
|
|
|
|
goto L; // BAD
|
|
v := 3;
|
|
L:
|
|
|
|
is erroneous because the jump to label L skips the creation of v.
|
|
|
|
|
|
Function declarations
|
|
----
|
|
|
|
Functions contain declarations and statements. They may be
|
|
recursive. Functions may be anonymous and appear as
|
|
literals in expressions.
|
|
|
|
A function declaration declares an identifier of type function.
|
|
|
|
FunctionDecl = "func" identifier FunctionType ( ";" | Block ) .
|
|
|
|
func min(x int, y int) int {
|
|
if x < y {
|
|
return x;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
A function declaration without a block serves as a forward declaration:
|
|
|
|
func MakeNode(left, right *Node) *Node;
|
|
|
|
|
|
Implementation restrictions: Functions can only be declared at the global level.
|
|
A function must be declared or forward-declared before it can be invoked.
|
|
|
|
|
|
Methods
|
|
----
|
|
|
|
A method declaration declares a function with a receiver.
|
|
|
|
MethodDecl = "func" Receiver identifier FunctionType ( ";" | Block ) .
|
|
Receiver = "(" identifier Type ")" .
|
|
|
|
A method is bound to the type of its receiver.
|
|
For instance, given type Point, the declarations
|
|
|
|
func (p *Point) Length() float {
|
|
return Math.sqrt(p.x * p.x + p.y * p.y);
|
|
}
|
|
|
|
func (p *Point) Scale(factor float) {
|
|
p.x = p.x * factor;
|
|
p.y = p.y * factor;
|
|
}
|
|
|
|
create methods for type *Point. Note that methods may appear anywhere
|
|
after the declaration of the receiver type and may be forward-declared.
|
|
|
|
|
|
Predeclared functions
|
|
----
|
|
|
|
cap
|
|
convert
|
|
len
|
|
new
|
|
panic
|
|
print
|
|
typeof
|
|
|
|
|
|
TODO: (gri) suggests that we should consider assert() as a built-in function.
|
|
It is like panic, but takes a boolean guard as first argument. (rsc also thinks
|
|
this is a good idea).
|
|
|
|
|
|
Length and capacity
|
|
----
|
|
|
|
The predeclared function "len()" takes a value of type string,
|
|
array or map type, or of pointer to array or map type, and
|
|
returns the length of the string in bytes, or the number of array
|
|
of map elements, respectively.
|
|
|
|
The predeclared function "cap()" takes a value of array or pointer
|
|
to array type and returns the number of elements for which there
|
|
is space allocated in the array. For an array "a", at any time the
|
|
following relationship holds:
|
|
|
|
0 <= len(a) <= cap(a)
|
|
|
|
TODO(gri) Change this and the following sections to use a table indexed
|
|
by functions and parameter types instead of lots of prose.
|
|
|
|
|
|
Conversions
|
|
----
|
|
|
|
TODO: gri believes this section is too complicated. Instead we should
|
|
replace this with: 1) proper conversions of basic types, 2) compound
|
|
literals, and 3) type assertions.
|
|
|
|
Conversions create new values of a specified type derived from the
|
|
elements of a list of expressions of a different type.
|
|
|
|
The most general conversion takes the form of a call to "convert",
|
|
with the result type and a list of expressions as arguments:
|
|
|
|
convert(int, PI * 1000.0);
|
|
convert([]int, 1, 2, 3, 4);
|
|
|
|
If the result type is a basic type, pointer type, or
|
|
interface type, there must be exactly one expression and there is a
|
|
specific set of permitted conversions, detailed later in the section.
|
|
These conversions are called ``simple conversions''.
|
|
TODO: if interfaces were explicitly pointers, this gets simpler.
|
|
|
|
convert(int, 3.14159);
|
|
convert(uint32, ^0);
|
|
convert(interface{}, new(S))
|
|
convert(*AStructType, interface_value)
|
|
|
|
For other result types - arrays, maps, structs - the expressions
|
|
form a list of values to be assigned to successive elements of the
|
|
resulting value. If the type is an array or map, the list may even be
|
|
empty. Unlike in a simple conversion, the types of the expressions
|
|
must be equivalent to the types of the elements of the result type;
|
|
the individual values are not converted. For instance, if result
|
|
type is []int, the expressions must be all of type int, not float or
|
|
uint. (For maps, the successive elements must be key-value pairs).
|
|
For arrays and struct types, if fewer elements are provided than
|
|
specified by the result type, the missing elements are
|
|
initialized to the respective ``zero'' value for that element type.
|
|
|
|
These conversions are called ``compound conversions''.
|
|
|
|
convert([]int) // empty array of ints
|
|
convert([]int, 1, 2, 3)
|
|
convert([5]int, 1, 2); // == convert([5]int, 1, 2, 0, 0, 0)
|
|
convert(map[string]int, "1", 1, "2", 2)
|
|
convert(struct{ x int; y float }, 3, sqrt(2.0))
|
|
|
|
TODO: are interface/struct and 'any' conversions legal? they're not
|
|
equivalent, just compatible. convert([]any, 1, "hi", nil);
|
|
|
|
There is syntactic help to make conversion expressions simpler to write.
|
|
|
|
If the result type is of ConversionType (a type name, array type,
|
|
map type, struct type, or interface type, essentially anything
|
|
except a pointer), the conversion can be rewritten to look
|
|
syntactically like a call to a function whose name is the type:
|
|
|
|
int(PI * 1000.0);
|
|
AStructType(an_interface_variable);
|
|
struct{ x int, y float }{3, sqrt(2.0)}
|
|
[]int{1, 2, 3, 4};
|
|
map[string]int{"1", 1, "2", 2};
|
|
|
|
This notation is convenient for declaring and initializing
|
|
variables of composite type:
|
|
|
|
primes := []int{2, 3, 5, 7, 9, 11, 13};
|
|
|
|
Simple conversions can also be written as a parenthesized type after
|
|
an expression and a period. Although intended for ease of conversion
|
|
within a method call chain, this form works in any expression context.
|
|
TODO: should it?
|
|
|
|
var s *AStructType = vec.index(2).(*AStructType);
|
|
fld := vec.index(2).(*AStructType).field;
|
|
a := foo[i].(string);
|
|
|
|
As said, for compound conversions the element types must be equivalent.
|
|
For simple conversions, the types can differ but only some combinations
|
|
are permitted:
|
|
|
|
1) Between integer types. If the value is a signed quantity, it is
|
|
sign extended to implicit infinite precision; otherwise it is zero
|
|
extended. It is then truncated to fit in the result type size.
|
|
For example, uint32(int8(0xFF)) is 0xFFFFFFFF. The conversion always
|
|
yields a valid value; there is no signal for overflow.
|
|
|
|
2) Between integer and floating point types, or between floating point
|
|
types. To avoid overdefining the properties of the conversion, for
|
|
now we define it as a ``best effort'' conversion. The conversion
|
|
always succeeds but the value may be a NaN or other problematic
|
|
result. TODO: clarify?
|
|
|
|
3) Conversions between interfaces and compatible interfaces and struct
|
|
pointers. Invalid conversions (that is, conversions between
|
|
incompatible types) yield nil values. TODO: is nil right here? Or
|
|
should incompatible conversions fail immediately?
|
|
|
|
4) Conversions between ``any'' values and arbitrary types. Invalid
|
|
conversions yield nil values. TODO: is nil right here? Or should
|
|
incompatible conversions fail immediately?
|
|
|
|
5) Strings permit two special conversions.
|
|
|
|
5a) Converting an integer value yields a string containing the UTF-8
|
|
representation of the integer.
|
|
|
|
string(0x65e5) // "\u65e5"
|
|
|
|
5b) Converting an array of uint8s yields a string whose successive
|
|
bytes are those of the array. (Recall byte is a synonym for uint8.)
|
|
|
|
string([]byte{'h', 'e', 'l', 'l', 'o'}) // "hello"
|
|
|
|
Note that there is no linguistic mechanism to convert between pointers
|
|
and integers. A library may be provided under restricted circumstances
|
|
to acccess this conversion in low-level code but it will not be available
|
|
in general.
|
|
|
|
|
|
Allocation
|
|
----
|
|
|
|
The built-in function "new()" takes a type "T", optionally followed by a
|
|
type-specific list of expressions. It allocates memory for a variable
|
|
of type "T" and returns a pointer of type "*T" to that variable. The
|
|
memory is initialized as described in the section on initial values.
|
|
|
|
new(type, [optional list of expressions])
|
|
|
|
For instance
|
|
|
|
type S struct { a int; b float }
|
|
new(S)
|
|
|
|
dynamically allocates memory for a variable of type S, initializes it
|
|
(a=0, b=0.0), and returns a value of type *S pointing to that variable.
|
|
|
|
The only defined parameters affect sizes for allocating arrays,
|
|
buffered channels, and maps.
|
|
|
|
ap := new([]int, 10); # a pointer to an open array of 10 ints
|
|
c := new(chan int, 10); # a pointer to a channel with a buffer size of 10
|
|
m := new(map[string] int, 100); # a pointer to a map with initial space for 100 elements
|
|
|
|
|
|
Packages
|
|
----
|
|
|
|
A package is a package clause, optionally followed by import declarations,
|
|
followed by a series of declarations.
|
|
|
|
Package = PackageClause { ImportDecl [ ";" ] } { Declaration [ ";" ] } .
|
|
|
|
|
|
Every source file identifies the package to which it belongs.
|
|
The file must begin with a package clause.
|
|
|
|
PackageClause = "package" PackageName .
|
|
|
|
package Math
|
|
|
|
|
|
A package can gain access to exported items from another package
|
|
through an import declaration:
|
|
|
|
ImportDecl = "import" ( ImportSpec | "(" ImportSpecList [ ";" ] ")" ) .
|
|
ImportSpec = [ "." | PackageName ] PackageFileName .
|
|
ImportSpecList = ImportSpec { ";" ImportSpec } .
|
|
|
|
An import statement makes the exported contents of the named
|
|
package file accessible in this package.
|
|
|
|
In the following discussion, assume we have a package in the
|
|
file "/lib/math", called package Math, which exports functions sin
|
|
and cos.
|
|
|
|
In the general form, with an explicit package name, the import
|
|
statement declares that package name as an identifier whose
|
|
contents are the exported elements of the imported package.
|
|
For instance, after
|
|
|
|
import M "/lib/math"
|
|
|
|
the contents of the package /lib/math can be accessed by
|
|
M.cos, M.sin, etc.
|
|
|
|
In its simplest form, with no package name, the import statement
|
|
implicitly uses the imported package name itself as the local
|
|
package name. After
|
|
|
|
import "/lib/math"
|
|
|
|
the contents are accessible by Math.sin, Math.cos.
|
|
|
|
Finally, if instead of a package name the import statement uses
|
|
an explicit period, the contents of the imported package are added
|
|
to the current package. After
|
|
|
|
import . "/lib/math"
|
|
|
|
the contents are accessible by sin and cos. In this instance, it is
|
|
an error if the import introduces name conflicts.
|
|
|
|
Here is a complete example Go package that implements a concurrent prime sieve:
|
|
|
|
package main
|
|
|
|
// Send the sequence 2, 3, 4, ... to channel 'ch'.
|
|
func Generate(ch *chan <- int) {
|
|
for i := 2; ; i++ {
|
|
ch <- i // Send 'i' to channel 'ch'.
|
|
}
|
|
}
|
|
|
|
// Copy the values from channel 'in' to channel 'out',
|
|
// removing those divisible by 'prime'.
|
|
func Filter(in *chan <- int, out *<-chan int, prime int) {
|
|
for {
|
|
i := <-in; // Receive value of new variable 'i' from 'in'.
|
|
if i % prime != 0 {
|
|
out <- i // Send 'i' to channel 'out'.
|
|
}
|
|
}
|
|
}
|
|
|
|
// The prime sieve: Daisy-chain Filter processes together.
|
|
func Sieve() {
|
|
ch := new(chan int); // Create a new channel.
|
|
go Generate(ch); // Start Generate() as a subprocess.
|
|
for {
|
|
prime := <-ch;
|
|
print(prime, "\n");
|
|
ch1 := new(chan int);
|
|
go Filter(ch, ch1, prime);
|
|
ch = ch1
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
Sieve()
|
|
}
|
|
|
|
|
|
Program initialization and execution
|
|
----
|
|
|
|
When memory is allocated to store a value, either through a declaration
|
|
or new(), and no explicit initialization is provided, the memory is
|
|
given a default initialization. Each element of such a value is
|
|
set to the ``zero'' for that type: "false" for booleans, "0" for integers,
|
|
"0.0" for floats, '''' for strings, and nil for pointers. This intialization
|
|
is done recursively, so for instance each element of an array of integers will
|
|
be set to 0 if no other value is specified.
|
|
|
|
These two simple declarations are equivalent:
|
|
|
|
var i int;
|
|
var i int = 0;
|
|
|
|
After
|
|
|
|
type T struct { i int; f float; next *T };
|
|
t := new(T);
|
|
|
|
the following holds:
|
|
|
|
t.i == 0
|
|
t.f == 0.0
|
|
t.next == nil
|
|
|
|
|
|
A package with no imports is initialized by assigning initial values to
|
|
all its global variables in declaration order and then calling any init()
|
|
functions defined in its source. Since a package may contain more
|
|
than one source file, there may be more than one init() function, but
|
|
only one per source file.
|
|
|
|
If a package has imports, the imported packages are initialized
|
|
before initializing the package itself. If multiple packages import
|
|
a package P, P will be initialized only once.
|
|
|
|
The importing of packages, by construction, guarantees that there can
|
|
be no cyclic dependencies in initialization.
|
|
|
|
A complete program, possibly created by linking multiple packages,
|
|
must have one package called main, with a function
|
|
|
|
func main() { ... }
|
|
|
|
defined. The function main.main() takes no arguments and returns no
|
|
value.
|
|
|
|
Program execution begins by initializing the main package and then
|
|
invoking main.main().
|
|
|
|
When main.main() returns, the program exits.
|
|
|
|
TODO: is there a way to override the default for package main or the
|
|
default for the function name main.main?
|
|
|
|
|
|
----
|
|
----
|
|
UNUSED PARTS OF OLD DOCUMENT go_lang.txt - KEEP AROUND UNTIL NOT NEEDED ANYMORE
|
|
----
|
|
|
|
Guiding principles
|
|
----
|
|
|
|
Go is a new systems programming language intended as an alternative to C++ at
|
|
Google. Its main purpose is to provide a productive and efficient programming
|
|
environment for compiled programs such as servers and distributed systems.
|
|
|
|
The design is motivated by the following guidelines:
|
|
|
|
- very fast compilation (1MLOC/s stretch goal); instantaneous incremental compilation
|
|
- procedural
|
|
- strongly typed
|
|
- concise syntax avoiding repetition
|
|
- few, orthogonal, and general concepts
|
|
- support for threading and interprocess communication
|
|
- garbage collection
|
|
- container library written in Go
|
|
- reasonably efficient (C ballpark)
|
|
|
|
The language should be strong enough that the compiler and run time can be
|
|
written in itself.
|
|
|
|
|
|
Program structure
|
|
----
|
|
|
|
A Go program consists of a number of ``packages''.
|
|
|
|
A package is built from one or more source files, each of which consists
|
|
of a package specifier followed by import declarations followed by other
|
|
declarations. There are no statements at the top level of a file.
|
|
|
|
By convention, one package, by default called main, is the starting point for
|
|
execution. It contains a function, also called main, that is the first function
|
|
invoked by the run time system.
|
|
|
|
If a source file within the program
|
|
contains a function init(), that function will be executed
|
|
before main.main() is called.
|
|
|
|
Source files can be compiled separately (without the source
|
|
code of packages they depend on), but not independently (the compiler does
|
|
check dependencies by consulting the symbol information in compiled packages).
|
|
|
|
|
|
Modularity, identifiers and scopes
|
|
----
|
|
|
|
A package is a collection of import, constant, type, variable, and function
|
|
declarations. Each declaration associates an ``identifier'' with a program
|
|
entity (such as a type).
|
|
|
|
In particular, all identifiers in a package are either
|
|
declared explicitly within the package, arise from an import statement,
|
|
or belong to a small set of predefined identifiers (such as "int32").
|
|
|
|
A package may make explicitly declared identifiers visible to other
|
|
packages by marking them as exported; there is no ``header file''.
|
|
Imported identifiers cannot be re-exported.
|
|
|
|
Scoping is essentially the same as in C: The scope of an identifier declared
|
|
within a ``block'' extends from the declaration of the identifier (that is, the
|
|
position immediately after the identifier) to the end of the block. An identifier
|
|
shadows identifiers with the same name declared in outer scopes. Within a
|
|
block, a particular identifier must be declared at most once.
|
|
|
|
|
|
Typing, polymorphism, and object-orientation
|
|
----
|
|
|
|
Go programs are strongly typed. Certain values can also be
|
|
polymorphic. The language provides mechanisms to make use of such
|
|
polymorphic values type-safe.
|
|
|
|
Interface types provide the mechanisms to support object-oriented
|
|
programming. Different interface types are independent of each
|
|
other and no explicit hierarchy is required (such as single or
|
|
multiple inheritance explicitly specified through respective type
|
|
declarations). Interface types only define a set of methods that a
|
|
corresponding implementation must provide. Thus interface and
|
|
implementation are strictly separated.
|
|
|
|
An interface is implemented by associating methods with types.
|
|
If a type defines all methods of an interface, it
|
|
implements that interface and thus can be used where that interface is
|
|
required. Unless used through a variable of interface type, methods
|
|
can always be statically bound (they are not ``virtual''), and incur no
|
|
runtime overhead compared to an ordinary function.
|
|
|
|
[OLD
|
|
Interface types, building on structures with methods, provide
|
|
the mechanisms to support object-oriented programming.
|
|
Different interface types are independent of each
|
|
other and no explicit hierarchy is required (such as single or
|
|
multiple inheritance explicitly specified through respective type
|
|
declarations). Interface types only define a set of methods that a
|
|
corresponding implementation must provide. Thus interface and
|
|
implementation are strictly separated.
|
|
|
|
An interface is implemented by associating methods with
|
|
structures. If a structure implements all methods of an interface, it
|
|
implements that interface and thus can be used where that interface is
|
|
required. Unless used through a variable of interface type, methods
|
|
can always be statically bound (they are not ``virtual''), and incur no
|
|
runtime overhead compared to an ordinary function.
|
|
END]
|
|
|
|
Go has no explicit notion of classes, sub-classes, or inheritance.
|
|
These concepts are trivially modeled in Go through the use of
|
|
functions, structures, associated methods, and interfaces.
|
|
|
|
Go has no explicit notion of type parameters or templates. Instead,
|
|
containers (such as stacks, lists, etc.) are implemented through the
|
|
use of abstract operations on interface types or polymorphic values.
|
|
|
|
|
|
Pointers and garbage collection
|
|
----
|
|
|
|
Variables may be allocated automatically (when entering the scope of
|
|
the variable) or explicitly on the heap. Pointers are used to refer
|
|
to heap-allocated variables. Pointers may also be used to point to
|
|
any other variable; such a pointer is obtained by "taking the
|
|
address" of that variable. Variables are automatically reclaimed when
|
|
they are no longer accessible. There is no pointer arithmetic in Go.
|
|
|
|
|
|
Multithreading and channels
|
|
----
|
|
|
|
Go supports multithreaded programming directly. A function may
|
|
be invoked as a parallel thread of execution. Communication and
|
|
synchronization are provided through channels and their associated
|
|
language support.
|
|
|
|
|
|
Values and references
|
|
----
|
|
|
|
All objects have value semantics, but their contents may be accessed
|
|
through different pointers referring to the same object.
|
|
For example, when calling a function with an array, the array is
|
|
passed by value, possibly by making a copy. To pass a reference,
|
|
one must explicitly pass a pointer to the array. For arrays in
|
|
particular, this is different from C.
|
|
|
|
There is also a built-in string type, which represents immutable
|
|
strings of bytes.
|
|
|
|
|
|
Syntax
|
|
----
|
|
|
|
The syntax of statements and expressions in Go borrows from the C tradition;
|
|
declarations are loosely derived from the Pascal tradition to allow more
|
|
comprehensible composability of types.
|
|
|
|
Interface of a type
|
|
----
|
|
|
|
The interface of a type is defined to be the unordered set of methods
|
|
associated with that type. Methods are defined in a later section;
|
|
they are functions bound to a type.
|
|
|
|
|
|
[OLD
|
|
It is legal to assign a pointer to a struct to a variable of
|
|
compatible interface type. It is legal to assign an interface
|
|
variable to any struct pointer variable but if the struct type is
|
|
incompatible the result will be nil.
|
|
END]
|
|
|
|
|
|
[OLD
|
|
The polymorphic "any" type
|
|
----
|
|
|
|
Given a variable of type "any", one can store any value into it by
|
|
plain assignment or implicitly, such as through a function parameter
|
|
or channel operation. Given an "any" variable v storing an underlying
|
|
value of type T, one may:
|
|
|
|
- copy v's value to another variable of type "any"
|
|
- extract the stored value by an explicit conversion operation T(v)
|
|
- copy v's value to a variable of type T
|
|
|
|
Attempts to convert/extract to an incompatible type will yield nil.
|
|
|
|
No other operations are defined (yet).
|
|
|
|
Note that type
|
|
interface {}
|
|
is a special case that can match any struct type, while type
|
|
any
|
|
can match any type at all, including basic types, arrays, etc.
|
|
|
|
TODO: details about reflection
|
|
END]
|
|
|
|
|
|
Equivalence of types
|
|
---
|
|
|
|
TODO: We may need to rethink this because of the new ways interfaces work.
|
|
|
|
Types are structurally equivalent: Two types are equivalent (``equal'') if they
|
|
are constructed the same way from equivalent types.
|
|
|
|
For instance, all variables declared as "*int" have equivalent type,
|
|
as do all variables declared as "map [string] *chan int".
|
|
|
|
More precisely, two struct types are equivalent if they have exactly the same fields
|
|
in the same order, with equal field names and types. For all other composite types,
|
|
the types of the components must be equivalent. Additionally, for equivalent arrays,
|
|
the lengths must be equal (or absent), and for channel types the mode must be equal
|
|
(">", "<", or none). The names of receivers, parameters, or result values of functions
|
|
are ignored for the purpose of type equivalence.
|
|
|
|
For instance, the struct type
|
|
|
|
struct {
|
|
a int;
|
|
b int;
|
|
f *(m *[32] float, x int, y int) bool
|
|
}
|
|
|
|
is equivalent to
|
|
|
|
struct {
|
|
a, b int;
|
|
f *F
|
|
}
|
|
|
|
where "F" is declared as "func (a *[30 + 2] float, b, c int) (ok bool)".
|
|
|
|
Finally, two interface types are equivalent if they both declare the same set of
|
|
methods: For each method in the first interface type there is a method in the
|
|
second interface type with the same method name and equivalent function type,
|
|
and vice versa. Note that the declaration order of the methods is not relevant.
|
|
|
|
|
|
[OLD
|
|
The nil value
|
|
----
|
|
|
|
The predeclared constant
|
|
|
|
nil
|
|
|
|
represents the ``zero'' value for a pointer type or interface type.
|
|
|
|
The only operations allowed for nil are to assign it to a pointer or
|
|
interface variable and to compare it for equality or inequality with a
|
|
pointer or interface value.
|
|
|
|
var p *int;
|
|
if p != nil {
|
|
print(p)
|
|
} else {
|
|
print("p points nowhere")
|
|
}
|
|
|
|
By default, pointers are initialized to nil.
|
|
|
|
TODO: This needs to be revisited.
|
|
|
|
[OLD
|
|
TODO: how does this definition jibe with using nil to specify
|
|
conversion failure if the result is not of pointer type, such
|
|
as an any variable holding an int?
|
|
|
|
TODO: if interfaces were explicitly pointers, this gets simpler.
|
|
END]
|
|
|
|
|
|
Expressions
|
|
----
|
|
|
|
Expression syntax is based on that of C but with fewer precedence levels.
|
|
|
|
Expression = BinaryExpr | UnaryExpr | PrimaryExpr .
|
|
BinaryExpr = Expression binary_op Expression .
|
|
UnaryExpr = unary_op Expression .
|
|
|
|
PrimaryExpr =
|
|
identifier | Literal | "(" Expression ")" | "iota" |
|
|
Call | Conversion | Allocation | Index |
|
|
Expression "." identifier | Expression "." "(" Type ")" .
|
|
|
|
Call = Expression "(" [ ExpressionList ] ")" .
|
|
Conversion =
|
|
"convert" "(" Type [ "," ExpressionList ] ")" | ConversionType "(" [ ExpressionList ] ")" .
|
|
ConversionType = TypeName | ArrayType | MapType | StructType | InterfaceType .
|
|
Allocation = "new" "(" Type [ "," ExpressionList ] ")" .
|
|
Index = SimpleIndex | Slice .
|
|
SimpleIndex = Expression "[" Expression"]" .
|
|
Slice = Expression "[" Expression ":" Expression "]" .
|
|
|
|
|
|
|
|
TODO
|
|
----
|
|
|
|
- TODO: type switch?
|
|
- TODO: words about slices
|
|
- TODO: really lock down semicolons
|
|
- TODO: need to talk (perhaps elsewhere) about libraries, sys.exit(), etc.
|