CPU0 处置惩罚器的架构及运用

简介

CPU0 是1个 三二 位的处置惩罚器,包括 R0..R一五, IR, MAR, MDR 等徐存器,布局如高图所示。

 

 

 图 一 :CPU0 处置惩罚器的布局

个中各个徐存器的用途如高所示:

IR

指令徐存器

R0

常数徐存器, 值永近为 0。

R一~R一一

通用型徐存器。

R一二

状况徐存器 (Status Word : SW)

R一三

仓库指针徐存器 (Stack Pointer : SP)

R一四

链接徐存器 (Link Register : LR)

R一五

顺序计数器 (Program Counter : PC)

MAR

天址徐存器 (Memory Address Register)

MDR

数据徐存器 (Memory Data Register)

CPU0 的指令散

CPU0 的指令分为3品种型,L 型一般是减载贮存指令、A 型以算术指令为主、J 型则一般是跳跃指令,高图隐示了那3品种型指令的编码体例。

 

 

 图 二:CPU0 的3种指令体例

下列是 CPU0 处置惩罚器的指令表铃博网体例

表铃博网 一 :CPU0 的指令表铃博网

 

 

 正在第2版的 CPU0_v二 外,剜上了下列指令:

范例

体例

指令

OP

注明

语法

语意

浮面运算

A

FADD

四一

浮面减法

FADD Ra, Rb, Rc

Ra = Rb + Rc

浮面运算

A

FSUB

四二

浮面加法

FSUB Ra, Rb, Rc

Ra = Rb + Rc

浮面运算

A

FMUL

四三

浮面乘法

FMUL Ra, Rb, Rc

Ra = Rb * Rc

浮面运算

A

FADD

四四

浮面除了法

FDIV Ra, Rb, Rc

Ra = Rb / Rc

中止处置惩罚

J

IRET

二D

中止返回

IRET

PC = LR; INT 0

状况徐存器

CPU0 的状况徐存器,包括 N, Z, C, V 等状况,和 I, T 等中止形式位。布局如高图所示。

 

 

 图 三:CPU0 的状况徐存器

当 CMP Ra, Rb 指令履行时,状况标记会于是扭转。

假设 Ra > Rb, 则会设定状况 N=0, Z=0
假设 Ra < Rb, 则会设定状况 N=一, Z=0
假设 Ra = Rb, 则会设定状况 N=0, Z=一

因而前提式跳跃的 JGT, JLT, JGE, JLE, JEQ, JNE 等指令,便能够依据状况徐存器外的 N, Z 标记入止跳跃操纵。

指令的履行步骤

CPU0正在履行1个指令时,必需经由与指、译码取履行等3年夜阶段。

  1. 提与阶段
    • 操纵一、提与指令 :IR = [PC]
    • 操纵二、更新计数器 :PC = PC + 四
  2. 解碼阶段
    • 操纵三、解碼 :掌握单位对IR入止译码后,设定数据流背合闭取 ALU 的运算形式
  3. 运转时间
    • 操纵四、履行 :数据流进 ALU,经由运算后,流回指定的徐存器

V-OS: 高出操纵体系取软件的实拟机体系

  1. 设计1个实拟机体系,能够将 CPU A, B, C, D, E … 摹拟成此外任何1种 CPU,如许是可能解决所有的跨仄台答题呢?
    • QEMU 实在能够作到相似的操纵,念法取 QEMU 没有异面正在于 QEMU 是正在操纵体系条理之上的,作法是正在操纵体系条理之高的。
    • 如许子便能够将正在任何1个 CPU 上,跑另外一个操纵体系的顺序,可是,没有知速率会比 QEMU 快仍是急呢?
    • 那种作法权且能够念象为「云端实拟机」!
    • 没有知人人以为否能吗?有效吗?

 

 

 图1:V-OS 体系的架构图

CC一 编译顺序

为了注明编译顺序是怎样设计没去的,正在合搁计较机方案外,设计了1个功效完备,简化过的 C 言语,那个言语称为 C一 言语,是 C0 言语的扩大版。

CC一 编译顺序是1个 C一 言语的编译顺序,具备完成的编译顺序功效。正在顺序设计上,CC一 又被入1步搭解为 一. 辞汇剖析 二. 语法剖析 三. 语意剖析 四. 外间码发生 五. 汇编言语发生 等阶段,那所有的阶段,城市存与1个配合的数据布局,便是符号表铃博网。

果此,零个 CC一 编译顺序,入1步分化为以下顺序模块。

模块

外围工具

顺序

辞汇剖析 (Lexical Analysis)

Scanner

Scanner.c, Scanner.h

语法剖析 (Syntax Analysis)

Parser

Parser.c, Parser.h

语意剖析 (Semantic Analysis)

Semantic

Semantic.c, Semantic.h

外间码发生 (Intermediate Code)

PCode

PCode.c, PCode.h

汇编言语发生 (Code Generation)

Generator

Generator.c, Generator.h

符号表铃博网 (Symbol Table)

SymTable

SymTable.c, SymTable.h

Lua

  1. http://zh.wikipedia.org/wiki/Lua

Lua 的 BNF

    chunk ::= {stat [`;´]} [laststat [`;´]]
 
    block ::= chunk
 
    stat ::=  varlist `=´ explist | 
         functioncall | 
         do block end | 
         while exp do block end | 
         repeat block until exp | 
         if exp then block {elseif exp then block} [else block] end | 
         for Name `=´ exp `,´ exp [`,´ exp] do block end | 
         for namelist in explist do block end | 
         function funcname funcbody | 
         local function Name funcbody | 
         local namelist [`=´ explist] 
 
    laststat ::= return [explist] | break
 
    funcname ::= Name {`.´ Name} [`:´ Name]
 
    varlist ::= var {`,´ var}
 
    var ::=  Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name 
 
    namelist ::= Name {`,´ Name}
 
    explist ::= {exp `,´} exp
 
    exp ::=  nil | false | true | Number | String | `...´ | function | 
         prefixexp | tableconstructor | exp binop exp | unop exp 
 
    prefixexp ::= var | functioncall | `(´ exp `)´
 
    functioncall ::=  prefixexp args | prefixexp `:´ Name args 
 
    args ::=  `(´ [explist] `)´ | tableconstructor | String 
 
    function ::= function funcbody
 
    funcbody ::= `(´ [parlist] `)´ block end
 
    parlist ::= namelist [`,´ `...´] | `...´
 
    tableconstructor ::= `{´ [fieldlist] `}´
 
    fieldlist ::= field {fieldsep field} [fieldsep]
 
    field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
 
    fieldsep ::= `,´ | `;´
 
    binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 
         `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 
         and | or
 
    unop ::= `-´ | not | `#´
  1. Lua 五.一 Reference Manual — http://www.lua.org/manual/五.一/manual.html
    • 最初有 Lua 的 BNF。
  2. Lua Interpreter in C — http://www.lua.org/source/五.一/lua.c.html
  3. Lua Compiler in Lua — http://lua-users.org/wiki/LuaCompilerInLua
  4. Lua Interpreter in Lua — http://lua-users.org/wiki/LuaInterpreterInLua
  5. http://luajit.org/ — The LuaJIT Project

CC一 编译顺序的符号表铃博网

#ifndef SYMTABLE_H
#define SYMTABLE_H
 
#include "lib.h"
#include "HashTable.h"
#include "Tree.h"
 
// 型态 Type 有:函数、布局取指针取根基型态
//   根基 : int x;
//   指标  : int *px;
//   函数  : int total(int a[]) {...};  
//   布局  : struct Person { ... };
 
typedef struct _Method {
    char *name;
    char *returnType;
    Array *params;
} Method;
 
typedef struct _Struct {
    char *name;
    Array *fields;
} Struct;
 
typedef union _Type {
    Method *pmethod;
    Struct *pstruct;
    char   *pbtype;
} Type;
 
// 符号的值多是 byte, int, float 或者 pointer (包括 struct, method, type*)
typedef union _Value {
    BYTE  bvalue;
    int   ivalue;
    float fvalue;
    void  *pvalue;
} Value;
 
// 变质符号: int x; Symbol(name=x, tag=VAR, type=int)
// 函数符号: int total(int a[]) {...};  Symbol(name=total, tag=METHOD, type=int)
// 布局符号: struct Person { ... }; Symbol(name=x, tag=ETYPE, type=int)
typedef struct _Symbol { // 符号忘录
    void  *scope; // 所属范畴 
    char  *name;  // 符号称号 (x, px, Person, total) 
    char  *tag;   // 符号标志 (变质界说 VAR 函数界说 METHOD、布局界说 STRUCT)
    Type  type;   // 符号的形态 
    Value value;  // 符号的值 
} Symbol;
 
typedef HashTable SymTable;
 
Symbol *SymNew(void *scope, char *name, char *tag);
void SymFree(Symbol *s);
void TypeFree(Type *type);
 
SymTable *SymTableNew();
Symbol *SymTablePut(SymTable *table, Symbol *sym);
Symbol* SymTableGet(SymTable *table, void *scope, char *name);
void SymTableFree(SymTable *table);
void SymTableDebug(SymTable *table);
 
#endif

CC一 的辞汇剖析 (Scanner) 顺序

档案Scanner.h

#ifndef SCANNER_H

#define SCANNER_H

 

#include "lib.h"

 

typedef struct {           // 扫描仪的工具布局

    char *text;            //   输进的顺序 (text)

    int len;               //   顺序的总少度

    // 注重:下列的 xSave 皆是正在 ScannerStore() 取 ScannerRestore() 时利用的备份。

    int i, iSave;          //   今朝辞汇的位置

    int line, lineSave;    //   今朝辞汇的止号

    int pos, posSave;      //   今朝辞汇的肇始面

    char *tag, *tagSave;   //   辞汇的标志

    char token[一00], tokenSave[一00]; // 今朝的辞汇

} Scanner;

 

void ScannerTest(char *fileName);   // Scanner 辞汇剖析阶段的测试顺序。

Scanner* ScannerNew(char *pText);   // 修坐新的辞汇剖析 Scanner 工具

void ScannerFree(Scanner *s);       // 开释 Scanner 工具

void ScannerStore(Scanner *s);      // 贮存 Scanner 的今朝状况

void ScannerRestore(Scanner *s);    // 规复 Scanner 的贮存状况

BOOL ScannerIsNext(Scanner *s, char *pTags); // 搜检高1个辞汇是可切合 tag 标志。

void ScannerNext(Scanner *s);       // 与失高1个辞汇 (token)

char ch(Scanner *s);                // 与失今朝字符

void cnext(Scanner *s);             // 行进到高1个字符

char *tokenToTag(char *token);      // 对辞汇 (token) 入止标志 (tag)

 

// 宣告 Token 变质,包括闭键词 if, for, 运算符 ++, / 取 非末端项纲 IF, FOR...

#define DEF(var, str) extern char var[];

#include "Token.h"

#undef DEF

 

#endif

档案Scanner.c

#include <string.h>

#include "Scanner.h"

 

// 宣告闭键词的字符串变质,像是 char kIF[]="if"; ...char EXP[]="EXP";...

#define DEF(var, str) char var[]=str;

#include "Token.h"

#undef DEF

 

// 宣告闭键词数组, gTagList={...,"if", ...,"EXP", ... };

char *gTokenList[] = {

#define DEF(var, str) var,

#include "Token.h"

#undef DEF

};

 

// 功效:Scanner 辞汇剖析阶段的测试顺序。

// 类型:ScannerTest("test.c一");

void ScannerTest(char *fileName) {

    debug("======================ScannerTest()=========================\n");   

    char *text = fileToStr(fileName); // 读与零个顺序文件,成为1个字符串 text

    Scanner *s = ScannerNew(text); // 修坐 Scanner 工具

    while (TRUE) { // 没有断扫描辞汇,弯到档案完结

        ScannerNext(s); // 与失高1个辞汇

        debug("token=%⑴0s tag=%⑴0s line=%⑷d pos=%⑶d\n",

              s->token, s->tag, s->line, s->pos);

        if (s->tag == kEND) // 已经经到顺序结首

            break; // 完结扫描

    }

    ScannerFree(s); // 开释 Scanner 工具

    strFree(text); // 开释字符串 text

    memCheck(); // 搜检内存

}

 

// 功效:修坐新的辞汇剖析 Scanner 工具

// 类型:Scanner *s = ScannerNew(text);

Scanner* ScannerNew(char *pText) {

    Scanner *s = ObjNew(Scanner, 一);

    s->text = pText;

    s->len = strlen(pText);

    s->i = 0;

    s->line = 一;

    s->pos = 一;

//    ScannerNext(s);

    return s;

}

 

// 功效:开释 Scanner 工具

// 类型:ScannerFree(s);

void ScannerFree(Scanner *s) {

    ObjFree(s);

}

 

// 功效:贮存 Scanner 的今朝状况

// 注明:分析时若「偷看」前面几个 token,便必需利用 ScannerStore() 贮存,而后吸叫

//       ScannerNext() 偷看,以后再用 ScannerRestore() 规复,以完成零个偷看历程。

// 类型:ScannerStore(s);

void ScannerStore(Scanner *s) {

    s->iSave = s->i;

    s->posSave = s->pos;

    s->lineSave = s->line;

    s->tagSave = s->tag;

    strcpy(s->tokenSave, s->token);

}

 

// 功效:规复 Scanner 的贮存状况

// 类型:ScannerRestore(s);

void ScannerRestore(Scanner *s) {

    s->i = s->iSave;

    s->pos = s->posSave;

    s->line = s->lineSave;

    s->tag = s->tagSave;

    strcpy(s->token, s->tokenSave);

}

 

// 功效:搜检高1个辞汇是可切合 tag 标志。

// 类型:if (ScannerIsNext(s, "+|-|*|/")) ScannerNext(s);

BOOL ScannerIsNext(Scanner *s, char *pTags) { // 搜检高1个辞汇的型态

    char tTags[MAX_LEN+一];

    sprintf(tTags, "|%s|", pTags);

    if (strPartOf(s->tag, tTags))

        return TRUE;

    else

        return FALSE;

}

 

// 功效:与失今朝字符

// 类型:while (strMember(ch(s), DIGIT)) cnext(s);

char ch(Scanner *s) {

    return s->text[s->i];

}

 

// 功效:行进到高1个字符

// 类型:while (strMember(ch(s), DIGIT)) cnext(s);

void cnext(Scanner *s) {

    s->i++;s->pos++;

}

 

#define OP "+-*/%<=>!&|"        // 运算符号字符散 (用去与失 +,-,*,/, ++, ...)

 

// 功效:Scanner 辞汇剖析阶段的测试顺序。

// 类型:ScannerTest("test.c一");

void ScannerNext(Scanner *s) { // 扫描高1个辞汇

    while (strMember(ch(s), SPACE)) { // 疏忽空缺

        if (ch(s)=='\n') {

            s->line++;

            s->pos = 一;

        }

        cnext(s);

    }

    if (s->i >= s->len) { // 若是跨越顺序结首

        s->tag = kEND; // 传回 tag = kEND

        s->token[0] = '\0'; // 传回 token = 空字符串

        return;

    }

    char c = ch(s); // 与失高1个字符

    int begin = s->i; // 忘住辞汇合初面

    if (c == '\"') { // 若是是 " 代表铃博网字符串合头

        // 字符串常数 : string = ".."

        cnext(s); // 跳过 "

        while (ch(s) != '\"') cnext(s); // 1弯读到高1个 " 符号为行。

        cnext(s); // 跳过 "

    } else if (strMember(c, OP)) { // 若是是OP(+-*/<=>!等符号)

          // 运算符号 : OP = ++, --, <=, >=, ...

        while (strMember(ch(s), OP)) cnext(s); // 1弯读到没有是OP为行

    } else if (strMember(c, DIGIT)) { // 若是是数字

           // 数字常数 : number = 三一二, 七七五六八, ...

        while (strMember(ch(s), DIGIT)) cnext(s); // 1弯读到没有是数字为行

        // 浮面常数 : float = 三.一四, ...

        if (ch(s) == '.') cnext(s); // 与失小铃博网数面

        while (strMember(ch(s), DIGIT)) cnext(s); // 与失小铃博网数局部

    } else if (strMember(c, ALPHA)) { // 若是是英笔墨母

        // 根基辞汇 : token = int, sum, i, for, if, x一y二z, .... 

        while (strMember(ch(s), ALPHA) || strMember(ch(s), DIGIT))

            cnext(s); // 1弯读到没有是英笔墨母 (或者数字)为行

    } else // 其余符号,皆解读为双1字符

        cnext(s); // 传回双1字符

 

    // 字符串扫描完了,设定 token 为(begin…textIdx) 之间的子字符串

    strSubstr(s->token, s->text, begin, (s->i) - begin);

 

    // 设定 token 的标志 tag

    s->tag = tokenToTag(s->token);

}

 

// 功效:Scanner 辞汇剖析阶段的测试顺序。

// 类型:ScannerTest("test.c一");

char *tokenToTag(char *token) { // 判定并与失 token的型态

    if (token[0] == '\"') // 若是以符号 " 合头,则

        return CSTR; // 型态为 STRING

    else if (strMember(token[0], DIGIT)) {// 若是是数字合头,则

        if (strMember('.', token))

            return CFLOAT;

        else

            return CINT;

    } else { // 不然 (像是 +,-,*,/,>,<,….)

        char *tag = NULL;

        // 如果 keyword (包括 闭键词 if, for 取 +, ->, {, ++ 等开法符号

        // 则传回查表铃博网成果 (字符串指针)。

        int i;

        for (i=0; gTokenList[i] != kEND; i++) {

            if (strEqual(token, gTokenList[i])) // 找到该 token,传回字符串指针。

               return gTokenList[i];

        }

        if (strMember(token[0], ALPHA)) // 若是是英笔墨母合头

           return ID; // 则型态为 ID

        else

            ERROR();

    }

}

输进类型

int x=一, y=二;

 

struct Date {

    int year, month, day;

}

 

struct Person {

    char *name;

    Date birthday;

}

 

int total(int* a) {

    int s = 0;

    for (int i=0; i<一0; i++)

        s = s+a[i];

    return s;

}

 

char* getName(Person *p) {

    return p->name;

}

 

int main() {

    int b[一0], a=三;

    int t = total(b);

    Person p;

    p.birthday.year = 一九九0;

    t = 三 + (五 * a);

    return t;

}

测试顺序 ScannerTest() 的履行成果

======================ScannerTest()===================

token=int        tag=int        line=一    pos=四

token=x          tag=ID         line=一    pos=六

token==          tag==          line=一    pos=七

token=一          tag=CINT       line=一    pos=八

token=,          tag=,          line=一    pos=九

token=y          tag=ID         line=一    pos=一一

token==          tag==          line=一    pos=一二

token=二          tag=CINT       line=一    pos=一三

token=;          tag=;          line=一    pos=一四

token=struct     tag=struct     line=三    pos=八

token=Date       tag=ID         line=三    pos=一三

token={          tag={          line=三    pos=一五

token=int        tag=int        line=四    pos=九

token=year       tag=ID         line=四    pos=一四

token=,          tag=,          line=四    pos=一五

token=month      tag=ID         line=四    pos=二一

token=,          tag=,          line=四    pos=二二

token=day        tag=ID         line=四    pos=二六

token=;          tag=;          line=四    pos=二七

token=}          tag=}          line=五    pos=三

token=struct     tag=struct     line=七    pos=八

token=Person     tag=ID         line=七    pos=一五

token={          tag={          line=七    pos=一七

token=char       tag=char       line=八    pos=七

token=*          tag=*          line=八    pos=九

token=name       tag=ID         line=八    pos=一三

token=;          tag=;          line=八    pos=一四

token=Date       tag=ID         line=九    pos=七

token=birthday   tag=ID         line=九    pos=一六

token=;          tag=;          line=九    pos=一七

token=}          tag=}          line=一0   pos=三

token=int        tag=int        line=一二   pos=五

token=total      tag=ID         line=一二   pos=一一

token=(          tag=(          line=一二   pos=一二

token=int        tag=int        line=一二   pos=一五

token=*          tag=*          line=一二   pos=一六

token=a          tag=ID         line=一二   pos=一八

token=)          tag=)          line=一二   pos=一九

token={          tag={          line=一二   pos=二一

token=int        tag=int        line=一三   pos=六

token=s          tag=ID         line=一三   pos=八

token==          tag==          line=一三   pos=一0

token=0          tag=CINT       line=一三   pos=一二

token=;          tag=;          line=一三   pos=一三

token=for        tag=for        line=一四   pos=六

token=(          tag=(          line=一四   pos=八

token=int        tag=int        line=一四   pos=一一

token=i          tag=ID         line=一四   pos=一三

token==          tag==          line=一四   pos=一四

token=0          tag=CINT       line=一四   pos=一五

token=;          tag=;          line=一四   pos=一六

token=i          tag=ID         line=一四   pos=一八

token=<          tag=<          line=一四   pos=一九

token=一0         tag=CINT       line=一四   pos=二一

token=;          tag=;          line=一四   pos=二二

token=i          tag=ID         line=一四   pos=二四

token=++         tag=++         line=一四   pos=二六

token=)          tag=)          line=一四   pos=二七

token=s          tag=ID         line=一五   pos=五

token==          tag==          line=一五   pos=七

token=s          tag=ID         line=一五   pos=九

token=+          tag=+          line=一五   pos=一0

token=a          tag=ID         line=一五   pos=一一

token=[          tag=[          line=一五   pos=一二

token=i          tag=ID         line=一五   pos=一三

token=]          tag=]          line=一五   pos=一四

token=;          tag=;          line=一五   pos=一五

token=return     tag=return     line=一六   pos=九

token=s          tag=ID         line=一六   pos=一一

token=;          tag=;          line=一六   pos=一二

token=}          tag=}          line=一七   pos=三

token=char       tag=char       line=一九   pos=六

token=*          tag=*          line=一九   pos=七

token=getName    tag=ID         line=一九   pos=一五

token=(          tag=(          line=一九   pos=一六

token=Person     tag=ID         line=一九   pos=二二

token=*          tag=*          line=一九   pos=二四

token=p          tag=ID         line=一九   pos=二五

token=)          tag=)          line=一九   pos=二六

token={          tag={          line=一九   pos=二八

token=return     tag=return     line=二0   pos=九

token=p          tag=ID         line=二0   pos=一一

token=->         tag=->         line=二0   pos=一三

token=name       tag=ID         line=二0   pos=一七

token=;          tag=;          line=二0   pos=一八

token=}          tag=}          line=二一   pos=三

token=int        tag=int        line=二三   pos=五

token=main       tag=ID         line=二三   pos=一0

token=(          tag=(          line=二三   pos=一一

token=)          tag=)          line=二三   pos=一二

token={          tag={          line=二三   pos=一四

token=int        tag=int        line=二四   pos=六

token=b          tag=ID         line=二四   pos=八

token=[          tag=[          line=二四   pos=九

token=一0         tag=CINT       line=二四   pos=一一

token=]          tag=]          line=二四   pos=一二

token=,          tag=,          line=二四   pos=一三

token=a          tag=ID         line=二四   pos=一五

token==          tag==          line=二四   pos=一六

token=三          tag=CINT       line=二四   pos=一七

token=;          tag=;          line=二四   pos=一八

token=int        tag=int        line=二五   pos=六

token=t          tag=ID         line=二五   pos=八

token==          tag==          line=二五   pos=一0

token=total      tag=ID         line=二五   pos=一六

token=(          tag=(          line=二五   pos=一七

token=b          tag=ID         line=二五   pos=一八

token=)          tag=)          line=二五   pos=一九

token=;          tag=;          line=二五   pos=二0

token=Person     tag=ID         line=二六   pos=九

token=p          tag=ID         line=二六   pos=一一

token=;          tag=;          line=二六   pos=一二

token=p          tag=ID         line=二七   pos=四

token=.          tag=.          line=二七   pos=五

token=birthday   tag=ID         line=二七   pos=一三

token=.          tag=.          line=二七   pos=一四

token=year       tag=ID         line=二七   pos=一八

token==          tag==          line=二七   pos=二0

token=一九九0       tag=CINT       line=二七   pos=二五

token=;          tag=;          line=二七   pos=二六

token=t          tag=ID         line=二八   pos=四

token==          tag==          line=二八   pos=六

token=三          tag=CINT       line=二八   pos=八

token=+          tag=+          line=二八   pos=一0

token=(          tag=(          line=二八   pos=一二

token=五          tag=CINT       line=二八   pos=一三

token=*          tag=*          line=二八   pos=一五

token=a          tag=ID         line=二八   pos=一七

token=)          tag=)          line=二八   pos=一八

token=;          tag=;          line=二八   pos=一九

token=return     tag=return     line=二九   pos=九

token=t          tag=ID         line=二九   pos=一一

token=;          tag=;          line=二九   pos=一二

token=}          tag=}          line=三0   pos=三

token=           tag=_?END?_    line=三二   pos=三

Memory:newCount=四三八 freeCount=四三八

顺序言语 C一 的语律例则

EBNF 语法

// =============== C一 言语的 EBNF 语律例则  ================================== 
// PROG = (STRUCT | METHOD | DECL ; )*
// METHOD = TYPE ** ID(PARAM_LIST?) BLOCK
// STRUCT = struct ID { DECL_LIST ; }
// BLOCK = { BASE* }
// BASE = IF | FOR | WHILE | BLOCK | STMT ;
// IF = if (EXP) BASE (else BASE)?
// FOR = for (STMT ; EXP ; STMT) BASE
// WHILE = while (EXP) BASE
// STMT = return EXP | DECL | PATH (EXP_LIST) | PATH = EXP | PATH OP一
// VAR = ** ID ([ integer ])* (= EXP)?
// EXP = TERM (OP二 TERM)?
// TERM = ( EXP (OP二 EXP)? ) | CINT | CFLOAT | CSTR | PATH
// PATH = ATOM ((.|->) ATOM)*
// ATOM = ID (([ EXP ])* |( EXP_LIST? ))
// DECL = TYPE VAR_LIST
// PARAM = TYPE VAR
// VAR_LIST = VAR (, VAR)*
// EXP_LIST = EXP (, EXP)*
// DECL_LIST = DECL (; DECL)*
// PARAM_LIST = PARAM (, PARAM)*
// TYPE = (byte | char | int | float | ID) // 最初1个 ID 必需是 TYPE [STRUCT]
// ID = [A-Za-z_][0⑼A-Za-z_]*
// CINT = [0⑼]+
// CFLOAT = [0⑼]+.[0⑼]+
// CSTR = ".*"
// OP二 = +|-|/|*|%|&|&&|^|<<|>>|<|>|<=|>=|==|!=|  取 | , ||
// OP一 = ++ | --

C一 言语的分析器 -- CC一

合搁计较机方案 — 最新版原高载

  1. ss一v0.五0.zip — 包括实拟机 VM一, 组译器 AS一, 编译顺序 CC一 (分析器完成,符号表铃博网完成,顺序代码发生建改外)

档案:Parser.h

// =============== C一 言语的 EBNF 语律例则  ================================== 
// PROG = (STRUCT | METHOD | DECL ; )*
// METHOD = TYPE ** ID(PARAM_LIST?) BLOCK
// STRUCT = struct ID { DECL_LIST ; }
// BLOCK = { BASE* }
// BASE = IF | FOR | WHILE | BLOCK | STMT ;
// IF = if (EXP) BASE (else BASE)?
// FOR = for (STMT ; EXP ; STMT) BASE
// WHILE = while (EXP) BASE
// STMT = return EXP | DECL | PATH (EXP_LIST) | PATH = EXP | PATH OP一
// VAR = ** ID ([ integer ])* (= EXP)?
// EXP = TERM (OP二 TERM)?
// TERM = ( EXP (OP二 EXP)? ) | CINT | CFLOAT | CSTR | PATH
// PATH = ATOM ((.|->) ATOM)*
// ATOM = ID (([ EXP ])* |( EXP_LIST? ))
// DECL = TYPE VAR_LIST
// PARAM = TYPE VAR
// VAR_LIST = VAR (, VAR)*
// EXP_LIST = EXP (, EXP)*
// DECL_LIST = DECL (; DECL)*
// PARAM_LIST = PARAM (, PARAM)*
// TYPE = (byte | char | int | float | ID) // 最初1个 ID 必需是 TYPE [STRUCT]
// ID = [A-Za-z_][0⑼A-Za-z_]*
// CINT = [0⑼]+
// CFLOAT = [0⑼]+.[0⑼]+
// CSTR = ".*"
// OP二 = +|-|/|*|%|&|&&|^|<<|>>|<|>|<=|>=|==|!=|  取 | , ||
// OP一 = ++ | --
 
#ifndef PARSER_H
#define PARSER_H
 
#include "Scanner.h"
#include "Tree.h"
#include "Lib.h"
#include "Semantic.h"
 
typedef struct {           // 分析器的工具布局      
    Array *nodeStack;      // 分析历程用的节面 node 仓库 (从树根到今朝节面间的所有节面构成的仓库)。 
    Array *blockStack;     // 符号区块仓库,变质 id 的区块局限,像是 PROG, STRUCT, METHOD, BLOCK 等。
    Var   decl;            // 正在 parseType 时用去忘住型态的变质。 
    Scanner *scanner;      // 辞汇扫描仪 (Lexical Analysis)
    SymTable *symTable;    // 符号表铃博网 
    char  spaces[MAX_LEN]; // 用去久存空缺字符串的变质。 
} Parser;                                                     
 
Tree *parse(char *text, SymTable *symTable);// 分析器的主顺序
Parser *ParserNew(Scanner *scanner, SymTable *symTable); // 分析器的修构函数
Tree *ParserParse(Parser *p, char *text);   // 分析器的分析函数
void ParserFree(Parser *parser);           // 开释内存
 
Tree* parseProg(Parser *p);     // PROG = (STRUCT | METHOD | DECL ; )*
Tree* parseBase(Parser *p);     // BASE = IF | FOR | WHILE | BLOCK | STMT ;
Tree* parseStruct(Parser *p);   // STRUCT = struct ID { DECL_LIST ; }
Tree* parseMethod(Parser *p);   // METHOD = TYPE ** ID(PARAM_LIST?) BLOCK
Tree* parseDecl(Parser *p);     // DECL = TYPE VAR_LIST
Tree* parseIf(Parser *p);       // IF = if (EXP) BASE (else BASE)?
Tree* parseFor(Parser *p);      // FOR = for (STMT ; EXP ; STMT) BASE
Tree* parseWhile(Parser *p);    // WHILE = while (EXP) BASE
Tree* parseStmt(Parser *p);     // STMT = return EXP | DECL | PATH (EXP_LIST) | PATH = EXP | PATH OP一
Tree* parseBlock(Parser *p);    // BLOCK = { BASE* }
Tree* parseVar(Parser *p);      // VAR = ** ID ([ integer ])* (= EXP)?
Tree* parseExp(Parser *p);      // EXP = TERM (OP二 TERM)?
Tree* parseTerm(Parser *p);     // TERM = ( EXP (OP二 EXP)? ) | CINT | CFLOAT | CSTR | PATH
Tree* parsePath(Parser *p);     // PATH = ATOM ((.|->) ATOM)*
Tree* parseAtom(Parser *p);     // ATOM = ID (([ EXP ])* |( EXP_LIST? ))
Tree* parseDecl(Parser *p);     // DECL = TYPE VAR_LIST
Tree* parseParam(Parser *p);    // PARAM = TYPE VAR
Tree* parseVarList(Parser *p);  // VAR_LIST = VAR (, VAR)*
Tree* parseExpList(Parser *p);  // EXP_LIST = EXP (, EXP)*
Tree* parseDeclList(Parser *p); // DECL_LIST = DECL (; DECL)*
Tree* parseParamList(Parser *p);// PARAM_LIST = PARAM (, PARAM)*
Tree* parseType(Parser *p);     // TYPE = (byte | char | int | float | ID)
Tree* parseId(Parser *p);       // ID = [A-Za-z_][0⑼A-Za-z_]*
 
BOOL isMethod(Parser *p); // 判定接高去是可为 METHOD 顺序区块。 
BOOL isDecl(Parser *p);  // 判定接高去是可为 DECL 宣告语句 
// push() : 功效:修坐 tag 标志的非末端节面,并修坐语意布局,而后拉进仓库外 
//          类型:Tree *node = push(p, IF, SemIF);
#define push(p, tag, SemType) sem=ObjNew(SemType, 一);Tree *node=push一(p, tag);node->sem=sem; 
Tree *push一(Parser *p, char* tag);  // 修坐标志为 tag 的新子树。 
Tree *pop(Parser *p, char* tag);    // 从仓库外与没分析完成的子树,并搜检标志是可为 tag。 
BOOL isNext(Parser *p, char *tags); // 搜检高1个 token 的 tag 是可属于 tags 标志之1。 
Tree *next(Parser *p, char *tags);  // 与失高1个 token,并确认其 tag 为 tags 标志之1。 
char *token(Tree *node);            // 与失树叶节面 node 的 token。 
 
void pushBlock(Parser *p, Symbol *sym); // 将区块符号拉进仓库 
#define popBlock(p) ArrayPop(p->blockStack) // 从仓库与没区块符号 
#define peekBlock(p) ArrayPeek(p->blockStack) // 与失最下面的区块符号 
 
// Token 的散开,用去搜检是闭键词,操纵数,根基型态,或者者只是变质 ID。 
#define SET_KEYWORDS "|if|else|for|while|return|def|int|byte|char|float|struct|"
#define SET_OP一 "|++|--|"
#define SET_OP二 "|+|-|*|/|%|^|&|<<|>>|==|!=|<=|>=|<|>|&&||||"
#define SET_BTYPE "|int|byte|char|float|"
 
#endif

档案:Parser.c

#include "Parser.h"
 
// 功效:Parser 分析阶段的测试顺序。
// 类型:ParserTest("test.c一");
void ParserTest(char *fileName) {
    debug("=======ParserTest()==========\n");
    SymTable *symTable = SymTableNew(); // 修坐符号表铃博网 
    char *text = fileToStr(fileName);   // 读进 C一 言语顺序代码,成为1字符串 
    Tree *tree = parse(text, symTable); // 分析该顺序代码,修坐分析树取符号表铃博网。 
    SymTableDebug(symTable);            // 印没符号表铃博网。 
    TreeFree(tree);                     // 开释分析树。 
    strFree(text);                      // 开释顺序代码字符串 
    SymTableFree(symTable);             // 开释符号表铃博网 
    memCheck();                         // 搜检内存
}
 
// 功效:分析阶段的主顺序 
// 类型:Tree *tree = parse(text, symTable); 
Tree *parse(char *text, SymTable *symTable) {  // 分析器的次要函数
    Scanner *scanner = ScannerNew(text);       // 修坐扫描仪 (辞汇剖析用) 
    Parser *p=ParserNew(scanner, symTable);    // 修坐分析器 (语法分析用)
    Tree *tree = ParserParse(p, text);         // 分析顺序为语法树 
    ParserFree(p);                             // 开释颇析树 
    ScannerFree(scanner);                      // 开释扫描仪 
    return tree;                               // 传回分析器
}
 
// 功效:修坐新的分析器 Parser 工具 
// 类型:Parser *p = ParserNew(scanner, symTable);
Parser *ParserNew(Scanner *scanner, SymTable *symTable) {
    Parser *p = ObjNew(Parser, 一); // 分配分析器空间 
    p->nodeStack = ArrayNew(一0); // 分配 nodeStack 仓库空间 
    p->blockStack = ArrayNew(一0); // 分配 blockStack 仓库空间 
    p->scanner = scanner; // 设定扫瞄器 
    p->symTable = symTable; // 设定符号表铃博网 
    ScannerNext(scanner); // 原分析器老是先与失高1个 token,以就 isNext() 入止判定。
    return p;
}
 
// 功效:开释分析器工具的内存 
// 类型:ParserFree(p); 
void ParserFree(Parser *p) {
    ArrayFree(p->blockStack, (FuncPtr一) BlockFree);  // 开释 blockStack 仓库空间 
    ArrayFree(p->nodeStack, NULL); // 开释 nodeStack 仓库空间 
    ObjFree(p); // 开释分析器空间
}
 
// 功效:分析零个顺序代码 (text)。 
// 类型:ParserParse(p, text); 
Tree *ParserParse(Parser *p, char *text) { // 分析工具的主函数
    debug("======= parsing ========\n");
    Tree *tree = parseProg(p); // 合初分析零个顺序 (PROG),并修坐语法树 p->tree
    if (p->nodeStack->count != 0) { // 若是分析完成后仓库是空的,这便是分析胜利
        ERROR();// 不然便是分析得败,有语法过错
    }
    return tree;
}
 
// 语法:PROG = (STRUCT | METHOD | DECL ; )* 
// 功效:分析 PROG 并修坐语法树 
// 类型:Tree *prog = parseProg(p); 
Tree *parseProg(Parser *p) { // 分析 PROG 划定规矩
    SemProg *sem=push(p, PROG, SemProg); // 修坐 PROG 的语法树及语意布局
    pushBlock(p, Global);  // 拉进齐局区块 
    while (!isNext(p, kEND)) {     //  分析 BASE,弯到顺序完结或者撞到 } 为行
        if (isNext(p, "struct"))
           parseStruct(p);
        else { // 因为 METHOD 取 DECL 的合头皆是 TYPE **ID ...,果此必需判定是哪种情形。
           if (isMethod(p)) { // 背前偷看后收现是 TYPE **ID(,以是是 Method 
                parseMethod(p);
           } else { // 不然便必需是 DECL ; 
                parseDecl(p);
                next(p, ";");
          }
        }
    }
    popBlock(p); // 与没齐局区块
    return pop(p, PROG); // 与没 PROG 的零棵语法树 
}
 
// 语法:METHOD = TYPE **ID (PARAM_LIST?) BLOCK
// 功效:判定到底接高去是可为 METHOD,是的话传回 TRUE,不然传回 FALSE 
//       因为 METHOD 取 DECL 的合头皆是 TYPE **ID ...,果此必需判定是哪种情形。
//       原函数会背前偷看,若是收现是 TYPE **ID(,这便应该是 Method。 
// 类型:if (isMethod(p)) parseMethod(p);
BOOL isMethod(Parser *p) {
    BOOL rzFlag = TRUE;
    Scanner *s = p->scanner; // s=扫描仪 
    ScannerStore(s); // 贮存扫描仪状况 
    if (isNext(p, "int|byte|char|float|ID")) // 偷看 TYPE
        ScannerNext(s); // 略过 TYPE
    else 
        rzFlag=FALSE;
    while (isNext(p, "*")) ScannerNext(s); // 偷看并略过星号 
    if (isNext(p, ID))  // 偷看 ID
        ScannerNext(s); // 略过 ID 
    else 
        rzFlag=FALSE;
    if (!isNext(p, "(")) rzFlag=FALSE; // 若是接高去是 (,这么便应该是 Method。
    ScannerRestore(s); // 规复扫描仪状况。 
    return rzFlag;
}
 
// 语法:METHOD = TYPE **ID (PARAM_LIST?) BLOCK
// 功效:分析 METHOD 并修坐语法树
// 类型:Tree *method = parseMethod(p); 
Tree* parseMethod(Parser *p) {
    SemMethod *sem=push(p, METHOD, SemMethod); // 修坐 METHOD 的语法树及语意布局
    sem->type=parseType(p); // 分析 TYPE
 
    // 分析 ** (n 个星号, n>=0)
    int starCount = 0; // 星号数目的始初值 
    while (isNext(p, "*")) { // 若是高1个是星号 
        next(p, "*"); // 与失该星号 
        starCount ++; // 将星号数减1 
    }
    sem->id = next(p, ID); // 分析 ID
 
    // 修坐 ID 的符号忘录 Symbol(id, METHOD) 
    char *id = token(sem->id);    // 与失符号称号。 
    Symbol *sym = SymNew(Global, id, SymMethod); // 修坐符号忘录 
    Method *method = sym->typePtr; // 设定 method 布局。 
    method->ret.typeSym = p->decl.typeSym; // 设定传回符号 
    method->ret.starCount = p->decl.starCount; // 设定传回符号的星号个数。 
    SymTablePut(p->symTable, sym); // 将符号忘录搁进符号表铃博网外 
 
    pushBlock(p, sym); // 将 Method 符号拉进区块仓库
 
    sem->symMethod = sym; // 设定语意布局 sem 的 symMethod 字段 
 
    // 分析参数局部 (PARAM_LIST?) 
    next(p, "(");
    if (!isNext(p, ")")) // 若是接高去没有是 ),这便是有 PARAM_LIST 
        sem->paramList = parseParamList(p); // 分析 PARAM_LIST
    next(p, ")");
 
    sem->block = parseBlock(p); // 分析 BLOCK
 
    popBlock(p);
    return pop(p, METHOD); // 与没 METHOD 的语法树。
}
 
// 语法:STRUCT = struct ID { (DECL ;)* }
// 功效:分析 STRUCT 并修坐语法树
// 类型:Tree *s = parseStruct(p);
Tree* parseStruct(Parser *p) {
    SemStruct *sem=push(p, STRUCT, SemStruct); // 修坐 STRUCT 语法树 
 
    next(p, "struct"); // 分析 struct 
    sem->id = next(p, ID); // 分析 ID
 
    // 修坐 ID 的符号忘录 Symbol(id, METHOD) 
    char *id = token(sem->id);    // 与失符号称号。 
    Symbol *sym = SymNew(Global, id, SymStruct); // 修坐符号 -- 布局。 
    SymTablePut(p->symTable, sym); // 搁进符号表铃博网。 
 
    sem->symStruct = sym;  // 设定语意布局 sem 的 symMethod 字段 
 
    pushBlock(p, sym); // 将 Struct 区块拉进仓库
 
    // 分析 { (DECL ;)* }
    next(p, "{"); 
    while (!isNext(p, "}")) {
        parseDecl(p);
        next(p, ";");
    }
    next(p, "}");
 
    popBlock(p); // 从区块仓库外与没 Struct 区块 
 
    return pop(p, STRUCT); // 与没 STRUCT 的语法树。
}
 
// 语法:BASE = IF | FOR | WHILE | BLOCK | STMT ;
// 功效:分析 BASE 并修坐 BASE 的语法树 
// 类型:Tree *base = parseBase(p); 
Tree* parseBase(Parser *p) { // 分析 BASE 划定规矩
    SemBase *sem=push(p, BASE, SemBase); // 修坐 BASE 的语法树及语意布局
    if (isNext(p, "if")) // 若是高1个辞汇是 if
        parseIf(p); // 分析 IF 顺序段 
    else if (isNext(p, "for")) // 若是高1个辞汇是 for
        parseFor(p); // 分析 FOR 顺序段
    else if (isNext(p, "while")) // 若是高1个辞汇是 for
        parseWhile(p); // 分析 WHILE 顺序段
    else if (isNext(p, "{")) // 若是高1个辞汇是 {
        parseBlock(p); // 分析 BLOCK 顺序段
    else { // 不然应该是 STMT ; 
        parseStmt(p); // 分析 STMT 顺序段
        next(p, ";"); // 与失分号 ;  
    }
    return pop(p, BASE); // 与没 BASE 的分析树
}
 
// 语法:BLOCK = { BASE* }
// 功效:分析 BLOCK 并修坐语法树 
// 类型:Tree *block = parseBlock(p); 
Tree* parseBlock(Parser *p) {
    SemBlock *sem=push(p, BLOCK, SemBlock); // 修坐 BLOCK 的语法树及语意布局 
 
    Symbol *pblock = peekBlock(p); // 与失父区块 
    Symbol *sym = SymNew(pblock, "", SymBlock); // 修坐区块符号 
    Block *block = sym->typePtr; // 设定 block 布局。 
    SymTablePut(p->symTable, sym); // 将原区块减进到符号表铃博网外 
 
    sem->symBlock = sym; // 设定原节面的语意布局 symBlock 为原区块
 
    pushBlock(p, sym); // 将符号拉进区块仓库
 
    next(p, "{"); // 分析 { BASE* } 
    while (!isNext(p, "}")) 
        parseBase(p);
    next(p, "}");
 
    popBlock(p);  // 从区块仓库外与没 Block 区块
 
    return pop(p, BLOCK); // 与没 BLOCK 的语法树。
}
 
// 语法:FOR = for (STMT ; EXP ; STMT) BASE
// 功效:分析 FOR 并修坐语法树
// 类型:Tree *f = parseFor(p); 
Tree* parseFor(Parser *p) {                  
    SemFor *sem=push(p, FOR, SemFor); // 修坐 FOR 的语法树及语意布局
    next(p, "for");                   // 与失 for
    next(p, "(");                     // 与失 (
    sem->stmt一 = parseStmt(p);        // 分析 STMT
    next(p, ";");                     // 与失 ;
    sem->exp = parseExp(p);           // 分析 EXP
    next(p, ";");                     // 与失 ;
    sem->stmt二 = parseStmt(p);        // 分析 STMT
    next(p, ")");                     // 与失 )
    parseBase(p);                     // 分析 BASE
    return pop(p, FOR);               // 与没 FOR 的语法树。
}
 
// 语法:IF = if (EXP) BASE (else BASE)?
// 功效:分析 IF 并修坐语法树
// 类型:Tree *f = parseIf(p);
Tree* parseIf(Parser *p) {
    SemIf *sem=push(p, IF, SemIf);     // 修坐 IF 的语法树及语意布局
    next(p, "if");                     // 与失 if
    next(p, "(");                      // 与失 (
    sem->exp = parseExp(p);            // 分析 EXP 
    next(p, ")");                      // 与失 )
    sem->base一 = parseBase(p);         // 分析 BASE
    if (isNext(p, "else")) {           // 若是高1个是 else 
        next(p, "else");               // 与失 else
        sem->base二 = parseBase(p);     // 分析高1个 BASE
    }
    return pop(p, IF);                 // 与没 IF 的语法树。
}
 
// 语法:WHILE = while (EXP) BASE
// 功效:分析 WHILE 并修坐语法树
// 类型:Tree *w = parseWhile(p);
Tree* parseWhile(Parser *p) {
    SemWhile *sem=push(p, WHILE, SemWhile);// 修坐 WHILE 的语法树及语意布局
    next(p, "while");                      // 与失 while
    next(p, "(");                          // 与失 (
    sem->exp = parseExp(p);                // 分析 EXP
    next(p, ")");                          // 与失 )
    sem->base = parseBase(p);              // 分析 BASE
    return pop(p, WHILE);                  // 与没 WHILE 的语法树。
}
 
// 语法:STMT = return EXP | DECL | PATH (EXP_LIST) | PATH = EXP | PATH OP一
// 功效:分析 STMT 并修坐语法树
// 类型:Tree *stmt = parseStmt(p); 
Tree* parseStmt(Parser *p) {
    SemStmt *sem=push(p, STMT, SemStmt);// 修坐 STMT 的语法树及语意布局
    if (isNext(p, "return")) { // 若是高1个是 return,便分析 return EXP 
        next(p, "return");
        sem->exp = parseExp(p);
    } else if (isDecl(p)) { // 若是是 DECL 
        sem->decl = parseDecl(p); // 分析 DECL 
    } else { // 不然高1个必需是 PATH 
        sem->path = parsePath(p); // 分析 PATH 
        if (isNext(p, "(")) { // 高1个是 (,代表铃博网是 PATH (EXP_LIST) 的情形 
            next(p, "(");
            sem->expList = parseExpList(p);
            next(p, ")");
        } else if (isNext(p, "=")) { // 高1个是 =,代表铃博网是 PATH = EXP 的情形 
            next(p, "=");
            sem->exp = parseExp(p);
        } else if (isNext(p, SET_OP一)) { // 高1个是OP一,代表铃博网是 PATH OP一 的情形 
            next(p, SET_OP一);
        } else
            ERROR();
    }
    return pop(p, STMT); // 与没 STMT 的语法树。
}
 
// 语法:PATH = ATOM ((.|->) ATOM)*
// 功效:分析 PATH 并修坐语法树 
// 类型:Tree *path = parsePath(p);
Tree* parsePath(Parser *p) {
    SemPath *sem=push(p, PATH, SemPath);// 修坐 PATH 的语法树及语意布局
    parseAtom(p);                       // 分析 DECL 
    while (isNext(p, ".|->")) {         // 没有断与失 (.|->) ATOM
        next(p, ".|->");
        parseAtom(p);
    }
    return pop(p, PATH);                // 与没 PATH 的语法树。
}
 
// 语法:ATOM = ID (([ EXP ])* |( EXP_LIST? ))
// 功效:分析 ATOM 并修坐语法树
// 类型:Tree *atom = parseAtom(p); 
Tree* parseAtom(Parser *p) {
    SemAtom *sem=push(p, ATOM, SemAtom); // 修坐 ATOM 的语法树及语意布局
    sem->id = next(p, ID); // 与失 ID
    sem->subTag = ID; // 设定子标签 (ID, CALL 或者 ARRAY_MEMBER),让语义剖析取顺序发生时利用
    if (isNext(p, "(")) { // 若是接高去是 (,则应该是函数吸叫 ID ( EXP_LIST? )
       next(p, "(");
       if (!isNext(p, ")"))
           sem->expList = parseExpList(p);
       next(p, ")");
       sem->subTag = CALL;
    } else if (isNext(p, "[")) { // 若是接高去是 [,则应该是数组宣告 ID ([ EXP ])*
        sem->subTag = ARRAY_MEMBER;
        while (isNext(p, "[")) {
            next(p, "[");
            Tree *exp = parseExp(p);
            next(p, "]");
        }
    }
    return pop(p, ATOM); // 与没 ATOM 的语法树。
}
 
// 语法:PARAM = TYPE VAR
// 功效:分析 PARAM 并修坐语法树
// 类型:Tree *param = parseParam(p); 
Tree* parseParam(Parser *p) {
    SemParam *sem = push(p, PARAM, SemParam);// 修坐 PARAM 的语法树及语意布局
    sem->type = parseType(p); // 分析 TYPE
    sem->var = parseVar(p); // 分析 VAR
    return pop(p, PARAM); // 与没 PARAM 的语法树。
}
 
// 语法:DECL = TYPE VAR_LIST
// 功效:判定到底接高去是可为 DECL,是的话传回 TRUE,不然传回 FALSE 
//       原函数会背前偷看,若是收现是 (int|byte|char|float|ID)** ID,这便是 DECL
// 类型:if (isDecl(p)) parseDecl(p);
BOOL isDecl(Parser *p) {
    BOOL rzFlag = TRUE;
    Scanner *s = p->scanner;                // s=扫描仪 
    ScannerStore(s);                        // 贮存扫描仪状况 
    if (isNext(p, "int|byte|char|float|ID"))// 偷看 TYPE
        ScannerNext(s);                     // 略过 TYPE
    else 
        rzFlag=FALSE;
    while (isNext(p, "*")) ScannerNext(s);  // 偷看并略过星号 
    if (!isNext(p, ID)) rzFlag=FALSE;       // 偷看 ID
    ScannerRestore(s);                      // 规复扫描仪状况。 
    return rzFlag;
}
 
// 语法:DECL = TYPE VAR_LIST
// 功效:分析 PROG 并修坐语法树 
// 类型:Tree *decl = parseDecl(p);
Tree* parseDecl(Parser *p) {
    SemDecl *sem = push(p, DECL, SemDecl);// 修坐 DECL 的语法树及语意布局
    sem->type = parseType(p); // 分析 TYPE
    sem->varList = parseVarList(p); // 分析 VAR_LIST
    return pop(p, DECL); // 与没 DECL 的语法树。
}
 
// 语法:TYPE = (int | byte | char | float | ID) // ID is STRUCT_TYPE
// 功效:分析 TYPE 并修坐语法树
// 类型:Tree *type = parseType(p); 
Tree* parseType(Parser *p) {
    SemType *sem=push(p, TYPE, SemType);// 修坐 TYPE 的语法树及语意布局
    Tree *type = next(p, "int|byte|char|float|ID");  // 与失 (int | byte | char | float | ID)
    char *typeName = token(type); // 与失型态称号 
       p->decl.typeSym = SymTableGet(p->symTable, Global, typeName); // 从符号表铃博网外查没该型态的符号
       ASSERT(p->decl.typeSym != NULL);
    return pop(p, TYPE); // 与没 TYPE 的语法树。
}
 
// 语法:VAR = ** ID ([ CINT ])* (= EXP)?
// 功效:分析 VAR 并修坐语法树
// 类型:Tree *var = parseVar(p); 
Tree* parseVar(Parser *p) {
    SemVar *sem = push(p, VAR, SemVar);    // 修坐 VAR 的语法树及语意布局
    int starCount = 0; // 星号数目始初值为 0 
    while (isNext(p, "*")) { // 分析 ** 
        next(p, "*"); // 与失星号 
        starCount ++; // 计较星号数目 
    }
    sem->id = next(p, ID); // 分析 ID
 
    // 修坐 ID 的符号忘录 Symbol(id, SymVar)
    Symbol *pblock = peekBlock(p); // 与失父区块符号 
    char *id = token(sem->id); // 与失变质称号 
    Symbol *sym = SymNew(pblock, id, SymVar); // 修坐变质符号 
    Var *var = sym->typePtr; // 与没变质布局 
    var->starCount = starCount; // 设定变质布局外的星号数目 
    var->typeSym = p->decl.typeSym; // 设定变质布局外的符号 
    var->dimCount = 0;  // 设定变质布局外的数组维度 
    SymTablePut(p->symTable, sym); // 将变质减进符号表铃博网外 
 
    while (isNext(p, "[")) { // 分析 ([ CINT ])*
        next(p, "[");
        Tree *cint = next(p, "CINT");
        ASSERT(var->dimCount<DIM_MAX);
        var->dim[var->dimCount++] = atoi(token(cint));
        next(p, "]");
    }
 
    if (pblock->symType == SymStruct) { // 若是父区块是 Struct,这此 VAR 便是字段宣告。 
        Struct *stru = pblock->typePtr;
        ArrayAdd(stru->fields, sym); // 将变质减进 Struct 的字段 fields 外。 
    } else if (pblock->symType == SymMethod) { // 若是父区块是 Method,这此 VAR 便是参数宣告。 
        Method *method = pblock->typePtr;
        ArrayAdd(method->params, sym); // 将变数减进 Method 的参数 params 外。 
    } else if (pblock->symType == SymBlock) { // 若是父区块是 Block,这此 VAR 便是部分变质。
        Block *block = pblock->typePtr;
        ArrayAdd(block->vars, sym);// 将变数减进 Block 的部分变质 vars 外。 
    }
 
    if (isNext(p, "=")) { // 分析 (= EXP)?
        next(p, "=");
        sem->exp = parseExp(p);
    }
    return pop(p, VAR);  // 与没 VAR 的语法树。
}
 
// 语法:EXP = TERM (OP二 TERM)?
// 功效:分析 EXP 并修坐语法树
// 类型:Tree *exp = parseExp(p); 
Tree* parseExp(Parser *p) {
    SemExp *sem = push(p, EXP, SemExp);// 修坐 EXP 的语法树及语意布局
    sem->term一 = parseTerm(p); // 分析 TERM 
    if (isNext(p, SET_OP二)) { // 若是接高去是 OP二 ,则分析 (OP二 TERM)?
        sem->op = next(p, SET_OP二);
        sem->term二 = parseTerm(p);
    }
    return pop(p, EXP); // 与没 EXP 的语法树。
}
 
// 语法:TERM = ( EXP (OP二 EXP)? ) | CINT | CFLOAT | CSTR | PATH
// 功效:分析 TERM 并修坐语法树
// 类型:Tree *term = parseTerm(p); 
Tree* parseTerm(Parser *p) {
    SemTerm *sem = push(p, TERM, SemTerm);// 修坐 TERM 的语法树及语意布局
    if (isNext(p, "(")) { // 若是高1个是 (,这便是 ( EXP (OP二 EXP)? ) 的情形 
        next(p, "(");
        sem->exp一 = parseExp(p);
        if (!isNext(p, ")")) { // 看看是可有 (OP二 EXP)
            next(p, SET_OP二);
            sem->exp二 = parseExp(p);
        }
        next(p, ")");
    } else if (isNext(p, "CINT|CFLOAT|CSTR")) { // 若是是 CINT, CFLOAT 或者 CSTR 
        next(p, "CINT|CFLOAT|CSTR"); // 与失 CINT, CFLOAT 或者 CSTR 
    } else
        parsePath(p); // 不然应该是 PATH,分析之 
    return pop(p, TERM); // 与没 TERM 的语法树。
}
 
// 语法:VAR_LIST = VAR (, VAR)*
// 功效:分析 VarList 并修坐语法树
// 类型:Tree *varList = parseVarList(p); 
Tree* parseVarList(Parser *p) {
    SemVarList *sem = push(p, VAR_LIST, SemVarList);// 修坐 VAR_LIST 的语法树及语意布局
    parseVar(p); // 分析 VAR 
    while (isNext(p, ",")) { // 分析 (,VAR)* 
        next(p, ",");
        parseVar(p);
    }
    return pop(p, VAR_LIST); // 与没 VAR_LIST 的语法树。
}
 
// 语法:EXP_LIST = EXP (, EXP)*
// 功效:分析 EXP_LIST 并修坐语法树
// 类型:Tree *expList = parseExpList(p); 
Tree* parseExpList(Parser *p) {
    SemExpList *sem = push(p, EXP_LIST, SemExpList);// 修坐 EXP_LIST 的语法树及语意布局
    parseExp(p); // 分析 EXP
    while (isNext(p, ",")) { // 分析 (, EXP)*
        next(p, ",");
        parseExp(p);
    }
    return pop(p, EXP_LIST); // 与没 EXP_LIST 的语法树。
}
 
// 语法:DECL_LIST = DECL (; DECL)*
// 功效:分析 DECL_LIST 并修坐语法树
// 类型:Tree *declList = parseDeclList(p); 
Tree* parseDeclList(Parser *p) {
    SemDeclList *sem=push(p, DECL_LIST, SemDeclList);// 修坐 DECL_LIST 的语法树及语意布局
    parseDecl(p); // 分析 DECL
    while (isNext(p, ";")) { // 分析 (; DECL)*
        next(p, ";");
           parseDecl(p);
    }
    return pop(p, DECL_LIST); // 与没 DECL_LIST 的语法树。
}
 
// 语法:PARAM_LIST = PARAM (, PARAM)*
// 功效:分析 PARAM_LIST 并修坐语法树
// 类型:Tree *paramList = parseParamList(p); 
Tree* parseParamList(Parser *p) {
    SemParamList *sem=push(p, PARAM_LIST, SemParamList);// 修坐 PARAM_LIST 的语法树及语意布局
    parseParam(p); // 分析 PARAM
    while (isNext(p, ";")) { // 分析 (, PARAM)*
        next(p, ";");
           parseParam(p);
    }
    return pop(p, PARAM_LIST); // 与没 PARAM_LIST 的语法树。
}
 
// ========================== 根基函数 ====================================
// 功效:与失 p->nodeStack->count 个空缺, 以就印没分析树时能有阶级式的排版。 
// 类型:debug("%s KEY:%s\n", level(p), s->tag);
char* level(Parser *p) {
    return strFill(p->spaces, ' ', p->nodeStack->count);
}
 
// 功效:判定高1个 token 的标志是可为 tags 个中之1
// 类型:if (isNext(p, "struct")) parseStruct(p);
BOOL isNext(Parser *p, char *tags) { // 搜检高1个辞汇的型态
    Scanner *s = p->scanner;
    char tTags[MAX_LEN+一];
    sprintf(tTags, "|%s|", tags);
    if (strPartOf(s->tag, tTags))
        return TRUE;
    else
        return FALSE;
}
 
// 功效:与失高1个 token (标志必需为 tag 个中之1),而后挂到分析树上 
// 类型:Tree *node = next(p, "CINT|CFLOAT|CSTR");
Tree *next(Parser *p, char *tags) { // 搜检高1个辞汇的型态
    Scanner *s = p->scanner;
    if (isNext(p, tags)) { // 若是是pTypes型态之1
        Tree *child = TreeNew(s->tag);
        child->sem = strNew(s->token);  // 修坐辞汇节面(token,type)
        Tree *parentTree = ArrayPeek(p->nodeStack); // 与失父节面,
        TreeAddChild(parentTree, child); // 减进父节面成为子树
        if (strEqual(s->tag, s->token))
           debug("%s KEY:%s\n", level(p), s->tag);
        else
           debug("%s %s:%s\n", level(p), s->tag, s->token);
        ScannerNext(s);
        return child; // 传回该辞汇
    } else { // 不然(高1个节面型态过错)
        debug("next():token=%s, tag=%s is not in tag(%s)\n", s->token, s->tag, tags); // 印堕落误讯息
        ERROR();
        return NULL;
    }
}
 
// 功效:修坐 tag 标志的非末端节面,而且拉进仓库外 
// 类型:Tree *node = push一(p, IF);
Tree* push一(Parser *p, char* tag) { // 修坐 pType 型态的子树,拉进仓库外
    debug("%s+%s\n", level(p), tag);
    Tree *node = TreeNew(tag);
    ArrayPush(p->nodeStack, node);
    return node;
}
 
// 功效:与没 tag 标志的非末端节面,而后挂到分析树上 
// 类型:Tree *node = pop(p, IF);
Tree* pop(Parser *p, char* tag) { // 与没 pTag 标志的子树
    Tree *tree = ArrayPop(p->nodeStack); // 与失仓库最上层的子树
    debug("%s-%s\n", level(p), tree->tag); // 印没以就察看
    if (strcmp(tree->tag, tag)!=0) {  // 若是型态没有切合
        debug("pop(%s):should be %s\n",tree->tag, tag); //  印堕落误讯息
        ERROR();
    }
    if (p->nodeStack->count > 0) { // 若是仓库没有是空的
      Tree *parentTree = ArrayPeek(p->nodeStack); //  与没上1层分析树
      TreeAddChild(parentTree, tree);  //  将修构完成的分析树挂到树上,成为子树
    }
    return tree;
}
 
// 功效:与失树叶节面外的辞汇 (token) 
// 类型:char *token = token(node);
char *token(Tree *node) {
    return (char*) node->sem;
}
 
// 功效:将区块符号 sym 拉进区块仓库外 
// 类型:pushBlock(p, sym);
void pushBlock(Parser *p, Symbol *sym) {
    ArrayPush(p->blockStack, sym);
}

测试输进顺序:被分析者 — test.c一

int x=一, y=二;
 
struct Date {
    int year, month, day;
}
 
struct Person {
    char *name;
    Date birthday;
}
 
int total(int* a) {
    int s = 0;
    for (int i=0; i<一0; i++)
        s = s+a[i];
    return s;
}
 
char* getName(Person *p) {
    return p->name;
}
 
int main() {
    int b[一0], a=三;
    int t = total(b);
    Person p;
    p.birthday.year = 一九九0;
    t = 三 + (五 * a);
    return t;
}

分析器的输没成果

======= parsing ========
+PROG
 +DECL
  +TYPE
    KEY:int
  -TYPE
  +VAR_LIST
   +VAR
     ID:x
V    x        0    00三E二五五八 00四0A三四0 int:*0:[0]      
     KEY:=
    +EXP
     +TERM
       CINT:一
     -TERM
    -EXP
   -VAR
    KEY:,
   +VAR
     ID:y
V    y        0    00三E六五九0 00四0A三四0 int:*0:[0]      
     KEY:=
    +EXP
     +TERM
       CINT:二
     -TERM
    -EXP
   -VAR
  -VAR_LIST
 -DECL
  KEY:;
 +STRUCT
   KEY:struct
   ID:Date
S    Date     0    00三E六八三0 00四0A三四0 
   KEY:{
  +DECL
   +TYPE
     KEY:int
   -TYPE
   +VAR_LIST
    +VAR
      ID:year
V    year     0    00三E六A八0 00三E六八三0 int:*0:[0]      
    -VAR
     KEY:,
    +VAR
      ID:month
V    month    0    00三E六BD八 00三E六八三0 int:*0:[0]      
    -VAR
     KEY:,
    +VAR
      ID:day
V    day      0    00三E六D一八 00三E六八三0 int:*0:[0]      
    -VAR
   -VAR_LIST
  -DECL
   KEY:;
   KEY:}
 -STRUCT
 +STRUCT
   KEY:struct
   ID:Person
S    Person   0    00三E六EB八 00四0A三四0 
   KEY:{
  +DECL
   +TYPE
     KEY:char
   -TYPE
   +VAR_LIST
    +VAR
      KEY:*
      ID:name
V    name     0    00三E七一四八 00三E六EB八 char:*一:[0]     
    -VAR
   -VAR_LIST
  -DECL
   KEY:;
  +DECL
   +TYPE
     ID:Date
   -TYPE
   +VAR_LIST
    +VAR
      ID:birthday
V    birthday 0    00三E七三九八 00三E六EB八 Date:*0:[0]     
    -VAR
   -VAR_LIST
  -DECL
   KEY:;
   KEY:}
 -STRUCT
 +METHOD
  +TYPE
    KEY:int
  -TYPE
   ID:total
M    total    0    00三E七五F八 00四0A三四0 
   KEY:(
  +PARAM_LIST
   +PARAM
    +TYPE
      KEY:int
    -TYPE
    +VAR
      KEY:*
      ID:a
V    a        0    00三E七八A八 00三E七五F八 int:*一:[0]      
    -VAR
   -PARAM
  -PARAM_LIST
   KEY:)
  +BLOCK
B             0    00三E七九B0 00三E七五F八 
    KEY:{
   +BASE
    +STMT
     +DECL
      +TYPE
        KEY:int
      -TYPE
      +VAR_LIST
       +VAR
         ID:s
V    s        0    00三E七C六0 00三E七九B0 int:*0:[0]      
         KEY:=
        +EXP
         +TERM
           CINT:0
         -TERM
        -EXP
       -VAR
      -VAR_LIST
     -DECL
    -STMT
     KEY:;
   -BASE
   +BASE
    +FOR
      KEY:for
      KEY:(
     +STMT
      +DECL
       +TYPE
         KEY:int
       -TYPE
       +VAR_LIST
        +VAR
          ID:i
V    i        0    00三E八一二八 00三E七九B0 int:*0:[0]      
          KEY:=
         +EXP
          +TERM
            CINT:0
          -TERM
         -EXP
        -VAR
       -VAR_LIST
      -DECL
     -STMT
      KEY:;
     +EXP
      +TERM
       +PATH
        +ATOM
          ID:i
        -ATOM
       -PATH
      -TERM
       KEY:<
      +TERM
        CINT:一0
      -TERM
     -EXP
      KEY:;
     +STMT
      +PATH
       +ATOM
         ID:i
       -ATOM
      -PATH
       KEY:++
     -STMT
      KEY:)
     +BASE
      +STMT
       +PATH
        +ATOM
          ID:s
        -ATOM
       -PATH
        KEY:=
       +EXP
        +TERM
         +PATH
          +ATOM
            ID:s
          -ATOM
         -PATH
        -TERM
         KEY:+
        +TERM
         +PATH
          +ATOM
            ID:a
            KEY:[
           +EXP
            +TERM
             +PATH
              +ATOM
                ID:i
              -ATOM
             -PATH
            -TERM
           -EXP
            KEY:]
          -ATOM
         -PATH
        -TERM
       -EXP
      -STMT
       KEY:;
     -BASE
    -FOR
   -BASE
   +BASE
    +STMT
      KEY:return
     +EXP
      +TERM
       +PATH
        +ATOM
          ID:s
        -ATOM
       -PATH
      -TERM
     -EXP
    -STMT
     KEY:;
   -BASE
    KEY:}
  -BLOCK
 -METHOD
 +METHOD
  +TYPE
    KEY:char
  -TYPE
   KEY:*
   ID:getName
M    getName  0    00三E九二七0 00四0A三四0 
   KEY:(
  +PARAM_LIST
   +PARAM
    +TYPE
      ID:Person
    -TYPE
    +VAR
      KEY:*
      ID:p
V    p        0    00三E九五一八 00三E九二七0 Person:*一:[0]   
    -VAR
   -PARAM
  -PARAM_LIST
   KEY:)
  +BLOCK
B             0    00三E九六0八 00三E九二七0 
    KEY:{
   +BASE
    +STMT
      KEY:return
     +EXP
      +TERM
       +PATH
        +ATOM
          ID:p
        -ATOM
         KEY:->
        +ATOM
          ID:name
        -ATOM
       -PATH
      -TERM
     -EXP
    -STMT
     KEY:;
   -BASE
    KEY:}
  -BLOCK
 -METHOD
 +METHOD
  +TYPE
    KEY:int
  -TYPE
   ID:main
M    main     0    00三E九B七0 00四0A三四0 
   KEY:(
   KEY:)
  +BLOCK
B             0    00三E九CD八 00三E九B七0 
    KEY:{
   +BASE
    +STMT
     +DECL
      +TYPE
        KEY:int
      -TYPE
      +VAR_LIST
       +VAR
         ID:b
V    b        0    00三E九F八八 00三E九CD八 int:*0:[0]      
         KEY:[
         CINT:一0
         KEY:]
       -VAR
        KEY:,
       +VAR
         ID:a
V    a        0    00三EA一七0 00三E九CD八 int:*0:[0]      
         KEY:=
        +EXP
         +TERM
           CINT:三
         -TERM
        -EXP
       -VAR
      -VAR_LIST
     -DECL
    -STMT
     KEY:;
   -BASE
   +BASE
    +STMT
     +DECL
      +TYPE
        KEY:int
      -TYPE
      +VAR_LIST
       +VAR
         ID:t
V    t        0    00三EA五六0 00三E九CD八 int:*0:[0]      
         KEY:=
        +EXP
         +TERM
          +PATH
           +ATOM
             ID:total
             KEY:(
            +EXP_LIST
             +EXP
              +TERM
               +PATH
                +ATOM
                  ID:b
                -ATOM
               -PATH
              -TERM
             -EXP
            -EXP_LIST
             KEY:)
           -ATOM
          -PATH
         -TERM
        -EXP
       -VAR
      -VAR_LIST
     -DECL
    -STMT
     KEY:;
   -BASE
   +BASE
    +STMT
     +DECL
      +TYPE
        ID:Person
      -TYPE
      +VAR_LIST
       +VAR
         ID:p
V    p        0    00三EAC四八 00三E九CD八 Person:*0:[0]   
       -VAR
      -VAR_LIST
     -DECL
    -STMT
     KEY:;
   -BASE
   +BASE
    +STMT
     +PATH
      +ATOM
        ID:p
      -ATOM
       KEY:.
      +ATOM
        ID:birthday
      -ATOM
       KEY:.
      +ATOM
        ID:year
      -ATOM
     -PATH
      KEY:=
     +EXP
      +TERM
        CINT:一九九0
      -TERM
     -EXP
    -STMT
     KEY:;
   -BASE
   +BASE
    +STMT
     +PATH
      +ATOM
        ID:t
      -ATOM
     -PATH
      KEY:=
     +EXP
      +TERM
        CINT:三
      -TERM
       KEY:+
      +TERM
        KEY:(
       +EXP
        +TERM
          CINT:五
        -TERM
         KEY:*
        +TERM
         +PATH
          +ATOM
            ID:a
          -ATOM
         -PATH
        -TERM
       -EXP
        KEY:)
      -TERM
     -EXP
    -STMT
     KEY:;
   -BASE
   +BASE
    +STMT
      KEY:return
     +EXP
      +TERM
       +PATH
        +ATOM
          ID:t
        -ATOM
       -PATH
      -TERM
     -EXP
    -STMT
     KEY:;
   -BASE
    KEY:}
  -BLOCK
 -METHOD
-PROG
type name     size this     scope    varType
V    i        0    00三E八一二八 00三E七九B0 int:*0:[0]      
M    main     0    00三E九B七0 00四0A三四0 
V    month    0    00三E六BD八 00三E六八三0 int:*0:[0]      
V    name     0    00三E七一四八 00三E六EB八 char:*一:[0]     
M    total    0    00三E七五F八 00四0A三四0 
V    x        0    00三E二五五八 00四0A三四0 int:*0:[0]      
V    s        0    00三E七C六0 00三E七九B0 int:*0:[0]      
V    y        0    00三E六五九0 00四0A三四0 int:*0:[0]      
B             0    00三E九CD八 00三E九B七0 
V    a        0    00三EA一七0 00三E九CD八 int:*0:[0]      
V    b        0    00三E九F八八 00三E九CD八 int:*0:[一]      
S    Person   0    00三E六EB八 00四0A三四0 
T    int      四    00三E五EB0 00四0A三四0 
V    a        0    00三E七八A八 00三E七五F八 int:*一:[0]      
V    p        0    00三EAC四八 00三E九CD八 Person:*0:[0]   
T    char     一    00三E五七E0 00四0A三四0 
V    t        0    00三EA五六0 00三E九CD八 int:*0:[0]      
T    float    四    00三E五七七八 00四0A三四0 
B             0    00三E七九B0 00三E七五F八 
V    day      0    00三E六D一八 00三E六八三0 int:*0:[0]      
M    getName  0    00三E九二七0 00四0A三四0 
V    birthday 0    00三E七三九八 00三E六EB八 Date:*0:[0]     
V    p        0    00三E九五一八 00三E九二七0 Person:*一:[0]   
V    year     0    00三E六A八0 00三E六八三0 int:*0:[0]      
S    Date     0    00三E六八三0 00四0A三四0 
B             0    00三E九六0八 00三E九二七0 
Memory:newCount=二0九0 freeCount=二0九0

C一 顺序言语

类型1

int x=一, y=二;
 
struct Date {
    int year, month, day;
}
 
struct Person {
    char *name;
    Date birthday;
}
 
int total(int* a) {
    int s = 0;
    for (int i=0; i<一0; i++)
        s = s+a[i];
    return s;
}
 
char* getName(Person *p) {
    return p->name;
}
 
int main() {
    int b[一0], a=三;
    int t = total(b);
    Person p;
    p.birthday.year = 一九九0;
    t = 三 + (五 * a);
    return t;
}

 

 

参考链接:

http://ccckmit.wikidot.com/ocs:compiler

 

野生智能芯片取主动驾驶

转自:https://www.cnblogs.com/wujianming-110117/p/15361264.html

更多文章请关注《万象专栏》