Skip to content

Commit

Permalink
feat: add code check
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-jerry-ye committed Dec 13, 2024
1 parent cd29192 commit a7d8444
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 15 deletions.
48 changes: 48 additions & 0 deletions next/_ext/check.py
Original file line number Diff line number Diff line change
@@ -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))
2 changes: 1 addition & 1 deletion next/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
50 changes: 36 additions & 14 deletions next/language/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down

0 comments on commit a7d8444

Please sign in to comment.