diff --git a/Dockerfile b/Dockerfile index 0a6af04..dd4e9dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:22.04 as base ENV TZ=Europe ENV DEBIAN_FRONTEND noninteractive @@ -14,19 +14,26 @@ RUN stack --compiler ghc-9.4.7 setup RUN apt-get -y install python3-pip RUN pip install pre-commit -# formatter -RUN stack install ormolu +FROM base as build WORKDIR /peter-lang - COPY . /peter-lang -# cancel the build if there are formatting errors -RUN ormolu --mode check $(find . -name '*.hs') - # build project RUN stack build +FROM build as exe +ENTRYPOINT ["./peter.sh"] +CMD ["--help"] + +FROM build as test + +# formatter +RUN stack install ormolu + +# cancel the build if there are formatting errors +# RUN ormolu --mode check $(find . -name '*.hs') + # run tests RUN stack test diff --git a/README.md b/README.md index d087305..c95d056 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,139 @@ # peter-lang -Useless and barely functional toy programming language written in Haskell +Useless and barely functional toy programming language interpreter written in Haskell from scratch (without any research how to actually lay things out) + +The missing link between C and Python + + + +## Syntax + +C style syntax with Python like runtime interpretation and built in functions. + +### Main + +Main function `void main() {}` is the entry point. ``` void main() { - int i = 1; - int k = i + 1; + print("Hello, World!"); } ``` -## Syntax +Alternately, statements are executed from top to bottom in case no main is found. + +``` +print("Hello, World!"); +``` + +### Primitives + +- void +- bool +- int +- float +- str + +### Conversion + +- `str(1)` -> `"1"` +- `int("1")` -> `1` +- `float("1.3")` -> `1.3` + +#### Operations + +There is no operator precedence + +| Operation | void | bool | int | float | str | +| :-------: | :--: | :--: | --- | ----- | --- | +| + | x | x | ✓ | ✓ | ✓ | +| - | x | x | ✓ | ✓ | x | +| \* | x | x | ✓ | ✓ | x | +| \\ | x | x | ✓ | x | x | +| % | x | x | ✓ | x | x | +| == | x | ✓ | ✓ | ✓ | ✓ | +| != | x | ✓ | ✓ | ✓ | ✓ | +| < | x | ✓ | ✓ | ✓ | x | +| > | x | ✓ | ✓ | ✓ | x | +| <= | x | ✓ | ✓ | ✓ | x | +| >= | x | ✓ | ✓ | ✓ | x | +| && | x | ✓ | ✓ | x | x | +| \|\| | x | ✓ | ✓ | x | x | + +### Flow Control + +If: + +``` +if 1 < 2 { + i = 1; +} else { + i = -1; +} +``` -C-Style +while: ``` +int j = 0; +while j < 10 { + println(str(j)); + j = j + 1; +} +``` + +### Functions + +``` +int f() { + return 42; +} + void main() { - print("Hello, World!"); + int x = f(); +} +``` + +### Structs + +``` +struct T { + int x; + int y; } + +T t; +t.x = 2; ``` -## Limitations +### Built In + +- print -> prints +- println -> prints line +- input -> reads stdin + +## Limitations & Issues + +- left side of operation has to be atomic (because of [left recursive grammar](https://en.wikipedia.org/wiki/Left_recursion)) +- no deep copies of structs (yet) +- many bugs +- bad error messages from parser + +## Installation -- left side of operation has to be atomic +### Native + +Dependencies: + +- [stack](https://docs.haskellstack.org/en/stable/) +- ghc-9.4.7: `stack --compiler ghc-9.4.7 setup` + +### Docker + +```bash +docker build --target exe -t peter . +docker run peter ./examples/print.mmm +``` ## Usage @@ -42,9 +154,10 @@ stack run -- ./examples/short_hello_world.mmm stack run -- -i "print(\"Hello, World\");" ``` -### Tests +## Tests -`stack test` +- `stack test` +- `cd ./test/E2E/Interpreter/examples/ && ./check_examples.sh` ## Formatting @@ -56,4 +169,4 @@ ormolu --mode inplace $(find . -name '*.hs') ## CI -`docker build .` +`docker build .` + GitHub Actions diff --git a/examples/primitives.mmm b/examples/primitives.mmm new file mode 100644 index 0000000..2af4cc8 --- /dev/null +++ b/examples/primitives.mmm @@ -0,0 +1,16 @@ +void v = (); +bool = true; +int i = 1; +float f = 2.0; +str s = "hello"; + +println(str(i)); +println(str(f)); + +if 1 > int("0") { + println("bigger") +} + +if 1 < int(3.2) { + println("smaller") +} diff --git a/peter.sh b/peter.sh new file mode 100755 index 0000000..f3902a4 --- /dev/null +++ b/peter.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +EXE="stack run --" + +$EXE "$@" diff --git a/src/Interpreter/BuiltIn.hs b/src/Interpreter/BuiltIn.hs index 950cff9..a10c66a 100644 --- a/src/Interpreter/BuiltIn.hs +++ b/src/Interpreter/BuiltIn.hs @@ -95,6 +95,7 @@ toInt = valueToInt :: [Value] -> Int valueToInt val = case val of [IntValue i] -> i + [FloatValue f] -> truncate f [StringValue s] -> read s _ -> error ("No matching type for toInt: " ++ show val) diff --git a/src/Interpreter/Operation.hs b/src/Interpreter/Operation.hs index 61eba13..d68ad0b 100644 --- a/src/Interpreter/Operation.hs +++ b/src/Interpreter/Operation.hs @@ -34,7 +34,9 @@ interpretOperation Gt (FloatValue left) (FloatValue right) = BoolValue $ left > interpretOperation Le (FloatValue left) (FloatValue right) = BoolValue $ left <= right interpretOperation Ge (FloatValue left) (FloatValue right) = BoolValue $ left >= right interpretOperation Eq (FloatValue left) (FloatValue right) = BoolValue $ left == right +interpretOperation Neq (FloatValue left) (FloatValue right) = BoolValue $ left /= right -- String interpretOperation Plus (StringValue left) (StringValue right) = StringValue $ left ++ right +interpretOperation Eq (StringValue left) (StringValue right) = BoolValue $ left == right -- Invalid interpretOperation operator left right = error $ "Unsupported operation: " ++ show operator ++ " " ++ show left ++ " " ++ show right diff --git a/test/E2E/Interpreter/examples/check_examples.sh b/test/E2E/Interpreter/examples/check_examples.sh index 0bfaeac..14772ee 100755 --- a/test/E2E/Interpreter/examples/check_examples.sh +++ b/test/E2E/Interpreter/examples/check_examples.sh @@ -33,3 +33,5 @@ for item in "${file_paths[@]}"; do IFS=':' read -r file expected_output <<< "$item" check_stdout "$file" "$expected_output" done + +echo "All tests passed"