静态大小变量(除映射和动态大小的数组类型的所有内容)在存储连续布置从需要小于32个字节被打包成一个单一的存储槽是否可能位置0多个项目开始,根据下面的规则:
当使用小于32字节的元素时,您的合同的天然气使用量可能会更高。这是因为EVM一次运行32个字节。因此,如果元素小于该值,则EVM必须使用更多的操作,以便将元素的大小从32个字节减小到所需的大小。
如果您正在处理存储值,则使用缩小的参数是有益的,因为编译器将将多个元素打包到一个存储槽中,从而将多个读取或写入组合到一个操作中。当处理函数参数或内存值时,没有固有的优点,因为编译器不会打包这些值。
最后,为了使EVM能够进行优化,请确保您尝试对存储变量和结构体成员进行排序,使其可以紧密包装。例如,以uint128,uint128,uint256而不是uint128,uint256,uint128的顺序声明存储变量,因为前者只占用两个存储槽,而后者将占用三个。
结构体和数组的元素相互存储,就像它们被明确给出一样。
由于其不可预测的大小,映射和动态大小的数组类型使用Keccak-256哈希计算来查找值或数组数据的起始位置。这些起始位置始终是整个堆栈槽。
映射或动态数组本身根据上述规则(或通过递归地将此规则用于映射到映射或数组阵列)占用某个位置p处的存储空间(未填充)。对于动态数组,这个槽存储数组中的元素数(字节数组和字符串在这里是一个例外,见下文)。对于映射,插槽未使用(但是需要这样两个相等的映射将使用不同的散列分布)。阵列数据位于keccak256(p),对应于映射密钥k的值位于keccak256(k.p),其中。是连接。如果该值再次是非基本类型,则通过添加keccak256(k.p)的偏移来找到位置。
字节和字符串将它们的数据存储在同一个存储槽中,如果它们很短,存储长度也是如此。特别是:如果数据长度最多为31个字节,则存储在高字节(左对齐),最低位字节存储长度* 2.如果长度较长,则主槽存储长度* 2 + 1,并且数据像keccak256(插槽)一样存储。
因此,对于本合同段:
pragma solidity ^0.4.0;
contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
}
Solidity保留三个256位插槽:
暂存空间可以语句(即,内联汇编)之间使用。
Solidity总是将新对象放在空闲的内存指针上,而且内存永远不会被释放(这在将来可能会改变)。
在Solidity中有一些操作需要大于64字节的临时内存区域,因此不能适应临时空间。 它们将被放置在可用内存指向的位置,但是由于其生命周期短,所以指针不被更新。 内存可能被删除也可能不会被清零。 因此,不应该期望可用内存被清零。
当部署一个Solidity合约时,当从一个帐户被调用时,输入数据被假定为ABI规范中的格式。 ABI规范要求将参数填充为32个字节的倍数。 内部函数调用使用不同的约定。
当值小于256位时,在某些情况下必须清除其余位。 Solidity编译器旨在在可能受到剩余位中的潜在垃圾负面影响的任何操作之前清理这些剩余位。 例如,在向存储器写入值之前,剩余的位需要被清除,因为存储器内容可以用于计算散列或作为消息调用的数据发送。 类似地,在将值存储在存储器中之前,剩下的位需要被清除,因为否则可以观察到乱码。
另一方面,如果紧随其后的操作不受影响,我们不清理这些位。 例如,由于JUMPI指令的任何非零值都被认为是真的,所以在将它们用作JUMPI的条件之前,我们不会清除布尔值。
除了上述设计原理之外,Solidity编译器在将其加载到堆栈时清理输入数据。
不同的类型有不同的清除无效值的规则:
类型 | 有效值 | 无效值意味着 |
---|---|---|
n个成员的枚举 | 0到n-1 | 异常 |
布尔 | 0或1 | 1 |
有符号整数 | 符号扩展字 | 目前默默地包; 在未来的异常将被抛出 |
无符号整数 | 高位归零 | 目前默默地包; 在未来的异常将被抛出 |
Solidity优化器在装配上运行,因此可以被其他语言使用。它将指令序列分为JUMP和JUMPDEST中的基本块。在这些块内,分析指令,并将堆栈,存储器或存储器的每个修改记录为由指令和基本上指向其他表达式的指针组成的表达式。主要思想是找到总是相等的表达式(在每个输入上),并将它们组合成一个表达式类。优化器首先尝试在已知表达式的列表中查找每个新表达式。如果这不起作用,则表达式将按照常数+常量= sum_of_constants或X * 1 = X的规则进行简化。由于这是递归完成的,如果第二个因素是更复杂的表达式,那么我们也可以应用后一个规则知道它总是会评价一个。对存储和存储器位置的修改必须消除关于不知道不同的存储和存储器位置的知识:如果我们首先写入位置x然后到位置y并且都是输入变量,则第二个可以覆盖第一个,因此我们实际上我们写信给y后,不知道什么是x存储。另一方面,如果表达式x-y的简化评估为非零常数,则我们知道我们可以保持对x存储的知识。
在这个过程的最后,我们知道哪些表达式最后必须在堆栈中,并且有一个对内存和存储的修改列表。该信息与基本块一起存储,并用于链接它们。此外,关于堆栈,存储和存储器配置的知识被转发到下一个块。如果我们知道所有JUMP和JUMPI指令的目标,我们可以构建程序的完整的控制流程图。如果只有一个目标我们不知道(原则上可能发生跳跃目标可以从输入计算),我们必须清除所有关于块的输入状态的知识,因为它可以是未知JUMP的目标。如果发现一个JUMPI,其条件评估为常数,则将其转换为无条件跳转。
作为最后一步,每个块中的代码都被完全重新生成。依赖图是从块上的堆栈中的表达式创建的,并且不是此图的一部分的每个操作都基本上被删除。现在生成的代码是按照原始代码中的顺序将修改应用于内存和存储(删除被发现不需要的修改),最后生成正确的堆栈所需的所有值地点。
这些步骤适用于每个基本块,如果新生成的代码较小,则将新生成的代码用作替换。如果基本块在JUMPI处被分割,并且在分析过程中,条件计算结果为一个常量,JUMPI将根据常量的值进行替换,因此代码如
var x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
被简化为可以编译的代码
data[7] = 9;
return 1;
即使指令在开头就包含跳转。
作为AST输出的一部分,编译器提供由AST中相应节点表示的源代码范围。 这可以用于各种目的,从静态分析工具,基于AST的报告错误和突出显示局部变量及其用途的调试工具。
此外,编译器还可以生成从字节码到生成指令的源代码范围的映射。 这对于操作字节码级别的静态分析工具和在调试器中的源代码中显示当前位置或进行断点处理也是重要的。
这两种源映射都使用整数标识符来引用源文件。 这些是通常称为“sourceList”的源文件列表中的常规数组索引,它是组合json和json / npm编译器输出的一部分。
AST内的源映射使用以下符号:
s:l:f
其中s是源文件中范围开始的字节偏移量,l是源字段的长度,以字节为单位,f是上面提到的源索引。
字节码的源映射中的编码更复杂:它是一个列表:s:l:f:j由…分隔。 这些元素中的每一个对应于一条指令,即不能使用字节偏移,但必须使用指令偏移量(推送指令长于单个字节)。 字段s,l和f如上,j可以是i,o或 - 表示跳转指令是否进入函数,从函数返回或作为例如一部分的常规跳转。 一个循环。
为了压缩这些源映射,特别是对于字节码,使用以下规则:
这意味着以下源映射表示相同的信息:
1:2:1;1:9:1;2:1:2;2:1:2;2:1:2
1:2:1;:9;2::2;;
Solidity编译器会自动生成包含当前合同信息的JSON文件(合同元数据)。 它可以用于查询编译器版本,使用的源,ABI和NatSpec文档,以便更安全地与合同交互并验证其源代码。
编译器将元数据文件的Swarm哈希附加到每个合同的字节码(有关详细信息,请参见下文)的末尾,以便您可以以经过身份验证的方式检索文件,而无需使用集中式数据提供程序。
当然,您必须将元数据文件发布给Swarm(或其他一些服务),以便其他人可以访问它。 该文件可以使用solc –metadata输出,该文件将被称为ContractName_meta.json。 它将包含Swarm对源代码的引用,因此您必须上传所有源文件和元数据文件。
元数据文件具有以下格式。 下面的例子以人类可读的方式呈现。 正确格式化的元数据应该正确使用引号,将空格减少到最小,并对所有对象的键进行排序以获得唯一的格式。 注释当然也不允许在这里只用于说明目的。
{
// 必须的: 元数据格式的版本
version: "1",
// 必须的: 源代码语言,基本上选择了“子版本”的规范
language: "Solidity",
// 必须的: 有关编译器的详细信息,内容特定于该语言。
compiler: {
// Solidity需要:编译器的版本
version: "0.4.6+commit.2dabbdf0.Emscripten.clang",
// 可选的: 生成此输出的编译器二进制的哈希
keccak256: "0x123..."
},
// 必须的: 编译源文件/源单位,键是文件名
sources:
{
"myFile.sol": {
// 必须的: keccak256源文件的哈希
"keccak256": "0x123...",
// 必须的 (除非使用“content”,请参见下文): 排序的URL(s)到源文件,协议或多或少是任意的,但建议使用Swarm URL
"urls": [ "bzzr://56ab..." ]
},
"mortal": {
// 必须的: 源文件的keccak256
"keccak256": "0x234...",
// 必须的 (除非是使用url): 源文件的文字内容
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// 必须的: 编译器设置
settings:
{
// Solidity必需:排序的重新列出的列表
remappings: [ ":g/dir" ],
// 可选:优化设置(默认为false)
optimizer: {
enabled: true,
runs: 500
},
// Solidity需要:创建此元数据的合同或库的文件和名称。
compilationTarget: {
"myFile.sol": "MyContract"
},
// Solidity需要使用的库地址
libraries: {
"MyLib": "0x123123..."
}
},
// 必需:生成关于合约的信息。
output:
{
// 必需:ABI合同定义
abi: [ ... ],
// 必需:NatSpec用户合同文档
userdoc: [ ... ],
// 必需:NatSpec开发人员合同文档
devdoc: [ ... ],
}
}
注意上面的ABI定义没有固定的顺序。 它可以随编译器版本而改变
由于生成的合同的字节码包含元数据哈希值,元数据的任何更改将导致字节码的更改。 此外,由于元数据包括使用的所有源的散列,任何源代码中的单个空格改变将导致不同的元数据,并且随后将导致不同的字节码。
因为我们以后可能会支持其他方法来检索元数据文件,所以映射{“bzzr0”:}被存储为CBOR编码。 由于编码的开始是不容易找到的,所以它的长度被加入一个两字节的大端编码。 因此,当前版本的Solidity编译器会将以下内容添加到部署字节码的末尾:
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
因此,为了检索数据,可以检查部署的字节码的结尾以匹配该模式,并使用Swarm哈希来检索文件。
元数据以以下方式使用:要与合同交互的组件(例如,Mist)从那些被检索的文件的Swarm哈希中检索合同的代码。 该文件被JSON解码成如上所述的结构。
然后,组件可以使用ABI自动生成合同的基本用户界面。
此外,Mist可以使用userdoc在与合同交互时向用户显示确认消息。
为了验证编译,可以通过元数据文件中的链接从Swarm中检索源。 正确版本的编译器(被检查为“官方”编译器的一部分)将在具有指定设置的输入上调用。 将生成的字节码与创建事务或CREATE操作码数据的数据进行比较。 这将自动验证元数据,因为它的散列是字节码的一部分。 过多的数据对应于构造函数输入数据,应根据接口进行解码并呈现给用户。
delete
删除其所有元素。address(contractVariable).send(amount)
。x = MyStruct({a: 1, b: 2});
以下是按照评估顺序列出的运算符的优先顺序。
优先权 | 描述 | header 2 |
---|---|---|
1 | Postfix增量和减量 | ++ -- |
1 | 新表达式 | new |
1 | 数组下标 |
|
1 | 成员访问 |
|
1 | 类似函数的调用 |
|
1 | 括号 | ( |
2 | 前缀增量和减量 | ++ -- |
2 | 一元加减 | + - |
2 | 一元运算 | delete |
2 | 逻辑非 | ! |
2 | 位非 | ~ |
3 | 幂 | ** |
4 | 乘法,除法和模数 | * / % |
5 | 加减 | + - |
6 | 按位移位运算符 | << >> |
7 | 按位AND | & |
8 | 按位异或 | ^ |
9 | 按位OR | ` |
10 | 不平等运算符 | < > <= >= |
11 | 平等运算符 | == != |
12 | 逻辑和 | && |
13 | 逻辑或 | ` |
14 | 三元运算符 |
|
15 | 赋值运算符 | = , ` |
16 | 逗号运算符 | , |
block.blockhash(uint blockNumber) returns (bytes32)
:给定块的散列 - 仅适用于256个最近的块block.coinbase (address)
:当前块矿工的地址block.difficulty (uint)
:当前块难度block.gaslimit (uint)
:当前块gaslimitblock.number (uint)
:当前块号block.timestamp (uint)
:当前块的时间戳msg.data (bytes)
:完整的calldatamsg.gas (uint)
: 剩余gasmsg.sender (address)
: 消息的发送者(当前呼叫)msg.value (uint)
: 与消息一起发送的数量now (uint)
:当前块时间戳(block.timestamp的别名)tx.gasprice (uint)
: gas价格的交易tx.origin (address)
:交易的发送者(全调用链)assert(bool condition)
: 如果条件为false,则中止执行并恢复状态更改(用于内部错误)require(bool condition)
: 如果条件为false,则中止执行并恢复状态更改(用于格式错误的输入或外部组件中的错误)revert()
: 中止执行并恢复状态更改keccak256(...) returns (bytes32)
:计算(紧密包装)参数的Ethereum-SHA-3(Keccak-256)散列sha3(...) returns (bytes32)
:keccak256的别名sha256(...) returns (bytes32)
: 计算(紧密包装)参数的SHA-256散列ripemd160(...) returns (bytes20)
: 计算(紧密包装)参数的RIPEMD-160哈希值ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
: 从椭圆曲线签名恢复与公钥相关联的地址,错误返回零addmod(uint x, uint y, uint k) returns (uint)
: 计算(x + y)%k,其中以任意精度执行加法,并且不在2 ** 256周围mulmod(uint x, uint y, uint k) returns (uint)
: 计算(x * y)%k,其中以任意精度执行乘法,并且不会在2 ** 256周围this (current contract’s type)
: 目前的合约,明确转换为地址super
: 较高的继承层次结构中的合同一个级别selfdestruct(address recipient)
: 摧毁目前的合约,将资金送到给定地址suicide(address recipieint)
: selfdestruct的别名.balance (uint256)
: 余额地址在wei.send(uint256 amount) returns (bool)
: wei发送给定量到地址,失败时返回假.transfer(uint256 amount)
: 发给定量的wei先生解决,抛出失败function myFunction() <visibility specifier> returns (bool) {
return true;
}
public
: 在外部和内部可见(为存储/状态变量创建一个getter函数)private
: 仅在当前合约中可见external
: 只能从外部看到(仅用于函数) - 即只能通过消息调用(通过this.func)internal
: 只在内部可见pure
对于函数 : 不允许修改或访问状态 - 这还没有被强制执行。view
对于函数 : 不允许修改状态 - 这还没有执行。payable
对于函数 : 允许他们通过调用接收Ether。constant
对于状态变量 : 不允许分配(初始化除外),不占用存储槽。constant
对于函数 : 和view
一样anonymous
对于事件 : 不会将事件签名存储为主题。indexed
对于事件参数 : 将参数存储为主题。这些关键字保留在Solidity。 他们可能会成为未来语法的一部分:
abstract
, after
, case
, catch
, default
, final
, in
, inline
, let
, match
, null
, of
, relocatable
, static
, switch
, try
, type
, typeof
.
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')'
ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')'
TypeNameList = '(' ( TypeName (',' TypeName )* )? ')'
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | StateMutability )*
( 'returns' TypeNameList )?
StorageLocation = 'memory' | 'storage'
StateMutability = 'pure' | 'constant' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
| Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
| Expression ('<' | '>' | '<=' | '>=') Expression
| Expression ('==' | '!=') Expression
| Expression '&&' Expression
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
TupleExpression = '(' ( Expression ( ',' Expression )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | 'var'
| Int | Uint | Byte | Fixed | Ufixed
Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
Fixed = 'fixed' | ( 'fixed' DecimalNumber 'x' DecimalNumber )
Uixed = 'ufixed' | ( 'ufixed' DecimalNumber 'x' DecimalNumber )
InlineAssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
AssemblyLabel = Identifier ':'
FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'
Next Previous