Python源码分析5 – 语法分析器PyParser

python 今日小知识1——parser 它将检查命令行,把每个参数转换为适当的类型然后调用相应的操作。— 应该读取的命令行参数个数,可以是具体的数字,或者是?— 在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称.— 参数的帮助信息,当指定为 argparse.SUPPRESS 时表示不显示该参数的帮助信息.程序定义它需要的参数,然后 argparse 将弄清如何从 sys.argv 解析出那些参数。— 解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线. 阅读详情

Introduction

上一篇文章我们分析了Python是如何对语法文件Grammar进行预处理,生成语法数据,并在运行时生成Acclerators加速语法分析的过程。当分析完这些内容之后,下一步便是分析Python中语法分析的机制。回顾一下Python的整个处理流程:

1.     PyTokenizer进行词法分析,把源程序分解为Token

2.     PyParser根据Token创建CST

3.     CST被转换为AST

4.     AST被编译为字节码

5.     执行字节码

语法分析处于整个流程的第二步,其目标是处理token生成CST(Concrete Syntax Tree)。因此,在具体分析PyParser的工作方式之前,我们先看一下什么是CST。

CST (Concrete Syntax Tree)

CST (Concrete Syntax Tree) 和AST (Abstract Syntax Tree) 类似,都是语法分析所获得的中间结果。他们的不同之处在于,CST直接对应语法分析的匹配的过程,是直接生成的,含有大量冗余信息。而AST省略了中间的冗余信息,直接对应实际的语义,也就是分析的结果。用例子说明要清楚一些:

假设有这样一个表达式a,

CST是这样的:(->表示从父结点到子结点)

file_input -> stmt -> simple_stmt -> small_stmt -> expr_stmt -> testlist -> test ->or_test ->and_test ->not_test -> comparison -> expr -> xor_expr -> and_expr -> shift_expr -> arith_expr -> term -> factor -> power -> atom -> (NAME, “a”)

而AST则是:

(stmt_ty, expr_kind) -> (expr_ty, name_kind) ->(“a”)

可以看到CST表述了整个分析a的过程,从file_input一直推导到最后的NAME,每一步推导都成了树的结点,而大部分信息都可以说是无用的。AST的结构要简单和直接的多,直接表明a是一个表达式语句(假定a是一个单独的语句),内容是一个标示符,值为”a”。Python的语法分析生成的是CST而非AST,之后Python会调用PyAst_FromNode将CST转换为AST。

CST的结点称为Node,其结构定义在node.h中:

typedef struct _node {

    short            n_type;

    char             *n_str;

    int              n_lineno;

    int              n_col_offset;

    int              n_nchildren;

    struct _node     *n_child;

} node;

 

Field

Description

n_type

结点类型,终结符定义在token.h中,而非终结符定义在graminit.h中

n_str

结点所对应的字符串的内容

n_lineno

对应的行号

n_col_offset

列号

n_nchildren

子结点的个数

n_child

子结点数组,动态分配内存

 

Python提供了下面的函数/宏来操作CST,同样定义在node.h中:

PyAPI_FUNC(node *) PyNode_New(int type);

PyAPI_FUNC(int) PyNode_AddChild(node *n, int type,

                                      char *str, int lineno, int col_offset);

PyAPI_FUNC(void) PyNode_Free(node *n);

 

/* Node access functions */

#define NCH(n)             ((n)->n_nchildren)

      

#define CHILD(n, i)  (&(n)->n_child[i])

#define RCHILD(n, i) (CHILD(n, NCH(n) + i))

#define TYPE(n)            ((n)->n_type)

#define STR(n)             ((n)->n_str)

 

/* Assert that the type of a node is what we expect */

#define REQ(n, type) assert(TYPE(n) == (type))

 

PyAPI_FUNC(void) PyNode_ListTree(node *);

 

PyNode_New和PyNode_Delete负责创建和释放node结构:

node *

PyNode_New(int type)

{

       node *n = (node *) PyObject_MALLOC(1 * sizeof(node));

       if (n == NULL)

              return NULL;

       n->n_type = type;

       n->n_str = NULL;

       n->n_lineno = 0;

       n->n_nchildren = 0;

       n->n_child = NULL;

       return n;

}

 

void

PyNode_Free(node *n)

{

       if (n != NULL) {

              freechildren(n);

              PyObject_FREE(n);

       }

}

 

static void

freechildren(node *n)

{

       int i;

       for (i = NCH(n); --i >= 0; )

              freechildren(CHILD(n, i));

       if (n->n_child != NULL)

              PyObject_FREE(n->n_child);

       if (STR(n) != NULL)

              PyObject_FREE(STR(n));

}

 

NCH/CHILD/RCHILD/TYPE/STR是用来封装对node的成员的访问的。需要提一下的是,CHILD(n, i)是从左边开始算,传入i的是正数,而RCHILD(n, i)则是从右边往左,传入的参数i是负数。

PyNode_AddChild将一个新的子结点加入到子结点数组中。由于结点数量是动态变化的,因此在当前分配的结点数组大小不够的时候,Python会调用realloc重新分配内存。内存分配是一个非常耗时的动作,因此Python在PyNode_AddChild之中用到了和std::vector类似的技巧来尽量减少内存分配的次数,每次增长的时候都会根据某个规则进行RoundUp,而不是需要多少就分配多少。XXXROUNDUP函数负责进行此运算。n<=1时, 返回n。1<n<=128的时候,会RoundUp到4的倍数。n>128, 会调用fancy_roundup来RoundUp到2的幂。

#define XXXROUNDUP(n) ((n) <= 1 ? (n) :         /

                     (n) <= 128 ? (((n) + 3) & ~3) :   /

                     fancy_roundup(n))

有了XXXROUNDUP之后,实现PyNode_AddChild就非常直接了:

int

PyNode_AddChild(register node *n1, int type, char *str, int lineno, int col_offset)

{

       const int nch = n1->n_nchildren;

       int current_capacity;

       int required_capacity;

       node *n;

 

       if (nch == INT_MAX || nch < 0)

              return E_OVERFLOW;

 

       current_capacity = XXXROUNDUP(nch);

       required_capacity = XXXROUNDUP(nch + 1);

       if (current_capacity < 0 || required_capacity < 0)

              return E_OVERFLOW;

       if (current_capacity < required_capacity) {

              n = n1->n_child;

              n = (node *) PyObject_REALLOC(n,

                                        required_capacity * sizeof(node));

              if (n == NULL)

                     return E_NOMEM;

              n1->n_child = n;

       }

 

       n = &n1->n_child[n1->n_nchildren++];

       n->n_type = type;

       n->n_str = str;

       n->n_lineno = lineno;

       n->n_col_offset = col_offset;

       n->n_nchildren = 0;

       n->n_child = NULL;

       return 0;

}

 

值得注意的是该函数并没有记录下当前的最大容量,这个可以通过XXXROUNDUP(nch)计算出来。

PyParser

PyParser所作的事情就是根据token生成CST。整个生成树的过程其实也就是一个遍历语法图的过程。在前一篇文章中我们知道语法图是由多个DFA组成的,而输入的token和当前的所处的状态结点可以决定下一个状态结点。由于PyParser是在多个DFA中遍历,因此当结束了某个DFA的遍历需要回到上一个DFA,这些信息都是由一个专门的栈保存着。PyParser所对应的结构是parser_state,这个结构保存着PyParser的内部状态,如下:

typedef struct {

       stack        p_stack;      /* Stack of parser states */

       grammar       *p_grammar;   /* Grammar to use */

       node          *p_tree;      /* Top of parse tree */

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD

       unsigned long p_flags;      /* see co_flags in Include/code.h */

#endif

} parser_state;

 

Field

Description

p_stack

PyParser状态栈,每一个状态都对应着某个DFA中的某个状态

p_grammar

语法图的指针

p_tree

CST的根结点

p_flags

PyParser的flags,唯一用到的是CO_FUTURE_WITH_STATEMENT

 

状态栈的定义如下:

typedef struct {

       stackentry    *s_top;              /* Top entry */

       stackentry    s_base[MAXSTACK];   /* Array of stack entries */

                                         /* NB The stack grows down */

} stack;

 

状态栈中的状态如下:

typedef struct {

       int           s_state;     /* State in current DFA */

       dfa           *s_dfa;       /* Current DFA */

       struct _node  *s_parent;    /* Where to add next node */

} stackentry;

 

Field

Description

s_state

当前DFA中的状态ID

s_dfa

当前DFA指针

s_parent

当前的parent结点。PyParser会把新的结点作为Child加到栈顶状态的s_parent结点中去

PyParser调用下面这些函数来操作栈:

static void s_reset(stack *s);

#define s_empty(s) ((s)->s_top == &(s)->s_base[MAXSTACK])

static int s_push(register stack *s, dfa *d, node *parent)

#define s_pop(s) (s)->s_top++

这些函数的实现非常简单,不再赘述。

PyParser所支持的“成员”函数如下:

parser_state *PyParser_New(grammar *g, int start);

void PyParser_Delete(parser_state *ps);

int PyParser_AddToken(parser_state *ps, int type, char *str,

       int lineno, int col_offset,int *expected_ret);

void PyGrammar_AddAccelerators(grammar *g);

PyParser_New & PyParser_Delete显然是用于创建和销毁PyParser的实例的,和PyTokenizer一致。PyGrammar_AddAccelerators在我的前一篇Python源码研究4中提到过,主要用于处理Python的Grammar数据生成Accelerator加快语法分析的速度。在这些函数中,最为核心的是PyParser_AddToken,这个函数的作用是根据PyTokenizer所获得的token和当前所处的状态/DFA,跳转到下一个状态,并添加到CST中。在parsetok函数中,有如下的代码(省略了大部分):

parser_state *ps;

ps = PyParser_New(g, start);

for (;;) {

       char *a, *b;

       int type;

       type = PyTokenizer_Get(tok, &a, &b);    

       PyParser_AddToken(ps, (int)type, str, tok->lineno, col_offset, &(err_ret->expected));

}

Parsetok的作用是分析某段代码。可以看到,parsetok会反复调用PyTokenizer_Get获得下一个token,然后将反复将获得的token传给PyParser_AddToken来逐步构造整个CST,当所有token都处理过了之后,整棵树也就建立完毕了。

PyParser API

Python不会直接调用PyParser和PyTokenizer的函数,而是直接调用下面的这些Python API:

PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int,

                                        perrdetail *);

PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int,

                                       char *, char *, perrdetail *);

 

PyAPI_FUNC(node *) PyParser_ParseStringFlags(const char *, grammar *, int,

                                             perrdetail *, int);

PyAPI_FUNC(node *) PyParser_ParseFileFlags(FILE *, const char *, grammar *,

                                           int, char *, char *,

                                           perrdetail *, int);

 

PyAPI_FUNC(node *) PyParser_ParseStringFlagsFilename(const char *,

                                               const char *,

                                               grammar *, int,

                                                perrdetail *, int);

 

/* Note that he following function is defined in pythonrun.c not parsetok.c. */

PyAPI_FUNC(void) PyParser_SetError(perrdetail *);

PyAPI_FUNC宏是用于定义公用的Python API,表明这些函数可以被外界调用。在Windows上面Python Core被编译成一个DLL,因此PyAPI_FUNC等价于大家常用的__declspec(dllexport)/__declspec(dllimport)。这些函数把PyParser和PyTokenizer对象的接口和细节包装起来,使用者可以直接调用PyParser_ParseXXXX函数来使用PyParser和PyTokenizer的功能而无需知道PyPaser/PyTokenizer的工作方式,这可以看作是一个典型的Façade模式。以PyParser_ParseFile为例,该函数分析传入的FILE返回生成的CST。其他的函数与此类似,只是分析的对象不同和传入参数的不同。

PyParser_AddToken implementation

在全面了解了PyParser的接口和调用方式之后,我们来看一下PyParser_AddToken的具体实现。PyParser_AddToken会调用3个内部函数来做处理:classify, push, shift

Classify根据type和str,确定对应的Label,实现如下:

static int

classify(parser_state *ps, int type, char *str)

{

       grammar *g = ps->p_grammar;

       register int n = g->g_ll.ll_nlabels;

      

       if (type == NAME) {

              register char *s = str;

              register label *l = g->g_ll.ll_label;

              register int i;

              for (i = n; i > 0; i--, l++) {

                     if (l->lb_type != NAME || l->lb_str == NULL ||

                         l->lb_str[0] != s[0] ||

                         strcmp(l->lb_str, s) != 0)

                           continue;

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD

                     if (!(ps->p_flags & CO_FUTURE_WITH_STATEMENT)) {

                           if (s[0] == 'w' && strcmp(s, "with") == 0)

                                  break; /* not a keyword yet */

                           else if (s[0] == 'a' && strcmp(s, "as") == 0)

                                  break; /* not a keyword yet */

                     }

#endif

                     D(printf("It's a keyword/n"));

                     return n - i;

              }

       }

      

       {

              register label *l = g->g_ll.ll_label;

              register int i;

              for (i = n; i > 0; i--, l++) {

                     if (l->lb_type == type && l->lb_str == NULL) {

                           D(printf("It's a token we know/n"));

                           return n - i;

                     }

              }

       }

      

       D(printf("Illegal token/n"));

       return -1;

}

可以看到classify作了一些针对NAME的特殊处理。有两种NAME,一种是标示符,一种是关键字。语法图中标示符的str是NULL(因为在语法图中无法实现知道标示符的内容,也不用知道),而关键字的str是有值的,不同的关键字对应着不同的语句,因此需要进行比较。classify首先处理关键字的情况,如果不是关键字则当普通情况下处理。两种情况下都需要遍历整个LabelList来确定Label的ID,唯一不同的是关键字的情况下需要比较具体的str内容而其他情况下无须比较。这里的效率看起来还是有改进的余地,在非关键字情况下可以用一个数组来做索引,直接根据type查找到id。在关键字的情况下至少可以把关键字作为一个单独的Token/非终结符来识别,这样就不用和普通标示符的情况弄混了,而目前的实现即使是普通标示符也会进到第一个if语句中进行关键字的判断。还可以使用hash来快速查找等。

Shift改变当前栈顶的状态(注意并不跳转到另外的DFA,这个改变只限于单个DFA中,跳转到另外一个DFA需要调用push压栈),并把当前的type/str作为一个新的子结点加入到栈顶的s_parent结点,通常s_parent结点对应着当前的DFA的结点。

假设我们在DFA0中,当前栈顶为(DFA0, s1),type=NAME

                                                                NAME

DFA0: s0               ->            s1            ->           s2

从s1跳转到s2不会离开DFA0,不用进栈,只需改变当前(DFA0, s1)到(DFA0, s2)即可。

static int

shift(register stack *s, int type, char *str, int newstate, int lineno, int col_offset)

{

       int err;

       assert(!s_empty(s));

       err = PyNode_AddChild(s->s_top->s_parent, type, str, lineno, col_offset);

       if (err)

              return err;

       s->s_top->s_state = newstate;

       return 0;

}

 

Push同样也会把type/str作为新的子结点n加入到当前s_parent结点并改变当前栈顶的状态为newstate。但是newstate并非是下一个状态,而是当新的DFA遍历完毕之后退栈才会到。然后,把目标DFA压栈。新生成的子结点n作为新的s_parent。

还是举一个例子比较容易说明这个过程:

                                ‘a’

DFA 1:    s0            ->            s1

                                DFA1

DFA 0:    s1            ->            s2

假设当前我们处于(DFA0, s1), type/str告诉我们下一个状态为s2,label是非终结符DFA1,对应着DFA1,因此我们会把当前栈顶(DFA0, s1)修改为(DFA0, s2),然后跳转到DFA1中进行匹配,同时(DFA1, s0)作为新的栈顶压栈,当(DFA1,s0)退栈之后,说明DFA1匹配完毕,回到(DFA0, s2)。

具体的实现如下:

static int

push(register stack *s, int type, dfa *d, int newstate, int lineno, int col_offset)

{

       int err;

       register node *n;

       n = s->s_top->s_parent;

       assert(!s_empty(s));

       err = PyNode_AddChild(n, type, (char *)NULL, lineno, col_offset);

       if (err)

              return err;

       s->s_top->s_state = newstate;

       return s_push(s, d, CHILD(n, NCH(n)-1));

}

 

在讨论完这些函数之后就可以开始着手研究PyParser_AddToken的实现了:

int

PyParser_AddToken(register parser_state *ps, register int type, char *str,

                 int lineno, int col_offset, int *expected_ret)

{

       register int ilabel;

       int err;

      

       D(printf("Token %s/'%s' ... ", _PyParser_TokenNames[type], str));

      

       /* Find out which label this token is */

       ilabel = classify(ps, type, str);

       if (ilabel < 0)

              return E_SYNTAX;

PyParser_AddToken第一步是根据type和str,调用classify获得对应的label。

PyParser_AddToken获得了对应的label之后,进入一个for loop:

       /* Loop until the token is shifted or an error occurred */

       for (;;) {

              /* Fetch the current dfa and state */

              register dfa *d = ps->p_stack.s_top->s_dfa;

              register state *s = &d->d_state[ps->p_stack.s_top->s_state];

             

              D(printf(" DFA '%s', state %d:",

                     d->d_name, ps->p_stack.s_top->s_state));

这个for loop反复拿到当前栈顶,也就是DFA和DFA状态,然后根据当前的状态和Label决定下一步的动作。基本的规则如下:

              /* Check accelerator */

              if (s->s_lower <= ilabel && ilabel < s->s_upper) {

                     register int x = s->s_accel[ilabel - s->s_lower];

有对应的Accelerator,为x

1.     X第8位为1,说明x对应着一个非终结符,记录着目标状态的DFA ID和状态

DFA ID

1

目标状态,7bit

                在这种情况下会跳转到另外一个DFA,把目标DFA+状态压栈。

                     if (x != -1) {

                           if (x & (1<<7)) {

                                  /* Push non-terminal */

                                  int nt = (x >> 8) + NT_OFFSET;

                                  int arrow = x & ((1<<7)-1);

                                  dfa *d1 = PyGrammar_FindDFA(

                                         ps->p_grammar, nt);

                                  if ((err = push(&ps->p_stack, nt, d1,

                                         arrow, lineno, col_offset)) > 0) {

                                         D(printf(" MemError: push/n"));

                                         return err;

                                  }

                                  D(printf(" Push .../n"));

                                  continue;

                           }

                          

2.     否则,x对应终结符,调用Shift来改变栈顶的状态并把结点添加到CST中。如果栈顶对应着一个Accept状态的话,说明当前DFA已经匹配完毕,反复退栈直到当前状态不为Accept状态为止。

                           /* Shift the token */

                           if ((err = shift(&ps->p_stack, type, str,

                                         x, lineno, col_offset)) > 0) {

                                  D(printf(" MemError: shift./n"));

                                  return err;

                           }

                           D(printf(" Shift./n"));

                           /* Pop while we are in an accept-only state */

                           while (s = &d->d_state

                                         [ps->p_stack.s_top->s_state],

                                  s->s_accept && s->s_narcs == 1) {

                                  D(printf("  DFA '%s', state %d: "

                                          "Direct pop./n",

                                          d->d_name,

                                          ps->p_stack.s_top->s_state));

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD

                                  if (d->d_name[0] == 'i' &&

                                      strcmp(d->d_name,

                                            "import_stmt") == 0)

                                         future_hack(ps);

#endif

                                  s_pop(&ps->p_stack);

                                  if (s_empty(&ps->p_stack)) {

                                         D(printf("  ACCEPT./n"));

                                         return E_DONE;

                                  }

                                  d = ps->p_stack.s_top->s_dfa;

                           }

                           return E_OK;

 

如果没有Acclerator,有可能该结点已经是Accept状态,同样退栈:

              if (s->s_accept) {

#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD

                     if (d->d_name[0] == 'i' &&

                         strcmp(d->d_name, "import_stmt") == 0)

                           future_hack(ps);

#endif

                     /* Pop this dfa and try again */

                     s_pop(&ps->p_stack);

                     D(printf(" Pop .../n"));

                     if (s_empty(&ps->p_stack)) {

                           D(printf(" Error: bottom of stack./n"));

                           return E_SYNTAX;

                     }

                     continue;

              }

否则,语法错误,假如可能遇到的token只有一种可能的话,设置expected_ret为当前我们所期望看到的token,这样Python才可以显示语法错误信息。

              /* Stuck, report syntax error */

              D(printf(" Error./n"));

              if (expected_ret) {

                     if (s->s_lower == s->s_upper - 1) {

                           /* Only one possible expected token */

                           *expected_ret = ps->p_grammar->

                               g_ll.ll_label[s->s_lower].lb_type;

                     }

                     else

                            *expected_ret = -1;

              }

              return E_SYNTAX;

 

至此PyParser已经分析完毕。下一篇文章将着重分析从CST到AST转换过程。

 

作者:      ATField
E-Mail:   atfield_zhang@hotmail.com
Blog:     
http://blog.youkuaiyun.com/atfield

 
【Python】Parser通常用法 Parser在Python程序中经常使用,使用前需要import argparse,简单来说argparse模块就是对参数进行设置。 阅读详情

相关推荐

【Python】parser的使用,详细解释

argparse是一个功能强大的命令行参数解析库,可以让你轻松处理命令行输入。通过对象和方法,可以定义所需的命令行参数。argparse支持位置参数、可选参数、布尔类型参数、互斥参数组等高级功能。自动生成的帮助信息让用户可以轻松了解如何使用脚本。

wq6qeg88的博客 1800

【Python】Parser 用法-通俗易懂!

AssertionError: Please activate one of the 实例化ArgumentParser 使用add_argument函数添加参数 add_argument() 方法定义如何解析命令行参数 使用parse_args解析参数 案例实践:action的可选参数store_true的作用 报错AssertionError: Please activate one of the

MengYa_Dream的博客 6万+

Python全面解析——源码实战解析

本篇文章将从Python语言的基础入手,逐步引入Python中的高级特性,并通过源码实例进行详细讲解。比如字符串、字典、列表等常见数据结构的基本使用方法,以及常用的流程控制语句,如if-else、while、for循环等。除此之外,Python中的模块也是非常重要的一部分,在Python中,一个.py文件就可以作为一个模块,里面可以存放多个函数和变量。本文介绍了Python语言的基础知识、函数和模块的使用方法、Python的高级特性以及Python在不同领域的应用。这些特性可以大大提升程序的效率和可读性。

WangWEel的博客 390

Python的parser和argparse库详解,给出例子

当我们编写Python代码时,常常需要解析各种输入和文件格式。例如,我们可能需要解析命令行参数,或者解析一些标记化的文本格式。Python的parser库就是为此而生的。Python的parser库提供了一些工具,可以帮助我们解析文本或文件,并将其转换成Python对象。在本篇博客中,我们将介绍parser库的基本使用方法,并提供一些例子来说明其用法。

大模型天花板 8260

[调参神器]使用VOFA+上位机进行PID调参(附下位机代码)

​ 简单地来说,VOFA+是一个超级串口助手,除了可以实现一般串口助手的串口数据收发,它还可以实现数据绘图(包括直方图、FFT图),控件编辑,图像显示等功能。​ 如果只是想要用VOFA+来进行数据绘图,直接使用一个波形图控件就行,但是如果想要把VOFA+当作一个长期使用的调参助手,我们最好设置一下控件。VOFA+简洁好用,但是又有点太简洁了,部分体验并不是很好(比如不能调整控件大小,保存文件不太方便等),所以如果学有余力,推荐自己用QT/PyQT写上位机。接下来,我们要将指令中的数据提取出来。

qq_56089182的博客 4万+

米哈游 AI产品经理面试题精选:10道高频考题+答案解析(附PDF)

米哈游是全球知名游戏开发商,以《原神》《崩坏》系列著称,专注二次元文化与AI技术创新。本文整理了10道AI产品经理面试题,涵盖Transformer架构、智能NPC设计、机器学习应用、AIGC产品规划等核心领域。题目既考察技术理解(如RAG原理、多模态模型),又关注产品落地(游戏平衡性优化、伦理安全),同时强调跨团队协作能力。参考答案均结合米哈游业务场景,体现技术驱动与玩家体验并重的产品思维,展现AI在游戏行业的创新应用前景。

ZHHHHH15的博客 679

python实现语法分析器_Python源码分析5 – 语法分析器PyParser | 学步园

Introduction上一篇文章我们分析了Python是如何对语法文件Grammar进行预处理,生成语法数据,并在运行时生成Acclerators加速语法分析的过程。当分析完这些内容之后,下一步便是分析Python中语法分析的机制。回顾一下Python的整个处理流程:1.PyTokenizer进行词法分析,把源程序分解为Token2.PyParser根据Token创建CST3.CST被转换为AS...

weixin_36000045的博客 1858

Red Hat Enterprise Linux 9.2

安装Red Hat Enterprise Linux 9.2

zhurobert的博客 940

python代码解读器_Python源码分析5 – 语法分析器PyParser

Introduction上一篇文章我们分析了Python是如何对语法文件Grammar进行预处理,生成语法数据,并在运行时生成Acclerators加速语法分析的过程。当分析完这些内容之后,下一步便是分析Python中语法分析的机制。回顾一下Python的整个处理流程:1. PyTokenizer进行词法分析,把源程序分解为Token2. PyParser根据Token创建CST3. CST被转换...

weixin_42443992的博客 771

Springboot计算机毕业设计校园树洞微信小程序【附源码】开题+论文+mysql+程序+部署

因此,开发一款专门针对校园用户的树洞微信小程序,为学生提供一个安全、私密、便捷的倾诉和交流平台,具有重要的现实意义和应用价值。通过本研究的实施,我们预期能够开发出一款功能全面、操作便捷的校园树洞微信小程序,为校园师生提供一个安全、私密、便捷的倾诉和交流平台。此外,通过不断优化和改进小程序的功能和服务,我们预期能够提升用户体验和满意度,推动校园树洞小程序的持续发展,并为校园生活的和谐与健康发展做出积极贡献。同时,通过不断优化小程序的功能和服务,提升用户体验和满意度,推动校园树洞小程序的广泛应用和持续发展。

光冯毕设程序 1666

Android 13高通平台CTS camera部分fail项修改(续)

接着上一篇,有几个提了case高通也分析了很久的fail项,其中最离谱的是问题1,第一个接手case的人反反复复处理、内部排查踢皮球、要求log,折腾了很久,最终就给出个解不了的结论。后面还是靠着自己发现了一些蛛丝马迹,并且换了个case接手人才最终解决。

hkj887tg的博客 2059

【Python】科研代码学习:九 parser,argparse,HfArgumentParser

【代码】【Python】科研代码学习:九 parser,argparse,HfArgumentParser。

溢流眼泪的博客 2292

《备考系统分析师》 - 专栏介绍和目录

本专栏提供软考高级系统分析师全套备考资料,包含四大核心内容:1.详细考点梳理(覆盖基础知识、关键技术、案例实践三大模块16个章节);2.历年真题及解析(2009-2022年所有真题含综合知识、案例分析和论文);3.100+篇高质量论文范文;4.预测模拟题。早鸟价仅需9.9元,可节约80%资料搜集时间,帮助考生高效备考、顺利通过考试。专栏持续更新,有问题可随时联系作者。

数据知道的博客 1万+

【专题】python源码解读

文章目录准备工作Python整体结构源码目录Python对象Python内对象类型对象对象的创建类型的类型Python中的整数对象小整形对象大整数对象 准备工作 Python整体结构 python架构主要分为三部分 python文件 python解释器 scanner词法分析,将代码且分为一个个token parser词法分析建立AST compiler根据AST生成python字节码 code Evaluator(虚拟机)执行这些字节码 运行时环境 内建对象:list dict 内存分配器:和

qq_36581961的博客 3625

C#与欧姆龙PLC 通信——Fins Tcp协议

该调试助手专门为了帮助大家学习以及测试Fins-Tcp通信,下面进行详细介绍。

qq_34059233的博客 2081

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Python源码剖析-深度探索动态语言核心技术

Python源码剖析-深度探索动态语言核心技术内容简介Python 总体架构Python源代码的组织 内容简介 为了更好地利用python语言,无论是python本身还是和C/C++配合使用,深刻理解python的运行原理都是非常重要的,本文以CPython源码为研究对象,深入剖析Python的实现。主要包含: Python内置对象 Python虚拟机 Python高级特性 本文参考: Python源码剖析一书 学习建议:唯有亲身尝试,才能深解其中三昧。 在真实的源码和文章的描述相比较揣摩,动手捣鼓捣鼓

专注游戏研发 4222

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Parser参数解析器范例(Python, Pytorch, 深度学习)

使用parse_args()来解析参数。

Croi的博客 601

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Python(1)——Parser解析器

【代码】Python(1)——Parser解析器。

qq_23413169的博客 509

【Python基础函数笔记】parser

学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

2401_84003977的博客 998

Python——命令行交互argparse

字符串区间索引 [A:B]表示从A到B的部分字符串 索引方式有正向索引、反向索引 例如,对10CNY(10元钱)进行货币提取 currency_str = '10CNY' unit = currency_str[-3:]

weixin_44823313的博客 669
上一篇: .NET / Rotor 源码研究1 - Building Rotor
下一篇: .NET / Rotor 源码研究2 – PAL Initialization / Termination
ATField
博客等级 码龄25年 673粉丝 119原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值