$("div:empty") 深刻理解

本文详细介绍了jQuery中的一些关键选择器,包括如何选择包含特定文本的元素、如何选择非空的div元素等。通过实例展示了不同选择器的用法,帮助读者更好地理解和运用jQuery进行网页元素的选择。

$("div:has('.mini')")

$("div:empty") 

$("div:odd")

$("div:contains('di')")


div:~~~   意义: 选择什么?选择div,选择什么样的div,选择符合后面条件的div

如:

 :contains('di')    意义:选择什么? 什么都选择,条件: 选择所有包括di字符串的元素。。。包括body




选择非空的div

方法一

$("div:parent")   有子元素的都是非空的,,所有选择父元素parent

方法二

$("div:not(:empty)")


方法一常用,,更直接

实验内容: 可选择LL1分析法、算符优先分析法、LR分析法之一,实现如下表达式文法的语法分析器: (1)E→E+T | E-T | T (2)T→T*F | T/F | F (3)F→P^F | P (4)P→(E) | i 实验目的: 1.掌握语法分析的基本概念和基本方法; 2.正确理解LL1分析法、算符优先分析法、LR分析法的设计与使用方法。 实验要求: 1.按要求设计实现能识别上述文法所表示语言的语法分析器,并要求输出全部分析过程; 2.要求详细描述所选分析方法针对上述文法的分析表构造过程; 3.完成对所设计语法分析器的功能测试,并给出测试数据和实验结果; 4.为增加程序可读性,请在程序中进行适当注释说明; 5.按软件工程管理模式完成实验报告撰写工作,最后需要针对实验过程进行经验总结; 6.认真完成并按时提交实验报告。 ****** 实验报告内容详述 ****** 一、 需求分析 LL(1) 分析法是一种自顶向下的预测分析方法,它从左到右扫描输入符号,并基于当前非终结符和下一个输入符号唯一地选择产生式,从而无需回溯就能高效解析符合该文法的输入字符串。由于 LL(1) 文法要求消除左递归、去除公共左因子并且无二义性,因此可以构造出简单清晰的预测分析表,便于手写解析器实现并在编译器中快速集成。对于大多数编程语言的语法子集,通过适当的左递归消除和左因子提取,通常可将其文法转换为 LL(1) 形式,从而在保留语法结构的同时获得可预测的解析性能。 与 LR(0) 相比,LL(1) 分析表的构造过程更直观,且在遇到不符合预期的符号时能够立即报错并指明具体的产生式,因而更易于调试。 此外,LL(1) 分析器占用内存结构简单、运行效率高,非常适合在资源受限或对性能要求严格的编译器前端场景中使用。因此,在本次实验中选用 LL(1) 分析法,可以兼顾实现难度低、运行效率高和错误定位清晰等多重优势。 二、 实现方法 表驱动(非递归)LL(1) 与自顶向下的递归下降在实现风格和适用场景上各有优劣。递归下降直接把每条文法产生式写成一个函数,通过函数调用和回溯来识别输入,代码可读性和可维护性较好;而表驱动解析则将文法信息(FIRST/FOLLOW、预测分析表)预先计算好,运行阶段用统一的栈和查表逻辑驱动,具有更好的性能、扩展性和自动化生成的潜力。 递归下降每个非终结符对应一个函数,函数体中用 if/else 或 switch 根据下一个输入符号选择产生式,并递归调用其他函数来匹配右部符号。文法必须先消除左递归、左因子化,才能避免无限递归或回溯;手工处理较繁琐,但逻辑直观。 表驱动先通过算法(或手工)计算出 FIRST/FOLLOW 集,并据此构造预测分析表;运行时采用栈和map<pair<NonTerminal, Terminal>, Production>进行驱动。其中,分析表通过键值对映射实现 —— 以(非终结符, 终结符)作为键,对应产生式作为值,等价于二维数组的查表逻辑。该结构支持动态查询,无需预先定义固定维度,在文法扩展时更具灵活性,同时保持表驱动解析的高效性。 三、 实验步骤 1、 针对本次实验的表达式语法,需要先做消除左递归处理。改写文法消除左递归后得到: E → T E' E' → + T E' | - T E' | ε T → F T' T' → * F T' | / F T' | ε F → P F' F' → ^ F | ε P → ( E ) | i 2、 接下来进行FIRST和 FOLLOW的计算 FIRST集计算:  若产生式以终结符开头,直接加入FIRST集  若产生式以非终结符开头,递归加入其FIRST集  若产生式右部包含ε,需考虑后续符号的FIRST集 FIRST(E) = FIRST(T) = FIRST(F) = FIRST(P) = { (, i } FIRST(E') = { +, -, ε } FIRST(T') = { *, /, ε } FIRST(F') = { ^, ε } FOLLOW集计算:  对每个非终结符,追踪其在产生式中的位置,继承后续符号的FIRST集。  若某非终结符位于产生式末尾,继承左部非终结符的FOLLOW集。 FOLLOW(E) = { ), $ } FOLLOW(E') = { ), $ } FOLLOW(T) = { +, -, ), $ } FOLLOW(T') = { +, -, ), $ } FOLLOW(F) = { *, /, +, -, ), $ } FOLLOW(F') = { *, /, +, -, ), $ } FOLLOW(P) = { ^, *, /, +, -, ), $ } 3、 通过FIRST和FOLLOW集确定每个产生式的选择条件,构造预测分析表: 表格 1 预测分析表 非终结符 输入符号 产生式 E (,i E → T E' E' + E' → + T E' E' - E' → - T E' E' ), $ E' → ε T ( T → F T' T i T → F T' T' * T' → * F T' T' / T' → / F T' T' +, -, ), $ T' → ε F ( F → P F' F i F → P F' F' ^ F' → ^ F F' *, /, +, -, ), $ F' → ε P ( P → ( E ) P i P → i 所有产生式的SELECT集互不相交,满足LL1文法要求。 四、 实现设计 1. 总体思路 本实验采用表驱动法实现 LL1语法分析器,核心思想是将文法的预测分析表(预测分析矩阵)预先构建好,然后在解析过程中仅通过不断查表和栈操作来驱动分析,避免了递归调用的复杂性,并且能够清晰地输出每一步匹配或规约的过程。 1) 初始化栈:[ $, E ] 2) 读取输入符号流(自动添加末尾 $) 3) 循环直到栈为空: a. 栈顶为终结符:  与输入符号匹配 → 弹出栈顶,读取下一个输入。  不匹配 → 报错。 b. 栈顶为非终结符:  查表获取产生式 → 弹出栈顶,逆序压入右部符号。  表中无条目 → 报错。 4) 栈空且输入为 $ → 接受;否则 → 报错。 2. 输出分析过程 1) 每一步输出当前步骤编号、栈内容、剩余输入串、动作描述(匹配或应用哪个产生式),例: 栈: [E'] [T] [$] 输入: i + i * i 应用: E -> T E' (产生式 1) 2) 遇错时打印错误类型及位置。 3. 关键数据结构 1) 符号与产生式定义 // 定义终结符枚举类型 enum Terminal { PLUS,MINUS,MULT,DIV,POW,LPAREN,RPAREN,I,END }; // 定义非终结符枚举类型 enum NonTerminal { E, E_PRIME, T, T_PRIME, F, F_PRIME, P }; // 符号结构体(终结符或非终结符) struct Symbol { bool isTerminal; int value; Symbol(bool isTerminal, int value) : isTerminal(isTerminal), value(value) {} }; // 产生式结构体 struct Production { NonTerminal lhs; vector<Symbol> rhs; Production() = default; Production(NonTerminal lhs, vector<Symbol> rhs) : lhs(lhs), rhs(rhs) {} }; 2) 预测分析表 通过 map<pair<NonTerminal, Terminal>, Production> 实现动态查表: map<pair<NonTerminal, Terminal>, Production> table; 3) 分析栈 基于 std::stack<Symbol>,初始时从栈底依次压入 $(结束符)和起始符号 E 五、 测试与结果 1、 测试数据 序号 输入串 预期结果 说明 1 i 接受 单个标识符,验证基本符号匹配(对应产生式 P→i) 2 i+i 接受 加法表达式,验证E→E+T产生式及运算符优先级 3 i*i*i 接受 乘法连锁,验证T→T*F产生式及左结合性 4 i^i^i 接受 幂运算连锁,验证F→P^F产生式(右结合性:i^(i^i)) 5 i+i*i 接受 混合运算,验证优先级(*高于+) 6 (i+i)*i 接受 括号改变优先级,验证P→(E)产生式 7 i+(i*i) 接受 嵌套括号,验证多层表达式解析 8 i-i/i 接受 减法与除法混合,验证E→E-T和T→T/F产生式 9 i^(i+i) 接受 幂运算与加法混合,验证F→P^F和E→E+T的优先级 10 ((i)) 接受 多重括号包裹,验证括号匹配逻辑 11 i+*i 错误 非法运算符连用(+*),验证错误处理机制 12 i+(i*i 错误 未闭合左括号,验证括号匹配错误检测 13 i+i) 错误 多余右括号,验证语法错误定位 14 i^+i 错误 运算符位置错误(^后无操作数),验证表达式结构合法性 15 i i 错误 标识符间无运算符,验证表达式结构完整性 2、 运行结果 1) 2) 3) 4) 5) 6) 7) 8) 9) 10) 11) 12) 13) 14) 15) 六、 经验与体会 通过本次实验,我深刻理解了 LL1 分析法作为自顶向下语法分析方法的核心原理。从理论到实践的过程中,我认识到 LL1 文法需满足消除左递归、提取公共左因子及无二义性的要求,而这些步骤并非孤立 —— 例如,消除左递归后的文法结构直接影响 FIRST/FOLLOW 集的计算,进而决定预测分析表的构造。以表达式文法E→E+T改写为E→T E'为例,这种结构调整使递归变为迭代,不仅消除了语法分析时的无限循环风险,还让 FIRST 集的推导更清晰(如FIRST(E')包含+、-和ε)。这让我明白,理论中的形式化定义需与工程实践结合,才能真正掌握其本质。 七、 源代码 #include <iostream> #include <vector> #include <stack> #include <map> #include <string> using namespace std; // 定义终结符枚举类型 enum Terminal { PLUS, // + MINUS, // - MULT, // * DIV, // / POW, // ^ LPAREN, // ( RPAREN, // ) I, // i END // $ }; // 定义非终结符枚举类型 enum NonTerminal { E, // E E_PRIME, // E' T, // T T_PRIME, // T' F, // F F_PRIME, // F' P // P }; // 符号结构体(终结符或非终结符) struct Symbol { bool isTerminal; int value; Symbol(bool isTerminal, int value) : isTerminal(isTerminal), value(value) {} }; // 产生式结构体 struct Production { NonTerminal lhs; vector<Symbol> rhs; Production() = default; Production(NonTerminal lhs, vector<Symbol> rhs) : lhs(lhs), rhs(rhs) {} }; // 词法分析器:将输入字符串转换为终结符序列 vector<Terminal> tokenize(const string& input) { vector<Terminal> tokens; for (char c : input) { switch (c) { case '+': tokens.push_back(PLUS); break; case '-': tokens.push_back(MINUS); break; case '*': tokens.push_back(MULT); break; case '/': tokens.push_back(DIV); break; case '^': tokens.push_back(POW); break; case '(': tokens.push_back(LPAREN); break; case ')': tokens.push_back(RPAREN); break; case 'i': tokens.push_back(I); break; default: throw runtime_error("非法字符: " + string(1, c)); } } tokens.push_back(END); // 添加结束符$ return tokens; } // 初始化LL(1)分析表 map<pair<NonTerminal, Terminal>, Production> buildParseTable() { map<pair<NonTerminal, Terminal>, Production> table; // 产生式定义 Production E_T_Eprime(E, {Symbol(false, T), Symbol(false, E_PRIME)}); Production Eprime_plus_T_Eprime(E_PRIME, {Symbol(true, PLUS), Symbol(false, T), Symbol(false, E_PRIME)}); Production Eprime_minus_T_Eprime(E_PRIME, {Symbol(true, MINUS), Symbol(false, T), Symbol(false, E_PRIME)}); Production Eprime_epsilon(E_PRIME, {}); Production T_F_Tprime(T, {Symbol(false, F), Symbol(false, T_PRIME)}); Production Tprime_mult_F_Tprime(T_PRIME, {Symbol(true, MULT), Symbol(false, F), Symbol(false, T_PRIME)}); Production Tprime_div_F_Tprime(T_PRIME, {Symbol(true, DIV), Symbol(false, F), Symbol(false, T_PRIME)}); Production Tprime_epsilon(T_PRIME, {}); Production F_P_Fprime(F, {Symbol(false, P), Symbol(false, F_PRIME)}); Production Fprime_pow_F(F_PRIME, {Symbol(true, POW), Symbol(false, F)}); Production Fprime_epsilon(F_PRIME, {}); Production P_lparen_E_rparen(P, {Symbol(true, LPAREN), Symbol(false, E), Symbol(true, RPAREN)}); Production P_i(P, {Symbol(true, I)}); // 填充分析表 // E → T E' table[{E, LPAREN}] = E_T_Eprime; table[{E, I}] = E_T_Eprime; // E' → + T E' | - T E' | ε table[{E_PRIME, PLUS}] = Eprime_plus_T_Eprime; table[{E_PRIME, MINUS}] = Eprime_minus_T_Eprime; table[{E_PRIME, RPAREN}] = Eprime_epsilon; table[{E_PRIME, END}] = Eprime_epsilon; // T → F T' table[{T, LPAREN}] = T_F_Tprime; table[{T, I}] = T_F_Tprime; // T' → * F T' | / F T' | ε table[{T_PRIME, MULT}] = Tprime_mult_F_Tprime; table[{T_PRIME, DIV}] = Tprime_div_F_Tprime; table[{T_PRIME, PLUS}] = Tprime_epsilon; table[{T_PRIME, MINUS}] = Tprime_epsilon; table[{T_PRIME, RPAREN}] = Tprime_epsilon; table[{T_PRIME, END}] = Tprime_epsilon; // F → P F' table[{F, LPAREN}] = F_P_Fprime; table[{F, I}] = F_P_Fprime; // F' → ^ F | ε table[{F_PRIME, POW}] = Fprime_pow_F; table[{F_PRIME, MULT}] = Fprime_epsilon; table[{F_PRIME, DIV}] = Fprime_epsilon; table[{F_PRIME, PLUS}] = Fprime_epsilon; table[{F_PRIME, MINUS}] = Fprime_epsilon; table[{F_PRIME, RPAREN}] = Fprime_epsilon; table[{F_PRIME, END}] = Fprime_epsilon; // P → ( E ) | i table[{P, LPAREN}] = P_lparen_E_rparen; table[{P, I}] = P_i; return table; } // 获取符号名称的辅助函数 string getSymbolName(Symbol s) { if (s.isTerminal) { switch (s.value) { case PLUS: return "+"; case MINUS: return "-"; case MULT: return "*"; case DIV: return "/"; case POW: return "^"; case LPAREN: return "("; case RPAREN: return ")"; case I: return "i"; case END: return "$"; default: return "?"; } } else { switch (s.value) { case E: return "E"; case E_PRIME: return "E'"; case T: return "T"; case T_PRIME: return "T'"; case F: return "F"; case F_PRIME: return "F'"; case P: return "P"; default: return "?"; } } } // 语法分析函数 void parse(const vector<Terminal>& tokens) { auto table = buildParseTable(); stack<Symbol> parseStack; parseStack.push(Symbol(true, END)); // 结束符$ parseStack.push(Symbol(false, E)); // 初始非终结符E size_t index = 0; Terminal currentToken = tokens[index]; cout << "步骤\t栈内容\t\t输入\t\t动作" << endl; int step = 1; while (!parseStack.empty()) { // 打印当前状态 string stackStr; stack<Symbol> temp = parseStack; while (!temp.empty()) { stackStr = getSymbolName(temp.top()) + " " + stackStr; temp.pop(); } string inputStr; for (size_t i = index; i < tokens.size(); ++i) { inputStr += getSymbolName(Symbol(true, tokens[i])) + " "; } Symbol top = parseStack.top(); parseStack.pop(); // 栈顶为终结符 if (top.isTerminal) { if (top.value == currentToken) { // 匹配成功,移动到下一个输入符号 cout << step++ << "\t" << stackStr << "\t\t" << inputStr << "\t匹配 " << getSymbolName(top) << endl; if (currentToken != END) { currentToken = tokens[++index]; } } else { // 错误处理 cerr << "错误:期望 " << getSymbolName(top) << " 但遇到 " << getSymbolName(Symbol(true, currentToken)) << endl; return; } } else { // 栈顶为非终结符 auto key = pair<NonTerminal, Terminal>{(NonTerminal)top.value, currentToken}; if (table.find(key) != table.end()) { Production prod = table[key]; // 逆序压栈 vector<Symbol> rhs = prod.rhs; string productionStr = getSymbolName(top) + " -> "; for (const auto& s : rhs) { productionStr += getSymbolName(s) + " "; } if (rhs.empty()) productionStr += "ε"; cout << step++ << "\t" << stackStr << "\t\t" << inputStr << "\t应用 " << productionStr << endl; for (auto it = rhs.rbegin(); it != rhs.rend(); ++it) { parseStack.push(*it); } } else { cerr << "错误:在 " << getSymbolName(top) << " 处无法处理输入符号 " << getSymbolName(Symbol(true, currentToken)) << endl; return; } } } cout << "分析成功!" << endl; } int main() { string input; cout << "请输入表达式: "; getline(cin, input); try { vector<Terminal> tokens = tokenize(input); parse(tokens); } catch (const exception& e) { cerr << "错误: " << e.what() << endl; } return 0; } 在上述实验基础上,实现语法制导翻译功能,输出翻译后所得四元式序列; 2.要求详细描述所选分析方法进行制导翻译的设计过程; 3.完成对所设计分析器的功能测试,并给出测试数据和实验结果; 4.为增加程序可读性,请在程序中进行适当注释说明; 5.整理上机步骤,总结经验和体会,认真完成并按时提交实验报告。
最新发布
05-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值