You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

YACC解析器首行语法错误,空测试程序亦报错求助

问题描述

我正在完成一项基于Lex和YACC的语言设计作业,任务是搭建一个简单的YACC解析器。但遇到两个问题:

  • 传入测试文件后,解析器总是返回“首行存在语法错误”的提示,且似乎未执行到示例的第一条语句;
  • 即使测试程序为空,也会返回错误信息。

Lex代码

%{
    #include <stdio.h>
    #include "y.tab.h"
    void yyerror(char *);   
%}
lowerLetter    [a-z]
letter         [a-zA-Z]
digit          [0-9]
signs          [+-]
integer        {signs}?{digit}+
double        {signs}?{digit}*(\.)?{digit}+
word           {lowerLetter}+({letter}*{digit}*)*
string         \"[^\"]*\"
day            ("Monday"|"Tuesday"|"Wednesday"|"Thursday"|"Friday"|"Saturday"|"Sunday")
month          ("January"|"February"|"March"|"April"|"May"|"June"|"July"|"August"|"September"|"October"|"November"|"December")
time           {day}(\,)(\ )[0-3][1-9](\ ){month}(\ ){digit}*(\ )[0-2][0-9](\:)[0-5][0-9](\:)[0-5][0-9]((\ )("GMT")(((\+)|(\-)){integer}(\:){integer})?)?
sensor         (\$)("s"){digit}*
switch         (\$)("sw"){digit}
url            ("http")("s")?("://")("www.")?[a-zA-Z0-9]*(\.)(.)*
type           ("integer"|"double"|"string"|"sensor"|"switch"|"url"|"boolean"|"time"|"letter")
boolean        ("true")|("false")|([a-zA-Z0-9]+((\ )*)?(((\=|\<|\>|\!)(\=))|(\>|\<))+((\ )*)?[a-zA-Z0-9]+))
identifier     ({letter}*|{digit}*(\_)*)*
%%
while          return WHILE;
for            return FOR;
return         return RETURN;
sysin             return SYSIN;
sysout         return SYSOUT;
if             return IF;
main          return MAIN;
end            return END;
else           return ELSE;
fun            return FUNCTION_IDENTIFIER;
cons           return CONS;
isURL          return IS_URL;
connect        return CONNECT;
send           return SEND;
receive        return RECEIVE;
\\\n           return NL;
\.             return DOT;
\,             return COMMA;
\:             return COLON;
\;             return SEMICOLON;
\+             return PLUS_OP;
\-             return MINUS_OP;
\*             return MULTIPLY_OP;
\/             return DIVIDE_OP;
\%             return MOD_OP;
\#             return HASHTAG;
\$             return SENSOR_IDENTIFIER;
\^             return POWER_OP;
\_             return UNDER_SCORE;
\?                return QUESTION;
\!             return NOT;
\(             return LP;
\)             return RP;
\{             return LCB;
\}             return RCB;
\[             return LSB;
\]             return RSB;
\=             return ASSN_OP;
\>             return GT;
\<             return LT;
\=\=           return EQ;
\>\=           return GEQ;
\<\=           return LEQ;
\!\=           return NE;
\&\&           return AND;
\|\|           return OR;
\/\/           return DS;
{day}          return DAY;
{month}        return MONTH;
{time}         return TIME;
{type}         return TYPE;
{lowerLetter}  return LOWER_LETTER;
{letter}       return LETTER; 
{integer}      return INTEGER;
{string}       return STRING;
{sensor}       return SENSOR;
{switch}       return SWITCH;
{double}       return DOUBLE;
{boolean}      return BOOLEAN;
{url}          return URL;
{word}         return WORD;
{identifier}   return IDENTIFIER;
[ \t]           ;
%%
int yywrap(void) 
{
  return 1;
}

YACC代码

%{
    #include "stdio.h"
    #include <stdlib.h>
    void yyerror(char *);
    extern int yylineno;
    #include "y.tab.h"
    int yylex(void);
%}
%token MAIN
%token DOT COMMA COLON SEMICOLON UNDER_SCORE QUESTION LP RP LCB RCB LSB RSB DS NL 
%token WHILE FOR RETURN SYSIN SYSOUT IF END ELSE FUNCTION_IDENTIFIER NOT CONS PLUS_OP MINUS_OP MULTIPLY_OP DIVIDE_OP MOD_OP HASHTAG SENSOR_IDENTIFIER POWER_OP 
%token ASSN_OP GT LT EQ GEQ LEQ NE AND OR DAY MONTH TIME TYPE  IS_URL CONNECT SEND RECEIVE
%token LOWER_LETTER LETTER WORD STRING INTEGER DOUBLE BOOLEAN SENSOR SWITCH URL IDENTIFIER
%nonassoc ELSE
%left PLUS_OP MINUS_OP
%left MULTIPLY_OP DIVIDE_OP
%left POWER_OP MOD_OP
%start program
%%
program:
    stmts {printf("\rProgram is valid.\n");};

stmts:    stmt
        | stmts stmt;

stmt:    if_stmt 
        | non_if_stmt ;

if_stmt: IF LP logical_expr RP LCB stmts RCB
        | IF LP logical_expr RP LCB stmts RCB ELSE LCB stmts RCB;

non_if_stmt: loops
        | aritmetic_op
        | func_call
        | func_dec
        | initialize
        | decl
        | decl_ini
        | input_stmt
        | output_stmt
        //| comment
        | url_checker
        | send
        | receive
        | connect;

loops: while_loop
        | for_loop;

while_loop: WHILE LP logical_expr RP LCB stmts RCB;

for_loop: FOR LP decl_ini SEMICOLON logical_expr SEMICOLON aritmetic_op RP LCB stmts RCB;

initialize: IDENTIFIER ASSN_OP value SEMICOLON;
    
logical_expr:  logical_term logical_op logical_term
        | logical_expr logical_connector logical_term
        | boolean_stmt;

boolean_stmt:  BOOLEAN
        | NOT boolean_stmt;

logical_term:  term;
        //| logical_term AND term;

logical_connector:  AND
        | OR;

term: IDENTIFIER
        | constant;

constant:  CONS IDENTIFIER;

logical_op:  EQ
        | NE
        | LT
        | GT
        | LEQ
        | GEQ;

decl: TYPE term SEMICOLON;

decl_ini:  TYPE term ASSN_OP value SEMICOLON;

value: number
        | STRING
        | SENSOR
        | SWITCH
        | URL
        | BOOLEAN
        | LETTER
        | TIME;

number: DOUBLE
        | INTEGER;
/*
value_list: value
        | value_list COMMA value;

array_decl: TYPE term LSB RSB SEMICOLON;

array_ini:  TYPE term LSB RSB ASSN_OP LSB value_list RSB SEMICOLON
        | term ASSN_OP LSB value_list RSB SEMICOLON;

get_array_val:  term LSB INTEGER RSB;
*/
func_dec: FUNCTION_IDENTIFIER TYPE IDENTIFIER LP arguments RP LCB function_block RCB;

func_call: IDENTIFIER LP arguments RP SEMICOLON;

function_block:  stmts
        | stmts RETURN value
        | stmts RETURN term;

arguments: TYPE term
        | arguments COMMA TYPE term;

aritmetic_op:  addition
        | subtraction
        | multiplication
        | division
        | modulo
        | power;

addition: aritmetic_op PLUS_OP term
        | term PLUS_OP term
        | term PLUS_OP aritmetic_op;

subtraction: aritmetic_op MINUS_OP term
        | term MINUS_OP term
        | term MINUS_OP aritmetic_op;

multiplication: aritmetic_op MULTIPLY_OP term
        | term MULTIPLY_OP term
        | term MULTIPLY_OP aritmetic_op;

division:  aritmetic_op DIVIDE_OP term
        | term DIVIDE_OP term
        | term DIVIDE_OP aritmetic_op;

modulo: aritmetic_op MOD_OP term
        | term MOD_OP term
        | term MOD_OP aritmetic_op;

power:  aritmetic_op POWER_OP term
        | term POWER_OP term
        | term POWER_OP aritmetic_op;
/*
comment:
        | DS sentence NL;

sentence:
        | IDENTIFIER
        | DOT | COMMA | COLON | SEMICOLON | PLUS_OP | MINUS_OP | MULTIPLY_OP | DIVIDE_OP
        | MOD_OP | HASHTAG | SENSOR_IDENTIFIER | POWER_OP | UNDER_SCORE | QUESTION | NOT
        | LP | RP | LCB | RCB | ASSN_OP | GT | LT
        | sentence sentence;
        */

input_stmt:  TYPE term ASSN_OP SYSIN LP RP SEMICOLON
        | SYSIN LP RP SEMICOLON;

output_stmt: SYSOUT LP output RP SEMICOLON;

output:  term
        | value
        | aritmetic_op 
        | output COMMA term;

url_checker:  IS_URL LP STRING RP
        | IS_URL LP term RP;

connect: CONNECT LP URL RP;

send:  SEND LP number COMMA URL RP;

receive: RECEIVE LP URL RP;



%%

void yyerror(char *s) 
{
  fprintf(stderr, "syntax error at line: %d %s\n", yylineno, s);
}

int main(void){
    yyparse();
    if(yynerrs < 1) printf("there are no syntax errors!!\n");
}

简单测试程序

integer a = 5;
double e = 5.5;
double f = 3.0;
sadasd

扩展测试程序

sensor a = $s1;
switch b = $sw1;
integer c = -5;
integer d = 75;
double e = 5.5;
double f = 3.0;
c = c + d;
f = f + e;
sysout (e + f);
integer g = sysin();

url k = https://www.cs.bilkent.edu.tr/~guvenir/courses/CS315/Pr1.htm;
url l = https://docs.oracle.com/cd/E19504-01/802-5880/lex-6/index.html; 

if(isURL(k)){connect(k);} //Connecting to URL k after checking if its a URL
send(g, k); //Sending the integer g to the URL k
integer h = receive(l); //Receiving the integer h from URL l
sysout (h); //Printing the integer h which we took from the URL l

if (e > f) {
    e = f + e;
}else{
    e = f - e;
}

time t = Friday, 14 October 2022 18:05:52
time t = Monday, 17 October 2022 12:05:52 GMT
time t = Saturday, 22 October 2022 20:05:52 GMT+03:00 //Defining time in different ways

while (e > f) {
    e = e - f;
}

for (integer i = 0, e <= f, i = i + 1) {
    e = e + i;
}

fun boolean isGreater(integer x, integer y){ 
    boolean z = (x > y);
    return z;
}

boolean g = isGreater(e, f); 

sysout (k);
sysout (l);

Makefile

LEX = lex
YACC = yacc -d

CC = gcc

all: parser clean

parser: y.tab.o lex.yy.o
    $(CC) -o parser y.tab.o lex.yy.o 
    ./parser < test.txt

lex.yy.o: lex.yy.c y.tab.h
lex.yy.o y.tab.o: y.tab.c

y.tab.c y.tab.h: y.y
    $(YACC) -v y.y

lex.yy.c: lex.l
    $(LEX) lex.l

clean:
    -rm -f *.o lex.yy.c *.tab.* parser *.output

问题分析与修复方案

1. 空输入报错的解决

YACC的program规则强制要求至少有一条语句,空输入无法匹配。修改规则允许空程序:

program:
    stmts {printf("\rProgram is valid.\n");}
    | {printf("\rEmpty program is valid.\n");};

2. 首行语法错误的核心修复

(1)Lex未处理换行符

当前Lex仅忽略空格和制表符,换行符会被识别为未知字符,触发语法错误。添加规则忽略换行:

[ \t\n]           ; /* 忽略空格、制表符、换行符 */

(2)Boolean规则冲突

原boolean规则会匹配a > b这类表达式,导致Lex将整个表达式作为单个BOOLEANtoken返回,与YACC的语法规则冲突。修改规则仅匹配true/false:

boolean        ("true")|("false")

(3)URL规则匹配范围错误

原URL规则会把后面的分号也包含进URLtoken,导致YACC找不到语句结束的分号。修改规则限制匹配范围:

url            ("http")("s")?("://")("www.")?[a-zA-Z0-9_\-\.~\/]*

(4)For循环语法不匹配

YACC的for_loop规则要求用分号分隔循环三要素,但测试程序用了逗号。修改测试程序的for语句:

for (integer i = 0; e <= f; i = i + 1) {
    e = e + i;
}

(5)Time语句缺少分号

扩展测试程序中的time赋值语句未加结束分号,添加分号:

time t = Friday, 14 October 2022 18:05:52;
time t = Monday, 17 October 2022 12:05:52 GMT;
time t = Saturday, 22 October 2022 20:05:52 GMT+03:00;

3. 其他细节修复

  • Lex中sysin规则的多余空格:改为sysin return SYSIN;避免匹配异常;
  • 在Lex中添加int yylineno = 1;,确保行号统计正确;
  • 测试程序中的sadasd属于无意义标识符,若要允许此类语句,需在YACC的non_if_stmt中添加IDENTIFIER SEMICOLON规则。

内容的提问来源于stack exchange,提问作者asimtot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:41:07