diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..8ebb199
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "ldmc"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+chumsky = "0.9.0"
+ariadne = "0.2"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..5df47b9
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,93 @@
+use {
+    ariadne::{Color, Label, Report, ReportKind, Source},
+    chumsky::{
+        prelude::*, text::*
+    }
+};
+
+#[derive(Debug)]
+struct Morphism {
+    symbol: String,
+    src_type: String,
+    dst_type: String,
+    type_args: Vec<(String, String)>,
+    locations: Vec<String>
+}
+
+/* morphism-base text format:
+ *     NAME '(' [TYPE-ARG-NAME ':' KIND]  ')'
+ *           SRC-TYPE
+ *     '-->' DST-TYPE
+ *     '@' [ LOCATION ':' ]
+ */
+fn parser() -> impl Parser<char, Vec<Morphism>, Error = Simple<char>> {
+    ident().padded()
+    .then(
+        ident().padded()
+        .then_ignore(just(':').padded())
+        .then(none_of(",)").repeated().padded())
+        .separated_by(just(',').padded())
+        .delimited_by(just('('), just(')'))
+    )
+    .then_ignore(just('\n'))
+    .then(
+        take_until(just("-->").ignored())
+        .then(take_until(just('@').ignored()))
+    )
+    .then(
+        none_of(":\n").repeated().separated_by(just(':'))
+    )
+    .map(|(((symbol, type_args), ((src_type, _), (dst_type, _))), locations)| {
+        Morphism {
+            symbol,
+            src_type: src_type.iter().collect(),
+            dst_type: dst_type.iter().collect(),
+            type_args: type_args.into_iter().map(|(v,k)| (v,k.into_iter().collect())).collect(),
+            locations: locations.into_iter().map(|l| l.into_iter().collect()).collect()
+        }
+    })
+    .separated_by(text::newline())
+}
+
+fn main() {
+    println!("Hello, world!");
+
+    let src = "
+        morph_digit_as_char_to_uint8 (Radix:ℤ_16)
+            <Digit Radix> ~ Char ~ Ascii ~ Byte
+        --> <Digit Radix> ~ x86.UInt8 ~ Byte
+        @lib/libmorph_posint.so:src/posint.c
+
+        morph_string_as_nullterm_to_length_prefix ()
+            [~<ValueDelim '\0'> Char ~ Ascii]
+        --> [~<LengthPrefix x86.UInt64> Char ~ Ascii]
+        @lib/libmorph_length-prefix.so:src/length_prefix.c
+
+        morph_string_as_length_prefix_to_nullterm ()
+            [~<LengthPrefix x86.UInt64> Char ~ Ascii]
+        --> [~<ValueDelim '\0'> Char ~ Ascii]
+        @lib/libmorph_length-prefix.so:src/length_prefix.c
+    ";
+
+    let result = parser().parse(src);
+
+    match result {
+        Ok(morphisms) => {
+            println!("parse ok.\n{:?}", morphisms);
+        }
+        Err(errs) => {
+            errs.into_iter().for_each(|e| {
+                    Report::build(ReportKind::Error, (), e.span().start)
+                        .with_message(e.to_string())
+                        .with_label(
+                            Label::new(e.span())
+                                .with_message(e)
+                                .with_color(Color::Red),
+                        )
+                        .finish()
+                        .print(Source::from(&src))
+                        .unwrap()
+                    });
+        }
+    }
+}