[Dart] 정규 표현식, XML, JSON, Tokenizer, Lexer, Parser, AST

2025. 4. 28. 20:47Dart & Flutter

이번 포스트에서는 정규 표현식(Regex), XML, JSON, Tokenizer, Lexer, Parser, AST까지, 개발자라면 꼭 알아야 할 핵심 개념들을 한 번에 정리해보려 합니다.

 

이 개념들은 모두 ‘데이터를 표현하고 구조화하는 방법’ 혹은 '데이터를 분석하고 해석하는 과정’과 깊은 관련이 있습니다.

 

그럼 지금부터, 이 개념들이 어떤 역할을 하고 어떻게 연결되는지 Dart 코드 예시와 함께 살펴보며 차근차근 알아보도록 하겠습니다.


1. 정규 표현식(Regular Expression)

정규 표현식(Regex)은 문자열에서 특정한 패턴을 찾거나, 검사하거나, 치환할 때 사용하는 표현 방법입니다.

 

예를 들어, 이메일 주소를 검사하고 싶다면 단순히 “@“가 있는지 보는 것만으로는 부족하겠죠. 이때 정규 표현식을 사용하면 더 정확하게 조건을 설정할 수 있습니다.

const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('example@email.com')); // true
console.log(emailRegex.test('hello-world'));       // false

 

^ : 문자열의 시작

[a-zA-Z0-9._%+-]+ : 영문자, 숫자, 일부 특수문자 허용

@ : @ 기호 포함

. : 도메인 구분

$ : 문자열의 끝

 

요약: 문자열의 규칙을 검사하거나 찾는 기술


Dart에서 정규 표현식 사용하기

void main() {
  String email = "test@example.com";
  RegExp regex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
  
  if (regex.hasMatch(email)) {
    print("유효한 이메일입니다.");
  } else {
    print("유효하지 않은 이메일입니다.");
  }
}

 

결과

유효한 이메일입니다.

 

RegExp 클래스를 사용해서 정규 표현식을 다룰 수 있습니다.


2. XML(eXtensible Markup Language)

XML은 데이터를 구조화해서 표현할 수 있도록 만든 마크업 언어로, 데이터를 태그를 이용해서 표현하는 포멧입니다.

<person>
  <name>조성은</name>
  <age>23</age>
</person>

 

주로 데이터 저장이나 시스템 간 데이터 교환에 사용됩니다. 다만, 요즘은 무겁고 복잡하다는 이유로 JSON에 비해 덜 사용되는 추세입니다.

 

특징

사람이 읽을 수 있음

구조화된 데이터 표현

다소 무겁고 verbose(장황)

 

요약: 데이터를 태그로 구조화한 문서 형식


Flutter에서 XML 다루기

 

만약 Flutter에서 XML 데이터를 다뤄야 한다면, xml 패키지를 사용할 수 있어요.

dependencies:
  xml: ^6.3.0
import 'package:xml/xml.dart';

void main() {
  final document = XmlDocument.parse('''
  <person>
    <name>조성은</name>
    <age>23</age>
  </person>
  ''');

  final name = document.findAllElements('name').single.text;
  print('이름: $name');
}

 

결과

이름: 조성은

3. JSON(JavaScript Object Notation)

JSON은 JavaScript 문법을 기반으로 만든 가볍고 간단한 데이터 포맷입니다.

사람도 읽기 쉽고, 기계도 읽기 쉽습니다.

현재 Flutter에서는 서버 간 통신에서 가장 많이 쓰이는 데이터 포맷이에요.

 

{
  "name": "조성은",
  "age": 23
}

 

XML보다 훨씬 간결한 것을 확인할 수 있습니다.

 

특징

키-값 쌍으로 구성

대부분의 프로그래밍 언어에서 쉽게 읽고 쓸 수 있음

가볍고 빠름

 

요약: 가볍고 직관적인 데이터 교환 포맷


Flutter에서 JSON 다루기

import 'dart:convert';

void main() {
  String jsonString = '{"name": "조성은", "age": 23}';
  
  Map<String, dynamic> user = jsonDecode(jsonString);
  
  print('이름: ${user['name']}');
}

 

결과

이름: 조성은

 

✅ Dart에서는 dart:convert 라이브러리를 사용해서 쉽게 jsonDecodejsonEncode를 사용할 수 있어요.


4. Tokenizer

Tokenizer는 긴 문자열을 토큰(Token) 이라는 작은 단위로 나누는 작업을 합니다.

 

예를 들어 이런 코드가 있을 때:

let x = 10;

 

Tokenizer는 다음처럼 쪼갤 수 있어요:

let

x

=

10

;

 

이 조각들을 가지고 프로그래밍 언어가 “이건 변수 선언이구나”라고 이해할 수 있게 되는 것입니다.

 

Tokenizer 결과:

[ 'let', 'x', '=', '10', ';' ]

 

Tokenizer는 단순히 문장을 ‘자르기’만 합니다.

 

요약: 긴 문장을 의미 있는 조각으로 분해하는 첫 번째 단계


Dart로 간단한 Tokenizer 만들기

List<String> simpleTokenizer(String code) {
  return code.split(RegExp(r'(\s+|;|=)')).where((token) => token.isNotEmpty).toList();
}

void main() {
  String code = "let x = 10;";
  List<String> tokens = simpleTokenizer(code);

  print(tokens);
}

 

결과

[let, x, =, 10, ;]

5. Lexer

Lexer는 Tokenizer의 확장된 개념입니다

단순히 자르는 것뿐 아니라, 각 조각에 의미(토큰 타입) 를 부여합니다.

 

예를 들면:

토큰 타입
let 키워드
x 식별자
= 연산자
10 숫자 리터럴
; 구분자

 

Lexer는 “이게 단순한 글자가 아니라 이런 의미를 가진 조각이야”라고 해석합니다.

 

요약: 조각에 ‘의미’까지 부여하는 과정


Dart로 간단한 Lexer 만들기

class Token {
  final String type;
  final String value;

  Token(this.type, this.value);

  @override
  String toString() => '[$type: $value]';
}

List<Token> simpleLexer(String code) {
  List<String> rawTokens = code.split(RegExp(r'(\s+|;|=)')).where((token) => token.isNotEmpty).toList();
  
  return rawTokens.map((token) {
    if (token == 'let') return Token('Keyword', token);
    if (token == '=') return Token('Operator', token);
    if (RegExp(r'^[0-9]+$').hasMatch(token)) return Token('Number', token);
    return Token('Identifier', token);
  }).toList();
}

void main() {
  String code = "let x = 10;";
  List<Token> tokens = simpleLexer(code);

  print(tokens);
}

 

결과

[Keyword: let, Identifier: x, Operator: =, Number: 10, Identifier: ;]

6. Parser

Parser는 Lexer가 넘겨준 토큰들을 보고 전체 문법 구조를 분석하고, 이를 트리 형태로 구성합니다.

let x = 10;

 

이 코드가 “변수 x를 10으로 초기화하는 선언문이다”라는 구조를 이해하는 과정입니다.

 

Parser는 코드를 읽어 나가면서 규칙에 맞는지 검사하고, 코드의 계층적 구조(Tree 구조) 를 만들어냅니다.

 

요약: 토큰들을 보고 문법 구조를 이해하는 단계


Dart로 간단한 Parser 흐름 설명

void parse(List<Token> tokens) {
  if (tokens.length >= 4 && tokens[0].value == 'let' && tokens[2].value == '=') {
    print('변수 선언: ${tokens[1].value} = ${tokens[3].value}');
  } else {
    print('구문 오류!');
  }
}

void main() {
  String code = "let x = 10;";
  List<Token> tokens = simpleLexer(code);
  
  parse(tokens);
}

 

결과

변수 선언: x = 10

7. AST(Abstract Syntax Tree)

AST는 Parsing 결과로 만들어지는 트리(Tree) 구조입니다.

코드의 논리적 구조를 표현하는데 사용됩니다.

 

위 코드를 AST로 표현하면 이렇게 됩니다:

VariableDeclaration
 ├── Identifier (x)
 └── NumericLiteral (10)

 

코드의 ‘뼈대’라고 볼 수 있어요. 이후 컴파일하거나 코드를 최적화할 때 이 트리를 기반으로 작업합니다.

 

이 그림은

변수 선언(VariableDeclaration)이 루트

x라는 식별자(Identifier)가 하나 있고

10이라는 숫자 리터럴(NumericLiteral)로 초기화

한다는 걸 표현하고 있습니다.

 

요약: 코드의 구조를 트리 형태로 표현한 것


Dart로 간단한 AST 구조 나타내기

class ASTNode {
  final String type;
  final dynamic value;
  final List<ASTNode> children;

  ASTNode({required this.type, this.value, this.children = const []});

  void printTree([int depth = 0]) {
    print('${'  ' * depth}$type: $value');
    for (var child in children) {
      child.printTree(depth + 1);
    }
  }
}

void main() {
  ASTNode tree = ASTNode(
    type: 'VariableDeclaration',
    children: [
      ASTNode(type: 'Identifier', value: 'x'),
      ASTNode(type: 'NumericLiteral', value: 10),
    ],
  );

  tree.printTree();
}

 

결과

VariableDeclaration: null
  Identifier: x
  NumericLiteral: 10

마무리

개념 설명
정규 표현식 문자열 패턴을 다루는 도구
XML 태그로 데이터 구조화
JSON 간단한 데이터 포맷
Tokenizer 긴 문자열을 조각내기
Lexer 조각에 의미 부여히기
Parser 조각들을 문법적으로 해석하기
AST 해석 결과를 트리로 표현

전체 흐름 정리

소스 코드
   ↓
Tokenizer
(문자열 → 토큰 분리)
   ↓
Lexer
(토큰에 의미 부여)
   ↓
Parser
(문법 구조 분석)
   ↓
AST
(코드 구조를 트리로 표현)

 

정규 표현식은 문자열을 검사하는 기술

XML/JSON은 데이터를 저장하고 교환하는 포맷

Tokenizer → Lexer → Parser → AST는 코드를 읽어 이해하고 구조화하는 과정입니다.

 

정규 표현식, XML, JSON은 주로 데이터를 다루는 방법이고,

Tokenizer, Lexer, Parser, AST는 프로그래밍 언어를 이해하는 과정입니다.

 

위 개념들은 “문자열을 다루고”, “구조를 만들고”, “의미를 부여하는 것” 이라는 공통점으로 서로 자연스럽게 연결되어 있습니다.