C 语言程序统计工具的实现与分析
1. 模块概述
在软件开发过程中,对程序进行统计分析是一项重要的工作。这里我们将介绍一个用于生成程序统计信息的工具,它可以统计程序中的行数、括号嵌套深度、注释比例等信息。该工具包含多个子模块,各子模块协同工作,完成统计任务。
2. 子模块介绍
- 括号计数器子模块 :有括号计数器子模块和圆括号计数器子模块,它们功能相似,圆括号计数器子模块是通过复制括号计数器子模块并进行简单修改得到的。未来可以考虑将这两个子模块合并为一个,通过参数来指定统计的对象。
- 注释计数器子模块(cc) :该模块用于统计包含注释的行、包含代码的行、同时包含注释和代码的行以及空行的数量,并在文件末尾输出统计结果。
3.
do_file
过程
do_file
过程是处理单个文件的核心函数,它逐词读取文件内容,并将每个词发送给各个统计类的
take_token
例程。以下是
do_file
过程的代码:
static void do_file(const char *const name)
{
enum TOKEN_TYPE cur_token; /* Current token type */
/*
* Initialize the counters
*/
lc_init();
pc_init();
bc_init();
cc_init();
if (in_open(name) != 0) {
printf("Error: Could not open file %s for reading\n", name);
return;
}
while (1) {
cur_token = next_token();
lc_take_token(cur_token);
pc_take_token(cur_token);
bc_take_token(cur_token);
cc_take_token(cur_token);
switch (cur_token) {
case T_NEWLINE:
lc_line_start();
pc_line_start();
bc_line_start();
cc_line_start();
in_flush();
break;
case T_EOF:
lc_eof();
pc_eof();
bc_eof();
cc_eof();
in_close();
return;
default:
/* Do nothing */
break;
}
下面是
do_file
过程的流程图:
graph TD;
A[开始] --> B[初始化计数器];
B --> C[打开文件];
C -->|失败| D[输出错误信息并返回];
C -->|成功| E[循环读取令牌];
E --> F[获取下一个令牌];
F --> G[各统计类处理令牌];
G --> H{令牌类型};
H -->|换行符| I[输出行统计信息并刷新];
H -->|文件结束符| J[输出文件结束统计信息并关闭文件];
J --> K[结束];
H -->|其他| E;
I --> E;
4. 可扩展性
在设计软件时,可扩展性是一个需要考虑的重要因素。如果要为程序添加新的统计功能,例如添加一个单词计数子模块(wc),需要定义四个过程:
wc_init
、
wc_take_token
、
wc_line_start
和
wc_eof
。在 C 语言中,添加新统计功能时,可以通过编辑器查找注释计数器子模块过程的使用位置,并复制其调用代码。但这种方法不是最优的,特别是当调用分散在多个文件中时。而 C++ 没有这样的限制,使用 C++ 类可以更方便地实现可扩展性和可维护性。
5. 测试
为了测试该程序,我们编写了一个包含各种可能令牌的小型 C 程序,测试结果如下:
1 (0 {0 /* This is a single line comment */
2 (0 {0 /*
3 (0 {0 * This is a multiline
4 (0 {0 * comment.
5 (0 {0 */
6 (0 {0 int main()
7 (0 {1 {
8 (0 {1 /* A procedure */
9 (0 {1 int i; /* Comment / code line */
10 (0 {1 char foo[10];
11 (0 {1
12 (0 {1 strcpy(foo, "abc"); /* String */
13 (0 {1 strcpy(foo, "a\"bc"); /* String with special character */
14 (0 {1
15 (0 {1 foo[0] = 'a'; /* Character */
16 (0 {1 foo[1] = '\''; /* Character with escape */
17 (0 {1
18 (0 {1 i = 3 / 2; /* Slash that's not a commment */
19 (0 {1 i = 3; /* Normal number */
20 (0 {1 i = 0x123ABC; /* Hex number */
21 (0 {1
22 (1 {1 i = ((1 + 2) * /* Nested () */
23 (0 {1 (3 + 4));
24 (0 {1
25 (0 {2 {
26 (0 {2 int j; /* Nested {} */
27 (0 {1 }
28 (0 {1 return (0);
29 (0 {0 }
30 (0 {0
Total number of lines: 30
Maximum nesting of () : 2
Maximum nesting of {} : 2
Number of blank lines .................6
Number of comment only lines ..........6
Number of code only lines .............8
Number of lines with code and comments 10
Comment to code ratio 88.9%
6. 程序文件
该程序包含多个文件,下面是各文件的功能和主要代码:
| 文件名称 | 功能 |
| ---- | ---- |
|
in_file.h
| 定义输入文件相关的函数和数据结构,如打开文件、关闭文件、读取字符等。 |
|
in_file.c
| 实现
in_file.h
中定义的函数。 |
|
ch_type.h
| 定义字符类型的枚举和判断字符类型的函数。 |
|
ch_type.c
| 实现字符类型判断的相关函数。 |
|
token.h
| 定义词法单元的枚举和获取下一个词法单元的函数。 |
|
token.c
| 实现获取下一个词法单元的函数。 |
|
stat.c
| 实现统计功能的核心代码,包含行计数器、圆括号计数器、括号计数器和注释计数器等模块。 |
以下是部分文件的代码示例:
in_file.h
文件:
/*******************************************************
*
* input_file -- Data from the input file.
*
*
*
* The current two characters are stored in
*
* cur_char and next_char.
*
* Lines are buffered so that they can be output to
*
* the screen after a line is assembled.
*
*
*
* Functions:
*
* in_open -- Opens the input file.
*
* in_close -- Closes the input file.
*
* read_char -- Reads the next character.
*
* in_char_char -- Returns the current character.
*
* in_next_char -- Returns the next character.
*
* in_flush -- Sends line to the screen.
*
********************************************************
/
/*******************************************************
*
* in_open -- Opens the input file.
*
*
*
* Parameters
*
* name -- Name of disk file to use for input.
*
*
*
* Returns
*
* 0 -- Open succeeded.
*
* nonzero -- Open failed.
*
********************************************************
/
extern int in_open(const char name[]);
/*******************************************************
*
* in_close -- Closes the input file.
*
********************************************************
/
extern void in_close(void);
/*******************************************************
*
* in_read_char -- Read the next character from the
*
* input file.
*
********************************************************
/
extern void in_read_char(void);
/*******************************************************
*
* in_cur_char -- Gets the current input character.
*
*
*
* Returns
*
* current character.
*
********************************************************
/
extern int in_cur_char(void);
/*******************************************************
*
* in_next_char -- Peeks ahead one character.
*
*
*
* Returns
*
* next character.
*
********************************************************
/
extern int in_next_char(void);
/*******************************************************
*
* in_flush -- Flushes the buffered input line to the
*
* screen.
*
********************************************************
/
extern void in_flush(void);
ch_type.h
文件:
/*******************************************************
*
* char_type -- Character type module.
*
********************************************************
/
enum CHAR_TYPE {
C_EOF, /* End of file character */
C_WHITE, /* Whitespace or control character */
C_NEWLINE, /* A newline character */
C_ALPHA, /* A Letter (includes _) */
C_DIGIT, /* A Number */
C_OPERATOR, /* Random operator */
C_SLASH, /* The character '/' */
C_L_PAREN, /* The character '(' */
C_R_PAREN, /* The character ')' */
C_L_CURLY, /* The character '{' */
C_R_CURLY, /* The character '}' */
C_SINGLE, /* The character '\'' */
C_DOUBLE, /* The character '"' */
/* End of simple types, more complex, derrived types
follow */
C_HEX_DIGIT,/* Hexidecimal digit */
C_ALPHA_NUMERIC/* Alpha numeric */
};
/*******************************************************
*
* is_char_type -- Determines if a character belongs to
*
* a given character type.
*
*
*
* Parameters
*
* ch -- Character to check.
*
* kind -- Type to check it for.
*
*
*
* Returns:
*
* 0 -- Character is not of the specified kind.
*
* 1 -- Character is of the specified kind.
*
********************************************************
/
extern int is_char_type(int ch, enum CHAR_TYPE kind);
/*******************************************************
*
* get_char_type -- Given a character, returns its
type.*
*
*
* Note: We return the simple types. Composite types
*
* such as C_HEX_DIGIT and C_ALPHA_NUMERIC are not
*
* returned.
*
*
*
* Parameters:
*
* ch -- Character having the type we want.
*
*
*
* Returns
*
* character type.
*
********************************************************
/
extern enum CHAR_TYPE get_char_type(int ch);
C 语言程序统计工具的实现与分析
7. 各模块详细代码分析
7.1
in_file.c
文件
该文件实现了
in_file.h
中定义的函数,用于处理输入文件的打开、读取和显示等操作。以下是详细代码:
/*******************************************************
*
* infile module
*
* Handles opening, reading, and display of
*
* data from the input file.
*
*
*
* Functions:
*
* in_open -- Opens the input file.
*
* in_close -- Closes the input file.
*
* read_char -- Reads the next character.
*
* in_char_char -- Returns the current character.
*
* in_next_char -- Returns the next character.
*
* in_flush -- Sends line to the screen.
*
********************************************************
/
#include <stdio.h>
#include <errno.h>
#include "in_file.h"
#define LINE_MAX 500 /* Longest possible line */
struct input_file {
FILE *file; /* File we are reading */
char line[LINE_MAX];/* Current line */
char *char_ptr; /* Current character on the line
*/
int cur_char; /* Current character (can be
EOF)*/
int next_char; /* Next character (can be EOF)
*/
};
/* Input file that we are reading */
static struct input_file in_file = {
NULL, /* file */
"", /* line */
NULL, /* char_ptr */
'\0', /* cur_char */
'\0' /* next_char */
};
/*******************************************************
*
* in_open -- Opens the input file.
*
*
*
* Parameters
*
* name -- Name of disk file to use for input.
*
*
*
* Returns
*
* 0 -- Open succeeded.
*
* nonzero -- Open failed.
*
********************************************************
/
int in_open(const char name[])
{
in_file.file = fopen(name, "r");
if (in_file.file == NULL)
return (errno);
/*
* Initialize the input file and read the first two
* characters.
*/
in_file.cur_char = fgetc(in_file.file);
in_file.next_char = fgetc(in_file.file);
in_file.char_ptr = in_file.line;
return (0);
}
/*******************************************************
*
* in_close -- Closes the input file.
*
********************************************************
/
void in_close(void)
{
if (in_file.file != NULL) {
fclose(in_file.file);
in_file.file = NULL;
}
}
/*******************************************************
*
* in_cur_char -- Gets the current input character.
*
*
*
* Returns
*
* current character.
*
********************************************************
/
int in_cur_char(void)
{
return (in_file.cur_char);
}
/*******************************************************
*
* in_next_char -- Peeks ahead one character.
*
*
*
* Returns
*
* next character.
*
********************************************************
/
int in_next_char(void)
{
return (in_file.next_char);
}
/*******************************************************
*
* in_flush -- Flushes the buffered input line to the
*
* screen.
*
********************************************************
/
void in_flush(void)
{
*in_file.char_ptr = '\0'; /* End the line
*/
fputs(in_file.line, stdout); /* Send the line
*/
in_file.char_ptr = in_file.line; /* Reset the
line */
}
/*******************************************************
*
* in_read_char -- Reads the next character from the
*
* input file.
*
********************************************************
/
void in_read_char(void)
{
*in_file.char_ptr = in_file.cur_char;
++in_file.char_ptr;
in_file.cur_char = in_file.next_char;
in_file.next_char = fgetc(in_file.file);
};
该文件的主要操作流程如下:
1. 定义了一个
input_file
结构体,用于存储文件指针、当前行、当前字符指针以及当前和下一个字符。
2.
in_open
函数用于打开文件,并初始化当前和下一个字符。
3.
in_close
函数用于关闭文件。
4.
in_cur_char
和
in_next_char
函数分别用于获取当前和下一个字符。
5.
in_flush
函数用于将缓冲区的行输出到屏幕。
6.
in_read_char
函数用于读取下一个字符。
7.2
ch_type.c
文件
该文件实现了字符类型判断的相关函数,以下是详细代码:
/*******************************************************
*
* ch_type package
*
*
*
* This module is used to determine the type of
*
* various characters.
*
*
*
* Public functions:
*
* init_char_type -- Initializes the table.
*
* is_char_type -- Is a character of a given type?
*
* get_char_type -- Given char, returns type.
*
********************************************************
/
#include <stdio.h>
#include "ch_type.h"
/* Define the type information array */
static enum CHAR_TYPE type_info[256];
static int ch_setup = 0; /* True if character
type info setup */
/*******************************************************
*
* fill_range -- Fills in a range of types for the
*
* character type class.
*
*
*
* Parameters
*
* start, end -- Range of items to fill in.
*
* type -- Type to use for filling.
*
********************************************************
/
static void fill_range(int start, int end, enum
CHAR_TYPE type)
{
int cur_ch; /* Character we are handling now */
for (cur_ch = start; cur_ch <= end; ++cur_ch) {
type_info[cur_ch] = type;
}
}
/*******************************************************
**
* init_char_type -- Initializes the char type table.
*
********************************************************
*/
static void init_char_type(void)
{
fill_range(0, 255, C_WHITE);
fill_range('A', 'Z', C_ALPHA);
fill_range('a', 'z', C_ALPHA);
type_info['_'] = C_ALPHA;
fill_range('0', '9', C_DIGIT);
type_info['!'] = C_OPERATOR;
type_info['#'] = C_OPERATOR;
type_info['$'] = C_OPERATOR;
type_info['%'] = C_OPERATOR;
type_info['^'] = C_OPERATOR;
type_info['&'] = C_OPERATOR;
type_info['*'] = C_OPERATOR;
type_info['-'] = C_OPERATOR;
type_info['+'] = C_OPERATOR;
type_info['='] = C_OPERATOR;
type_info['|'] = C_OPERATOR;
type_info['~'] = C_OPERATOR;
type_info[','] = C_OPERATOR;
type_info[':'] = C_OPERATOR;
type_info['?'] = C_OPERATOR;
type_info['.'] = C_OPERATOR;
type_info['<'] = C_OPERATOR;
type_info['>'] = C_OPERATOR;
type_info['/'] = C_SLASH;
type_info['\n'] = C_NEWLINE;
type_info['('] = C_L_PAREN;
type_info[')'] = C_R_PAREN;
type_info['{'] = C_L_CURLY;
type_info['}'] = C_R_CURLY;
type_info['"'] = C_DOUBLE;
type_info['\''] = C_SINGLE;
}
/*******************************************************
*
* is_char_type -- Determines if a character belongs to
*
* a given character type.
*
*
*
* Parameters
*
* ch -- Character to check.
*
* kind -- Type to check it for.
*
*
*
* Returns:
*
* 0 -- Character is not of the specified kind.
*
* 1 -- Character is of the specified kind.
*
********************************************************
/
int is_char_type(int ch, enum CHAR_TYPE kind)
{
if (!ch_setup) {
init_char_type();
ch_setup = 1;
}
if (ch == EOF) return (kind == C_EOF);
switch (kind) {
case C_HEX_DIGIT:
if (type_info[ch] == C_DIGIT)
return (1);
if ((ch >= 'A') && (ch <= 'F'))
return (1);
if ((ch >= 'a') && (ch <= 'f'))
return (1);
return (0);
case C_ALPHA_NUMERIC:
return ((type_info[ch] == C_ALPHA) ||
(type_info[ch] == C_DIGIT));
default:
return (type_info[ch] == kind);
}
};
/*******************************************************
*
* get_char_type -- Given a character, returns its
type.*
*
*
* Note: We return the simple types. Composite types
*
* such as C_HEX_DIGIT and C_ALPHA_NUMERIC are not
*
* returned.
*
*
*
* Parameters:
*
* ch -- Character having the type we want.
*
*
*
* Returns
*
* character type.
*
********************************************************
/
enum CHAR_TYPE get_char_type(int ch) {
if (!ch_setup) {
init_char_type();
ch_setup = 1;
}
if (ch == EOF) return (C_EOF);
return (type_info[ch]);
}
该文件的主要操作流程如下:
1. 定义了一个
type_info
数组,用于存储字符类型信息。
2.
fill_range
函数用于填充字符类型范围。
3.
init_char_type
函数用于初始化字符类型表。
4.
is_char_type
函数用于判断一个字符是否属于给定的类型。
5.
get_char_type
函数用于根据字符返回其类型。
8. 统计模块详细分析
8.1 行计数器模块
行计数器模块用于统计文件中的行数,以下是相关代码:
/*******************************************************
*
* line_counter -- Handles line number / line count
*
* stat.
*
*
*
* Counts the number of T_NEW_LINE tokens seen and
*
* outputs the current line number at the beginning
*
* of the line.
*
*
*
* At EOF, it will output the total number of lines.
*
********************************************************
/
static int cur_line; /* Current line number
*/
/*******************************************************
*
* lc_init -- Initializes the line counter variables.
*
********************************************************
/
static void lc_init(void)
{
cur_line = 0;
};
/*******************************************************
*
* lc_take_token -- Consumes tokens and looks for
*
* end-of-line tokens.
*
*
*
* Parameters
*
* token -- The token coming in from the input
*
* stream.
*
********************************************************
/
static void lc_take_token(enum TOKEN_TYPE token) {
if (token == T_NEWLINE)
++cur_line;
}
/*******************************************************
*
* lc_line_start -- Outputs the per-line statistics,
*
* namely the current line number.
*
********************************************************
/
static void lc_line_start(void) {
printf("%4d ", cur_line);
}
/*******************************************************
*
* lc_eof -- Outputs the eof statistics.
*
* In this case, the number of lines.
*
********************************************************
/
static void lc_eof(void) {
printf("Total number of lines: %d\n", cur_line);
}
该模块的主要操作流程如下:
1.
lc_init
函数用于初始化当前行号为 0。
2.
lc_take_token
函数用于处理令牌,当遇到换行符时,当前行号加 1。
3.
lc_line_start
函数用于输出当前行号。
4.
lc_eof
函数用于在文件结束时输出总行数。
8.2 圆括号计数器模块
圆括号计数器模块用于统计圆括号的嵌套深度,以下是相关代码:
/*******************************************************
*
********************************************************
********************************************************
* paren_count -- Counts the nesting level of ().
*
*
*
* Counts the number of T_L_PAREN vs T_R_PAREN tokens
*
* and writes the current nesting level at the
beginning*
* of each line.
*
*
*
* Also keeps track of the maximum nesting level.
*
********************************************************
/
static int pc_cur_level;
static int pc_max_level;
/*******************************************************
*
* pc_init -- Initializes the () counter variables.
*
********************************************************
/
void pc_init(void) {
pc_cur_level = 0;
pc_max_level = 0;
};
/*******************************************************
*
* pc_take_token -- Consumes tokens and looks for
*
* () tokens.
*
*
*
* Parameters
*
* token -- The token coming in from the input
*
* stream.
*
********************************************************
/
void pc_take_token(enum TOKEN_TYPE token) {
switch (token) {
case T_L_PAREN:
++pc_cur_level;
if (pc_cur_level > pc_max_level)
pc_max_level = pc_cur_level;
break;
case T_R_PAREN:
--pc_cur_level;
break;
default:
/* Ignore */
break;
}
}
/*******************************************************
*
* pc_line_start -- Outputs the per-line statistics,
*
* namely the current () nesting.
*
********************************************************
/
static void pc_line_start(void) {
printf("(%-2d ", pc_cur_level);
}
/*******************************************************
*
* pc_eof -- Outputs the eof statistics.
*
* In this case, the max nesting of ().
*
********************************************************
/
void pc_eof(void) {
printf("Maximum nesting of () : %d\n", pc_max_level);
}
该模块的主要操作流程如下:
1.
pc_init
函数用于初始化当前嵌套级别和最大嵌套级别为 0。
2.
pc_take_token
函数用于处理令牌,当遇到左括号时,当前嵌套级别加 1,并更新最大嵌套级别;当遇到右括号时,当前嵌套级别减 1。
3.
pc_line_start
函数用于输出当前嵌套级别。
4.
pc_eof
函数用于在文件结束时输出最大嵌套级别。
9. 总结
通过以上分析,我们详细了解了一个 C 语言程序统计工具的实现。该工具通过多个模块协同工作,实现了行计数、括号嵌套计数、注释统计等功能。在设计过程中,考虑了可扩展性,但在 C 语言中实现可扩展性存在一定的局限性。同时,通过测试用例验证了程序的正确性。在实际应用中,可以根据需要添加更多的统计功能,以满足不同的需求。
以下是各统计模块的功能总结表格:
| 模块名称 | 功能 |
| ---- | ---- |
| 行计数器模块 | 统计文件中的行数 |
| 圆括号计数器模块 | 统计圆括号的嵌套深度 |
| 括号计数器模块 | 统计花括号的嵌套深度 |
| 注释计数器模块 | 统计注释和代码的行数及比例 |
下面是整个程序的主要流程 mermaid 流程图:
graph TD;
A[开始] --> B[初始化各统计模块];
B --> C[打开文件];
C -->|失败| D[输出错误信息并结束];
C -->|成功| E[循环读取令牌];
E --> F[获取下一个令牌];
F --> G[各统计模块处理令牌];
G --> H{令牌类型};
H -->|换行符| I[输出行统计信息并刷新];
H -->|文件结束符| J[输出文件结束统计信息并关闭文件];
J --> K[结束];
H -->|其他| E;
I --> E;
通过这个工具,我们可以更好地了解程序的结构和代码质量,为程序的优化和维护提供有力的支持。
超级会员免费看

被折叠的 条评论
为什么被折叠?



