From 5a6d2792c44e11d63eb063386f5615a3fc5b01ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Sat, 10 Feb 2024 10:08:37 +0100 Subject: [PATCH] Parse macro definitions as part of a program --- app/elm/Compiler/Ast.elm | 52 +++++++++++++++++++++++++++++++++++-- app/elm/Compiler/Parser.elm | 35 ++++++++++++++++++++++++- app/elm/Vm/Error.elm | 4 +++ app/elm/Vm/Exception.elm | 1 + app/elm/Vm/Vm.elm | 3 +++ tests/Test/Parser.elm | 10 +++---- tests/Test/Run.elm | 12 +++++++++ 7 files changed, 109 insertions(+), 8 deletions(-) diff --git a/app/elm/Compiler/Ast.elm b/app/elm/Compiler/Ast.elm index 53b7788..140c449 100644 --- a/app/elm/Compiler/Ast.elm +++ b/app/elm/Compiler/Ast.elm @@ -1,5 +1,6 @@ module Compiler.Ast exposing ( CompiledFunction + , CompiledMacro , CompiledProgram , Context(..) , Function @@ -26,6 +27,7 @@ import Vm.Type as Type type alias Program = { functions : List Function + , macros : List Macro , body : List Node } @@ -35,6 +37,7 @@ type alias Program = type alias CompiledProgram = { instructions : List Instruction , compiledFunctions : List CompiledFunction + , compiledMacros : List CompiledMacro } @@ -48,7 +51,7 @@ type alias Function = type alias Macro = { name : String - , requiredArguments : List String + , arguments : List String , body : List Node } @@ -69,6 +72,15 @@ type alias CompiledFunctionInstance = } +{-| Represent a compiled macro. +-} +type alias CompiledMacro = + { name : String + , arguments : List String + , body : List Instruction + } + + type Node = Sequence (List Node) Node | Repeat Node (List Node) @@ -860,16 +872,20 @@ compile context node = compileProgram : Context -> Program -> CompiledProgram -compileProgram context { functions, body } = +compileProgram context { functions, macros, body } = let compiledFunctions = List.map compileFunction functions + compiledMacros = + List.map compileMacro macros + instructions = List.concatMap (compileInContext context) body in { instructions = instructions , compiledFunctions = compiledFunctions + , compiledMacros = compiledMacros } @@ -973,3 +989,35 @@ compileFunction ({ name, requiredArguments, optionalArguments } as function) = , optionalArguments = List.map Tuple.first optionalArguments , instances = compileFunctionInstances function } + + +{-| Compile a macro. +-} +compileMacro : Macro -> CompiledMacro +compileMacro { name, arguments, body } = + let + instructionsForArguments = + [ [ PushLocalScope ] + , List.concatMap compileRequiredArgument arguments + ] + |> List.concat + + instructionsForBody = + [ List.concatMap (compileInContext Statement) body + , [ PushVoid + , PopLocalScope + , Instruction.Return + ] + ] + |> List.concat + + compiledBody = + [ instructionsForArguments + , instructionsForBody + ] + |> List.concat + in + { name = name + , arguments = arguments + , body = compiledBody + } diff --git a/app/elm/Compiler/Parser.elm b/app/elm/Compiler/Parser.elm index 939c06b..41556b8 100644 --- a/app/elm/Compiler/Parser.elm +++ b/app/elm/Compiler/Parser.elm @@ -17,7 +17,7 @@ module Compiler.Parser exposing -} import Char -import Compiler.Ast as Ast exposing (CompiledFunction) +import Compiler.Ast as Ast exposing (CompiledFunction, CompiledMacro) import Compiler.Ast.Introspect as Introspect import Compiler.Parser.Callable as Callable import Compiler.Parser.Context exposing (Context(..)) @@ -48,6 +48,8 @@ type alias Error = type alias State = { newFunctions : Dict String Ast.Function , existingFunctions : Dict String CompiledFunction + , newMacros : Dict String Ast.Macro + , existingMacros : Dict String CompiledMacro , parsedBody : List Ast.Node , inFunction : Bool } @@ -57,6 +59,8 @@ defaultState : State defaultState = { newFunctions = Dict.empty , existingFunctions = Dict.empty + , newMacros = Dict.empty + , existingMacros = Dict.empty , parsedBody = [] , inFunction = False } @@ -77,6 +81,7 @@ toplevel state = let program = { functions = Dict.values state.newFunctions + , macros = Dict.values state.newMacros , body = state.parsedBody } in @@ -95,6 +100,7 @@ toplevel_ state = |. Helper.maybeSpaces |= P.oneOf [ function state + , macro state , toplevelStatements state , P.succeed state ] @@ -236,6 +242,33 @@ functionBody_ state acc = ] +defineMacroUnlessDefined : State -> Ast.Macro -> State +defineMacroUnlessDefined state newMacro = + if + Dict.member newMacro.name state.newMacros + || Dict.member newMacro.name state.existingMacros + then + let + parsedBody = + state.parsedBody + ++ [ Ast.Raise (Exception.MacroAlreadyDefined newMacro.name) ] + in + { state | parsedBody = parsedBody } + + else + let + newMacros = + Dict.insert newMacro.name newMacro state.newMacros + in + { state | newMacros = newMacros } + + +macro : State -> Parser State +macro state = + macroDefinition state + |> P.map (defineMacroUnlessDefined state) + + defineMacro : State -> Ast.Macro -> Parser Ast.Macro defineMacro state newMacro = functionBody state diff --git a/app/elm/Vm/Error.elm b/app/elm/Vm/Error.elm index 9716045..ff2365d 100644 --- a/app/elm/Vm/Error.elm +++ b/app/elm/Vm/Error.elm @@ -34,6 +34,7 @@ type Error | NotEnoughInputs String | TooManyInputs String | FunctionAlreadyDefined String + | MacroAlreadyDefined String | CallableUndefined String @@ -64,6 +65,9 @@ toString error = FunctionAlreadyDefined functionName -> functionName ++ " is already defined" + MacroAlreadyDefined macroName -> + macroName ++ " is already defined" + CallableUndefined functionName -> "I don’t know how to " ++ functionName diff --git a/app/elm/Vm/Exception.elm b/app/elm/Vm/Exception.elm index ae9cf92..2f30bfe 100644 --- a/app/elm/Vm/Exception.elm +++ b/app/elm/Vm/Exception.elm @@ -9,4 +9,5 @@ type Exception | NotEnoughInputs String | TooManyInputs String | FunctionAlreadyDefined String + | MacroAlreadyDefined String | CallableUndefined String diff --git a/app/elm/Vm/Vm.elm b/app/elm/Vm/Vm.elm index 69797dc..66e4860 100644 --- a/app/elm/Vm/Vm.elm +++ b/app/elm/Vm/Vm.elm @@ -1095,6 +1095,9 @@ raise exception vm = Exception.FunctionAlreadyDefined functionName -> Err <| FunctionAlreadyDefined functionName + Exception.MacroAlreadyDefined macroName -> + Err <| MacroAlreadyDefined macroName + Exception.CallableUndefined functionName -> Err <| CallableUndefined functionName diff --git a/tests/Test/Parser.elm b/tests/Test/Parser.elm index c073bdf..404c8f6 100644 --- a/tests/Test/Parser.elm +++ b/tests/Test/Parser.elm @@ -131,18 +131,18 @@ parsesMacro source macro = macroDefinition : Test macroDefinition = describe "define a macro" <| - [ test "with mandatory arguments and no body" <| + [ test "with arguments and no body" <| \_ -> parsesMacro ".macro foo :bar :baz\nend\n" { name = "foo" - , requiredArguments = [ "bar", "baz" ] + , arguments = [ "bar", "baz" ] , body = [] } - , test "with mandatory argument and body" <| + , test "with argument and body" <| \_ -> parsesMacro ".macro foo :bar\nprint :bar\nend\n" { name = "foo" - , requiredArguments = [ "bar" ] + , arguments = [ "bar" ] , body = [ Ast.CommandN { name = "print", f = C.printN, numberOfDefaultArguments = 1 } @@ -153,7 +153,7 @@ macroDefinition = \_ -> parsesMacro ".macro foo\nend\n" { name = "foo" - , requiredArguments = [] + , arguments = [] , body = [] } ] diff --git a/tests/Test/Run.elm b/tests/Test/Run.elm index 7b6aa90..776b6d2 100644 --- a/tests/Test/Run.elm +++ b/tests/Test/Run.elm @@ -231,6 +231,18 @@ end ] +macroDefinition : Test +macroDefinition = + describe "define macro" <| + [ printsLines + """.macro foo :bar +output lput (word "" :bar) [print] +end +""" + [] + ] + + parenthesesIndicatingPreference : Test parenthesesIndicatingPreference = describe "parentheses can indicate preference" <|