Skip to content
← Back to Examples

S-Expression Parser

Rustcode templateintermediate

Parse S-expressions (symbolic expressions), the nested list structure used in Lisp-family languages. Supports symbols, numbers, strings, and nested lists.

Key Concepts

  • recursive grammar
  • atom types
  • list structure
  • nested parsing
  • code-as-data

Why Parse This Format?

S-expressions are a fundamental data structure in Lisp languages and are used for code-as-data representation, configuration files, and data interchange in functional programming communities.

Use Cases

  • Lisp/Scheme interpreter implementation
  • Configuration file parsing
  • Code transformation tools
  • Symbolic computation
  • Data serialization

Input Format

S-expressions are recursively defined: atoms (symbols, numbers, strings) or lists of s-expressions in parentheses. Whitespace separates elements.

sexp = atom | ( sexp* ) where atom = symbol | number | "string"

Examples

42VALID

Number atom

-3.14VALID

Negative float atom

helloVALID

Symbol atom

"hello world"VALID

String atom

(+ 1 2)VALID

Simple list (addition)

(+ (* 2 3) (- 10 5))VALID

Nested lists

(defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))VALID

Function definition

(list 1 2 3 "four" (quote five))VALID

Mixed types in list

(unclosedINVALID

Invalid: missing closing paren

Output Format

The parser returns an SExp enum representing the expression tree with proper type distinctions.

Structure

SExp

Enum: Symbol(String), Int(i64), Float(f64), String(String), List(Vec<SExp>)

List

Recursive structure containing nested SExp values

Visual Overview

S-Expression Parser diagram

How the Code Works

This section explains how the parsing rules map to the format semantics.

Atom Parsing

.rule("symbol", re("[a-zA-Z_+\\-*/=<>!][a-zA-Z0-9_+\\-*/=<>!]*"))
.rule("number", re("-?[0-9]+(\\.[0-9]+)?"))
.rule("string", re("\"[^\"]*\""))

.rule(
    "atom",
    choice(vec![
        dynamic(re("[a-zA-Z_+\\-*/=<>!][a-zA-Z0-9_+\\-*/=<>!]*")),  // symbol
        dynamic(re("-?[0-9]+(\\.[0-9]+)?")),                        // number
        dynamic(re("\"[^\"]*\"")),                                  // string
    ]),
)

Atoms are the primitive values in S-expressions: Symbols can include operator characters (`+`, `-`, `*`, etc.) for Lisp-style operators.

List Parsing

.rule(
    "list",
    seq(vec![
        dynamic(str("(")),
        dynamic(re("[ \t\n\r]*")),  // optional whitespace
        dynamic(re("\\)*")),         // items (simplified)
        dynamic(str(")")),
    ]),
)

Lists are parenthesized sequences:

Recursive Parsing

fn parse_sexp_list(input: &str) -> Result<Vec<SExp>, String> {
    let mut items = Vec::new();
    let mut depth = 0;
    let mut current = String::new();

    for c in input.chars() {
        match c {
            '(' => {
                depth += 1;
                current.push(c);
            }
            ')' => {
                depth -= 1;
                current.push(c);
            }
            ' ' | '\t' | '\n' | '\r' if depth == 0 => {
                if !current.is_empty() {
                    items.push(parse_sexp(&current)?);
                    current.clear();
                }
            }
            _ => current.push(c),
        }
    }
    Ok(items)
}

S-expressions are parsed recursively by tracking nesting depth: When depth is 0 and whitespace is found, the current item is complete.

Value Detection

pub fn parse_sexp(input: &str) -> Result<SExp, String> {
    if input.starts_with('(') && input.ends_with(')') {
        return Ok(SExp::List(parse_sexp_list(inner)?));
    }
    if input.starts_with('"') && input.ends_with('"') {
        return Ok(SExp::String(input[1..len-1].to_string()));
    }
    if let Ok(n) = input.parse::<i64>() {
        return Ok(SExp::Int(n));
    }
    if let Ok(n) = input.parse::<f64>() {
        return Ok(SExp::Float(n));
    }
    Ok(SExp::Symbol(input.to_string()))
}

Values are detected by their format:

Value Detection

pub fn parse_sexp(input: &str) -> Result<SExp, String> {
    if input.starts_with('(') && input.ends_with(')') {
        return Ok(SExp::List(parse_sexp_list(inner)?));
    }
    if input.starts_with('"') && input.ends_with('"') {
        return Ok(SExp::String(input[1..len-1].to_string()));
    }
    if let Ok(n) = input.parse::<i64>() {
        return Ok(SExp::Int(n));
    }
    if let Ok(n) = input.parse::<f64>() {
        return Ok(SExp::Float(n));
    }
    Ok(SExp::Symbol(input.to_string()))
}

Values are detected by their format:

Source Code

Rust
//! S-Expression Parser Example
//!
//! This example demonstrates parsing S-expressions (Lisp-style syntax).
//! Shows recursive grammar definition and nested structure handling.
//!
//! Run with: cargo run --example sexp_parser --no-default-features

#![allow(clippy::get_first)]
#![allow(clippy::single_match)]

use parsanol::portable::{
    parser_dsl::{choice, dynamic, re, seq, str, GrammarBuilder},
    Grammar,
};

/// Build an S-expression grammar
#[allow(dead_code)]
fn build_sexp_grammar() -> Grammar {
    GrammarBuilder::new()
        // Whitespace (spaces, tabs, newlines)
        .rule("whitespace", re("[ \t\n\r]+"))
        // Atom: symbol or number
        .rule("symbol", re("[a-zA-Z_+\\-*/=<>!][a-zA-Z0-9_+\\-*/=<>!]*"))
        .rule("number", re("-?[0-9]+(\\.[0-9]+)?"))
        .rule("string", re("\"[^\"]*\""))
        // Atom: any of the above
        .rule(
            "atom",
            choice(vec![
                dynamic(re("[a-zA-Z_+\\-*/=<>!][a-zA-Z0-9_+\\-*/=<>!]*")),
                dynamic(re("-?[0-9]+(\\.[0-9]+)?")),
                dynamic(re("\"[^\"]*\"")),
            ]),
        )
        // List: (items...)
        .rule(
            "list",
            seq(vec![
                dynamic(str("(")),
                dynamic(re("[ \t\n\r]*")), // optional whitespace
                dynamic(re("\\)*")),       // items (simplified)
                dynamic(str(")")),
            ]),
        )
        // S-expression: atom or list
        .rule(
            "sexp",
            choice(vec![
                dynamic(re("[a-zA-Z_+\\-*/=<>!][a-zA-Z0-9_+\\-*/=<>!]*")),
                dynamic(re("-?[0-9]+(\\.[0-9]+)?")),
                dynamic(re("\"[^\"]*\"")),
                dynamic(seq(vec![
                    dynamic(str("(")),
                    dynamic(re("[^)]*")), // content
                    dynamic(str(")")),
                ])),
            ]),
        )
        .build()
}

/// S-expression value
#[derive(Debug, Clone)]
pub enum SExp {
    /// Symbol (identifier)
    Symbol(String),
    /// Integer number
    Int(i64),
    /// Floating point number
    Float(f64),
    /// String literal
    String(String),
    /// List of S-expressions
    List(Vec<SExp>),
}

impl std::fmt::Display for SExp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SExp::Symbol(s) => write!(f, "{}", s),
            SExp::Int(n) => write!(f, "{}", n),
            SExp::Float(n) => write!(f, "{}", n),
            SExp::String(s) => write!(f, "\"{}\"", s),
            SExp::List(items) => {
                write!(f, "(")?;
                for (i, item) in items.iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    write!(f, "{}", item)?;
                }
                write!(f, ")")
            }
        }
    }
}

/// Parse an S-expression string
pub fn parse_sexp(input: &str) -> Result<SExp, String> {
    let input = input.trim();

    // Empty
    if input.is_empty() {
        return Err("Empty input".to_string());
    }

    // List
    if input.starts_with('(') && input.ends_with(')') {
        let inner = &input[1..input.len() - 1];
        let items = parse_sexp_list(inner)?;
        return Ok(SExp::List(items));
    }

    // String
    if input.starts_with('"') && input.ends_with('"') {
        return Ok(SExp::String(input[1..input.len() - 1].to_string()));
    }

    // Number
    if let Ok(n) = input.parse::<i64>() {
        return Ok(SExp::Int(n));
    }
    if let Ok(n) = input.parse::<f64>() {
        return Ok(SExp::Float(n));
    }

    // Symbol
    if input
        .chars()
        .all(|c| c.is_alphanumeric() || "+-*/_=<>!".contains(c))
    {
        return Ok(SExp::Symbol(input.to_string()));
    }

    Err(format!("Invalid S-expression: {}", input))
}

/// Parse a list of S-expressions (inner content of parentheses)
fn parse_sexp_list(input: &str) -> Result<Vec<SExp>, String> {
    let mut items = Vec::new();
    let mut chars = input.chars().peekable();
    let mut current = String::new();
    let mut depth = 0;

    while let Some(c) = chars.next() {
        match c {
            '(' => {
                depth += 1;
                current.push(c);
            }
            ')' => {
                depth -= 1;
                current.push(c);
            }
            '"' => {
                current.push(c);
                // Read until closing quote
                while let Some(&next) = chars.peek() {
                    current.push(chars.next().unwrap());
                    if next == '"' {
                        break;
                    }
                }
            }
            ' ' | '\t' | '\n' | '\r' if depth == 0 => {
                if !current.is_empty() {
                    items.push(parse_sexp(&current)?);
                    current.clear();
                }
            }
            _ => {
                current.push(c);
            }
        }
    }

    if !current.is_empty() {
        items.push(parse_sexp(&current)?);
    }

    Ok(items)
}

fn main() {
    println!("S-Expression Parser Example");
    println!("===========================\n");

    let expressions = [
        "42",
        "-3.14",
        "hello",
        "\"hello world\"",
        "(+ 1 2)",
        "(+ (* 2 3) (- 10 5))",
        "(defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))",
        "(list 1 2 3 \"four\" (quote five))",
    ];

    for expr in expressions {
        println!("Input: {}", expr);
        match parse_sexp(expr) {
            Ok(sexp) => {
                println!("Parsed: {}", sexp);
                println!("Debug: {:?}", sexp);
            }
            Err(e) => println!("Error: {}", e),
        }
        println!();
    }

    // Demonstrate evaluation
    println!("Simple Evaluation Demo:");
    println!("-----------------------");

    let expr = "(+ (* 2 3) (- 10 5))";
    match parse_sexp(expr) {
        Ok(SExp::List(items)) => {
            println!("Expression: {}", expr);
            println!("Operator: {:?}", items.first());
            println!("Operands: {:?}", &items[1..]);
            if let Some(SExp::Symbol(op)) = items.first() {
                if op == "+" {
                    let sum: i64 = items[1..]
                        .iter()
                        .filter_map(|x| if let SExp::Int(n) = x { Some(*n) } else { None })
                        .sum();
                    println!("Sum of integers: {}", sum);
                }
            }
        }
        _ => {}
    }
}