ANTLR PCRE 语法到 JS 目标

2023-11-30

我正在尝试构建 Bart Kiers 的 ANTLR PCRE 语法(请参阅:http://big-o.nl/apps/pcreparser/pcre/PCREParser.html) 到 JS 目标。我构建它的唯一方法是使用全局回溯和记忆,它生成的代码在这里无效是语法:

grammar PCRE;

options {
    language=JavaScript;
    backtrack=true;
    memoize=true;
}

parse
   :  regexAtom* EOF
   ;

 ... and the rest of the grammar as seen: http://big-o.nl/apps/pcreparser/pcre/PCREParser.html

词法分析器正在生成如下所示的代码:

//$ANTLR 3.4 PCRE.g 2011-11-19 23:22:35

var PCRELexer = function(input, state) {
// alternate constructor @todo
// public PCRELexer(CharStream input)
// public PCRELexer(CharStream input, RecognizerSharedState state) {
    if (!state) {
        state = new org.antlr.runtime.RecognizerSharedState();
    }

    (function(){
    }).call(this);

    PCRELexer.superclass.constructor.call(this, input, state);

    };


org.antlr.lang.augmentObject(PCRELexer, {
     : ,
     : ,
     : ,
     : ,
     : ,
  ... and more of this empty object.

当我尝试使用这个生成的代码时,我在上面的 argumentObject 行上收到了 JS 错误。有人可以指导我如何正确构建 JS 目标吗?我最终将构建一个步行器来生成与同一页面上显示的输出类似的输出。



更新:生成 JS 词法分析器和解析器文件的语法可以在这里找到:https://github.com/bkiers/PCREParser/tree/js


我已经有一段时间没有更新该页面了(如果有时间的话,我会尽快更新)。这是修改后的语法,不使用全局回溯,并且可以(据我所知)与 JavaScript 一起工作(使用 ANTLR v3.3 进行测试):

grammar PCRE;

options {
  output=AST;
  language=JavaScript;
}

tokens {
  REGEX;
  ATOM;
  DOT;
  OR;
  CHAR_CLASS;
  NEG_CHAR_CLASS;
  RANGE;
  QUOTATION;
  INT;
  QUANTIFIER;
  GREEDY;
  RELUCTANT;
  POSSESSIVE;
  BACK_REFERENCE;
  CAPTURE_GROUP;
  FLAG_GROUP;
  ATOMIC_GROUP;
  NON_CAPTURE_GROUP;
  POSITIVE_LOOK_AHEAD;
  NEGATIVE_LOOK_AHEAD;
  POSITIVE_LOOK_BEHIND;
  NEGATIVE_LOOK_BEHIND;
  FLAGS;
  ENABLE;
  DISABLE;
  DOT;
  ATOM;
  ATOMS;
}

// parser rules

parse
  :  regexAtoms EOF -> ^(REGEX regexAtoms)
  ;

regexAtoms
  :  atoms (Or^ atoms)*
  ;

atoms
  :  regexAtom* -> ^(ATOMS regexAtom*)
  ;

regexAtom
  :  unit quantifier? -> ^(ATOM unit quantifier?)
  ;

unit
  :  charClass
  |  singleChar
  |  boundaryMatch
  |  quotation
  |  backReference
  |  group
  |  ShorthandCharacterClass
  |  PosixCharacterClass
  |  Dot
  ;

quantifier
  :  (greedy -> ^(GREEDY greedy))
     ('+'    -> ^(POSSESSIVE greedy)
     |'?'    -> ^(RELUCTANT greedy)
     )?
  ;

greedy
  :  '+'            -> INT["1"] INT["2147483647"]
  |  '*'            -> INT["0"] INT["2147483647"]
  |  '?'            -> INT["0"] INT["1"]
  |  '{' (a=integer -> INT[$a.text] INT[$a.text]) 
     (
       (','         -> INT[$a.text] INT["2147483647"])
       (b=integer   -> INT[$a.text] INT[$b.text])?
     )? 
     '}'
  ;

charClass
  :  '[' (('^')=> '^' charClassAtom+ ']' -> ^(NEG_CHAR_CLASS charClassAtom+)
         |        charClassAtom+ ']'     -> ^(CHAR_CLASS charClassAtom+)
         )
  ;

charClassAtom
  :  (charClassSingleChar '-' charClassSingleChar)=> 
     charClassSingleChar '-' charClassSingleChar -> ^(RANGE charClassSingleChar charClassSingleChar)
  |  quotation
  |  ShorthandCharacterClass
  |  BoundaryMatch
  |  PosixCharacterClass
  |  charClassSingleChar
  ;

charClassSingleChar
  :  charClassEscape
  |  EscapeSequence
  |  OctalNumber
  |  SmallHexNumber
  |  UnicodeChar
  |  Or
  |  Caret
  |  Hyphen
  |  Colon
  |  Dollar
  |  SquareBracketStart
  |  RoundBracketStart
  |  RoundBracketEnd
  |  CurlyBracketStart
  |  CurlyBracketEnd
  |  Equals
  |  LessThan
  |  GreaterThan
  |  ExclamationMark
  |  Comma
  |  Plus
  |  Star
  |  QuestionMark
  |  Dot
  |  Digit
  |  OtherChar
  ;

charClassEscape
  :  '\\' ('\\' | '^' | ']' | '-')
  ;

singleChar
  :  regexEscape
  |  EscapeSequence
  |  OctalNumber
  |  SmallHexNumber
  |  UnicodeChar
  |  Hyphen
  |  Colon
  |  SquareBracketEnd
  |  CurlyBracketEnd
  |  Equals
  |  LessThan
  |  GreaterThan
  |  ExclamationMark
  |  Comma
  |  Digit
  |  OtherChar
  ;

regexEscape
  :  '\\' ('\\' | '|' | '^' | '$' | '[' | '(' | ')' | '{' | '}' | '+' | '*' | '?' | '.')
  ;

boundaryMatch
  :  Caret
  |  Dollar
  |  BoundaryMatch
  ;

backReference
  :  '\\' integer -> ^(BACK_REFERENCE integer)
  ;

group
  :  '(' 
     ( '?' ( (flags                -> ^(FLAG_GROUP flags)
             ) 
             (':' regexAtoms       -> ^(NON_CAPTURE_GROUP flags regexAtoms)
             )?
           | '>' regexAtoms        -> ^(ATOMIC_GROUP regexAtoms)
           | '!' regexAtoms        -> ^(NEGATIVE_LOOK_AHEAD regexAtoms)
           | '=' regexAtoms        -> ^(POSITIVE_LOOK_AHEAD regexAtoms)
           | '<' ( '!' regexAtoms  -> ^(NEGATIVE_LOOK_BEHIND regexAtoms)
                 | '=' regexAtoms  -> ^(POSITIVE_LOOK_BEHIND regexAtoms)
                 )
           )
     | regexAtoms                  -> ^(CAPTURE_GROUP regexAtoms)
     )
     ')'
  ;

flags
  :  (a=singleFlags     -> ^(FLAGS ^(ENABLE $a))) 
     ('-' b=singleFlags -> ^(FLAGS ^(ENABLE $a) ^(DISABLE $b))
     )?
  ;

singleFlags
  :  OtherChar*
  ;

quotation
  :  QuotationStart innerQuotation QuotationEnd -> ^(QUOTATION innerQuotation)
  ;

innerQuotation
  :  (~QuotationEnd)*
  ;

integer
  :  (options{greedy=true;}: Digit)+
  ;

// lexer rules

QuotationStart
  :  '\\Q'
  ;

QuotationEnd
  :  '\\E'
  ;

PosixCharacterClass
  :  '\\p{' ('Lower' | 'Upper' | 'ASCII' | 'Alpha' | 'Digit' | 'Alnum' | 'Punct' | 'Graph' | 'Print' | 'Blank' | 'Cntrl' | 'XDigit' | 'Space') '}'
  ;

ShorthandCharacterClass
  :  Escape ('d' | 'D' | 's' | 'S' | 'w' | 'W')
  ;

BoundaryMatch
  :  Escape ('b' | 'B' | 'A' | 'Z' | 'z' | 'G')
  ;

OctalNumber
  :  Escape '0' ( OctDigit? OctDigit 
                | '0'..'3' OctDigit OctDigit
                )
  ;

SmallHexNumber
  :  Escape 'x' HexDigit HexDigit
  ;

U    nicodeChar
  :  Escape 'u' HexDigit HexDigit HexDigit HexDigit
  ;

EscapeSequence
  :  Escape ('t' | 'n' | 'r' | 'f' | 'a' | 'e' | ~('a'..'z' | 'A'..'Z' | '0'..'9'))
  ;

Escape             : '\\';
Or                 : '|';
Hyphen             : '-';
Caret              : '^';
Colon              : ':';
Dollar             : '$';
SquareBracketStart : '[';
SquareBracketEnd   : ']';
RoundBracketStart  : '(';
RoundBracketEnd    : ')';
CurlyBracketStart  : '{';
CurlyBracketEnd    : '}';
Equals             : '=';
LessThan           : '<';
GreaterThan        : '>';
ExclamationMark    : '!';
Comma              : ',';
Plus               : '+';
Star               : '*';
QuestionMark       : '?';
Dot                : '.';
Digit              : '0'..'9';
OtherChar          :  . ;

// fragments
fragment OctDigit : '0'..'7';
fragment HexDigit : ('0'..'9' | 'a'..'f' | 'A'..'F');

它几乎不包含目标特定代码。我做的唯一一件事就是使用几个字符串文字来重写 AST(请参阅量词)和一些.text调用,但几乎所有 ANTLR 目标都接受双引号字符串文字,并且.text,所以您应该熟悉 Java、Python、C 和 JavaScript。对于 C#,我想你需要更改.text调用.Text.

您可以使用以下 HTML 文件对其进行测试:

<html>
  <head>
    <script type="text/javascript" src="antlr3-all-min.js"></script>
    <script type="text/javascript" src="PCRELexer.js"></script>
    <script type="text/javascript" src="PCREParser.js"></script>

    <style type="text/css">
      #tree {
        padding: 20px;
        font-family: Monospace;
      }
      .leaf {
        font-weight: bold; 
        font-size: 130%;
      }
    </style>

    <script type="text/javascript">

    function init() {
      document.getElementById("parse").onclick = parseRegex;
    }

    function parseRegex() {
      document.getElementById("tree").innerHTML = "";
      var regex = document.getElementById("regex").value;
      if(regex) {
        var lexer = new PCRELexer(new org.antlr.runtime.ANTLRStringStream(regex));
        var parser = new PCREParser(new org.antlr.runtime.CommonTokenStream(lexer));
        var root = parser.parse().getTree();
        printTree(root, 0);
      }
      else {
        document.getElementById("regex").value = "enter a regex here first"; 
      }
    }

    function printTree(root, indent) {
      if(!root) return;
      for(var i = 0; i < indent; i++) {
        document.getElementById("tree").innerHTML += ".&nbsp;";
      }
      var n = root.getChildCount();
      if(n == 0) {
        document.getElementById("tree").innerHTML += "<span class=\"leaf\">" + root + "</span><br />";
      }
      else {
        document.getElementById("tree").innerHTML += root + "<br />";
      }
      for(i = 0; i < n; i++) {
        printTree(root.getChild(i), indent + 1);  
      }
    }

    </script>
  </head>
<body onload="init()">
  <input id="regex" type="text" size="50" />
  <button id="parse">parse</button>
  <div id="tree"></div>
</body>
</html>

(我很少使用 JavaScript,所以不要介意上面发布的混乱!)

如果您现在解析正则表达式:

[^-234-7]|(?=[ab\]@]++$).|^$|\1\.\(

借助上面的 HTML 文件,您将看到以下内容打印到屏幕上:


REGEX
. |
. . |
. . . |
. . . . ATOMS
. . . . . ATOM
. . . . . . NEG_CHAR_CLASS
. . . . . . . -
. . . . . . . 2
. . . . . . . 3
. . . . . . . RANGE
. . . . . . . . 4
. . . . . . . . 7
. . . . ATOMS
. . . . . ATOM
. . . . . . POSITIVE_LOOK_AHEAD
. . . . . . . ATOMS
. . . . . . . . ATOM
. . . . . . . . . CHAR_CLASS
. . . . . . . . . . a
. . . . . . . . . . b
. . . . . . . . . . \]
. . . . . . . . . . @
. . . . . . . . . POSSESSIVE
. . . . . . . . . . 1
. . . . . . . . . . 2147483647
. . . . . . . . ATOM
. . . . . . . . . $
. . . . . ATOM
. . . . . . .
. . . ATOMS
. . . . ATOM
. . . . . ^
. . . . ATOM
. . . . . $
. . ATOMS
. . . ATOM
. . . . BACK_REFERENCE
. . . . . 1
. . . ATOM
. . . . \.
. . . ATOM
. . . . \(  

小心,我没有正确测试语法!如果您发现其中有任何错误,请告诉我,我将不胜感激。

EDIT

如果您取消注释该行language=JavaScript;,重新生成词法分析器和解析器并运行以下类:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = "[^-234-7]|(?=[ab\\]@]++$).|^$|\\1\\.\\(|\\Q*+[\\E";
    PCRELexer lexer = new PCRELexer(new ANTLRStringStream(src));
    PCREParser parser = new PCREParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.parse().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  }
}

您将看到对应于以下 AST 的 DOT 输出:

enter image description here

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ANTLR PCRE 语法到 JS 目标 的相关文章

  • 如何用js获取一个月的4个星期一?

    我正在构建一个图表 其中 x 轴应该是一个月的四个星期 我只想显示该月的四个星期一 我已经有了currentMonth和currentYear变量 而且我知道如何获取该月的第一天 我所需要的只是将一个月的四个星期一放入数组中 所有这些都在同
  • jqgrid 在编辑框中选择不正确的下拉选项值

    我正在使用表单编辑 表单中有两个选择框 一个选择框是国家 地区 另一个选择框是州 州选择框取决于所选的国家 地区 并将动态填充 例如 Country 美国 期权价值 1 英国 期权价值 2 美国的状态 阿拉巴马州 选项值 1 加利福尼亚州
  • javascript中文本区域限制每行的字符数

    我试图用 javascript 限制文本区域中每行的字符数 我在这里看到了一些例子 但并不完全符合我的要求 我写了一些东西 只有当你每次添加超过限制时才可以 换句话说 我每行有 10 个字符的限制 如果你总是输入至少 10 个字符就可以正常
  • 如何处理 d3 中 Beeswarm 图中的碰撞?

    我一直在玩这个例子here https gf neocities org co2bs co2bee html一会儿 我想做的是突出显示图中的单个节点 圆圈 通过使用边框使其变大 稍后我也想在其中添加文本或字母 目前 我已经圈了Bhutan图
  • 如何延迟 NavLink 的反应?

    Delay e gt e preventDefault setTimeout gt e unpreventDefault make this work 500 render
  • JavaScript:参数列表后缺少 )

    这个 JavaScript 产生一个错误 参数列表后缺少 在 firebug 中使用代码 我究竟做错了什么 功能d缺少左括号 answer after 不应该逃避 只需常规报价即可
  • jquery:如何检查div中的所有单选按钮是否被选中

    我的 html 看起来像这样 div div
  • Backbone.js 与 Google 地图 - 有关此问题和侦听器的问题

    我有一个为 Google Maps v3 创建的模块 我正在尝试将其转换为 Backbone js 视图构造函数 到目前为止 这是我的视图模块 我将解释代码后遇到的问题 pg views CreateMap Backbone View ex
  • 如何使用javascript隐藏div

    我想使用 Javascript 隐藏一个 div 下面是我的div div class ui dialog titlebar ui widget header ui corner all ui helper clearfix span cl
  • 设置股票数据 Highcharts xAxis 的格式

    我已经浏览了需要为 xAxis 属性设置的 Highcharts 选项来格式化时间标签 但没有运气了解这对于这种情况到底是如何工作的 我在白天 盘中 检索了股票的动态数据 我需要显示这些数据 因为检索的数据每天从 9 30 开始到 17 0
  • 将数字限制为段的最优雅的方法是什么?

    比方说x a and b是数字 我需要限制x到段的边界 a b 换句话说 我需要一个钳位功能 https math stackexchange com q 1336636 clamp x max a min x b 有人能想出一个更易读的版
  • 如何按值删除数组中的多个项目?

    我正在尝试做一个removeAll 函数 它将删除具有该特定值 而不是索引 的数组的所有元素 当我们对循环进行任何更改时 棘手的部分就出现了 索引往往会移动 使其很难像我们想要的那样工作 并且每次更改时都重新启动循环 这在大数组上效率非常低
  • Material.Angular.io mat-autocomplete [displayWith] 函数更新范围变量

    我遇到了一个问题 我可以在实例化 mat autocomplete 的组件控制器中访问本地声明的变量 我面临的问题是局部变量被困在这个范围内 我无法更新它们 有关更新 mat autocomplete 范围变量的任何想法或想法 最终我要做的
  • 我将 X Y Z 坐标转换为屏幕 X Y 坐标有什么问题吗?

    我正在制作 3D 空间中弹跳的球体的 HTML5 Canvas 演示 这非常简单 每个球都有 X Y 和 Z 坐标 然后 这些坐标将转换为我在此处阅读的屏幕 X 和 Y 坐标 http answers google com answers
  • Jquery获取每个div的子子div并将信息抓取到数组中

    我有一些看起来像这样的 html div div class sub main div div
  • select 元素是否具有标准值属性?

    这是一个简单的问题 但我找不到任何参考资料 所以就在这里 假设我有一个选择元素
  • backbone.js:视图中影响集合中不同模型的按钮

    我刚刚开始使用backbone js 到目前为止 我真的很喜欢它 我有这样的事情 ModelA ModelB ViewA ViewB ModelA 持有 ModelB 的集合 如何使用按钮构建模型 B 的视图 单击该按钮会更改集合中下一个
  • Skrollr 添加空白

    我已经尝试了一切 我在谷歌上阅读了 4 5 页试图找到适合我的修复程序 已经筋疲力尽了 即使我使用 skrollr 示例 我的问题仍然存在 不是说他们做错了什么 我知道我只是没有正确理解它 因此 我上传了一个演示 仅在移动设备上展示这个尴尬
  • 通过ajax执行后期操作时如何克服CORS重定向问题?

    我可以通过外部登录表单中的 post 方法类型提交表单来登录 roundcube 实例 托管在另一台服务器上 我收到此错误 通过 ajax 签名时 XMLHttpRequest 无法加载https 192 168 0 7 mail http
  • 在 Javascript 中创建数组

    我对 javascript 不太熟悉 并且在用 javascript 制作 2d 或者也许我可能需要 3d 数组时遇到了一些麻烦 我目前需要收集 2 条信息 一个 ID 和一个值 因此我创建了以下内容 var myArray var id

随机推荐

  • WPF TreeView 悬停时突出显示行

    目前 将鼠标悬停在 TreeView 中的标题上将突出显示该标题 我想将鼠标悬停在 TreeView 中行的任何部分上以突出显示整行 就像 Windows 资源管理器所做的那样 有人可以提供一个如何做到这一点的例子吗 您需要更改Contro
  • python如何更改嵌套列表中的元素[重复]

    这个问题在这里已经有答案了 我已经使用Python很多次了 今天我对一个简单的嵌套列表感到惊讶 如何更改列表中元素的值 gt gt gt l 0 0 10 gt gt gt l 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  • Matplotlib 在阶梯图中仅绘制水平线

    我正在使用 matplotlib 从数据框中绘制一些步骤函数 df s1 plot c b drawstyle steps post df s2 plot c b drawstyle steps post 结果看起来像 我希望只绘制水平线
  • 如何从 PowerShell 调用复杂的 COM 方法?

    是否可以使用命名参数从 PowerShell 调用 COM 方法 我正在使用的 COM 对象方法有数十个参数 object GridData DataFile xCol yCol zCol ExclusionFilter DupMethod
  • 使用 Break 和 continue 语句的 While 循环

    我是初学者 while 语句中的break 和 continue 语句用于什么 while break continue 这两个关键字可以循环使用来改变它的行为方式 Break 语句终止循环并继续执行下一个可执行语句 continue 语句
  • 如何删除仅包含 NA 的列?

    我有一个 data frame 其中包含一些具有所有 NA 值的列 如何从 data frame 中删除它们 我可以使用该功能吗 na omit 指定一些额外的参数 一种方法是 df colSums is na df nrow df 如果一
  • C# - For 循环和 lambda 表达式

    我的问题是为什么我在 lambda 表达式中使用迭代器变量会得到错误的结果 好吧 我已经关注了 SWeko 的回答here 但这对我不起作用 这是我的伪代码 List
  • Odoo 在树视图第二行记录上抛出 MissingError

    我如何解决此错误 Odoo 在单击 TreeView 第二行按钮时抛出 MissingError 第一个记录行有效 但我需要这样 对于树上的每个记录 下面的代码将能够传递上下文并打开适当的视图 请协助 这是按钮上调用的代码 api mult
  • 绘制彼此靠近的对象[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 我想在屏幕上绘制下图 sphere cylinder angle 45 cyli
  • datetimepicker getDate 返回 UTC 格式的日期/时间

    我碰巧读到了这篇文章日期时间选择器插件并发现它很有用 我使用此工具面临的问题是我无法获取 UTC 或其他格式的数据 时间格式 我的目的是至少获取 UTC 格式的日期 时间 starttime datetimepicker ampm true
  • FluentNHibernate:使用 NotFound.Ignore() 映射引用时的性能损失

    我使用 FluentNhibernate 并且当关联引用映射为 NHibernate 时 我看到 NHibernate 执行许多查询NotFound Ignore 由于我的遗留数据库的引用完整性有点糟糕 我想知道是否有解决方法或者是否有我可
    1. /

    我有这种 xml 在哪里some li tags没有 ol ul 因为它是父级 需要添加为父级 Example ul ol li
  • GCP云函数实例生命周期

    我在 AWS 环境中工作了 4 年 主要使用 Lambda 根据我的经验 我知道 Lambda 函数的 实例 将存活大约 2 小时 现在我要为一个使用 GCP 及其云功能的项目工作 是否有关于 CF 实例 的生存时间的信息 我通常需要知道这
  • 在线程内使用全局字典

    假设我有一个Dictionary
  • 将自定义对象绑定到 JMS MapMessage

    是否有一种标准方法可以将我自己的自定义对象添加到 Map 然后将其正确编组到 MapMessage 中 目前我收到无效的对象类型消息 我注意到WebSphere有一个解决方案 但我正在寻找一些不绑定到特定AS的东西 如果没有这样的方法 也许
  • 如何将库添加到 Eclipse 项目

    我正在尝试向 Eclipse Galileo 添加一个库 但没有成功 库 gdi32 lib gt 在 MinGW 中 libgdi32 a 无论我尝试什么 它都不起作用 谁能准确地告诉我要在哪里输入什么才能完成此任务 Details 我有
  • 输入表单属性可以指定多个表单ID吗?或不? [复制]

    这个问题在这里已经有答案了 w3学校指定 定义和用法 form属性指定一个or more形成元素所属的形式 在http www w3schools com tags att input form asp 和 提示 要引用多种形式 使用空格分
  • 如何用循环绑定多个数据帧?

    我有 105 个带有 xts zoo class 的数据框 我想将它们的第 6 列合并到一个数据框中 因此 我创建了一个包含所有数据框名称的数据框 以便将其与 for 函数一起使用 mydata lt AAL for i in 2 105
  • Amazon S3 和 jqueryfileupload 插件的图像上传性能问题

    我在亚马逊上还有另一个问题 它与文件上传有关 我正在使用jquery文件上传和amazon API上传文件到amazon S3 我已经成功上传了 但是涉及到一个技巧 我必须将图像存储在我的服务器上 然后使用以下命令将其从那里移动到 S3pu
  • ANTLR PCRE 语法到 JS 目标

    我正在尝试构建 Bart Kiers 的 ANTLR PCRE 语法 请参阅 http big o nl apps pcreparser pcre PCREParser html 到 JS 目标 我构建它的唯一方法是使用全局回溯和记忆 它生