词法结构

文档

M 文档是 Unicode 字符的有序序列。 M 允许在 M 文档的不同部分使用不同类别的 Unicode 字符。 有关 Unicode 字符类的信息,请参阅 Unicode 标准,版本 3.0 中的第 4.5 节。

文档要么由一个表达式组成,要么由组织成节的多组定义构成 。 第 10 章对节进行了详细说明。 从概念上讲,以下步骤用于从文档中读取表达式:

  1. 文档根据其字符编码方案被解码为一个 Unicode 字符序列。

  2. 执行词法分析,从而将 Unicode 字符流转换为令牌流。 本节余下的小节将介绍词法分析。

  3. 执行词法分析,从而将令牌流转换为可计算的形式。 后续部分将介绍此过程。

语法约定

词法和句法用文法产生式来表示。 每个文法产生式定义一个非终端符号,以及该非终端符号可能扩展成一系列非终端或终端符号。 在文法产生式中,非终端+ 符号以斜体显示,终端符号以固定宽度字体显示。

文法产生式的第一行是定义的非终端符号的名称,后跟冒号。 每一个后续缩进行都包含一个非终端符的可能扩展,该非终端符由一系列非终端符或终端符符号组成。 例如,产生式:

if-expression:
      ifif-conditionthentrue-expressionelsefalse-expression

定义一个 if-expression,由令牌 if 后跟 if-condition,令牌 then 后跟 true-expression 以及令牌 else 后跟 false-expression 组成。

当非终端符号有多个可能的扩展时,替代项在单独的行中列出。 例如,产生式:

variable-list:
      可变
      variable-list
,variable

定义一个 variable-list 由一个变量或 variable-list 后跟 variable 组成 。 换言之,这个定义是递归的,它指定变量列表由一个或多个(用逗号分隔的)变量组成。

下标后缀“opt”用于指示可选符号。 产生式:

field-specification:
      optionalopt field-name=field-type

为以下的简写:

field-specification:
      field-name
=field-type
      optionalfield-name=field-type

并定义了 field-specification 可选地以终端符号 optional 开头,后跟 field-name、终端符号 = 和 field-type 。

替代项通常在单独的行中列出,但在有许多替代项的情况下,“one of”一词可能出现在单行上给出的一列扩展之前。 这只是对在单独的行中列出每个替代项的简化。 例如,产生式:

decimal-digit: one of
      0 1 2 3 4 5 6 7 8 9

为以下的简写:

decimal-digit:
      0
      1
      2
      3
      4
      5
      6
      7
      8
      9

词法分析

lexical-unit 产生式定义了 M 文档的词法语法。 每个有效的 M 文档都遵循此语法。

lexical-unit:
      lexical-elementsopt
lexical-elements:
      lexical-element
      lexical-element
      lexical-elements
lexical-element:
      whitespace
      token comment

在词法级别,M 文档由一系列空白、注释和令牌元素组成 。 以下各节将介绍这些产生式。 在语法中,只有令牌元素是有意义的。

空格

空格用于分隔 M 文档中的注释和令牌。 空格包括空格字符(它是 Unicode 类 Zs 的一部分),以及水平和垂直制表符、换页符和换行符序列。 换行字符序列包括回车符、换行符、后跟换行符的回车符、下一行和段落分隔符。

空格
      带有 Unicode 类 Zs 的任何字符
      水平制表符字符 (U+0009)
      垂直制表符 (U+000B)
      换页符 (U+000C)
      后跟换行符 (U+000A) 的回车符 (U+000D)
      new-line-character
new-line-character
      回车符 (U+000D)
      换行符 (U+000A)
      换行符 (U+0085)
      行分隔符 (U+2028)
      段落分隔符 (U+2029)

为了与添加 end-of-file 标记的源代码编辑工具兼容,并使文档能够被看作正确终止的行序列,将按顺序对 M 文档应用以下转换:

  • 如果文档的最后一个字符是 Control-Z 字符 (U+001A),则删除此字符。

  • 如果文档非空并且文档的最后一个字符不是回车符 (U+000D)、换行符 (U+000A)、行分隔符 (U+2028) 或段落分隔符 (U+2029),则在文档末尾添加回车符 (U+000D)。

注释

支持两种形式的注释:单行注释和分隔注释。 S 注释以字符 // 开头,并扩展到源行的末尾。 分隔注释以字符 /* 开头,以字符 */ 结尾。

分隔注释可能跨多行。

comment:
      single-line-comment
      delimited-comment
single-line-comment:

      //single-line-comment-charactersopt
single-line-comment-characters:
      single-line-comment-character single-line-comment-charactersopt
single-line-comment-character:

      除 new-line-character 之外的任何 Unicode 字符
delimited-comment:

      /*delimited-comment-textopt asterisks/
delimited-comment-text:
      delimited-comment-section delimited-comment-textopt
delimited-comment-section:

      /
      asterisksopt not-slash-or-asterisk
asterisks:

      *asterisksopt
not-slash-or-asterisk:

      除 */ 之外的任何 Unicode 字符

注释不嵌套。 字符序列 /**/ 在单行注释中没有特殊含义,字符序列 ///* 在分隔注释中没有特殊含义。

不在文字串中处理注释。 示例

/* Hello, world 
*/ 
    "Hello, world"

包含分隔注释。

示例

// Hello, world 
// 
"Hello, world" // This is an example of a text literal

显示若干单行注释。

令牌

令牌是标识符、关键字、文字、运算符或标点符号。 空白和注释用于分隔标记,但不会将其视为令牌。

token:
      标识符
      关键字 (keyword)
      文本
      operator-or-punctuator

字符转义序列

M 文本值可以包含任意 Unicode 字符。 然而,文字文本仅限于图形字符,需要对非图形字符使用转义序列。 例如,要在文字文本中包含回车符、换行符或制表符,可以分别使用 #(cr)#(lf)#(tab) 转义序列。 若要在文字文本中嵌入转义序列开始字符 #(# 本身需要进行转义:

#(#)(

转义序列也可以包含短(四个十六进制数字)或长(八个十六进制数字)Unicode 码位值。 因此,以下三个转义序列是等效的:

#(000D)     // short Unicode hexadecimal value 
#(0000000D) // long Unicode hexadecimal value 
#(cr)       // compact escape shorthand for carriage return

单个转义序列中可以包含多个转义码,用逗号分隔;因此,以下两个序列是等效的:

#(cr,lf) 
#(cr)#(lf)

下面介绍 M 文档中字符转义的标准机制。

character-escape-sequence:
      #(escape-sequence-list)
escape-sequence-list:
      single-escape-sequence
      single-escape-sequence
,escape-sequence-list
single-escape-sequence:
      long-unicode-escape-sequence
      short-unicode-escape-sequence
      control-character-escape-sequence
      escape-escape
long-unicode-escape-sequence:
      hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit hex-digit
short-unicode-escape-sequence:
      hex-digit hex-digit hex-digit hex-digit
control-character-escape-sequence:
      control-character
control-character:

      cr
      lf
      tab
escape-escape:
      #

文字

文本是值的源代码表示形式。

literal:
      logical-literal
      number-literal
      text-literal
      null-literal
      verbatim-literal

null 文本

null 文本用于写入 null 值。 null 值表示不存在的值。

null-literal:
      null

逻辑文本

逻辑文本用于写入值 truefalse,并生成逻辑值。

logical-literal:
      true
      false

数字文本

数字文字用于写入数值并生成数值。

number-literal:
      decimal-number-literal
      hexadecimal-number-literal
decimal-number-literal:
      decimal-digits
.decimal-digits exponent-partopt
      .decimal-digits exponent-partopt
      decimal-digits exponent-partopt
decimal-digits:
      decimal-digit decimal-digitsopt
decimal-digit:
one of
      0 1 2 3 4 5 6 7 8 9
exponent-part:
      esignopt decimal-digits
      Esignopt decimal-digits
sign:
one of
      + -
hexadecimal-number-literal:
      0xhex-digits
      0Xhex-digits
hex-digits:
      hex-digit hex-digitsopt
hex-digit:
one of
      0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

通过在十六进制数字前面加上字符 0x,可以十六进制格式指定一个数字。 例如:

0xff // 255

请注意,如果数字文本中包含小数点,则它后面必须至少有一个数字。 例如,1.3 是数字文本,但 1.1.e3 不是。

文本文字

文本文字用于写入 Unicode 字符序列并生成文本值。

text-literal:
      "text-literal-charactersopt"
text-literal-characters:
      text-literal-character text-literal-charactersopt
text-literal-character:
      single-text-character
      character-escape-sequence
      double-quote-escape-sequence
single-text-character:

      除后跟 ( (U+0028) 的 " (U+0022) 或 # (U+0023) 外的任何字符
double-quote-escape-sequence:
      "" (U+0022, U+0022)

若要在文本值中包含引号,请重复使用引号,如下所示:

"The ""quoted"" text" // The "quoted" text

可使用 character-escape-sequence 产生式在文本值中写入字符,而无需在文档中将它们直接编码为 Unicode 字符。 例如,回车和换行可以用文本值写入:

"Hello world#(cr,lf)"

逐字文本

逐字文本用于存储用户作为代码输入但无法正确分析为代码的 Unicode 字符序列。 在运行时,它会生成一个错误值。

verbatim-literal:
      #!"text-literal-charactersopt"

标识符

标识符是用于引用值的名称。 标识符可以是常规标识符,也可以是带引号的标识符。

identifier:
      regular-identifier
      quoted-identifier
regular-identifier:
      available-identifier
      available-identifier dot-character regular-identifier
available-identifier:

      不是 keyword 的 keyword-or-identifier
keyword-or-identifier:
      identifier-start-character identifier-part-charactersopt
identifier-start-character:
      letter-character
      underscore-character
identifier-part-characters:
      identifier-part-character identifier-part-charactersopt
identifier-part-character:
      letter-character
      decimal-digit-character
      underscore-character
      connecting-character
      combining-character
      formatting-character
dot-character:

      . (U+002E)
underscore-character:
      _ (U+005F)
letter-character:
      Lu、Ll、Lt、Lm、Lo 或 Nl 类的 Unicode 字符
combining-character:
      Mn 或 Mc 类的 Unicode 字符
decimal-digit-character:
      Nd 类的 Unicode 字符
connecting-character:
      Pc 类的 Unicode 字符
formatting-character:
      Cf 类的 Unicode 字符

带引号的标识符可用于允许零个或多个 Unicode 字符的任何序列用作标识符,包括关键字、空格、注释、运算符和标点符号。

quoted-identifier:
      #"text-literal-charactersopt"

注意,转义序列和用于转义引号的双引号可以在带引号的标识符中使用,就像在 text-literal 中一样 。

以下示例对包含空格字符的名称使用标识符引号:

[ 
    #"1998 Sales" = 1000, 
    #"1999 Sales" = 1100, 
    #"Total Sales" = #"1998 Sales" + #"1999 Sales"
]

以下示例使用标识符引号将 + 运算符包含在标识符中:

[ 
    #"A + B" = A + B, 
    A = 1, 
    B = 2 
]

通用化标识符

在 M 中有两个地方没有由包含空格或关键字或数字文字的标识符引起的歧义。 这些位置是记录文本中记录字段的名称,在字段访问运算符 ([ ]) 中,M 允许这样的标识符,而不必使用带引号的标识符。

[ 
    Data = [ Base Line = 100, Rate = 1.8 ], 
    Progression = Data[Base Line] * Data[Rate]
]

用于命名和访问字段的标识符称为通用标识符,定义如下:

generalized-identifier:
      generalized-identifier-part
      generalized-identifier
separated only by blanks (U+0020)
generalized-identifier-part
generalized-identifier-part:
      generalized-identifier-segment
      decimal-digit-character generalized-identifier-segment
generalized-identifier-segment:
      keyword-or-identifier
      keyword-or-identifier dot-character keyword-or-identifier

关键字

关键字是保留的类似标识符的字符序列,不能用作标识符,除非使用标识引用机制允许使用通用标识符

keyword: one of
       and as each else error false if in is let meta not null or otherwise
       section shared then true try type #binary #date #datetime
       #datetimezone #duration #infinity #nan #sections #shared #table #time

运算符和标点符号

有多种运算符和标点符号。 表达式中使用运算符来描述涉及一个或多个操作数的操作。 例如,表达式 a + b 使用 + 运算符添加两个操作数 ab。 标点符号用于分组和分隔。

operator-or-punctuator: one of
      , ; = < <= > >= <> + - * / & ( ) [ ] { } @ ! ? ?? => .. ...