自己动手写编译器:使用 PDA 实现增强和属性语法的解析

在前面章节中我们了解了增强语法和属性语法,特别是看到了这两种语法的结合体,本节我们看看如何使用前面我们说过的自顶向下自动机来实现这两种语法结合体的解析,这里使用的方法也是成熟编译器常用的一种语法解析算法。

首先我们先给出上一节给出的混合语法:

stmt -> epsilon | {t=newName()} expr_(t) SEMI stmt
expr_(t) -> term_(t) expr_prime_(t)
expr_prime_(t) -> PLUS {t2 = newName()} term_(t2) {print(%s+=%s\n",t,t2) freenName(t2)} expr_prime_(t) | epsilon
term_(t) -> factor term_prime
term_prime_(t) -> MUL {t2 = newName()} factor_(t2) {print("%s*=%s\n",t,t2) freeName(t2)} term_prime_(t)
factor_(t) -> NUM {print("%s=%s\n",t, lexeme)} | LEFT_PAREN expr_(t) RIGHT_PAREN

在前面我们谈到 PDA 算法时说过,我们需要使用一个堆栈来存储状态机的当前状态,堆栈顶部的节点对应状态机当前所在节点,拿到当前节点和当前的输入后,我们到一个行动表里查询应该采取的行动。对应当前例子而言,状态机的节点就对应到语法中的终结符,非终结符,和行动(例如{t2=newName()}这种),对于当前语法而言,行动表对应的行动就是将对应表达式右边的符号逆向压入堆栈。

举个例子,假设当前堆栈顶部的元素是非终结符 stmt,如果此时输入对应的标签是 EOF,那么我们就采用 stmt -> epsilon 这个规则,于是就将 stmt 出栈,然后将 epsilon 入栈,于是此时堆栈顶部的节点就是 epsilon,对应该状态点,行动表对应的动作就是什么动作都不做。如果顶部元素为 stmt,同时当前输入不为空,那么对应的操作就是将语法右边的符号压入堆栈,此时我们要从最右边的符号开始压入,根据表达式 stmt -> {t=newName()} expr_(t) SEMI stmt,我们首先将 stmt 压入,然后压入 SEMI,接着是 expr(注意这里只压入符号,对于符号附带的属性我们需要另外处理),接着是{t=newName()}

如果当前堆栈顶部的元素是一个终结符,例如 NUM,那么行动表对应的动作就是检测当前输入的元素对应标签是否为 NUM,如果不是那么报告语法错误,识别过程结束,如果是,那么将当前终结符 NUM 弹出堆栈,然后根据当前堆栈顶部元素来采取相应动作。

现在还有一个问题在于如何处理语法符号所附带的属性。我们看下面这个语法:

expr_prime_(t) -> PLUS {t2 = newName()} term_(t2) {print(%s+=%s\n",t,t2)}

这里右边的符号 term 自己附带了一个属性,而这个属性由{t2=newName()}这个动作创建,同时{print(“%s+=%s\n”,t,t2)} 使用了两个属性,一个属性 t 来自与箭头左边 expr_prime 对应的属性,而 t2 对应 term 符号附带的属性,现在问题是当我们要执行这个操作时,我们如何获取这两个属性呢?

解决办法是再创建一个新的堆栈叫属性堆栈,堆栈中的元素对应如下的数据结构或者结构体:

type Attribute struct {
left string
right string}

我们每次将一个符号压入符号堆栈时,我们就创建上面的一个结构体实例,将 left, right 初始化为空字符串,然后将该实例也压入属性堆栈。其中 left 对应的就是箭头左边符号附带的属性,right 对应的是当前符号自身附带的属性,如果符号自己没有附带属性,那么 right 就保留为空字符串。

下面我们先给出解析算法的步骤说明,你看了可能会感觉懵逼,不用担心,只要我们使用代码来实现你就会立马明白:
算法数据结构:
1, 一个解析堆栈,堆栈元素为整形数值,用来代表语法解析中的符号
2, 一个属性堆栈,堆栈元素为结构体 Attribute
3, 一个字符串类型的变量 lhs,
初始化:
将语法解析的初始符号压入解析堆栈(对应上面例子就是 stmt)
初始化一个 Attribute 结构体实例,将其两个字段 left,right 初始化为空字符串然后压入属性堆栈。
执行如下步骤:
0:如果当前堆栈为空,解析结束
1,如果当前堆栈顶部元素是一个动作节点(例如 {t2 = newName()}),那么执行其对应操作,然后将其弹出栈顶。
2,如果当前栈顶元素是非终结符则执行如下步骤:
a,将变量 lhs 的值设置为当前属性堆栈顶部元素结构体中的 right 字段
b,将元素弹出堆栈,然后将它在解析式右边的符号,从最右边开始依次压入堆栈。每压入一个元素今日堆栈,那么就创建一个 Attribute 结构体实例,将其left,right 两个字段都初始化为 lhs,然后压入属性堆栈
c,跳转到步骤 0 重新执行。
3,如果堆栈顶部的元素是终结符,判断当前输入对应的标签跟终结符相匹配,如果不匹配则报错退出,如果匹配则将符号弹出堆栈,然后跳转到步骤 0

由于我们在属性堆栈压入了多个 Attribute 结构体实例,在语法解析过程中我们就需要引用属性堆栈中某个位置的元素,因此我们使用特定的符号来表示对特定属性对象的引用,我们使用符号"$$“表示引用当前属性堆栈栈顶元素的 left 字段,”$0"表示引用距离栈顶元素偏移 0 个位置的元素的 right 字段,“$1"表示引用距离栈顶偏移 1 个位置元素的 right 字段,”$2"表示引用距离栈顶偏移 2 个位置的元素的 right 字段,我们看个具体例子:

expr_prime -> PLUS {$1=$2=newName()} term {printf("%s+=%s\n", $$, $0); freeName($0)}

如果我们使用 valueStack 来表示属性堆栈的话,那么在执行动作{$1=$2=newName()},在代码上实现时就相当与:

t := newName()
stackTop = len(valueStack)-1
valueStack[stackTop-1].right = t //对应$1
valueStack[stackTop-2].right = t //对应$2

同理行动{printf(“%s+=%s\n”, $$,$0)}在代码实现上相当于:

stackTop = len(valueStack)-1
fmt.Printf("%s+=%s\n", valueStack[stackTop].left, valueStack[stackTop].right)

最后我们把前面的语法规则做一些修改以便后面代码实现:

stmt -> epsilon | {$1=$2=newName()} expr {freeName($0)} SEMI stmt
expr -> term expr_prime
expr_prime -> PLUS {$1=$2=newName()} term {printf("%s+=%s\n", $$, $0); freeName($0);} expr_prime| epsilon
term -> factor term_prime
term_prime -> MUL {$1=$2=newName();} factor {printf("%s *= %s\n", $$, $0); freeName($0);} term_prime | epsilon
factor -> NUM {printf("%s=%s\n", $$, lexer.lexeme)}
factor -> LEFT_PAREN expr RIGHT_PAREN 

注意这里的$1, $2, $0 指示我们如何引用属性堆栈上的 Attribute 结构体对象,此外为了方便代码实现,我们把表达式中的各个元素在数值上做一些映射,把所有终结符映射到 1-255 这个区间的数值,于是有:
LEFT_PAREN -> 5, NUM -> 4, PLUS -> 2, RIGHT_PAREN -> 6, SEMI -> 1, MUL -> 3, EPSILON -> 255, EOF ->0
非终结符映射到数值区间 256->511:
expr -> 257, expr_prime -> 259, factor->260, stmt->256, term->258, term_prime->261
行动映射到数值区间 512以上:
{op(‘+’)} -> 512, {op(‘*’)}->513, {create_tmp(lexer.lexeme)}-> 514, {assign_name}->515,
{free_name}->516

这里 {op(‘+’)} 对应{printf(“%s+=%s\n”, KaTeX parse error: Can't use function '$' in math mode at position 3: , $̲0); freeName($0…, $0); freeName($0);}
{create_temp(lexer.lexeme)}对应{printf(“%s=%s\n”, $$, lexer.lexeme)}
{assign_name}对应{$1=$2=newName()}
{free_name}对应{freeName($0)}

接下来我们看看如何实现行动查询表,首先我们会在代码中构造一个映射表yy_pushtab,他对应我们的语法表达式,其内如如下:

//注意队列中的数值是表达式右边部分符号的逆向排列
yy_pushtab[0]=[255] //stmt-> epsilon
yy_pushtab[1]=[256, 1, 516,257,515] //stmt-> {$1=$2=newName()} expr {freeName($0)} SEMI stmt
yy_pushtab[2]=[259, 258] //expr -> term expr_prime
yy_pushtab[3]=[259, 512, 258, 515, 2] //expr_prime -> PLUS {assign_name} term {op('+')} expr_prime
yy_pushtab[4] = [255] //expr_prime -> epsilon
yy_pushtab[5] = [261, 260]//term -> factor term_prime
yy_pushtab[6]=[261, 513, 260, 515, 3]//MUL {assign_name} factor {op('*')} term_prime
yy_pushtab[7] = [255] //term_prime -> epsilon
yy_pushtab[8] =[514, 4] //factor ->num {create_tmp(lexer.lexeme)}
yy_pushtab[9] = [6, 257, 5] //factor -> LEFT_PAREN expr RIGHT_PAREN

接下来我们设置动作查询表yyd,也就给定当前节点和当前输入,状态机应该采取什么动作:

请添加图片描述

上面行动查询表的意思是,如果当前状态节点值为 stmt,如果当前输入是 EOF,那么就将 yy_pushtab[0]对应的数值压入解析堆栈,如果当前输入是 SEMI,由于表中数值是-1,因此表示解析出错,其他的依次类推。这里你是否有疑问,表中元素的取值是如何确定的?例如我们怎么知道 yyd[stmt][SEMI] 就应该等于-1,而 yyd[term][LP]就应该取值 5?个中原因我们还需要在后续章节中对相应的概念和算法进行说明,这里我们暂时放一放。

下面我们看看具体代码实现,新建一个目录名为 llma_parser,在里面添加文件 llma_parser.go,添加代码如下:

package llama_parserimport ("fmt""lexer"
)type Attribute struct {left  stringright string
}const (ERROR   = -1EOF     = 0SEMI    = 2PLUS    = 3MUL     = 4NUM     = 5LP      = 6RP      = 7EPSILON = 255EXPR       = 257EXPR_PRIME = 259FACTOR     = 260STMT       = 256TERM       = 258TERM_PRIME = 261OP_PLUS     = 512OP_MUL      = 513CREATE_TMP  = 514ASSIGN_NAME = 515FREE_NAME   = 516
)type ActionQuery struct {state inttoken lexer.Tag
}type LLAMAParser struct {parseStack     []intattributeStack []Attributeyy_pushtab     [][]intyyd            map[ActionQuery]intparserLexer    lexer.LexerregisterNames  []string//存储当前已分配寄存器的名字regiserStack []string//当前可用寄存器名字的下标registerNameIdx intreverseToken []lexer.Token
}func initYyPushTab(parser *LLAMAParser) {// stmt -> epsilonparser.yy_pushtab = append(parser.yy_pushtab, []int{EPSILON})// stmt -> {assign_name} expr {free_name} semi stmtparser.yy_pushtab = append(parser.yy_pushtab, []int{STMT, SEMI, FREE_NAME, EXPR, ASSIGN_NAME})// expr -> term expr_primeparser.yy_pushtab = append(parser.yy_pushtab, []int{EXPR_PRIME, TERM})// expr_prime -> PLUS {assign_name} term {op('+')} expr_primeparser.yy_pushtab = append(parser.yy_pushtab, []int{EXPR_PRIME, OP_PLUS, TERM, ASSIGN_NAME, PLUS})// expr_prime -> epsilonparser.yy_pushtab = append(parser.yy_pushtab, []int{EPSILON})// term -> factor term_primeparser.yy_pushtab = append(parser.yy_pushtab, []int{TERM_PRIME, FACTOR})// term_prime -> MUL {assign_name} factor {op('*')} term_primeparser.yy_pushtab = append(parser.yy_pushtab, []int{TERM_PRIME, OP_MUL, FACTOR, ASSIGN_NAME, MUL})// term_prime -> epsilonparser.yy_pushtab = append(parser.yy_pushtab, []int{EPSILON})// factor -> NUM {create_tmp}parser.yy_pushtab = append(parser.yy_pushtab, []int{CREATE_TMP, NUM})// factor -> LP EXPR RPparser.yy_pushtab = append(parser.yy_pushtab, []int{RP, EXPR, LP})
}func initYYDRow(parser *LLAMAParser, state int,tags []lexer.Tag, val int) {for _, tag := range tags {parser.yyd[ActionQuery{state: state,token: tag,}] = val}
}func initActionMap(parser *LLAMAParser) {//设置 yyd 表,后面我们能自动生成,这里我们先手动设置initYYDRow(parser, STMT,[]lexer.Tag{lexer.SEMI, lexer.PLUS,lexer.MUL, lexer.RIGHT_BRACKET}, -1)initYYDRow(parser, STMT, []lexer.Tag{lexer.EOF}, 0)initYYDRow(parser, STMT, []lexer.Tag{lexer.NUM, lexer.LEFT_BRACKET}, 1)initYYDRow(parser, EXPR, []lexer.Tag{lexer.EOF, lexer.SEMI, lexer.PLUS,lexer.MUL, lexer.RIGHT_BRACKET}, -1)initYYDRow(parser, EXPR, []lexer.Tag{lexer.NUM, lexer.LEFT_BRACKET}, 2)initYYDRow(parser, TERM, []lexer.Tag{lexer.EOF, lexer.SEMI, lexer.PLUS,lexer.MUL, lexer.RIGHT_BRACKET}, -1)initYYDRow(parser, TERM, []lexer.Tag{lexer.NUM, lexer.LEFT_BRACKET}, 5)initYYDRow(parser, EXPR_PRIME, []lexer.Tag{lexer.EOF,lexer.MUL, lexer.NUM, lexer.LEFT_BRACKET}, -1)initYYDRow(parser, EXPR_PRIME, []lexer.Tag{lexer.SEMI,lexer.RIGHT_BRACKET}, 4)initYYDRow(parser, EXPR_PRIME, []lexer.Tag{lexer.PLUS}, 3)initYYDRow(parser, FACTOR, []lexer.Tag{lexer.EOF,lexer.SEMI, lexer.PLUS, lexer.MUL, lexer.RIGHT_BRACKET}, -1)initYYDRow(parser, FACTOR, []lexer.Tag{lexer.NUM}, 8)initYYDRow(parser, FACTOR, []lexer.Tag{lexer.LEFT_BRACKET}, 9)initYYDRow(parser, TERM_PRIME, []lexer.Tag{lexer.EOF,lexer.MUL, lexer.NUM, lexer.MUL, lexer.LEFT_BRACKET}, -1)initYYDRow(parser, TERM_PRIME, []lexer.Tag{lexer.MUL}, 6)initYYDRow(parser, TERM_PRIME, []lexer.Tag{lexer.SEMI,lexer.PLUS, lexer.RIGHT_BRACKET}, 7)
}func NewLLAMAParser(parserLexer lexer.Lexer) *LLAMAParser {parser := &LLAMAParser{parseStack:      make([]int, 0),attributeStack:  make([]Attribute, 0),yy_pushtab:      make([][]int, 0),parserLexer:     parserLexer,yyd:             make(map[ActionQuery]int),registerNames:   []string{"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"},regiserStack:    make([]string, 0),registerNameIdx: 0,reverseToken:    make([]lexer.Token, 0),}initYyPushTab(parser)initActionMap(parser)parser.parseStack = append(parser.parseStack, STMT)parser.attributeStack = append(parser.attributeStack, Attribute{left:  "",right: "",})return parser
}func (l *LLAMAParser) get(state int, tag lexer.Tag) int {return l.yyd[ActionQuery{state: state,token: tag,}]
}func (l *LLAMAParser) newName() string {//返回一个寄存器的名字if l.registerNameIdx >= len(l.registerNames) {//没有寄存器可用panic("register name running out")}name := l.registerNames[l.registerNameIdx]l.registerNameIdx += 1return name
}func (l *LLAMAParser) putbackName(name string) {//释放当前寄存器名字if l.registerNameIdx > len(l.registerNames) {panic("register name index out of bound")}if l.registerNameIdx == 0 {panic("register name is full")}l.registerNameIdx -= 1l.registerNames[l.registerNameIdx] = name
}func (l *LLAMAParser) assignName() {//{$1=$2 = newName}stackTop := len(l.attributeStack) - 1t := l.newName()//$1 相当于 stackTop - 1, $2 相当于 stackTop - 2l.attributeStack[stackTop-1].right = tl.attributeStack[stackTop-2].right = t
}func (l *LLAMAParser) freeName() {//{freeName($0)}stackTop := len(l.attributeStack) - 1//$0 对应栈顶元素l.putbackName(l.attributeStack[stackTop].right)
}func (l *LLAMAParser) op(action string) {//$$对应栈顶元素的 left 字段//$0 对应栈顶元素的 right 字段stackTop := len(l.attributeStack) - 1fmt.Printf("%s %s= %s\n", l.attributeStack[stackTop].left,action, l.attributeStack[stackTop].right)l.freeName()
}func (l *LLAMAParser) createTmp() {//$$ 对应栈顶元素的 left 字段stackTop := len(l.attributeStack) - 1fmt.Printf("%s = %s\n", l.attributeStack[stackTop].left,l.parserLexer.Lexeme)
}func (l *LLAMAParser) putbackToken(token lexer.Token) {l.reverseToken = append(l.reverseToken, token)
}func (l *LLAMAParser) getToken() lexer.Token {//先看看有没有上次退回去的 tokenif len(l.reverseToken) > 0 {token := l.reverseToken[len(l.reverseToken)-1]l.reverseToken = l.reverseToken[0 : len(l.reverseToken)-1]return token}token, err := l.parserLexer.Scan()if err != nil && token.Tag != lexer.EOF {sErr := fmt.Sprintf("get token with err:%s\n", err)panic(sErr)}return token
}func (l *LLAMAParser) match(tag lexer.Tag) {token := l.getToken()if token.Tag != tag {panic("terminal symbol no match")}
}func (l *LLAMAParser) takeAction(action int, rightOnTop string) {actions := l.yy_pushtab[action]for _, val := range actions {l.parseStack = append(l.parseStack, val)l.attributeStack = append(l.attributeStack, Attribute{left:  rightOnTop,right: rightOnTop,})}
}func (l *LLAMAParser) isAction(symbol int) bool {return symbol >= 512
}/*
stmt -> epsilon | {$1=$2=newName()} expr {freeName($0)} SEMI stmt
expr -> term expr_prime
expr_prime -> PLUS {$1=$2=newName()} term {printf("%s+=%s\n", $$, $0); freeName($0);} expr_prime| epsilon
term -> factor term_prime
term_prime -> MUL {$1=$2=newName();} factor {printf("%s *= %s\n", $$, $0); freeName($0);} term_prime | epsilon
factor -> NUM {printf("%s=%s\n", $$, lexer.lexeme)}*/func (l *LLAMAParser) Parse() {for len(l.parseStack) != 0 {symbol := l.parseStack[len(l.parseStack)-1]//顶部堆栈元素相当于表达式箭头左边的非终结符 sl.parseStack = l.parseStack[0 : len(l.parseStack)-1]rightOnTop := l.attributeStack[len(l.attributeStack)-1].rightif l.isAction(symbol) != true {l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]}switch symbol {//匹配行动case ASSIGN_NAME:l.assignName()l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]case CREATE_TMP:l.createTmp()l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]case FREE_NAME:l.freeName()l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]case OP_PLUS:l.op("+")l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]case OP_MUL:l.op("*")l.attributeStack = l.attributeStack[0 : len(l.attributeStack)-1]//匹配终结符case EOF:l.match(lexer.EOF)case NUM:l.match(lexer.NUM)case SEMI:l.match(lexer.SEMI)case PLUS:l.match(lexer.PLUS)case MUL:l.match(lexer.MUL)case LP:l.match(lexer.LEFT_BRACKET)case RP:l.match(lexer.RIGHT_BRACKET)//匹配非终结符case STMT:fallthroughcase EXPR:fallthroughcase EXPR_PRIME:fallthroughcase TERM:fallthroughcase TERM_PRIME:fallthroughcase FACTOR:token := l.getToken()l.putbackToken(token)action := l.get(symbol, token.Tag)if action == -1 {panic("parse error, not action for given symbol and input")}l.takeAction(action, rightOnTop)}}
}

我们细说一下如上代码实现,首先需要说明的是上面代码在后续章节中会通过自动化的方式生成,因此我们这里先通过繁琐的手动方式做一遍。在代码实现中我们先定义结构体 Attribute 作为传递语法参数的对象。

函数initYyPushTab用于初始化要压入解析堆栈的符号,它本质上是将语法解析式右边的符号通过逆向的方式存放成一个队列。此外需要注意initYYDRow,和initActionMap两个函数,他们用于初始化行动查询表,注意看前面行动查询表,它每一行中有多列的内容有多种重复,因此initYYDRow通过循环的方式来设置表中一行的内容。

在构造函数NewLLAMAParser中,我们还初始化两个堆栈,一个是 parseStack,它对应解析堆栈,另一个是 attributeStack,它是属性堆栈。其他的函数例如 get, newName, assignName, freeName, createTmp, putbackToken, getToken 等实现跟我们前面的章节一样,isAction用于判断当前节点是否对应行动,它的判断依据就是当前节点数值是否大于 512。

在解析函数Parse中它的基本逻辑为,首先判断当前解析堆栈是否为空,如果为空,那么解析结束。如果不空,那么取出当前栈顶元素,同时也取出属性堆栈顶部元素的 right 字段。这里需要注意的是如果当前解析堆栈顶部元素不是行动,那么我们可以直接将属性堆栈顶部元素弹出,因为解析过程用不上,但如果当前元素是行动,那么就需要执行完对应代码后才能弹出属性堆栈顶部元素,因为该顶部元素需要在行动对应的代码执行过程中使用到。

如果解析堆栈顶部元素是终结符,那么我们必须判断当前读取的标签于对应的终结符相匹配,不然就是语法错误,如果是非终结符,那么我们就把对应语法解析式右边的符号压入堆栈即可,上面代码完成后执行结果如下:

t0 = 1
t1 = 2
t2 = 4
t3 = 3
t2 += t3
t1 *= t2
t0 += t1

可以看到其效果跟我们前面章节实现的一模一样,更多详细的讲解和调试演示请在 b 站搜索 coding 迪斯尼,本问代码下载地址为:
https://github.com/wycl16514/llma_parser

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/260478.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【运维】站点可靠性工程介绍:研发,运维,SRE,Devops的关系

文章目录 1、什么是SRE2、SRE与研发、运维的区别 1、什么是SRE 站点可靠性工程(SRE) 是 IT 运维的软件工程方案。 SRE 团队使用软件作为工具,来管理系统、解决问题并实现运维任务自动化。 SRE 执行的任务以前通常由运维团队手动执行&#x…

URL、DNS过滤,AV---防火墙综合实验

拓扑图 该实验之前的配置请看我的上一篇博客,这里仅配置URL、DNS过滤,AV 需求 8,分公司内部的客户端可以通过域名访问到内部的服务器 这次的拓扑图在外网多增加了一个DNS服务器和HTTP服务器 DNS服务器IP:40.0.0.30 HTTP服务器…

计算机视觉主要知识点

计算机视觉是指利用计算机和算法来解析和理解图片和视频中的内容。这是一个跨学科领域,融合了计算机科学、图像处理、机器学习和模式识别等多方面的技术。以下是一些计算机视觉入门的基本知识点: 主要知识点 图像基础: 像素:图片…

文献学习-1-Continuum Robots for Medical Interventions

Chapt 5. 连续体机构分析 5.1 文献学习 5.1.1 Continuum Robots for Medical Interventions Authors: PIERRE E. DUPONT , Fellow IEEE, NABIL SIMAAN , Fellow IEEE, HOWIE CHOSET , Fellow IEEE, AND CALEB RUCKER , Member IEEE 连续体机器人在医学上得到了广泛的应用&a…

深度学习基础之《TensorFlow框架(4)—Operation》

一、常见的OP 1、举例 类型实例标量运算add,sub,mul,div,exp,log,greater,less,equal向量运算concat,slice,splot,canstant,rank&am…

通配符ssl证书产品

SSL数字证书可以对网站传输数据进行加密以及对服务器的身份进行认证。然而,随着互联网的发展,不管是个人还是企事业单位创建的域名网站越来越多,单域名SSL数字证书无法满足需求,因此通配符SSL证书应运而生。今天就随SSL盾小编了解…

【elk查日志 elastic(kibana)】

文章目录 概要具体的使用方式一:查找接口调用历史二:查找自己的打印日志三:查找错误日志 概要 每次查日志,我都需要别人帮我,时间长了总觉得不好意思,所以这次下定决心好好的梳理一下,怎么查日…

文件IO,目录IO的学习

一&#xff0c;头文件的添加 #ifndef _HEAD_H_ //防止重新定义宏 #define _HEAD_H_#include<stdio.h> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h> #include<unistd.h> #include<string.h>#endif…

SpringBoot + Nacos 实现动态化线程池

1.背景 在后台开发中&#xff0c;会经常用到线程池技术&#xff0c;对于线程池核心参数的配置很大程度上依靠经验。然而&#xff0c;由于系统运行过程中存在的不确定性&#xff0c;我们很难一劳永逸地规划一个合理的线程池参数。 在对线程池配置参数进行调整时&#xff0c;一…

【已解决】PPT无法复制内容怎么办?

想要复制PPT文件里的内容&#xff0c;却发现复制不了&#xff0c;怎么办&#xff1f; 这种情况&#xff0c;一般是PPT文件被设置了以“只读方式”打开&#xff0c;“只读方式”下的PPT无法进行编辑更改&#xff0c;也无法进行复制粘贴的操作。 想要解决这个问题&#xff0c;我…

PHP分析二维数据表(长度|数字字段|空值|纯姓名|英文用户名|科学计数|是否等长|是否唯一)

先看图&#xff0c;后有完整代码 <?php $t "Excel数据转Sql查询系统字段半智能分析"; $s "Excel复制过来的二维结构表内容,分析查询条件&#xff01;"; $x "字段|最大长度|长度有|数字字段|空值存在|纯姓名|英文用户名|科学计数|是否等长|是否…

DP读书:《openEuler操作系统》(十)套接字 Socket 数据传输的基本模型

10min速通Socket 套接字简介数据传输基本模型1.TCP/IP模型2.UDP模型 套接字类型套接字&#xff08;Socket&#xff09;编程Socket 的连接1.连接概述(1)基本概念(2)连接状态(3)连接队列 2.建立连接3.关闭连接 socket 编程接口介绍数据的传输1. 阻塞与非阻塞2. I/O复用 数据的传输…

2024.02.20作业

1. 使用多进程完成两个文件的拷贝&#xff0c;父进程拷贝前一半&#xff0c;子进程拷贝后一半&#xff0c;父进程回收子进程的资源 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #includ…

C#,二叉搜索树(Binary Search Tree)的迭代方法与源代码

1 二叉搜索树 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;又称二叉查找树或二叉排序树。 一棵二叉搜索树是以二叉树来组织的&#xff0c;可以使用一个链表数据结构来表示&#xff0c;其中每一个结点就是一个对象。 一般地&#xff0c;除了key和位置…

【AIGC】Stable Diffusion的常见错误

Stable Diffusion 在使用过程中可能会遇到各种各样的错误。以下是一些常见的错误以及可能的解决方案&#xff1a; 模型加载错误&#xff1a;可能出现模型文件损坏或缺失的情况。解决方案包括重新下载模型文件&#xff0c;确保文件完整并放置在正确的位置。 依赖项错误&#x…

手持三防平板丨国产化加固平板丨国产三防平板发展的意义是什么?

随着现代科技的快速发展&#xff0c;平板电脑在我们的生活中扮演着越来越重要的角色。然而&#xff0c;传统的平板电脑只能在普通的环境中使用&#xff0c;而无法在恶劣的环境中使用&#xff0c;例如在高海拔、高温、高湿度、沙漠等环境中&#xff0c;传统平板电脑往往会出现故…

适用于Android 的 7 大短信恢复应用程序

对于 Android 用户来说&#xff0c;丢失重要的短信可能是一种令人沮丧的体验。幸运的是&#xff0c;有许多短信恢复应用程序可以帮助恢复丢失或删除的短信。在本文中&#xff0c;将与您分享 7 个最佳短信恢复应用程序&#xff0c;并帮助您找到可用于恢复已删除消息的最佳应用程…

美容小程序:让预约更简单,服务更贴心

在当今繁忙的生活节奏中&#xff0c;美容预约常常令人感到繁琐和疲惫。为了解决这个问题&#xff0c;许多美容院和SPA中心已经开始采用美容小程序来简化预约流程&#xff0c;并提供更加贴心的服务。在这篇文章中&#xff0c;我们将引导您了解如何制作一个美容小程序&#xff0c…

阿里云幻兽帕鲁服务器,游戏服务端版本升级怎么操作?

用阿里云一键部署的幻兽帕鲁服务器&#xff0c;想要更新游戏服务端版本&#xff0c;现在非常简单。之前还需要通过输入一行命令来更新&#xff0c;而现在可以直接通过面板上的选型来操作。 打开阿里云的计算巢&#xff0c;找到你的这台服务实例&#xff0c;点击进入&#xff0…

谈谈:你在工作中用到的设计模式!

谈谈:你在工作中用到的设计模式! Hello大家龙年好! 春节的假期转眼间过去,我们也要回归往日的节奏 因为最近和小伙伴们聊天发现,我们普遍在面试中,对被问起设计模式在工作中的应用,既有点熟悉,又有点陌生, 在网上看吧,又感觉鸡肋(为啥?不能解燃煤之急啊!哈哈),所以,为了打破这…