Repository: jd-develop/huitr
Pull request #1: Add examples & CLI program
Author: login: Hugo-Vangilluwen
Created: 2025-05-14T19:04:51Z | Merged: 2025-05-14T20:03:19Z

I have write some examples and create a simple CLI program to execute Huitr file.


Diff:
diff --git a/README.md b/README.md
index 9827f50..01e942d 100644
--- a/README.md
+++ b/README.md
@@ -6,3 +6,13 @@
 Huitr – a purely functional programming language.
 
 “Huitr” stands for “Huitr is tough to read”.
+
+### Execute
+
+```bash
+./main.py path_to_your_file
+```
+
+### Grammar
+
+[Here](src/parser/grammar.txt)
diff --git a/all_examples.sh b/all_examples.sh
new file mode 100755
index 0000000..f27e182
--- /dev/null
+++ b/all_examples.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/bash
+# Run all huitr examples
+
+for ex in $(ls examples/*.8tr); do
+    echo -n "File : "
+    basename $ex
+    ./main.py -d $ex
+done
diff --git a/examples/chain.8tr b/examples/chain.8tr
new file mode 100644
index 0000000..0a3c08b
--- /dev/null
+++ b/examples/chain.8tr
@@ -0,0 +1,9 @@
+..
+Huitr - a purely functional programming language.
+Copyright (C) 2025  Hugo Vangilluwen
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+..
+
+1>0.0>"youpi"
diff --git a/examples/int.8tr b/examples/int.8tr
new file mode 100644
index 0000000..2bf8105
--- /dev/null
+++ b/examples/int.8tr
@@ -0,0 +1,9 @@
+..
+Huitr - a purely functional programming language.
+Copyright (C) 2025  Hugo Vangilluwen
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+..
+
+42
diff --git a/examples/unit.8tr b/examples/unit.8tr
new file mode 100644
index 0000000..404af4f
--- /dev/null
+++ b/examples/unit.8tr
@@ -0,0 +1,11 @@
+..
+Huitr - a purely functional programming language.
+Copyright (C) 2025  Hugo Vangilluwen
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+..
+
+. Output : [None, None]
+();
+3>()
diff --git a/main.py b/main.py
old mode 100644
new mode 100755
index cdb7665..8944220
--- a/main.py
+++ b/main.py
@@ -17,6 +17,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-from src.lexer.lexer import Lexer
+from src.cli.cli import main_cli
 
-print("Hello world!")
+main_cli()
diff --git a/src/cli/cli.py b/src/cli/cli.py
new file mode 100644
index 0000000..cf39e09
--- /dev/null
+++ b/src/cli/cli.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+# Huitr - a purely functional programming language.
+# Copyright (C) 2025  Hugo Vangilluwen
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# Global Python imports
+import argparse
+
+# Huitr API imports
+from src.cli.execute import execute_file
+
+
+def main_cli():
+    arg_parser = argparse.ArgumentParser(
+        prog="Huitr file executor",
+        description="Execute a Huitr program written in a file",
+        epilog="""Huitr – a purely functional programming language."
+                “Huitr” stands for “Huitr is tough to read”."""
+        )
+    arg_parser.add_argument(
+        'filename',
+        type=str,
+        help="File where the Huitr program is"
+        )
+    arg_parser.add_argument(
+        "-d",
+        "--debug",
+        action="store_true",
+        help="print tokens structure"
+        )
+
+    args = arg_parser.parse_args()
+
+    execute_file(args.filename, args.debug)
diff --git a/src/cli/execute.py b/src/cli/execute.py
new file mode 100644
index 0000000..f5c4022
--- /dev/null
+++ b/src/cli/execute.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+# Huitr - a purely functional programming language.
+# Copyright (C) 2025  Hugo Vangilluwen
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.*
+
+# Global Python imports
+import os
+
+# Huitr API imports
+from src.lexer.lexer import Lexer
+from src.parser.parser import Parser
+from src.runtime. interpreter import visit
+
+
+def execute_file(filename: str, debug: bool):
+    if not os.path.exists(filename):
+        print(f"File {filename} not found")
+        return
+
+    with open(filename, "r") as file_source:
+        file_lexer = Lexer(file_source.read(), filename)
+        file_tokens = file_lexer.tokenize()
+        if file_tokens[1] is None:
+            file_parser = Parser(file_tokens[0])
+            file_output = file_parser.parse()
+            if file_output[0] is None:
+                print(file_output[1])
+            else:
+                if debug:
+                    print(file_output[0])
+                print(visit(file_output[0]))
+        else:
+            print(file_tokens[1])
diff --git a/src/lexer/lexer.py b/src/lexer/lexer.py
index bea1968..a1c4cc8 100644
--- a/src/lexer/lexer.py
+++ b/src/lexer/lexer.py
@@ -116,7 +116,7 @@ def tokenize(self) -> tuple[list[Token], None | SyntaxError]:
                     while not (self.current == self.get_next() == "."):
                         n = self.next()
                         if n is None:  # end of file
-                            break
+                            return [], SyntaxError("Unclosed multi-line comment", self.cursor_pos)
                     self.next()  # Multi-line comments ends with .. (double dot)
                 else:
                     while self.current != "\n":
diff --git a/src/parser/grammar.txt b/src/parser/grammar.txt
index 0d16381..b9d04a3 100644
--- a/src/parser/grammar.txt
+++ b/src/parser/grammar.txt
@@ -24,3 +24,19 @@ identifier ::= NAMESP? IDENTIFIER (NAMESP IDENTIFIER)+
 function ::= LSQUARE statements RSQUARE
 
 unit ::= LPAREN RPAREN
+
+# Punctuation
+SEMICOLON ::= ";"
+COMMA ::= ","
+DOT ::= "."
+DDOT ::= ".."
+LPAREN ::= "("
+RPAREN ::= ")"
+LSQUARE ::= "["
+RSQUARE ::= "]"
+CHAINOP ::= ">"
+NAMESP ::= "::"
+
+# To make a comment
+comment ::= DOT string_literal DOT
+multiline_comment = DDOT string_literal DDOT
diff --git a/src/runtime/interpreter.py b/src/runtime/interpreter.py
index 498fcac..5c314bc 100644
--- a/src/runtime/interpreter.py
+++ b/src/runtime/interpreter.py
@@ -7,24 +7,93 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+# Global Python imports
+from typing import Any
+
 # Huitr API imports
 from src.parser.nodes import *
 
 
 def visit_chain_node(node: ChainNode):
+    # stack = []
+    # for n in node.chain:
+    #     if isinstance(n, FuncDefNode):
+    #         return NotImplemented
+    #     elif isinstance(n, UnitNode):
+    #         stack = []
+    #     else:
+    #         stack.append(visit(n))
+
+    # if len(stack) == 0:
+    #     return None
+    # elif len(stack) == 1:
+    #     return stack[0]
+    # else:
+    #     return stack
     ...
 
 
-def visit_list_node(node: ListNode):
-    ...
+# def visit_list_node(node: ListNode) -> Any | list[Any]:
+#     if len(node.list) == 1:
+#         return visit(node.list[0])
+#     else:
+#         return [visit(n) for n in node.list]
+
+
+# def visit_string_node(node: ListNode) -> str:
+#     return node.string_token.value
+
+
+# def visit_int_node(node: IntNode) -> int:
+#     return node.int_token.value
+
+
+# def visit_float_node(node: FloatNode) -> float:
+#     return node.float_token.value
+
+
+# def visit_no_node(node: NoNode) -> None:
+#     return None
+
+
+# def visit_identifier_node(node: IdentifierNode):
+#     pass
+
+
+# def visit_libidentifier_node(node: LibIdentifierNode):
+#     pass
+
+
+# def visit_funcdef_node(node: FuncDefNode):
+#     pass
+
+
+# def visit_unit_node(node: UnitNode):
+#     pass
 
 
 def visit(node: Node):
     """Visit (execute) a node"""
     if isinstance(node, ChainNode):
         return visit_chain_node(node)
-    elif isinstance(node, ListNode):
-        return visit_list_node(node)
+    # elif isinstance(node, ListNode):
+    #     return visit_list_node(node)
+    # elif isinstance(node, StringNode):
+    #     return visit_string_node(node)
+    # elif isinstance(node, IntNode):
+    #     return visit_int_node(node)
+    # elif isinstance(node, FloatNode):
+    #     return visit_float_node(node)
+    # elif isinstance(node, NoNode):
+    #     return visit_no_node(node)
+    # elif isinstance(node, IdentifierNode):
+    #     return visit_identifier_node(node)
+    # elif isinstance(node, LibIdentifierNode):
+    #     return visit_libidentifier_node(node)
+    # elif isinstance(node, FuncDefNode):
+    #     return visit_funcdef_node(node)
+    # elif isinstance(node, UnitNode):
+    #     return visit_unit_node(node)
     else:
         return NotImplemented
 
diff --git a/src/runtime/value.py b/src/runtime/value.py
index f4ded47..5315f35 100644
--- a/src/runtime/value.py
+++ b/src/runtime/value.py
@@ -9,6 +9,7 @@
 
 # Global Python imports
 from typing import Any
+
 # Huitr API imports
 from src.lexer.position import Position
 from src.runtime.context import Context


Reviews:
user: login: jd-develop
body: Je vais corriger ça vite fait
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-14T19:25:16Z
user: login: jd-develop
body: @Hugo-Vangilluwen je te laisse regarder un peu ce que j’ai fait (pour l’interpréteur j’ai commenté pour l’instant, ça pourra peut-être servir quand on codera proprement avec des `Value`s)
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-14T19:30:41Z
user: login: Hugo-Vangilluwen
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T19:52:12Z

Review comments:
diff_hunk: @@ -116,13 +116,13 @@ def tokenize(self) -> tuple[list[Token], None | SyntaxError]:
                     while not (self.current == self.get_next() == "."):
                         n = self.next()
                         if n is None:  # end of file
-                            break
+                            return [], SyntaxError("Unclosed multi-line comment", self.cursor_pos)
                     self.next()  # Multi-line comments ends with .. (double dot)
                 else:
-                    while self.current != "\n":
+                    while self.current != ".":
path: src/lexer/lexer.py
user: login: jd-develop
body: Les commentaires inline terminent par un \n comme tous les langages…
created_at: 2025-05-14T19:21:26Z
author_association: OWNER
diff_hunk: @@ -116,13 +116,13 @@ def tokenize(self) -> tuple[list[Token], None | SyntaxError]:
                     while not (self.current == self.get_next() == "."):
                         n = self.next()
                         if n is None:  # end of file
-                            break
+                            return [], SyntaxError("Unclosed multi-line comment", self.cursor_pos)
                     self.next()  # Multi-line comments ends with .. (double dot)
                 else:
-                    while self.current != "\n":
+                    while self.current != ".":
path: src/lexer/lexer.py
user: login: Hugo-Vangilluwen
body: Mince, je me suis basé sur la grammaire. Et c'est moi qui ait écrit cette partie de la grammaire :)
created_at: 2025-05-14T19:52:12Z
author_association: COLLABORATOR

PR comments:
user: login: erwan-fb
created_at: 2025-05-14T19:23:20Z
body: you were bored weren't you?
author_association: COLLABORATOR
user: login: Hugo-Vangilluwen
created_at: 2025-05-14T19:56:53Z
body: J'ai codé l'interpréteur pour pouvoir tester la CLI.
author_association: COLLABORATOR
user: login: jd-develop
created_at: 2025-05-14T20:03:14Z
body: bon je merge du coup :)
author_association: OWNER