├── deps ├── .gitignore └── build.jl ├── .github └── workflows │ └── main.yml ├── .gitignore ├── src ├── array.jl ├── fixLines.jl ├── shouldFail.jl ├── metaModelicaTypes.jl ├── MetaModelica.jl ├── utilityMacros.jl ├── exportmetaRuntime.jl ├── union.jl ├── functionInheritance.jl ├── dangerous.jl ├── matchcontinue.jl └── metaRuntime.jl ├── test ├── metaModelicaTypeTest.jl ├── shouldFailTests.jl ├── crossModuleMatchTest.jl ├── crossModule.jl ├── runtests.jl ├── matchTests.jl ├── uniontypeTests.jl ├── functionExtensionTest.jl ├── matchcontinueTests.jl ├── listTests.jl └── runtimeTest.jl ├── Project.toml ├── .CI ├── Jenkinsfile └── docker │ └── Dockerfile ├── LICENSE.md └── README.md /deps/.gitignore: -------------------------------------------------------------------------------- 1 | *.log -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Actions to be added here. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.*.cov 3 | *.jl.mem 4 | *~ 5 | deps/deps.jl 6 | ~* 7 | -------------------------------------------------------------------------------- /src/array.jl: -------------------------------------------------------------------------------- 1 | #= It seems that we can just reuse the Julia arrays straight away=# 2 | 3 | #= For MetaModelica comptability =# 4 | function array(args...) 5 | [args...] 6 | end 7 | -------------------------------------------------------------------------------- /deps/build.jl: -------------------------------------------------------------------------------- 1 | @info("MetaModelica: Starting build script") 2 | 3 | push!(LOAD_PATH, "@v#.#", "@stdlib") 4 | 5 | using Pkg 6 | 7 | # Add dependencies 8 | function buildDeps() 9 | Pkg.add("DataStructures") 10 | Pkg.add("MacroTools") 11 | Pkg.add("ImmutableList") 12 | Pkg.add("Setfield") 13 | @info("Build all dependencies succesfull") 14 | end 15 | 16 | buildDeps() 17 | @info("MetaModelica: Finished build script") 18 | -------------------------------------------------------------------------------- /test/metaModelicaTypeTest.jl: -------------------------------------------------------------------------------- 1 | module MetaModelicaTypeTest 2 | 3 | using MetaModelica 4 | using Test 5 | 6 | function f(a::ModelicaReal, b::ModelicaReal)::ModelicaReal 7 | return a + b 8 | end 9 | 10 | function f2(a::ModelicaInteger)::ModelicaInteger 11 | return a 12 | end 13 | 14 | @test_throws MethodError f(true, true) 15 | 16 | @test_throws MethodError f(true, 1) 17 | 18 | @test f2(1) + f2(1) == 2 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/shouldFailTests.jl: -------------------------------------------------------------------------------- 1 | module ShouldFailTests 2 | using MetaModelica 3 | using Test 4 | 5 | @test_throws MatchFailure @shouldFail(false) 6 | @test_throws MatchFailure @shouldFail(true) 7 | 8 | @test begin 9 | try 10 | @shouldFail throw(MatchFailure("My failure")) 11 | true 12 | catch e 13 | if !isa(e, MetaModelicaException) 14 | rethrow(e) 15 | end 16 | false 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/crossModuleMatchTest.jl: -------------------------------------------------------------------------------- 1 | module CrossModuleTest 2 | include("crossModule.jl") 3 | import .CrossModule 4 | using Test 5 | 6 | function callIsAC() 7 | c::CrossModule.C = CrossModule.C() 8 | CrossModule.isC(c) 9 | end 10 | 11 | function callIsAC2() 12 | c::CrossModule.C = CrossModule.C() 13 | CrossModule.isC2(c) 14 | end 15 | 16 | @test callIsAC() == true 17 | 18 | @test callIsAC2() == CrossModule.C 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/crossModule.jl: -------------------------------------------------------------------------------- 1 | module CrossModule 2 | 3 | using ExportAll 4 | using MetaModelica 5 | 6 | abstract type S end 7 | 8 | struct A <: S end 9 | 10 | struct B <: S end 11 | 12 | struct C <: S end 13 | 14 | function isC(a::S) 15 | @match a begin 16 | A() => false 17 | B() => false 18 | C() => true 19 | end 20 | end 21 | 22 | function isC2(a::S) 23 | @match a begin 24 | A() => A 25 | B() => B 26 | C() => C 27 | end 28 | end 29 | 30 | @exportAll() 31 | 32 | end 33 | -------------------------------------------------------------------------------- /src/fixLines.jl: -------------------------------------------------------------------------------- 1 | function isLineNumberNode(a, file::String, lines::LineNumberNode) 2 | if typeof(a) <: LineNumberNode 3 | a.file == Symbol(file) 4 | else 5 | false 6 | end 7 | end 8 | 9 | function replaceLineNum(a::Expr, file::String, lines::LineNumberNode) 10 | replace!(arg -> isLineNumberNode(arg, file, lines) ? lines : arg, a.args) 11 | for n in a.args 12 | replaceLineNum(n, file, lines) 13 | end 14 | end 15 | function replaceLineNum(a::Any, file::String, lines::LineNumberNode) end 16 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "MetaModelica" 2 | uuid = "9d7f2a79-07b5-5542-8b19-c0100dda6b06" 3 | authors = ["Martin Sjölund ", "John Tinnerholm "] 4 | version = "0.0.5" 5 | 6 | [deps] 7 | Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" 8 | DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" 9 | ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" 10 | FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" 11 | ImmutableList = "4a558cac-c1ed-11e9-20da-3584bcd8709a" 12 | MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" 13 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 14 | 15 | [compat] 16 | julia = "1.7" 17 | -------------------------------------------------------------------------------- /src/shouldFail.jl: -------------------------------------------------------------------------------- 1 | """ 2 | This function implements the @shouldFail macro. 3 | @shouldFail(arg) succeeds if arg fails, where arg is a local match-equation/statement. 4 | """ 5 | function genShouldFail(expr) 6 | quote 7 | local __have_failed__ = false 8 | try 9 | $(esc(expr)) 10 | __have_failed__ = true 11 | catch 12 | end 13 | if __have_failed__ 14 | throw(MatchFailure("got failure", "Failure fail")) 15 | end 16 | end 17 | end 18 | """ @shouldFail(arg) succeeds if arg fails, where arg is a local match-equation/statement. """ 19 | macro shouldFail(expr) 20 | genShouldFail(expr) 21 | end 22 | 23 | export @shouldFail 24 | -------------------------------------------------------------------------------- /src/metaModelicaTypes.jl: -------------------------------------------------------------------------------- 1 | #= !!For internal use only!! =# 2 | module MetaModelicaTypes 3 | 4 | #= Real numbers are a bit different in Modelica compared to Julia =# 5 | const ModelicaReal = Float64 6 | const ModelicaInteger = Int 7 | #= 8 | TODO: 9 | 10 | #1 Ideally we would like MetaModelica Real to be defined in such a way that it 11 | can accept Int64, but when it returns it will return AbstractFloat 12 | 13 | #2 Furthermore, we do not wish to mix ModelicaInteger with ModelicaReal. 14 | Attemped this during the 7th of July. I failed :( 15 | =# 16 | 17 | abstract type MetaModelicaException <: Exception end 18 | 19 | export ModelicaInteger, ModelicaReal, MetaModelicaException 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using MetaModelica 2 | using Test 3 | 4 | @testset "MetaModelica" begin 5 | 6 | @testset "@match" begin 7 | include("./matchTests.jl") 8 | @testset "@matchcontinue" begin 9 | include("./matchcontinueTests.jl") 10 | end 11 | end 12 | 13 | @testset "list" begin 14 | include("./listTests.jl") 15 | end 16 | 17 | @testset "metaModelicatypes" begin 18 | include("./metaModelicaTypeTest.jl") 19 | end 20 | 21 | @testset "Uniontype" begin 22 | include("./uniontypeTests.jl") 23 | end #=End of uniontype =# 24 | 25 | @testset "Function extension test" begin 26 | # include("./functionExtensionTest.jl") Disabled for now. Julia 1.6 broke something.. 27 | end #= Function extension test =# 28 | 29 | @testset "Runtime tests" begin 30 | include("./runtimeTest.jl") 31 | end 32 | 33 | @testset "Cross module match" begin 34 | include("crossModuleMatchTest.jl") 35 | end 36 | 37 | @testset "Should fail tests" begin 38 | include("shouldFailTests.jl") 39 | end 40 | 41 | end #= End MetaModelica testset =# 42 | -------------------------------------------------------------------------------- /src/MetaModelica.jl: -------------------------------------------------------------------------------- 1 | module MetaModelica 2 | 3 | import MacroTools 4 | import MacroTools: @capture 5 | import ExportAll 6 | #= 7 | Have to treat the types slightly different. 8 | Precompilation of the types need to occur before everything else 9 | =# 10 | include("metaModelicaTypes.jl") 11 | import .MetaModelicaTypes 12 | using .MetaModelicaTypes 13 | include("union.jl") 14 | import .UniontypeDef 15 | using .UniontypeDef 16 | using ImmutableList 17 | include("matchcontinue.jl") 18 | include("functionInheritance.jl") 19 | include("metaRuntime.jl") 20 | include("shouldFail.jl") 21 | include("utilityMacros.jl") 22 | 23 | export @match, @matchcontinue, @unsafematch, MatchFailure, ModelicaReal, ModelicaInteger 24 | export @Uniontype, @Record, @UniontypeDecl, @ExtendedFunction, @ExtendedAnonFunction 25 | export List, list, Nil, nil, Cons, cons, =>, Option, SOME, NONE, SourceInfo, SOURCEINFO 26 | export @do_threaded_for, <|, @shouldFail, sourceInfo, _cons, @importDBG 27 | export @assign, @Mutable_Uniontype, @closure 28 | 29 | include("exportmetaRuntime.jl") 30 | include("dangerous.jl") 31 | include("array.jl") 32 | 33 | end 34 | -------------------------------------------------------------------------------- /.CI/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | docker { 4 | // Large image with full OpenModelica build dependencies; lacks omc and OMPython 5 | label 'linux' 6 | image 'julia:1.10.0-bookworm' 7 | alwaysPull true 8 | args '--privileged' 9 | } 10 | } 11 | options { 12 | skipDefaultCheckout true 13 | } 14 | stages { 15 | stage('build') { 16 | environment { 17 | USER = 'jenkins' 18 | } 19 | steps { 20 | dir("MetaModelica.jl") { 21 | checkout scm 22 | sh '(pwd)' 23 | sh ''' 24 | export HOME=$PWD 25 | julia -e "using Pkg; 26 | Pkg.add(\\"ExportAll\\"); 27 | Pkg.add(\\"DataStructures\\"); 28 | Pkg.add(\\"MacroTools\\"); 29 | Pkg.add(\\"Test\\"); 30 | Pkg.add(PackageSpec(url=\\"https://github.com/OpenModelica/ImmutableList.jl\\"))" 31 | ''' 32 | sh 'export HOME=$PWD; julia -e "using Pkg; Pkg.REPLMode.pkgstr(\\"add $PWD\\")"' 33 | sh 'export HOME=$PWD; julia -e "using Pkg; Pkg.test(\\"MetaModelica\\", coverage=true)"' 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The source code in this repository is licensed according to the OSMC-PL 2 | version 1.2: 3 | 4 | > This file is part of OpenModelica. 5 | > 6 | > Copyright (c) 1998-CurrentYear, Open Source Modelica Consortium (OSMC), 7 | > c/o Linköpings universitet, Department of Computer and Information Science, 8 | > SE-58183 Linköping, Sweden. 9 | > 10 | > All rights reserved. 11 | > 12 | > THIS PROGRAM IS PROVIDED UNDER THE TERMS OF THE BSD NEW LICENSE OR THE 13 | > GPL VERSION 3 LICENSE OR THE OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2. 14 | > ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES 15 | > RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3, 16 | > ACCORDING TO RECIPIENTS CHOICE. 17 | > 18 | > The OpenModelica software and the OSMC (Open Source Modelica Consortium) 19 | > Public License (OSMC-PL) are obtained from OSMC, either from the above 20 | > address, from the URLs: http://www.openmodelica.org or 21 | > http://www.ida.liu.se/projects/OpenModelica, and in the OpenModelica 22 | > distribution. GNU version 3 is obtained from: 23 | > http://www.gnu.org/copyleft/gpl.html. The New BSD License is obtained from: 24 | > http://www.opensource.org/licenses/BSD-3-Clause. 25 | > 26 | > This program is distributed WITHOUT ANY WARRANTY; without even the implied 27 | > warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, EXCEPT AS 28 | > EXPRESSLY SET FORTH IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE 29 | > CONDITIONS OF OSMC-PL. 30 | 31 | Exceptions to this license: 32 | 33 | MatchContinue.jl is based on Rematch.jl and is licensed under the 34 | Apache License, Version 2.0 35 | -------------------------------------------------------------------------------- /.CI/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM julia:1.2.0 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends \ 5 | apt-transport-https \ 6 | ca-certificates \ 7 | autoconf \ 8 | build-essential \ 9 | git \ 10 | mc \ 11 | nano \ 12 | curl \ 13 | wget \ 14 | gpg \ 15 | && \ 16 | 17 | RUN export HOME=/home/julia && \ 18 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add Compat ;precompile");using Compat' && \ 19 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add DataFrames;precompile");using DataFrames' && \ 20 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add DataStructures ;precompile");using DataStructures' && \ 21 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add LightXML ;precompile");using LightXML' && \ 22 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add Random ;precompile");using Random' && \ 23 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add Test ;precompile");using Test' && \ 24 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add https://github.com/OpenModelica/ImmutableList.jl.git ;precompile"); using ImmutableList' && \ 25 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add ExportAll ;precompile");using ExportAll' && \ 26 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add MacroTools ;precompile");using MacroTools' && \ 27 | julia -O3 -e 'using Pkg;Pkg.REPLMode.pkgstr("add https://github.com/OpenModelica/MetaModelica.jl.git ;precompile"); using MetaModelica' && \ 28 | (cd /home/julia && tar cf /home/julia.tar .julia) && rm -rf /home/julia/.julia && chmod ugo+rwx /home/julia 29 | -------------------------------------------------------------------------------- /test/matchTests.jl: -------------------------------------------------------------------------------- 1 | module MatchTests 2 | 3 | using MetaModelica 4 | using Test 5 | 6 | @test 1 == @match Cons(1, nil) begin 7 | # case x::_ then x 8 | Cons(head=x) => x 9 | # else 2 10 | _ => 2 11 | end 12 | 13 | @test_throws MatchFailure 1 == @match 1 <| nil begin 14 | # case 2::_ then x // unbound variable error 15 | Cons(head=2) => x 16 | end 17 | 18 | @test @match Cons(1, nil) begin 19 | # case _::{} then true, using the Nil type 20 | Cons(tail=Nil) => true 21 | end 22 | 23 | @test @match Cons(1, nil) begin 24 | # case _::{} then true, using the Nil value 25 | Cons(tail=Nil) => true 26 | end 27 | 28 | @test 3 == @match 1 <| 2 <| nil begin 29 | # case x::y::{} then x+y 30 | x <| y <| nil() => x + y 31 | end 32 | 33 | @test 1 == @match 1 <| nil begin 34 | # case x::y::{} then x+y 35 | x <| nil() => x 36 | end 37 | 38 | @test 3 == @match 1 <| 2 <| nil begin 39 | # case x::y::{} then x+y 40 | x <| y <| nil() => x + y 41 | end 42 | 43 | @test 1 == @match list(1, 2) begin 44 | # case x::2::{} then x 45 | Cons(head=x, tail=Cons(head=2, tail=Nil)) => x 46 | end 47 | 48 | #= More advanced matching =# 49 | let 50 | H, T = @match list(1, 2, 3) begin 51 | H <| T => let 52 | H, T 53 | end 54 | end 55 | @test H == 1 56 | @test T == list(2, 3) 57 | end 58 | 59 | let 60 | T = list(1, 2, 3) 61 | H, T = @match T begin 62 | H <| T => let 63 | H, T 64 | end 65 | end 66 | @test H == 1 67 | @test T == list(2, 3) 68 | end 69 | 70 | #= Wildcard with structs =# 71 | struct foo end 72 | struct bar end 73 | struct barBar end 74 | a = barBar() 75 | @test 1 == @match a begin 76 | foo() => 2 77 | bar() => 3 78 | _ => 1 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /test/uniontypeTests.jl: -------------------------------------------------------------------------------- 1 | module UniontypeTests 2 | 3 | using MetaModelica 4 | using Test 5 | 6 | @Uniontype number begin 7 | @Record REAL begin 8 | r 9 | end 10 | @Record IMG begin 11 | r 12 | #We can ofcourse have comments here :D# 13 | i 14 | end 15 | end 16 | 17 | someIMG = IMG(1, 2) 18 | 19 | @test someIMG.r == 1 20 | 21 | @test someIMG.i == 2 22 | 23 | function realOrIMG(a::number) 24 | @match a begin 25 | IMG(x, y) => (x, y) 26 | REAL(x) => x 27 | end 28 | end 29 | 30 | @test realOrIMG(REAL(1)) == 1 31 | 32 | @test realOrIMG(IMG(1, 2)) == (1, 2) 33 | 34 | #Check that uniontypes work with match.. 35 | @Uniontype uK begin 36 | @Record SCOTLAND begin 37 | end 38 | @Record WALES begin 39 | end 40 | @Record ENGLAND begin 41 | end 42 | @Record NorthIreland begin 43 | end 44 | end 45 | 46 | function forgotWales(x::uK) 47 | @match x begin 48 | ENGLAND() => "ok" 49 | SCOTLAND() => "ok" 50 | end 51 | end 52 | 53 | @test_throws MatchFailure forgotWales(WALES()) 54 | #= Tests mutally recursive Uniontypes =# 55 | #= N.B: We have to inform Julia through forward declarations as a workaround for now=, see the following issue, as of 2019 Julia does not support this. MM does however: https://github.com/JuliaLang/julia/issues/269# 56 | =# 57 | 58 | @UniontypeDecl NICE_NUMBER 59 | @UniontypeDecl NUMBER 60 | 61 | @Uniontype NICE_NUMBER begin 62 | @Record CONCRETE_NICE_NUMBER begin 63 | r::NUMBER 64 | end 65 | end 66 | 67 | @Uniontype NUMBER begin 68 | @Record REAL1 begin 69 | r::NICE_NUMBER 70 | end 71 | @Record IMG1 begin 72 | r::NICE_NUMBER 73 | i 74 | end 75 | @Record BASE begin 76 | i 77 | end 78 | end 79 | 80 | @test_nowarn REAL1(CONCRETE_NICE_NUMBER(BASE(4))) 81 | 82 | 83 | end 84 | -------------------------------------------------------------------------------- /src/utilityMacros.jl: -------------------------------------------------------------------------------- 1 | #import Setfield 2 | import FastClosures 3 | import Accessors 4 | """ 5 | Helper function for the assignmacro, see @assign 6 | We have one case where we assign a immutable structure to an immutable structure or something to a primitive variable. 7 | If it is not a primitive we assign to a subcomponent of that structure. We then clone the structure with that particular field changed. 8 | """ 9 | function assignFunc(expr) 10 | res = 11 | if @capture(expr, lhs_.sub_.sub_ = rhs_) 12 | Accessors.setmacro(identity, expr, overwrite=true) 13 | elseif @capture(expr, lhs_.sub_ = rhs_) #= Captures a.b=# 14 | tmp = Accessors.setmacro(identity, expr, overwrite=true) 15 | #= 16 | The second condition is a temporary fix. 17 | It is due to what seems to be a bug 18 | for setfield in which it consumes a lot of memory if used for a linked list 19 | =# 20 | sym = :($sub) 21 | quote 22 | #local tmp1 = $(esc(lhs)) 23 | #local tmp2 = $(esc("$sym")) 24 | #local tmp3 = Symbol(tmp2) 25 | #local tmp4 = getproperty(tmp1, tmp3) 26 | #@assert(!(tmp4 isa List)) 27 | #@assert(!(tmp4 isa Vector)) 28 | $tmp 29 | end 30 | elseif @capture(expr, lhs_.sub__= rhs_) 31 | quote 32 | $tmp 33 | end 34 | else 35 | quote 36 | $(esc(expr)) 37 | end 38 | end 39 | return res 40 | end 41 | 42 | """ 43 | This macro reimplements the MetaModelica assignment semantics using 44 | setfield to assign to variables. 45 | For assignments using primitives, the regular Julia assignment is generated. 46 | For cases where deeply nested immutable structures are manipulated we use setfield 47 | E.g.: 48 | a.b.c = 5 49 | Where a is a nested immutable struct 50 | """ 51 | macro assign(expr) 52 | res = assignFunc(expr) 53 | replaceLineNum(res, @__FILE__, __source__) 54 | res 55 | end 56 | 57 | """ 58 | Wraps the @closure macro of FastClosures. 59 | See the FastClosure package for more information. 60 | """ 61 | macro closure(expr) 62 | esc(FastClosures.wrap_closure(__module__, expr)) 63 | end 64 | -------------------------------------------------------------------------------- /test/functionExtensionTest.jl: -------------------------------------------------------------------------------- 1 | module FunctionExtensionTest 2 | 3 | using MetaModelica 4 | using Test 5 | 6 | function callOneArgNoDefault1(a) 7 | a 8 | end 9 | 10 | @ExtendedFunction callA1 callOneArgNoDefault1(a=1) 11 | 12 | @test callA1() == 1 13 | 14 | function callOneArgNoDefault2(a, b) 15 | a + b 16 | end 17 | 18 | @ExtendedFunction callA1 callOneArgNoDefault2(a=1, b=1) 19 | 20 | @test callA1() == 2 21 | 22 | function pathString(usefq=true) 23 | usefq 24 | end 25 | 26 | @ExtendedFunction pathStringNoQual pathString(usefq=false) 27 | 28 | @test pathStringNoQual() != pathString() 29 | 30 | @ExtendedFunction pathStringNoQualInv pathString(usefq=true) 31 | 32 | @test pathStringNoQualInv() == pathString() 33 | 34 | function foo(a, b=1, c=2, d=3, e=4) 35 | (a, b, c, d, e) 36 | end 37 | 38 | @ExtendedFunction foo3 foo(d=2000) 39 | 40 | @test sum(foo3(1)) > 2000 41 | 42 | @ExtendedFunction foo2 foo(a=1, c=4, d=6) 43 | 44 | @test sum(foo2()) == sum((1, 1, 4, 6, 4)) 45 | 46 | # = Testing inheritance in several steps #= 47 | 48 | function fooBar(a=1) 49 | a 50 | end 51 | 52 | @ExtendedFunction fooBar1 fooBar(a=4) 53 | 54 | @ExtendedFunction fooBar2 fooBar1(a=100) 55 | 56 | @test fooBar1() == 4 57 | 58 | @test fooBar2() == 100 59 | 60 | #= Testing anon functions =# 61 | 62 | f = @ExtendedAnonFunction fooBar() 63 | 64 | @test 1 == f() 65 | 66 | f = @ExtendedAnonFunction fooBar(a=500) 67 | 68 | @test f() == 500 69 | 70 | function zeroArgFoo() 71 | 5 72 | end 73 | 74 | function takeFoo(a) 75 | a() 76 | end 77 | 78 | 79 | function takeFoo2(a) 80 | a(1) 81 | end 82 | 83 | @test takeFoo(@ExtendedAnonFunction zeroArgFoo()) == 5 84 | 85 | @test takeFoo(@ExtendedAnonFunction fooBar1()) == 4 86 | 87 | function pathString(path::String, delimiter::String=".", usefq::Bool=true, 88 | reverse::Bool=false)::String 89 | usefq 90 | end 91 | 92 | @test 5 == begin 93 | function zeroArgFoo() 94 | 5 95 | end 96 | function fooFoo() 97 | f = takeFoo(@ExtendedAnonFunction zeroArgFoo()) 98 | f 99 | end 100 | fooFoo() 101 | end 102 | 103 | function twoArgFoo(a, b=4) 104 | a + b 105 | end 106 | 107 | 108 | function fooFoo() 109 | @ExtendedFunction X twoArgFoo(a=4, b=1) 110 | X(1) 111 | end 112 | 113 | @test fooFoo() == 2 114 | 115 | end 116 | -------------------------------------------------------------------------------- /test/matchcontinueTests.jl: -------------------------------------------------------------------------------- 1 | module MatchContinueTests 2 | 3 | using MetaModelica 4 | using Test 5 | 6 | @test 2 == @matchcontinue Cons(1, nil) begin 7 | # MM: case x::_ then fail() 8 | x <| _ => throw(MatchFailure("Some custom failure here", 0)) 9 | # MM: case (x as 1)::_ then 2*x 10 | (x && 1) <| _ => 2 * x 11 | _ => 3 12 | end 13 | 14 | #= Try nested matchcontinue =# 15 | @test 1 == @matchcontinue 2 begin 16 | 2 => @match 3 begin 17 | 3 => @match 3 begin 18 | 2 => 1 19 | end 20 | end 21 | _ => 1 22 | end 23 | 24 | #= Test support for all wildcard matching =# 25 | @testset "Wildcard test" begin 26 | begin 27 | struct foo end 28 | struct bar end 29 | a = bar() 30 | #=Empty fields. Wildcard match=# 31 | @test 2 == @match a begin 32 | foo(__) => 1 33 | bar(__) => 2 34 | end 35 | end 36 | 37 | #= Test the new all wild syntax. Needed since I cannot figure out how to get that info from Susan =# 38 | let 39 | struct foo1 40 | a::Any 41 | end 42 | 43 | struct bar2 44 | a::Any 45 | b::Any 46 | end 47 | 48 | struct baz3 49 | a::Any 50 | b::Any 51 | c::Any 52 | end 53 | 54 | a = baz3(1, 2, 3) 55 | @test 4 == begin 56 | @match a begin 57 | bar2(__) => 2 58 | foo1(a=1) => 3 59 | foo1(__) => 3 60 | _ => 4 61 | end 62 | end 63 | 64 | a = foo1(8) 65 | @test 1 == @match a begin 66 | bar2(__) => 2 67 | foo1(a=7) => 3 68 | bar2(__) => 5 69 | bar2(a=1, b=2) => 6 70 | foo1(__) => 1 71 | end 72 | end 73 | 74 | @test begin 75 | function testSideEffects(a) 76 | local someVariableWeWantToMutate1 = false 77 | local someVariableWeWantToMutate2 = false 78 | begin 79 | @match a begin 80 | 1 => begin 81 | someVariableWeWantToMutate1 = true 82 | someVariableWeWantToMutate2 = true 83 | () 84 | end 85 | end 86 | someVariableWeWantToMutate1 && someVariableWeWantToMutate2 87 | end 88 | end 89 | testSideEffects(1) 90 | end 91 | end 92 | 93 | @testset "Matching on member variables" begin 94 | struct FOO 95 | a::Any 96 | end 97 | foo = FOO(1) 98 | @test 1 == @match (@match foo.a = 1) begin 99 | 1 => true 100 | _ => false 101 | end 102 | end 103 | 104 | 105 | end #= End module =# 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetaModelica.jl [![License: OSMC-PL](https://img.shields.io/badge/license-OSMC--PL-lightgrey.svg)](OSMC-License.txt) 2 | 3 | This package replicates the runtime of the programming language MetaModelica. It also exposes 4 | other packages that are a part of this runtime such as ImmutableList.jl 5 | 6 | MetaModelica supports a powerfull but expensive mechanism for pattern matching called matchcontinue 7 | This is provided by an extension of Rematch.jl 8 | 9 | # Style 10 | This package follows [YASGuide](https://github.com/jrevels/YASGuide). 11 | Adherence to the standard will be checked during CI. 12 | 13 | # Pattern Matching and Patterns: 14 | 15 | * `_` matches anything 16 | * `foo` matches anything, binds value to `foo` 17 | * `foo(__)` wildcard match on all subfields of foo, binds value to `foo` 18 | * `Foo(x,y,z)` matches structs of type `Foo` with fields matching `x,y,z` 19 | * `Foo(x=y)` matches structs of type `Foo` with a field named `x` matching `y` 20 | * `[x,y,z]` matches `AbstractArray`s with 3 entries matching `x,y,z` 21 | * `(x,y,z)` matches `Tuple`s with 3 entries matching `x,y,z` 22 | * `[x,y...,z]` matches `AbstractArray`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries. 23 | * `(x,y...,z)` matches `Tuple`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries. 24 | * `_::T` matches any subtype (`isa`) of T 25 | * `x || y` matches values which match either `x` or `y` (only variables which exist in both branches will be bound) 26 | * `x && y` matches values which match both `x` and `y` 27 | * `x where condition` matches only if `condition` is true (`condition` may use any variables that occur earlier in the pattern eg `(x, y, z where x + y > z)`) 28 | * Anything else is treated as a constant and tested for equality 29 | 30 | * Patterns can be nested arbitrarily. 31 | 32 | * Pattern matching is also possible on the list implementation provided by ImmutableList.jl: 33 | 34 | `H <| T matches the head to H and the tail to T for a given input list ` 35 | 36 | `_ <| T matches a wildcard head and the tail to T for a given input list ` 37 | 38 | `nil() or list() matches the empty list` 39 | 40 | 41 | * Repeated variables only match if they are equal: 42 | eg `(x,x)` matches `(1,1)` but not `(1,2)`. 43 | 44 | # @shouldFail 45 | 46 | `@shouldFail(arg)` Is a special construct of MetaModelica. 47 | It succeeds if arg fails, where arg is a local match-equation/statement. 48 | 49 | # Optional 50 | An optional datatype 51 | SOME(X), NONE(). Works in match statements 52 | -------------------------------------------------------------------------------- /src/exportmetaRuntime.jl: -------------------------------------------------------------------------------- 1 | #= 2 | This file contains all functions that should be exported for the MetaModelica runtime. 3 | Add new exports here :) 4 | =# 5 | export anyString 6 | export arrayAppend 7 | export arrayCopy 8 | export arrayCreate 9 | export arrayEmpty 10 | export arrayGet 11 | export arrayLength 12 | export arrayList 13 | export arrayUpdate 14 | export boolNot 15 | export boolAnd 16 | export boolEq 17 | export boolOr 18 | export boolString 19 | export clock 20 | export cons 21 | export debug_print 22 | export equality 23 | export fail 24 | export getGlobalRoot 25 | export intAbs 26 | export intAdd 27 | export intBitLShift 28 | export intBitNot 29 | export intBitAnd 30 | export intBitOr 31 | export intBitRShift 32 | export intBitXor 33 | export intDiv 34 | export intEq 35 | export intGe 36 | export intGt 37 | export intLe 38 | export intLt 39 | export intMax 40 | export intMin 41 | export intMod 42 | export intMul 43 | export intMul 44 | export intNe 45 | export intNeg 46 | export intReal 47 | export intString 48 | export intStringChar 49 | export intSub 50 | export isNone 51 | export isPresent 52 | export isSome 53 | export listAppend 54 | export listArray 55 | export listDelete 56 | export listEmpty 57 | export listGet 58 | export listHead 59 | export listLength 60 | export listMember 61 | export listRest 62 | export listReverse 63 | export listReverseInPlace 64 | export listStringCharString 65 | export printAny 66 | export realAbs 67 | export realAdd 68 | export realDiv 69 | export realEq 70 | export realGe 71 | export realGt 72 | export realInt 73 | export realLe 74 | export realLt 75 | export realMax 76 | export realMin 77 | export realMod 78 | export realMul 79 | export realNe 80 | export realNeg 81 | export realNeg 82 | export realPow 83 | export realString 84 | export realSub 85 | export referenceDebugString 86 | export referenceEq 87 | export referencePointerString 88 | export setGlobalRoot 89 | export setStackOverflowSignal 90 | export stringAppend 91 | export stringAppendList 92 | export stringCharInt 93 | export stringCharListString 94 | export stringCompare 95 | export stringDelimitList 96 | export stringEmpty 97 | export stringEqual 98 | export stringEq 99 | export stringGet 100 | export stringGetStringChar 101 | export stringHash 102 | export stringHashDjb2 103 | export stringHashDjb2Mod 104 | export stringHashSdbm 105 | export stringInt 106 | export stringLength 107 | export stringListStringChar 108 | export stringReal 109 | export stringUpdateStringChar 110 | export String 111 | export substring 112 | export tick 113 | export resetTick 114 | export valueCompare 115 | export valueConstructor 116 | export valueEq 117 | export valueHashMod 118 | export valueSlots 119 | export _listAppend 120 | export getInstanceName 121 | export StringFunction 122 | -------------------------------------------------------------------------------- /src/union.jl: -------------------------------------------------------------------------------- 1 | #= 2 | Implementation of tagged unions for MetaModelica.jl 3 | They can be used in that way with match.jl 4 | =# 5 | 6 | #= Example: 7 | #@Uniontype number begin 8 | # @Record real begin 9 | # r::Real 10 | # end 11 | # @Record img begin 12 | # r::Real 13 | # i::Real 14 | # end 15 | #end 16 | # Or for short: 17 | #@Uniontype number begin 18 | # @Record real r::Real 19 | # @Record img r::Real i::Real 20 | #end 21 | # This is syntactic sugar for: 22 | # abstract type number end 23 | # struct real <: number 24 | # r 25 | # end 26 | # struct img <: number 27 | # r 28 | # i 29 | # end 30 | =# 31 | 32 | #= 33 | TODO: Sometimes when people use type aliasing @Uniontype will not know about the specific type during compilation time 34 | =# 35 | module UniontypeDef 36 | 37 | include("metaModelicaTypes.jl") 38 | import .MetaModelicaTypes 39 | using MacroTools 40 | 41 | function makeRecord(recordExpr::Expr) 42 | local arr = [] 43 | local sourceInfo = nothing 44 | for i in recordExpr.args 45 | if typeof(i) == Expr 46 | push!(arr, (i, sourceInfo)) 47 | elseif typeof(i) <: LineNumberNode 48 | sourceInfo = i 49 | end 50 | end 51 | return [(el[1].args[3], el[1].args[4], el[2]) for el in arr] 52 | end 53 | 54 | function makeTuple(name, fields) 55 | return quote 56 | ($name, $fields) 57 | end 58 | end 59 | 60 | function isLineNumberNode(a, lines::LineNumberNode) 61 | if typeof(a) <: LineNumberNode 62 | a.file == Symbol(@__FILE__) 63 | else 64 | false 65 | end 66 | end 67 | 68 | function replaceLineNum(a::Expr, lines::LineNumberNode) 69 | replace!(arg -> isLineNumberNode(arg, lines) ? lines : arg, a.args) 70 | for n in a.args 71 | replaceLineNum(n, lines) 72 | end 73 | end 74 | 75 | function replaceLineNum(a::Any, lines::LineNumberNode) end 76 | 77 | function makeUniontypes(name, records, lineNode::LineNumberNode; mutable = false) 78 | recordsArray1 = Array.(records) 79 | recordsArray2 = recordsArray1[1] 80 | constructedRecords = [] 81 | for r in recordsArray2 82 | structName = r[1] 83 | recordNode = if ! mutable 84 | quote 85 | struct $(structName) <: $name 86 | $(r[2]) 87 | end 88 | end 89 | else 90 | quote 91 | mutable struct $(structName) <: $name 92 | $(r[2]) 93 | end 94 | end 95 | end 96 | replaceLineNum(recordNode, isa(r[3], Nothing) ? lineNode : r[3]) 97 | push!(constructedRecords, recordNode) 98 | end 99 | #= Construct the Union =# 100 | res = quote 101 | abstract type $name end 102 | $(constructedRecords...) 103 | end 104 | # Make debugging and profiling easier by pretending the record was 105 | # allocated in the source file where the macro was invoked 106 | replaceLineNum(res, lineNode) 107 | return res 108 | end 109 | 110 | """ Creates a uniontype consisting of 0...N records """ 111 | macro Uniontype(name, records...) 112 | recordCollection = [makeRecord(r) for r in records] 113 | esc(makeUniontypes(name, recordCollection, __source__)) 114 | end 115 | 116 | """ 117 | Creates a mutable uniontype constisting of 0...N records 118 | """ 119 | macro Mutable_Uniontype(name, records...) 120 | recordCollection = [makeRecord(r) for r in records] 121 | esc(makeUniontypes(name, recordCollection, __source__; mutable = true)) 122 | end 123 | 124 | """ Creates a record belonging to a Uniontype """ 125 | macro Record(name, fields...) 126 | makeTuple(name, fields) 127 | end 128 | 129 | #= It is "possible" to manipulate the Julia ast during compilation time so that all declaration of a uniontype creates a module-top-level abstract type definition. I leave that as a exercise to the reader of this comment :D =# 130 | macro UniontypeDecl(uDecl) 131 | esc(quote 132 | abstract type $uDecl end 133 | end) 134 | end 135 | 136 | export @Uniontype, @Record, @UniontypeDecl, @Mutable_Uniontype 137 | 138 | end 139 | -------------------------------------------------------------------------------- /test/listTests.jl: -------------------------------------------------------------------------------- 1 | module ListTests 2 | 3 | using MetaModelica 4 | using Test 5 | @testset "Baseline tests" begin 6 | 7 | @test 0 == begin 8 | Ints = List{Int} 9 | a::Ints = nil 10 | length(a) 11 | end 12 | 13 | @test 3 == begin 14 | ints::List{Int} = list(1, 2, 3) 15 | length(ints) 16 | end 17 | 18 | @test 3 == begin 19 | length(Cons(1, Cons(2, Cons(3, nil)))) 20 | end 21 | 22 | #= Test cons operator =# 23 | @test 3 == begin 24 | length(1 <| 2 <| 3 <| nil) 25 | end 26 | 27 | #= The empty list is a List =# 28 | @test nil == list() 29 | 30 | @test 3 == begin 31 | local lst = nil 32 | for i in [1, 2, 3] 33 | lst = i <| lst 34 | end 35 | length(lst) 36 | end 37 | @test 3 == begin 38 | local lst = nil 39 | for i in [1, 2, 3] 40 | lst = i <| lst 41 | end 42 | length(lst) 43 | end 44 | #Test Concrete type 45 | @test let 46 | try 47 | lst1::List{Int64} = 1 <| nil 48 | true 49 | catch E 50 | println(E) 51 | false 52 | end 53 | end 54 | #Test generic type 1 55 | @test let 56 | try 57 | lst1::List{Any} = 1 <| nil 58 | true 59 | catch E 60 | println(E) 61 | false 62 | end 63 | end 64 | #Test generic type 2 65 | @test let 66 | try 67 | lst1::List{Any} = 1 <| nil 68 | true 69 | catch E 70 | println(E) 71 | false 72 | end 73 | end 74 | #Test generic type 3 75 | @test let 76 | try 77 | lst1::List{Integer} = list(1, 2, 3) 78 | true 79 | catch E 80 | println(E) 81 | false 82 | end 83 | end 84 | end 85 | 86 | @testset "List assignment tests for complex types" begin 87 | abstract type SUPER end 88 | struct SUB <: SUPER 89 | A::Any 90 | B::Any 91 | end 92 | struct SUB2 <: SUPER end 93 | 94 | @test let 95 | try 96 | lst1::List{Any} = list(SUB(1, 2), SUB(1, 2), SUB(1, 2)) 97 | true 98 | catch E 99 | println(E) 100 | false 101 | end 102 | end 103 | 104 | #Test generic assignment for a complex type when returning from a function 105 | @test let 106 | foo()::List{Any} = list(SUB(1, 2), SUB(1, 2), SUB(1, 2)) 107 | try 108 | lst1::List{Any} = foo() 109 | true 110 | catch E 111 | prinln(E) 112 | false 113 | end 114 | end 115 | 116 | #Test supertype assignment for a complex type when returning from a function 117 | @test let 118 | function bar(lst::List{T})::List{T} where {T<:Any} 119 | lst 120 | end 121 | try 122 | lst2::List{SUPER} = bar(list(SUB(1, 2), SUB(1, 2), SUB(1, 2))) 123 | true 124 | catch E 125 | println(E) 126 | false 127 | end 128 | end 129 | 130 | @test let 131 | try 132 | x = cons(SUB(1, 2), cons(SUB2(), Cons{SUB2}(SUB2(), nil))) 133 | t = convert(List{SUPER}, x) 134 | Cons{SUPER} == typeof(x) 135 | catch E 136 | println(E) 137 | false 138 | end 139 | end 140 | 141 | end 142 | 143 | #Test list comprehension 144 | @testset "List comprehension test" begin 145 | @test length(list(i for i = 1:3)) == 3 146 | @test length(list(i for i in list())) == 0 147 | lst = list(i * 2 for i in list(1, 2, 3)) 148 | @test sum(lst) == 12 149 | #= Test guard statement =# 150 | lst2 = list(i for i in list(1, 2, 3) if i == 3) 151 | @test sum(lst2) == 3 152 | @test length(lst2) == 1 153 | end 154 | 155 | #=We also need to support https://trac.openmodelica.org/OpenModelica/ticket/2816 =# 156 | @testset "Reduction and list flatten test" begin 157 | 158 | @testset "Flatten test" begin 159 | lst = list(i for i = 1:10 for j = 1:10) 160 | @test sum(lst) == 550 161 | lst = list(i for i = 1:10 for j = 1:10 for k = 1:10) 162 | @test sum(lst) == 5500 163 | end 164 | @testset "Reduction test" begin 165 | #= 166 | MM:list(a+b for a in 1:2, b in 3:4); // {{4,5}, {5,6}} 167 | JL:list(a+b for a in 1:2, b in 3:4); // {{4,5}, {5,6}} 168 | =# 169 | lst1 = list(a + b for a = 1:2, b = 3:4) 170 | @test length(lst1) == 2 171 | @test listHead(listHead(lst1)) == 4 172 | #= 173 | MM:list(a+b threaded for a in 1:2, b in 3:4) = {4, 6} 174 | JL:list(a+b @threaded for a in 1:2,b in 3:4) = {4, 6} 175 | =# 176 | @testset "Threaded Reduction test" begin 177 | @test list(@do_threaded_for a + b (a, b) (1:2, 3:4)) == list(4, 6) 178 | @test sum(list(@do_threaded_for a + b (a, b) (1:10, 1:10))) == 110 179 | lst = 1 <| list(@do_threaded_for a + b (a, b) (1:2, 3:4)) 180 | @test lst == list(1, 4, 6) 181 | end 182 | end 183 | end 184 | 185 | @testset "Eltype and instantiation of composite with subtype tests" begin 186 | @test Int64 == eltype(list(1, 2, 3)) 187 | #= Our list is now a union =# 188 | @test Cons{Int64} == eltype(list(list(1))) 189 | 190 | abstract type AS end 191 | struct SUBTYPE <: AS 192 | a::Any 193 | end 194 | struct SS 195 | a::List{AS} 196 | end 197 | @test begin 198 | try 199 | SS(list(SUBTYPE(1), SUBTYPE(2), SUBTYPE(3))) 200 | true 201 | catch 202 | false 203 | end 204 | end 205 | end 206 | 207 | @testset "Testing type conversion for lists of lists" begin 208 | @test true == begin 209 | try 210 | let 211 | a::List{List{Integer}} = list(list()) 212 | true 213 | end 214 | catch 215 | println("Conversion failure") 216 | false 217 | end 218 | end 219 | @test true == let 220 | try 221 | a::List{List{Integer}} = list(list(1)) 222 | b::List{List{Integer}} = list(list(1, 2, 3)) 223 | length(a) == length(b) 224 | catch 225 | println("Conversion failure") 226 | false 227 | end 228 | end 229 | end 230 | end #=End module=# 231 | -------------------------------------------------------------------------------- /src/functionInheritance.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of function inheritance. E.g enabling functions to extend other functions 3 | @ExtendedFunction pathStringNoQual pathString(usefq=false) 4 | 5 | This means defining a new function pathStringNoQual equal to pathString. However, 6 | the usefq argument now has a different default value. 7 | 8 | We do so by creating a new function but passing the redefined arguments to it. 9 | 10 | It is also possible to define an anonymous function using the following syntax: 11 | 12 | @ExtendedAnonFunction pathString(usefq=false) 13 | 14 | The later defines a lambda function that extends pathString. However, the default values of the 15 | parameters can be changed MetaModelica style. 16 | 17 | @Author John Tinnerholm, same license as the rest of this package 18 | 19 | TODO: Note that ExtendedAnonFunction does not work for inline expressions 20 | Same with @ExtendedFunction. The reason being that certain variables will not be in use by 21 | then. 22 | Implement @ExtendedAnonInlineFunction, which does the same but requires less available compile time 23 | information. 24 | """ 25 | 26 | #= Used to indicate that a specific symbolic parameter does not have a default argument =# 27 | struct NoDefaultArg end 28 | 29 | function getSignatureAsArrayFor(func::Function)::Array 30 | methodLst = methods(func) 31 | sizeOfLeastGenericSignature = size(methodLst.ms, 1) 32 | @assert(sizeOfLeastGenericSignature >= 1, 33 | "Invalid function passed to getSignatureAsArrayFor(). Size was: $(sizeOfLeastGenericSignature)") 34 | argumentSymbols::Array = Base.method_argnames(methodLst.ms[sizeOfLeastGenericSignature])[2:end] 35 | #= Creates an array with the max(positional arguments) =# 36 | defaultValues = code_lowered(func)[1].code[1].args[2:end] 37 | #= Construct an array consisting of (Symbol, defaultValue if it exists otherwise nothing, the type for the argument) =# 38 | local signatureArray::Array = [] 39 | local nDefaultValues::Integer = size(defaultValues, 1) 40 | local nArgumentSymbols::Integer = size(argumentSymbols, 1) 41 | for i = 1:nArgumentSymbols 42 | #= 43 | If no default value is present. We add nothing instead place. 44 | This case occurs if i is larger then the ammount of default values. 45 | However, sometimes Core.SlotNumber occurs other times it does not. 46 | =# 47 | if i > nDefaultValues 48 | push!(signatureArray, (argumentSymbols[i], NoDefaultArg())) 49 | elseif typeof(defaultValues[i]) == Core.SlotNumber 50 | push!(signatureArray, (argumentSymbols[i], NoDefaultArg())) 51 | else 52 | push!(signatureArray, (argumentSymbols[i], defaultValues[i])) 53 | end 54 | end 55 | signatureArray 56 | end 57 | 58 | function getNewFunctionArgs(functionToExtend, func::Function)::Array{Tuple} 59 | #= These are the arguments that we would like to change =# 60 | functionToExtendArgs::Array = functionToExtend.args[2:end] #Skipping function symbol 61 | oldFunctionSig::Array = getSignatureAsArrayFor(func) 62 | local functionToExtendArgsAsTuples::Array{Tuple} = [] 63 | for t in functionToExtendArgs 64 | #Convert expr to tuple 65 | pair = Tuple(t.args) 66 | @assert(size(pair, 1) >= 2, "Incorrect parameter passed to @ExtendFunction") 67 | push!(functionToExtendArgsAsTuples, (first(pair), last(pair))) 68 | end 69 | local numberOfArguments::Integer = size(functionToExtendArgsAsTuples, 1) 70 | #= Create arguments for the new function. 71 | Generally the new functions have more symbols then the old =# 72 | newArgsAsString::Array = [] 73 | for i = 1:numberOfArguments 74 | symToFind = first(functionToExtendArgsAsTuples[i]) 75 | findFunc(x) = first(x) == symToFind 76 | #= We will get an array with one index! Functions must have unique symbols as arguments=# 77 | indexOfSym = first(findall(findFunc, oldFunctionSig)) 78 | oldFunctionSig[indexOfSym] = functionToExtendArgsAsTuples[i] 79 | end 80 | newFuncSig = oldFunctionSig 81 | end 82 | 83 | function tupleToKwOrSymbol(tuple::Tuple) 84 | sym = first(tuple) 85 | val = last(tuple) 86 | if isa(:($val), NoDefaultArg) 87 | sym 88 | else 89 | Expr(:kw, :($sym), :($val)) 90 | end 91 | end 92 | 93 | function tupleToArgSym(tuple::Tuple)::Symbol 94 | sym = first(tuple) 95 | :($sym) 96 | end 97 | 98 | function getFuncFromSym(func::Symbol, __module__::Module)::Function 99 | getfield(__module__, func) 100 | end 101 | 102 | function makeFunctionHelper(functionToExtend::Expr, __module__::Module)::Tuple 103 | funcSym::Symbol = functionToExtend.args[1] 104 | func::Function = getFuncFromSym(funcSym, __module__) 105 | args::Array{Tuple} = getNewFunctionArgs(functionToExtend, func) 106 | newFuncArgs::Tuple = Tuple(map(tupleToKwOrSymbol, args)) 107 | argSymArr::Tuple = Tuple(map(tupleToArgSym, args)) 108 | (func, newFuncArgs, argSymArr) 109 | end 110 | 111 | function makeExtendedFunction(nameOfNewFunc::Symbol, functionToExtend::Expr, 112 | __module__::Module) 113 | (func, newFuncArgs, argSymArr) = makeFunctionHelper(functionToExtend, __module__) 114 | quote 115 | function $nameOfNewFunc($(newFuncArgs...)) 116 | $func($(argSymArr...)) 117 | end 118 | end |> esc 119 | end 120 | 121 | #= Creates a lambda that extends an existing function =# 122 | function makeExtendedLambdaFunction(functionToExtend::Expr, __module__::Module) 123 | (func, newFuncArgs, argSymArr) = makeFunctionHelper(functionToExtend, __module__) 124 | if size(newFuncArgs, 1) == 0 125 | :(() -> $func($(argSymArr...))) |> esc 126 | else 127 | quote 128 | $((newFuncArgs...)) -> $func($(argSymArr...)) 129 | end |> esc 130 | end 131 | end 132 | 133 | macro ExtendedFunction(newFunction, functionToExtend) 134 | local nf::Symbol = newFunction 135 | local fte = functionToExtend 136 | makeExtendedFunction(nf, fte, __module__) 137 | end |> esc 138 | 139 | macro ExtendedAnonFunction(functionToExtend) 140 | local fte = functionToExtend 141 | makeExtendedLambdaFunction(fte, __module__) 142 | end |> esc 143 | -------------------------------------------------------------------------------- /src/dangerous.jl: -------------------------------------------------------------------------------- 1 | #= 2 | The MetaModelica.Dangerous module. 3 | Most things here are stubs 4 | =# 5 | 6 | module Dangerous 7 | 8 | import ExportAll 9 | using ..MetaModelica 10 | 11 | """ O(1) """ 12 | function arrayGetNoBoundsChecking(arr::Vector{A}, index::ModelicaInteger) where {A} 13 | @inbounds arr[index] 14 | end 15 | 16 | """ O(1) """ 17 | function arrayUpdateNoBoundsChecking(arr::Vector{A}, 18 | index::ModelicaInteger, 19 | newValue::A) where {A} 20 | local newArray = arr 21 | @inbounds newArray[index] = newValue 22 | return newArray 23 | end 24 | 25 | """ Creates a new array where the elements are *not* initialized!. Any attempt to 26 | access an uninitialized elements may cause segmentation faults if you're 27 | lucky, and pretty much anything else if you're not. Do not use unless you will 28 | immediately fill the whole array with data. The dummy variable is used to fix 29 | the type of the array. 30 | """ 31 | function arrayCreateNoInit(size::ModelicaInteger, dummy::A)::Array{A} where {A} 32 | local arr::Array{A} = fill(dummy, size) 33 | arr 34 | end 35 | 36 | """ O(1) """ 37 | function stringGetNoBoundsChecking(str::String, index::ModelicaInteger) 38 | local ch::ModelicaInteger 39 | ch = @inbounds str[index] 40 | end 41 | 42 | """ Not possible unless we write a C list impl for Julia """ 43 | function listReverseInPlace(inList::List{T})::List{T} where {T} 44 | MetaModelica.listReverse(inList) 45 | end 46 | 47 | function listReverseInPlace2(inList::Nil) 48 | return inList#MetaModelica.listReverse(inList) 49 | end 50 | 51 | """ 52 | Unsafe implementation of list reverse in place. 53 | Instead of creating new cons cells we swap pointers... 54 | """ 55 | function listReverseInPlace2(lst::Cons{T}) where {T} 56 | local prev = nil 57 | #= Declare an unsafe pointer to the list =# 58 | local oldCdrPtr::Ptr{List{T}} 59 | GC.@preserve while (!(lst isa Nil)) 60 | println("prev at the iteration:") 61 | println(prev) 62 | println("lst at the iteration:") 63 | println(lst) 64 | println("before oldCdr = $(lst.tail)") 65 | oldCdr = deepcopy(lst.tail) 66 | println("Before listSetRest($lst, $prev)") 67 | listSetRest(lst, prev) 68 | println("Before prev = $lst") 69 | prev = lst 70 | println("Before lst = $(oldCdr) //oldCdr") 71 | lst = oldCdr 72 | end 73 | println("After loop") 74 | return prev 75 | end 76 | 77 | 78 | # """ 79 | # O(1). A destructive operation changing the \"first\" part of a cons-cell. 80 | # TODO: Not implemented 81 | # """ 82 | # function listSetFirst(inConsCell::Cons{A}, inNewContent::A) where {A} #= A non-empty list =# 83 | # firstPtr::Ptr{A} = unsafe_getListAsPtr(inConsCell) 84 | # #local newHead = Cons{T}(inNewContent, inConsCell.tail) 85 | # # unsafe_store!(firstPtr, inNewContent) 86 | # end 87 | 88 | """ O(1). A destructive operation changing the rest part of a cons-cell """ 89 | #= NOTE: Make sure you do NOT create cycles as infinite lists are not handled well in the compiler. =# 90 | function listSetRest(inConsCell::Cons{A}, inNewRest::Cons{A}) where {A} #= A non-empty list =# 91 | newTailPtr::Ptr{Cons{A}} = unsafe_getListAsPtr(inNewRest) 92 | inConsCellTailPtr::Ptr{Cons{A}} = unsafe_getListTailAsPtr(inConsCell) 93 | inConsCellTailPtr2::Ptr{Cons{A}} = unsafe_getListAsPtr(inConsCell) 94 | GC.@preserve(unsafe_store!(inConsCellTailPtr, unsafe_load(newTailPtr))) 95 | return inConsCell 96 | end 97 | 98 | """ 99 | We create one cons cell when the tail we are setting is a nil... 100 | """ 101 | function listSetRest(inConsCell::Cons{A}, inNewRest::Nil) where {A} #= A non-empty list =# 102 | local lstPtr::Ptr{Cons{A}} = unsafe_getListAsPtr(inConsCell) 103 | local val = inConsCell.head 104 | GC.@preserve unsafe_store!(lstPtr, Cons{A}(inConsCell.head, inNewRest)) 105 | return inConsCell 106 | end 107 | 108 | """ O(1). A destructive operation changing the \"first\" part of a cons-cell. """ 109 | function listSetFirst(inConsCell::Cons{A}, inNewContent::A) where {A} #= A non-empty list =# 110 | @assign inConsCell.head = inNewConent 111 | end 112 | 113 | """ 114 | O(1). A destructive operation changing the rest part of a cons-cell 115 | NOTE: Make sure you do NOT create cycles as infinite lists are not handled well in the compiler. 116 | """ 117 | function listSetRest(inConsCell::Cons{T}, inNewRest::List{T}) where {T} #= A non-empty list =# 118 | @assign inConsCell.tail = inNewRest 119 | end 120 | 121 | 122 | """ O(n) """ 123 | function listArrayLiteral(lst::List{A})::Array{A} where {A} 124 | local arr::Array{A} = listArray(lst) 125 | arr 126 | end 127 | 128 | """ 129 | ``` 130 | listGetFirstAsPtr(lst::Cons{T})::Ptr{T} 131 | ``` 132 | 133 | Dangerous function. 134 | Gets the first element of the list as a pointer of type T. 135 | Unless it is nil then we get a NULL pointer 136 | """ 137 | function unsafe_getListHeadAsPtr(lst::Cons{T}) where{T} 138 | convert(Ptr{T}, unsafe_pointer_from_objref(lst.head)) 139 | end 140 | 141 | """ 142 | ``` listGetFirstAsPtr(nil)::Ptr{Nothing}``` 143 | Returns a null pointer 144 | """ 145 | function unsafe_getListHeadAsPtr(lst::Nil) 146 | unsafe_pointer_from_objref(nil) 147 | end 148 | 149 | """ 150 | Fetches the pointer to the tail of the list 151 | ``` 152 | unsafe_listGetTailAsPtr{lst::List{T}}::Ptr{Cons{T}} 153 | ``` 154 | """ 155 | function unsafe_getListTailAsPtr(lst::List{T}) where {T} 156 | if lst.tail === nil 157 | return unsafe_pointer_from_objref(nil) 158 | else 159 | convert(Ptr{Cons{T}}, unsafe_pointer_from_objref(lst.tail)) 160 | end 161 | end 162 | 163 | """ 164 | Unsafley get a pointer to a list. 165 | """ 166 | function unsafe_getListAsPtr(lst::List{T}) where {T} 167 | if lst === nil 168 | ptrToNil::Ptr{Nil{Any}} = unsafe_pointer_from_objref(nil) 169 | return ptrToNil 170 | else 171 | convert(Ptr{Cons{T}}, unsafe_pointer_from_objref(lst)) 172 | end 173 | end 174 | 175 | """ 176 | Unsafe function to get pointers from immutable struct. 177 | Use with !care! 178 | """ 179 | function unsafe_pointer_from_objref(@nospecialize(x)) 180 | ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x) 181 | end 182 | 183 | 184 | ExportAll.@exportAll() 185 | 186 | end #=End dangerous =# 187 | -------------------------------------------------------------------------------- /test/runtimeTest.jl: -------------------------------------------------------------------------------- 1 | #= Various tests for the MetaModelica runtime =# 2 | module RuntimeTests 3 | 4 | using MetaModelica 5 | using Test 6 | #= Creates arr = {3, 3, 3} =# 7 | 8 | @testset "Testing Array creation" begin 9 | global arr = arrayCreate(3, 3) 10 | for i in arr 11 | @test i == 3 12 | end 13 | @test arrayEmpty(arr) == false 14 | @test arrayLength(arr) == 3 15 | @test arrayGet(arr, 3) == 3 16 | end 17 | 18 | @testset "Common conversions betwen MetaArrays and Lists" begin 19 | local arr = [1, 2, 3] 20 | local lst = arrayList(arr) 21 | @test listHead(lst) == 1 22 | @test listEmpty(lst) == false 23 | @test listMember(4, lst) == false 24 | @test listMember(3, lst) == true 25 | @test listHead(list(1, 2, 3)) == 1 26 | lst2::List{Int64} = list(1, 2, 3) 27 | lst2 = listReverse(lst2) 28 | @test listHead(lst2) == 3 29 | @test list(2, 3) == listRest(list(1, 2, 3)) 30 | lstA = list(1, 2, 3) 31 | lstB = list(1, 2, 3) 32 | lstC = listAppend(lstA, lstB) 33 | @test listLength(lstC) == 6 34 | @test listAppend(list(1, 2, 3), list(4.0, 5)) == begin 35 | #= Should be common abstract type in this case =# 36 | Cons{Real}(1, Cons{Real}(2, Cons{Real}(3, Cons{Real}(4.0, Cons{Real}(5, Nil{Any}()))))) 37 | end 38 | lstD = listReverse(lstC) 39 | @test listHead(lstD) == 3 40 | @test listMember(6, lstD) == false 41 | @test listMember(3, lstD) == true 42 | @test listGet(lstD, 1) == 3 43 | @test listGet(lstD, 3) == 1 44 | @test listGet(lstD, 6) == 1 45 | #=Try to convert a list to an array=# 46 | arr = listArray(lstD) 47 | @test length(arr) == 6 48 | end 49 | 50 | 51 | @Uniontype Complex begin 52 | @Record COMPLEX begin 53 | r::ModelicaReal 54 | i::ModelicaReal 55 | end 56 | @Record COMPLEX_INT begin 57 | r::ModelicaInteger 58 | i::ModelicaInteger 59 | end 60 | end 61 | 62 | @Uniontype EvenMoreComplex begin 63 | @Record EVENMORECOMPLEX begin 64 | lst::List{Complex} 65 | end 66 | end 67 | 68 | @Uniontype EvenMoreComplexVector begin 69 | @Record EVENMORECOMPLEXVECTOR begin 70 | lst::Vector{Complex} 71 | end 72 | end 73 | 74 | 75 | @Uniontype InnerLists begin 76 | @Record INNERLISTS begin 77 | a::String 78 | lst0::List{String} 79 | lst1::List{String} 80 | lst2::List{String} 81 | end 82 | end 83 | 84 | @Uniontype InnerVectors begin 85 | @Record INNERVECTORS begin 86 | a::String 87 | lst0::Vector{String} 88 | lst1::Vector{String} 89 | lst2::Vector{String} 90 | end 91 | end 92 | 93 | @Uniontype Comment begin 94 | @Record COMMENT begin 95 | annotation_::Option{String} 96 | comment::Option{String} 97 | end 98 | end 99 | 100 | struct TEST2{T0 <: String} 101 | lst0::Vector{T0} 102 | lst1::Vector{T0} 103 | lst2::Vector{T0} 104 | end 105 | 106 | struct TEST4{T0 <: String, T1 <: String, T2 <: String, T3 <: String} 107 | a::T0 108 | lst0::List{T1} 109 | lst1::List{T2} 110 | lst2::List{T3} 111 | end 112 | 113 | @testset "Complex structure test" begin 114 | @testset "Testing list of complex types" begin 115 | local lst::List{Complex} = list(COMPLEX(0., 0.), COMPLEX(0., 0.), COMPLEX(0., 0.)) 116 | @test length(lst) == 3 117 | @test listHead(lst) == COMPLEX(0., 0.) 118 | @test listLength(listReverse(lst)) == 3 119 | local lst2::List{Complex} = COMPLEX(0., 0.) <| lst 120 | @test length(lst2) == 4 121 | local lst3::List{Complex} = listAppend(lst, lst2) 122 | @test length(lst3) == 7 123 | end 124 | @testset "Testing arrays of complex types" begin 125 | #=Array of Complex elements to a List of Complex elements=# 126 | local A::Array{Complex} = arrayCreate(5, COMPLEX(0., 0.)) 127 | @test length(A) == 5 128 | local L::List{Complex} = arrayList(A) 129 | @test length(L) == 5 130 | end 131 | @testset "Test even more complex (Uniontype with abstract containers)" begin 132 | a = COMPLEX(1., 2.) 133 | tst = EVENMORECOMPLEX(list(a)) 134 | @test length(tst.lst) == 1 135 | tst = EVENMORECOMPLEX(list(COMPLEX(1., 2.), COMPLEX_INT(1,1))) 136 | @test length(tst.lst) == 2 137 | tst = EVENMORECOMPLEXVECTOR([COMPLEX(1., 2.), COMPLEX(1., 2.)]) 138 | @test length(tst.lst) == 2 139 | tst = INNERVECTORS("foo", ["FOO"], String[], String[]) 140 | tst = INNERLISTS("foo", list("FOO"), Nil{String}(), Nil{String}()) 141 | tst = COMMENT(NONE(), NONE()) 142 | end 143 | end 144 | 145 | @testset "Testing array copy" begin 146 | arr2 = arrayCopy(arr) 147 | @test arrayLength(arr2) == 3 148 | end 149 | 150 | @testset "Testing tick()" begin 151 | #= Testing tick =# 152 | @test resetTick() == 0 153 | @test tick() == 1 154 | @test tick() == 2 155 | @test tick() == 3 156 | end 157 | 158 | @testset "Testing the Optional type" begin 159 | 160 | @test begin 161 | try 162 | let 163 | anOpt::Option{Integer} = SOME(4) 164 | bOpt::Option{Integer} = NONE() 165 | cOpt::Option{Any} = SOME(4) 166 | end 167 | true 168 | catch 169 | false 170 | end 171 | end 172 | 173 | struct foo1 174 | a::Any 175 | end 176 | 177 | struct bar2 178 | a::Any 179 | b::Any 180 | end 181 | 182 | struct baz3 183 | a::Any 184 | b::Any 185 | c::Any 186 | end 187 | 188 | a = NONE() 189 | @test 0 == @match a begin 190 | bar2(__) => 2 191 | foo1(a=7) => 3 192 | bar2(__) => 5 193 | bar2(a=1, b=2) => 6 194 | foo1(__) => 1 195 | #= We should match the wildcard =# 196 | _ => 0 197 | end 198 | 199 | struct optionalFoo 200 | a::Option{Integer} 201 | b::Option{Integer} 202 | c::Option{Integer} 203 | end 204 | 205 | try 206 | @test 0 == begin 207 | a = optionalFoo(NONE(), NONE(), NONE()) 208 | @match a begin 209 | optionalFoo(NONE(), NONE(), NONE()) => 0 210 | _ => 1 211 | end 212 | end 213 | catch 214 | false 215 | end 216 | 217 | aa = optionalFoo(SOME(1), NONE(), NONE()) 218 | cc = optionalFoo(NONE(), SOME(2), NONE()) 219 | dd = optionalFoo(NONE(), NONE(), SOME(3)) 220 | ee = optionalFoo(SOME(1), SOME(2), SOME(3)) 221 | 222 | @test 1 == @match aa begin 223 | optionalFoo(SOME(2), _, _) => 2 224 | _ => 1 225 | end 226 | 227 | @test 1 == @match aa begin 228 | optionalFoo(SOME(1), _, _) => 1 229 | _ => 1 230 | end 231 | 232 | @test 1 == @match cc begin 233 | optionalFoo(_, _, _) => 1 234 | _ => 2 235 | end 236 | 237 | @test 2 == @match ee begin 238 | optionalFoo(SOME(2), _, _) => 1 239 | optionalFoo(__) => 2 240 | _ => 3 241 | end 242 | 243 | struct optionalBar 244 | a::Option{Integer} 245 | end 246 | 247 | 248 | a = optionalBar(NONE()) 249 | b = optionalBar(SOME(1)) 250 | 251 | @test 2 == @match a begin 252 | optionalBar(SOME(1)) => 1 253 | optionalBar(NONE()) => 2 254 | end 255 | 256 | @test 1 == @match b begin 257 | optionalBar(NONE()) => 2 258 | optionalBar(SOME(1)) => 1 259 | end 260 | 261 | @test 4 == @match ee begin 262 | optionalFoo(SOME(1), NONE(), NONE()) => 1 263 | optionalFoo(NONE(), SOME(2), NONE()) => 2 264 | optionalFoo(NONE(), NONE(), SOME(3)) => 3 265 | optionalFoo(SOME(1), SOME(2), SOME(3)) => 4 266 | _ => 4 267 | end 268 | 269 | @testset "Testing String for MetaModelica" begin 270 | @test "AB" == "A" + "B" 271 | end 272 | 273 | @testset "Testing MetaModelica assignment semantics" begin 274 | struct A 275 | a::Any 276 | end 277 | struct B 278 | b::Any 279 | end 280 | struct C 281 | c::Any 282 | end 283 | nested = A(B(C(1))) 284 | @assign nested.a.b.c = 4 285 | @test nested.a.b.c == 4 286 | @assign a = 4 287 | @test a == 4 288 | @assign simple = 4 289 | @test simple == 4 290 | end 291 | 292 | end #=End runtime tests=# 293 | end #=End module=# 294 | -------------------------------------------------------------------------------- /src/matchcontinue.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2019-CurrentYear: Open Source Modelica Consortium (OSMC) 3 | Copyright 2018: RelationalAI, Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | The code is originally based on https://github.com/RelationalAI-oss/Rematch.jl with 18 | changes to allow keyword argument matching on structs along with 19 | matching on the immutable list construct accompanying MetaModelica + some other improvements and bug fixes. 20 | It also provides @matchcontinue macro (try the next case when any exception is thrown). 21 | """ 22 | 23 | include("fixLines.jl") 24 | 25 | macro splice(iterator, body) 26 | @assert iterator.head === :call 27 | @assert iterator.args[1] === :in 28 | Expr(:..., :(($(esc(body)) for $(esc(iterator.args[2])) in $(esc(iterator.args[3]))))) 29 | end 30 | 31 | struct MatchFailure <: MetaModelicaException 32 | msg::Any 33 | value::Any 34 | end 35 | 36 | """ 37 | Statically get the fieldcount of a type. Useful to avoid runtime calls to 38 | fieldcount. 39 | """ 40 | @generated function evaluated_fieldcount(t::Type{T}) where {T} 41 | res = T !== NONE ? fieldcount(T) : 0 42 | end 43 | 44 | """ 45 | Statically get the fieldnames of a type. Useful to avoid runtime calls to 46 | fieldnames (which includes many allocations). 47 | """ 48 | @generated function evaluated_fieldnames(t::Type{T}) where {T} 49 | fieldnames(T) 50 | end 51 | 52 | """ 53 | Handles the deconstruction of fields 54 | """ 55 | function handle_destruct_fields(value::Symbol, pattern, subpatterns, len, get::Symbol, 56 | bound::Set{Symbol}, asserts::Vector{Expr}; allow_splat=true) 57 | # NOTE we assume `len` is cheap 58 | fields = [] 59 | seen_splat = false 60 | for (i, subpattern) in enumerate(subpatterns) 61 | if (subpattern isa Expr) && (subpattern.head === :(...)) 62 | @assert allow_splat && !seen_splat "Too many ... in pattern $pattern" 63 | @assert length(subpattern.args) == 1 64 | seen_splat = true 65 | push!(fields, (:($i:($len-$(length(subpatterns) - i))), subpattern.args[1])) 66 | elseif seen_splat 67 | push!(fields, (:($len - $(length(subpatterns) - i)), subpattern)) 68 | elseif (subpattern isa Expr) && (subpattern.head === :kw) 69 | T_sym = Meta.quot(subpattern.args[1]) 70 | push!(fields, (:($T_sym), subpattern.args[2])) 71 | else 72 | push!(fields, (i, subpattern)) 73 | end 74 | end 75 | Expr(:&&, if seen_splat 76 | :($len >= $(length(subpatterns) - 1)) 77 | else 78 | :($len == $(length(subpatterns))) 79 | end, @splice (i, (field, subpattern)) in enumerate(fields) quote 80 | $(Symbol("$(value)_$i")) = $get($value, $field) 81 | $(handle_destruct(Symbol("$(value)_$i"), subpattern, bound, asserts)) 82 | end) 83 | end 84 | 85 | """ 86 | Top level utility function. 87 | Handles deconstruction of patterns together with the value symbol. 88 | """ 89 | function handle_destruct(value::Symbol, pattern, bound::Set{Symbol}, asserts::Vector{Expr}) 90 | if pattern === :(_) 91 | # wildcard 92 | true 93 | elseif !(pattern isa Expr || pattern isa Symbol) || 94 | pattern === :nothing || 95 | @capture(pattern, _quote_macrocall) || 96 | @capture(pattern, Symbol(_)) 97 | # constant 98 | # TODO do we have to be careful about QuoteNode etc? 99 | #Probably not //John 100 | quote 101 | $value === $pattern 102 | end 103 | elseif @capture(pattern, subpattern_Symbol) 104 | # variable 105 | # if the pattern doesn't match, we don't want to set the variable 106 | # so for now just set a temp variable 107 | our_sym = Symbol("variable_", pattern) 108 | if pattern in bound 109 | # already bound, check that this value matches 110 | quote 111 | $our_sym == $value 112 | end 113 | else 114 | # bind 115 | push!(bound, pattern) 116 | quote 117 | $our_sym = $value 118 | true 119 | end 120 | end 121 | elseif @capture(pattern, subpattern1_ || subpattern2_) || 122 | (@capture(pattern, f_(subpattern1_, subpattern2_)) && f === :|) 123 | # disjunction 124 | # need to only bind variables which exist in both branches 125 | bound1 = copy(bound) 126 | bound2 = copy(bound) 127 | body1 = handle_destruct(value, subpattern1, bound1, asserts) 128 | body2 = handle_destruct(value, subpattern2, bound2, asserts) 129 | union!(bound, intersect(bound1, bound2)) 130 | quote 131 | $body1 || $body2 132 | end 133 | elseif @capture(pattern, subpattern1_ && subpattern2_) || 134 | (@capture(pattern, f_(subpattern1_, subpattern2_)) && f === :&) 135 | # conjunction 136 | body1 = handle_destruct(value, subpattern1, bound, asserts) 137 | body2 = handle_destruct(value, subpattern2, bound, asserts) 138 | quote 139 | $body1 && $body2 140 | end 141 | elseif @capture(pattern, _where) 142 | # guard 143 | @assert length(pattern.args) == 2 144 | subpattern = pattern.args[1] 145 | guard = pattern.args[2] 146 | quote 147 | $(handle_destruct(value, subpattern, bound, asserts)) && let $(bound...) 148 | # bind variables locally so they can be used in the guard 149 | $(@splice variable in bound quote 150 | $(esc(variable)) = $(Symbol("variable_", variable)) 151 | end) 152 | $(esc(guard)) 153 | end 154 | end 155 | elseif @capture(pattern, T_(subpatterns__)) #= All wild =# 156 | if length(subpatterns) == 1 && subpatterns[1] === :(__) 157 | #= 158 | Fields not interesting when matching against a wildcard. 159 | NONE() matched against a wildcard is also true 160 | =# 161 | quote 162 | $value isa $(esc(T)) 163 | end 164 | else 165 | T = handleSugar(T) 166 | len = length(subpatterns) 167 | named_fields = [pat.args[1] 168 | for pat in subpatterns if (pat isa Expr) && pat.head === :(kw)] 169 | nNamed = length(named_fields) 170 | @assert length(named_fields) == length(unique(named_fields)) "Pattern $pattern has duplicate named arguments: $(named_fields)" 171 | @assert nNamed == 0 || len == nNamed "Pattern $pattern mixes named and positional arguments" 172 | # struct 173 | if false 174 | elseif nNamed == 0 175 | push!(asserts, 176 | quote 177 | a = typeof($(esc(T))) 178 | #= NONE is a function. However, we treat it as a special case =# 179 | if $(esc(T)) !== NONE && typeof($(esc(T))) <: Function 180 | func = $(esc(T)) 181 | file = @__FILE__ 182 | throw(LoadError("Attempted to match on a function at $(file)", @__LINE__, 183 | AssertionError("Incorrect match usage attempted to match on: $func"))) 184 | end 185 | if !(isstructtype(typeof($(esc(T)))) || issabstracttype(typeof($(esc(T))))) 186 | throw(LoadError("Attempted to match on a pattern that is not a struct at $(file)", 187 | @__LINE__, 188 | AssertionError("Incorrect match usage. Attempted to match on a pattern that is not a struct"))) 189 | end 190 | pattern = $(esc(T)) 191 | if $(esc(T)) !== NONE 192 | if evaluated_fieldcount($(esc(T))) < $(esc(len)) 193 | error("Field count for pattern of type: $pattern is $($(esc(len))) expected $(evaluated_fieldcount($(esc(T))))") 194 | end 195 | end 196 | end) 197 | else # Uses keyword arguments 198 | struct_name = gensym("$(T)_match") 199 | type_name = string(T) 200 | assertcond = true 201 | for field in named_fields 202 | local tmp 203 | tmp = quote 204 | $(Meta.quot(field)) in $struct_name 205 | end 206 | assertcond = Expr(:&&, tmp, assertcond) 207 | end 208 | push!(asserts, quote 209 | if !(let 210 | $struct_name = evaluated_fieldnames($(esc(T))) 211 | $assertcond 212 | end) 213 | error("Pattern contains named argument not in the type at: ") 214 | end 215 | end) 216 | end 217 | quote 218 | $value === nothing && $(esc(T)) === Nothing || 219 | $value isa $(esc(T)) && 220 | $(handle_destruct_fields(value, pattern, subpatterns, length(subpatterns), 221 | :getfield, bound, asserts; allow_splat=false)) 222 | end 223 | end 224 | elseif @capture(pattern, (subpatterns__,)) # Tuple 225 | quote 226 | ($value isa Tuple) && 227 | $(handle_destruct_fields(value, pattern, subpatterns, :(length($value)), :getindex, 228 | bound, asserts; allow_splat=true)) 229 | end 230 | elseif @capture(pattern, [subpatterns__]) # Array 231 | quote 232 | ($value isa AbstractArray) && 233 | $(handle_destruct_fields(value, pattern, subpatterns, :(length($value)), :getindex, 234 | bound, asserts; allow_splat=true)) 235 | end 236 | elseif @capture(pattern, subpattern_::T_) #ImmutableList 237 | quote 238 | # typeassert 239 | ($value isa $(esc(T))) && $(handle_destruct(value, subpattern, bound, asserts)) 240 | end 241 | elseif @capture(pattern, _.__) #Sub member of a variable 242 | quote 243 | $value == $(esc(pattern)) 244 | end 245 | else 246 | println(pattern) 247 | error("Unrecognized pattern syntax: $pattern") 248 | end 249 | end 250 | 251 | """ 252 | Handle syntactic sugar for MetaModelica mode. 253 | Mostly lists but also for the optional type. 254 | Parenthesis for these expressions are skipped 255 | """ 256 | function handleSugar(T) 257 | T = if T === :(<|) 258 | # Syntactic sugar cons. 259 | :Cons 260 | elseif T === :_cons 261 | #= This is legacy for the code generator. For match equation we need to allow this as well =# 262 | :Cons 263 | elseif T === :nil 264 | # Syntactic sugar for Nil 265 | :Nil 266 | elseif T === :NONE 267 | # Syntactic sugar for Nothing 268 | :Nothing 269 | else 270 | T 271 | end 272 | end 273 | 274 | """ 275 | Handles match equations such as 276 | @match x = 4 277 | """ 278 | function handle_match_eq(expr) 279 | if @capture(expr, pattern_ = value_) 280 | asserts = Expr[] 281 | bound = Set{Symbol}() 282 | body = handle_destruct(:value, pattern, bound, asserts) 283 | quote 284 | $(asserts...) 285 | value = $(esc(value)) 286 | __omc_match_done = false 287 | $body || throw(MatchFailure("no match", value)) 288 | $(@splice variable in bound quote 289 | $(esc(variable)) = $(Symbol("variable_$variable")) 290 | end) 291 | value 292 | end 293 | else 294 | error("Unrecognized match syntax: $expr") 295 | end 296 | end 297 | 298 | """ 299 | Handles match cases both for the matchcontinue and regular match case 300 | calls handle_destruct. See handle_destruct for more details. 301 | """ 302 | function handle_match_case(value, case, tail, asserts, matchcontinue::Bool) 303 | if @capture(case, pattern_ => result_) 304 | bound = Set{Symbol}() 305 | body = handle_destruct(:value, pattern, bound, asserts) 306 | if matchcontinue 307 | quote 308 | if (!__omc_match_done) && $body 309 | try 310 | res = let $(bound...) 311 | # export bindings 312 | $(@splice variable in bound quote 313 | $(esc(variable)) = $(Symbol("variable_", variable)) 314 | end) 315 | $(esc(result)) 316 | end 317 | __omc_match_done = true 318 | catch e 319 | #= 320 | We only rethrow for two kinds of exceptions currently. 321 | One for list, and one for generic MetaModelicaExceptions. 322 | =# 323 | #if isa(e, MetaModelicaException) || isa(e, ImmutableListException) 324 | # println(e.msg) 325 | #else 326 | # showerror(stderr, e, catch_backtrace()) 327 | #end 328 | if !isa(e, MetaModelicaException) && !isa(e, ImmutableListException) 329 | if isa(e, MatchFailure) 330 | println("MatchFailure:" + e.msg) 331 | else 332 | showerror(stderr, e, catch_backtrace()) 333 | end 334 | rethrow(e) 335 | end 336 | __omc_match_done = false 337 | end 338 | end 339 | $tail 340 | end 341 | else 342 | quote 343 | if (!__omc_match_done) && $body 344 | res = let $(bound...) 345 | # export bindings 346 | $(@splice variable in bound quote 347 | $(esc(variable)) = $(Symbol("variable_", variable)) 348 | end) 349 | $(esc(result)) 350 | end 351 | __omc_match_done = true 352 | end 353 | $tail 354 | end 355 | end 356 | else 357 | error("Unrecognized case syntax: $case") 358 | end 359 | end 360 | 361 | 362 | """ 363 | Top level function for all match macros except for the match equation macro. 364 | """ 365 | function handle_match_cases(value, match::Expr; mathcontinue::Bool=false) 366 | tail = nothing 367 | if match.head != :block 368 | error("Unrecognized match syntax: Expected begin block $match") 369 | end 370 | line = nothing 371 | local neverFails = false 372 | cases = Expr[] 373 | asserts = Expr[] 374 | for arg in match.args 375 | if isa(arg, LineNumberNode) 376 | line = arg 377 | continue 378 | elseif isa(arg, Expr) 379 | push!(cases, arg) 380 | end 381 | end 382 | for case in reverse(cases) 383 | tail = handle_match_case(:value, case, tail, asserts, mathcontinue) 384 | if line !== nothing 385 | replaceLineNum(tail, @__FILE__, line) 386 | end 387 | #= If one case contains a _ we know this match never fails. =# 388 | pat = case.args[2] 389 | if pat === :_ 390 | neverFails = true 391 | end 392 | end 393 | if neverFails == false || mathcontinue 394 | quote 395 | $(asserts...) 396 | local value = $(esc(value)) 397 | local __omc_match_done::Bool = false 398 | local res 399 | $tail 400 | if !__omc_match_done 401 | throw(MatchFailure("unfinished", value)) 402 | end 403 | res 404 | end 405 | else 406 | quote 407 | $(asserts...) 408 | local value = $(esc(value)) 409 | local __omc_match_done::Bool = false 410 | local res 411 | $tail 412 | res 413 | end 414 | end 415 | end 416 | 417 | 418 | function unsafe_handle_match_cases(value, match::Expr; mathcontinue::Bool=false) 419 | tail = nothing 420 | if match.head != :block 421 | error("Unrecognized match syntax: Expected begin block $match") 422 | end 423 | line = nothing 424 | cases = Expr[] 425 | asserts = Expr[] 426 | for arg in match.args 427 | if isa(arg, LineNumberNode) 428 | line = arg 429 | continue 430 | elseif isa(arg, Expr) 431 | push!(cases, arg) 432 | end 433 | end 434 | for case in reverse(cases) 435 | tail = handle_match_case(:value, case, tail, asserts, mathcontinue) 436 | if line !== nothing 437 | replaceLineNum(tail, @__FILE__, line) 438 | end 439 | end 440 | quote 441 | $(asserts...) 442 | local value = $(esc(value)) 443 | local __omc_match_done::Bool = false 444 | local res 445 | $tail 446 | if !__omc_match_done 447 | value 448 | else 449 | res 450 | end 451 | end 452 | end 453 | 454 | """ 455 | @match pattern = value 456 | If `value` matches `pattern`, bind variables and return `value`. Otherwise, throw `MatchFailure`. 457 | """ 458 | macro match(expr) 459 | res = handle_match_eq(expr) 460 | replaceLineNum(res, @__FILE__, __source__) 461 | res 462 | end 463 | 464 | """ 465 | @matchcontinue value begin 466 | pattern1 => result1 467 | pattern2 => result2 468 | ... 469 | end 470 | 471 | Return `result` for the first matching `pattern`. If there are no matches, throw `MatchFailure`. 472 | """ 473 | macro matchcontinue(value, cases) 474 | res = handle_match_cases(value, cases; mathcontinue=true) 475 | replaceLineNum(res, @__FILE__, __source__) 476 | res 477 | end 478 | 479 | """ 480 | @match value begin 481 | pattern1 => result1 482 | pattern2 => result2 483 | ... 484 | end 485 | 486 | Return `result` for the first matching `pattern`. If there are no matches, throw `MatchFailure`. 487 | """ 488 | macro match(value, cases) 489 | res = handle_match_cases(value, cases; mathcontinue=false) 490 | replaceLineNum(res, @__FILE__, __source__) 491 | res 492 | end 493 | 494 | """ 495 | @unsafematch value begin 496 | pattern1 => result1 497 | pattern2 => result2 498 | ... 499 | end 500 | Return `result` for the first matching `pattern`. If there are no matches, returns `value`. 501 | """ 502 | macro unsafematch(value, cases) 503 | res = unsafe_handle_match_cases(value, cases; mathcontinue=false) 504 | replaceLineNum(res, @__FILE__, __source__) 505 | res 506 | end 507 | 508 | """ 509 | Patterns: 510 | 511 | * `_` matches anything 512 | * `foo` matches anything, binds value to `foo` 513 | * `foo(__)` wildcard match on all subfields of foo, binds value to `foo` 514 | * `Foo(x,y,z)` matches structs of type `Foo` with fields matching `x,y,z` 515 | * `Foo(x=y)` matches structs of type `Foo` with a field named `x` matching `y` 516 | * `[x,y,z]` matches `AbstractArray`s with 3 entries matching `x,y,z` 517 | * `(x,y,z)` matches `Tuple`s with 3 entries matching `x,y,z` 518 | * `[x,y...,z]` matches `AbstractArray`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries. 519 | * `(x,y...,z)` matches `Tuple`s with at least 2 entries, where `x` matches the first entry, `z` matches the last entry and `y` matches the remaining entries. 520 | * `_::T` matches any subtype (`isa`) of T 521 | * `x || y` matches values which match either `x` or `y` (only variables which exist in both branches will be bound) 522 | * `x && y` matches values which match both `x` and `y` 523 | * `x where condition` matches only if `condition` is true (`condition` may use any variables that occur earlier in the pattern eg `(x, y, z where x + y > z)`) 524 | * `x => y` is syntactic sugar for `cons(x,y)` [Preliminary] 525 | * Anything else is treated as a constant and tested for equality 526 | 527 | Patterns can be nested arbitrarily. 528 | 529 | Repeated variables only match if they are `==` eg `(x,x)` matches `(1,1)` but not `(1,2)`. 530 | """ 531 | :(@matchcontinue) 532 | -------------------------------------------------------------------------------- /src/metaRuntime.jl: -------------------------------------------------------------------------------- 1 | #= 2 | The MetaModelica runtime. Partly automatically generated by the transpiler 3 | Functions that are named #= Defined in the runtime =# 4 | are defined in the C runtime in the compiler and interfaces Boehm GC. 5 | These functions should be remimplemented here or removed all together =# 6 | 7 | struct MetaModelicaGeneralException <: MetaModelicaException 8 | msg::Any 9 | end 10 | 11 | """ 12 | SOME 13 | Optional with a value T 14 | """ 15 | struct SOME{T} 16 | data::T 17 | end 18 | 19 | """The optional type is defined as the Union of SOME{T} and the Nothing type. """ 20 | const Option{T} = Union{SOME{T},Nothing} 21 | 22 | """ NONE is defined as nothing. """ 23 | const NONE() = Nothing() 24 | 25 | Base.convert(::Type{Option{S}}, x::SOME{T}) where {S,T<:S} = 26 | let 27 | SOME{S}(convert(S, x.data)) 28 | end 29 | 30 | Base.convert(::Type{Option{T}}, nothing) where {T} = 31 | let 32 | Nothing() 33 | end 34 | 35 | """ Identity case """ 36 | Base.convert(::Type{Union{Nothing,SOME{T}}}, x::Union{Nothing,SOME{T}}) where {T} = 37 | let 38 | x 39 | end 40 | 41 | Base.convert(::Type{Union{Nothing,SOME{S}}}, x::SOME{S}) where {S} = 42 | let 43 | x 44 | end 45 | 46 | """ Logically combine two Booleans with 'and' operator """ 47 | function boolAnd(b1::Bool, b2::Bool)::Bool 48 | b = b1 && b2 49 | end 50 | 51 | """ Logically combine two Booleans with 'or' operator """ 52 | function boolOr(b1::Bool, b2::Bool)::Bool 53 | b = b1 || b2 54 | end 55 | 56 | """ Logically invert Boolean value using 'not' operator """ 57 | function boolNot(b::Bool)::Bool 58 | nb = !b 59 | end 60 | 61 | """ Compares two Booleans """ 62 | function boolEq(b1::Bool, b2::Bool)::Bool 63 | b1 === b2 64 | end 65 | 66 | """ Returns \\\"true\\\" or \\\"false\\\" string """ 67 | function boolString(b::Bool)::String 68 | str = if (b) 69 | "true" 70 | else 71 | "false" 72 | end 73 | end 74 | 75 | """ Adds two Integer values """ 76 | function intAdd(i1::ModelicaInteger, i2::ModelicaInteger) 77 | local i::ModelicaInteger 78 | i = i1 + i2 79 | end 80 | 81 | """ Subtracts two Integer values """ 82 | function intSub(i1::ModelicaInteger, i2::ModelicaInteger) 83 | local i::ModelicaInteger 84 | i = i1 - i2 85 | end 86 | 87 | """ Multiplies two Integer values """ 88 | function intMul(i1::ModelicaInteger, i2::ModelicaInteger) 89 | local i::ModelicaInteger 90 | i = i1 * i2 91 | end 92 | 93 | """ Divides two Integer values """ 94 | function intDiv(i1::ModelicaInteger, i2::ModelicaInteger) 95 | local i::ModelicaInteger 96 | i = div(i1, i2) 97 | end 98 | 99 | """ Calculates remainder of Integer division i1/i2 """ 100 | function intMod(i1::ModelicaInteger, i2::ModelicaInteger) 101 | local i::ModelicaInteger 102 | i = mod(i1, i2) 103 | end 104 | 105 | """ Returns the bigger one of two Integer values """ 106 | function intMax(i1::ModelicaInteger, i2::ModelicaInteger) 107 | local i::ModelicaInteger 108 | i = max(i1, i2) 109 | end 110 | 111 | """ Returns the smaller one of two Integer values """ 112 | function intMin(i1::ModelicaInteger, i2::ModelicaInteger) 113 | local i::ModelicaInteger 114 | i = min(i1, i2) 115 | end 116 | 117 | """ Returns the absolute value of Integer i """ 118 | function intAbs(i::ModelicaInteger) 119 | local oi::ModelicaInteger 120 | oi = abs(i) 121 | end 122 | 123 | """ Returns negative value of Integer i """ 124 | function intNeg(i::ModelicaInteger) 125 | local oi::ModelicaInteger 126 | oi = -i 127 | end 128 | 129 | """ Returns whether Integer i1 is smaller than Integer i2 """ 130 | function intLt(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 131 | local b::Bool 132 | b = i1 < i2 133 | end 134 | 135 | """ Returns whether Integer i1 is smaller than or equal to Integer i2 """ 136 | function intLe(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 137 | local b::Bool 138 | 139 | b = i1 <= i2 140 | b 141 | end 142 | 143 | """ Returns whether Integer i1 is equal to Integer i2 """ 144 | function intEq(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 145 | local b::Bool 146 | 147 | b = i1 == i2 148 | b 149 | end 150 | 151 | """ Returns whether Integer i1 is not equal to Integer i2 """ 152 | function intNe(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 153 | local b::Bool 154 | 155 | b = i1 != i2 156 | b 157 | end 158 | 159 | """ Returns whether Integer i1 is greater than or equal to Integer i2 """ 160 | function intGe(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 161 | local b::Bool 162 | 163 | b = i1 >= i2 164 | b 165 | end 166 | 167 | """ Returns whether Integer i1 is greater than Integer i2 """ 168 | function intGt(i1::ModelicaInteger, i2::ModelicaInteger)::Bool 169 | local b::Bool 170 | b = i1 > i2 171 | b 172 | end 173 | 174 | """ Returns bitwise inverted Integer number of i """ 175 | function intBitNot(i::ModelicaInteger) 176 | local o::ModelicaInteger = ~i 177 | o 178 | end 179 | 180 | """ Returns bitwise \'and\' of Integers i1 and i2 """ 181 | function intBitAnd(i1::ModelicaInteger, i2::ModelicaInteger) 182 | local o::ModelicaInteger = i1 & i2 183 | o 184 | end 185 | 186 | """ Returns bitwise 'or' of Integers i1 and i2 """ 187 | function intBitOr(i1::ModelicaInteger, i2::ModelicaInteger) 188 | local o::ModelicaInteger = i1 | i2 189 | o 190 | end 191 | 192 | """ Returns bitwise 'xor' of Integers i1 and i2 """ 193 | function intBitXor(i1::ModelicaInteger, i2::ModelicaInteger) 194 | local o::ModelicaInteger = i1 ⊻ i2 195 | o 196 | end 197 | 198 | """ Returns bitwise left shift of Integer i by s bits """ 199 | function intBitLShift(i::ModelicaInteger, s::ModelicaInteger) 200 | local o::ModelicaInteger = i << s 201 | o 202 | end 203 | 204 | """ Returns bitwise right shift of Integer i by s bits """ 205 | function intBitRShift(i::ModelicaInteger, s::ModelicaInteger) 206 | local o::ModelicaInteger = i >> s 207 | o 208 | end 209 | 210 | """ Converts Integer to Real """ 211 | function intReal(i::ModelicaInteger)::Float64 212 | Float64(i) 213 | end 214 | 215 | """ Converts Integer to String """ 216 | function intString(i::ModelicaInteger)::String 217 | string(i) 218 | end 219 | 220 | function realAdd(r1::ModelicaReal, r2::ModelicaReal)::Float64 221 | local r::ModelicaReal 222 | 223 | r = r1 + r2 224 | r 225 | end 226 | 227 | function realSub(r1::ModelicaReal, r2::ModelicaReal)::Float64 228 | local r::ModelicaReal 229 | 230 | r = r1 - r2 231 | r 232 | end 233 | 234 | function realMul(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 235 | local r::ModelicaReal 236 | 237 | r = r1 * r2 238 | r 239 | end 240 | 241 | function realDiv(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 242 | local r::ModelicaReal 243 | 244 | r = r1 / r2 245 | r 246 | end 247 | 248 | function realMod(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 249 | local r::ModelicaReal 250 | 251 | r = mod(r1, r2) 252 | r 253 | end 254 | 255 | function realPow(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 256 | local r::ModelicaReal 257 | 258 | r = r1^r2 259 | r 260 | end 261 | 262 | function realMax(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 263 | local r::ModelicaReal 264 | r = max(r1, r2) 265 | end 266 | 267 | function realMin(r1::ModelicaReal, r2::ModelicaReal)::ModelicaReal 268 | local r::ModelicaReal 269 | r = min(r1, r2) 270 | end 271 | 272 | function realAbs(x::ModelicaReal)::ModelicaReal 273 | local y::ModelicaReal 274 | y = abs(x) 275 | end 276 | 277 | function realNeg(x::ModelicaReal)::ModelicaReal 278 | local y::ModelicaReal 279 | y = -x 280 | end 281 | 282 | function realLt(x1::ModelicaReal, x2::ModelicaReal)::Bool 283 | local b::Bool 284 | b = x1 < x2 285 | end 286 | 287 | function realLe(x1::ModelicaReal, x2::ModelicaReal)::Bool 288 | local b::Bool 289 | b = x1 <= x2 290 | end 291 | 292 | function realEq(x1::ModelicaReal, x2::ModelicaReal)::Bool 293 | local b::Bool 294 | b = x1 == x2 295 | end 296 | 297 | function realNe(x1::ModelicaReal, x2::ModelicaReal)::Bool 298 | local b::Bool 299 | b = x1 != x2 300 | end 301 | 302 | function realGe(x1::ModelicaReal, x2::ModelicaReal)::Bool 303 | local b::Bool 304 | 305 | b = x1 >= x2 306 | b 307 | end 308 | 309 | function realGt(x1::ModelicaReal, x2::ModelicaReal)::Bool 310 | local b::Bool 311 | 312 | b = x1 > x2 313 | b 314 | end 315 | 316 | function realInt(r::ModelicaReal) 317 | local i::ModelicaInteger 318 | i = Integer(trunc(r)) 319 | end 320 | 321 | function realString(r::ModelicaReal)::String 322 | local str::String = string(r) 323 | str 324 | end 325 | 326 | function stringCharInt(ch::String) 327 | local i::ModelicaInteger = Int64(ch[1]) 328 | i 329 | end 330 | 331 | function intStringChar(i::ModelicaInteger)::String 332 | local ch::String = string(Char(i)) 333 | ch 334 | end 335 | 336 | function stringInt(str::String) 337 | local i::ModelicaInteger = Int64(str) 338 | i 339 | end 340 | 341 | """ This function fails unless the whole string can be consumed by strtod without 342 | setting errno. For more details, see man 3 strtod """ 343 | function stringReal(str::String)::ModelicaReal 344 | local r::ModelicaReal = parse(Float64, str) 345 | r 346 | end 347 | 348 | """ O(str) """ 349 | function stringListStringChar(str::String)::List{String} 350 | local chars::List{String} = nil 351 | for i = length(chars):-1:1 352 | chars = _cons(string(str[i]), chars) 353 | end 354 | chars 355 | end 356 | 357 | """ O(str) """ 358 | function stringAppendList(strs::List)::String 359 | local str::String = "" 360 | for n in strs 361 | str = str + n 362 | end 363 | str 364 | end 365 | 366 | """ O(str) 367 | Takes a list of strings and a string delimiter and appends all 368 | list elements with the string delimiter inserted between elements. 369 | Example: stringDelimitList({\"x\",\"y\",\"z\"}, \", \") => \"x, y, z\" 370 | """ 371 | function stringDelimitList(strs::List, delimiter::String)::String 372 | buffer = IOBuffer() 373 | for (i,n) in enumerate(strs) 374 | if i == 1 375 | print(buffer, n) 376 | else 377 | print(buffer, delimiter) 378 | print(buffer, n) 379 | end 380 | end 381 | return String(take!(buffer))#str 382 | end 383 | 384 | stringDelimitList(lst::Nil, delim::String) = "" 385 | 386 | """ O(1) """ 387 | function stringLength(str::String) 388 | length(str) 389 | end 390 | 391 | """ O(1) """ 392 | function stringEmpty(str::String)::Bool 393 | local isEmpty::Bool 394 | isEmpty = stringLength(str) == 0 395 | end 396 | 397 | """ O(1) """ 398 | function stringGet(str::String, index::ModelicaInteger) 399 | str[index] 400 | end 401 | 402 | """ O(1) """ 403 | function stringGetStringChar(str::String, index::ModelicaInteger)::String 404 | if index < 0 405 | println("stringGetStringChar: index < 0!") 406 | fail() 407 | end 408 | local ch::String = string(str[index]) 409 | ch 410 | end 411 | 412 | """ O(n) """ 413 | function stringUpdateStringChar(str::String, newch::String, index::ModelicaInteger)::String 414 | local news::String = str 415 | if index < 0 416 | println("stringUpdateStringChar: index < 0!") 417 | fail() 418 | end 419 | news[index] = newch[1] 420 | news 421 | end 422 | 423 | """ O(s1+s2) """ 424 | function stringAppend(s1::String, s2::String)::String 425 | s1 * s2 426 | end 427 | 428 | """ O(N) """ 429 | function stringEq(s1::String, s2::String)::Bool 430 | s1 == s2 431 | end 432 | 433 | """ O(N) """ 434 | function stringEqual(s1::String, s2::String)::Bool 435 | s1 == s2 436 | end 437 | 438 | function stringCompare(s1::String, s2::String) 439 | local res = cmp(s1, s2) 440 | return res 441 | end 442 | 443 | function myhash(s::String) 444 | local h::ModelicaInteger = mod(hash(s), typemax(ModelicaInteger)) 445 | h 446 | end 447 | 448 | function stringHash(str::String) 449 | local h::ModelicaInteger = ModelicaInteger(myhash(str)) 450 | h 451 | end 452 | 453 | #= TODO: Defined in the runtime =# 454 | function stringHashDjb2(str::String) 455 | local h::ModelicaInteger = ModelicaInteger(myhash(str)) 456 | h 457 | end 458 | 459 | """ Does hashing+modulo without intermediate results. """ 460 | function stringHashDjb2Mod(str::String, m::ModelicaInteger) 461 | local h::ModelicaInteger = mod(ModelicaInteger(myhash(str)), m) 462 | h 463 | end 464 | 465 | function stringHashSdbm(str::String) 466 | local h::ModelicaInteger = ModelicaInteger(myhash(str)) 467 | h 468 | end 469 | 470 | function substring(str::String, start::ModelicaInteger, stop::ModelicaInteger)::String #= stop index, first character is 1 =# 471 | if start < 0 472 | println("substring: start < 0!") 473 | fail() 474 | end 475 | local out = str[start:stop] 476 | out 477 | end 478 | 479 | """ O(1) ? """ 480 | function arrayLength(arr::Array{T}) where {T} 481 | length(arr) 482 | end 483 | 484 | """ O(1) """ 485 | function arrayEmpty(arr::Array{A})::Bool where {A} 486 | length(arr) == 0 487 | end 488 | 489 | """ O(1) """ 490 | function arrayGet(arr::Array{A}, index::ModelicaInteger) where {A} 491 | if index < 0 492 | println("arrayGet: index < 0!") 493 | fail() 494 | end 495 | arr[index] 496 | end 497 | 498 | """ O(size) """ 499 | function arrayCreate(size::ModelicaInteger, initialValue::A) where {A} 500 | fill(initialValue, size) 501 | end 502 | 503 | """ O(N) """ 504 | function arrayList(arr::Array{T}) where {T} 505 | local lst::List{T} = nil 506 | for i = length(arr):-1:1 507 | lst = Cons{T}(arr[i], lst) 508 | end 509 | lst 510 | end 511 | 512 | """ O(n) """ 513 | function listArray(lst::Cons{T}) where {T} 514 | local N = length(lst) 515 | local arr::Vector{T} = Vector{T}(undef, N) 516 | i = 1 517 | while lst !== nil 518 | arr[i] = lst.head 519 | i += 1 520 | lst = lst.tail 521 | end 522 | return arr 523 | end 524 | 525 | """ O(1) """ 526 | function listArray(lst::Nil) 527 | [] 528 | end 529 | 530 | """ 531 | O(n) 532 | 533 | Same as listArray but with a dummy argument to specify the type. 534 | """ 535 | function listArray(lst::Cons{T}, ty) where {T} 536 | local arr = Vector{ty}(undef, length(lst)) 537 | for i in lst 538 | arr[i] 539 | end 540 | return arr 541 | end 542 | 543 | """ 544 | Same as listArray but with a dummy argument to specify the type. 545 | """ 546 | function listArray(lst::Nil, ty) 547 | ty[] 548 | end 549 | 550 | """ O(1) """ 551 | function arrayUpdate(arr::Array{A}, index::ModelicaInteger, 552 | newValue::B)::Array{A} where {A,B} 553 | #local newArray::Array{A} = arr #= same as the input array; used for folding =# 554 | arr[index] = newValue 555 | #= Defined in the runtime =# 556 | arr #= same as the input array; used for folding =# 557 | end 558 | 559 | """ O(n) """ 560 | function arrayCopy(arr::Array{A})::Array{A} where {A} 561 | copy(arr) 562 | end 563 | 564 | """ Appends arr2 to arr1. O(length(arr1) + length(arr2)). 565 | Note that this operation is *not* destructive, i.e. a new array is created. """ 566 | function arrayAppend(arr1::Array{A}, arr2::Array{A})::Array{A} where {A} 567 | local arr::Array{A} 568 | @error "Defined in the runtime" 569 | fail() 570 | end 571 | 572 | """ 573 | Returns the string representation of any value. 574 | Rather slow; only use this for debugging! 575 | """ 576 | function anyString(a::A) where {A} 577 | string(dump(a)) 578 | end 579 | 580 | """ print(anyString(a)), but to stderr """ 581 | function printAny(a::A) where {A} 582 | println(dump(a)) 583 | end 584 | 585 | """ For RML compatibility """ 586 | function debug_print(str::String, a::A) where {A} 587 | #= Defined in the runtime =# 588 | println(str) 589 | @show a 590 | end 591 | 592 | let tickCounter = 0 593 | global function tick() 594 | tickCounter += 1 595 | end 596 | global function resetTick() 597 | tickCounter = 0 598 | end 599 | end 600 | 601 | function equality(a1::A1, a2::A2) where {A1,A2} 602 | #= Defined in the runtime =# 603 | if !valueEq(a1, a2) 604 | fail() 605 | end 606 | end 607 | 608 | # cannot use nothing in the globalRoots array 609 | # as NONE() is nothing so we use the struct below 610 | # to signal when an element is not set 611 | 612 | """ Sets the index of the root variable with index 1..1024 613 | This is a global mutable value and should be used sparingly. 614 | You are recommended not to use "missing" the runtime system treats this values as uninitialized and fail getGlobalRoot later on. 615 | """ 616 | const global globalRoots::Vector{Any} = Vector{Any}(missing, 1024) 617 | 618 | function setGlobalRoot(index::ModelicaInteger, value::T) where {T} 619 | if index > 1023 || index < 0 620 | fail() 621 | end 622 | globalRoots[index+1] = value 623 | end 624 | 625 | function getGlobalRoot(index::ModelicaInteger) 626 | if index > 1023 || index < 0 627 | fail() 628 | end 629 | val = globalRoots[index+1] 630 | if ismissing(val) 631 | fail() 632 | end 633 | val 634 | end 635 | 636 | """ The return-value is compiler-dependent on the runtime implementation of 637 | boxed values. The number of bits reserved for the constructor is generally 638 | between 6 and 8 bits. """ 639 | function valueConstructor(value::A) where {A} 640 | # hack! hack! hack! 641 | local ctor::ModelicaInteger = myhash(string(typeof(value))) 642 | ctor 643 | end 644 | 645 | """ The number of slots a boxed value has. This is dependent on sizeof(void*) 646 | on the architecture in question. """ 647 | function valueSlots(value::A) where {A} 648 | local slots::ModelicaInteger = 0 649 | try 650 | slots = nfields(value) 651 | catch ex 652 | # do nothing 653 | end 654 | slots 655 | end 656 | 657 | """ Structural equality """ 658 | function valueEq(a1::A, a2::B)::Bool where {A,B} 659 | local b::Bool = @match (a1, a2) begin 660 | (SOME(x1), SOME(x2)) => valueEq(x1, x2) 661 | (_, _) => a1 === a2 662 | end 663 | b 664 | end 665 | 666 | """ a1 > a2? """ 667 | function valueCompare(a1::A, a2::A) where {A} 668 | local i::ModelicaInteger = if valueConstructor(a1) < valueConstructor(a2) 669 | -1 670 | elseif valueConstructor(a1) > valueConstructor(a2) 671 | 1 672 | else 673 | 0 674 | end 675 | i #= -1, 0, 1 =# 676 | end 677 | 678 | function valueHashMod(value::A, mod::ModelicaInteger) where {A} 679 | local h::ModelicaInteger = mod(ModelicaInteger(myhash(string(value))), m) 680 | h 681 | end 682 | 683 | """ This is a very fast comparison of two values which only checks if the pointers are equal. """ 684 | function referenceEq(a1::A1, a2::A2)::Bool where {A1,A2} 685 | #TODO: Should be like this? 686 | a1 === a2 687 | end 688 | 689 | """ Returns the pointer address of a reference as a hexadecimal string that can 690 | be used for debugging. """ 691 | function referencePointerString(ref::A)::String where {A} 692 | local str::String 693 | @assert false "not implemented" 694 | str 695 | end 696 | 697 | """ Use the diff to compare two time samples to each other. Not very accurate. """ 698 | function clock()::ModelicaReal 699 | local t::ModelicaReal 700 | @assert false "not implemented" 701 | t 702 | end 703 | 704 | """ Returns true if the input is NONE() """ 705 | function isNone(opt::Option{A})::Bool where {A} 706 | (opt === nothing) # isa(opt, NONE)) 707 | end 708 | 709 | """ Returns true if the input is SOME() """ 710 | function isSome(opt::Option{A})::Bool where {A} 711 | isa(opt, SOME) 712 | end 713 | 714 | function listStringCharString(strs::List{String})::String 715 | local str::String 716 | @assert false "not implemented" 717 | str 718 | end 719 | 720 | function stringCharListString(strs::List{String})::String 721 | local str::String = "" 722 | for s in strs 723 | str = str + s 724 | end 725 | str 726 | end 727 | 728 | function fail() 729 | throw(MetaModelicaGeneralException("Runtime defined generic Meta Modelica failure")) 730 | end 731 | 732 | function fail(msg::String) 733 | throw(MetaModelicaGeneralException(msg)) 734 | end 735 | 736 | """ Sets the stack overflow signal to the given value and returns the old one """ 737 | function setStackOverflowSignal(inSignal::Bool)::Bool 738 | local outSignal::Bool 739 | 740 | outSignal = inSignal 741 | outSignal 742 | end 743 | 744 | function referenceDebugString(functionSymbol::A)::String where {A} 745 | local name::String 746 | @assert false "not implemented" 747 | name 748 | end 749 | 750 | """ TODO: I am far from sure that this will fly.. in Julia. 751 | The code generated from the transpiler is correct however""" 752 | function isPresent(ident::T)::Bool where {T} 753 | local b::Bool 754 | b = true 755 | end 756 | 757 | #= The Info attribute provides location information for elements and classes. =# 758 | @Uniontype SourceInfo begin 759 | @Record SOURCEINFO begin 760 | fileName::String #= fileName where the class is defined in =# 761 | isReadOnly::Bool #= isReadOnly : (true|false). Should be true for libraries =# 762 | lineNumberStart::ModelicaInteger #= lineNumberStart =# 763 | columnNumberStart::ModelicaInteger #= columnNumberStart =# 764 | lineNumberEnd::ModelicaInteger #= lineNumberEnd =# 765 | columnNumberEnd::ModelicaInteger #= columnNumberEnd =# 766 | lastModification::ModelicaReal #= mtime in stat(2), stored as a double for increased precision on 32-bit platforms =# 767 | end 768 | end 769 | 770 | SOURCEINFO(fileName::String, isReadOnly::Bool, lineNumberStart::ModelicaInteger, 771 | columnNumberSTart::ModelicaInteger, lineNumberEnd::ModelicaInteger, 772 | columnNumberEnd::ModelicaInteger) = 773 | let 774 | #=No source info=# 775 | SOURCEINFO(fileName, isReadOnly, lineNumberStart, columnNumberSTart, lineNumberEnd, 776 | columnNumberEnd, 0.0) 777 | end 778 | 779 | function sourceInfo()::SourceInfo 780 | local info::SourceInfo 781 | #= Defined in the runtime =# 782 | SOURCEINFO("", true, 1, 2, 3, 4, 0.0) 783 | end 784 | 785 | Base.:+(x::String, y::String) = 786 | let 787 | x * y 788 | end 789 | 790 | """ Imports and prints if the import is sucessful """ 791 | macro importDBG(moduleName) 792 | quote 793 | import $moduleName 794 | x = string(@__MODULE__) 795 | y = string($(esc(moduleName))) 796 | println("Importing " * y * " in " * x) 797 | end 798 | end 799 | 800 | const NOT_IMPLEMENTED_MSG::String = "__NOT_IMPLEMENTED__" 801 | function getInstanceName()::String 802 | return NOT_IMPLEMENTED_MSG 803 | end 804 | 805 | function StringFunction(i::Int64)::String 806 | intString(i) 807 | end 808 | 809 | function StringFunction(r::Float64)::String 810 | realString(r) 811 | end 812 | 813 | function String(arg)::String 814 | return string(arg) 815 | end 816 | --------------------------------------------------------------------------------