From f6f78b994de5ba86c2da6b01a13e78f000bcfc6b Mon Sep 17 00:00:00 2001 From: zihang Date: Fri, 13 Dec 2024 16:17:29 +0800 Subject: [PATCH 1/3] feat: add code check --- next/_ext/check.py | 48 +++++++++++++++++++++++++++++++++ next/conf.py | 2 +- next/language/introduction.md | 50 +++++++++++++++++++++++++---------- 3 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 next/_ext/check.py diff --git a/next/_ext/check.py b/next/_ext/check.py new file mode 100644 index 00000000..f826f736 --- /dev/null +++ b/next/_ext/check.py @@ -0,0 +1,48 @@ +import tempfile +import subprocess +from docutils.nodes import document, Node, NodeVisitor +from sphinx.application import Sphinx +from sphinx.util.typing import ExtensionMetadata +from sphinx.util import logging + +logger = logging.getLogger(__name__) + +def setup(app: Sphinx) -> ExtensionMetadata: + result = subprocess.run(["moonc", '-v'], capture_output=True) + if result.returncode != 0: + logger.warning("moonbit compiler is missing! No code check performed") + else: + logger.info(f"moonc version: {result.stdout.decode().strip()}") + app.connect("doctree-read", source_read_handler) + +class Visitor(NodeVisitor): + def visit_literal_block(self, node : Node): + if 'language' in node.attributes \ + and (node.attributes['language'] == 'moonbit' or node.attributes['language'] == 'mbt') \ + and 'classes' in node.attributes: + if node.attributes['classes'].count('expr') > 0: + # Check as expression + with tempfile.NamedTemporaryFile(suffix=".mbt") as temp_file: + temp_file.write("fn init {\n".encode()) + temp_file.write("\n".join([" " + line for line in node.astext().splitlines()]).encode()) + temp_file.write("\n}".encode()) + temp_file_path = temp_file.name + + result = subprocess.run(["moonc", "compile", "-stop-after-parsing", temp_file_path], capture_output=True) + if result.returncode != 0: + logger.error(f"code check failed: {result.stderr.decode().strip()}") + + elif node.attributes['classes'].count('top-level') > 0: + # Check as top-level + with tempfile.NamedTemporaryFile(suffix=".mbt") as temp_file: + temp_file.write(node.astext().encode()) + temp_file_path = temp_file.name + + result = subprocess.run(["moonc", "compile", "-stop-after-parsing", temp_file_path], capture_output=True) + if result.returncode != 0: + logger.error(f"code check failed: {result.stderr.decode().strip()}", location=node) + def unknown_visit(self, _node): + return + +def source_read_handler(_app : Sphinx, doctree: document): + doctree.walk(Visitor(doctree)) diff --git a/next/conf.py b/next/conf.py index 031585bc..58336500 100644 --- a/next/conf.py +++ b/next/conf.py @@ -18,7 +18,7 @@ from pathlib import Path sys.path.append(str(Path("_ext").resolve())) -extensions = ['myst_parser', 'lexer', 'indent', 'sphinx_copybutton'] +extensions = ['myst_parser', 'lexer', 'check', 'indent', 'sphinx_copybutton'] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', ".env", '.venv', "README", 'sources'] diff --git a/next/language/introduction.md b/next/language/introduction.md index 3fd8a0f1..a8091cb8 100644 --- a/next/language/introduction.md +++ b/next/language/introduction.md @@ -11,10 +11,18 @@ A MoonBit program consists of top-level definitions including: MoonBit distinguishes between statements and expressions. In a function body, only the last clause should be an expression, which serves as a return value. For example: -```{literalinclude} /sources/language/src/functions/top.mbt -:language: moonbit -:start-after: start expression -:end-before: end expression +```{code-block} moonbit +:class: top-level +fn foo() -> Int { + let x = 1 + x + 1 +} + +fn bar() -> Int { + let x = 1 + //! x + 1 + x + 2 +} ``` Expressions include: @@ -42,8 +50,18 @@ A variable can be declared as mutable or immutable using `let mut` or `let`, res A constant can only be declared at top level and cannot be changed. -```{literalinclude} /sources/language/src/variable/top.mbt -:language: moonbit +```{code-block} moonbit +:class: top-level +let zero = 0 + +const ZERO = 0 + +fn main { + //! const ZERO = 0 + let mut i = 10 + i = 20 + println(i + zero + ZERO) +} ``` ## Naming conventions @@ -64,20 +82,24 @@ There is a specialized function called `init` function. The `init` function is s 3. An `init` function can't be explicitly called or referred to by other functions. Instead, all `init` functions will be implicitly called when initializing a package. Therefore, `init` functions should only consist of statements. -```{literalinclude} /sources/language/src/main/top.mbt -:language: moonbit -:start-after: start init -:end-before: end init +```{code-block} moonbit +:class: top-level +fn init { + let x = 1 + println(x) +} ``` There is another specialized function called `main` function. The `main` function is the main entrance of the program, and it will be executed after the initialization stage. Same as the `init` function, it has no parameter list nor return type. -```{literalinclude} /sources/language/src/main/top.mbt -:language: moonbit -:start-after: start main -:end-before: end main +```{code-block} moonbit +:class: top-level +fn main { + let x = 2 + println(x) +} ``` The previous two code snippets will print the following at runtime: From 26906cc51d9ca7a5c92bb9161d63f1fabbcfbd4d Mon Sep 17 00:00:00 2001 From: zihang Date: Fri, 13 Dec 2024 16:29:02 +0800 Subject: [PATCH 2/3] fix: allow failure when moonc is missing --- next/_ext/check.py | 17 ++++++++++++----- next/_ext/indent.py | 4 +++- next/conf.py | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/next/_ext/check.py b/next/_ext/check.py index f826f736..eeb89fcb 100644 --- a/next/_ext/check.py +++ b/next/_ext/check.py @@ -8,12 +8,19 @@ logger = logging.getLogger(__name__) def setup(app: Sphinx) -> ExtensionMetadata: - result = subprocess.run(["moonc", '-v'], capture_output=True) - if result.returncode != 0: + metadata = { + "version": "0.1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } + try: + result = subprocess.run(["moonc", '-v'], capture_output=True, check=True) + except (FileNotFoundError, subprocess.CalledProcessError): logger.warning("moonbit compiler is missing! No code check performed") - else: - logger.info(f"moonc version: {result.stdout.decode().strip()}") - app.connect("doctree-read", source_read_handler) + return metadata + logger.info(f"moonc version: {result.stdout.decode().strip()}") + app.connect("doctree-read", source_read_handler) + return metadata class Visitor(NodeVisitor): def visit_literal_block(self, node : Node): diff --git a/next/_ext/indent.py b/next/_ext/indent.py index 9b775022..d1780673 100644 --- a/next/_ext/indent.py +++ b/next/_ext/indent.py @@ -9,4 +9,6 @@ def __init__(self, s, _): def __radd__(self, _): return f"```\n{self.s}\n```" -i18n.indent = ModifiedIndent \ No newline at end of file +i18n.indent = ModifiedIndent + +def setup(_app): pass \ No newline at end of file diff --git a/next/conf.py b/next/conf.py index 58336500..7737fd9f 100644 --- a/next/conf.py +++ b/next/conf.py @@ -27,7 +27,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_book_theme' -html_static_path = ['_static'] +# html_static_path = ['_static'] html_theme_options = { "repository_url": "https://github.com/moonbitlang/moonbit-docs/", "path_to_docs": "next", From 61601b43a1f12d7e27d1cc34f77ca7d96494b1b1 Mon Sep 17 00:00:00 2001 From: zihang Date: Mon, 23 Dec 2024 11:09:04 +0800 Subject: [PATCH 3/3] fix: flush after write --- next/_ext/check.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/next/_ext/check.py b/next/_ext/check.py index eeb89fcb..92d1d9cc 100644 --- a/next/_ext/check.py +++ b/next/_ext/check.py @@ -33,6 +33,7 @@ def visit_literal_block(self, node : Node): temp_file.write("fn init {\n".encode()) temp_file.write("\n".join([" " + line for line in node.astext().splitlines()]).encode()) temp_file.write("\n}".encode()) + temp_file.flush() temp_file_path = temp_file.name result = subprocess.run(["moonc", "compile", "-stop-after-parsing", temp_file_path], capture_output=True) @@ -43,6 +44,7 @@ def visit_literal_block(self, node : Node): # Check as top-level with tempfile.NamedTemporaryFile(suffix=".mbt") as temp_file: temp_file.write(node.astext().encode()) + temp_file.flush() temp_file_path = temp_file.name result = subprocess.run(["moonc", "compile", "-stop-after-parsing", temp_file_path], capture_output=True)