C언어 관련해서 질문드립니다..

xxxvan의 이미지

제가 개발하려는게 일종의 interpreter 입니다. 파일을 읽으려니 많은 부분을 생각해야하더군요.

예를 들어 (가 나 다 라) 혹은 (aaa bbb ccc) 이런식의 내용이 있는 파일이 있다고 가정을 하고 이를 읽을때 저는 fscanf 함수를 이용해야겠다 싶었습니다.

그런데 문제는 만약 %s 로 읽었을시 첫 번째 읽기부터 (aaa을 읽어버리니 원하는 aaa값을 읽지 못하더군요. ( "ccc)" 도 마찬가지입니다.)

문제는 이뿐만이 아닌 (가 (나 다) 라) 혹은 (aa (bb cc (ab bc)) dd) 이런 형태의 입력내용도 있다는 것입니다.

입력값의 괄호부분이 상당히 까다롭더군요.. string.h에 포함된 많은 함수를 생각해봤습니다만 잘되지 않더군요..

코드는 바라지않고 저 부분을 어떠한 방식으로 접근해야하나 혹은 표준 library의 어떤 함수를 사용하는게 좋겠다하는 조언하나 부탁드리겠습니다.

감사합니다.

jick의 이미지

그런 기능을 C의 기본 함수만으로 구현하려면 엄청난 삽질을 하셔야 할 것 같습니다.

처음에 배우는 시간을 좀 투자해야 하지만 제대로 된 lexical analyzer (lexer)를 사용하시는 것을 추천해 드립니다. 좀 구닥다리이지만 C에서 쓰려면 flex가 괜찮습니다.

http://flex.sourceforge.net/

HDNua의 이미지

1. 저는 JavaScript로 C Compiler를 개발하고 있는데, StringBuffer라는 도구를 만들어봤습니다. C로 보여드릴게요. 이것이 바로 jick님이 말씀하신 삽질이지만요.

2. 컴파일러 공부라면.. 제가 연재중인 글을 참고하셔도 좋을 것 같습니다(당당)
https://kldp.org/node/151702

code.txt

int main(void) { int a=10, b=20; return a + b; }

실행 결과

token: int
token: main
token: (
token: void
token: )
token: {
token: int
token: a
token: =
token: 10
token: ,
token: b
token: =
token: 20
token: ;
token: return
token: a
token: +
token: b
token: ;
token: }

main.c

#include <stdio.h>
#include "StringBuffer.h"
 
#pragma warning(disable:4996)
#define MAX_BUF_SIZ     256
 
int fsize(FILE *fp);
 
int main(void) {
    FILE *fp;
    StringBuffer *buffer;
    char buf[MAX_BUF_SIZ] = "";
 
    // 파일에서 문자열을 획득합니다.
    fp = fopen("code.txt", "rt");
    fread(buf, sizeof(char), fsize(fp), fp);
    fclose(fp);
 
    // 획득한 문자열을 바탕으로 버퍼를 초기화합니다.
    // StringBuffer는 내부적으로 문자열을 동적 할당하여 복사하므로
    // buf는 수정해도 상관없습니다.
    buffer = sb_new(buf);
 
    // 버퍼에 문자가 남지 않을 때까지 토큰을 획득합니다.
    fp = fopen("out.txt", "wt");
    while (sb_isempty(buffer) == FALSE) {
        if (sb_gettok(buffer, buf) < 0) // 토큰 획득에 실패하면 종료합니다.
            break;
        fprintf(fp, "token: %s\n", buf); // 획득한 토큰을 출력합니다.
    }
    fclose(fp);
    sb_delete(buffer);
 
    printf("load complete\n");
    return 0;
}
 
int fsize(FILE *fp) {
    int size;
    fseek(fp, 0, SEEK_END);
    size = (int)ftell(fp);
    fseek(fp, 0, SEEK_SET);
    return size;
}

StringBuffer.h

#ifndef __HANDY_STRING_BUFFER__
#define __HANDY_STRING_BUFFER__
 
#define TRUE    1
#define FALSE   0
 
typedef struct _HandyStringBuffer {
    char *str;      // StringBuffer 구조체가 관리하는 문자열입니다.
    int len;        // StringBuffer 구조체의 문자열의 길이입니다.
    int idx;        // StringBuffer가 현재 가리키고 있는 문자의 인덱스입니다.
 
} StringBuffer;
 
// 새 StringBuffer 인스턴스를 생성합니다.
StringBuffer *sb_new(const char *s);
// StringBuffer 인스턴스를 메모리에서 해제합니다.
void sb_delete(StringBuffer *buffer);
 
// StringBuffer로부터 문자를 획득하고 포인터를 이동합니다. 성공시 획득한 문자, 실패시 -1을 반환합니다.
int sb_getc(StringBuffer *buffer);
// StringBuffer로부터 문자를 획득합니다. 포인터는 이동하지 않습니다. 성공시 획득한 문자, 실패시 -1을 반환합니다.
int sb_peekc(StringBuffer *buffer);
// StringBuffer가 획득한 문자를 되돌립니다. 성공시 0, 실패시 1을 반환합니다.
int sb_ungetc(StringBuffer *buffer);
 
// StringBuffer에 획득할 수 있는 문자가 남아있는지 확인합니다. 남아있으면 0을, 아니면 1을 반환합니다.
int sb_isempty(StringBuffer *buffer);
 
// StringBuffer에서 토큰을 획득합니다. 실패하면 -1을 반환합니다.
// 문자열을 획득한 경우 0을, 단일 문자를 획득한 경우 해당 문자의 값을 반환합니다.
int sb_gettok(StringBuffer *buffer, char *token);
 
#endif

StringBuffer.c

#include "StringBuffer.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
 
#pragma warning(disable:4996)
 
// 새 StringBuffer 인스턴스를 생성합니다.
StringBuffer *sb_new(const char *s) {
    // StringBuffer 인스턴스를 생성합니다.
    StringBuffer *buffer = (StringBuffer *)malloc(sizeof(StringBuffer));
    buffer->str = strdup(s); // 문자열을 복사합니다.
    buffer->len = (int)strlen(s); // 문자열의 길이를 구합니다.
    buffer->idx = 0; // 맨 처음 획득할 문자의 위치로 초기화합니다.
 
    // 생성한 인스턴스를 반환합니다.
    return buffer;
}
// StringBuffer 인스턴스를 메모리에서 해제합니다.
void sb_delete(StringBuffer *buffer) {
    free(buffer->str);
    free(buffer);
}
 
// StringBuffer로부터 문자를 획득합니다. 성공시 획득한 문자, 실패시 -1을 반환합니다.
int sb_getc(StringBuffer *buffer) {
    if (sb_isempty(buffer) == TRUE) // 문자를 획득할 수 없으면 -1을 반환합니다.
        return -1;
    else {
        char ret = buffer->str[buffer->idx]; // 반환할 문자를 획득합니다.
        (buffer->idx)++; // 획득한 문자이므로 넘어갑니다.
        return ret; // 획득한 문자를 반환합니다.
    }
//    return buffer->str[buffer->idx++]; // 한 문장으로 합친 결과입니다.
}
// StringBuffer로부터 문자를 획득합니다. 포인터는 이동하지 않습니다. 성공시 획득한 문자, 실패시 -1을 반환합니다.
int sb_peekc(StringBuffer *buffer) {
    if (sb_isempty(buffer) == TRUE)
        return -1;
    return buffer->str[buffer->idx]; // 포인터가 가리키는 위치의 문자를 반환합니다.
}
// StringBuffer가 획득한 문자를 되돌립니다. 성공시 0, 실패시 1을 반환합니다.
int sb_ungetc(StringBuffer *buffer) {
    if (buffer->idx == 0) // 되돌릴 수 없으면 1을 반환합니다.
        return 1;
    --(buffer->idx); // 문자를 되돌립니다.
    return 0; // 되돌리는 데 성공했다면 0을 반환합니다.
}
 
// StringBuffer에서 현재 위치부터 다음 토큰 문자의 시작 사이의 공백을 제거합니다.
static void sb_trim(StringBuffer *buffer) {
    while (sb_isempty(buffer) == FALSE) { // 버퍼에 문자가 남아있는 동안
        if (isspace(sb_peekc(buffer)) == FALSE) // 현재 포인터가 가리키는 문자가 공백이 아니라면 탈출합니다.
            break;
        sb_getc(buffer); // 공백이므로 포인터를 옮깁니다.
    }
}
 
// StringBuffer에 획득할 수 있는 문자가 남아있는지 확인합니다. 남아있으면 0을, 아니면 1을 반환합니다.
int sb_isempty(StringBuffer *buffer) {
    // 현재 문자 포인터의 위치를 기준으로 판정합니다.
    return (buffer->idx >= buffer->len) ? TRUE : FALSE;
}
 
// 현재 문자가 첫 식별자 문자인지 판정합니다.
static int sb_isfnamch(int c) {
    return (c == '_' || isalpha(c)) ? TRUE : FALSE;
}
// 현재 문자가 식별자 문자인지 판정합니다.
static int sb_isnamch(int c) {
    return (c == '_' || isalnum(c)) ? TRUE : FALSE;
}
 
// 식별자를 획득합니다. 토큰을 저장한 포인터의 값을 반환합니다.
static char *get_identifier(StringBuffer *buffer, char *token) {
    char *writer = token; // 목적지에 토큰을 기록하기 위해 포인터를 생성합니다.
    while (sb_isempty(buffer) == FALSE) { // 버퍼에 문자가 남아있는 동안
        if (sb_isnamch(sb_peekc(buffer)) == FALSE) // 문자가 식별자 문자가 아니면 탈출합니다.
            break;
        *writer++ = sb_getc(buffer); // 문자를 획득하여 토큰에 추가합니다.
    }
    *writer = '\0'; // 토큰의 마지막에 널 문자를 추가합니다.
    return token; // 획득한 토큰을 반환합니다.
}
 
// 정수를 획득합니다. 토큰을 저장한 포인터의 값을 반환합니다.
static char *get_integer(StringBuffer *buffer, char *token) {
    char *writer = token; // 목적지에 토큰을 기록하기 위해 포인터를 생성합니다.
    while (sb_isempty(buffer) == FALSE) { // 버퍼에 문자가 남아있는 동안
        if (!isdigit(sb_peekc(buffer))) // 문자가 식별자 문자가 아니면 탈출합니다.
            break;
        *writer++ = sb_getc(buffer); // 문자를 획득하여 토큰에 추가합니다.
    }
    *writer = '\0'; // 토큰의 마지막에 널 문자를 추가합니다.
    return token; // 획득한 토큰을 반환합니다.
}
 
// StringBuffer에서 토큰을 획득합니다. 실패하면 -1을 반환합니다.
// 문자열을 획득한 경우 0을, 단일 문자를 획득한 경우 해당 문자의 값을 반환합니다.
int sb_gettok(StringBuffer *buffer, char *token) {
    sb_trim(buffer); // 포인터를 다음 토큰의 앞까지 옮깁니다.
    if (sb_isempty(buffer) == TRUE) // 버퍼가 비어있다면 획득할 수 없으므로 NULL을 반환합니다.
        return -1;
 
    if (sb_isfnamch(sb_peekc(buffer)) == TRUE) { // 식별자 첫 문자가 발견되었다면 식별자를 획득합니다.
        get_identifier(buffer, token);
    }
    else if (isdigit(sb_peekc(buffer))) { // 숫자가 발견되었다면 정수를 획득합니다.
        get_integer(buffer, token);
    }
    else { // 그 외의 경우 한 글자만 획득합니다.
        *token = sb_getc(buffer);
        token[1] = '\0';
        return *token;
    }
    return 0; // 토큰을 저장한 포인터를 반환합니다.
}

저는 이렇게 생각했습니다.

powersys의 이미지

C포인터를 활용하면 스트링처리하기 매우편리합니다... 특별한 함수나 라이브러리의 도움없이도 처리가 가능하죠..

실제로 직접 함수들을 재작하셔도.. 다른언어들의 웬만한 함수기능을 몇줄로도 구현되요..

그런데 님이 만드실 인터프리터는 애초부터 그리간단한 작업이 아니죠.. 구현범위부터 생각해보셔야할듯하네요..

아마 특화된부분에 간단히 처리할거라면
fread, fget 등으로 읽어서.. strtok 등으로 토큰을 분리해내시고..

그렇지 않다면 파싱 함수부분을 직접 구현하시는게 좋을듯하네요..

xxxvan의 이미지

사실 이게 한 3년전 자료구조 대학과제라서 복학하기전에 도전하려 했던건데 번번히 실패로 돌아간 관계로 꽤나 애를 먹고 있었습니다.
참고로 구현범위야 그렇게 넓지 않습니다. 위에서 리스트에 해당하는 부분을 reverse flatten append 하는 과정이 포함되어있습니다.
제가 아직 다른 언어는 배우지 못하여서 c로만 구현하는 어려움이 다소 있네요. 조언 감사드리며 다시 한번 도전해보도록 하겠습니다.

"Cogito ergo sum" - René Descartes

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.