【问题标题】:JavaScript RegEx SyntaxJavaScript 正则表达式语法
【发布时间】:2011-08-23 12:33:55
【问题描述】:

我正在编写 C# 代码来将 JavaScript 解析为令牌,而我对 JavaScript 的了解并不是 100%。

让我印象深刻的一件事是 JavaScript 正则表达式没有用引号括起来。那么解析器如何检测它们何时开始和结束呢?看起来它们以 / 开头,但之后几乎可以包含任何字符。

请注意,我并不是在询问匹配某些字符所需的语法,这就是我的 Google 搜索的所有结果所涉及的内容。我只想知道确定我如何知道正则表达式从哪里开始和在哪里结束的规则。

【问题讨论】:

  • 我知道编写解析器很有趣,但是根据您的要求,您应该知道那里有 ECMAScript 解析器。如果您想计算 JScript,甚至还有一个内置于 .NET 中的本机脚本。 (我知道,我知道,但我用它来构建和运行 sizzle.js,所以我认为它非常合规)。看看Coco/R compiler generator,它有一个 C# 实现,可以从 BNF 风格的语法构建解析器。
  • 天哪,这太难了。我花了将近一整年的时间来学习解析 JavaScript。
  • @harpo:谢谢,但是使用别人的代码有什么乐趣呢? :-)
  • @ChaoesPandion:你为什么这么说?编写我的标记器对我来说似乎非常简单。我只是不确定正则表达式,因为我不太了解语法。
  • @Jonathan - 那一年还包括编写一个完整的运行时,但我也可能只是个傻瓜。不过这很有趣。

标签: javascript regex parsing


【解决方案1】:

我认为以下 RegExp 是一个合理的近似值。

/(\\/|[^/])+/([a-zA-Z])*

正式定义规则:

RegularExpressionLiteral :: 见 7.8.5 / 正则表达式主体 / 正则表达式标志 正则表达式体 :: 见 7.8.5 正则表达式FirstChar 正则表达式字符 正则表达式字符 :: 见 7.8.5 [空的] 正则表达式字符 正则表达式字符 RegularExpressionFirstChar :: 见 7.8.5 RegularExpressionNonTerminator 但不是 * 或 \ 或 / 或 [ 之一 正则表达式反斜杠序列 正则表达式类 正则表达式字符 :: 见 7.8.5 RegularExpressionNonTerminator 但不是 \ 或 / 或 [ 正则表达式反斜杠序列 正则表达式类 正则表达式反斜杠序列 :: 见 7.8.5 \ 正则表达式非终结符 RegularExpressionNonTerminator :: 见 7.8.5 SourceCharacter 但不是 LineTerminator 正则表达式类 :: 见 7.8.5 [正则表达式类字符] 正则表达式类字符 :: 见 7.8.5 [空的] 正则表达式类字符正则表达式类字符 RegularExpressionClassChar :: 见 7.8.5 RegularExpressionNonTerminator 但不是 ] 或 \ 正则表达式反斜杠序列 正则表达式标志 :: 见 7.8.5 [空的] 正则表达式标志标识符部分

Full Specification

这里有一些快速而肮脏的代码,可能会帮助您入门。

class CharStream
{
    private readonly Stack<int> _states;
    private readonly string _input;
    private readonly int _length;
    private int _index;

    public char Current
    {
        get { return _input[_index]; }
    }

    public CharStream(string input)
    {
        _states = new Stack<int>();
        _input = input;
        _length = input.Length;
        _index = -1;
    }

    public bool Next()
    {
        if (_index < 0)
            _index++;
        if (_index == _length)
            return false;
        _index++;
        return true;
    }

    public bool ExpectNext(char c)
    {
        if (_index == _length)
            return false;
        if (_input[_index + 1] != c)
            return false;
        _index++;
        return true;
    }

    public bool Back()
    {
        if (_index == 0)
            return false;
        _index--;
        return true;
    }

    public void PushState()
    {
        _states.Push(_index);
    }

    public T PopState<T>()
    {
        _index = _states.Pop();
        return default(T);
    }
}

static string ParseRegularExpressionLiteral(CharStream cs)
{
    string body, flags;
    cs.PushState();
    if (!cs.ExpectNext('/'))
        return cs.PopState<string>();
    if ((body = ParseRegularExpressionBody(cs)) == null)
        return cs.PopState<string>();
    if (!cs.ExpectNext('/'))
        return cs.PopState<string>();
    if ((flags = ParseRegularExpressionFlags(cs)) == null)
        return cs.PopState<string>();
    return "/" + body + "/" + flags;
}

static string ParseRegularExpressionBody(CharStream cs)
{
    string firstChar, chars;
    cs.PushState();
    if ((firstChar = ParseRegularExpressionFirstChar(cs)) == null)
        return cs.PopState<string>();
    if ((chars = ParseRegularExpressionChars(cs)) == null)
        return cs.PopState<string>();
    return firstChar + chars;
}

static string ParseRegularExpressionChars(CharStream cs)
{
    var sb = new StringBuilder();
    string @char;
    while ((@char = ParseRegularExpressionChar(cs)) != null)
        sb.Append(@char);
    return sb.ToString();
}

static string ParseRegularExpressionFirstChar(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionChar(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionBackslashSequence(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionNonTerminator(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionClass(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionClassChars(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionClassChar(CharStream cs)
{
    return null;
}

static string ParseRegularExpressionFlags(CharStream cs)
{
    return null;
}

至于如何找到文字的结尾?那么诀窍是递归地遵循我列出的产品。考虑产生式RegularExpressionBody。简单地阅读生产告诉我它需要RegularExpressionFirstChar,然后是RegularExpressionChars。注意RegularExpressionChars[empty]RegularExpressionChars RegularExpressionChar。本质上它是由它自己定义的。一旦该生产以[empty] 终止,您就知道唯一有效的字符应该是结束/。如果没有找到,这不是一个有效的文字。

【讨论】:

  • @Kobi - 除非您正在构建 ECMAScript 的完整实现,否则没有什么相关的。我这里贴的是词汇语法的一部分。
  • @ChaosPandion:我很欣赏详细的信息,但必须承认我很难理解它的正面或反面。例如,如果你问我整数中可以包含哪些字符,我可能会说“0123456789”。很好很简单。在上面的正式规则或您的代码示例中,它告诉我哪些字符在正则表达式中是合法的,哪些在标志部分是合法的?我一定在这里遗漏了什么。
  • @Jonathan - 你错了。 JavaScript 还支持负数 (-12)、八进制文字 (010 = 8)、十六进制文字 (0xa = 10) 和浮点数 (2e3 = 2000)。这真的很有趣。查看语言的形式语法是一个非常好的主意。
  • @Jonathan - 缺少一些作品,我会为您添加。
  • @Jonathan - 我决定添加一个完整的规范链接,这样我就不需要手动格式化所有内容。您很可能希望查看 7.6 标识符名称和标识符,因为这部分语法用于 RegExp 标志。
【解决方案2】:
  1. var test = new RegExp("\\d/\\d/\\d", "g");(标志是第二个参数)
  2. test = /\d\/\d\/\d/;
  3. test = /\d\/\d\/\d/g(标志在最后一个 / 之后)

使用/ 转义第二个字符中的字符。解释:
/      - 正则表达式的开头
\d    - 数字字符
\/    - 转义的/(与实际的/ 字符匹配)
\d    - 数字字符
\/    - 转义 /(与实际的 / 字符匹配)
\d    - 数字字符
/      - 正则表达式结束

这将匹配1/2/3

【讨论】:

  • new RegExp("\\d/\\d/\\d") 的反斜杠必须加倍
  • @mVChr:哎呀,忘记添加了。谢谢! :)
  • 不要忘记 javascript 并不是一种严格的语言。如果您正在解析未以一致方式编码的 javascript,那么测试用例将不仅仅是 1/2/3。人们可能会在他们的正则表达式周围加上引号或忘记输入分号等。
  • @BestPractices: 就像我在 #3 中所做的一样 :(
  • 谢谢,但我不完全清楚如何判断是否找到了表达式的结尾,或者 / 是否只是表示标志的开始。
【解决方案3】:

文字 Javascript 正则表达式可能如下所示:

/myregularexpressionliteral/

/yregularexpressionlitera/myregex flags

在此处查看更多信息:http://lawrence.ecorp.net/inet/samples/regexp-intro.php

【讨论】:

  • 那么,当我遇到第二个/时,我怎么知道我找到了表达式的结尾还是标志的开头?
  • 你可能会找到一个“;”在行尾。这将取决于正则表达式的编码方式,因为有人可以以任何方式将它们放入 javascript 中。例如:myregex = /test/;或 myregex = "/test/";
  • 它们不能也作为参数传递吗?如果是这样,; 不会立即跟随它们。我想我正在寻找比“可能”更明确的东西。
【解决方案4】:

对于匹配字符,文字 / 像 \/ 一样被转义,因此您在找到结尾 / 时应该没有任何困难。之后是标志,它们的数量是有限的。

【讨论】:

  • 其他答案似乎表明标志是可选的。如果是这样,我怎么知道第二个/ 是标志的结束还是开始?
【解决方案5】:

我最终解决这个问题的方法是依赖以前的令牌。

主要问题是区分正则表达式文字和分隔符号。我这样做的唯一方法是查看它出现的上下文。

因此,例如,如果前一个标记是数字,则正斜杠只能是除法运算符。

在某些情况下,这不是 100% 可靠的。但就我而言,这似乎是最好的解决方案。

【讨论】:

    猜你喜欢
    • 2012-02-24
    • 2011-03-22
    • 2012-05-06
    • 2010-09-20
    • 2011-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    相关资源
    最近更新 更多