Fab
Fab

Reputation: 1564

Using lex and bison together with swift from Xcode

I'm working on integrating Swift with a simple parser generated using lex and bison in an Xcode 16.1 project.

Below is my current code:

calc.l

%{
#include "calc.tab.h"
#include <stdlib.h>    
%}


%%
[0-9]+      { yylval.num = atoi(yytext); return NUMBER; }
[ \t\n]+    ;  
"+"         return '+';
"-"         return '-';
"*"         return '*';
"/"         return '/';
"("         return '(';
")"         return ')';
.           return yytext[0]; 
%%

calc.y

%{
#include <stdio.h>
#include <stdlib.h>

#include "calc.h"

void yyerror(const char *s);  
int result;  
%}


%union {
    int num;
}

/* Define tokens with types */
%token <num> NUMBER
%left '+' '-'
%left '*' '/'

/* Use %type to declare the type of non-terminal symbols */
%type <num> expr

%%
expr:
    expr '+' expr { $$ = $1 + $3; }
  | expr '-' expr { $$ = $1 - $3; }
  | expr '*' expr { $$ = $1 * $3; }
  | expr '/' expr { $$ = $1 / $3; }
  | '(' expr ')' { $$ = $2; }
  | NUMBER       { $$ = $1; }
  ;
%%

int main(void) {
    return yyparse();
}

void yyerror(const char *s) {
    fprintf(stderr, "Error: %s\n", s);
}

calc.h

#ifndef CALC_H
#define CALC_H



extern int yylex(void);
extern int yyparse(void);
void yyerror(const char *s);
int parseExpression(void);

#endif

calc.c

#include "calc.h"
#include <stdio.h>

int result;

int parseExpression(void) {
    if (yyparse() == 0) {
        return result;
    } else {
        return -1; // Error
    }
}

void yyerror(const char *s) {
    fprintf(stderr, "Error: %s\n", s);
}

Bridging-Header.h

#import "calc.h"

This is a simple SwiftUI view to call the parser:


struct ContentView: View {
    @State var result: Int32 = 0
    
    var body: some View {
        VStack {
            Text("\(result)")
            
            Button("Calculate") {
                let expression = "3 + 5 * (10 - 4)"
                result = evaluate(expression: expression)
                print("Result: \(result)")
            }
        }
        .padding()
    }
    
    
    func evaluate(expression: String) -> Int32 {
        
        expression.withCString { cString in
            freopen("/dev/stdin", "r", stdin)
            fputs(cString, stdin)
        }
        return parseExpression()
    }

}

And I manually run these commands from a terminal:

flex -o lex.yy.c calc.l 
bison -d calc.y -o calc.tab.c

When I compile the project in Xcode, I encounter the following error that I haven’t been able to resolve:


Undefined symbols for architecture arm64:
  "_yywrap", referenced from:
      _yylex in lex.yy.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Could anyone provide some guidance or point me in the right direction?

Upvotes: 0

Views: 69

Answers (1)

Fab
Fab

Reputation: 1564

My question was not a duplication of an existing one and just adding the option:

%option noyywrap

as suggested did not resolve the issue. In fact, it introduced multiple errors due to duplicate variable and function definitions.

This was because including both .l (Flex) and .y (Bison) files in the Xcode project automatically triggers both Flex and Bison behind the scenes.

The solution was to update the tokenizer and parser definitions to avoid referencing calc.tab.h, which was not available.

Below are the final working files:

calc.l

%option noyywrap

%{
#include <stdio.h>
#include <stdlib.h>

enum {
    NUMBER = 258,
    PLUS,
    MINUS,
    MULTIPLY,
    DIVIDE,
    LEFTPAR,
    RIGHTPAR
};
    
union YYSTYPE {
    int num;
};
    
extern union YYSTYPE yylval;
extern int expression_value;
extern void yyerror(const char *s);

%}


%%
[0-9]+      { yylval.num = atoi(yytext); return NUMBER; }
[ \t\n]+    ;
"+"         return PLUS;
"-"         return MINUS;
"*"         return MULTIPLY;
"/"         return DIVIDE;
"("         return LEFTPAR;
")"         return RIGHTPAR;
.           return yytext[0];
%%

calc.y

%{
#include <stdio.h>
#include <stdlib.h>

void yyerror(const char *s);
int yylex(void);

int expression_value;

%}


%union {
    int num;
}

%token <num> NUMBER
%token PLUS
%token MINUS
%token MULTIPLY
%token DIVIDE
%token LEFTPAR
%token RIGHTPAR
%left PLUS MINUS
%left MULTIPLY DIVIDE

%type <num> expr

%%
    
input:
  |  expr { expression_value = $1; }
  ;
    
expr:
    expr PLUS expr  { $$ = $1 + $3; }
  | expr MINUS expr { $$ = $1 - $3; }
  | expr MULTIPLY expr { $$ = $1 * $3; }
  | expr DIVIDE expr { $$ = $1 / $3; }
  | LEFTPAR expr RIGHTPAR { $$ = $2; }
  | NUMBER       { $$ = $1; }
  ;
%%

void yyerror(const char *s) {
    fprintf(stderr, "Error: %s\n", s);
}

calc.h

#ifndef CALC_H
#define CALC_H

int evaluate_expression(const char *expression, int *result);

#endif

calc.c

#include "calc.h"
#include <stdio.h>

extern int yyparse(void);
extern void yy_scan_string(const char *str);
extern void yylex_destroy(void);
extern int expression_value;

int evaluate_expression(const char *expression, int *result) {
    
    yy_scan_string(expression);

    // Parse and evaluate the expression
    int status = yyparse();

    yylex_destroy();
    
    if (status == 0) {
        *result = expression_value;
    }
    
    return  status;
}

This is the Swift(UI) code that call the parser:

struct ContentView: View {
    @State var expression: String = ""
    @State var result: Int32?

    var body: some View {
        VStack {
            TextEditor(text: $expression)

            if let result {
                Text("\(result)")
            }

            Button("Calculate") {
                result = evaluate(expression: expression)
            }
        }
        .padding()
    }

    func evaluate(expression: String) -> Int32? {
        var result: Int32 = 0

        let status = expression.withCString { cString in
            evaluate_expression(UnsafeMutablePointer(mutating: cString), &result)
        }

        if status != 0 {
            // The parse returned an error
            return nil
        }

        return result
    }
}

Note that a Swift-Bridging.h file is required to call the evaluate_expression function.

Upvotes: -1

Related Questions