《C++程序设计语言》10.2一个桌面计算器示例,多个头文件组织

分析

  1. 计算器包含六部分:语法分析器(Parser)、词法分析器(lexer),驱动器(driver)、符号表(table)、错误处理(error)、输入及初始化(main)。
  2. 书中采用一种名为“递归下降”的语法分析机制。在处理输入环节,词法分析器使用Token_stream,它负责把字符流(如123.45)转换成Token。一个Token是一个结构体{单词类型,值},例如{number,123.45},其中123.45已被转换成一个浮点值。词法分析器主体部分只需要知道Token_stream的名字是ts以及如何从它那里获取Token。为了读取下一个Token,它调用ts.get(),函数ts.current()返回刚刚读到的单词。
  3. 程序:接口(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;	// case块中定义变量,一定要加{}限制作用域
		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");
		// 读取')'下一个Token
		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 { //跳过除'\n'以外的空白符
		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);
		// 把数字读入ct
		*ip >> ct.number_value;
		ct.kind = Kind::number;
		return ct;
	default:	// 名字,名字=,或者错误
		if (isalpha(ch)) {
			ct.string_value = ch;
			while (ip->get(ch) && isalnum(ch)) {
				// 把ch加到string_value的末尾
				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;	// 初始化为0

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值