diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fb3c9b3..7712442 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -2,8 +2,6 @@ name: Documentation on: push: - branches: - - main branches-ignore: - gh-pages diff --git a/README.md b/README.md index b2dff4a..cfd0419 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Molang Parser & Runtime for C# Getting Started ------------ -Coming soon. +See the [documentation here](https://concretemc.github.io/MolangSharp/articles/getting-started.html). Awesome Repositories ------------------------ diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md new file mode 100644 index 0000000..3056541 --- /dev/null +++ b/docs/articles/getting-started.md @@ -0,0 +1,26 @@ +# Getting Started + +## Basic example +```c# +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Runtime; + +//Initialize the runtime +MoLangRuntime runtime = new MoLangRuntime(); + +//Parse our MoLang expression +var parsed = MoLangParser.Parse("10 * 45.0"); + +//Execute the expression +var result = runtime.Execute(parsed); +Console.WriteLine($"Result={result.AsDouble()}"); +``` + +### Output +```c# +Result=450 +``` + +## More examples +Check out the Examples folder in the Github repository: +[https://github.com/ConcreteMC/MolangSharp/tree/main/src/examples](https://github.com/ConcreteMC/MolangSharp/tree/main/src/examples) \ No newline at end of file diff --git a/docs/articles/index.md b/docs/articles/index.md new file mode 100644 index 0000000..5a9dd54 --- /dev/null +++ b/docs/articles/index.md @@ -0,0 +1 @@ +# Articles diff --git a/docs/articles/interop.md b/docs/articles/interop.md new file mode 100644 index 0000000..2a917d4 --- /dev/null +++ b/docs/articles/interop.md @@ -0,0 +1,62 @@ +# Interoperability +MolangSharp has the ability to expose class properties, fields & methods to the runtime. + +## Defining a property +In the following example we will show howto expose a property or field to MoLang. + +```c# +[MoProperty("myProperty")] +public double MyProperty { get; set; } + +//The following property only has a GET accessor and will be readonly +[MoProperty("myReadOnlyProperty")] +public double ReadonlyProperty { get; } + +//This will expose the field to MoLang +[MoProperty("myField")] +public readonly double MyField; +``` + +## Defining a function/method +In the following example we will show howto expose a method to MoLang. +```c# +[MoFunction("add")] +public double Add(double a, double b) +{ + return a + b; +} +``` + +## Example class +This example will show how to expose our custom MyMath class to MoLang + +### The class +This is the class we'll be exposing to MoLang +```c# +public class MyMath +{ + [MoFunction("sin")] + public double Sin(double value) => Math.Sin(value * (Math.PI / 180d)); + + [MoFunction("asin")] + public double Asin(double value) => Math.Asin(value * (Math.PI / 180d)); + + [MoFunction("cos")] + public double Cos(double value) => Math.Cos(value * (Math.PI / 180d)); + + [MoFunction("acos")] + public double Acos(double value) => Math.Acos(value * (Math.PI / 180d)); +} +``` + +### Making the class available to the runtime +```c# +MoLangEnvironment environment = new MoLangEnvironment(); +environment.Structs.Add("custom_math", new InteropStruct(new MyMath())); +MoLangRuntime runtime = new MoLangRuntime(environment); +``` + +This would expose the MyMath class to the runtime instance created in the example. The following example would now be valid. +``` +custom_math.sin(12.0); +``` \ No newline at end of file diff --git a/docs/articles/intro.md b/docs/articles/intro.md deleted file mode 100644 index f65681d..0000000 --- a/docs/articles/intro.md +++ /dev/null @@ -1 +0,0 @@ -# Coming soon... \ No newline at end of file diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index ff89ef1..9c42cb2 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -1,2 +1,8 @@ -- name: Introduction - href: intro.md +- name: Home + href: index.md + +- name: Getting Started + href: getting-started.md + +- name: Interoperability + href: interop.md \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json index cb1b46c..30df283 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -52,10 +52,7 @@ "dest": "_site", "globalMetadataFiles": [], "fileMetadataFiles": [], - "template": [ - "default", - "templates/material" - ], + "template": ["default", "templates/discordfx"], "globalMetadata": { "_enableSearch": "true" }, diff --git a/docs/templates/discordfx/layout/_master.tmpl b/docs/templates/discordfx/layout/_master.tmpl new file mode 100644 index 0000000..f25a5ee --- /dev/null +++ b/docs/templates/discordfx/layout/_master.tmpl @@ -0,0 +1,68 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} +{{!include(/^styles/.*/)}} +{{!include(/^fonts/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} +{{!include(search-stopwords.json)}} + + + + + {{>partials/head}} + + +
+ + + + + + {{>partials/logo}} + +
+ +
+ +
+ +
+ +
+ + {{>partials/navbar}} + +
+ + {{^_disableToc}} + {{>partials/toc}} + {{/_disableToc}} + +
+ + {{>partials/footer}} + +
+ +
+ +
+ + {{^_disableBreadcrumb}} + {{>partials/breadcrumb}} + {{/_disableBreadcrumb}} + +
+ {{!body}} +
+ +
+
+
+ + {{>partials/scripts}} + + + + diff --git a/docs/templates/discordfx/partials/footer.tmpl.partial b/docs/templates/discordfx/partials/footer.tmpl.partial new file mode 100644 index 0000000..b110641 --- /dev/null +++ b/docs/templates/discordfx/partials/footer.tmpl.partial @@ -0,0 +1,4 @@ +
+ {{{_appFooter}}} + {{^_appFooter}}Generated by DocFX{{/_appFooter}} +
\ No newline at end of file diff --git a/docs/templates/discordfx/partials/head.tmpl.partial b/docs/templates/discordfx/partials/head.tmpl.partial new file mode 100644 index 0000000..43a2cc7 --- /dev/null +++ b/docs/templates/discordfx/partials/head.tmpl.partial @@ -0,0 +1,22 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + diff --git a/docs/templates/discordfx/partials/li.tmpl.partial b/docs/templates/discordfx/partials/li.tmpl.partial new file mode 100644 index 0000000..2c8a3d0 --- /dev/null +++ b/docs/templates/discordfx/partials/li.tmpl.partial @@ -0,0 +1,31 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
    + {{#items}} + {{^dropdown}} +
  • + {{^leaf}} + + {{/leaf}} + {{#topicHref}} + {{name}} + {{/topicHref}} + {{^topicHref}} + {{{name}}} + {{/topicHref}} + + {{^leaf}} + {{>partials/li}} + {{/leaf}} +
  • + {{/dropdown}} + {{#dropdown}} +
  • + {{name}} +
      + {{>partials/dd-li}} +
    +
  • + {{/dropdown}} + {{/items}} +
diff --git a/docs/templates/discordfx/partials/logo.tmpl.partial b/docs/templates/discordfx/partials/logo.tmpl.partial new file mode 100644 index 0000000..738ab5b --- /dev/null +++ b/docs/templates/discordfx/partials/logo.tmpl.partial @@ -0,0 +1,6 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + {{_appName}} + {{_appName}} + \ No newline at end of file diff --git a/docs/templates/discordfx/partials/navbar.tmpl.partial b/docs/templates/discordfx/partials/navbar.tmpl.partial new file mode 100644 index 0000000..10b82d7 --- /dev/null +++ b/docs/templates/discordfx/partials/navbar.tmpl.partial @@ -0,0 +1,13 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + +
+ {{>partials/logo}} + +
+ +
+ +
+ diff --git a/docs/templates/discordfx/partials/scripts.tmpl.partial b/docs/templates/discordfx/partials/scripts.tmpl.partial new file mode 100644 index 0000000..749cbcd --- /dev/null +++ b/docs/templates/discordfx/partials/scripts.tmpl.partial @@ -0,0 +1,11 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + + + + + + diff --git a/docs/templates/discordfx/partials/toc.tmpl.partial b/docs/templates/discordfx/partials/toc.tmpl.partial new file mode 100644 index 0000000..c660966 --- /dev/null +++ b/docs/templates/discordfx/partials/toc.tmpl.partial @@ -0,0 +1,5 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
+
+
diff --git a/docs/templates/discordfx/styles/colors.css b/docs/templates/discordfx/styles/colors.css new file mode 100644 index 0000000..63590c7 --- /dev/null +++ b/docs/templates/discordfx/styles/colors.css @@ -0,0 +1,73 @@ +/* Color schemes */ +:root +{ + /* General */ + --main-bg-color: #36393f; + --footer-bg-color: rgba(0,0,0,.4); + --table-strip-bg-color: #121315; + --table-header-bg-color: #202225;; + --table-header-color: hsla(0,0%,100%,.8); + --table-header-border-color: #040405; + + /* Text */ + --text-color: #dcddde; + --link-color: #00b0f4; + --link-hover-color: #4bd5ff; + --link-active-color: #fff; + --link-active-bg-color: #7289da; + --h3-color: #ffffff85; + --h4-color: #ffffffeb; + --h5-color: #ffffffd1; + + /* Topbar */ + --topbar-bg-color: #18191c; + + /* Button */ + --button-color: #747f8d; + + /* Sidebar */ + --separator-color: #4f545c; + --sidebar-bg-color: #2f3136; + --sidebar-item-color: #b9bbbe; + --sidebar-item-2nd-color: hsla(0,0%,100%,.35); + --sidebar-item-3rd-color: hsla(0,0%,100%,.25); + + /* Scrollbar */ + --scrollbar-bg-color: transparent; + --scrollbar-thumb-bg-color: rgba(0,0,0,.4); + --scrollbar-thumb-border-color: transparent; + + /* Alerts and Blocks */ + --alert-info-border-color: rgba(114,137,218,.5); + --alert-info-bg-color: rgba(114,137,218,.1); + + --alert-warning-border-color: rgba(250,166,26,.5); + --alert-warning-bg-color: rgba(250,166,26,.1); + + --alert-danger-border-color: rgba(240,71,71,.5); + --alert-danger-bg-color: rgba(240,71,71,.1); + + --alert-tip-border-color: rgba(255,255,255,.5); + --alert-tip-bg-color: rgba(255,255,255,.1); + + --blockquote-border-color: rgba(255,255,255,.5); + --blockquote-bg-color: rgba(255,255,255,.1); + + --breadcrumb-bg-color: #2f3136; + + /* Code Higlighting */ + --code-bg-color: #18191c; + --code-color: #8790A3; + --code-keyword-color: #569cd6; + --code-comment-color: #57a64a; + --code-macro-color: #beb7ff; + --code-string-color: #d69d85; + --code-string-escape-color: #ffd68f; + --code-field-color: #c8c8c8; + --code-function-color: #dcdcaa; + --code-control-color: #d8a0df; + --code-class-color: #4ec9b0; + --code-number-color: #b5cea8; + --code-params-color: #9a9a9a; + --code-breakpoint-color: #8c2f2f; +} diff --git a/docs/templates/discordfx/styles/discord.css b/docs/templates/discordfx/styles/discord.css new file mode 100644 index 0000000..a5a97b4 --- /dev/null +++ b/docs/templates/discordfx/styles/discord.css @@ -0,0 +1,681 @@ +/* Discord Style */ + +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-bg-color); +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-bg-color); + border-color: var(--scrollbar-thumb-border-color); + border-radius: 5px; +} + +::marker { + unicode-bidi: isolate; + font-variant-numeric: tabular-nums; + text-transform: none; + text-indent: 0px !important; + text-align: start !important; + text-align-last: start !important; +} + +*, :after, :before +{ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, body +{ + padding: 0; + margin: 0; + font: 15px/150% 'Roboto', sans-serif; + overflow: hidden; + color: var(--text-color); + background-color: var(--main-bg-color); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +img { + max-width: 100%; +} + +ul > li, ol > li { + display: list-item; +} + +h1,h2,h3,h4,h5 +{ + color: var(--link-active-color); + position: relative; +} + +h1, h2 +{ + margin-block-start: 2em; +} + +h3 +{ + margin-block-start: 1em; + font-weight: 300; + font-size: 1.5em; + color: var(--h3-color); + margin-block-start: 3em; +} + +h4 +{ + opacity: 1; + color: var(--h4-color); + font-size: large; + border-bottom: 2px solid var(--separator-color); + margin: 20px 0 0 0; +} + + +h5 { + margin-block-end: .8em; + margin-block-start: 1em; + font-size: .85em; + font-weight: 500; + color: var(--h5-color); +} + +h6 { + font-size: .75em; + margin: 0; +} + +p +{ + font-weight: 400; +} + +ul +{ + position: relative; +} + +ul, ol +{ + padding-inline-start: 3em; +} + +ul.level1 +{ + list-style-type: none; + padding-inline-start: 0; +} + +ul.level2, ul.level3 +{ + padding-inline-start: 1em; + list-style-type: none; + font-size: .9em; +} + +a +{ + color: var(--link-color); + text-decoration: none; + transition: color .25s; +} + +a:focus, a:hover +{ + color: var(--link-hover-color); + text-decoration: underline; +} + +a.anchorjs-link:hover { + text-decoration: none; +} + +a.active, a:active +{ + color: var(--link-active-color); +} + +.body-content +{ + display: flex; + flex-direction: row; + height: 100%; + overflow-x: hidden; + overflow-y: hidden; +} + +.page-title +{ + margin-block-start: 0; +} + +nav +{ + width: 300px; + transition: left .5s ease-out; + position: fixed; + left: -350px; + top: 40px; + bottom: 0; + background-color: var(--sidebar-bg-color); + overflow-y: auto; + + display: flex; + flex-direction: column; + + z-index: 1000; +} + +h1:first-child +{ + margin-block-start: 1.1em; + margin-top: 1.1em; +} + +.sidebar +{ + padding: 32px 17px 32px 32px; + flex: 1; +} + +.sidebar-item +{ + font-size: 1em; + font-weight: 400; + display: block; + padding: 4px 16px; + color: var(--sidebar-item-color); +} + +.sidebar-item.large, #navbar .sidebar-item +{ + padding: 8px 16px; +} + +a.sidebar-item:hover, a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: none; +} + +a.sidebar-item.active +{ + color: var(--link-active-color); +} + +ul.level1 > li > a.sidebar-item +{ + background-color: transparent; + border-radius: 4px; +} + +#toc ul.level1 > li > a.sidebar-item.active +{ + background-color: var(--link-active-bg-color); +} + +.sidebar-item-separator +{ + height: 2px; + width: 100%; + background-color: var(--separator-color); + margin: 2em 0; + opacity: .8; +} + +span.sidebar-item +{ + font-weight: 700; + text-transform: uppercase; + font-size: .8em; + color: var(--text-color); + margin-block-start: 1.25em; +} + +.main-panel +{ + background-color: var(--main-bg-color); + flex: 1; + overflow-y: auto; + padding: 20px 40px; +} + +.top-navbar +{ + display: flex; + flex-direction: row; + align-items: center; + padding: 0 40px; + height: 40px; + background-color: var(--topbar-bg-color); +} + +.burger-icon +{ + margin-right: 1em; + color: var(--button-color); +} + +.burger-icon:hover, .burger-icon:focus +{ + color: var(--link-active-color); +} + +.burger-icon.active, .burger-icon:active +{ + color: var(--link-active-color); +} + +.brand +{ + display: flex; + align-items: center; + justify-content: start; +} + +.logomark +{ + height: 28px; +} + +.brand-title +{ + padding: 0 .5em; + font-size: .9em; + color: var(--link-active-color); +} + +.footer +{ + background-color: var(--footer-bg-color); + padding: 20px; + margin: 0 20px 20px 20px; + border-radius: 8px; + color: var(--link-active-color); +} + +.footer > h4 +{ + margin-block-start: 0; +} + +.blackout +{ + display: block; + visibility: hidden; + position: absolute; + z-index: 100; + top: 40px; + bottom: 0; + left: 0; + right: 0; + background-color: var(--footer-bg-color); +} + +@keyframes showThat { + 0% { opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; } + 100% { opacity: 1; visibility: visible;} +} + +@keyframes hideThat { + 0% { opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; } + 100% { opacity: 0; visibility: hidden;} +} + +.showThat +{ + animation: showThat .5s forwards; +} + +.hideThat +{ + animation: hideThat .5s forwards; +} + + + +@media (min-width: 1024px) +{ + nav + { + position: relative; + left: 0!important; + top: 0; + bottom: 0; + } + + .top-navbar + { + display: none; + } + + .blackout + { + display: none; + } +} + +/* Table */ + +.table-responsive +{ + overflow-x: auto; + margin-bottom: 64px; +} + +table +{ + background-color: var(--code-bg-color); + border-collapse: collapse; + width: 100%; + table-layout: auto; +} + +table.table-striped tbody tr:nth-child(2n) +{ + background-color: var(--table-strip-bg-color); +} + +table thead +{ + background: var(--table-header-bg-color); +} + +table th +{ + color: var(--table-header-color); + text-transform: uppercase; + font-size: 12px; + line-height: 15px; + border-bottom: 1px solid var(--table-header-border-color); + padding: 8px; +} + +.table-condensed th { + text-align: left; +} + +table td +{ + padding: 8px; + font-weight: 300; +} + +table td > p +{ + margin: 0; +} + +/* Alerts */ +.alert { + border-radius: 4px; + padding: 8px; + margin: 25px 0; +} + +.alert > h5 +{ + display: none; + margin: 0; +} + +.alert > p +{ + margin: 0; + font-weight: 300; + font-size: 13px; +} + +.alert.alert-info +{ + border: 2px solid var(--alert-info-border-color); + background: var(--alert-info-bg-color); +} + +.alert.alert-warning +{ + border: 2px solid var(--alert-warning-border-color); + background: var(--alert-warning-bg-color); +} + +.alert.alert-danger +{ + border: 2px solid var(--alert-danger-border-color); + background: var(--alert-danger-bg-color); +} + +.TIP.alert.alert-info +{ + border: 2px solid var(--alert-tip-border-color); + background: var(--alert-tip-bg-color); +} + +blockquote { + margin: 8px 0; + border-left: 4px solid var(--blockquote-border-color); + padding: 8px; + background: var(--blockquote-bg-color); + border-radius: 4px; +} + +blockquote > p { + margin: 0; + font-style: italic; + font-size: 13px; +} + + +/* Breadcrumb */ + +#breadcrumb +{ + padding: 8px 16px; + background: var(--breadcrumb-bg-color); + border-radius: 4px; + margin-bottom: 30px; +} + +#breadcrumb:empty +{ + display: none; +} + +ul.breadcrumb +{ + display: flex; + flex-direction: row; + margin: 0; +} + +ul.breadcrumb > li { + margin-right: 6px; +} + +ul.breadcrumb > li::before +{ + content: "/"; + margin-right: 5px; +} + +ul.breadcrumb > li:first-child::before +{ + content: ""; + margin: 0; +} + + +/* Code */ + +legend, pre +{ + display: block; + background-color: var(--code-bg-color); + padding: 16px; + border-radius: 4px; +} + +code +{ + background-color: var(--code-bg-color); + padding: 2px 4px; + border-radius: 4px; +} + +.hljs +{ + background: transparent; +} + +/* DocFX related */ + +.small { + font-size: .9em; +} + +.pull-right +{ + float: right; +} + +.mobile-hide +{ + visibility: hidden; +} + +@media (min-width: 1024px) +{ + .mobile-hide + { + visibility: visible; + } +} + +li +{ + display: block; + position: relative; +} + +.expand-stub +{ + cursor: pointer; + position: absolute; + width: 20px; + height: 20px; + left: -10px; +} + +ul.level1 > li > .expand-stub +{ + display: none; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before +{ + content: " "; + position: absolute; + transform: rotate(-90deg); + width: 10px; + height: 10px; + top: 5px; + left: 5px; + background-repeat: no-repeat; + background: url(/styles/down-arrow.svg); +} + +.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before +{ + transform: none; +} + +li > ul +{ + display: none; +} + +li.in > ul +{ + display: block; +} + +ul.level2 > li > a.sidebar-item, +ul.level3 > li > a.sidebar-item +{ + font-weight: 500; + font-size: .95em; + padding: 0; + margin: 2px 16px; +} + +ul.level2 > li > a.sidebar-item +{ + color: var(--sidebar-item-2nd-color); +} + +ul.level3 > li > a.sidebar-item +{ + color: var(--sidebar-item-3rd-color); +} + +ul.level2 > li > a.sidebar-item:hover, +ul.level2 > li > a.sidebar-item:focus, +ul.level3 > li > a.sidebar-item:hover, +ul.level3 > li > a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: underline; +} + +ul.level2 > li > a.sidebar-item.active, +ul.level3 > li > a.sidebar-item.active +{ + color: var(--link-active-color); +} + +.inheritance .level0:before, +.inheritance .level1:before, +.inheritance .level2:before, +.inheritance .level3:before, +.inheritance .level4:before, +.inheritance .level5:before { + content: '↳'; + margin-right: 5px; +} + +.inheritance .level0 { + margin-left: 0em; +} + +.inheritance .level1 { + margin-left: 1em; +} + +.inheritance .level2 { + margin-left: 2em; +} + +.inheritance .level3 { + margin-left: 3em; +} + +.inheritance .level4 { + margin-left: 4em; +} + +.inheritance .level5 { + margin-left: 5em; +} \ No newline at end of file diff --git a/docs/templates/discordfx/styles/down-arrow.svg b/docs/templates/discordfx/styles/down-arrow.svg new file mode 100644 index 0000000..e086126 --- /dev/null +++ b/docs/templates/discordfx/styles/down-arrow.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/templates/discordfx/styles/jquery.twbsPagination.js b/docs/templates/discordfx/styles/jquery.twbsPagination.js new file mode 100644 index 0000000..332c01c --- /dev/null +++ b/docs/templates/discordfx/styles/jquery.twbsPagination.js @@ -0,0 +1,317 @@ +/*! + * jQuery pagination plugin v1.4.1 + * http://esimakin.github.io/twbs-pagination/ + * + * Copyright 2014-2016, Eugene Simakin + * Released under Apache 2.0 license + * http://apache.org/licenses/LICENSE-2.0.html + */ +(function ($, window, document, undefined) { + + 'use strict'; + + var old = $.fn.twbsPagination; + + // PROTOTYPE AND CONSTRUCTOR + + var TwbsPagination = function (element, options) { + this.$element = $(element); + this.options = $.extend({}, $.fn.twbsPagination.defaults, options); + + if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { + throw new Error('Start page option is incorrect'); + } + + this.options.totalPages = parseInt(this.options.totalPages); + if (isNaN(this.options.totalPages)) { + throw new Error('Total pages option is not correct!'); + } + + this.options.visiblePages = parseInt(this.options.visiblePages); + if (isNaN(this.options.visiblePages)) { + throw new Error('Visible pages option is not correct!'); + } + + if (this.options.onPageClick instanceof Function) { + this.$element.first().on('page', this.options.onPageClick); + } + + // hide if only one page exists + if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { + this.$element.trigger('page', 1); + return this; + } + + if (this.options.totalPages < this.options.visiblePages) { + this.options.visiblePages = this.options.totalPages; + } + + if (this.options.href) { + this.options.startPage = this.getPageFromQueryString(); + if (!this.options.startPage) { + this.options.startPage = 1; + } + } + + var tagName = (typeof this.$element.prop === 'function') ? + this.$element.prop('tagName') : this.$element.attr('tagName'); + + if (tagName === 'UL') { + this.$listContainer = this.$element; + } else { + this.$listContainer = $('
    '); + } + + this.$listContainer.addClass(this.options.paginationClass); + + if (tagName !== 'UL') { + this.$element.append(this.$listContainer); + } + + if (this.options.initiateStartPageClick) { + this.show(this.options.startPage); + } else { + this.render(this.getPages(this.options.startPage)); + this.setupEvents(); + } + + return this; + }; + + TwbsPagination.prototype = { + + constructor: TwbsPagination, + + destroy: function () { + this.$element.empty(); + this.$element.removeData('twbs-pagination'); + this.$element.off('page'); + + return this; + }, + + show: function (page) { + if (page < 1 || page > this.options.totalPages) { + throw new Error('Page is incorrect.'); + } + this.currentPage = page; + + this.render(this.getPages(page)); + this.setupEvents(); + + this.$element.trigger('page', page); + + return this; + }, + + buildListItems: function (pages) { + var listItems = []; + + if (this.options.first) { + listItems.push(this.buildItem('first', 1)); + } + + if (this.options.prev) { + var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; + listItems.push(this.buildItem('prev', prev)); + } + + for (var i = 0; i < pages.numeric.length; i++) { + listItems.push(this.buildItem('page', pages.numeric[i])); + } + + if (this.options.next) { + var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; + listItems.push(this.buildItem('next', next)); + } + + if (this.options.last) { + listItems.push(this.buildItem('last', this.options.totalPages)); + } + + return listItems; + }, + + buildItem: function (type, page) { + var $itemContainer = $('
  • '), + $itemContent = $(''), + itemText = this.options[type] ? this.makeText(this.options[type], page) : page; + + $itemContainer.addClass(this.options[type + 'Class']); + $itemContainer.data('page', page); + $itemContainer.data('page-type', type); + $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); + + return $itemContainer; + }, + + getPages: function (currentPage) { + var pages = []; + + var half = Math.floor(this.options.visiblePages / 2); + var start = currentPage - half + 1 - this.options.visiblePages % 2; + var end = currentPage + half; + + // handle boundary case + if (start <= 0) { + start = 1; + end = this.options.visiblePages; + } + if (end > this.options.totalPages) { + start = this.options.totalPages - this.options.visiblePages + 1; + end = this.options.totalPages; + } + + var itPage = start; + while (itPage <= end) { + pages.push(itPage); + itPage++; + } + + return {"currentPage": currentPage, "numeric": pages}; + }, + + render: function (pages) { + var _this = this; + this.$listContainer.children().remove(); + var items = this.buildListItems(pages); + jQuery.each(items, function(key, item){ + _this.$listContainer.append(item); + }); + + this.$listContainer.children().each(function () { + var $this = $(this), + pageType = $this.data('page-type'); + + switch (pageType) { + case 'page': + if ($this.data('page') === pages.currentPage) { + $this.addClass(_this.options.activeClass); + } + break; + case 'first': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); + break; + case 'last': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); + break; + case 'prev': + $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); + break; + case 'next': + $this.toggleClass(_this.options.disabledClass, + !_this.options.loop && pages.currentPage === _this.options.totalPages); + break; + default: + break; + } + + }); + }, + + setupEvents: function () { + var _this = this; + this.$listContainer.off('click').on('click', 'li', function (evt) { + var $this = $(this); + if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { + return false; + } + // Prevent click event if href is not set. + !_this.options.href && evt.preventDefault(); + _this.show(parseInt($this.data('page'))); + }); + }, + + makeHref: function (page) { + return this.options.href ? this.generateQueryString(page) : "#"; + }, + + makeText: function (text, page) { + return text.replace(this.options.pageVariable, page) + .replace(this.options.totalPagesVariable, this.options.totalPages) + }, + getPageFromQueryString: function (searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), + page = regex.exec(search); + if (!page || !page[2]) { + return null; + } + page = decodeURIComponent(page[2]); + page = parseInt(page); + if (isNaN(page)) { + return null; + } + return page; + }, + generateQueryString: function (pageNumber, searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); + if (!search) return ''; + return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); + + }, + getSearchString: function (searchStr) { + var search = searchStr || window.location.search; + if (search === '') { + return null; + } + if (search.indexOf('?') === 0) search = search.substr(1); + return search; + } + + }; + + // PLUGIN DEFINITION + + $.fn.twbsPagination = function (option) { + var args = Array.prototype.slice.call(arguments, 1); + var methodReturn; + + var $this = $(this); + var data = $this.data('twbs-pagination'); + var options = typeof option === 'object' ? option : {}; + + if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); + if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); + + return ( methodReturn === undefined ) ? $this : methodReturn; + }; + + $.fn.twbsPagination.defaults = { + totalPages: 1, + startPage: 1, + visiblePages: 5, + initiateStartPageClick: true, + hideOnlyOnePage: false, + href: false, + pageVariable: '{{page}}', + totalPagesVariable: '{{total_pages}}', + page: null, + first: 'First', + prev: 'Previous', + next: 'Next', + last: 'Last', + loop: false, + onPageClick: null, + paginationClass: 'pagination', + nextClass: 'page-item next', + prevClass: 'page-item prev', + lastClass: 'page-item last', + firstClass: 'page-item first', + pageClass: 'page-item', + activeClass: 'active', + disabledClass: 'disabled', + anchorClass: 'page-link' + }; + + $.fn.twbsPagination.Constructor = TwbsPagination; + + $.fn.twbsPagination.noConflict = function () { + $.fn.twbsPagination = old; + return this; + }; + + $.fn.twbsPagination.version = "1.4.1"; + +})(window.jQuery, window, document); diff --git a/docs/templates/discordfx/styles/main.css b/docs/templates/discordfx/styles/main.css new file mode 100644 index 0000000..e69de29 diff --git a/docs/templates/discordfx/styles/main.js b/docs/templates/discordfx/styles/main.js new file mode 100644 index 0000000..b9150b4 --- /dev/null +++ b/docs/templates/discordfx/styles/main.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. + +function toggleMenu() { + + var x = document.getElementById("sidebar"); + var b = document.getElementById("blackout"); + + if (x.style.left === "0px") + { + x.style.left = "-350px"; + b.classList.remove("showThat"); + b.classList.add("hideThat"); + } + else + { + x.style.left = "0px"; + b.classList.remove("hideThat"); + b.classList.add("showThat"); + } +} \ No newline at end of file diff --git a/docs/templates/discordfx/styles/url.min.js b/docs/templates/discordfx/styles/url.min.js new file mode 100644 index 0000000..8057e0a --- /dev/null +++ b/docs/templates/discordfx/styles/url.min.js @@ -0,0 +1 @@ +/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml index c829e93..ab7ff6a 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,6 +1,7 @@ - name: Articles href: articles/ - homepage: articles/intro.md + homepage: articles/index.md + - name: Api Documentation href: api/ homepage: api/index.md diff --git a/src/Alex.MoLang.sln b/src/Alex.MoLang.sln index a20ea21..4392500 100644 --- a/src/Alex.MoLang.sln +++ b/src/Alex.MoLang.sln @@ -4,6 +4,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MolangSharp", "MolangSharp\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MolangSharp.Tests", "MolangSharp.Tests\MolangSharp.Tests.csproj", "{3FF89B16-9D85-4E87-83C7-D937F2ED7F5B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{D73569B7-CCA2-458B-98CE-CC7531D414FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MolangSharp.Examples.Basic", "Examples\MolangSharp.Examples.Basic\MolangSharp.Examples.Basic.csproj", "{30FD4BFD-0792-495D-A6F6-600713011C77}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensibility", "Extensibility", "{EC0FCA2E-4326-4F6A-9210-684F90267012}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MolangSharp.Json.Newtonsoft", "Extensibility\MolangSharp.Json.Newtonsoft\MolangSharp.Json.Newtonsoft.csproj", "{D5E15128-EB40-4263-B9C9-9544F3ACA4CB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +26,17 @@ Global {3FF89B16-9D85-4E87-83C7-D937F2ED7F5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {3FF89B16-9D85-4E87-83C7-D937F2ED7F5B}.Release|Any CPU.ActiveCfg = Release|Any CPU {3FF89B16-9D85-4E87-83C7-D937F2ED7F5B}.Release|Any CPU.Build.0 = Release|Any CPU + {30FD4BFD-0792-495D-A6F6-600713011C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30FD4BFD-0792-495D-A6F6-600713011C77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30FD4BFD-0792-495D-A6F6-600713011C77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30FD4BFD-0792-495D-A6F6-600713011C77}.Release|Any CPU.Build.0 = Release|Any CPU + {D5E15128-EB40-4263-B9C9-9544F3ACA4CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5E15128-EB40-4263-B9C9-9544F3ACA4CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5E15128-EB40-4263-B9C9-9544F3ACA4CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5E15128-EB40-4263-B9C9-9544F3ACA4CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {30FD4BFD-0792-495D-A6F6-600713011C77} = {D73569B7-CCA2-458B-98CE-CC7531D414FA} + {D5E15128-EB40-4263-B9C9-9544F3ACA4CB} = {EC0FCA2E-4326-4F6A-9210-684F90267012} EndGlobalSection EndGlobal diff --git a/src/Examples/MolangSharp.Examples.Basic/BaseEntity.cs b/src/Examples/MolangSharp.Examples.Basic/BaseEntity.cs new file mode 100644 index 0000000..b1042fd --- /dev/null +++ b/src/Examples/MolangSharp.Examples.Basic/BaseEntity.cs @@ -0,0 +1,18 @@ +using System.Diagnostics; +using ConcreteMC.MolangSharp.Attributes; +using ConcreteMC.MolangSharp.Runtime; +using ConcreteMC.MolangSharp.Runtime.Struct; + +namespace MolangSharp.Examples.Basic; + +public class BaseEntity : MoLangEnvironment +{ + private Stopwatch _stopwatch = Stopwatch.StartNew(); + public BaseEntity() + { + Structs["query"] = new InteropStruct(this); + } + + [MoProperty("life_time")] + public double LifeTime => _stopwatch.Elapsed.TotalSeconds; +} \ No newline at end of file diff --git a/src/Examples/MolangSharp.Examples.Basic/MolangSharp.Examples.Basic.csproj b/src/Examples/MolangSharp.Examples.Basic/MolangSharp.Examples.Basic.csproj new file mode 100644 index 0000000..54bc415 --- /dev/null +++ b/src/Examples/MolangSharp.Examples.Basic/MolangSharp.Examples.Basic.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/src/Examples/MolangSharp.Examples.Basic/Program.cs b/src/Examples/MolangSharp.Examples.Basic/Program.cs new file mode 100644 index 0000000..4cca536 --- /dev/null +++ b/src/Examples/MolangSharp.Examples.Basic/Program.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using System.Globalization; +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Runtime; +using MolangSharp.Examples.Basic; + +Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; +Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + +//Create a new Entity which will use Interop to allow for MoLang->C# communication +var myEntity = new BaseEntity(); + +//Initialize the runtime +MoLangRuntime runtime = new MoLangRuntime(myEntity); + +//Parse our MoLang expression +var parsed = MoLangParser.Parse("math.cos(query.life_time * 60) * -45.0"); + +Stopwatch sw = Stopwatch.StartNew(); +while (true) +{ + var value = runtime.Execute(parsed); + Console.WriteLine($"[{sw.Elapsed.ToString("mm\\:ss\\.fff")}] Rotation: {value.AsDouble():F3}"); + Thread.Sleep(100); +} \ No newline at end of file diff --git a/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangExpressionConverter.cs b/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangExpressionConverter.cs new file mode 100644 index 0000000..b70356b --- /dev/null +++ b/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangExpressionConverter.cs @@ -0,0 +1,56 @@ +using System; +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Parser.Expressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MolangSharp.Json.Newtonsoft +{ + /// + /// Implements a JsonConverter for reading + /// + public class MolangExpressionConverter : JsonConverter + { + public MolangExpressionConverter() + { + + } + + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + JToken token = JToken.Load(reader); + if (token.Type == JTokenType.String) + { + string molang = token.Value(); + return MoLangParser.Parse(molang); + } + else if (token.Type == JTokenType.Integer) + { + return new NumberExpression(token.Value()); + } + else if (token.Type == JTokenType.Float) + { + return new NumberExpression(token.Value()); + } + else if (token.Type == JTokenType.Boolean) + { + return new BooleanExpression(token.Value()); + } + + return existingValue; + } + + public override bool CanConvert(Type objectType) + { + return typeof(IExpression).IsAssignableFrom(objectType); + } + } +} \ No newline at end of file diff --git a/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangSharp.Json.Newtonsoft.csproj b/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangSharp.Json.Newtonsoft.csproj new file mode 100644 index 0000000..124ccf8 --- /dev/null +++ b/src/Extensibility/MolangSharp.Json.Newtonsoft/MolangSharp.Json.Newtonsoft.csproj @@ -0,0 +1,35 @@ + + + + netstandard2.1; netcoreapp6.0 + disable + disable + true + ConcreteMC.MolangSharp.Json.Newtonsoft + ConcreteMC.MolangSharp.Json.Newtonsoft + ConcreteMC.MolangSharp.Json.Newtonsoft + Implements a JsonConverter for Molang expressions + Kenny van Vulpen + Kenny van Vulpen + Copyright Kenny van Vulpen 2019-2022 + Minecraft Molang MCPE MoLang bedrock mojang alex json newtonsoft newtonsoft.json + + git + https://github.com/ConcreteMC/MolangSharp.git + https://github.com/ConcreteMC/MolangSharp + + 1.0.0 + + - Initial release + + + + + + + + + + + + diff --git a/src/MolangSharp.Tests/BinaryOpTest.cs b/src/MolangSharp.Tests/BinaryOpTest.cs new file mode 100644 index 0000000..b1fbd83 --- /dev/null +++ b/src/MolangSharp.Tests/BinaryOpTest.cs @@ -0,0 +1,38 @@ +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Runtime; +using ConcreteMC.MolangSharp.Runtime.Value; +using ConcreteMC.MolangSharp.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConcreteMC.MolangSharp.Tests; + +[TestClass] +public class BinaryOpTest +{ + private MoLangRuntime _runtime; + + [TestInitialize] + public void Setup() + { + _runtime = new MoLangRuntime(); + } + + [TestMethod] + public void ZeroReturnsFalse() + { + var parsed = MoLangParser.Parse("return 0 ?? 'test'"); + var result = _runtime.Execute(parsed); + + Assert.AreEqual(result.AsString(), "test"); + } + + [TestMethod] + public void ZeroVariableReturnsFalse() + { + _runtime.Environment.SetValue(new MoPath("variable.lastNumber"), new DoubleValue(0)); + var parsed = MoLangParser.Parse("return variable.lastNumber ?? 'test'"); + var result = _runtime.Execute(parsed); + + Assert.AreEqual(result.AsString(), "test"); + } +} \ No newline at end of file diff --git a/src/MolangSharp.Tests/InteropOptimizationVisitorTest.cs b/src/MolangSharp.Tests/InteropOptimizationVisitorTest.cs new file mode 100644 index 0000000..f1048cd --- /dev/null +++ b/src/MolangSharp.Tests/InteropOptimizationVisitorTest.cs @@ -0,0 +1,95 @@ +using ConcreteMC.MolangSharp.Attributes; +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Parser.Tokenizer; +using ConcreteMC.MolangSharp.Parser.Visitors.InteropOptimization; +using ConcreteMC.MolangSharp.Runtime; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConcreteMC.MolangSharp.Tests; + +[TestClass] +public class InteropOptimizationVisitorTest +{ + [TestMethod] + public void VerifyFunctionCallOptimization() + { + var entity = new TestEntity(); + var parser = new MoLangParser(new TokenIterator("query.getHealth()")); + parser.ExpressionTraverser.Visitors.Add(new InteropOptimizationVisitor(new InteropOptimizationVisitorOptions() + { + InteropEntries = new [] + { + new InteropEntry("query", entity) + } + })); + + var expression = parser.Parse(); + Assert.IsInstanceOfType(expression, typeof(OptimizedFunctionCallExpression)); + + var runtime = new MoLangRuntime(); + var result = runtime.Execute(expression); + Assert.IsNotNull(result); + Assert.AreEqual(20, result.AsDouble()); + } + + [TestMethod] + public void VerifyPropertyAccessOptimization() + { + var entity = new TestEntity(); + var parser = new MoLangParser(new TokenIterator("query.health")); + parser.ExpressionTraverser.Visitors.Add(new InteropOptimizationVisitor(new InteropOptimizationVisitorOptions() + { + InteropEntries = new [] + { + new InteropEntry("query", entity) + } + })); + + var expression = parser.Parse(); + Assert.IsInstanceOfType(expression, typeof(OptimizedNameExpression)); + + var runtime = new MoLangRuntime(); + var result = runtime.Execute(expression); + Assert.IsNotNull(result); + Assert.AreEqual(20, result.AsDouble()); + } + + [TestMethod] + public void VerifyPropertyAssignOptimization() + { + var entity = new TestEntity(); + var parser = new MoLangParser(new TokenIterator("query.health = 21")); + parser.ExpressionTraverser.Visitors.Add(new InteropOptimizationVisitor(new InteropOptimizationVisitorOptions() + { + InteropEntries = new [] + { + new InteropEntry("query", entity) + } + })); + + var expression = parser.Parse(); + Assert.IsInstanceOfType(expression, typeof(OptimizedAssignExpression)); + + var runtime = new MoLangRuntime(); + var result = runtime.Execute(expression); + Assert.IsNotNull(result); + Assert.AreEqual(21, entity.Health); + } + + private class TestEntity + { + [MoProperty("health")] + public double Health { get; set; } + + public TestEntity() + { + Health = 20; + } + + [MoFunction("getHealth")] + public double GetHealth() + { + return Health; + } + } +} \ No newline at end of file diff --git a/src/MolangSharp.Tests/MathOptimizationVisitorTest.cs b/src/MolangSharp.Tests/MathOptimizationVisitorTest.cs new file mode 100644 index 0000000..dfb80b4 --- /dev/null +++ b/src/MolangSharp.Tests/MathOptimizationVisitorTest.cs @@ -0,0 +1,42 @@ +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Parser.Expressions; +using ConcreteMC.MolangSharp.Parser.Tokenizer; +using ConcreteMC.MolangSharp.Parser.Visitors; +using ConcreteMC.MolangSharp.Runtime; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConcreteMC.MolangSharp.Tests; + +[TestClass] +public class MathOptimizationVisitorTest +{ + [TestMethod] + public void Constants() + { + MoLangParser parser = new MoLangParser(new TokenIterator("7 + 2 * (6 + 3) / 3 - 7")); + parser.ExpressionTraverser.Visitors.Add(new MathOptimizationVisitor()); + + var expr = parser.Parse(); + Assert.IsInstanceOfType(expr, typeof(NumberExpression)); + + MoLangRuntime runtime = new MoLangRuntime(); + var result = runtime.Execute(expr); + + Assert.AreEqual(6, result.AsDouble()); + } + + [TestMethod] + public void MathFunctions() + { + MoLangParser parser = new MoLangParser(new TokenIterator("math.floor(10.1 + 20.1)")); + parser.ExpressionTraverser.Visitors.Add(new MathOptimizationVisitor()); + + var expr = parser.Parse(); + Assert.IsInstanceOfType(expr, typeof(NumberExpression)); + + MoLangRuntime runtime = new MoLangRuntime(); + var result = runtime.Execute(expr); + + Assert.AreEqual(30, result.AsDouble()); + } +} \ No newline at end of file diff --git a/src/MolangSharp.Tests/MathTest.cs b/src/MolangSharp.Tests/MathTest.cs index 529fb39..e261aee 100644 --- a/src/MolangSharp.Tests/MathTest.cs +++ b/src/MolangSharp.Tests/MathTest.cs @@ -90,7 +90,7 @@ public void ArithmeticsDivision() [TestMethod] public void OrderOfOperations() { - var runtime = Setup(10, 20); + var runtime = Setup(10, 20); var expr = MoLangParser.Parse("v.b + (7 + 2 * (6 + 3) / 3 - 7) + v.a"); var result = runtime.Execute(expr); diff --git a/src/MolangSharp.Tests/PerformanceTest.cs b/src/MolangSharp.Tests/PerformanceTest.cs new file mode 100644 index 0000000..c9ef392 --- /dev/null +++ b/src/MolangSharp.Tests/PerformanceTest.cs @@ -0,0 +1,192 @@ +using System.Diagnostics; +using ConcreteMC.MolangSharp.Parser; +using ConcreteMC.MolangSharp.Runtime; +using ConcreteMC.MolangSharp.Runtime.Value; +using ConcreteMC.MolangSharp.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ConcreteMC.MolangSharp.Tests; + +[TestClass] +public class PerformanceTest +{ + private Stopwatch _sw; + private MoLangEnvironment _environment; + private MoLangRuntime _runtime; + + [TestInitialize] + public void Init() + { + _sw = new Stopwatch(); + _environment = new MoLangEnvironment(); + _runtime = new MoLangRuntime(_environment); + + _environment.SetValue(new MoPath("variable.test"), DoubleValue.One); + _environment.SetValue(new MoPath("variable.life_time"), DoubleValue.Zero); + + MoLangRuntimeConfiguration.UseDummyValuesInsteadOfExceptions = false; + MoLangRuntimeConfiguration.UseMoLangStackTrace = true; + } + + [TestCleanup] + public void Cleanup() + { + MoLangRuntimeConfiguration.UseMoLangStackTrace = true; + MoLangRuntimeConfiguration.UseDummyValuesInsteadOfExceptions = false; + } + + [TestMethod] + public void TestVariableReadingMath() + { + const int iterations = 5000000; + long total = 0; + + var expression = MoLangParser.Parse("variable.test * 20"); + for (int i = 0; i < iterations; i++) + { + _sw.Restart(); + var a = _runtime.Execute(expression); + total += _sw.ElapsedMilliseconds; + Assert.AreEqual(20, a.AsDouble()); + } + + Debug.WriteLine($"Average: {((double)total / iterations):N10}ms"); + } + + [TestMethod] + public void TestVariableReading() + { + const int iterations = 5000000; + long total = 0; + + var expression = MoLangParser.Parse("variable.test"); + for (int i = 0; i < iterations; i++) + { + _sw.Restart(); + var a = _runtime.Execute(expression); + total += _sw.ElapsedMilliseconds; + Assert.AreEqual(1, a.AsDouble()); + } + + Debug.WriteLine($"Average: {((double)total / iterations):N10}ms"); + } + + [TestMethod] + public void TestVariableWriting() + { + const int iterations = 5000000; + long total = 0; + + var expression = MoLangParser.Parse("variable.test = 20"); + for (int i = 0; i < iterations; i++) + { + _sw.Restart(); + _runtime.Execute(expression); + total += _sw.ElapsedMilliseconds; + } + + Debug.WriteLine($"Average: {((double)total / iterations):N10}ms"); + } + + [TestMethod] + public void TestInvalidPathPerformance() + { + const int iterations = 50000; + long total = 0; + + var expression = MoLangParser.Parse("fr.test"); + for (int i = 0; i < iterations; i++) + { + _sw.Restart(); + + try + { + _runtime.Execute(expression); + } + catch + { + + } + total += _sw.ElapsedMilliseconds; + } + + Debug.WriteLine($"Average: {((double)total / iterations):N10}ms"); + } + + [TestMethod] + public void TestDummyValuesConfiguration() + { + var expression = MoLangParser.Parse("return fr.test"); + MoLangRuntimeConfiguration.UseDummyValuesInsteadOfExceptions = true; + var result = _runtime.Execute(expression); + Assert.AreEqual(0d, result.AsDouble()); + } + + [TestMethod] + public void RawPerformanceTest() + { + var parsed = MoLangParser.Parse("math.cos(v.life_time * 60) * -45.0"); + + RunPerformanceTestAndOutput(parsed); + } + + [TestMethod] + public void InvalidPathPerformanceTest() + { + var parsed = MoLangParser.Parse("math.cos(a.life_time * 60) * -45.0"); + + RunPerformanceTestAndOutput(parsed); + } + + [TestMethod] + public void InvalidPathPerformanceWithoutExceptionThrowingTest() + { + MoLangRuntimeConfiguration.UseDummyValuesInsteadOfExceptions = true; + var parsed = MoLangParser.Parse("math.cos(a.life_time * 60) * -45.0"); + + RunPerformanceTestAndOutput(parsed); + } + + [TestMethod] + public void InvalidPathPerformanceWithoutStackTraceThrowingTest() + { + MoLangRuntimeConfiguration.UseMoLangStackTrace = false; + var parsed = MoLangParser.Parse("math.cos(a.life_time * 60) * -45.0"); + + RunPerformanceTestAndOutput(parsed); + } + + + private void LogOutput(long iterations, long timespent) + { + Debug.WriteLine($"Iterations: {iterations}. Elapsed: {timespent}ms Avg: {timespent / (double)iterations}ms"); + } + + private void RunPerformanceTestAndOutput(IExpression expression) + { + var result = RunPerformanceTest(expression); + LogOutput(result.iterations, result.timeSpent); + } + + private (long iterations, long timeSpent) RunPerformanceTest(IExpression expression) + { + var iterations = 0L; + var sw = Stopwatch.StartNew(); + while (sw.ElapsedMilliseconds < 1000) + { + try + { + var e = _runtime.Execute(expression); + } + catch + { + + } + + iterations++; + } + sw.Stop(); + + return (iterations, sw.ElapsedMilliseconds); + } +} \ No newline at end of file diff --git a/src/MolangSharp/Attributes/MoFunctionAttribute.cs b/src/MolangSharp/Attributes/MoFunctionAttribute.cs index 41c10b8..fdb795f 100644 --- a/src/MolangSharp/Attributes/MoFunctionAttribute.cs +++ b/src/MolangSharp/Attributes/MoFunctionAttribute.cs @@ -2,11 +2,21 @@ namespace ConcreteMC.MolangSharp.Attributes { + /// + /// Identifies a method as a MoLang Accessible function + /// [AttributeUsage(AttributeTargets.Method)] public class MoFunctionAttribute : Attribute { + /// + /// The names this method is exposed via + /// public string[] Name { get; } + /// + /// Identifies a method as a MoLang Accessible function + /// + /// The names this function is callable by in a MoLang expression public MoFunctionAttribute(params string[] functionNames) { Name = functionNames; diff --git a/src/MolangSharp/Attributes/MoObservableAttribute.cs b/src/MolangSharp/Attributes/MoObservableAttribute.cs index c24bf89..4b32317 100644 --- a/src/MolangSharp/Attributes/MoObservableAttribute.cs +++ b/src/MolangSharp/Attributes/MoObservableAttribute.cs @@ -2,9 +2,6 @@ namespace ConcreteMC.MolangSharp.Attributes { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] - public class MoObservableAttribute : Attribute - { - - } + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class MoObservableAttribute : Attribute { } } \ No newline at end of file diff --git a/src/MolangSharp/Attributes/MoPropertyAttribute.cs b/src/MolangSharp/Attributes/MoPropertyAttribute.cs index 9ea000f..74b97a8 100644 --- a/src/MolangSharp/Attributes/MoPropertyAttribute.cs +++ b/src/MolangSharp/Attributes/MoPropertyAttribute.cs @@ -2,11 +2,21 @@ namespace ConcreteMC.MolangSharp.Attributes { + /// + /// Identifies a class property/field as MoLang Accessible + /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] public class MoPropertyAttribute : Attribute { + /// + /// The name of this property as used in a MoLang expression + /// public string Name { get; } + /// + /// Identifies a class property/field as MoLang Accessible + /// + /// The name used for this property. public MoPropertyAttribute(string name) { Name = name; diff --git a/src/MolangSharp/MolangSharp.csproj b/src/MolangSharp/MolangSharp.csproj index a7bc644..546a62d 100644 --- a/src/MolangSharp/MolangSharp.csproj +++ b/src/MolangSharp/MolangSharp.csproj @@ -4,9 +4,9 @@ Debug;Release AnyCPU;x64 true - 1.0.0 + 1.0.3 true - netstandard2.1 + netstandard2.1; netcoreapp6.0 https://github.com/ConcreteMC/MolangSharp https://github.com/ConcreteMC/MolangSharp.git MPL-2.0 @@ -15,25 +15,25 @@ Kenny van Vulpen Copyright Kenny van Vulpen 2019-2022 Minecraft Molang MCPE MoLang bedrock mojang alex - - Added a maths optimization step -- Added documentation for a bunch of classes + + - Fixed an issue with struct accessor ConcreteMC.MolangSharp ConcreteMC.MolangSharp - 1.0.1 + 1.0.3 ConcreteMC.MolangSharp git ConcreteMC.MolangSharp - ConcreteMC.MolangSharp + Alex.MoLang - bin\x64\Debug\netstandard2.1\Alex.MoLang.xml + bin\x64\Debug\netstandard2.1\Alex.MoLang.xml - bin\x64\Release\netstandard2.1\Alex.MoLang.xml + bin\x64\Release\netstandard2.1\Alex.MoLang.xml diff --git a/src/MolangSharp/Parser/Expression.cs b/src/MolangSharp/Parser/Expression.cs new file mode 100644 index 0000000..00b0bcf --- /dev/null +++ b/src/MolangSharp/Parser/Expression.cs @@ -0,0 +1,40 @@ +using System; +using ConcreteMC.MolangSharp.Runtime; +using ConcreteMC.MolangSharp.Runtime.Value; + +namespace ConcreteMC.MolangSharp.Parser +{ + /// + /// An abstract base class for all Expressions + /// + public abstract class Expression : IExpression + { + /// + /// The parameters used by this expression + /// + public IExpression[] Parameters { get; set; } + + /// + public ExpressionMeta Meta { get; } = new ExpressionMeta(); + + /// + /// Create a new instance of the class + /// + /// + /// The parameters used by this expression + /// + protected Expression(params IExpression[] parameters) + { + Parameters = parameters; + } + + /// + public abstract IMoValue Evaluate(MoScope scope, MoLangEnvironment environment); + + /// + public virtual void Assign(MoScope scope, MoLangEnvironment environment, IMoValue value) + { + throw new Exception("Cannot assign a value to " + this.GetType()); + } + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/ExpressionMeta.cs b/src/MolangSharp/Parser/ExpressionMeta.cs new file mode 100644 index 0000000..0eaa6c9 --- /dev/null +++ b/src/MolangSharp/Parser/ExpressionMeta.cs @@ -0,0 +1,72 @@ +using System.Text; +using ConcreteMC.MolangSharp.Parser.Tokenizer; + +namespace ConcreteMC.MolangSharp.Parser +{ + /// + /// Contains metadata about an expression + /// + public class ExpressionMeta + { + /// + /// The token + /// + public Token Token { get; set; } + + /// + /// The parent expression + /// + public IExpression Parent { get; set; } + + /// + /// The previous expression + /// + public IExpression Previous { get; set; } + + /// + /// The next expression + /// + public IExpression Next { get; set; } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(255); + bool includeFileInfoIfAvailable; + + if (Token != null) + { + sb.Append(Token.Text); + includeFileInfoIfAvailable = true; + } + else + { + includeFileInfoIfAvailable = false; + } + + if (includeFileInfoIfAvailable) + { + // sb.Append(" at offset "); + + // if (_nativeOffset == OFFSET_UNKNOWN) + // sb.Append(""); + // else + // sb.Append(_nativeOffset); + + sb.Append(" in file:line:column "); + sb.Append(""); + sb.Append(':'); + sb.Append(Token.Position.LineNumber); + sb.Append(':'); + sb.Append(Token.Position.Index); + } + else + { + sb.Append(""); + } + + sb.AppendLine(); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/ExprTraverser.cs b/src/MolangSharp/Parser/ExpressionTraverser.cs similarity index 77% rename from src/MolangSharp/Parser/ExprTraverser.cs rename to src/MolangSharp/Parser/ExpressionTraverser.cs index 60ac83f..6a86d54 100644 --- a/src/MolangSharp/Parser/ExprTraverser.cs +++ b/src/MolangSharp/Parser/ExpressionTraverser.cs @@ -7,12 +7,23 @@ namespace ConcreteMC.MolangSharp.Parser { - public class ExprTraverser + /// + /// Traverses arrays of with 's + /// + public class ExpressionTraverser { - public readonly List Visitors = new List(); + /// + /// The list of that will visit the traversed expressions + /// + public readonly List Visitors = new List(); private bool _stop = false; + /// + /// Traverse an array of expressions. + /// + /// The array of expressions to visit + /// The traversed array of expressions public IExpression[] Traverse(IExpression[] expressions) { TraverseArray(expressions); @@ -22,7 +33,7 @@ public IExpression[] Traverse(IExpression[] expressions) private void TraverseArray(IExpression[] expressions) { - foreach (IExprVisitor visitor in Visitors) + foreach (IExpressionVisitor visitor in Visitors) { visitor.BeforeTraverse(expressions); } @@ -42,7 +53,7 @@ private void TraverseArray(IExpression[] expressions) } } - foreach (IExprVisitor visitor in Visitors) + foreach (IExpressionVisitor visitor in Visitors) { visitor.AfterTraverse(expressions); } @@ -54,10 +65,11 @@ private IExpression TraverseExpr(IExpression expression, IExpression parent) expression.Meta.Parent = parent; var parameters = expression.Parameters; + for (var index = 0; index < parameters.Length; index++) { parameters[index] = TraverseExpr(parameters[index], expression); - + if (_stop) { break; @@ -71,7 +83,7 @@ private IExpression TraverseExpr(IExpression expression, IExpression parent) return expression; } - internal IExpression Visit(IExpression expression) + private IExpression Visit(IExpression expression) { foreach (var visitor in Visitors) { diff --git a/src/MolangSharp/Parser/ExpressionVisitor.cs b/src/MolangSharp/Parser/ExpressionVisitor.cs new file mode 100644 index 0000000..5199b53 --- /dev/null +++ b/src/MolangSharp/Parser/ExpressionVisitor.cs @@ -0,0 +1,20 @@ +namespace ConcreteMC.MolangSharp.Parser +{ + /// + /// An abstract class that can be used as a base for any ExpressionVisitor + /// + public abstract class ExpressionVisitor : IExpressionVisitor + { + /// + public virtual void BeforeTraverse(IExpression[] expressions) { } + + /// + public abstract IExpression OnVisit(ExpressionTraverser traverser, IExpression expression); + + /// + public virtual void OnLeave(IExpression expression) { } + + /// + public virtual void AfterTraverse(IExpression[] expressions) { } + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/Expressions/ArrayAccessExpression.cs b/src/MolangSharp/Parser/Expressions/ArrayAccessExpression.cs index 2fa1d1c..b980fc6 100644 --- a/src/MolangSharp/Parser/Expressions/ArrayAccessExpression.cs +++ b/src/MolangSharp/Parser/Expressions/ArrayAccessExpression.cs @@ -10,15 +10,14 @@ public class ArrayAccessExpression : Expression public IExpression Array => Parameters[0]; public IExpression Index => Parameters[1]; - public ArrayAccessExpression(IExpression array, IExpression index) : base(array, index) - { - } + public ArrayAccessExpression(IExpression array, IExpression index) : base(array, index) { } /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) { var index = (int) Index.Evaluate(scope, environment).AsDouble(); MoPath path; + if (Array is NameExpression nameExpression) { var p = nameExpression.Name; @@ -31,9 +30,10 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) } var array = environment.GetValue(path); + if (array is ArrayStruct asArray) return asArray[index]; - + return environment.GetValue(path); } @@ -41,8 +41,9 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) public override void Assign(MoScope scope, MoLangEnvironment environment, IMoValue value) { var index = (int) Index.Evaluate(scope, environment).AsDouble(); - + MoPath path; + if (Array is NameExpression nameExpression) { var p = nameExpression.Name; @@ -53,8 +54,9 @@ public override void Assign(MoScope scope, MoLangEnvironment environment, IMoVal var eval = Array.Evaluate(scope, environment); path = new MoPath($"{eval.AsString()}"); } - + var array = environment.GetValue(path); + if (array is ArrayStruct asArray) { asArray[index] = value; diff --git a/src/MolangSharp/Parser/Expressions/AssignExpression.cs b/src/MolangSharp/Parser/Expressions/AssignExpression.cs index 2a702b0..a795a85 100644 --- a/src/MolangSharp/Parser/Expressions/AssignExpression.cs +++ b/src/MolangSharp/Parser/Expressions/AssignExpression.cs @@ -8,10 +8,7 @@ public class AssignExpression : Expression public IExpression Variable => Parameters[0]; public IExpression Expression => Parameters[1]; - public AssignExpression(IExpression variable, IExpression expr) : base(variable, expr) - { - - } + public AssignExpression(IExpression variable, IExpression expr) : base(variable, expr) { } /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) diff --git a/src/MolangSharp/Parser/Expressions/BinaryOp/CoalesceExpression.cs b/src/MolangSharp/Parser/Expressions/BinaryOp/CoalesceExpression.cs index b08ad22..23c9c2d 100644 --- a/src/MolangSharp/Parser/Expressions/BinaryOp/CoalesceExpression.cs +++ b/src/MolangSharp/Parser/Expressions/BinaryOp/CoalesceExpression.cs @@ -13,9 +13,10 @@ public CoalesceExpression(IExpression l, IExpression r) : base(l, r) { } public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) { IMoValue evalLeft = Left.Evaluate(scope, environment); - IMoValue value = environment.GetValue(new MoPath(evalLeft.AsString())); - if (value == null || !value.AsBool()) + //IMoValue value = environment.GetValue(new MoPath(evalLeft.AsString())); + + if (evalLeft == null || !evalLeft.AsBool()) { return Right.Evaluate(scope, environment); } diff --git a/src/MolangSharp/Parser/Expressions/BinaryOp/PlusExpression.cs b/src/MolangSharp/Parser/Expressions/BinaryOp/PlusExpression.cs index fc125bb..6ce086e 100644 --- a/src/MolangSharp/Parser/Expressions/BinaryOp/PlusExpression.cs +++ b/src/MolangSharp/Parser/Expressions/BinaryOp/PlusExpression.cs @@ -20,7 +20,7 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) } catch (Exception ex) { - throw new MoLangRuntimeException("An unexpected error occured.", ex); + throw new MoLangRuntimeException(this, "An unexpected error occured.", ex); } } diff --git a/src/MolangSharp/Parser/Expressions/BinaryOpExpression.cs b/src/MolangSharp/Parser/Expressions/BinaryOpExpression.cs index fbc2c30..fe40521 100644 --- a/src/MolangSharp/Parser/Expressions/BinaryOpExpression.cs +++ b/src/MolangSharp/Parser/Expressions/BinaryOpExpression.cs @@ -17,9 +17,7 @@ public IExpression Right set { Parameters[1] = value; } } - protected BinaryOpExpression(IExpression l, IExpression r) : base(l,r) - { - } + protected BinaryOpExpression(IExpression l, IExpression r) : base(l, r) { } public abstract string GetSigil(); } diff --git a/src/MolangSharp/Parser/Expressions/ForEachExpression.cs b/src/MolangSharp/Parser/Expressions/ForEachExpression.cs index f3cc2fe..011a93a 100644 --- a/src/MolangSharp/Parser/Expressions/ForEachExpression.cs +++ b/src/MolangSharp/Parser/Expressions/ForEachExpression.cs @@ -11,10 +11,8 @@ public class ForEachExpression : Expression public IExpression Array => Parameters[1]; public IExpression Body => Parameters[2]; - public ForEachExpression(IExpression variable, IExpression array, IExpression body) : base(variable, array, body) - { - - } + public ForEachExpression(IExpression variable, IExpression array, IExpression body) : base( + variable, array, body) { } /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) diff --git a/src/MolangSharp/Parser/Expressions/FuncCallExpression.cs b/src/MolangSharp/Parser/Expressions/FuncCallExpression.cs index 78c0fad..4a419a5 100644 --- a/src/MolangSharp/Parser/Expressions/FuncCallExpression.cs +++ b/src/MolangSharp/Parser/Expressions/FuncCallExpression.cs @@ -7,6 +7,7 @@ namespace ConcreteMC.MolangSharp.Parser.Expressions public class FuncCallExpression : Expression { public MoPath Name { get; set; } + public FuncCallExpression(MoPath name, IExpression[] args) : base(args) { Name = name; @@ -16,7 +17,7 @@ public FuncCallExpression(MoPath name, IExpression[] args) : base(args) public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) { //List p = Args.ToList(); - MoPath name = Name;/* Name is NameExpression expression ? expression.Name : + MoPath name = Name; /* Name is NameExpression expression ? expression.Name : new MoPath(Name.Evaluate(scope, environment).ToString());*/ IMoValue[] arguments = new IMoValue[Parameters.Length]; diff --git a/src/MolangSharp/Parser/Expressions/LoopExpression.cs b/src/MolangSharp/Parser/Expressions/LoopExpression.cs index c31e082..537e4e7 100644 --- a/src/MolangSharp/Parser/Expressions/LoopExpression.cs +++ b/src/MolangSharp/Parser/Expressions/LoopExpression.cs @@ -8,18 +8,13 @@ public class LoopExpression : Expression public IExpression Count => Parameters[0]; public IExpression Body => Parameters[1]; - public LoopExpression(IExpression count, IExpression body) : base(count, body) - { - } + public LoopExpression(IExpression count, IExpression body) : base(count, body) { } /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) { - int loop = (int)(double)Count.Evaluate(scope, environment).Value; - MoScope subScope = new MoScope(scope.Runtime) - { - Runtime = scope.Runtime - }; + int loop = (int) Count.Evaluate(scope, environment).AsDouble(); + MoScope subScope = new MoScope(scope.Runtime) {Runtime = scope.Runtime}; while (loop > 0) { diff --git a/src/MolangSharp/Parser/Expressions/NumberExpression.cs b/src/MolangSharp/Parser/Expressions/NumberExpression.cs index ce9b270..9dc6990 100644 --- a/src/MolangSharp/Parser/Expressions/NumberExpression.cs +++ b/src/MolangSharp/Parser/Expressions/NumberExpression.cs @@ -18,7 +18,7 @@ public NumberExpression(double value) : base() { _value = new DoubleValue(value); } - + public NumberExpression(IMoValue value) : base() { _value = value; diff --git a/src/MolangSharp/Parser/Expressions/ReturnExpression.cs b/src/MolangSharp/Parser/Expressions/ReturnExpression.cs index 758e410..4c33305 100644 --- a/src/MolangSharp/Parser/Expressions/ReturnExpression.cs +++ b/src/MolangSharp/Parser/Expressions/ReturnExpression.cs @@ -15,8 +15,6 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) } /// - public ReturnExpression(IExpression value) : base(value) - { - } + public ReturnExpression(IExpression value) : base(value) { } } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/Expressions/ScriptExpression.cs b/src/MolangSharp/Parser/Expressions/ScriptExpression.cs index 9c6fa96..ab68147 100644 --- a/src/MolangSharp/Parser/Expressions/ScriptExpression.cs +++ b/src/MolangSharp/Parser/Expressions/ScriptExpression.cs @@ -5,42 +5,39 @@ namespace ConcreteMC.MolangSharp.Parser.Expressions { - public class ScriptExpression : Expression - { - public ScriptExpression(IExpression[] expressions) : base(expressions) - { - - } - - public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) - { - IMoValue result = DoubleValue.Zero; - // MoScope scope = new MoScope(this); - - foreach (IExpression expression in Parameters) - { - if (expression == null) - continue; + public class ScriptExpression : Expression + { + public ScriptExpression(IExpression[] expressions) : base(expressions) { } - try - { - result = expression.Evaluate(scope, environment); + public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) + { + IMoValue result = DoubleValue.Zero; + // MoScope scope = new MoScope(this); - if (scope.ReturnValue != null) - { - result = scope.ReturnValue; + foreach (IExpression expression in Parameters) + { + if (expression == null) + continue; - break; - } - } - catch (Exception ex) - { - throw new MoLangRuntimeException( - expression, "An error occured while evaluating the expression", ex); - } - } + try + { + result = expression.Evaluate(scope, environment); - return result; - } - } + if (scope.ReturnValue != null) + { + result = scope.ReturnValue; + + break; + } + } + catch (Exception ex) + { + throw new MoLangRuntimeException( + expression, "An error occured while evaluating the expression", ex); + } + } + + return result; + } + } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/Expressions/StatementExpression.cs b/src/MolangSharp/Parser/Expressions/StatementExpression.cs index 8846830..ea9d452 100644 --- a/src/MolangSharp/Parser/Expressions/StatementExpression.cs +++ b/src/MolangSharp/Parser/Expressions/StatementExpression.cs @@ -26,8 +26,6 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) } /// - public StatementExpression(IExpression[] value) : base(value) - { - } + public StatementExpression(IExpression[] value) : base(value) { } } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/Expressions/TernaryExpression.cs b/src/MolangSharp/Parser/Expressions/TernaryExpression.cs index f774290..78ed88f 100644 --- a/src/MolangSharp/Parser/Expressions/TernaryExpression.cs +++ b/src/MolangSharp/Parser/Expressions/TernaryExpression.cs @@ -9,10 +9,8 @@ public class TernaryExpression : Expression public IExpression ThenExpr => Parameters[1]; public IExpression ElseExpr => Parameters[2]; - public TernaryExpression(IExpression condition, IExpression thenExpr, IExpression elseExpr) : base(condition, thenExpr, elseExpr) - { - - } + public TernaryExpression(IExpression condition, IExpression thenExpr, IExpression elseExpr) : base( + condition, thenExpr, elseExpr) { } /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) diff --git a/src/MolangSharp/Parser/Expressions/UnaryMinusExpression.cs b/src/MolangSharp/Parser/Expressions/UnaryMinusExpression.cs index a9affdf..8e84b1c 100644 --- a/src/MolangSharp/Parser/Expressions/UnaryMinusExpression.cs +++ b/src/MolangSharp/Parser/Expressions/UnaryMinusExpression.cs @@ -12,9 +12,6 @@ public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) } /// - public UnaryMinusExpression(IExpression value) : base(value) - { - - } + public UnaryMinusExpression(IExpression value) : base(value) { } } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/Expressions/UnaryPlusExpression.cs b/src/MolangSharp/Parser/Expressions/UnaryPlusExpression.cs index e2e0852..47e0863 100644 --- a/src/MolangSharp/Parser/Expressions/UnaryPlusExpression.cs +++ b/src/MolangSharp/Parser/Expressions/UnaryPlusExpression.cs @@ -10,7 +10,7 @@ public UnaryPlusExpression(IExpression value) : base(value) { //_value = value; } - + /// public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) { diff --git a/src/MolangSharp/Parser/IExprVisitor.cs b/src/MolangSharp/Parser/IExprVisitor.cs deleted file mode 100644 index a83bb03..0000000 --- a/src/MolangSharp/Parser/IExprVisitor.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace ConcreteMC.MolangSharp.Parser -{ - public interface IExprVisitor - { - void BeforeTraverse(IExpression[] expressions); - - IExpression OnVisit(ExprTraverser traverser, IExpression expression); - - void OnLeave(IExpression expression); - - void AfterTraverse(IExpression[] expressions); - } - - public abstract class ExprVisitor : IExprVisitor - { - /// - public virtual void BeforeTraverse(IExpression[] expressions) { } - - /// - public abstract IExpression OnVisit(ExprTraverser traverser, IExpression expression); - - /// - public virtual void OnLeave(IExpression expression) { } - - /// - public virtual void AfterTraverse(IExpression[] expressions) { } - } -} \ No newline at end of file diff --git a/src/MolangSharp/Parser/IExpression.cs b/src/MolangSharp/Parser/IExpression.cs index e5895c6..a846f59 100644 --- a/src/MolangSharp/Parser/IExpression.cs +++ b/src/MolangSharp/Parser/IExpression.cs @@ -1,93 +1,40 @@ -using System; -using System.Text; -using ConcreteMC.MolangSharp.Parser.Tokenizer; using ConcreteMC.MolangSharp.Runtime; using ConcreteMC.MolangSharp.Runtime.Value; namespace ConcreteMC.MolangSharp.Parser { + /// + /// The interface for all expressions to implement + /// public interface IExpression { + /// + /// Contains metadata about this expression + /// ExpressionMeta Meta { get; } + /// + /// Evaluate the expression + /// + /// The current scope + /// The environment provided by the + /// The value returned by the expression IMoValue Evaluate(MoScope scope, MoLangEnvironment environment); + /// + /// Invoked when trying to assign a value to a property/field + /// + /// The current scope + /// The environment provided by the + /// The value to assign void Assign(MoScope scope, MoLangEnvironment environment, IMoValue value); + /// + /// The parameters used by this expression + /// IExpression[] Parameters { get; set; } } - public class ExpressionMeta - { - public Token Token { get; set; } - public IExpression Parent { get; set; } - public IExpression Previous { get; set; } - public IExpression Next { get; set; } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(255); - bool includeFileInfoIfAvailable; - - if (Token != null) - { - sb.Append(Token.Text); - includeFileInfoIfAvailable = true; - } - else - { - includeFileInfoIfAvailable = false; - } - - if (includeFileInfoIfAvailable) - { - // sb.Append(" at offset "); - - // if (_nativeOffset == OFFSET_UNKNOWN) - // sb.Append(""); - // else - // sb.Append(_nativeOffset); - - sb.Append(" in file:line:column "); - sb.Append(""); - sb.Append(':'); - sb.Append(Token.Position.LineNumber); - sb.Append(':'); - sb.Append(Token.Position.Index); - } - else - { - sb.Append(""); - } - - sb.AppendLine(); - - return sb.ToString(); - } - } - - public abstract class Expression : IExpression - { - public IExpression[] Parameters { get; set; } - - /// - public ExpressionMeta Meta { get; } = new ExpressionMeta(); - - public Expression(params IExpression[] parameters) - { - Parameters = parameters; - } - - /// - public abstract IMoValue Evaluate(MoScope scope, MoLangEnvironment environment); - - /// - public virtual void Assign(MoScope scope, MoLangEnvironment environment, IMoValue value) - { - throw new Exception("Cannot assign a value to " + this.GetType()); - } - } - public abstract class Expression : Expression { protected Expression(T value) : base() diff --git a/src/MolangSharp/Parser/IExpressionVisitor.cs b/src/MolangSharp/Parser/IExpressionVisitor.cs new file mode 100644 index 0000000..a295ef0 --- /dev/null +++ b/src/MolangSharp/Parser/IExpressionVisitor.cs @@ -0,0 +1,38 @@ +namespace ConcreteMC.MolangSharp.Parser +{ + /// + /// The base interface for expression visitors. + /// + /// + /// Expression visitors are mainly used to optimize arrays of + /// One such example is the + /// + public interface IExpressionVisitor + { + /// + /// Invoked before any ExpressionVisitor has traversed expression array + /// + /// The expressions to be traversed + void BeforeTraverse(IExpression[] expressions); + + /// + /// Invoked once for every + /// + /// The that invoked the visitor + /// The expressions to visit + /// The visited expression + IExpression OnVisit(ExpressionTraverser traverser, IExpression expression); + + /// + /// Invoked when all ExpressionVisitors have visited an expression + /// + /// The expressions that has been visited + void OnLeave(IExpression expression); + + /// + /// Invoked after all of the ExpressionVisitors have traversed an expression array + /// + /// The visited expression array + void AfterTraverse(IExpression[] expressions); + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/InfixParselet.cs b/src/MolangSharp/Parser/InfixParselet.cs index fa00019..d4176ce 100644 --- a/src/MolangSharp/Parser/InfixParselet.cs +++ b/src/MolangSharp/Parser/InfixParselet.cs @@ -13,9 +13,4 @@ public InfixParselet(Precedence precedence) public abstract IExpression Parse(MoLangParser parser, Token token, IExpression leftExpr); } - - public abstract class PrefixParselet - { - public abstract IExpression Parse(MoLangParser parser, Token token); - } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/MoLangParser.cs b/src/MolangSharp/Parser/MoLangParser.cs index 52a6fa5..364bb04 100644 --- a/src/MolangSharp/Parser/MoLangParser.cs +++ b/src/MolangSharp/Parser/MoLangParser.cs @@ -17,7 +17,7 @@ namespace ConcreteMC.MolangSharp.Parser public class MoLangParser { public delegate MoLangParser ParserFactory(ITokenIterator iterator); - + /// /// The factory method used to provide instances of the /// @@ -69,18 +69,19 @@ static MoLangParser() InfixParselets.Add(TokenType.Arrow, new GenericBinaryOpParselet(Precedence.Arrow)); InfixParselets.Add(TokenType.Assign, new AssignParselet()); } - + /// /// The expression traverser executed after the parsing of the expressions /// - public ExprTraverser ExprTraverser { get; } - + public ExpressionTraverser ExpressionTraverser { get; } + private readonly ITokenIterator _tokenIterator; private readonly List _readTokens = new List(); + public MoLangParser(ITokenIterator iterator) { _tokenIterator = iterator; - ExprTraverser = new ExprTraverser(); + ExpressionTraverser = new ExpressionTraverser(); } /// @@ -110,7 +111,8 @@ public IExpression Parse() } } while (MatchToken(TokenType.Semicolon)); - var result = ExprTraverser.Traverse(exprs.ToArray()); + var result = ExpressionTraverser.Traverse(exprs.ToArray()); + if (result.Length > 1) { return new ScriptExpression(result); @@ -156,7 +158,7 @@ internal IExpression ParseExpression(Precedence precedence) IExpression expr = parselet.Parse(this, token); InitExpr(expr, token); - + return ParseInfixExpression(expr, precedence); } @@ -215,16 +217,19 @@ private Precedence GetPrecedence() internal bool TryParseArgs(out IExpression[] expressions) { expressions = null; - + if (!MatchToken(TokenType.BracketLeft)) return false; + if (MatchToken(TokenType.BracketRight)) { expressions = Array.Empty(); + return true; } - + List args = new List(); + do { args.Add(ParseExpression()); @@ -233,6 +238,7 @@ internal bool TryParseArgs(out IExpression[] expressions) ConsumeToken(TokenType.BracketRight); expressions = args.ToArray(); + return true; } @@ -264,6 +270,7 @@ internal MoPath FixNameShortcut(MoPath name) } name.SetValue(first); + return name; // String.Join(".", splits); } @@ -275,6 +282,7 @@ private Token ConsumeToken() internal Token ConsumeToken(TokenType expectedType) { _tokenIterator.Step(); + if (!TryReadToken(expectedType, out var token)) { throw new MoLangParserException( @@ -300,7 +308,7 @@ internal bool MatchToken(TokenType expectedType, bool consume) { if (!TryReadToken(expectedType, out _)) return false; - + if (consume) ConsumeToken(); @@ -310,6 +318,7 @@ internal bool MatchToken(TokenType expectedType, bool consume) private bool TryReadToken(TokenType expectedType, out Token result) { result = ReadToken(); + if (result == null || (expectedType != null && !result.Type.Equals(expectedType))) { return false; @@ -317,7 +326,7 @@ private bool TryReadToken(TokenType expectedType, out Token result) return true; } - + private Token ReadToken() { return ReadToken(0); @@ -342,12 +351,12 @@ public static IExpression Parse(string input) { return Factory(new TokenIterator(input)).Parse(); } - + private static MoLangParser DefaultFactory(ITokenIterator iterator) { var parser = new MoLangParser(iterator); - parser.ExprTraverser.Visitors.Add(new MathOptimizationVisitor()); - + parser.ExpressionTraverser.Visitors.Add(new MathOptimizationVisitor()); + return parser; } } diff --git a/src/MolangSharp/Parser/Parselet/ArrayAccessParselet.cs b/src/MolangSharp/Parser/Parselet/ArrayAccessParselet.cs index bdb474e..444b852 100644 --- a/src/MolangSharp/Parser/Parselet/ArrayAccessParselet.cs +++ b/src/MolangSharp/Parser/Parselet/ArrayAccessParselet.cs @@ -3,6 +3,12 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the indexer/array accessor parser + /// + /// + /// Parses expressions such as "array[0]" + /// public class ArrayAccessParselet : InfixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/AssignParselet.cs b/src/MolangSharp/Parser/Parselet/AssignParselet.cs index fce99bf..a820902 100644 --- a/src/MolangSharp/Parser/Parselet/AssignParselet.cs +++ b/src/MolangSharp/Parser/Parselet/AssignParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "=" parselet + /// public class AssignParselet : InfixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/BooleanNotParselet.cs b/src/MolangSharp/Parser/Parselet/BooleanNotParselet.cs index bda4151..cd5e2e2 100644 --- a/src/MolangSharp/Parser/Parselet/BooleanNotParselet.cs +++ b/src/MolangSharp/Parser/Parselet/BooleanNotParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "!=" parselet + /// public class BooleanNotParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/BooleanParselet.cs b/src/MolangSharp/Parser/Parselet/BooleanParselet.cs index a4a5b92..7bf2ea4 100644 --- a/src/MolangSharp/Parser/Parselet/BooleanParselet.cs +++ b/src/MolangSharp/Parser/Parselet/BooleanParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the boolean parselet + /// public class BooleanParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/BracketScopeParselet.cs b/src/MolangSharp/Parser/Parselet/BracketScopeParselet.cs index b318ba7..de2f45f 100644 --- a/src/MolangSharp/Parser/Parselet/BracketScopeParselet.cs +++ b/src/MolangSharp/Parser/Parselet/BracketScopeParselet.cs @@ -4,6 +4,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the scope parselet + /// public class BracketScopeParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/BreakParselet.cs b/src/MolangSharp/Parser/Parselet/BreakParselet.cs index e8cb6df..cd50097 100644 --- a/src/MolangSharp/Parser/Parselet/BreakParselet.cs +++ b/src/MolangSharp/Parser/Parselet/BreakParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "break" instruction parser + /// public class BreakParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/ContinueParselet.cs b/src/MolangSharp/Parser/Parselet/ContinueParselet.cs index 1f8b073..7725f18 100644 --- a/src/MolangSharp/Parser/Parselet/ContinueParselet.cs +++ b/src/MolangSharp/Parser/Parselet/ContinueParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "continue" instruction parser + /// public class ContinueParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/FloatParselet.cs b/src/MolangSharp/Parser/Parselet/FloatParselet.cs index 3b8223b..dbd79dd 100644 --- a/src/MolangSharp/Parser/Parselet/FloatParselet.cs +++ b/src/MolangSharp/Parser/Parselet/FloatParselet.cs @@ -1,15 +1,22 @@ +using System.Globalization; using ConcreteMC.MolangSharp.Parser.Exceptions; using ConcreteMC.MolangSharp.Parser.Expressions; using ConcreteMC.MolangSharp.Parser.Tokenizer; namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements Float parsing + /// public class FloatParselet : PrefixParselet { + private const NumberStyles NumberStyle = System.Globalization.NumberStyles.AllowDecimalPoint; + private static readonly CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; + /// public override IExpression Parse(MoLangParser parser, Token token) { - if (float.TryParse(token.Text, out var result)) + if (float.TryParse(token.Text, NumberStyle, Culture, out var result)) { return new NumberExpression(result); } diff --git a/src/MolangSharp/Parser/Parselet/ForEachParselet.cs b/src/MolangSharp/Parser/Parselet/ForEachParselet.cs index 75d0a40..4332474 100644 --- a/src/MolangSharp/Parser/Parselet/ForEachParselet.cs +++ b/src/MolangSharp/Parser/Parselet/ForEachParselet.cs @@ -4,14 +4,18 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "foreach" instruction parser + /// public class ForEachParselet : PrefixParselet { /// public override IExpression Parse(MoLangParser parser, Token token) { if (!parser.TryParseArgs(out var expressions) || expressions.Length != 3) - throw new MoLangParserException($"ForEach: Expected 3 argument, {(expressions?.Length ?? 0)} argument given"); - + throw new MoLangParserException( + $"ForEach: Expected 3 argument, {(expressions?.Length ?? 0)} argument given"); + return new ForEachExpression(expressions[0], expressions[1], expressions[2]); } } diff --git a/src/MolangSharp/Parser/Parselet/GenericBinaryOpParselet.cs b/src/MolangSharp/Parser/Parselet/GenericBinaryOpParselet.cs index b346384..869ed07 100644 --- a/src/MolangSharp/Parser/Parselet/GenericBinaryOpParselet.cs +++ b/src/MolangSharp/Parser/Parselet/GenericBinaryOpParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Generic binary operator parselet + /// public class GenericBinaryOpParselet : InfixParselet { /// @@ -24,7 +27,7 @@ public override IExpression Parse(MoLangParser parser, Token token, IExpression if (token.Type.Equals(TokenType.Coalesce)) return new CoalesceExpression(leftExpr, rightExpr); - + if (token.Type.Equals(TokenType.Slash)) return new DivideExpression(leftExpr, rightExpr); diff --git a/src/MolangSharp/Parser/Parselet/GroupParselet.cs b/src/MolangSharp/Parser/Parselet/GroupParselet.cs index 40eac26..7de6aa2 100644 --- a/src/MolangSharp/Parser/Parselet/GroupParselet.cs +++ b/src/MolangSharp/Parser/Parselet/GroupParselet.cs @@ -2,6 +2,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "group" parselet + /// public class GroupParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/LoopParselet.cs b/src/MolangSharp/Parser/Parselet/LoopParselet.cs index f30966b..3a03198 100644 --- a/src/MolangSharp/Parser/Parselet/LoopParselet.cs +++ b/src/MolangSharp/Parser/Parselet/LoopParselet.cs @@ -4,14 +4,18 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "loop" instruction parser + /// public class LoopParselet : PrefixParselet { /// public override IExpression Parse(MoLangParser parser, Token token) { if (!parser.TryParseArgs(out var expressions) || expressions.Length != 2) - throw new MoLangParserException($"Loop: Expected 2 argument, {(expressions?.Length ?? 0)} argument given"); - + throw new MoLangParserException( + $"Loop: Expected 2 argument, {(expressions?.Length ?? 0)} argument given"); + return new LoopExpression(expressions[0], expressions[1]); } } diff --git a/src/MolangSharp/Parser/Parselet/NameParselet.cs b/src/MolangSharp/Parser/Parselet/NameParselet.cs index 48f075f..0cd2e86 100644 --- a/src/MolangSharp/Parser/Parselet/NameParselet.cs +++ b/src/MolangSharp/Parser/Parselet/NameParselet.cs @@ -4,6 +4,13 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "name" parser + /// + /// + /// Used to parse function calls or property accessors. + /// For example: "query.frame_time" or "math.min(10, 20)" + /// public class NameParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/NumberParselet.cs b/src/MolangSharp/Parser/Parselet/NumberParselet.cs index 4edba8f..f33b297 100644 --- a/src/MolangSharp/Parser/Parselet/NumberParselet.cs +++ b/src/MolangSharp/Parser/Parselet/NumberParselet.cs @@ -5,6 +5,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements number parsing + /// public class NumberParselet : PrefixParselet { private const NumberStyles NumberStyle = System.Globalization.NumberStyles.AllowDecimalPoint; @@ -13,7 +16,7 @@ public class NumberParselet : PrefixParselet /// public override IExpression Parse(MoLangParser parser, Token token) { - if (double.TryParse(token.Text, out var result)) + if (double.TryParse(token.Text, NumberStyle, Culture, out var result)) { return new NumberExpression(result); } diff --git a/src/MolangSharp/Parser/Parselet/ReturnParselet.cs b/src/MolangSharp/Parser/Parselet/ReturnParselet.cs index 60f1dc7..2b15bc2 100644 --- a/src/MolangSharp/Parser/Parselet/ReturnParselet.cs +++ b/src/MolangSharp/Parser/Parselet/ReturnParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "return" instruction parser + /// public class ReturnParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/StringParselet.cs b/src/MolangSharp/Parser/Parselet/StringParselet.cs index 16d196e..65c2e38 100644 --- a/src/MolangSharp/Parser/Parselet/StringParselet.cs +++ b/src/MolangSharp/Parser/Parselet/StringParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the string parser + /// public class StringParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/TernaryParselet.cs b/src/MolangSharp/Parser/Parselet/TernaryParselet.cs index 22d8187..9c94b72 100644 --- a/src/MolangSharp/Parser/Parselet/TernaryParselet.cs +++ b/src/MolangSharp/Parser/Parselet/TernaryParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the Ternary expression parser + /// public class TernaryParselet : InfixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/ThisParselet.cs b/src/MolangSharp/Parser/Parselet/ThisParselet.cs index 7aef8a9..1f3b606 100644 --- a/src/MolangSharp/Parser/Parselet/ThisParselet.cs +++ b/src/MolangSharp/Parser/Parselet/ThisParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the "this" instruction parser + /// public class ThisParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/UnaryMinusParselet.cs b/src/MolangSharp/Parser/Parselet/UnaryMinusParselet.cs index ccfef17..7a43345 100644 --- a/src/MolangSharp/Parser/Parselet/UnaryMinusParselet.cs +++ b/src/MolangSharp/Parser/Parselet/UnaryMinusParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the unary minus parser + /// public class UnaryMinusParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/Parselet/UnaryPlusParselet.cs b/src/MolangSharp/Parser/Parselet/UnaryPlusParselet.cs index 90c4715..a8f9160 100644 --- a/src/MolangSharp/Parser/Parselet/UnaryPlusParselet.cs +++ b/src/MolangSharp/Parser/Parselet/UnaryPlusParselet.cs @@ -3,6 +3,9 @@ namespace ConcreteMC.MolangSharp.Parser.Parselet { + /// + /// Implements the unary plus parser + /// public class UnaryPlusParselet : PrefixParselet { /// diff --git a/src/MolangSharp/Parser/PrefixParselet.cs b/src/MolangSharp/Parser/PrefixParselet.cs new file mode 100644 index 0000000..2620c98 --- /dev/null +++ b/src/MolangSharp/Parser/PrefixParselet.cs @@ -0,0 +1,9 @@ +using ConcreteMC.MolangSharp.Parser.Tokenizer; + +namespace ConcreteMC.MolangSharp.Parser +{ + public abstract class PrefixParselet + { + public abstract IExpression Parse(MoLangParser parser, Token token); + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/Tokenizer/ITokenIterator.cs b/src/MolangSharp/Parser/Tokenizer/ITokenIterator.cs index e1c5dfb..7c381e9 100644 --- a/src/MolangSharp/Parser/Tokenizer/ITokenIterator.cs +++ b/src/MolangSharp/Parser/Tokenizer/ITokenIterator.cs @@ -1,8 +1,9 @@ namespace ConcreteMC.MolangSharp.Parser.Tokenizer { - public interface ITokenIterator - { - Token Next(); - void Step(); - } + public interface ITokenIterator + { + Token Next(); + + void Step(); + } } \ No newline at end of file diff --git a/src/MolangSharp/Parser/Tokenizer/Token.cs b/src/MolangSharp/Parser/Tokenizer/Token.cs index 306d09a..e25230c 100644 --- a/src/MolangSharp/Parser/Tokenizer/Token.cs +++ b/src/MolangSharp/Parser/Tokenizer/Token.cs @@ -1,11 +1,30 @@ namespace ConcreteMC.MolangSharp.Parser.Tokenizer { + /// + /// A token is a piece of code in an expression + /// public class Token { - public TokenType Type; - public string Text; - public TokenPosition Position; + /// + /// The type of this token + /// + public TokenType Type { get; } + /// + /// The piece of code defining this token + /// + public string Text { get; } + + /// + /// The position in the source expression that this token was found at + /// + public TokenPosition Position { get; } + + /// + /// Initializes a new token + /// + /// + /// public Token(TokenType tokenType, TokenPosition position) { this.Type = tokenType; @@ -13,6 +32,12 @@ public Token(TokenType tokenType, TokenPosition position) this.Position = position; } + /// + /// Initializes a new token + /// + /// + /// + /// public Token(TokenType tokenType, string text, TokenPosition position) { this.Type = tokenType; diff --git a/src/MolangSharp/Parser/Tokenizer/TokenPosition.cs b/src/MolangSharp/Parser/Tokenizer/TokenPosition.cs index ae6a8f0..b3ba135 100644 --- a/src/MolangSharp/Parser/Tokenizer/TokenPosition.cs +++ b/src/MolangSharp/Parser/Tokenizer/TokenPosition.cs @@ -1,13 +1,23 @@ namespace ConcreteMC.MolangSharp.Parser.Tokenizer { + /// + /// Describes the position of a + /// public class TokenPosition { // public int StartLineNumber; //public int EndLineNumber; //public int StartColumn; // public int EndColumn; - public int LineNumber; - public int Index; + /// + /// The linenumber this token was found at + /// + public int LineNumber { get; } + + /// + /// The character index of this token + /// + public int Index { get; } public TokenPosition(int lastStepLine, int currentLine, int lastStep, int index) { diff --git a/src/MolangSharp/Parser/Tokenizer/TokenType.cs b/src/MolangSharp/Parser/Tokenizer/TokenType.cs index 2faf1d6..c65924d 100644 --- a/src/MolangSharp/Parser/Tokenizer/TokenType.cs +++ b/src/MolangSharp/Parser/Tokenizer/TokenType.cs @@ -48,7 +48,14 @@ public sealed class TokenType private static int _typeCounter = 0; + /// + /// A string "identifying" this tokentype + /// public string Symbol { get; } + + /// + /// A human readable identifier for this tokentype + /// public string TypeName { get; } private readonly int _typeId; @@ -69,6 +76,11 @@ private TokenType(string symbol, string typename = "") This, True, False, String, Number, Name, Eof }; + /// + /// Finds the TokenType by symbol + /// + /// The symbol to lookup + /// The resulting TokenType or null if no match has been found public static TokenType BySymbol(string symbol) { foreach (TokenType tokenType in TokenType.Values) @@ -82,11 +94,21 @@ public static TokenType BySymbol(string symbol) return null; } + /// + /// Finds the TokenType by symbol + /// + /// The symbol to lookup + /// The resulting TokenType or null if no match has been found public static TokenType BySymbol(char symbol) { return BySymbol(symbol.ToString()); } + /// + /// Checks if this tokentype is equal to + /// + /// + /// public bool Equals(TokenType other) { return _typeId == other._typeId; diff --git a/src/MolangSharp/Parser/Visitors/ExprConnectingVisitor.cs b/src/MolangSharp/Parser/Visitors/ExpressionConnectingVisitor.cs similarity index 86% rename from src/MolangSharp/Parser/Visitors/ExprConnectingVisitor.cs rename to src/MolangSharp/Parser/Visitors/ExpressionConnectingVisitor.cs index f34055f..6f20f87 100644 --- a/src/MolangSharp/Parser/Visitors/ExprConnectingVisitor.cs +++ b/src/MolangSharp/Parser/Visitors/ExpressionConnectingVisitor.cs @@ -1,6 +1,6 @@ namespace ConcreteMC.MolangSharp.Parser.Visitors { - public class ExprConnectingVisitor : ExprVisitor + public class ExpressionConnectingVisitor : ExpressionVisitor { //private LinkedList Stack { get; set; } = new LinkedList(); @@ -17,7 +17,7 @@ public override void BeforeTraverse(IExpression[] expressions) } /// - public override IExpression OnVisit(ExprTraverser traverser, IExpression expression) + public override IExpression OnVisit(ExpressionTraverser traverser, IExpression expression) { var previous = _last; expression.Meta.Previous = previous; diff --git a/src/MolangSharp/Parser/Visitors/FindingVisitor.cs b/src/MolangSharp/Parser/Visitors/FindingVisitor.cs index b0c2e40..ddaaa58 100644 --- a/src/MolangSharp/Parser/Visitors/FindingVisitor.cs +++ b/src/MolangSharp/Parser/Visitors/FindingVisitor.cs @@ -3,7 +3,7 @@ namespace ConcreteMC.MolangSharp.Parser.Visitors { - public class FindingVisitor : ExprVisitor + public class FindingVisitor : ExpressionVisitor { private Predicate _predicate; public List FoundExpressions = new List(); @@ -14,7 +14,7 @@ public FindingVisitor(Predicate predicate) } /// - public override IExpression OnVisit(ExprTraverser traverser, IExpression expression) + public override IExpression OnVisit(ExpressionTraverser traverser, IExpression expression) { if (_predicate(expression)) { diff --git a/src/MolangSharp/Parser/Visitors/FirstFindingVisitor.cs b/src/MolangSharp/Parser/Visitors/FirstFindingVisitor.cs index ffaebed..03fa026 100644 --- a/src/MolangSharp/Parser/Visitors/FirstFindingVisitor.cs +++ b/src/MolangSharp/Parser/Visitors/FirstFindingVisitor.cs @@ -2,7 +2,7 @@ namespace ConcreteMC.MolangSharp.Parser.Visitors { - public class FirstFindingVisitor : ExprVisitor + public class FirstFindingVisitor : ExpressionVisitor { private Predicate _predicate; public IExpression Found = null; @@ -13,7 +13,7 @@ public FirstFindingVisitor(Predicate predicate) } /// - public override IExpression OnVisit(ExprTraverser traverser, IExpression expression) + public override IExpression OnVisit(ExpressionTraverser traverser, IExpression expression) { if (_predicate(expression)) { diff --git a/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitor.cs b/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitor.cs new file mode 100644 index 0000000..dc4c315 --- /dev/null +++ b/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitor.cs @@ -0,0 +1,206 @@ +using System; +using System.Linq; +using ConcreteMC.MolangSharp.Parser.Expressions; +using ConcreteMC.MolangSharp.Runtime; +using ConcreteMC.MolangSharp.Runtime.Struct.Interop; +using ConcreteMC.MolangSharp.Runtime.Value; +using ConcreteMC.MolangSharp.Utils; + +namespace ConcreteMC.MolangSharp.Parser.Visitors.InteropOptimization +{ + /// + /// Optimizes interop calls to call the methods or properties directly instead of going through the MoLang query system. + /// + /// + /// This is a very simple optimization that can be done to improve performance, it does come with some limitations however.
    + /// These limitations are:
    + /// - You need to pass in the object instance to the visitor options, this is because the visitor cannot know what object you are trying to access.
    + /// - You cannot access properties or functions that are not directly on the object, for example: query.myThing.blabla will not work, but query.blabla will work.
    + ///
    + public class InteropOptimizationVisitor : ExpressionVisitor + { + private readonly InteropOptimizationVisitorOptions _options; + + public InteropOptimizationVisitor(InteropOptimizationVisitorOptions options) + { + options.Validate(); + + _options = options; + } + + /// + public override IExpression OnVisit(ExpressionTraverser traverser, IExpression expression) + { + switch (expression) + { + case FuncCallExpression functionCall: + return _options.OptimizeFunctionCalls ? TryOptimizeFunctionCall(functionCall) : functionCall; + + case NameExpression nameExpression: + return _options.OptimizeVariableAccess ? TryOptimizeNameExpression(nameExpression) : nameExpression; + + case AssignExpression assignExpression: + return _options.OptimizeVariableAssignment ? TryOptimizeAssignExpression(assignExpression) : assignExpression; + + default: + return expression; + } + } + + private IExpression TryOptimizeAssignExpression(AssignExpression expression) + { + //Optimize variable assignment to call the method directly. + var variableToAccess = expression.Variable; + + if (variableToAccess is NameExpression nameExpression) + { + var name = nameExpression.Name.Value; + var interopEntry = _options.InteropEntries.FirstOrDefault(x => x.Name == name); + + if (interopEntry == null) + return expression; + + if (TryGetProperty(interopEntry.Cache, nameExpression.Name.Next, out var valueAccessor)) + { + return new OptimizedAssignExpression(interopEntry.Instance, valueAccessor, expression.Expression); + } + } + + return expression; + } + + private IExpression TryOptimizeNameExpression(NameExpression expression) + { + //Optimize variable access to call the method directly. + + var name = expression.Name.Value; + var interopEntry = _options.InteropEntries.FirstOrDefault(x => x.Name == name); + + if (interopEntry == null) + return expression; + + if (TryGetProperty(interopEntry.Cache, expression.Name.Next, out var valueAccessor)) + return new OptimizedNameExpression(interopEntry.Instance, valueAccessor); + + return expression; + } + + private IExpression TryOptimizeFunctionCall(FuncCallExpression expression) + { + //Optimize function call to call the method directly. + var name = expression.Name.Value; + var interopEntry = _options.InteropEntries.FirstOrDefault(x => x.Name == name); + + if (interopEntry == null) + return expression; + + if (TryGetFunction(interopEntry.Cache, expression.Name.Next, out var function)) + return new OptimizedFunctionCallExpression(interopEntry.Instance, function, expression.Parameters); + + return expression; + } + + private bool TryGetFunction(PropertyCache cache, MoPath path, out Func function) + { + if (path.HasChildren) + { + //Possibly trying to access another level in, this is not currently supported. + function = null; + return false; + } + + if (cache.Functions.TryGetValue(path.Value, out var f)) + { + function = f; + return true; + } + + function = null; + return false; + } + + private bool TryGetProperty(PropertyCache cache, MoPath path, out ValueAccessor valueAccessor) + { + if (path.HasChildren) + { + //Possibly trying to access another level in, this is not currently supported. + valueAccessor = null; + return false; + } + + if (cache.Properties.TryGetValue(path.Value, out var f)) + { + valueAccessor = f; + return true; + } + + valueAccessor = null; + return false; + } + } + + public class OptimizedAssignExpression : Expression + { + private readonly object _instance; + private readonly ValueAccessor _valueAccessor; + private readonly IExpression _expressionExpression; + + public OptimizedAssignExpression(object instance, ValueAccessor valueAccessor, IExpression expressionExpression) + { + _instance = instance; + _valueAccessor = valueAccessor; + _expressionExpression = expressionExpression; + } + + /// + public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) + { + _valueAccessor.Set(_instance, _expressionExpression.Evaluate(scope, environment)); + + return DoubleValue.Zero; + } + } + + public class OptimizedFunctionCallExpression : Expression + { + private readonly object _instance; + private readonly Func _function; + + public OptimizedFunctionCallExpression(object instance, Func function, IExpression[] args) : base(args) + { + _instance = instance; + _function = function; + } + + /// + public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) + { + var arguments = new IMoValue[Parameters.Length]; + + for (int i = 0; i < arguments.Length; i++) + { + arguments[i] = Parameters[i].Evaluate(scope, environment); + } + + return _function.Invoke(_instance, new MoParams(arguments)); + } + } + + public class OptimizedNameExpression : Expression + { + private readonly object _instance; + private readonly ValueAccessor _valueAccessor; + + public OptimizedNameExpression(object instance, ValueAccessor valueAccessor) : base() + { + _instance = instance; + _valueAccessor = valueAccessor; + } + + /// + public override IMoValue Evaluate(MoScope scope, MoLangEnvironment environment) + { + return _valueAccessor.Get(_instance); + } + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitorOptions.cs b/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitorOptions.cs new file mode 100644 index 0000000..327a690 --- /dev/null +++ b/src/MolangSharp/Parser/Visitors/InteropOptimization/InteropOptimizationVisitorOptions.cs @@ -0,0 +1,56 @@ +using System; +using ConcreteMC.MolangSharp.Runtime.Struct.Interop; + +namespace ConcreteMC.MolangSharp.Parser.Visitors.InteropOptimization +{ + public class InteropEntry + { + public string Name { get; } + public PropertyCache Cache { get; } + public object Instance { get; } + + /// + /// + /// + /// The name of the struct + /// The struct instance + public InteropEntry(string name, object instance) + { + Name = name; + Cache = new PropertyCache(instance.GetType()); + Instance = instance; + } + } + + public class InteropOptimizationVisitorOptions + { + public InteropEntry[] InteropEntries { get; set; } = Array.Empty(); + + /// + /// Optimize variable access (MoProperty) + /// + public bool OptimizeVariableAccess { get; set; } = true; + + /// + /// Optimize variable assignment (MoProperty) + /// + public bool OptimizeVariableAssignment { get; set; } = true; + + /// + /// Optimize function calls (MoFunction) + /// + public bool OptimizeFunctionCalls { get; set; } = true; + + internal void Validate() + { + if (InteropEntries == null) + throw new ArgumentNullException(nameof(InteropEntries)); + + if (InteropEntries.Length == 0) + throw new ArgumentException("InteropEntries cannot be empty", nameof(InteropEntries)); + + if (!OptimizeVariableAccess && !OptimizeVariableAssignment && !OptimizeFunctionCalls) + throw new ArgumentException("At least one optimization must be enabled", nameof(OptimizeVariableAccess)); + } + } +} \ No newline at end of file diff --git a/src/MolangSharp/Parser/Visitors/MathOptimizationVisitor.cs b/src/MolangSharp/Parser/Visitors/MathOptimizationVisitor.cs index 9bfe8c5..13965f3 100644 --- a/src/MolangSharp/Parser/Visitors/MathOptimizationVisitor.cs +++ b/src/MolangSharp/Parser/Visitors/MathOptimizationVisitor.cs @@ -1,42 +1,73 @@ -using ConcreteMC.MolangSharp.Parser.Expressions; +using System; +using System.Linq; +using ConcreteMC.MolangSharp.Parser.Expressions; using ConcreteMC.MolangSharp.Runtime; namespace ConcreteMC.MolangSharp.Parser.Visitors { - /// - /// Optimizes expressions by pre-calculating static maths - /// - public class MathOptimizationVisitor : ExprVisitor - { - private MoScope _scope = new MoScope(new MoLangRuntime()); - private MoLangEnvironment _environment = new MoLangEnvironment(); - - public override IExpression OnVisit(ExprTraverser traverser, IExpression expression) - { - if (expression is BinaryOpExpression binaryOp) - { - return TryOptimize(binaryOp); - } - - return expression; - } - - private IExpression TryOptimize(BinaryOpExpression expression) - { - if (expression.Left is BinaryOpExpression l) - expression.Left = TryOptimize(l); - - if (expression.Right is BinaryOpExpression r) - expression.Right = TryOptimize(r); - - if (expression.Left is NumberExpression && expression.Right is NumberExpression) - { - //Can be pre-calculated! - var eval = expression.Evaluate(_scope, _environment); - return new NumberExpression(eval); - } - - return expression; - } - } + /// + /// Optimizes expressions by pre-calculating constant maths + /// + public class MathOptimizationVisitor : ExpressionVisitor + { + private static MoScope _scope = new MoScope(new MoLangRuntime()); + private static MoLangEnvironment _environment = new MoLangEnvironment(); + + public override IExpression OnVisit(ExpressionTraverser traverser, IExpression expression) + { + return Visit(expression); + } + + private static IExpression Visit(IExpression expression) + { + if (expression is FuncCallExpression nameExpression && nameExpression.Name.Value.Equals( + "math", StringComparison.InvariantCultureIgnoreCase)) + { + return TryOptimizeMathFunction(nameExpression); + } + + if (expression is BinaryOpExpression binaryOp) + { + return TryOptimize(binaryOp); + } + + return expression; + } + + private static IExpression TryOptimizeMathFunction(FuncCallExpression expression) + { + for (int i = 0; i < expression.Parameters.Length; i++) + { + expression.Parameters[i] = Visit(expression.Parameters[i]); + } + + if (expression.Parameters.All(x => x is NumberExpression)) + { + var eval = expression.Evaluate(_scope, _environment); + + return new NumberExpression(eval); + } + + return expression; + } + + private static IExpression TryOptimize(BinaryOpExpression expression) + { + if (expression.Left is BinaryOpExpression l) + expression.Left = TryOptimize(l); + + if (expression.Right is BinaryOpExpression r) + expression.Right = TryOptimize(r); + + if (expression.Left is NumberExpression && expression.Right is NumberExpression) + { + //Can be pre-calculated! + var eval = expression.Evaluate(_scope, _environment); + + return new NumberExpression(eval); + } + + return expression; + } + } } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/Exceptions/MissingQueryMethodException.cs b/src/MolangSharp/Runtime/Exceptions/MissingQueryMethodException.cs deleted file mode 100644 index 44fc44f..0000000 --- a/src/MolangSharp/Runtime/Exceptions/MissingQueryMethodException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using ConcreteMC.MolangSharp.Parser; - -namespace ConcreteMC.MolangSharp.Runtime.Exceptions -{ - public class MissingQueryMethodException : MoLangRuntimeException - { - /// - public MissingQueryMethodException(string message, Exception baseException) : base(message, baseException) { } - - /// - public MissingQueryMethodException(IExpression expression, string message, Exception baseException) : base( - expression, message, baseException) { } - } -} \ No newline at end of file diff --git a/src/MolangSharp/Runtime/Exceptions/MoLangRuntimeException.cs b/src/MolangSharp/Runtime/Exceptions/MoLangRuntimeException.cs index bf66caf..9637e28 100644 --- a/src/MolangSharp/Runtime/Exceptions/MoLangRuntimeException.cs +++ b/src/MolangSharp/Runtime/Exceptions/MoLangRuntimeException.cs @@ -4,38 +4,64 @@ namespace ConcreteMC.MolangSharp.Runtime.Exceptions { + /// + /// Represents an error that occured during the execution of a MoLang expression + /// public class MoLangRuntimeException : Exception { + /// + /// Contains a trace to where the exception occured in a MoLang expression. + /// public string MolangTrace { get; } + /// + /// Initializes a new instance of the MoLangRuntimeException class with a specified error message. + /// + /// The error message + public MoLangRuntimeException(string message) : this(message, null) { } + + /// + /// Initializes a new instance of the MoLangRuntimeException class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message + /// A reference to the inner exception that is the cause of this exception public MoLangRuntimeException(string message, Exception baseException) : base(message, baseException) { MolangTrace = "Unknown"; } + /// + /// Initializes a new instance of the MoLangRuntimeException class with a reference to the expression that the error occured at, an error message and a reference to the inner exception that is the cause of this exception. + /// + /// The expression that this exception occured at + /// The error message + /// A reference to the inner exception that is the cause of this exception public MoLangRuntimeException(IExpression expression, string message, Exception baseException) : base( message, baseException) { - StringBuilder sb = new StringBuilder(); - - do + if (MoLangRuntimeConfiguration.UseMoLangStackTrace) { - if (expression.Meta?.Token?.Position != null) + StringBuilder sb = new StringBuilder(); + + do { - var token = expression.Meta.Token; - var tokenPosition = token.Position; + if (expression.Meta?.Token?.Position != null) + { + var token = expression.Meta.Token; + var tokenPosition = token.Position; - sb.Append( - $"at <{tokenPosition.LineNumber}:{tokenPosition.Index}> near {token.Type.TypeName} \"{token.Text}\""); - //var frame = new StackFrame(null, tokenPosition.LineNumber, tokenPosition.Index); + sb.Append( + $"at <{tokenPosition.LineNumber}:{tokenPosition.Index}> near {token.Type.TypeName} \"{token.Text}\""); + //var frame = new StackFrame(null, tokenPosition.LineNumber, tokenPosition.Index); - // frames.Add(frame); - } + // frames.Add(frame); + } - expression = expression.Meta.Parent; - } while (expression?.Meta?.Parent != null); + expression = expression.Meta.Parent; + } while (expression?.Meta?.Parent != null); - MolangTrace = sb.ToString(); + MolangTrace = sb.ToString(); + } //st.GetFrames() } diff --git a/src/MolangSharp/Runtime/MoLangEnvironment.cs b/src/MolangSharp/Runtime/MoLangEnvironment.cs index c31e53d..67c41c0 100644 --- a/src/MolangSharp/Runtime/MoLangEnvironment.cs +++ b/src/MolangSharp/Runtime/MoLangEnvironment.cs @@ -7,18 +7,39 @@ namespace ConcreteMC.MolangSharp.Runtime { + /// + /// Provides an execution environment for the + /// + /// + /// An example of a MoLangEnvironment would be a minecraft entity. + /// public class MoLangEnvironment : IMoValue { /// public object Value => Structs; - public Dictionary Structs { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - + /// + /// The available root paths + /// + /// + /// Contains the following root structs by default:
    + /// math.
    + /// temp.
    + /// variable.
    + /// array.
    + /// context. + ///
    + public Dictionary Structs { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + /// /// The value that should be returned when an expression tries to access "this" /// public IMoValue ThisVariable { get; set; } = DoubleValue.Zero; + /// + /// Creates a new instance of the MoLangEnvironment class + /// public MoLangEnvironment() { Structs.TryAdd("math", MoLangMath.Library); @@ -29,44 +50,61 @@ public MoLangEnvironment() Structs.TryAdd("context", new ContextStruct()); } - public IMoValue GetValue(MoPath name) + /// + /// Get a MoLang variable by it's path + /// + /// The path to access + /// The variable at specified path + public IMoValue GetValue(MoPath path) { - return GetValue(name, MoParams.Empty); + return GetValue(path, MoParams.Empty); } - public IMoValue GetValue(MoPath name, MoParams param) + /// + /// Get a MoLang variable by it's path and specified parameters + /// + /// The path to access + /// The variable at specified path + public IMoValue GetValue(MoPath path, MoParams param) { - try - { - return Structs[name.Value].Get(name.Next, param); - } - catch (Exception ex) + if (!Structs.TryGetValue(path.Value, out var v)) { - throw new MoLangRuntimeException($"Cannot retrieve struct: {name}", ex); + if (MoLangRuntimeConfiguration.UseDummyValuesInsteadOfExceptions) + return DoubleValue.Zero; + + throw new MoLangRuntimeException($"Invalid path: {path.Path}"); } + + return v.Get(path.Next, param); } - public void SetValue(MoPath name, IMoValue value) + /// + /// Set a MoLang variable by its path + /// + /// + /// + /// + public void SetValue(MoPath path, IMoValue value) { - if (!Structs.TryGetValue(name.Value, out var v)) + if (!Structs.TryGetValue(path.Value, out var v)) { - throw new MoLangRuntimeException($"Invalid path: {name.Path}", null); + throw new MoLangRuntimeException($"Invalid path: {path.Path}", null); } - + try { - v.Set(name.Next, value); + v.Set(path.Next, value); } catch (Exception ex) { - throw new MoLangRuntimeException($"Cannot set value on struct: {name}", ex); + throw new MoLangRuntimeException($"Cannot set value on struct: {path}", ex); } } /// public bool Equals(IMoValue b) { - return Equals((object)b); + return Equals((object) b); } } } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/MoLangMath.cs b/src/MolangSharp/Runtime/MoLangMath.cs index ae51e8f..6d05361 100644 --- a/src/MolangSharp/Runtime/MoLangMath.cs +++ b/src/MolangSharp/Runtime/MoLangMath.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using ConcreteMC.MolangSharp.Attributes; using ConcreteMC.MolangSharp.Runtime.Struct; +using ConcreteMC.MolangSharp.Runtime.Value; namespace ConcreteMC.MolangSharp.Runtime { @@ -15,35 +17,33 @@ public static class MoLangMath public static readonly QueryStruct Library = new QueryStruct( new Dictionary>(StringComparer.OrdinalIgnoreCase) { - { "abs", param => Math.Abs(param.GetDouble(0)) }, - { "acos", param => Math.Acos(param.GetDouble(0)) }, - { "sin", param => Math.Sin(param.GetDouble(0) * (Math.PI / 180d)) }, - { "asin", param => Math.Asin(param.GetDouble(0)) }, - { "atan", param => Math.Atan(param.GetDouble(0)) }, - { "atan2", param => Math.Atan2(param.GetDouble(0), param.GetDouble(1)) }, - { "ceil", param => Math.Ceiling(param.GetDouble(0)) }, - { - "clamp", param => Math.Min(param.GetDouble(1), Math.Max(param.GetDouble(0), param.GetDouble(2))) - }, - { "cos", param => Math.Cos(param.GetDouble(0) * (Math.PI / 180d)) }, - { "die_roll", param => DieRoll(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2)) }, - { "die_roll_integer", param => DieRollInt(param.GetInt(0), param.GetInt(1), param.GetInt(2)) }, - { "exp", param => Math.Exp(param.GetDouble(0)) }, - { "mod", param => param.GetDouble(0) % param.GetDouble(1) }, - { "floor", param => Math.Floor(param.GetDouble(0)) }, - { "hermite_blend", param => HermiteBlend(param.GetInt(0)) }, - { "lerp", param => Lerp(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2)) }, - { "lerp_rotate", param => LerpRotate(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2)) }, - { "ln", param => Math.Log(param.GetDouble(0)) }, - { "max", param => Math.Max(param.GetDouble(0), param.GetDouble(1)) }, - { "min", param => Math.Min(param.GetDouble(0), param.GetDouble(1)) }, - { "pi", param => Math.PI }, - { "pow", param => Math.Pow(param.GetDouble(0), param.GetDouble(1)) }, - { "random", param => Random(param.GetDouble(0), param.GetDouble(1)) }, - { "random_integer", param => RandomInt(param.GetInt(0), param.GetInt(1)) }, - { "round", param => Math.Round(param.GetDouble(0)) }, - { "sqrt", param => Math.Sqrt(param.GetDouble(0)) }, - { "trunc", param => Math.Floor(param.GetDouble(0)) }, + {"abs", param => Math.Abs(param.GetDouble(0))}, + {"acos", param => Math.Acos(param.GetDouble(0))}, + {"sin", param => Math.Sin(param.GetDouble(0) * (Math.PI / 180d))}, + {"asin", param => Math.Asin(param.GetDouble(0))}, + {"atan", param => Math.Atan(param.GetDouble(0))}, + {"atan2", param => Math.Atan2(param.GetDouble(0), param.GetDouble(1))}, + {"ceil", param => Math.Ceiling(param.GetDouble(0))}, + {"clamp", param => Math.Min(param.GetDouble(1), Math.Max(param.GetDouble(0), param.GetDouble(2)))}, + {"cos", param => Math.Cos(param.GetDouble(0) * (Math.PI / 180d))}, + {"die_roll", param => DieRoll(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2))}, + {"die_roll_integer", param => DieRollInt(param.GetInt(0), param.GetInt(1), param.GetInt(2))}, + {"exp", param => Math.Exp(param.GetDouble(0))}, + {"mod", param => param.GetDouble(0) % param.GetDouble(1)}, + {"floor", param => Math.Floor(param.GetDouble(0))}, + {"hermite_blend", param => HermiteBlend(param.GetInt(0))}, + {"lerp", param => Lerp(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2))}, + {"lerp_rotate", param => LerpRotate(param.GetDouble(0), param.GetDouble(1), param.GetDouble(2))}, + {"ln", param => Math.Log(param.GetDouble(0))}, + {"max", param => Math.Max(param.GetDouble(0), param.GetDouble(1))}, + {"min", param => Math.Min(param.GetDouble(0), param.GetDouble(1))}, + {"pi", param => Math.PI}, + {"pow", param => Math.Pow(param.GetDouble(0), param.GetDouble(1))}, + {"random", param => Random(param.GetDouble(0), param.GetDouble(1))}, + {"random_integer", param => RandomInt(param.GetInt(0), param.GetInt(1))}, + {"round", param => Math.Round(param.GetDouble(0))}, + {"sqrt", param => Math.Sqrt(param.GetDouble(0))}, + {"trunc", param => Math.Floor(param.GetDouble(0))}, }); public static double Random(double low, double high) @@ -111,4 +111,146 @@ public static double Radify(double num) return (((num + 180) % 360) + 180) % 360; } } + + /// + /// The default Math implementations for the MoLang runtime + /// + public sealed class MoLangMathImpl + { + private static IMoStruct _instance; + public static IMoStruct Library => _instance ??= new InteropStruct(new MoLangMathImpl()); + + private MoLangMathImpl() { } + + [MoFunction("abs")] + public double Abs(double value) => Math.Abs(value); + + [MoFunction("sin")] + public double Sin(double value) => Math.Sin(value * (Math.PI / 180d)); + + [MoFunction("asin")] + public double Asin(double value) => Math.Asin(value); + + [MoFunction("cos")] + public double Cos(double value) => Math.Cos(value * (Math.PI / 180d)); + + [MoFunction("acos")] + public double Acos(double value) => Math.Acos(value); + + [MoFunction("atan")] + public double Atan(double value) => Math.Atan(value); + + [MoFunction("atan2")] + public double Atan2(double y, double x) => Math.Atan2(y, x); + + [MoFunction("ceil")] + public double Ceiling(double value) => Math.Ceiling(value); + + [MoFunction("clamp")] + public double Clamp(double value, double min, double max) => Math.Clamp(value, min, max); + + [MoFunction("die_roll")] + public double DieRoll(double num, double low, double high) + { + int i = 0; + double total = 0; + while (i++ < num) total += Random(low, high); + + return total; + } + + [MoFunction("die_roll_integer")] + public int DieRollInt(int num, int low, int high) + { + int i = 0; + int total = 0; + while (i++ < num) total += RandomInt(low, high); + + return total; + } + + [MoFunction("exp")] + public double Exp(double value) => Math.Exp(value); + + [MoFunction("mod")] + public double Modulus(double x, double y) => x % y; + + [MoFunction("floor")] + public double Floor(double value) => Math.Floor(value); + + [MoFunction("hermite_blend")] + public int HermiteBlend(int value) => (3 * value) ^ (2 - 2 * value) ^ 3; + + [MoFunction("lerp")] + public double Lerp(double start, double end, double amount) + { + amount = Math.Max(0, Math.Min(1, amount)); + + return start + (end - start) * amount; + } + + [MoFunction("lerp_rotate")] + public double LerpRotate(double start, double end, double amount) + { + start = Radify(start); + end = Radify(end); + + if (start > end) + { + (start, end) = (end, start); + } + + if (end - start > 180) + { + return Radify(end + amount * (360 - (end - start))); + } + + return start + amount * (end - start); + } + + [MoFunction("ln")] + public double Log(double value) => Math.Log(value); + + [MoFunction("max")] + public double Max(double value1, double value2) => Math.Max(value1, value2); + + [MoFunction("min")] + public double Min(double value1, double value2) => Math.Min(value1, value2); + + [MoFunction("pi")] + public double PiFunc() => Math.PI; + + [MoProperty("pi")] public double PI => Math.PI; + + [MoFunction("pow")] + public double Pow(double x, double y) => Math.Pow(x, y); + + [MoFunction("random")] + public double Random(double low, double high) + { + return low + _random.NextDouble() * (high - low); + } + + [MoFunction("random_integer")] + public int RandomInt(int low, int high) + { + return _random.Next(low, high); + } + + [MoFunction("round")] + public double Round(double value) => Math.Round(value); + + [MoFunction("sqrt")] + public double Sqrt(double value) => Math.Sqrt(value); + + [MoFunction("trunc")] + public double Truncate(double value) => Math.Floor(value); + + public double Radify(double num) + { + return (((num + 180) % 360) + 180) % 360; + } + + private Random _random = new Random(); + } } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/MoLangRuntime.cs b/src/MolangSharp/Runtime/MoLangRuntime.cs index e3bdb51..6a62736 100644 --- a/src/MolangSharp/Runtime/MoLangRuntime.cs +++ b/src/MolangSharp/Runtime/MoLangRuntime.cs @@ -14,7 +14,7 @@ public sealed class MoLangRuntime /// The environment associated with this runtime instance ///
    public MoLangEnvironment Environment { get; } - + /// /// Create a new instance of MoLangRuntime with a new /// @@ -30,7 +30,7 @@ public MoLangRuntime(MoLangEnvironment environment) { Environment = environment; } - + /// /// Evaluates the expressions provided and returns the resulting value (if any) or /// @@ -56,14 +56,13 @@ public IMoValue Execute(IExpression expression, IDictionary co if (expression == null) return DoubleValue.Zero; - if (Environment.Structs.TryGetValue("context", out IMoStruct cont) && cont is ContextStruct contextStruct) + if (context != null && Environment.Structs.TryGetValue("context", out var cont) && cont is ContextStruct contextStruct) { contextStruct.Container = context; } IMoValue result = null; - MoScope scope = new MoScope(this); - + var scope = new MoScope(this); result = expression.Evaluate(scope, Environment); Environment.Structs["temp"].Clear(); diff --git a/src/MolangSharp/Runtime/MoLangRuntimeConfiguration.cs b/src/MolangSharp/Runtime/MoLangRuntimeConfiguration.cs new file mode 100644 index 0000000..c7b5ad1 --- /dev/null +++ b/src/MolangSharp/Runtime/MoLangRuntimeConfiguration.cs @@ -0,0 +1,18 @@ +namespace ConcreteMC.MolangSharp.Runtime +{ + /// + /// Global MoLang Runtime configuration options + /// + public static class MoLangRuntimeConfiguration + { + /// + /// If true (default) allows you to locate an exception in the MoLang script when a runtime exception is thrown. + /// + public static bool UseMoLangStackTrace { get; set; } = true; + + /// + /// If true, returns dummy values instead of throwing an exception when a variable is not found during runtime execution. (Experimental) + /// + public static bool UseDummyValuesInsteadOfExceptions { get; set; } = false; + } +} \ No newline at end of file diff --git a/src/MolangSharp/Runtime/MoParams.cs b/src/MolangSharp/Runtime/MoParams.cs index 51079f2..3132e15 100644 --- a/src/MolangSharp/Runtime/MoParams.cs +++ b/src/MolangSharp/Runtime/MoParams.cs @@ -18,7 +18,7 @@ public MoParams(params IMoValue[] param) { _parameters = param; } - + /// /// Gets the parameter at and returns its value as an /// @@ -50,7 +50,7 @@ public T Get(int index) if (obj?.GetType() == typeof(T)) { - return (T)obj; + return (T) obj; } else { @@ -81,9 +81,9 @@ public bool Contains(int index) /// public int GetInt(int index) { - return (int)GetDouble(index); + return (int) GetDouble(index); } - + /// /// Gets the parameter at and returns its value as a /// @@ -95,7 +95,7 @@ public double GetDouble(int index) { return Get(index).Value; } - + /// /// Gets the parameter at and returns its value as a /// @@ -107,7 +107,7 @@ public IMoStruct GetStruct(int index) { return Get(index); } - + /// /// Gets the parameter at and returns its value as a /// @@ -119,7 +119,7 @@ public string GetString(int index) { return Get(index).Value; } - + /// /// Gets the parameter at and returns its value as a /// diff --git a/src/MolangSharp/Runtime/Struct/ArrayStruct.cs b/src/MolangSharp/Runtime/Struct/ArrayStruct.cs index cccf2dd..8074e55 100644 --- a/src/MolangSharp/Runtime/Struct/ArrayStruct.cs +++ b/src/MolangSharp/Runtime/Struct/ArrayStruct.cs @@ -14,42 +14,46 @@ public class ArrayStruct : IMoStruct { private IMoValue[] _array; + /// + /// Initializes a new instance of the ArrayStruct class with no values. + /// public ArrayStruct() { _array = new IMoValue[0]; } + /// + /// Initializes a new instance of the ArrayStruct class. + /// + /// The values this ArrayStruct contains public ArrayStruct(IEnumerable values) { _array = values.ToArray(); - //_array.AddRange(values); } - private void Resize(int size) - { - if (size >= _array.Length) - Array.Resize(ref _array, size + 1); - } - + /// + /// Indexes the ArrayStruct + /// + /// The index to get/set the value of public IMoValue this[int index] { get { if (index >= _array.Length) return DoubleValue.Zero; - //Resize(index); + return _array[index % _array.Length]; } set { if (_array.Length == 0) return; - //Resize(index); - + _array[index % _array.Length] = value; } } + /// public void Set(MoPath key, IMoValue value) { if (int.TryParse(key.Value, out int index)) @@ -58,6 +62,7 @@ public void Set(MoPath key, IMoValue value) } } + /// public IMoValue Get(MoPath key, MoParams parameters) { if (int.TryParse(key.Value, out int index)) @@ -65,19 +70,22 @@ public IMoValue Get(MoPath key, MoParams parameters) return this[index]; } - throw new MoLangRuntimeException($"Invalid path for array access: {key.Path.ToString()}", null); + throw new MoLangRuntimeException($"Invalid path for array access: {key.Path.ToString()}"); } + /// public void Clear() { - throw new NotImplementedException(); + throw new NotSupportedException("Cannot clear an ArrayStruct"); } + /// public object Value => _array; } public class VariableArrayStruct : VariableStruct { + /// protected override IMoStruct CreateNew() { return new ArrayStruct(); diff --git a/src/MolangSharp/Runtime/Struct/IMoStruct.cs b/src/MolangSharp/Runtime/Struct/IMoStruct.cs index 1a7abdb..e4b0fd0 100644 --- a/src/MolangSharp/Runtime/Struct/IMoStruct.cs +++ b/src/MolangSharp/Runtime/Struct/IMoStruct.cs @@ -1,20 +1,38 @@ +using System; using ConcreteMC.MolangSharp.Runtime.Value; using ConcreteMC.MolangSharp.Utils; namespace ConcreteMC.MolangSharp.Runtime.Struct { + /// + /// The interface used for all MoLang containers/structs + /// public interface IMoStruct : IMoValue { + /// + /// Assign a value to a property + /// + /// The path of the property to modify + /// The value to set void Set(MoPath key, IMoValue value); + /// + /// Get the value of a property + /// + /// The path of the property to get + /// The parameters used to retrieve the value + /// The value of the property retrieved IMoValue Get(MoPath key, MoParams parameters); + /// + /// Clears the struct + /// void Clear(); /// - bool IMoValue.Equals(IMoValue b) + bool IEquatable.Equals(IMoValue other) { - return this.Equals((object)b); + return this.Equals((object) other); } } } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/Struct/Interop/FieldAccessor.cs b/src/MolangSharp/Runtime/Struct/Interop/FieldAccessor.cs index 33b65dd..189f6e4 100644 --- a/src/MolangSharp/Runtime/Struct/Interop/FieldAccessor.cs +++ b/src/MolangSharp/Runtime/Struct/Interop/FieldAccessor.cs @@ -14,7 +14,7 @@ public FieldAccessor(FieldInfo fieldInfo) public override bool CanRead => true; public override bool CanWrite => !_fieldInfo.IsInitOnly; - + /// public override IMoValue Get(object instance) { @@ -22,7 +22,7 @@ public override IMoValue Get(object instance) return value is IMoValue moValue ? moValue : MoValue.FromObject(value); } - + /// public override void Set(object instance, IMoValue value) { diff --git a/src/MolangSharp/Runtime/Struct/Interop/PropertyAccessor.cs b/src/MolangSharp/Runtime/Struct/Interop/PropertyAccessor.cs index d4acdf5..d4edea6 100644 --- a/src/MolangSharp/Runtime/Struct/Interop/PropertyAccessor.cs +++ b/src/MolangSharp/Runtime/Struct/Interop/PropertyAccessor.cs @@ -14,7 +14,7 @@ public PropertyAccessor(PropertyInfo propertyInfo) public override bool CanRead => _propertyInfo.CanRead; public override bool CanWrite => _propertyInfo.CanWrite; - + /// public override IMoValue Get(object instance) { @@ -31,24 +31,28 @@ public override void Set(object instance, IMoValue value) if (propType == typeof(double)) { _propertyInfo.SetValue(instance, value.AsDouble()); + return; } else if (propType == typeof(float)) { _propertyInfo.SetValue(instance, value.AsFloat()); + return; } else if (propType == typeof(bool)) { _propertyInfo.SetValue(instance, value.AsBool()); + return; } else if (propType == typeof(string)) { _propertyInfo.SetValue(instance, value.AsString()); + return; } - + _propertyInfo.SetValue(instance, value); InvokeChanged(); } diff --git a/src/MolangSharp/Runtime/Struct/Interop/PropertyCache.cs b/src/MolangSharp/Runtime/Struct/Interop/PropertyCache.cs index d2ca46b..dfa9bac 100644 --- a/src/MolangSharp/Runtime/Struct/Interop/PropertyCache.cs +++ b/src/MolangSharp/Runtime/Struct/Interop/PropertyCache.cs @@ -16,9 +16,8 @@ public class PropertyCache public PropertyCache(Type arg) { var properties = new Dictionary(StringComparer.OrdinalIgnoreCase); - var functions = new Dictionary>( - StringComparer.OrdinalIgnoreCase); - + var functions = new Dictionary>(StringComparer.OrdinalIgnoreCase); + ProcessMethods(arg, functions); ProcessProperties(arg, properties); @@ -90,7 +89,7 @@ IMoValue ExecuteMolangFunction(object instance, MoParams mo) } else if (t == typeof(float)) { - parameters[index] = (float)mo.GetDouble(index); + parameters[index] = (float) mo.GetDouble(index); } else if (t == typeof(string)) { @@ -142,6 +141,7 @@ private static void ProcessProperties(IReflect type, IDictionary() != null) accessor.Observable = true; @@ -157,8 +157,9 @@ private static void ProcessProperties(IReflect type, IDictionary() != null) accessor.Observable = true; diff --git a/src/MolangSharp/Runtime/Struct/Interop/ValueAccessor.cs b/src/MolangSharp/Runtime/Struct/Interop/ValueAccessor.cs index b767940..0d2f6bc 100644 --- a/src/MolangSharp/Runtime/Struct/Interop/ValueAccessor.cs +++ b/src/MolangSharp/Runtime/Struct/Interop/ValueAccessor.cs @@ -5,13 +5,14 @@ namespace ConcreteMC.MolangSharp.Runtime.Struct.Interop { public abstract class ValueAccessor { - public event EventHandler ValueChanged; + public event EventHandler ValueChanged; public bool Observable { get; internal set; } public abstract bool CanRead { get; } public abstract bool CanWrite { get; } public abstract IMoValue Get(object instance); + public abstract void Set(object instance, IMoValue value); protected void InvokeChanged() diff --git a/src/MolangSharp/Runtime/Struct/InteropStruct.cs b/src/MolangSharp/Runtime/Struct/InteropStruct.cs index 3ef616e..4358e17 100644 --- a/src/MolangSharp/Runtime/Struct/InteropStruct.cs +++ b/src/MolangSharp/Runtime/Struct/InteropStruct.cs @@ -44,7 +44,7 @@ public void Set(MoPath key, IMoValue value) { if (!key.HasChildren) { - if (_propertyCache.Properties.TryGetValue(key.ToString(), out var accessor)) + if (_propertyCache.Properties.TryGetValue(key.Value, out var accessor)) { if (!accessor.CanWrite) throw new MoLangRuntimeException("Cannot write to ReadOnly property!", null); @@ -87,16 +87,19 @@ private bool ExecuteGet(MoPath property, string main, MoParams parameters, out I throw new MoLangRuntimeException($"Cannot read from property '{property.ToString()}'", null); returnValue = accessor.Get(_instance); + return true; } if (_propertyCache.Functions.TryGetValue(main, out var f)) { returnValue = f.Invoke(_instance, parameters); + return true; } returnValue = DoubleValue.Zero; + return false; } @@ -120,7 +123,7 @@ public IMoValue Get(MoPath key, MoParams parameters) } } - if (!ExecuteGet(key, key.ToString(), parameters, out var returnValue)) + if (!ExecuteGet(key, key.Value, parameters, out var returnValue)) Debug.WriteLine($"({_instance.ToString()}) Unknown query: {key}"); return returnValue; @@ -129,7 +132,7 @@ public IMoValue Get(MoPath key, MoParams parameters) /// public void Clear() { - throw new NotImplementedException(); + throw new NotSupportedException("Cannot clear an InteropStruct."); } } } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/Struct/QueryStruct.cs b/src/MolangSharp/Runtime/Struct/QueryStruct.cs index 705574f..9846a4b 100644 --- a/src/MolangSharp/Runtime/Struct/QueryStruct.cs +++ b/src/MolangSharp/Runtime/Struct/QueryStruct.cs @@ -32,11 +32,11 @@ public void Set(MoPath key, IMoValue value) /// public IMoValue Get(MoPath key, MoParams parameters) { - if (Functions.TryGetValue(key.ToString(), out var func)) + if (Functions.TryGetValue(key.Value, out var func)) { return MoValue.FromObject(func(parameters)); } - + return DoubleValue.Zero; } diff --git a/src/MolangSharp/Runtime/Struct/VariableStruct.cs b/src/MolangSharp/Runtime/Struct/VariableStruct.cs index cc48595..4bddb2e 100644 --- a/src/MolangSharp/Runtime/Struct/VariableStruct.cs +++ b/src/MolangSharp/Runtime/Struct/VariableStruct.cs @@ -27,6 +27,10 @@ public VariableStruct(IEnumerable> values) { Map = new Dictionary(values, StringComparer.OrdinalIgnoreCase); } + else + { + Map = new Dictionary(StringComparer.OrdinalIgnoreCase); + } } protected virtual IMoStruct CreateNew() @@ -37,8 +41,6 @@ protected virtual IMoStruct CreateNew() /// public virtual void Set(MoPath key, IMoValue value) { - //var index = key.IndexOf('.'); - if (!key.HasChildren) { Map[key.Value] = value; @@ -50,13 +52,10 @@ public virtual void Set(MoPath key, IMoValue value) if (!string.IsNullOrWhiteSpace(main)) { - //object vstruct = Get(main, MoParams.Empty); - if (!Map.TryGetValue(main, out var container)) { if (!key.HasChildren) { - //Map.TryAdd(main, container = new VariableStruct()); throw new MoLangRuntimeException($"Variable was not a struct: {key}", null); } @@ -71,29 +70,22 @@ public virtual void Set(MoPath key, IMoValue value) { throw new MoLangRuntimeException($"Variable was not a struct: {key}", null); } - - //((IMoStruct) vstruct).Set(string.Join(".", segments), value); - - //Map[main] = (IMoStruct)vstruct;//.Add(main, (IMoStruct) vstruct); } } /// public virtual IMoValue Get(MoPath key, MoParams parameters) { - //var index = key.IndexOf('.'); - if (key.HasChildren) { string main = key.Value; if (!string.IsNullOrWhiteSpace(main)) { - IMoValue value = null; //Map[main]; + IMoValue value = null; if (!Map.TryGetValue(main, out value)) { - // Log.Info($"Unknown variable map: {key}"); return DoubleValue.Zero; } @@ -109,9 +101,6 @@ public virtual IMoValue Get(MoPath key, MoParams parameters) if (Map.TryGetValue(key.Value, out var v)) return v; - // - // Log.Info($"Unknown variable: {key}"); - return DoubleValue.Zero; } diff --git a/src/MolangSharp/Runtime/Value/DoubleValue.cs b/src/MolangSharp/Runtime/Value/DoubleValue.cs index 8102b98..bc5b614 100644 --- a/src/MolangSharp/Runtime/Value/DoubleValue.cs +++ b/src/MolangSharp/Runtime/Value/DoubleValue.cs @@ -2,10 +2,18 @@ namespace ConcreteMC.MolangSharp.Runtime.Value { - public class DoubleValue : IMoValue + /// + /// Represents a double-precision floating-point number + /// + public class DoubleValue : IMoValue { + private readonly double _value; + + /// + object IMoValue.Value => _value; + /// - object IMoValue.Value => Value; + public double Value => _value; /// public bool Equals(IMoValue b) @@ -16,11 +24,6 @@ public bool Equals(IMoValue b) return false; } - private readonly double _value; - - /// - public double Value => _value; - public DoubleValue(object value) { if (value is bool boolean) @@ -65,7 +68,7 @@ public override bool Equals(object obj) // ...the rest of the equality implementation } - protected bool Equals(DoubleValue other) + public bool Equals(DoubleValue other) { return _value.Equals(other._value); } @@ -79,43 +82,16 @@ public override int GetHashCode() /// public double AsDouble() { - return Value; + return _value; } /// public float AsFloat() { - return (float)Value; + return (float) _value; } public static DoubleValue Zero { get; } = new DoubleValue(0d); public static DoubleValue One { get; } = new DoubleValue(1d); } - - /*public class FloatValue : IMoValue - { - /// - object IMoValue.Value => Value; - - /// - public float Value { get; } - - public FloatValue(object value) { - if (value is bool) { - Value = (bool) value ? 1.0f : 0.0f; - } else if (value is float) { - Value = (float) value; - } else { - Value = 1.0f; - } - } - - public FloatValue(float value) - { - Value = value; - } - - public static FloatValue Zero => new FloatValue(0); - public static FloatValue One => new FloatValue(1); - }*/ } \ No newline at end of file diff --git a/src/MolangSharp/Runtime/Value/MoValue.cs b/src/MolangSharp/Runtime/Value/MoValue.cs index d7e527d..514bed3 100644 --- a/src/MolangSharp/Runtime/Value/MoValue.cs +++ b/src/MolangSharp/Runtime/Value/MoValue.cs @@ -1,26 +1,22 @@ +using System; using System.Collections; using System.Collections.Generic; using ConcreteMC.MolangSharp.Runtime.Struct; namespace ConcreteMC.MolangSharp.Runtime.Value - { - public interface IMoValue : IMoValue - { - new T Value { get; } - } - - public interface IMoValue + /// + /// Resembles a MoLang compatible value. + /// + public interface IMoValue : IEquatable { object Value { get; } - bool Equals(IMoValue b); - - string AsString() => Value.ToString(); + virtual string AsString() => Value.ToString(); virtual double AsDouble() => Value is double db ? db : 0d; - virtual float AsFloat() => Value is float flt ? flt : (float)AsDouble(); + virtual float AsFloat() => Value is float flt ? flt : (float) AsDouble(); virtual bool AsBool() => Value is bool b ? b : AsDouble() > 0; } diff --git a/src/MolangSharp/Runtime/Value/StringValue.cs b/src/MolangSharp/Runtime/Value/StringValue.cs index ab4e9ff..dab4636 100644 --- a/src/MolangSharp/Runtime/Value/StringValue.cs +++ b/src/MolangSharp/Runtime/Value/StringValue.cs @@ -1,6 +1,9 @@ namespace ConcreteMC.MolangSharp.Runtime.Value { - public class StringValue : IMoValue + /// + /// Represents a string value + /// + public class StringValue : IMoValue { /// object IMoValue.Value => Value; diff --git a/src/MolangSharp/Utils/MoPath.cs b/src/MolangSharp/Utils/MoPath.cs index 8268399..84ae1ba 100644 --- a/src/MolangSharp/Utils/MoPath.cs +++ b/src/MolangSharp/Utils/MoPath.cs @@ -1,13 +1,36 @@ namespace ConcreteMC.MolangSharp.Utils { + /// + /// Describes the path of a variable/function + /// public class MoPath { + /// + /// The root of this path + /// public MoPath Root { get; } + + /// + /// The next element + /// public MoPath Next { get; private set; } + /// + /// The full path + /// public string Path { get; } + + /// + /// The value of this path + /// + /// + /// Think of this is the filename in a path + /// public string Value { get; private set; } + /// + /// Whether this path has any child elements + /// public bool HasChildren => Next != null; public MoPath(string path) @@ -20,6 +43,7 @@ public MoPath(string path) Value = segments[0]; MoPath current = this; + if (segments.Length > 1) { string currentPath = $"{Value}"; @@ -40,7 +64,7 @@ public MoPath(string path) } } - internal MoPath(MoPath root,string path, string value) + internal MoPath(MoPath root, string path, string value) { Root = root; Path = path; @@ -51,11 +75,11 @@ internal void SetValue(string value) { Value = value; } - + /// public override string ToString() { - return Value; + return Path; } } } \ No newline at end of file