一、注意事项:

本文所讨论的正则表达式,均是JavaScript版本的。

二、应用:

本系列文章所讨论的技术,都已经使用在了函数涂鸦板 中。

三、要解决的问题:函数涂鸦板

在《识别幂函数输入的正则表达式(二)》中,通过使用增强的幂函数识别正则表达式

/(\w+)\s*\^\s*([-\+]?\s*\w+|\(.*\))/

实现了对

y = x ^ 2

y = x ^ –2

y = x ^ + 2

y = x ^ (-3/2)

这几种类型的幂函数输入模式进行识别。对于单一函数输入,这个解决方案已经完美。但是,

  1. 对于多函数输入如 y = x^3; y = x^2; ,上述正则表达式将只匹配第一个。
  2. 对于嵌套幂函数输入如 y = x^2^3;,上述正则表达式将只能识别外层的一个。

本文将逐步构造出一个终极的幂函数识别正则表达式,达到能够识别以下各种模式的幂函数输入:

  • 增强幂函数识别正则表达式所能识别的各种(向下兼容),即
    • y = x^2;
    • y = x^-2;
    • y = x^+2;
    • y = x^(-3/2);
  • 嵌套幂函数的输入,即
    • y = x^2^3;
    • y = (x^2)^3;
    • y = x^(2^3);
    • y = sin(x)^2;
    • y = sin(x^2);
    • y = x^(-2)^3;
    • y = x^2^-3;
  • 以上各种函数的组合(各函数使用;号分隔)

四、解决方案:

终极的幂函数识别正则表达式为:

/(\w+?(?:\([^\^]*?\))??|\([^\^]*?\))\s*?\^\s*?([-\+]??\s*?\w+?|\(.*?\))/g

为实现对于嵌套幂函数的识别与替换,需要使用循环,因为JavaScript不支持嵌套的正则表达式语法。

function processUserInput(s) {
	// 识别幂函数输入:
	var rePower = /(\w+?(?:\([^\^]*?\))??|\([^\^]*?\))\s*?\^\s*?([-\+]??\s*?\w+?|\(.*?\))/g;
	while (rePower.test(s)) {
		s = s.replace(rePower, "pow($1, $2)");
		// 下面这个语句非常重要!!!
		rePower.lastIndex = 0;
	}
	return s;
}

五、原理:

  1. 实现对于以;号分隔的函数的组合进行识别:这个非常简单,将正则表达式的模式改为全局模式,即/(\w+)\s*\^\s*([-\+]?\s*\w+|\(.*\))/g
  2. 为识别 (x)^3 或者 sin(x)^2 这样的模式,将原来的指数左半部分即 ^ 号左边的正则表达式作一扩充:
    • (\w+) –> (\w+(\([^\^]*\))?|\([^\^]*\))
    •   <li>原来左半部分的是一个捕获性分组,即一个单词。于是只识别 x^3 中的 x。扩充后的,| 号左边匹配 x^3 中的 x,或者 sin(x) ^ 3 中的 sin(x)。注意到那个?号了吗?就是()部分可有可无。| 号右边的,和左边后半部分一样,即专门匹配 (x) ^3 中的 (x)。所以不带?号。| 号是或者的关系。 </li>
      
        <li>[^\^]*表示()号中可以是除了^号之外的任意字符。 </li>
      
        <li>于是,原来的正则表达式,由/<font color="#ff0000">(\w+)</font>\s*\^\s*([-\+]?\s*\w+|\(.*\))/升级成了/<font color="#ff0000">(\w+(\([^\^]*\))?|\([^\^]*\))</font>\s*\^\s*([-\+]?\s*\w+|\(.*\))/<font color="#ff0000">g</font> </li>
      
        <li>在正则表达式中,使用捕获性分组,会降低效率(虽然对于现在的计算机,这算不上什么,但这里还是节约了这点效率)。于是将不必要的分组由捕获性分组变成非捕获性分组,于是:/<font color="#ff0000">(\w+(\([^\^]*\))?|\([^\^]*\))</font>\s*\^\s*([-\+]?\s*\w+|\(.*\))/<font color="#ff0000">g</font>&#160; --&gt;&#160; /<font color="#ff0000">(\w+(<strong><font color="#800000">?:</font></strong>\([^\^]*\))?|\([^\^]*\))</font>\s*\^\s*([-\+]?\s*\w+|\(.*\))/<font color="#ff0000">g</font>&#160; </li>
      </ul>
      
    • 为了能够识别嵌套的模式,需要使用惰性量词,即先看字符串中的第一个字母是否匹配。如果单独这一个字符还不够,就读入下一个字符,组成两个字符的字符串。如果还是没有发现匹配,惰性量词继续从字符串中添加字符直到发现匹配或者整个字符串都检查过也没有匹配。如果使用惰性量词,会不会造成只识别第一个出现的模式呢?对!会出现这种情况,但是,别担心,在解决方案中,我们用了循环!惰性量词就是添加?号,于是/(\w+(?:\([^\^]*\))?|\([^\^]*\))\s*\^\s*([-\+]?\s*\w+|\(.*\))/g  --> /(\w+?(?:\([^\^]*?\))??|\([^\^]*?\))\s*?\^\s*?([-\+]??\s*?\w+?|\(.*?\))/g 
    • 成了!

六、附录:

JavaScript 正则表达式贪婪、惰性、支配量词的概念介绍:参见《自定义 String 对象的trim()方法》一文最底部。

JavaScript 正则表达式贪婪、惰性、支配量词的表示方法列表:

  <td valign="top" width="150">
    <p align="center"><strong>惰性</strong></p>
  </td>

  <td valign="top" width="150">
    <p align="center"><strong>支配</strong></p>
  </td>

  <td valign="top" width="195">
    <p align="center"><strong>描述</strong></p>
  </td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">??</p>
  </td>

  <td valign="top" width="150">
    <p align="center">?+</p>
  </td>

  <td valign="top" width="195">&#160; 零次或一次出现</td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">*</p>
  </td>

  <td valign="top" width="150">
    <p align="center">*?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">*+</p>
  </td>

  <td valign="top" width="195">&#160; 零次或多次出现</td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">+</p>
  </td>

  <td valign="top" width="150">
    <p align="center">+?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">++</p>
  </td>

  <td valign="top" width="195">&#160; 一次或多次出现</td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">{n}</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n}?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n}+</p>
  </td>

  <td valign="top" width="195">&#160; 恰好n次出现</td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">{n,m}</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n,m}?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n,m}+</p>
  </td>

  <td valign="top" width="195">&#160; 至少n次至多m次出现</td>
</tr>

<tr>
  <td valign="top" width="150">
    <p align="center">{n,}</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n,}?</p>
  </td>

  <td valign="top" width="150">
    <p align="center">{n,}+</p>
  </td>

  <td valign="top" width="195">&#160; 至少n次出现</td>
</tr>

贪婪