分析
- 计算器包含六部分:语法分析器(Parser)、词法分析器(lexer),驱动器(driver)、符号表(table)、错误处理(error)、输入及初始化(main)。
- 书中采用一种名为“递归下降”的语法分析机制。在处理输入环节,词法分析器使用Token_stream,它负责把字符流(如123.45)转换成Token。一个Token是一个结构体{单词类型,值},例如{number,123.45},其中123.45已被转换成一个浮点值。词法分析器主体部分只需要知道Token_stream的名字是ts以及如何从它那里获取Token。为了读取下一个Token,它调用ts.get(),函数ts.current()返回刚刚读到的单词。
- 程序:接口(parser.h, lexer.h, driver.h, table.h, error.h),实现(parser.cpp,
lexer.cpp, driver.cpp, table.cpp, error.cpp),入口(main.cpp)


parser.h
#pragma once
#include "lexer.h"
#include "error.h"
using Error::error;
using namespace Lexer;
namespace Parser {
double prim(bool get, Token_stream& ts);
double term(bool get, Token_stream& ts);
double expr(bool get, Token_stream& ts);
}
lexer.h
#pragma once
#include<string>
#include<iostream>
namespace Lexer {
enum class Kind : char {
name, number, end, print,
plus = '+', minus = '-', mul = '*', div = '/',
assign = '=', lp = '(', rp = ')'
};
class Token {
public:
Kind kind;
double number_value;
std::string string_value;
};
class Token_stream {
public:
Token_stream(std::istream& s) : ip{ &s }, owns{ false } {}
Token_stream(std::istream* p) : ip{ p }, owns{ true } {}
~Token_stream() { close(); };
Token get();
const Token& current();
void set_input(std::istream& s) { close(); ip = &s; owns = false; }
void set_input(std::istream* p) { close(); ip = p; owns = true; }
private:
void close() const { if (owns) { delete ip; } };
std::istream* ip;
bool owns;
Token ct {Kind::end};
};
}
driver.h
#pragma once
#include "lexer.h"
namespace Driver {
void calculate(Lexer::Token_stream&);
}
table.h
#pragma once
#include<string>
#include<map>
namespace Table {
extern std::map<std::string, double> table;
}
error.h
#pragma once
#include<string>
namespace Error {
extern int no_of_errors;
double error(const std::string& s);
}
parser.cpp
#include "parser.h"
#include "table.h"
double Parser::prim(bool get, Token_stream& ts) {
if (get) ts.get();
switch (ts.current().kind) {
case Kind::number: {
double v = ts.current().number_value;
ts.get();
return v;
}
case Kind::name: {
double& v = Table::table[ts.current().string_value];
if (ts.get().kind == Kind::assign) v = expr(true, ts);
return v;
}
case Kind::minus:
return -prim(true, ts);
case Kind::lp: {
auto v = expr(true, ts);
if (ts.current().kind != Kind::rp) return error("')' excepted");
ts.get();
return v;
}
default:
return error("primary excepted");
}
}
double Parser::term(bool get, Token_stream& ts) {
double left = prim(get, ts);
for (;;) {
switch (ts.current().kind) {
case Kind::mul:
left *= prim(true, ts);
break;
case Kind::div:
if (auto d = prim(true, ts)) {
left /= d;
break;
}
return error("divide by 0");
default:
return left;
}
}
}
double Parser::expr(bool get, Token_stream& ts) {
double left = term(get, ts);
for (;;) {
switch (ts.current().kind) {
case Kind::plus:
left += term(true, ts);
break;
case Kind::minus:
left -= term(true, ts);
break;
default:
return left;
}
}
}
lexer.cpp
#include <cctype>
# include "lexer.h"
# include "error.h"
Lexer::Token Lexer::Token_stream::get() {
char ch;
do {
if (!ip->get(ch)) return ct = { Kind::end };
} while (ch != '\n' && isspace(ch));
switch (ch) {
case ';':
case '\n':
return ct = { Kind::print };
case '+':
case '-':
case '*':
case '/':
case '(':
case ')':
case '=':
return ct = { static_cast<Kind>(ch) };
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
ip->putback(ch);
*ip >> ct.number_value;
ct.kind = Kind::number;
return ct;
default:
if (isalpha(ch)) {
ct.string_value = ch;
while (ip->get(ch) && isalnum(ch)) {
ct.string_value += ch;
}
ip->putback(ch);
ct.kind = Kind::name;
return ct;
}
}
Error::error("bad token");
return ct = { Kind::end };
}
const Lexer::Token& Lexer::Token_stream::current() {
return ct;
}
driver.cpp
#include "driver.h"
#include "parser.h"
void Driver::calculate(Lexer::Token_stream& ts) {
for (;;) {
ts.get();
if (ts.current().kind == Lexer::Kind::end) break;
if (ts.current().kind == Lexer::Kind::print) continue;
std::cout << Parser::expr(false, ts) << '\n';
}
}
table.cpp
#include "table.h"
std::map<std::string, double> Table::table;
error.cpp
#include "error.h"
#include <iostream>
int Error::no_of_errors;
double Error::error(const std::string& s) {
++no_of_errors;
std::cerr << "error:" << s << '\n';
return 1;
}
main.cpp
#include <sstream>
#include "parser.h"
#include "driver.h"
#include "table.h"
using Table::table;
int main(int argc, char* argv[]) {
table["pi"] = 3.1415926535897932385;
table["e"] = 2.7182818284590452354;
Token_stream ts{ std::cin };
Driver::calculate(ts);
std::cout << Error::no_of_errors << '\n';
return Error::no_of_errors;
}