├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── Icon.png └── IconShort.png ├── makefile └── src ├── Hades.Common ├── Datatype.cs ├── Extensions │ ├── AssemblyExtensions.cs │ ├── IEnumerableExtensions.cs │ └── StringExtensions.cs ├── Hades.Common.csproj ├── Source │ ├── SourceCode.cs │ ├── SourceLocation.cs │ └── Span.cs └── Util │ └── ConsoleExtensions.cs ├── Hades.Core ├── Hades.Core.csproj ├── Program.cs └── Tools │ ├── Hermes.cs │ └── ProjectInitializer.cs ├── Hades.Error ├── ErrorStrings.cs └── Hades.Error.csproj ├── Hades.Language ├── Hades.Language.csproj ├── Lexer │ ├── Keyword.cs │ ├── Lexer.cs │ └── LexerGrammar.ebnf └── Parser │ ├── Parser.cs │ └── ParserGrammar.ebnf ├── Hades.Runtime ├── Hades.Runtime.csproj ├── HadesRuntime.cs ├── Objects │ └── BuiltIns.cs ├── Scope.cs ├── ScopeValue.cs └── Values │ ├── BoolValue.cs │ ├── DecValue.cs │ ├── IntValue.cs │ ├── ListValue.cs │ ├── LiteralValue.cs │ └── StringValue.cs ├── Hades.Syntax ├── Expression │ ├── AccessModifier.cs │ ├── BlockNode.cs │ ├── Classifier.cs │ ├── LiteralNode.cs │ ├── Node.cs │ ├── Nodes │ │ ├── ArrayAccessNode.cs │ │ ├── AssignmentNode.cs │ │ ├── BlockNodes │ │ │ ├── ClassNode.cs │ │ │ ├── ForNode.cs │ │ │ ├── FunctionNode.cs │ │ │ ├── GenericBlockNode.cs │ │ │ ├── IfNode.cs │ │ │ ├── LambdaNode.cs │ │ │ ├── MatchNode.cs │ │ │ ├── StructNode.cs │ │ │ ├── TryCatchElseNode.cs │ │ │ ├── Util │ │ │ │ └── ParameterWriter.cs │ │ │ ├── VarBlockNode.cs │ │ │ └── WhileNode.cs │ │ ├── CallNode.cs │ │ ├── InlineIf.cs │ │ ├── LiteralNodes │ │ │ ├── ArgsNode.cs │ │ │ ├── BoolLiteralNode.cs │ │ │ ├── CommandNode.cs │ │ │ ├── DecLiteralNode.cs │ │ │ ├── IdentifierNode.cs │ │ │ ├── IntLiteralNode.cs │ │ │ ├── ListNode.cs │ │ │ ├── MultiDimensionalArrayNode.cs │ │ │ ├── NullValueNode.cs │ │ │ ├── OperationNodeNode.cs │ │ │ ├── PlaceHolderNode.cs │ │ │ └── StringLiteralNode.cs │ │ ├── NoVariableNode.cs │ │ ├── NullConditionNode.cs │ │ ├── OperationNode.cs │ │ ├── ParenthesesNode.cs │ │ ├── PipelineNode.cs │ │ ├── PutNode.cs │ │ ├── RaiseNode.cs │ │ ├── SideNode.cs │ │ ├── ValueCallNode.cs │ │ ├── VariableDeclarationNode.cs │ │ └── WithNode.cs │ └── RootNode.cs ├── Hades.Syntax.csproj └── Lexeme │ ├── Category.cs │ ├── Classifier.cs │ └── Token.cs ├── Hades.Testing ├── Hades.Testing.csproj ├── LexerTest.cs └── ParserTest.cs └── Hades.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | _site/api/ 263 | 264 | _site/ 265 | 266 | api/ 267 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | 2 | [submodule "docs"] 3 | path = docs 4 | url = https://github.com/Azer0s/HadesDoc.git 5 | branch = master 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | mono: none 3 | dotnet: 2.1 4 | script: 5 | - make interpreter 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 5/16/19 2 | 3 | ### Introduced extension methods 4 | 5 | This allows for cool things like behavior based programming where one could have a bunch of group classes ("interfaces"), extend functions based on group and import these extensions based on usage. 6 | 7 | ```swift 8 | class IDomainObject 9 | end 10 | 11 | class Person < IDomainObject 12 | @public 13 | var string firstname 14 | var string lastname 15 | var object birthday 16 | end 17 | end 18 | 19 | [..] 20 | 21 | func extends object::IDomainObject persist(object::IConnection connection) 22 | [..] 23 | end 24 | 25 | [..] 26 | 27 | func extends object::IDomainObject toJson() 28 | put [..] //Default impl. of index on object returns all the members 29 | end 30 | ``` 31 | 32 | So based on need, one could import `persist` or `toJson` as extensions. In the controller, for instance, `persist` is absolutely useless, so is `toJson` in a service. 33 | 34 | ## 5/5/19 35 | 36 | ### Fixed call on return #3dca537b60026e55dd33233da8ef11039da6980d 37 | 38 | ```js 39 | {x => x}(19) 40 | ({x => x}(19))(1) 41 | (({x => x}(19))(1))(19) 42 | 43 | test()() 44 | (test())() 45 | ((test())())() 46 | ``` 47 | 48 | ## 5/4/19 49 | 50 | ### Added try-catch-else block #5d514e61ea4189601e8ca8bfbc99e8916394c9a9 51 | 52 | ```js 53 | try 54 | connection->open() 55 | console->out("Connection open!") 56 | connection->close() 57 | catch(object::SqlException e) 58 | console->out("SqlException was caught!") 59 | catch(e) 60 | console->out("An unknown exception was caught!") 61 | end 62 | ``` 63 | 64 | ```js 65 | try 66 | connection->open() 67 | console->out("Connection open!") 68 | connection->close() 69 | catch(object::SqlException e) 70 | console->out("SqlException was caught!") 71 | catch(e) 72 | console->out("An unknown exception was caught!") 73 | else 74 | console->out("No exception thrown!") 75 | end 76 | ``` 77 | 78 | ```js 79 | try 80 | connection->open() 81 | console->out("Connection open!") 82 | connection->close() 83 | else //Exception is not handled; if there was no exception, else block is called 84 | console->out("No exception thrown!") 85 | end 86 | ``` 87 | 88 | ### Added if block, code beautification #8b3d4d6b35167b37a0efdb96e9ad2fc964719e1e 89 | 90 | ```js 91 | if(a < 10) 92 | console->out("a is smaller than 10") 93 | else if(a is 11) 94 | console->out("a is 11") 95 | else if(a > 11 and a < 21) 96 | console->out("a is greater than 11 and smaller than 21") 97 | else 98 | console->out("a is " + a) 99 | end 100 | ``` 101 | 102 | ```javascript 103 | if(a < 10) 104 | console->out("a is smaller than 10") 105 | else 106 | console->out("a is " + a) 107 | end 108 | ``` 109 | 110 | ```javascript 111 | if(a < 10) 112 | console->out("a is smaller than 10") 113 | end 114 | ``` 115 | 116 | ```js 117 | if(a < 10) 118 | console->out("a is smaller than 10") 119 | else if(a is 11) 120 | console->out("a is 11") 121 | end 122 | ``` 123 | 124 | ## 5/3/19 125 | 126 | ### Added fixed prefix #5aca4880462c36dc87643e614bd7e38602f7e334 127 | 128 | * Added the fixed prefix in front of func 129 | 130 | ### Added pipelines #f3129f23a2f7c7103a3dc8a89b19f846a84585a5 131 | 132 | This: 133 | 134 | ```js 135 | fruits 136 | |> map({x => x->toLower()}, ??) 137 | |> filter({x => x->startsWith("a")}, ??) 138 | |> forEach({x => console->out(x)}, ??) 139 | ``` 140 | 141 | is now a thing. 142 | 143 | ## 4/26/19 144 | 145 | ### Removed multidimensional array access #b2a09b8f55e2024277e7fbd0ad9331f20d5f013a 146 | 147 | * We moved from `.` to `,` when accessing multidimensional array 148 | 149 | ```js 150 | var int[3,3] matrix = {{1,0,0},{0,1,0},{0,0,1}} 151 | var int?[2,2,2] 3dArray = {{{1,2},{3,null}},{{null,6},{7,8}}} 152 | ``` 153 | 154 | ### Added specific object and proto validation to lambdas, function and variables #6623878e8ccec87a20c395cdd157931dea2b403b 155 | 156 | * Variable instantiations, function arguments and lambda arguments can now name specific class-/proto names 157 | 158 | ```swift 159 | func doStuff(args object::IClient a) 160 | a->stuff("Test") 161 | end 162 | ``` 163 | 164 | ```swift 165 | var x = {args object::IClient a, int b => 166 | a->moreStuff(b) 167 | } 168 | ``` 169 | 170 | ```js 171 | var proto::console c 172 | ``` 173 | 174 | ## 4/23/19 175 | 176 | ### Added lambda parameter types #c244d04d419cb163ce7e5581990df1dd39c44190 177 | 178 | * Lambdas can now have parameter types 179 | 180 | ```js 181 | var add = {x, y => x + y} 182 | ``` 183 | 184 | ```js 185 | var mul = {int x, int y => x * y} 186 | ``` 187 | 188 | ```js 189 | var sum = { args int vals => 190 | var result = 0 191 | 192 | for(var i in a) 193 | result += i 194 | end 195 | 196 | put result 197 | } 198 | ``` 199 | 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present Ariel Simulevski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | ## [WIP] Hades Programming Language 4 | 5 | [![Build Status](https://travis-ci.org/Azer0s/HadesLang.svg?branch=master)](https://travis-ci.org/Azer0s/HadesLang) 6 | [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/Azer0s/HadesLang/blob/master/LICENSE) 7 | 8 | ## Welcome! 9 | 10 | This is the official repository for the reference implementation of the Hades Programming Language (standard library & interpreter). 11 | 12 | ### Hello world 13 | ```js 14 | with console from std:io 15 | console.out("Hello world") 16 | ``` 17 | 18 | ### Pipelines 19 | ```js 20 | with list fixed from std:collections 21 | with console from std:io 22 | 23 | var fruits = list.of({"Apple", "Banana", "Mango", "Kiwi", "Avocado"}) 24 | 25 | fruits 26 | |> map(??, {x => x.toLower()}) 27 | |> filter({x => x.startsWith("a")}) 28 | //if ?? is not in the parameters, the method is inserted as the first parameter 29 | |> forEach(??, {x => console.out(x)}) 30 | ``` 31 | 32 | ### Function guards 33 | ```swift 34 | with console from std:io 35 | 36 | func myFunction(a int) requires a < 10 37 | console.out("a is smaller than 10") 38 | end 39 | 40 | func myFunction(a int) requires a > 10 41 | console.out("a is greater than 10") 42 | end 43 | 44 | myFunction(5) // a is smaller than 10 45 | myFunction(17) // a is greater than 10 46 | ``` 47 | 48 | ### Actors 49 | ```swift 50 | with msg from std:io 51 | with sleep from std:time 52 | 53 | func ping() 54 | receive(m) 55 | {:ping, data} => {_ => 56 | sleep.seconds(1) 57 | send(data, :pong) 58 | } 59 | end 60 | ping() 61 | end 62 | 63 | func pong() 64 | receive(m) 65 | {:pong, data} => {_ => 66 | sleep.seconds(1) 67 | send(data, :ping) 68 | } 69 | end 70 | pong() 71 | end 72 | 73 | var pingPid = spawn({_ => ping()} 74 | var pongPid = spawn({_ => pong()} 75 | 76 | send(pingPid, {:ping, pongPid}) 77 | ``` 78 | 79 | ### Fibonacci sequence 80 | ```js 81 | with console from std:io 82 | 83 | func fib(n) 84 | if((n is 0) or (n is 1)) 85 | put n 86 | end 87 | 88 | put fib(n-1) + fib(n-2) 89 | end 90 | 91 | fib(10) |> console.out 92 | ``` 93 | 94 | ### Optional static typing 95 | ```js 96 | with console from std:io 97 | 98 | func fib(n int64) -> int64 99 | if((n is 0) or (n is 1)) 100 | put n 101 | end 102 | 103 | put fib(n-1) + fib(n-2) 104 | end 105 | 106 | fib(10) |> console.out 107 | ``` 108 | 109 | ## Getting Started 110 | 111 | Learning Hades and writing your first programs. 112 | 113 | ### [Installing Hades](https://hadeslang.gitbook.io/doc/getting-started/installing-hades) 114 | 115 | Instructions for downloading HadesLang and/or embedding it into your programs. 116 | 117 | ### [Basic Syntax](https://hadeslang.gitbook.io/doc/getting-started/basic-syntax) 118 | 119 | Hades basics and quick introduction into the language. 120 | 121 | ### [Coding Conventions](https://hadeslang.gitbook.io/doc/getting-started/coding-conventions) 122 | 123 | Current coding style for HadesLang. 124 | 125 | ## References 126 | 127 | ### [Language Spec](https://hadeslang.gitbook.io/doc/language-spec) 128 | 129 | The official HadesLang specification. 130 | 131 | ### [Package Documentation](https://hadeslang.gitbook.io/doc/core-libraries/standard-library) 132 | 133 | Documentation and definition of the Hades standard library. 134 | 135 | ### [Tool Documentation](https://hadeslang.gitbook.io/doc/other/tools) 136 | 137 | Documentation for HadesLang tools. 138 | 139 | ### [Examples](https://hadeslang.gitbook.io/doc/other/examples) 140 | 141 | Examples of Hades in use. 142 | -------------------------------------------------------------------------------- /assets/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azer0s/HadesLang/9bf2c11898b4369370b096d33bf9a22badcb7346/assets/Icon.png -------------------------------------------------------------------------------- /assets/IconShort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azer0s/HadesLang/9bf2c11898b4369370b096d33bf9a22badcb7346/assets/IconShort.png -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @$(MAKE) interpreter 3 | 4 | interpreter: 5 | @echo 6 | @echo "\033[4m\033[1mBuilding HadesLang\033[0m" 7 | @echo 8 | @dotnet build src/Hades.Core/Hades.Core.csproj 9 | 10 | run: 11 | @echo 12 | @echo "\033[4m\033[1mRunning HadesLang\033[0m" 13 | @echo 14 | @dotnet run --project src/Hades.Core/Hades.Core.csproj 15 | -------------------------------------------------------------------------------- /src/Hades.Common/Datatype.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Common 2 | { 3 | public enum Datatype 4 | { 5 | NONE = 0, 6 | BOOL, 7 | STRING, 8 | INT, 9 | DEC, 10 | OBJECT, 11 | PROTO, 12 | STRUCT, 13 | LAMBDA 14 | } 15 | } -------------------------------------------------------------------------------- /src/Hades.Common/Extensions/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | 5 | namespace Hades.Common.Extensions 6 | { 7 | public static class AssemblyExtensions 8 | { 9 | public static DateTime GetBuildDate(this Assembly assembly) 10 | { 11 | const string buildVersionMetadataPrefix = "+build"; 12 | 13 | var attribute = assembly.GetCustomAttribute(); 14 | if (attribute?.InformationalVersion == null) return default; 15 | var value = attribute.InformationalVersion; 16 | var index = value.IndexOf(buildVersionMetadataPrefix, StringComparison.Ordinal); 17 | if (index <= 0) return default; 18 | value = value.Substring(index + buildVersionMetadataPrefix.Length); 19 | return DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result) ? result : default; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Hades.Common/Extensions/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | // ReSharper disable LoopCanBeConvertedToQuery 5 | // ReSharper disable UnusedMethodReturnValue.Global 6 | 7 | namespace Hades.Common.Extensions 8 | { 9 | public static class IEnumerableExtensions 10 | { 11 | public static IEnumerable Map(this IEnumerable source, Func action) 12 | { 13 | foreach (var x in source) 14 | { 15 | yield return action(x); 16 | } 17 | } 18 | 19 | public static S Filter(this IEnumerable source, Func accumulator, S initialAccumulator) 20 | { 21 | var init = initialAccumulator; 22 | foreach (var x in source) 23 | { 24 | init = accumulator(init, x); 25 | } 26 | 27 | return init; 28 | } 29 | 30 | public static void ForEach(this IEnumerable source, Action action) 31 | { 32 | foreach (var x in source) 33 | { 34 | action(x); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Hades.Common/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Common 2 | { 3 | public static class StringExtensions 4 | { 5 | /// 6 | /// Gets that at a given . 7 | /// 8 | /// The to search. 9 | /// The index of to return. 10 | /// 11 | /// The at the given . If is less than 0 12 | /// or greater than the length of the string, returns an ASCII NULL (\0). 13 | /// 14 | public static char CharAt(this string str, int index) 15 | { 16 | if (index > str.Length - 1 || index < 0) 17 | { 18 | return '\0'; 19 | } 20 | 21 | return str[index]; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Hades.Common/Hades.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | latest 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Hades.Common/Source/SourceCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Hades.Common.Source 4 | { 5 | public sealed class SourceCode 6 | { 7 | private readonly Lazy _lines; 8 | private readonly string _sourceCode; 9 | 10 | public SourceCode(string sourceCode) 11 | { 12 | _sourceCode = sourceCode; 13 | _lines = new Lazy(() => _sourceCode.Split(new[] {Environment.NewLine}, StringSplitOptions.None)); 14 | } 15 | 16 | public string[] Lines => _lines.Value; 17 | 18 | public char this[int index] => _sourceCode.CharAt(index); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Hades.Common/Source/SourceLocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Hades.Common.Source 4 | { 5 | public struct SourceLocation : IEquatable 6 | { 7 | public int Column { get; } 8 | 9 | public int Index { get; } 10 | 11 | public int Line { get; } 12 | 13 | public SourceLocation(int index, int line, int column) 14 | { 15 | Index = index; 16 | Line = line; 17 | Column = column; 18 | } 19 | 20 | public static bool operator !=(SourceLocation left, SourceLocation right) 21 | { 22 | return !left.Equals(right); 23 | } 24 | 25 | public static bool operator ==(SourceLocation left, SourceLocation right) 26 | { 27 | return left.Equals(right); 28 | } 29 | 30 | public override bool Equals(object obj) 31 | { 32 | if (obj is SourceLocation location) 33 | { 34 | return Equals(location); 35 | } 36 | 37 | return base.Equals(obj); 38 | } 39 | 40 | public bool Equals(SourceLocation other) 41 | { 42 | return other.GetHashCode() == GetHashCode(); 43 | } 44 | 45 | public override int GetHashCode() 46 | { 47 | return 0xB1679EE ^ Index ^ Line ^ Column; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Hades.Common/Source/Span.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Hades.Common.Source 4 | { 5 | public struct Span : IEquatable 6 | { 7 | public SourceLocation End { get; } 8 | 9 | public int Length => End.Index - Start.Index; 10 | 11 | public SourceLocation Start { get; } 12 | 13 | public Span(SourceLocation start, SourceLocation end) 14 | { 15 | Start = start; 16 | End = end; 17 | } 18 | 19 | public static bool operator !=(Span left, Span right) 20 | { 21 | return !left.Equals(right); 22 | } 23 | 24 | public static bool operator ==(Span left, Span right) 25 | { 26 | return left.Equals(right); 27 | } 28 | 29 | public override bool Equals(object obj) 30 | { 31 | if (obj is Span span) 32 | { 33 | return Equals(span); 34 | } 35 | 36 | return base.Equals(obj); 37 | } 38 | 39 | public bool Equals(Span other) 40 | { 41 | return other.Start == Start && other.End == End; 42 | } 43 | 44 | public override int GetHashCode() 45 | { 46 | return 0x509CE ^ Start.GetHashCode() ^ End.GetHashCode(); 47 | } 48 | 49 | public override string ToString() 50 | { 51 | return $"{Start.Line} {Start.Column} {Length}"; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Hades.Common/Util/ConsoleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Hades.Common.Util 4 | { 5 | public static class ConsoleFunctions 6 | { 7 | public static void ClearCurrentConsoleLine() 8 | { 9 | Console.SetCursorPosition(0, Console.CursorTop - 1); 10 | var currentLineCursor = Console.CursorTop; 11 | Console.SetCursorPosition(0, Console.CursorTop); 12 | Console.Write(new string(' ', Console.WindowWidth)); 13 | Console.SetCursorPosition(0, currentLineCursor); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Hades.Core/Hades.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | build$([System.DateTime]::Now.ToString("yyyyMMddHHmmss")) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Hades.Core/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Hades.Common; 6 | using Hades.Common.Extensions; 7 | using Hades.Common.Util; 8 | using Hades.Core.Tools; 9 | using Hades.Language.Lexer; 10 | using Hades.Language.Parser; 11 | using Hades.Runtime; 12 | using Hades.Syntax.Lexeme; 13 | using Newtonsoft.Json; 14 | using Classifier = Hades.Syntax.Lexeme.Classifier; 15 | 16 | namespace Hades.Core 17 | { 18 | public static class Program 19 | { 20 | private const string VERSION = "0.0.1"; 21 | private static Scope scope = new Scope(); 22 | 23 | private static void HighLight(IEnumerable tks) 24 | { 25 | Console.ForegroundColor = ConsoleColor.White; 26 | var tokens = tks.ToList(); 27 | for (var i = 0; i < tokens.Count; i++) 28 | { 29 | var token = tokens[i]; 30 | 31 | if (token.Kind == Classifier.Identifier && char.IsUpper(token.Value[0])) 32 | { 33 | Console.ForegroundColor = ConsoleColor.Green; 34 | } 35 | 36 | if (i + 1 < tokens.Count) 37 | { 38 | if (token.Kind == Classifier.Identifier && tokens[i + 1].Kind == Classifier.LeftParenthesis) 39 | { 40 | Console.ForegroundColor = ConsoleColor.DarkYellow; 41 | } 42 | } 43 | 44 | if (token.Kind == Classifier.Identifier && token.Value == "super") 45 | { 46 | Console.ForegroundColor = ConsoleColor.DarkCyan; 47 | } 48 | 49 | if (token.Kind == Classifier.Keyword) 50 | { 51 | Console.ForegroundColor = ConsoleColor.DarkCyan; 52 | } 53 | 54 | if (token.Kind == Classifier.StringLiteral) 55 | { 56 | Console.ForegroundColor = ConsoleColor.Red; 57 | token.Value = $"\"{token.Value}\""; 58 | } 59 | 60 | if (token.Kind == Classifier.IntLiteral || token.Kind == Classifier.DecLiteral) 61 | { 62 | Console.ForegroundColor = ConsoleColor.Green; 63 | } 64 | 65 | 66 | if (i - 1 >= 0) 67 | { 68 | if (token.Kind == Classifier.Identifier && tokens[i - 1].Kind == Classifier.Tag) 69 | { 70 | Console.ForegroundColor = ConsoleColor.Red; 71 | } 72 | } 73 | 74 | if (token.Kind == Classifier.BoolLiteral) 75 | { 76 | Console.ForegroundColor = ConsoleColor.DarkYellow; 77 | } 78 | 79 | if (token.Category == Category.Operator || token.Kind == Classifier.Question) 80 | { 81 | Console.ForegroundColor = ConsoleColor.Cyan; 82 | } 83 | 84 | if (Enum.TryParse(token.Value.ToUpper(), out _) && !token.Value.All(char.IsDigit)) 85 | { 86 | Console.ForegroundColor = ConsoleColor.DarkYellow; 87 | } 88 | 89 | if (token.Kind == Classifier.LeftBracket || token.Kind == Classifier.RightBracket || token.Kind == Classifier.LeftBrace || token.Kind == Classifier.RightBrace) 90 | { 91 | Console.ForegroundColor = ConsoleColor.DarkGreen; 92 | } 93 | 94 | if (token.Category == Category.Comment) 95 | { 96 | Console.ForegroundColor = ConsoleColor.DarkGray; 97 | } 98 | 99 | if (token.Value == "std") 100 | { 101 | Console.ForegroundColor = ConsoleColor.White; 102 | } 103 | 104 | Console.Write(token.Value); 105 | Console.ForegroundColor = ConsoleColor.White; 106 | } 107 | } 108 | 109 | private static void PrintStart() 110 | { 111 | Console.ForegroundColor = ConsoleColor.White; 112 | Console.WriteLine($"Hades (Hades Interactive Console). Built {Assembly.GetExecutingAssembly().GetBuildDate():M/d/yy h:mm:ss tt}"); 113 | Console.ResetColor(); 114 | 115 | Console.ForegroundColor = ConsoleColor.DarkGray; 116 | Console.WriteLine($"Hades version {VERSION}"); 117 | Console.WriteLine($"Running {Environment.OSVersion}"); 118 | Console.WriteLine("Press space to enter/exit multiline mode"); 119 | } 120 | 121 | private static IEnumerable GetTokens() 122 | { 123 | var lexer = new Lexer(); 124 | var line = Console.ReadLine(); 125 | IEnumerable tokens; 126 | 127 | if (line == " ") 128 | { 129 | // Multiple lines 130 | var lines = new List {line}; 131 | do 132 | { 133 | Console.ForegroundColor = ConsoleColor.DarkGray; 134 | Console.Write("..."); 135 | Console.ForegroundColor = ConsoleColor.White; 136 | line = Console.ReadLine(); 137 | 138 | ConsoleFunctions.ClearCurrentConsoleLine(); 139 | Console.ForegroundColor = ConsoleColor.DarkGray; 140 | Console.Write("..."); 141 | tokens = lexer.LexFile(line); 142 | HighLight(tokens); 143 | Console.WriteLine(); 144 | 145 | lines.Add(line); 146 | } while (line != " "); 147 | 148 | Console.WriteLine(); 149 | 150 | lines.RemoveAt(line.Length - 1); 151 | tokens = lexer.LexFile(string.Join("\n", lines)); 152 | } 153 | else 154 | { 155 | // Single line 156 | ConsoleFunctions.ClearCurrentConsoleLine(); 157 | Console.ForegroundColor = ConsoleColor.DarkGray; 158 | Console.Write("hd>"); 159 | tokens = lexer.LexFile(line); 160 | HighLight(tokens); 161 | Console.WriteLine(); 162 | 163 | tokens = lexer.LexFile(line); 164 | } 165 | 166 | return tokens; 167 | } 168 | 169 | public static int Main(string[] args) 170 | { 171 | if (args.Length != 0) 172 | { 173 | switch (args.First()) 174 | { 175 | case "new": 176 | return ProjectInitializer.Run(args.Skip(1).ToList()); 177 | case "package": 178 | return 0; 179 | } 180 | } 181 | 182 | PrintStart(); 183 | 184 | while (true) 185 | { 186 | Console.ForegroundColor = ConsoleColor.DarkGray; 187 | Console.Write("hd>"); 188 | Console.ResetColor(); 189 | 190 | try 191 | { 192 | var tokens = GetTokens(); 193 | var parser = new Parser(tokens, true); 194 | var root = parser.Parse(); 195 | var result = HadesRuntime.Run(root, ref scope); 196 | 197 | if (root.Children.Count == 1) 198 | { 199 | if (result.Datatype != Datatype.LAMBDA || result.Datatype != Datatype.PROTO || result.Datatype != Datatype.OBJECT || result.Datatype != Datatype.STRUCT) 200 | { 201 | Console.ForegroundColor = ConsoleColor.DarkGray; 202 | Console.WriteLine($"{result.Datatype.ToString().ToLower()} :: {result.Value}"); 203 | Console.ResetColor(); 204 | Console.WriteLine(); 205 | } 206 | } 207 | //TODO: We should probably smooth this entire process out. And also use IEnumerable (?) 208 | //Are IEnumerable even faster here? 209 | //Anyway...I'm thinking of a callback solution. I know, I know...we all hate callbacks...but I think they would be a good fit for this use-case 210 | } 211 | catch (Exception e) 212 | { 213 | Console.WriteLine(e.Message); 214 | Console.WriteLine(); 215 | } 216 | } 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /src/Hades.Core/Tools/Hermes.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Core.Tools 2 | { 3 | public class Hermes 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Hades.Core/Tools/ProjectInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using LibGit2Sharp; 7 | 8 | namespace Hades.Core.Tools 9 | { 10 | public static class ProjectInitializer 11 | { 12 | private static readonly Regex simpleName = new Regex("^[^\\.]*$"); 13 | private static readonly Regex orgName = new Regex("^([^\\.]*)\\.([^\\.]*)\\.([^\\.]*)$"); 14 | 15 | public static int Run(List args) 16 | { 17 | var name = args.First(); 18 | var dir = args[1]; //working name 19 | 20 | Repository.Init(dir); //Init git repository 21 | dir += Path.DirectorySeparatorChar; 22 | 23 | var src = dir + "src" + Path.DirectorySeparatorChar; 24 | Directory.CreateDirectory(src); 25 | 26 | //SRC folder 27 | if (simpleName.IsMatch(name)) 28 | { 29 | src += name; 30 | Directory.CreateDirectory(src); 31 | } 32 | else if (orgName.IsMatch(name)) 33 | { 34 | foreach (var s in orgName.Match(name).Groups.Select(a => a.Value).Skip(1).Reverse()) 35 | { 36 | src += s; 37 | Directory.CreateDirectory(src); 38 | src += Path.DirectorySeparatorChar; 39 | } 40 | } 41 | else 42 | { 43 | Console.Error.WriteLine("Could not create project: name has invalid format!"); 44 | return 1; 45 | } 46 | 47 | var mainHd = src + Path.DirectorySeparatorChar + "main.hd"; 48 | File.WriteAllText(mainHd, "with console from std:io\nconsole->out(\"Hello\")"); 49 | 50 | var projectJson = dir + Path.DirectorySeparatorChar + "project.json"; 51 | File.WriteAllText(projectJson, ""); 52 | //TODO: Fill project.json 53 | 54 | Directory.CreateDirectory(dir + "libs"); 55 | 56 | using (var repo = new Repository(dir)) 57 | { 58 | repo.Index.Add(projectJson.Substring(dir.Length + 1, projectJson.Length - dir.Length - 1)); 59 | repo.Index.Add(mainHd.Substring(dir.Length, mainHd.Length - dir.Length).Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}")); 60 | 61 | // Create the commiters signature and commit 62 | var author = new Signature("HadesProjectInitializer", "@hpi", DateTime.Now); 63 | var committer = author; 64 | 65 | // Commit to the repository 66 | repo.Commit("Initial commit", author, committer); 67 | } 68 | 69 | return 0; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Hades.Error/ErrorStrings.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | 3 | namespace Hades.Error 4 | { 5 | public static class ErrorStrings 6 | { 7 | public const string MESSAGE_UNKNOWN_RUNTIME_EXCEPTION = "Unknown runtime exception! Please check the AST! \n{0}"; 8 | public const string MESSAGE_DUPLICATE_VARIABLE_DECLARATION = "Duplicate decleration of variable {0}!"; 9 | 10 | public const string MESSAGE_EXPECTED_TYPE = "Expected a type!"; 11 | public const string MESSAGE_EXPECTED_ACCESS_MODIFIER = "Expected an access modifier!"; 12 | public const string MESSAGE_ILLEGAL_PROTECTED_IN_STRUCT = "A struct cannot contain a protected variable!"; 13 | public const string MESSAGE_UNEXPECTED_CALL_OR_INLINE_IF = "Unexpected call or inline if!"; 14 | public const string MESSAGE_CANT_USE_COMPLEX_LAMBDA_IN_MATCH_BLOCK = "Can't use a complex lambda in a match block!"; 15 | public const string MESSAGE_UNEXPECTED_NODE = "Unexpected node: {0}!"; 16 | public const string MESSAGE_UNEXPECTED_ACCESS_MODIFIER = "Unexpected access modifier!"; 17 | public const string MESSAGE_UNEXPECTED_STATEMENT = "Unexpected statement!"; 18 | public const string MESSAGE_CANT_HAVE_MULTIPLE_VARARGS = "Can't have multiple 'args' parameters in one function!"; 19 | public const string MESSAGE_EXPECTED_IN = "Expected 'in' keyword!"; 20 | public const string MESSAGE_UNEXPECTED_KEYWORD = "Unexpected keyword: {0}!"; 21 | public const string MESSAGE_EXPECTED_LEFT_PARENTHESIS = "Expected left parenthesis!"; 22 | public const string MESSAGE_OVERRIDE_WITHOUT_DECLARATION = "Function can't override another function or an operator without being marked as an override function!"; 23 | public const string MESSAGE_EXPECTED_RIGHT_PARENTHESIS = "Expected right parenthesis!"; 24 | public const string MESSAGE_EXPECTED_VALUE = "Expected {0}!"; 25 | public const string MESSAGE_INVALID_ARRAY_EXPECTED_COMMA = "Invalid array literal! Expected a comma!"; 26 | public const string MESSAGE_EXPECTED_COMMA = "Expected a comma!"; 27 | public const string MESSAGE_EXPECTED_COLON = "Expected a colon!"; 28 | public const string MESSAGE_EXPECTED_PARAMETERS = "Expected parameters!"; 29 | public const string MESSAGE_IMMUTABLE_CANT_BE_NULLABLE = "An immutable variable can't be nullable!"; 30 | public const string MESSAGE_TYPE_INFERRED_CANT_BE_NULLABLE = "A type infered variable can't be nullable!"; 31 | public const string MESSAGE_IMMUTABLE_CANT_BE_DYNAMIC = "An immutable variable can't be dynamic!"; 32 | public const string MESSAGE_DYNAMIC_NOT_POSSIBLE_WITH_STATIC_TYPES = "A variable with a static type can't also be a dynamic variable!"; 33 | public const string MESSAGE_UNEXPECTED_EOF = "Unexpected end of file!"; 34 | public const string MESSAGE_INVALID_LITERAL = "Invalid literal: {0}!"; 35 | public const string MESSAGE_EXPECTED_TOKEN = "Expected token: {0}!"; 36 | public const string MESSAGE_UNEXPECTED_TOKEN = "Unexpected token: {0}!"; 37 | public const string MESSAGE_EXPECTED_IDENTIFIER = "Expected an identifier!"; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Hades.Error/Hades.Error.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Hades.Language/Hades.Language.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Hades.Language/Lexer/Keyword.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Language.Lexer 2 | { 3 | public static class Keyword 4 | { 5 | public const string Class = "class"; 6 | public const string Func = "func"; 7 | public const string Args = "args"; 8 | public const string Requires = "requires"; 9 | public const string If = "if"; 10 | public const string Else = "else"; 11 | public const string While = "while"; 12 | public const string For = "for"; 13 | public const string In = "in"; 14 | public const string Stop = "stop"; 15 | public const string Skip = "skip"; 16 | public const string Try = "try"; 17 | public const string Catch = "catch"; 18 | public const string End = "end"; 19 | public const string Var = "var"; 20 | public const string Let = "let"; 21 | public const string Null = "null"; 22 | public const string Undefined = "undefined"; 23 | public const string Protected = "protected"; 24 | public const string Public = "public"; 25 | public const string Private = "private"; 26 | public const string With = "with"; 27 | public const string From = "from"; 28 | public const string As = "as"; 29 | public const string Sets = "sets"; 30 | public const string Put = "put"; 31 | public const string Raise = "raise"; 32 | public const string Fixed = "fixed"; 33 | public const string Match = "match"; 34 | public const string Struct = "struct"; 35 | public const string Extends = "extends"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Hades.Language/Lexer/Lexer.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Hades.Common.Source; 8 | using Hades.Syntax.Lexeme; 9 | 10 | namespace Hades.Language.Lexer 11 | { 12 | public class Lexer 13 | { 14 | private readonly StringBuilder _builder; 15 | private int _column; 16 | private int _index; 17 | private int _line; 18 | private SourceCode _sourceCode; 19 | private SourceLocation _tokenStart; 20 | 21 | public Lexer() 22 | { 23 | _builder = new StringBuilder(); 24 | _sourceCode = null; 25 | } 26 | 27 | private char Ch => _sourceCode[_index]; 28 | 29 | // ReSharper disable once UnusedMember.Local 30 | private char Last => Peek(-1); 31 | private char Next => Peek(1); 32 | 33 | #region Keywords 34 | 35 | private static readonly string[] BlockKeywords = 36 | { 37 | Keyword.Class, 38 | Keyword.Func, 39 | Keyword.Args, 40 | Keyword.Requires, 41 | Keyword.If, 42 | Keyword.Else, 43 | Keyword.While, 44 | Keyword.For, 45 | Keyword.In, 46 | Keyword.Stop, 47 | Keyword.Skip, 48 | Keyword.Raise, 49 | Keyword.Try, 50 | Keyword.Catch, 51 | Keyword.Struct, 52 | Keyword.Match, 53 | Keyword.Extends, 54 | Keyword.End 55 | }; 56 | 57 | private static readonly string[] VarKeywords = 58 | { 59 | Keyword.Var, 60 | Keyword.Let, 61 | Keyword.Null, 62 | Keyword.Undefined 63 | }; 64 | 65 | private static readonly string[] AccessModifierKeywords = 66 | { 67 | Keyword.Protected, 68 | Keyword.Public, 69 | Keyword.Private 70 | }; 71 | 72 | private static readonly string[] ImportKeywords = 73 | { 74 | Keyword.With, 75 | Keyword.From, 76 | Keyword.As, 77 | Keyword.Sets, 78 | Keyword.Fixed 79 | }; 80 | 81 | private static readonly string[] MiscKeywords = 82 | { 83 | Keyword.Put 84 | }; 85 | 86 | private static List _keywordList = new List(); 87 | 88 | private static List GetKeywordList() 89 | { 90 | if (_keywordList.Count != 0) return _keywordList; 91 | 92 | var list = BlockKeywords.ToList(); 93 | list.AddRange(VarKeywords.ToList()); 94 | list.AddRange(AccessModifierKeywords.ToList()); 95 | list.AddRange(ImportKeywords.ToList()); 96 | list.AddRange(MiscKeywords.ToList()); 97 | 98 | _keywordList = list; 99 | return _keywordList; 100 | } 101 | 102 | public static List Keywords => GetKeywordList(); 103 | 104 | #endregion 105 | 106 | #region Helper 107 | 108 | private void Advance() 109 | { 110 | _index++; 111 | _column++; 112 | } 113 | 114 | private void Consume() 115 | { 116 | _builder.Append(Ch); 117 | Advance(); 118 | } 119 | 120 | private void Clear() 121 | { 122 | _builder.Clear(); 123 | } 124 | 125 | private Token CreateToken(Classifier kind) 126 | { 127 | var contents = _builder.ToString(); 128 | var end = new SourceLocation(_index, _line, _column); 129 | var start = _tokenStart; 130 | 131 | _tokenStart = end; 132 | _builder.Clear(); 133 | 134 | return new Token(kind, contents, start, end); 135 | } 136 | 137 | private void DoNewLine() 138 | { 139 | _line++; 140 | _column = 0; 141 | } 142 | 143 | private char Peek(int ahead) 144 | { 145 | return _sourceCode[_index + ahead]; 146 | } 147 | 148 | #endregion 149 | 150 | #region Checks 151 | 152 | private bool IsDigit() 153 | { 154 | return char.IsDigit(Ch); 155 | } 156 | 157 | // ReSharper disable once InconsistentNaming 158 | private bool IsEOF() 159 | { 160 | return Ch == '\0'; 161 | } 162 | 163 | private bool IsIdentifier() 164 | { 165 | return IsLetterOrDigit() || Ch == '_'; 166 | } 167 | 168 | private bool IsKeyword() 169 | { 170 | return Keywords.Contains(_builder.ToString()); 171 | } 172 | 173 | private bool IsBoolLiteral() 174 | { 175 | var builder = _builder.ToString(); 176 | return builder == "true" || builder == "false"; 177 | } 178 | 179 | private bool IsLetter() 180 | { 181 | return char.IsLetter(Ch); 182 | } 183 | 184 | private bool IsLetterOrDigit() 185 | { 186 | return char.IsLetterOrDigit(Ch); 187 | } 188 | 189 | private bool IsNewLine() 190 | { 191 | return Ch == '\n'; 192 | } 193 | 194 | private bool IsPunctuation() 195 | { 196 | return "<>{}()[]!%^&*+-=/,?:|~#@.".Contains(Ch); 197 | } 198 | 199 | private bool IsWhiteSpace() 200 | { 201 | return (char.IsWhiteSpace(Ch) || IsEOF() || Ch == 8203) && !IsNewLine(); 202 | } 203 | 204 | #endregion 205 | 206 | #region Lexing 207 | 208 | public IEnumerable LexFile(string sourceCode) 209 | { 210 | return LexFile(new SourceCode(sourceCode)); 211 | } 212 | 213 | public IEnumerable LexFile(SourceCode source) 214 | { 215 | _sourceCode = source; 216 | _builder.Clear(); 217 | _line = 1; 218 | _index = 0; 219 | _column = 0; 220 | CreateToken(Classifier.EndOfFile); 221 | 222 | return LexContents(); 223 | } 224 | 225 | private IEnumerable LexContents() 226 | { 227 | while (!IsEOF()) 228 | { 229 | yield return LexToken(); 230 | } 231 | 232 | yield return CreateToken(Classifier.EndOfFile); 233 | } 234 | 235 | private Token LexToken() 236 | { 237 | if (IsEOF()) 238 | { 239 | return CreateToken(Classifier.EndOfFile); 240 | } 241 | 242 | if (IsNewLine()) 243 | { 244 | return ScanNewLine(); 245 | } 246 | 247 | if (IsWhiteSpace()) 248 | { 249 | return ScanWhiteSpace(); 250 | } 251 | 252 | if (IsDigit()) 253 | { 254 | return ScanInteger(); 255 | } 256 | 257 | if (Ch == '/' && (Next == '/' || Next == '*')) 258 | { 259 | return ScanComment(); 260 | } 261 | 262 | if (IsLetter() || Ch == '_' && Ch != '@') 263 | { 264 | return ScanIdentifier(); 265 | } 266 | 267 | if (Ch == '"') 268 | { 269 | return ScanStringLiteral(); 270 | } 271 | 272 | return IsPunctuation() ? ScanPunctuation() : ScanWord(); 273 | } 274 | 275 | private Token ScanBlockComment() 276 | { 277 | bool IsEndOfComment() 278 | { 279 | return Ch == '*' && Next == '/'; 280 | } 281 | 282 | while (!IsEndOfComment()) 283 | { 284 | if (IsEOF()) 285 | { 286 | return CreateToken(Classifier.Error); 287 | } 288 | 289 | if (IsNewLine()) 290 | { 291 | DoNewLine(); 292 | } 293 | 294 | Consume(); 295 | } 296 | 297 | Consume(); 298 | Consume(); 299 | 300 | return CreateToken(Classifier.BlockComment); 301 | } 302 | 303 | private Token ScanComment() 304 | { 305 | Consume(); 306 | if (Ch == '*') 307 | { 308 | return ScanBlockComment(); 309 | } 310 | 311 | Consume(); 312 | 313 | while (!IsNewLine() && !IsEOF()) 314 | { 315 | Consume(); 316 | } 317 | 318 | return CreateToken(Classifier.LineComment); 319 | } 320 | 321 | private Token ScanDec() 322 | { 323 | if (Ch == '.') 324 | { 325 | Consume(); 326 | } 327 | 328 | while (IsDigit()) 329 | { 330 | Consume(); 331 | } 332 | 333 | if (Ch == 'f') 334 | { 335 | Consume(); 336 | } 337 | 338 | if (!IsWhiteSpace() && !IsPunctuation() && !IsEOF() && !IsNewLine()) 339 | { 340 | if (IsLetter()) 341 | { 342 | return ScanWord("'{0}' is an invalid float value"); 343 | } 344 | 345 | return ScanWord(); 346 | } 347 | 348 | return CreateToken(Classifier.DecLiteral); 349 | } 350 | 351 | private Token ScanIdentifier() 352 | { 353 | while (IsIdentifier()) 354 | { 355 | Consume(); 356 | } 357 | 358 | if (!IsWhiteSpace() && !IsPunctuation() && !IsEOF() && !IsNewLine()) 359 | { 360 | return ScanWord(); 361 | } 362 | 363 | if (IsBoolLiteral()) 364 | { 365 | return CreateToken(Classifier.BoolLiteral); 366 | } 367 | 368 | switch (_builder.ToString()) 369 | { 370 | case "and": 371 | return CreateToken(Classifier.BooleanAnd); 372 | case "or": 373 | return CreateToken(Classifier.BooleanOr); 374 | case "not": 375 | return CreateToken(Classifier.Not); 376 | case "is": 377 | return CreateToken(Classifier.Equal); 378 | } 379 | 380 | return CreateToken(IsKeyword() ? Classifier.Keyword : Classifier.Identifier); 381 | } 382 | 383 | private Token ScanInteger() 384 | { 385 | var i = 0; 386 | var idx = _index; 387 | 388 | //TODO: Support for hex and binary digits 389 | 390 | while (IsDigit()) 391 | { 392 | Consume(); 393 | i++; 394 | } 395 | 396 | if (Ch == '.') 397 | { 398 | return i > 0 ? ScanDec() : ScanWord("Literal can't start with ."); 399 | } 400 | 401 | if (!IsWhiteSpace() && !IsPunctuation() && !IsEOF() && !IsNewLine()) 402 | { 403 | _index = idx; 404 | Clear(); 405 | return ScanIdentifier(); 406 | } 407 | 408 | return CreateToken(Classifier.IntLiteral); 409 | } 410 | 411 | private Token ScanNewLine() 412 | { 413 | Consume(); 414 | 415 | DoNewLine(); 416 | 417 | return CreateToken(Classifier.NewLine); 418 | } 419 | 420 | private Token ScanPunctuation() 421 | { 422 | switch (Ch) 423 | { 424 | case ':': 425 | Consume(); 426 | if (Ch != ':') return CreateToken(Classifier.Colon); 427 | Consume(); 428 | return CreateToken(Classifier.NullCondition); 429 | 430 | case '{': 431 | Consume(); 432 | return CreateToken(Classifier.LeftBracket); 433 | 434 | case '}': 435 | Consume(); 436 | return CreateToken(Classifier.RightBracket); 437 | 438 | case '[': 439 | Consume(); 440 | return CreateToken(Classifier.LeftBrace); 441 | 442 | case ']': 443 | Consume(); 444 | return CreateToken(Classifier.RightBrace); 445 | 446 | case '(': 447 | Consume(); 448 | return CreateToken(Classifier.LeftParenthesis); 449 | 450 | case ')': 451 | Consume(); 452 | return CreateToken(Classifier.RightParenthesis); 453 | 454 | case '>': 455 | Consume(); 456 | switch (Ch) 457 | { 458 | case '=': 459 | Consume(); 460 | return CreateToken(Classifier.GreaterThanOrEqual); 461 | case '>': 462 | Consume(); 463 | if (Ch == '=') 464 | { 465 | Consume(); 466 | return CreateToken(Classifier.BitShiftRightEqual); 467 | } 468 | 469 | return CreateToken(Classifier.BitShiftRight); 470 | default: 471 | return CreateToken(Classifier.GreaterThan); 472 | } 473 | 474 | case '<': 475 | Consume(); 476 | switch (Ch) 477 | { 478 | case '=': 479 | Consume(); 480 | return CreateToken(Classifier.LessThanOrEqual); 481 | case '<': 482 | Consume(); 483 | if (Ch != '=') return CreateToken(Classifier.BitShiftLeft); 484 | Consume(); 485 | return CreateToken(Classifier.BitShiftLeftEqual); 486 | default: 487 | return CreateToken(Classifier.LessThan); 488 | } 489 | 490 | case '+': 491 | Consume(); 492 | switch (Ch) 493 | { 494 | case '=': 495 | Consume(); 496 | return CreateToken(Classifier.PlusEqual); 497 | case '+': 498 | Consume(); 499 | return CreateToken(Classifier.PlusPlus); 500 | default: 501 | return CreateToken(Classifier.Plus); 502 | } 503 | 504 | case '-': 505 | Consume(); 506 | switch (Ch) 507 | { 508 | case '=': 509 | Consume(); 510 | return CreateToken(Classifier.MinusEqual); 511 | case '>': 512 | Consume(); 513 | return CreateToken(Classifier.Arrow); 514 | case '-': 515 | Consume(); 516 | return CreateToken(Classifier.MinusMinus); 517 | default: 518 | return CreateToken(Classifier.Minus); 519 | } 520 | 521 | case '=': 522 | Consume(); 523 | switch (Ch) 524 | { 525 | case '=': 526 | Consume(); 527 | return CreateToken(Classifier.Equal); 528 | case '>': 529 | Consume(); 530 | return CreateToken(Classifier.FatArrow); 531 | default: 532 | return CreateToken(Classifier.Assignment); 533 | } 534 | 535 | case '!': 536 | Consume(); 537 | if (Ch != '=') return CreateToken(Classifier.Not); 538 | Consume(); 539 | return CreateToken(Classifier.NotEqual); 540 | 541 | case '*': 542 | Consume(); 543 | if (Ch != '=') return CreateToken(Classifier.Mul); 544 | Consume(); 545 | return CreateToken(Classifier.MulEqual); 546 | 547 | case '/': 548 | Consume(); 549 | if (Ch != '=') return CreateToken(Classifier.Div); 550 | Consume(); 551 | return CreateToken(Classifier.DivEqual); 552 | 553 | case ',': 554 | Consume(); 555 | return CreateToken(Classifier.Comma); 556 | 557 | case '&': 558 | Consume(); 559 | switch (Ch) 560 | { 561 | case '&': 562 | Consume(); 563 | return CreateToken(Classifier.BooleanAnd); 564 | case '=': 565 | Consume(); 566 | return CreateToken(Classifier.BitwiseAndEqual); 567 | default: 568 | return CreateToken(Classifier.BitwiseAnd); 569 | } 570 | 571 | case '|': 572 | Consume(); 573 | switch (Ch) 574 | { 575 | case '|': 576 | Consume(); 577 | return CreateToken(Classifier.BooleanOr); 578 | case '=': 579 | Consume(); 580 | return CreateToken(Classifier.BitwiseOrEqual); 581 | case '>': 582 | Consume(); 583 | return CreateToken(Classifier.Pipeline); 584 | default: 585 | return CreateToken(Classifier.BitwiseOr); 586 | } 587 | 588 | case '%': 589 | Consume(); 590 | if (Ch != '=') return CreateToken(Classifier.Mod); 591 | Consume(); 592 | return CreateToken(Classifier.ModEqual); 593 | 594 | case '^': 595 | Consume(); 596 | if (Ch != '=') return CreateToken(Classifier.BitwiseXor); 597 | Consume(); 598 | return CreateToken(Classifier.BitwiseXorEqual); 599 | 600 | case '~': 601 | Consume(); 602 | if (Ch != '=') return CreateToken(Classifier.BitwiseNegate); 603 | Consume(); 604 | return CreateToken(Classifier.BitwiseNegateEqual); 605 | 606 | case '@': 607 | Consume(); 608 | return CreateToken(Classifier.At); 609 | 610 | case '#': 611 | Consume(); 612 | return CreateToken(Classifier.Tag); 613 | 614 | case '?': 615 | Consume(); 616 | if (Ch != '?') return CreateToken(Classifier.Question); 617 | Consume(); 618 | return CreateToken(Classifier.DoubleQuestion); 619 | 620 | case '.': 621 | Consume(); 622 | return CreateToken(Classifier.Dot); 623 | 624 | default: return ScanWord(); 625 | } 626 | } 627 | 628 | private Token ScanStringLiteral() 629 | { 630 | Advance(); 631 | 632 | var multiLine = Peek(0) == '"' && Peek(1) == '"'; 633 | var hasError = false; 634 | 635 | if (multiLine) 636 | { 637 | _index++; 638 | _index++; 639 | } 640 | 641 | while (true) 642 | { 643 | if (IsEOF()) 644 | { 645 | throw new Exception("Unexpected End Of File"); 646 | } 647 | 648 | if (IsNewLine() && !multiLine) 649 | { 650 | throw new Exception("No newline in strings allowed!"); 651 | } 652 | 653 | var consume = true; 654 | 655 | if (Ch == '\\' && Next == '"') 656 | { 657 | Consume(); 658 | Consume(); 659 | consume = false; 660 | } 661 | 662 | if (Ch == '"') 663 | { 664 | if (multiLine) 665 | { 666 | if (Peek(1) == '"' && Peek(2) == '"') 667 | { 668 | _index++; 669 | _index++; 670 | 671 | Advance(); 672 | return CreateToken(Classifier.MultiLineStringLiteral); 673 | } 674 | } 675 | else 676 | { 677 | break; 678 | } 679 | } 680 | 681 | if (consume) 682 | { 683 | Consume(); 684 | } 685 | } 686 | 687 | Advance(); 688 | 689 | return hasError ? CreateToken(Classifier.Error) : CreateToken(Classifier.StringLiteral); 690 | } 691 | 692 | private Token ScanWhiteSpace() 693 | { 694 | while (IsWhiteSpace() && Ch != '\0') 695 | { 696 | Consume(); 697 | } 698 | 699 | return CreateToken(Classifier.WhiteSpace); 700 | } 701 | 702 | private Token ScanWord(string message = "Unexpected Token '{0}'") 703 | { 704 | while (!IsWhiteSpace() && !IsEOF() && !IsPunctuation()) 705 | { 706 | Consume(); 707 | } 708 | 709 | throw new Exception(string.Format(message, _builder)); 710 | } 711 | 712 | #endregion 713 | } 714 | } -------------------------------------------------------------------------------- /src/Hades.Language/Lexer/LexerGrammar.ebnf: -------------------------------------------------------------------------------- 1 | letter = ? UNICODE Letter ? ; 2 | digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 3 | 4 | identifier = ( letter | "_" ), { letter | digit | "_" } ; 5 | string = "\"", { letter - new line }, "\"" ; 6 | multiline strings = "\"\"\"", { letter }, "\"\"\"" ; 7 | 8 | int = digit, { digit } ; 9 | bool = "true" | "false" ; 10 | dec = int, ".", int ; 11 | 12 | block keywords = "class" | "func" | "args" | "requires" | "if" | "else" | "while" | "for" | "in" | "stop" | "skip" | "try" | "catch" | "default" | "end" ; 13 | var keywords = "var" | "let" | "null" | "undefined" ; 14 | access modifier keywords = "global" | "public" | "private" ; 15 | comparison keywords = "is" | "not" | "and" | "or" ; 16 | import keywords = "with" | "from" | "as" | "sets"; 17 | misc keywords = "put" 18 | 19 | keyword = block keywords | var keywords | access modifier keywords | comparison keywords | import keywords ; 20 | 21 | new line = \n ; 22 | 23 | lineComment = "//", { letter - new line }, + new line ; 24 | blockComment = "/*", { letter }, "*/" ; 25 | 26 | arithmetic punctuation = "+" | "-" | "*" | "/" | "%" | "<" | "<=" | ">" | ">=" ; 27 | logical punctuation = ""==" | "!=" | "&&" | "||" ; 28 | bitwise punctuation = "<<" | ">>" | "&" | "|" | "^" | "~" ; 29 | misc punctuation = "!" | "@" | "*" | "=" | "|>" | "#"; 30 | punctuation = arithmetic punctuation | logical punctuation | bitwise punctuation | misc punctuation ; -------------------------------------------------------------------------------- /src/Hades.Language/Parser/Parser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Hades.Common; 5 | using Hades.Error; 6 | using Hades.Language.Lexer; 7 | using Hades.Syntax.Expression; 8 | using Hades.Syntax.Expression.Nodes; 9 | using Hades.Syntax.Expression.Nodes.BlockNodes; 10 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 11 | using Hades.Syntax.Lexeme; 12 | using Classifier = Hades.Syntax.Lexeme.Classifier; 13 | // ReSharper disable AccessToModifiedClosure 14 | 15 | namespace Hades.Language.Parser 16 | { 17 | public class Parser 18 | { 19 | private readonly IEnumerable _tokens; 20 | private int _index; 21 | private bool _repl; 22 | 23 | public Parser(IEnumerable tokens, bool repl) 24 | { 25 | _tokens = tokens.ToList().Where(a => a.Kind != Classifier.WhiteSpace && a.Category != Category.Comment && a.Kind != Classifier.NewLine); 26 | _index = 0; 27 | _repl = repl; 28 | } 29 | 30 | private Token Current => _tokens.ElementAtOrDefault(_index) ?? _tokens.Last(); 31 | private Token Last => Peek(-1); 32 | private Token Next => Peek(1); 33 | 34 | #region Helper 35 | 36 | private Token Peek(int ahead) 37 | { 38 | return _tokens.ElementAtOrDefault(_index + ahead) ?? _tokens.Last(); 39 | } 40 | 41 | private void Advance(int i = 0) 42 | { 43 | _index += i != 0 ? i : 1; 44 | } 45 | 46 | private void Error(string error) 47 | { 48 | throw new Exception($"{error} {Current.Span.Start.Line}:{Current.Span.Start.Index}"); 49 | } 50 | 51 | private void Error(string error, params object[] format) 52 | { 53 | Error(string.Format(error, format)); 54 | } 55 | 56 | private BlockNode ReadToEnd(BlockNode node, bool allowSkipStop = false, List keywords = null) 57 | { 58 | if (keywords == null) 59 | { 60 | keywords = new List {Keyword.End}; 61 | } 62 | 63 | while (!Is(keywords)) 64 | { 65 | if (IsEof()) 66 | { 67 | Error(ErrorStrings.MESSAGE_UNEXPECTED_EOF); 68 | } 69 | 70 | node.Children.Add(ParseNext(allowSkipStop)); 71 | } 72 | 73 | Advance(); 74 | node.Children = node.Children.Where(a => a != null).ToList(); 75 | return node; 76 | } 77 | 78 | private void ExpectIdentifier() 79 | { 80 | if (!Expect(Classifier.Identifier)) 81 | { 82 | Error(ErrorStrings.MESSAGE_EXPECTED_IDENTIFIER); 83 | } 84 | } 85 | 86 | private void EnforceIdentifier() 87 | { 88 | Advance(-1); 89 | ExpectIdentifier(); 90 | Advance(); 91 | } 92 | 93 | /// 94 | /// Gets the specific type of an object or proto 95 | /// ``` 96 | /// func doStuff(object::IClient a) 97 | /// a->stuff("Hello world") 98 | /// end 99 | /// ``` 100 | /// 101 | /// Specific type or null 102 | private (string specificType, Datatype dt) GetSpecificType() 103 | { 104 | var dt = (Datatype) Enum.Parse(typeof(Datatype), Current.Value.ToUpper()); 105 | string type = null; 106 | 107 | if (dt == Datatype.PROTO || dt == Datatype.OBJECT || dt == Datatype.STRUCT) 108 | { 109 | Advance(); 110 | if (Is(Classifier.NullCondition)) 111 | { 112 | ExpectIdentifier(); 113 | Advance(); 114 | type = Current.Value; 115 | Advance(); 116 | } 117 | else 118 | { 119 | Advance(-1); 120 | } 121 | } 122 | 123 | return (type, dt); 124 | } 125 | 126 | private List<(Node Key, Datatype? Value, string SpecificType)> ParseArguments(Classifier expectedClassifier, string expect) 127 | { 128 | var args = new List<(Node Key, Datatype? Value, string SpecificType)>(); 129 | do 130 | { 131 | Advance(); 132 | if (IsType()) 133 | { 134 | var (type, dt) = GetSpecificType(); 135 | 136 | if (type == null) 137 | { 138 | Advance(); 139 | } 140 | 141 | EnforceIdentifier(); 142 | args.Add((new IdentifierNode(Current.Value), dt, type)); 143 | Advance(); 144 | } 145 | else if (Is(Keyword.Args)) 146 | { 147 | Advance(); 148 | if (IsType()) 149 | { 150 | var (type, dt) = GetSpecificType(); 151 | 152 | if (type == null) 153 | { 154 | Advance(); 155 | } 156 | 157 | EnforceIdentifier(); 158 | args.Add((new ArgsNode(Current.Value), dt, type)); 159 | } 160 | else 161 | { 162 | EnforceIdentifier(); 163 | args.Add((new ArgsNode(Current.Value), Datatype.NONE, null)); 164 | } 165 | 166 | Advance(); 167 | } 168 | else if (IsIdentifier()) 169 | { 170 | args.Add((new IdentifierNode(Current.Value), null, null)); 171 | Advance(); 172 | } 173 | else 174 | { 175 | Error(ErrorStrings.MESSAGE_EXPECTED_IDENTIFIER); 176 | } 177 | } while (Is(Classifier.Comma)); 178 | 179 | if (args.Any(a => a.Key is ArgsNode)) 180 | { 181 | if (args.Count(a => a.Key is ArgsNode) > 1) 182 | { 183 | Error(ErrorStrings.MESSAGE_CANT_HAVE_MULTIPLE_VARARGS); 184 | } 185 | } 186 | 187 | if (!Is(expectedClassifier)) 188 | { 189 | Error(ErrorStrings.MESSAGE_EXPECTED_VALUE, expect); 190 | } 191 | 192 | Advance(); 193 | 194 | return args; 195 | } 196 | 197 | #endregion 198 | 199 | #region Checks 200 | 201 | private bool IsEof() 202 | { 203 | return Is(Classifier.EndOfFile); 204 | } 205 | 206 | private bool IsKeyword() 207 | { 208 | return Lexer.Lexer.Keywords.Contains(Current.Value); 209 | } 210 | 211 | private bool Type(Token token) 212 | { 213 | return Enum.GetValues(typeof(Datatype)).Cast().Select(a => a.ToString().ToLower()).Contains(token.Value); 214 | } 215 | 216 | private bool IsType() 217 | { 218 | return Type(Current); 219 | } 220 | 221 | private bool ExpectType() 222 | { 223 | return Type(Next); 224 | } 225 | 226 | private bool IsIdentifier() 227 | { 228 | return Is(Classifier.Identifier); 229 | } 230 | 231 | private bool Was(Classifier classifier) 232 | { 233 | return Last == classifier; 234 | } 235 | 236 | private bool Was(string token) 237 | { 238 | return Last == token; 239 | } 240 | 241 | private bool Is(string token) 242 | { 243 | return Current == token; 244 | } 245 | 246 | private bool Is(IEnumerable tokens) 247 | { 248 | return tokens.Any(token => token == Current); 249 | } 250 | 251 | private bool Is(Classifier classifier) 252 | { 253 | return Current == classifier; 254 | } 255 | 256 | private bool IsAccessModifier() 257 | { 258 | return Is(Keyword.Private) || Is(Keyword.Public) || Is(Keyword.Protected); 259 | } 260 | 261 | private bool Is(Category category) 262 | { 263 | return Current.Category == category; 264 | } 265 | 266 | private bool Expect(string token) 267 | { 268 | return Next == token; 269 | } 270 | 271 | private bool Expect(Classifier classifier) 272 | { 273 | return Next == classifier; 274 | } 275 | 276 | private bool Expect(Category category) 277 | { 278 | return Next.Category == category; 279 | } 280 | 281 | #endregion 282 | 283 | #region Parsing 284 | 285 | #region Blocks 286 | 287 | private Node ParseStruct(AccessModifier accessModifier) 288 | { 289 | Advance(); 290 | EnforceIdentifier(); 291 | 292 | var node = new StructNode{Name = Current.Value, AccessModifier = accessModifier}; 293 | Advance(); 294 | 295 | while (!Is(Keyword.End)) 296 | { 297 | if (IsAccessModifier() && (Expect(Keyword.Var) || Expect(Keyword.Let))) 298 | { 299 | Advance(); 300 | switch (Enum.Parse(Last.Value.First().ToString().ToUpper() + Last.Value.Substring(1))) 301 | { 302 | case AccessModifier.Protected: 303 | Error(ErrorStrings.MESSAGE_ILLEGAL_PROTECTED_IN_STRUCT); 304 | break; 305 | case AccessModifier.Public: 306 | node.PublicVariables.Add(ParseNext() as VariableDeclarationNode); 307 | break; 308 | default: 309 | node.PrivateVariables.Add(ParseNext() as VariableDeclarationNode); 310 | break; 311 | } 312 | } 313 | else 314 | { 315 | var childNode = ParseNext(); 316 | 317 | if (childNode is VariableDeclarationNode vn) 318 | { 319 | node.PrivateVariables.Add(vn); 320 | } 321 | else if (childNode is VarBlockNode vbn) 322 | { 323 | switch (vbn.AccessModifier) 324 | { 325 | case AccessModifier.Protected: 326 | Error(ErrorStrings.MESSAGE_ILLEGAL_PROTECTED_IN_STRUCT); 327 | break; 328 | case AccessModifier.Public: 329 | node.PublicVariables.AddRange(vbn.VariableDeclarationNodes); 330 | break; 331 | default: 332 | node.PrivateVariables.AddRange(vbn.VariableDeclarationNodes); 333 | break; 334 | } 335 | } 336 | else 337 | { 338 | Error(ErrorStrings.MESSAGE_UNEXPECTED_NODE, childNode.GetType().Name.Replace("Node", "")); 339 | } 340 | } 341 | } 342 | 343 | Advance(); 344 | return node; 345 | } 346 | 347 | private Node ParseClass(bool isFixed, AccessModifier accessModifier) 348 | { 349 | Advance(); 350 | EnforceIdentifier(); 351 | 352 | var node = new ClassNode {Name = Current.Value, Fixed = isFixed, AccessModifier = accessModifier}; 353 | 354 | Advance(); 355 | 356 | if (Is(Classifier.LessThan)) 357 | { 358 | do 359 | { 360 | Advance(); 361 | EnforceIdentifier(); 362 | node.Parents.Add(Current.Value); 363 | Advance(); 364 | } while (Is(Classifier.Comma)); 365 | 366 | Advance(-1); 367 | } 368 | 369 | while (!Is(Keyword.End)) 370 | { 371 | if (IsAccessModifier() && (Expect(Keyword.Var) || Expect(Keyword.Let))) 372 | { 373 | Advance(); 374 | switch (Enum.Parse(Last.Value.First().ToString().ToUpper() + Last.Value.Substring(1))) 375 | { 376 | case AccessModifier.Protected: 377 | node.ProtectedVariables.Add(ParseNext() as VariableDeclarationNode); 378 | break; 379 | case AccessModifier.Public: 380 | node.PublicVariables.Add(ParseNext() as VariableDeclarationNode); 381 | break; 382 | default: 383 | node.PrivateVariables.Add(ParseNext() as VariableDeclarationNode); 384 | break; 385 | } 386 | } 387 | else 388 | { 389 | var childNode = ParseNext(); 390 | 391 | if (childNode is VariableDeclarationNode vn) 392 | { 393 | node.PrivateVariables.Add(vn); 394 | } 395 | else if (childNode is FunctionNode fn) 396 | { 397 | if (fn.Name == node.Name) 398 | { 399 | node.Constructors.Add(fn); 400 | } 401 | else 402 | { 403 | node.Functions.Add(fn); 404 | } 405 | } 406 | else if (childNode is ClassNode cn) 407 | { 408 | node.Classes.Add(cn); 409 | } 410 | else if (childNode is StructNode sn) 411 | { 412 | node.Structs.Add(sn); 413 | } 414 | else if (childNode is VarBlockNode vbn) 415 | { 416 | switch (vbn.AccessModifier) 417 | { 418 | case AccessModifier.Protected: 419 | node.ProtectedVariables.AddRange(vbn.VariableDeclarationNodes); 420 | break; 421 | case AccessModifier.Public: 422 | node.PublicVariables.AddRange(vbn.VariableDeclarationNodes); 423 | break; 424 | default: 425 | node.PrivateVariables.AddRange(vbn.VariableDeclarationNodes); 426 | break; 427 | } 428 | } 429 | else 430 | { 431 | Error(ErrorStrings.MESSAGE_UNEXPECTED_NODE, childNode.GetType().Name.Replace("Node", "")); 432 | } 433 | } 434 | } 435 | 436 | Advance(); 437 | return node; 438 | } 439 | 440 | private Node ParseFunc(bool isFixed, AccessModifier accessModifier) 441 | { 442 | Advance(); 443 | var node = new FunctionNode {Fixed = isFixed, AccessModifier = accessModifier}; 444 | if (Is(Classifier.Not)) 445 | { 446 | node.Override = true; 447 | Advance(); 448 | } 449 | 450 | if (Is(Keyword.Extends)) 451 | { 452 | //TODO: Test this lol 453 | node.Extension = true; 454 | Advance(); 455 | 456 | if (!IsType()) 457 | { 458 | Error(ErrorStrings.MESSAGE_EXPECTED_TYPE); 459 | } 460 | 461 | var specificType = GetSpecificType(); 462 | 463 | if (string.IsNullOrEmpty(specificType.specificType)) 464 | { 465 | Advance(); 466 | } 467 | 468 | node.ExtensionType = specificType; 469 | } 470 | 471 | EnforceIdentifier(); 472 | 473 | if (Expect(Category.Operator)) 474 | { 475 | if (!node.Override) 476 | { 477 | Error(ErrorStrings.MESSAGE_OVERRIDE_WITHOUT_DECLARATION); 478 | } 479 | 480 | Advance(); 481 | node.Name = Current.Value; 482 | Advance(); 483 | } 484 | else 485 | { 486 | EnforceIdentifier(); 487 | 488 | node.Name = Current.Value; 489 | Advance(); 490 | } 491 | 492 | if (!Is(Classifier.LeftParenthesis)) 493 | { 494 | Error(ErrorStrings.MESSAGE_EXPECTED_LEFT_PARENTHESIS); 495 | } 496 | 497 | if (!Expect(Classifier.RightParenthesis)) 498 | { 499 | node.Parameters = ParseArguments(Classifier.RightParenthesis, "right parenthesis"); 500 | } 501 | 502 | if (Is(Keyword.Requires)) 503 | { 504 | Advance(); 505 | node.Guard = ParseStatement(); 506 | } 507 | 508 | return ReadToEnd(node); 509 | } 510 | 511 | private Node GetCondition() 512 | { 513 | if (!Is(Classifier.LeftParenthesis)) 514 | { 515 | Error(ErrorStrings.MESSAGE_EXPECTED_LEFT_PARENTHESIS); 516 | } 517 | 518 | Advance(); 519 | 520 | var node = ParseStatement(); 521 | 522 | if (!Is(Classifier.RightParenthesis)) 523 | { 524 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 525 | } 526 | 527 | Advance(); 528 | return node; 529 | } 530 | 531 | private Node ParseVariableGroup() 532 | { 533 | var node = new VarBlockNode(); 534 | Advance(); 535 | if (!IsAccessModifier()) 536 | { 537 | Error(ErrorStrings.MESSAGE_EXPECTED_ACCESS_MODIFIER); 538 | } 539 | 540 | node.AccessModifier = Enum.Parse(Current.Value.First().ToString().ToUpper() + Current.Value.Substring(1)); 541 | Advance(); 542 | 543 | while (!Is(Keyword.End)) 544 | { 545 | if (Is(Keyword.Var) || Is(Keyword.Let)) 546 | { 547 | node.VariableDeclarationNodes.Add(ParseNext() as VariableDeclarationNode); 548 | } 549 | } 550 | 551 | Advance(); 552 | return node; 553 | } 554 | 555 | private Node ParseMatch(bool allowSkipStop) 556 | { 557 | Advance(); 558 | var node = new MatchNode {First = Is("first")}; 559 | 560 | if (Is("first")) 561 | { 562 | Advance(); 563 | } 564 | 565 | if (!Is(Classifier.LeftParenthesis)) 566 | { 567 | Error(ErrorStrings.MESSAGE_EXPECTED_LEFT_PARENTHESIS); 568 | } 569 | 570 | Advance(); 571 | 572 | node.Match = Is(Classifier.Underscore /*No statement*/) ? new NoVariableNode() : ParseStatement(); 573 | 574 | if (!Is(Classifier.RightParenthesis)) 575 | { 576 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 577 | } 578 | 579 | Advance(); 580 | 581 | while (!Is(Keyword.End)) 582 | { 583 | var cond = ParseStatement(); 584 | 585 | if (!Is(Classifier.FatArrow)) 586 | { 587 | Error(ErrorStrings.MESSAGE_EXPECTED_TOKEN, "=>"); 588 | } 589 | 590 | Advance(); 591 | 592 | var action = ParseStatement(allowSkipStop); 593 | 594 | if (action is LambdaNode ln) 595 | { 596 | if (ln.Complex) 597 | { 598 | Error(ErrorStrings.MESSAGE_CANT_USE_COMPLEX_LAMBDA_IN_MATCH_BLOCK); 599 | } 600 | } 601 | 602 | //Actually...mostly anything can be an action if you think about it. 603 | //We could have something like "Hello" => a->getAction(10) 604 | //So here, the action would be a call which would return a lambda 605 | //The only thing we *really* can't have is a complex lambda 606 | 607 | node.Statements.Add(cond, action); 608 | } 609 | 610 | Advance(); 611 | 612 | return node; 613 | } 614 | 615 | private Node ParseWhile() 616 | { 617 | Advance(); 618 | var node = new WhileNode {Condition = GetCondition()}; 619 | 620 | return ReadToEnd(node, true); 621 | } 622 | 623 | private Node ParseIf(bool allowSkipStop) 624 | { 625 | Advance(); 626 | var node = new IfNode {Condition = GetCondition(), If = ReadToEnd(new GenericBlockNode(), allowSkipStop, new List {Keyword.End, Keyword.Else})}; 627 | 628 | 629 | if (Was(Keyword.End)) 630 | { 631 | return node; 632 | } 633 | 634 | while (Was(Keyword.Else) && Is(Keyword.If)) 635 | { 636 | Advance(); 637 | node.ElseIfNodes.Add(new IfNode {Condition = GetCondition(), If = ReadToEnd(new GenericBlockNode(), allowSkipStop, new List {Keyword.End, Keyword.Else})}); 638 | } 639 | 640 | if (Was(Keyword.Else)) 641 | { 642 | node.Else = ReadToEnd(new GenericBlockNode(), allowSkipStop); 643 | } 644 | 645 | return node; 646 | } 647 | 648 | private Node ParseFor() 649 | { 650 | Advance(); 651 | var node = new ForNode(); 652 | 653 | if (!Is(Classifier.LeftParenthesis)) 654 | { 655 | Error(ErrorStrings.MESSAGE_EXPECTED_LEFT_PARENTHESIS); 656 | } 657 | 658 | Advance(); 659 | 660 | if (Is("_")) 661 | { 662 | node.Variable = new NoVariableNode(); 663 | Advance(); 664 | } 665 | else 666 | { 667 | var index = _index; 668 | try 669 | { 670 | node.Variable = ParseVariableDeclaration(); 671 | } 672 | catch (Exception) 673 | { 674 | _index = index; 675 | EnforceIdentifier(); 676 | 677 | node.Variable = new IdentifierNode(Current.Value); 678 | Advance(); 679 | } 680 | } 681 | 682 | if (!Is(Keyword.In)) 683 | { 684 | Error(ErrorStrings.MESSAGE_EXPECTED_IN); 685 | } 686 | 687 | Advance(); 688 | 689 | node.Source = ParseStatement(); 690 | 691 | if (!Is(Classifier.RightParenthesis)) 692 | { 693 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 694 | } 695 | 696 | Advance(); 697 | 698 | return ReadToEnd(node, true); 699 | } 700 | 701 | private Node ParseTryCatchElse(bool allowSkipStop) 702 | { 703 | Advance(); 704 | var node = new TryCatchElseNode {Try = ReadToEnd(new GenericBlockNode(), allowSkipStop, new List {Keyword.Catch, Keyword.Else})}; 705 | 706 | 707 | while (Was(Keyword.Catch)) 708 | { 709 | var catchNode = new TryCatchElseNode.CatchBlock(); 710 | 711 | if (!Is(Classifier.LeftParenthesis)) 712 | { 713 | Error(ErrorStrings.MESSAGE_EXPECTED_LEFT_PARENTHESIS); 714 | } 715 | 716 | Advance(); 717 | 718 | if (IsType()) 719 | { 720 | (catchNode.SpecificType, catchNode.Datatype) = GetSpecificType(); 721 | 722 | if (catchNode.SpecificType == null) 723 | { 724 | Advance(); 725 | } 726 | } 727 | 728 | EnforceIdentifier(); 729 | catchNode.Name = Current.Value; 730 | Advance(); 731 | 732 | if (!Is(Classifier.RightParenthesis)) 733 | { 734 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 735 | } 736 | 737 | Advance(); 738 | 739 | catchNode.Block = ReadToEnd(new GenericBlockNode(), allowSkipStop, new List {Keyword.Catch, Keyword.End, Keyword.Else}); 740 | node.Catch.Add(catchNode); 741 | } 742 | 743 | if (Was(Keyword.Else)) 744 | { 745 | node.Else = ReadToEnd(new GenericBlockNode(), allowSkipStop); 746 | } 747 | 748 | return node; 749 | } 750 | 751 | #endregion 752 | 753 | #region Statements 754 | 755 | private Node ParsePackageImport() 756 | { 757 | var node = new WithNode(); 758 | Advance(); 759 | 760 | EnforceIdentifier(); 761 | 762 | node.Target = Current.Value; 763 | 764 | if (Expect(Keyword.Fixed)) 765 | { 766 | node.Fixed = true; 767 | Advance(); 768 | } 769 | 770 | if (Expect(Keyword.As)) 771 | { 772 | Advance(2); 773 | 774 | 775 | EnforceIdentifier(); 776 | 777 | node.Name = Current.Value; 778 | } 779 | 780 | Advance(); 781 | 782 | if (Is(Keyword.From)) //with x FROM ... 783 | { 784 | Advance(); 785 | EnforceIdentifier(); 786 | 787 | if (Expect(Classifier.Colon)) 788 | { 789 | node.Native = true; 790 | node.NativePackage = Current.Value; 791 | 792 | Advance(2); 793 | EnforceIdentifier(); 794 | 795 | node.Source = Current.Value; 796 | } 797 | else 798 | { 799 | node.Source = Current.Value; 800 | } 801 | 802 | Advance(); 803 | } 804 | 805 | return node; 806 | } 807 | 808 | private Node ParseCall(Node baseNode) 809 | { 810 | EnforceIdentifier(); 811 | 812 | var node = new CallNode {Source = baseNode, Target = new IdentifierNode(Current.Value)}; 813 | Advance(); 814 | 815 | return ParseCallSignature(node, true); 816 | } 817 | 818 | private Node ParseCallSignature(CallNode node, bool parseValueCall) 819 | { 820 | if (Is(Classifier.LeftParenthesis)) 821 | { 822 | Advance(); 823 | if (Is(Classifier.RightParenthesis)) 824 | { 825 | Advance(); 826 | } 827 | else 828 | { 829 | do 830 | { 831 | var name = ""; 832 | if (Is(Classifier.Identifier) && Expect(Classifier.Assignment)) 833 | { 834 | name = Current.Value; 835 | Advance(2); 836 | } 837 | 838 | node.Parameters.Add(ParseStatement(), name); 839 | 840 | if (!Is(Classifier.RightParenthesis)) 841 | { 842 | if (!Is(Classifier.Comma)) 843 | { 844 | Error(ErrorStrings.MESSAGE_EXPECTED_COMMA); 845 | } 846 | 847 | Advance(); 848 | } 849 | } while (!Is(Classifier.RightParenthesis)); 850 | 851 | Advance(); 852 | } 853 | } 854 | else 855 | { 856 | if (parseValueCall) 857 | { 858 | return new ValueCallNode {Source = node.Source, Target = node.Target}; 859 | } 860 | 861 | Error(ErrorStrings.MESSAGE_EXPECTED_PARAMETERS); 862 | } 863 | 864 | return node; 865 | } 866 | 867 | private Node ParseDeepCall(Node node) 868 | { 869 | Node deepCall = null; 870 | 871 | while (Is(Classifier.Arrow)) 872 | { 873 | Advance(); 874 | deepCall = ParseCall(deepCall ?? node); 875 | } 876 | 877 | return deepCall; 878 | } 879 | 880 | private Node ParseInlineIf(Node node) 881 | { 882 | Advance(); 883 | var truthy = ParseStatement(); 884 | 885 | if (!Is(Classifier.Colon)) 886 | { 887 | Error(ErrorStrings.MESSAGE_EXPECTED_COLON); 888 | } 889 | 890 | Advance(); 891 | var falsy = ParseStatement(); 892 | return new InlineIf {Condition = node, Falsy = falsy, Truthy = truthy}; 893 | } 894 | 895 | private Node ParsePipeline(Node node) 896 | { 897 | var root = node; 898 | do 899 | { 900 | Advance(); 901 | var child = ParseStatement(true); 902 | 903 | if (child is ValueCallNode vcn) 904 | { 905 | var callNode = new CallNode {Source = vcn.Source, Target = vcn.Target}; 906 | callNode.Parameters.Add(root, ""); 907 | root = callNode; 908 | } 909 | else if (child is IdentifierNode id) 910 | { 911 | var callNode = new CallNode {Source = new IdentifierNode("this"), Target = id}; 912 | callNode.Parameters.Add(root, ""); 913 | root = callNode; 914 | } 915 | else if (child is ArrayAccessNode an) 916 | { 917 | var callNode = new CallNode {Source = new IdentifierNode("this"), Target = an}; 918 | callNode.Parameters.Add(root, ""); 919 | root = callNode; 920 | } 921 | else if (child is CallNode cn) 922 | { 923 | var placeHolders = cn.Parameters.Where(a => a.Key is PlaceHolderNode).Select(a => a).ToList(); 924 | 925 | placeHolders.ForEach(a => { cn.Parameters.Remove(a.Key); }); 926 | 927 | placeHolders.ForEach(a => { cn.Parameters.Add(root, a.Value); }); 928 | 929 | root = cn; 930 | } 931 | else 932 | { 933 | Error(ErrorStrings.MESSAGE_UNEXPECTED_STATEMENT); 934 | } 935 | } while (Is(Classifier.Pipeline)); 936 | 937 | return root; 938 | } 939 | 940 | #endregion 941 | 942 | #region Variables 943 | 944 | private Node ParseVariableDeclaration() 945 | { 946 | var variable = new VariableDeclarationNode {Mutable = Is(Keyword.Var)}; 947 | 948 | if (Expect(Classifier.Mul)) 949 | { 950 | if (Is(Keyword.Let)) 951 | { 952 | Advance(); 953 | Error(ErrorStrings.MESSAGE_IMMUTABLE_CANT_BE_DYNAMIC); 954 | } 955 | 956 | Advance(); 957 | 958 | if (ExpectType()) 959 | { 960 | Advance(); 961 | Error(ErrorStrings.MESSAGE_DYNAMIC_NOT_POSSIBLE_WITH_STATIC_TYPES); 962 | } 963 | 964 | variable.Dynamic = true; 965 | } 966 | 967 | Advance(); 968 | 969 | if (IsType()) 970 | { 971 | var (type, dt) = GetSpecificType(); 972 | 973 | if (type == null) 974 | { 975 | Advance(); 976 | } 977 | 978 | variable.Datatype = dt; 979 | variable.SpecificType = type; 980 | } 981 | 982 | if (Is(Classifier.Question)) 983 | { 984 | if (variable.Datatype == null && Peek(2) != Classifier.Assignment) 985 | { 986 | Error(ErrorStrings.MESSAGE_TYPE_INFERRED_CANT_BE_NULLABLE); 987 | } 988 | 989 | if (!variable.Mutable) 990 | { 991 | Advance(-2); 992 | Error(ErrorStrings.MESSAGE_IMMUTABLE_CANT_BE_NULLABLE); 993 | } 994 | 995 | Advance(); 996 | variable.Nullable = true; 997 | } 998 | 999 | if (Is(Classifier.LeftBrace)) 1000 | { 1001 | variable.Array = true; 1002 | Advance(); //[ 1003 | 1004 | if (!Is(Classifier.RightBrace)) 1005 | { 1006 | if (Is(Classifier.Mul)) 1007 | { 1008 | variable.InfiniteArray = true; 1009 | Advance(); 1010 | if (!Is(Classifier.RightBrace)) 1011 | { 1012 | Error(ErrorStrings.MESSAGE_EXPECTED_TOKEN, Current.Kind.ToString()); 1013 | } 1014 | } 1015 | else 1016 | { 1017 | variable.ArraySize = ParseStatement(); 1018 | 1019 | if (Is(Classifier.Comma)) 1020 | { 1021 | var multiDimensionalArray = new MultiDimensionalArrayNode(); 1022 | multiDimensionalArray.Value.Add(variable.ArraySize); 1023 | while (Is(Classifier.Comma)) 1024 | { 1025 | Advance(); 1026 | multiDimensionalArray.Value.Add(ParseStatement()); 1027 | } 1028 | 1029 | variable.ArraySize = multiDimensionalArray; 1030 | } 1031 | } 1032 | } 1033 | 1034 | Advance(); //] 1035 | } 1036 | 1037 | if (IsIdentifier()) 1038 | { 1039 | variable.Name = Current.Value; 1040 | Advance(); 1041 | } 1042 | else 1043 | { 1044 | Error(ErrorStrings.MESSAGE_EXPECTED_IDENTIFIER); 1045 | } 1046 | 1047 | return variable; 1048 | } 1049 | 1050 | private Node ParseVariableDeclarationAndAssignment() 1051 | { 1052 | var variable = ParseVariableDeclaration() as VariableDeclarationNode; 1053 | 1054 | if (Is(Classifier.Assignment)) 1055 | { 1056 | Advance(); 1057 | if (variable != null) variable.Assignment = ParseStatement(); //change to ParseStatement 1058 | } 1059 | 1060 | return variable; 1061 | } 1062 | 1063 | private Node ParseAssignment(Node node) 1064 | { 1065 | Classifier GetNoAssignType(Classifier classifier) 1066 | { 1067 | return (Classifier) Enum.Parse(typeof(Classifier), classifier.ToString().Replace("Equal", "")); 1068 | } 1069 | 1070 | Advance(); 1071 | switch (Last.Kind) 1072 | { 1073 | case Classifier.Assignment: 1074 | return new AssignmentNode {Variable = node, Value = ParseStatement()}; 1075 | default: 1076 | return new AssignmentNode {Variable = node, Value = new OperationNode {Operations = new List {node, new OperationNodeNode(GetNoAssignType(Last.Kind), Last.Value.Replace("=", "")), ParseStatement()}}}; 1077 | } 1078 | } 1079 | 1080 | private Node ParseRightHand(Node node) 1081 | { 1082 | Advance(); 1083 | return new SideNode(node, new OperationNodeNode(Last.Kind, Last.Value), Side.RIGHT); 1084 | } 1085 | 1086 | private Node ParseArrayAccess(Node node) 1087 | { 1088 | Advance(); 1089 | var index = ParseStatement(); 1090 | 1091 | if (!Is(Classifier.RightBrace)) 1092 | { 1093 | Error(ErrorStrings.MESSAGE_EXPECTED_TOKEN, "]"); 1094 | } 1095 | 1096 | Advance(); 1097 | 1098 | return new ArrayAccessNode {BaseNode = node, Index = index}; 1099 | } 1100 | 1101 | private Node ParseLeftHand(OperationNodeNode node) 1102 | { 1103 | Advance(); 1104 | return new SideNode(ParseStatement(), node, Side.LEFT); 1105 | } 1106 | 1107 | private Node ParseArrayOrLambda() 1108 | { 1109 | Advance(); 1110 | 1111 | var parameters = new List<(Node Key, Datatype? Value, string SpecificType)>(); 1112 | var isLambda = true; 1113 | var index = _index; 1114 | 1115 | try 1116 | { 1117 | Advance(-1); 1118 | //Assume it's a lambda and parse arguments 1119 | parameters = ParseArguments(Classifier.FatArrow, "fat arrow"); 1120 | } 1121 | catch (Exception) 1122 | { 1123 | //Not a lambda 1124 | isLambda = false; 1125 | } 1126 | 1127 | //Lambda 1128 | Node n; 1129 | if (isLambda && Was(Classifier.FatArrow)) 1130 | { 1131 | var node = new LambdaNode(); 1132 | node.Parameters.AddRange(parameters); 1133 | 1134 | while (!Is(Classifier.RightBracket)) 1135 | { 1136 | node.Children.Add(ParseNext()); 1137 | } 1138 | 1139 | if (node.Children.Count == 1) 1140 | { 1141 | node.Complex = false; 1142 | } 1143 | 1144 | Advance(); 1145 | node.Children = node.Children.Where(a => a != null).ToList(); 1146 | n = node; 1147 | } 1148 | else 1149 | { 1150 | _index = index; 1151 | 1152 | var vals = new List(); 1153 | 1154 | //Collect array values 1155 | do 1156 | { 1157 | vals.Add(ParseStatement()); 1158 | if (!Is(Classifier.RightBracket)) 1159 | { 1160 | if (!Is(Classifier.Comma)) 1161 | { 1162 | Error(ErrorStrings.MESSAGE_INVALID_ARRAY_EXPECTED_COMMA); 1163 | } 1164 | 1165 | Advance(); 1166 | } 1167 | } while (!Is(Classifier.RightBracket)); 1168 | 1169 | Advance(); 1170 | var node = new ListNode {Value = vals}; 1171 | n = node; 1172 | } 1173 | 1174 | return n; 1175 | } 1176 | 1177 | #endregion 1178 | 1179 | #region Entry 1180 | 1181 | public RootNode Parse() 1182 | { 1183 | var node = new RootNode(); 1184 | while (!IsEof()) 1185 | { 1186 | node.Children.Add(ParseNext()); 1187 | } 1188 | 1189 | node.Children = node.Children.Where(a => a != null).ToList(); 1190 | return node; 1191 | } 1192 | 1193 | /// 1194 | /// Parses blocks 1195 | /// 1196 | /// 1197 | private Node ParseNext(bool allowSkipStop = false) 1198 | { 1199 | while (Is(Classifier.NewLine)) 1200 | { 1201 | Advance(); 1202 | } 1203 | 1204 | if (Is(Classifier.EndOfFile) || Is(Keyword.End)) 1205 | { 1206 | return null; 1207 | } 1208 | 1209 | if (Is(Classifier.At)) 1210 | { 1211 | return ParseVariableGroup(); 1212 | } 1213 | 1214 | if (Is(Classifier.Tag)) 1215 | { 1216 | ExpectIdentifier(); 1217 | Advance(); 1218 | var annotation = Current.Value; 1219 | 1220 | Advance(); 1221 | 1222 | Node annotationValue = new NoVariableNode(); 1223 | 1224 | if (Is(Classifier.LeftParenthesis)) 1225 | { 1226 | Advance(); 1227 | var val = ParseStatement(); 1228 | 1229 | if (val is BoolLiteralNode || val is DecLiteralNode || val is IntLiteralNode || val is StringLiteralNode || val is IdentifierNode) 1230 | { 1231 | annotationValue = val; 1232 | } 1233 | 1234 | if (!Is(Classifier.RightParenthesis)) 1235 | { 1236 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 1237 | } 1238 | 1239 | Advance(); 1240 | } 1241 | 1242 | var n = ParseNext(); 1243 | n.Annotations.Add(annotation, annotationValue); 1244 | return n; 1245 | } 1246 | 1247 | if (IsKeyword()) 1248 | { 1249 | if (allowSkipStop) 1250 | { 1251 | if (Is(Keyword.Skip)) 1252 | { 1253 | Advance(); 1254 | return new CommandNode(Keyword.Skip); 1255 | } 1256 | 1257 | if (Is(Keyword.Stop)) 1258 | { 1259 | Advance(); 1260 | return new CommandNode(Keyword.Stop); 1261 | } 1262 | } 1263 | 1264 | AccessModifier? accessModifier = null; 1265 | if (IsAccessModifier()) 1266 | { 1267 | accessModifier = Enum.Parse(Current.Value.First().ToString().ToUpper() + Current.Value.Substring(1)); 1268 | //TODO: Disallow protected? 1269 | Advance(); 1270 | } 1271 | 1272 | var isFixed = false; 1273 | if (Is(Keyword.Fixed)) 1274 | { 1275 | isFixed = true; 1276 | Advance(); 1277 | //HACK: this is not beautiful 1278 | } 1279 | 1280 | void NoAccessModifierOrFixed() 1281 | { 1282 | if (isFixed) Error(ErrorStrings.MESSAGE_UNEXPECTED_KEYWORD, Keyword.Fixed); 1283 | if (accessModifier != null) Error(ErrorStrings.MESSAGE_UNEXPECTED_ACCESS_MODIFIER); 1284 | } 1285 | 1286 | switch (Current.Value) 1287 | { 1288 | case Keyword.Put: 1289 | NoAccessModifierOrFixed(); 1290 | Advance(); 1291 | return new PutNode {Statement = ParseStatement()}; 1292 | 1293 | case Keyword.Match: 1294 | NoAccessModifierOrFixed(); 1295 | return ParseMatch(allowSkipStop); 1296 | 1297 | case Keyword.Class: 1298 | return ParseClass(isFixed, accessModifier.GetValueOrDefault()); 1299 | 1300 | case Keyword.Struct: 1301 | if (isFixed) Error(ErrorStrings.MESSAGE_UNEXPECTED_KEYWORD, Keyword.Fixed); 1302 | return ParseStruct(accessModifier.GetValueOrDefault()); 1303 | 1304 | case Keyword.Func: 1305 | return ParseFunc(isFixed, accessModifier.GetValueOrDefault()); 1306 | 1307 | case Keyword.While: 1308 | NoAccessModifierOrFixed(); 1309 | return ParseWhile(); 1310 | 1311 | case Keyword.If: 1312 | NoAccessModifierOrFixed(); 1313 | return ParseIf(allowSkipStop); 1314 | 1315 | case Keyword.For: 1316 | NoAccessModifierOrFixed(); 1317 | return ParseFor(); 1318 | 1319 | case Keyword.Try: 1320 | NoAccessModifierOrFixed(); 1321 | return ParseTryCatchElse(allowSkipStop); 1322 | 1323 | case Keyword.Skip: 1324 | case Keyword.Stop: 1325 | NoAccessModifierOrFixed(); 1326 | Error(ErrorStrings.MESSAGE_UNEXPECTED_KEYWORD, Current.Value); 1327 | break; 1328 | 1329 | default: 1330 | NoAccessModifierOrFixed(); 1331 | return ParseStatement(); 1332 | } 1333 | } 1334 | 1335 | //TODO: Do some cleanup, many of these conditions are in because of testing 1336 | if (IsIdentifier() || Is(Category.Literal) && (Expect(Classifier.Arrow) || Expect(Classifier.Question)) || Is(Category.Literal) && Expect(Category.Operator) || Is(Classifier.LeftParenthesis) || Is(Classifier.LeftBracket) || Is(Classifier.Not) || Is(Classifier.Minus)) 1337 | { 1338 | return ParseStatement(); 1339 | } 1340 | 1341 | if (_repl) 1342 | { 1343 | if (Is(Category.Literal)) 1344 | { 1345 | return ParseStatement(); 1346 | } 1347 | } 1348 | 1349 | Error(ErrorStrings.MESSAGE_UNEXPECTED_TOKEN, Current.Value); 1350 | 1351 | return null; 1352 | } 1353 | 1354 | /// 1355 | /// For statements that can have more complex nodes within the statement 1356 | /// 1357 | /// 1358 | private Node ParseStatement(bool pipeline = false) 1359 | { 1360 | Node GetOperation(Node initial = null) 1361 | { 1362 | var ops = new OperationNode(); 1363 | if (initial != null) 1364 | { 1365 | ops.Operations.Add(initial); 1366 | } 1367 | 1368 | while (Is(Category.Operator)) 1369 | { 1370 | ops.Operations.Add(new OperationNodeNode(Current.Kind, Current.Value)); 1371 | Advance(); 1372 | ops.Operations.Add(ParseStatement()); 1373 | } 1374 | 1375 | return ops; 1376 | } 1377 | 1378 | (Node, bool) GetAnonymousCall(Node init) 1379 | { 1380 | var ret = false; 1381 | if (init is LambdaNode || init is CallNode || init is ArrayAccessNode) 1382 | { 1383 | if (Is(Classifier.LeftParenthesis)) 1384 | { 1385 | init = ParseCallSignature(new CallNode {Source = init, Target = new IdentifierNode("anonymous")}, false); 1386 | ret = true; 1387 | } 1388 | } 1389 | 1390 | return (init, ret); 1391 | } 1392 | 1393 | Node node; 1394 | 1395 | if (Is(Classifier.LeftParenthesis)) 1396 | { 1397 | Advance(); 1398 | var n = ParseStatement(); 1399 | 1400 | if (!Is(Classifier.RightParenthesis)) 1401 | { 1402 | Error(ErrorStrings.MESSAGE_EXPECTED_RIGHT_PARENTHESIS); 1403 | } 1404 | 1405 | Advance(); 1406 | 1407 | while (Is(Category.Operator)) 1408 | { 1409 | n = GetOperation(n); 1410 | } 1411 | 1412 | //I wanted to manually differentiate between a calculation and a calculation in () 1413 | node = n is OperationNode ? new ParenthesesNode {Node = n} : n; 1414 | } 1415 | else 1416 | { 1417 | node = ParseStatementWithoutOperation(); 1418 | } 1419 | 1420 | if (Is(Category.Operator) && node != null) 1421 | { 1422 | node = GetOperation(node); 1423 | } 1424 | 1425 | if (Is(Classifier.NullCondition)) 1426 | { 1427 | Advance(); 1428 | return new NullConditionNode {Condition = node, Operation = ParseStatement()}; 1429 | } 1430 | 1431 | var isRet = false; 1432 | 1433 | do 1434 | { 1435 | if (Is(Classifier.Question)) 1436 | { 1437 | node = ParseInlineIf(node); 1438 | } 1439 | 1440 | if (Is(Classifier.Pipeline) && !pipeline) 1441 | { 1442 | node = ParsePipeline(node); 1443 | } 1444 | 1445 | if (Is(Classifier.Arrow)) 1446 | { 1447 | node = ParseDeepCall(node); 1448 | } 1449 | 1450 | if (Is(Classifier.LeftBrace)) 1451 | { 1452 | node = ParseArrayAccess(node); 1453 | } 1454 | } while (new Func(() => 1455 | { 1456 | (node, isRet) = GetAnonymousCall(node); 1457 | return isRet || Is(Classifier.Question) || (Is(Classifier.Pipeline) && !pipeline) || Is(Classifier.Arrow) || Is(Classifier.LeftBrace); 1458 | })()); 1459 | 1460 | if (Is(Category.Assignment)) 1461 | { 1462 | if (node is CallNode || node is InlineIf) 1463 | { 1464 | Error(ErrorStrings.MESSAGE_UNEXPECTED_CALL_OR_INLINE_IF); 1465 | } 1466 | 1467 | return ParseAssignment(node); 1468 | } 1469 | 1470 | if (Is(Category.RightHand)) 1471 | { 1472 | if (node is CallNode || node is InlineIf) 1473 | { 1474 | Error(ErrorStrings.MESSAGE_UNEXPECTED_CALL_OR_INLINE_IF); 1475 | } 1476 | 1477 | return ParseRightHand(node); 1478 | } 1479 | 1480 | while (Is(Category.Operator)) 1481 | { 1482 | node = GetOperation(node); 1483 | } 1484 | 1485 | return node; 1486 | } 1487 | 1488 | /// 1489 | /// For less complex statements (statements that do not require a preceding node 1490 | /// 1491 | /// Node 1492 | private Node ParseStatementWithoutOperation() 1493 | { 1494 | if (IsEof()) 1495 | { 1496 | Error(ErrorStrings.MESSAGE_UNEXPECTED_EOF); 1497 | } 1498 | 1499 | if (IsKeyword()) 1500 | { 1501 | switch (Current.Value) 1502 | { 1503 | case Keyword.Var: 1504 | case Keyword.Let: 1505 | return ParseVariableDeclarationAndAssignment(); 1506 | 1507 | case Keyword.With: 1508 | return ParsePackageImport(); 1509 | 1510 | case Keyword.Raise: 1511 | Advance(); 1512 | return new RaiseNode {Exception = ParseStatement()}; 1513 | 1514 | case Keyword.Null: 1515 | Advance(); 1516 | return new NullValueNode(); 1517 | 1518 | default: 1519 | Error(ErrorStrings.MESSAGE_UNEXPECTED_KEYWORD, Current.Value); 1520 | break; 1521 | } 1522 | } 1523 | 1524 | if (Is(Classifier.Identifier) && !IsKeyword()) 1525 | { 1526 | //Call on object 1527 | if (Expect(Classifier.Arrow)) 1528 | { 1529 | Advance(2); 1530 | return ParseCall(new IdentifierNode(Peek(-2).Value)); 1531 | } 1532 | 1533 | //Call on this 1534 | if (Expect(Classifier.LeftParenthesis)) 1535 | { 1536 | return ParseCall(new IdentifierNode("this")); 1537 | } 1538 | 1539 | Advance(); 1540 | return new IdentifierNode(Last.Value); 1541 | } 1542 | 1543 | if (Is(Classifier.DoubleQuestion)) 1544 | { 1545 | Advance(); 1546 | return new PlaceHolderNode(); 1547 | } 1548 | 1549 | if (Is(Category.Literal)) 1550 | { 1551 | Node node = null; 1552 | switch (Current.Kind) 1553 | { 1554 | case Classifier.IntLiteral: 1555 | node = new IntLiteralNode(Current); 1556 | break; 1557 | 1558 | case Classifier.BoolLiteral: 1559 | node = new BoolLiteralNode(Current); 1560 | break; 1561 | 1562 | case Classifier.StringLiteral: 1563 | node = new StringLiteralNode(Current); 1564 | break; 1565 | 1566 | case Classifier.DecLiteral: 1567 | node = new DecLiteralNode(Current); 1568 | break; 1569 | } 1570 | 1571 | if (node == null) 1572 | { 1573 | Error(ErrorStrings.MESSAGE_INVALID_LITERAL, Current.Value); 1574 | } 1575 | 1576 | Advance(); 1577 | 1578 | if (Is(Classifier.Arrow)) 1579 | { 1580 | Advance(); 1581 | return ParseCall(node); 1582 | } 1583 | 1584 | return node; 1585 | } 1586 | 1587 | if (Is(Classifier.LeftBracket)) 1588 | { 1589 | return ParseArrayOrLambda(); 1590 | } 1591 | 1592 | if (Is(Classifier.Not) || Is(Classifier.Minus)) 1593 | { 1594 | return ParseLeftHand(new OperationNodeNode(Current.Kind, Current.Value)); 1595 | } 1596 | 1597 | //TODO: Rework calculations 1598 | return null; 1599 | } 1600 | 1601 | #endregion 1602 | 1603 | #endregion 1604 | } 1605 | } -------------------------------------------------------------------------------- /src/Hades.Language/Parser/ParserGrammar.ebnf: -------------------------------------------------------------------------------- 1 | main = 2 | statement 3 | | assignment 4 | | ifBlock 5 | | forBlock 6 | | whileBlock 7 | | matchBlock 8 | | tryBlock 9 | | funcBlock 10 | | classBlock 11 | | structBlock 12 | | main 13 | ; -------------------------------------------------------------------------------- /src/Hades.Runtime/Hades.Runtime.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Hades.Runtime/HadesRuntime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Hades.Common; 5 | using Hades.Error; 6 | using Hades.Runtime.Objects; 7 | using Hades.Runtime.Values; 8 | using Hades.Syntax.Expression; 9 | using Hades.Syntax.Expression.Nodes; 10 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 11 | using Hades.Syntax.Lexeme; 12 | 13 | namespace Hades.Runtime 14 | { 15 | public static class HadesRuntime 16 | { 17 | #region Helpers 18 | 19 | private static void Error(string error, params object[] format) 20 | { 21 | Error(string.Format(error, format)); 22 | } 23 | 24 | private static void Error(string error) 25 | { 26 | throw new Exception(error); 27 | } 28 | 29 | #endregion 30 | 31 | public static Scope Run(RootNode rootNode, ref Scope scope) 32 | { 33 | foreach (var node in rootNode.Children) 34 | { 35 | if (node is VariableDeclarationNode) 36 | { 37 | var child = RunStatement(node, scope); 38 | 39 | if (scope.Variables.Any(a => a.Key == child.Name)) 40 | { 41 | Error(ErrorStrings.MESSAGE_DUPLICATE_VARIABLE_DECLARATION, child.Name); 42 | } 43 | 44 | scope.Variables.Add(child.Name, (child, AccessModifier.Private)); 45 | return child; 46 | } 47 | } 48 | 49 | if (rootNode.Children.Count == 1) 50 | { 51 | return RunStatement(rootNode.Children.First(), scope); 52 | } 53 | 54 | return new Scope(); 55 | } 56 | 57 | public static Scope RunStatement(Node node, Scope parent) 58 | { 59 | Scope scope = null; 60 | 61 | //Here are the literal values. These return a literal scope + all the built-ins the literal values have 62 | if (node is BoolLiteralNode boolLiteral) 63 | { 64 | // ReSharper disable once UseObjectOrCollectionInitializer 65 | scope = new Scope(); 66 | scope.Value = new BoolValue{Value = boolLiteral.Value}; 67 | scope.Functions = BuiltIns.BOOL_BUILT_INS; 68 | scope.Datatype = Datatype.BOOL; 69 | return scope; 70 | } 71 | 72 | //NOTE/TODO: Btw.: Annotations become important when dealing with var declarations, functions, classes and structs 73 | 74 | if (node is VariableDeclarationNode variableDeclaration) 75 | { 76 | scope = new Scope 77 | { 78 | Datatype = variableDeclaration.Datatype.GetValueOrDefault(Datatype.NONE), 79 | Name = variableDeclaration.Name, 80 | SpecificType = variableDeclaration.SpecificType, 81 | Mutable = variableDeclaration.Mutable, 82 | Dynamic = variableDeclaration.Dynamic, 83 | Nullable = variableDeclaration.Nullable 84 | }; 85 | 86 | if (variableDeclaration.Array) 87 | { 88 | var size = -1; 89 | if (variableDeclaration.InfiniteArray) 90 | { 91 | //TODO: Multidimensional array 92 | var result = RunStatement(variableDeclaration.ArraySize, parent); 93 | 94 | if (result == null) 95 | { 96 | Error(ErrorStrings.MESSAGE_UNKNOWN_RUNTIME_EXCEPTION, variableDeclaration.ToString()); 97 | } 98 | 99 | if (!(result.Value is IntValue)) 100 | { 101 | throw new Exception(); 102 | } 103 | } 104 | scope.Value = new ListValue {Size = size}; 105 | } 106 | 107 | scope.Value = RunStatement(variableDeclaration.Assignment, parent); 108 | } 109 | 110 | return scope; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Objects/BuiltIns.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Hades.Common; 4 | using Hades.Runtime.Values; 5 | using Hades.Syntax.Expression.Nodes; 6 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 7 | // ReSharper disable InconsistentNaming 8 | 9 | namespace Hades.Runtime.Objects 10 | { 11 | public class BuiltIns 12 | { 13 | private static string CreateMD5(string input) 14 | { 15 | // Use input string to calculate MD5 hash 16 | using (var md5 = System.Security.Cryptography.MD5.Create()) 17 | { 18 | var inputBytes = Encoding.ASCII.GetBytes(input); 19 | var hashBytes = md5.ComputeHash(inputBytes); 20 | 21 | // Convert the byte array to hexadecimal string 22 | var sb = new StringBuilder(); 23 | foreach (var t in hashBytes) 24 | { 25 | sb.Append(t.ToString("X2")); 26 | } 27 | return sb.ToString(); 28 | } 29 | } 30 | 31 | public static readonly Dictionary> OBJECT_BUILT_INS = new Dictionary> 32 | { 33 | { 34 | "toString", 35 | new List 36 | { 37 | new Scope 38 | { 39 | Name = "toString", 40 | IsNativeFunction = true, 41 | NativeFunctionSignature = new Dictionary{{"dst", Datatype.NONE}}, 42 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new CallNode{Source = new IdentifierNode("this"), Target = new IdentifierNode("toString")}, scopes[0]) 43 | } 44 | } 45 | 46 | //toString has to be implemented specifically for each value scope; this is why I call toString on the parameter 47 | //I am doing virtual abstract methods...pretty f-ing cool 😎 48 | }, 49 | { 50 | "hash", 51 | new List 52 | { 53 | new Scope 54 | { 55 | Name = "hash", 56 | IsNativeFunction = true, 57 | NativeFunctionSignature = new Dictionary{{"dst", Datatype.NONE}}, 58 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new CallNode{Source = new IdentifierNode("this"), Target = new IdentifierNode("hash")}, scopes[0]) 59 | }, 60 | new Scope 61 | { 62 | Name = "hash", 63 | IsNativeFunction = true, 64 | NativeFunctionSignature = new Dictionary(), 65 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new StringLiteralNode(CreateMD5((HadesRuntime.RunStatement(new CallNode{Source = new IdentifierNode("this"), Target = new IdentifierNode("toString")}, scope).Value as StringValue)?.Value)), scope) 66 | } 67 | } 68 | }, 69 | { 70 | "nameof", 71 | new List 72 | { 73 | new Scope 74 | { 75 | Name = "nameof", 76 | IsNativeFunction = true, 77 | NativeFunctionSignature = new Dictionary{{"dst", Datatype.NONE}}, 78 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new StringLiteralNode(scopes[0].Name), scope) 79 | }, 80 | new Scope 81 | { 82 | Name = "nameof", 83 | IsNativeFunction = true, 84 | NativeFunctionSignature = new Dictionary(), 85 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new StringLiteralNode(scope.Name), scope) 86 | } 87 | } 88 | }, 89 | { 90 | "type", 91 | new List 92 | { 93 | new Scope 94 | { 95 | Name = "type", 96 | IsNativeFunction = true, 97 | NativeFunctionSignature = new Dictionary{{"dst", Datatype.NONE}}, 98 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new StringLiteralNode(scopes[0].Datatype.ToString().ToLower()), scope) 99 | }, 100 | new Scope 101 | { 102 | Name = "type", 103 | IsNativeFunction = true, 104 | NativeFunctionSignature = new Dictionary(), 105 | NativeFunction = (scopes, scope) => HadesRuntime.RunStatement(new StringLiteralNode(scope.Datatype.ToString().ToLower()), scope) 106 | } 107 | } 108 | //TODO: equals 109 | } 110 | }; 111 | 112 | private static Dictionary> _bool_built_ins; 113 | 114 | public static Dictionary> BOOL_BUILT_INS 115 | { 116 | get 117 | { 118 | if (_bool_built_ins != null) return _bool_built_ins; 119 | 120 | _bool_built_ins = OBJECT_BUILT_INS; 121 | 122 | //TODO: Equals 123 | 124 | _bool_built_ins["toString"].Add(new Scope 125 | { 126 | IsNativeFunction = true, 127 | NativeFunctionSignature = new Dictionary(), 128 | NativeFunction = (scopes, scope) => 129 | { 130 | /* 131 | * Okay...let's unroll what happens here. toString returns a string. 132 | * As we all know, a string has built-ins. 133 | * So does the string that toString returns (obviously) 134 | * So to get all of these built-ins, we have to run a literal node through the runtime 135 | */ 136 | // ReSharper disable once ConvertToLambdaExpression 137 | return HadesRuntime.RunStatement( 138 | new StringLiteralNode((scope.Value as BoolValue)?.Value.ToString().ToLower()), 139 | scope); 140 | } 141 | }); 142 | 143 | return _bool_built_ins; 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Scope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Hades.Common; 4 | using Hades.Syntax.Expression; 5 | 6 | namespace Hades.Runtime 7 | { 8 | public class Scope : ScopeValue 9 | { 10 | //Except for classes and structs, every variable is private 11 | //I figured this is the best way to store variables 12 | //A variable needs to be identified, primarily by its name 13 | //A variable has an access modifier (in every scope - even in scopes where you can't set an access modifier -> then it's private) 14 | public Dictionary Variables { get; set; } = new Dictionary(); 15 | 16 | //A scope needs to have a datatype, the exec scope has the datatype "NONE", 17 | public Datatype Datatype { get; set; } 18 | 19 | //Classes, structs, variables and protos have names 20 | //Lambdas and functions also have names 21 | //When a function calls another function and the runtime can't find the other function 22 | //(For whatever reason) 23 | //This is the method of last resort (the runtime checks if the function that needs to be invoked is maybe this very function itself) 24 | //Note: I'll probably look at the parent scope for overloads first, if no overloads fits the invocation, this has to be the function to call 25 | //If it's still not the function to call -> ¯\_(ツ)_/¯ 26 | public string Name { get; set; } 27 | 28 | //A scope can be an object, struct or proto, these three can have a specific type 29 | public string SpecificType { get; set; } 30 | 31 | //A scope can have multiple functions, a function can have multiple overloads 32 | public Dictionary> Functions { get; set; } = new Dictionary>(); 33 | 34 | //A scope can contain classes 35 | public List Classes { get; set; } = new List(); 36 | 37 | //A scope can have structs 38 | public List Structs { get; set; } = new List(); 39 | 40 | //A scope can have code (is executable) 41 | public Node Code { get; set; } 42 | 43 | //A scope can be a variable, a variable can have the value of a scope (instance of an object), a proto (code) or a literal (ValueScope) 44 | public ScopeValue Value { get; set; } 45 | 46 | //A scope can be a variable, a variable can be nullable 47 | public bool Nullable { get; set; } 48 | 49 | //A scope can be a variable, a variable can be mutable 50 | public bool Mutable { get; set; } 51 | 52 | //A scope can be a variable, a variable can be dynamic 53 | public bool Dynamic { get; set; } 54 | 55 | //I know...it's a bit weird that this is here...but tbh...I didn't have the nerve to deal with polymorphism in this 56 | //Like...for real...none whatsoever 57 | //Not doing it 58 | //Nuh uh 59 | //Also polymorphism sux and makes everything slower. Only reason I used it in the parser is because there are *too fucking many* attributes to keep track of 60 | public bool IsNativeFunction { get; set; } 61 | 62 | //This exposes the function signature so I can check if the user is not being a stupid asshat 63 | //The goal of this is preventing stupid asshatery 64 | public Dictionary NativeFunctionSignature { get; set; } 65 | 66 | //Honestly...this is actually not a bad solution 67 | //No CLR reflection, no weird casting shit 68 | //Just plain old func 69 | //We stan a simple queen 70 | public Func NativeFunction { get; set; } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/ScopeValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime 2 | { 3 | public interface ScopeValue 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/BoolValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime.Values 2 | { 3 | public class BoolValue : LiteralValue, ScopeValue 4 | { 5 | public override string ToString() 6 | { 7 | return Value.ToString().ToLower(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/DecValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime.Values 2 | { 3 | public class DecValue : LiteralValue, ScopeValue 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/IntValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime.Values 2 | { 3 | public class IntValue : LiteralValue, ScopeValue 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/ListValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Hades.Runtime.Values 4 | { 5 | //A scope can have be an array variable 6 | //An array variable can be n-dimensional 7 | public class ListValue : LiteralValue>, ScopeValue 8 | { 9 | public int Size { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/LiteralValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime.Values 2 | { 3 | public abstract class LiteralValue 4 | { 5 | public T Value { get; set; } 6 | 7 | public override string ToString() 8 | { 9 | return Value.ToString(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Hades.Runtime/Values/StringValue.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Runtime.Values 2 | { 3 | public class StringValue : LiteralValue, ScopeValue 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/AccessModifier.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression 2 | { 3 | public enum AccessModifier 4 | { 5 | Private = 0, 6 | Protected, 7 | Public 8 | } 9 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/BlockNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Hades.Syntax.Expression 5 | { 6 | public abstract class BlockNode : Node 7 | { 8 | protected BlockNode(Classifier classifier) : base(classifier) 9 | { 10 | } 11 | 12 | public List Children { get; set; } = new List(); 13 | 14 | protected override string ToStr() 15 | { 16 | var str = ""; 17 | foreach (var child in Children) 18 | { 19 | str += string.Join('\n', child.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 20 | } 21 | 22 | if (!string.IsNullOrEmpty(str)) 23 | { 24 | str = str.Substring(0, str.Length - 1); 25 | } 26 | 27 | return str; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Classifier.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression 2 | { 3 | public enum Classifier 4 | { 5 | Root, 6 | With, 7 | VariableDeclaration, 8 | IntLiteral, 9 | BoolLiteral, 10 | StringLiteral, 11 | DecLiteral, 12 | Call, 13 | Identifier, 14 | Operation, 15 | NullCondition, 16 | Exception, 17 | Lambda, 18 | Put, 19 | Function, 20 | While, 21 | Command, 22 | ValueCall, 23 | Assignment, 24 | For, 25 | LeftHand, 26 | RightHand, 27 | Misc, 28 | ListLiteral, 29 | NullLiteral, 30 | MultiDimensionalArrayAccess, 31 | InlineIf, 32 | Pipeline, 33 | Placeholder, 34 | If, 35 | TryCatch, 36 | Class, 37 | Match, 38 | ArrayAccess, 39 | Struct 40 | } 41 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/LiteralNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression 2 | { 3 | public abstract class LiteralNode : Node 4 | { 5 | protected LiteralNode(Classifier classifier) : base(classifier) 6 | { 7 | } 8 | 9 | public T Value { get; set; } 10 | 11 | protected override string ToStr() 12 | { 13 | return string.Empty; 14 | } 15 | 16 | public override string ToString() 17 | { 18 | return $"Value: {Value}"; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Node.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Hades.Syntax.Expression 4 | { 5 | public abstract class Node 6 | { 7 | protected Node(Classifier classifier) 8 | { 9 | Classifier = classifier; 10 | } 11 | 12 | public Classifier Classifier { get; } 13 | public Dictionary Annotations { get; } = new Dictionary(); 14 | 15 | protected abstract string ToStr(); 16 | 17 | public override string ToString() 18 | { 19 | return $"{GetType().Name.Replace("Node", "")} => {ToStr()}"; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/ArrayAccessNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class ArrayAccessNode : Node 4 | { 5 | public ArrayAccessNode() : base(Classifier.ArrayAccess) 6 | { 7 | } 8 | 9 | public Node Index { get; set; } 10 | public Node BaseNode { get; set; } 11 | 12 | protected override string ToStr() 13 | { 14 | return $"Index [{Index}] from ({BaseNode})"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/AssignmentNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class AssignmentNode : Node 4 | { 5 | public AssignmentNode() : base(Classifier.Assignment) 6 | { 7 | } 8 | 9 | public Node Variable { get; set; } 10 | public Node Value { get; set; } 11 | 12 | protected override string ToStr() 13 | { 14 | return $"({Value}) to ({Variable})"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/ClassNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Hades.Common.Extensions; 4 | 5 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 6 | { 7 | public class ClassNode : Node 8 | { 9 | public ClassNode() : base(Classifier.Class) 10 | { 11 | } 12 | 13 | public AccessModifier AccessModifier { get; set; } 14 | public bool Fixed { get; set; } 15 | public List Parents { get; } = new List(); 16 | public string Name { get; set; } 17 | public List PublicVariables { get; } = new List(); 18 | public List PrivateVariables { get; } = new List(); 19 | public List ProtectedVariables { get; } = new List(); 20 | public List Functions { get; } = new List(); 21 | public List Classes { get; } = new List(); 22 | public List Constructors { get; } = new List(); 23 | public List Structs { get; } = new List(); 24 | 25 | protected override string ToStr() 26 | { 27 | var privateVars = string.Join("\n ", PrivateVariables); 28 | 29 | if (privateVars != string.Empty) 30 | { 31 | privateVars = $"\n Private variables:\n {privateVars}"; 32 | } 33 | 34 | var protectedVars = string.Join("\n ", ProtectedVariables); 35 | 36 | if (protectedVars != string.Empty) 37 | { 38 | protectedVars = $"\n Protected variables:\n {protectedVars}"; 39 | } 40 | 41 | var publicVars = string.Join("\n ", PublicVariables); 42 | 43 | if (publicVars != string.Empty) 44 | { 45 | publicVars = $"\n Public variables:\n {publicVars}"; 46 | } 47 | 48 | var functions = Functions.Map(a => a.ToString().Replace("\n", "\n ")).ToList(); 49 | var fn = string.Empty; 50 | 51 | if (functions.Count != 0) 52 | { 53 | fn = $"\n Functions:\n {string.Join("\n ", functions)}"; 54 | } 55 | 56 | var constructors = Constructors.Map(a => a.ToString().Replace("\n", "\n ")).ToList(); 57 | var ctor = string.Empty; 58 | 59 | if (constructors.Count != 0) 60 | { 61 | ctor = $"\n Constructors:\n {string.Join("\n ", constructors)}"; 62 | } 63 | 64 | var structs = Structs.Map(a => a.ToString().Replace("\n", "\n ")).ToList(); 65 | var stcts = string.Empty; 66 | 67 | if (constructors.Count != 0) 68 | { 69 | stcts = $"\n Structs:\n {string.Join("\n ", structs)}"; 70 | } 71 | 72 | var inherits = string.Empty; 73 | if (Parents.Count != 0) 74 | { 75 | inherits = $" inherits from {string.Join(", ", Parents)}"; 76 | } 77 | 78 | var fix = Fixed ? " fixed" : ""; 79 | 80 | return $"{Name}{fix}{inherits}{privateVars}{protectedVars}{publicVars}{ctor}{fn}{stcts}"; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/ForNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 2 | { 3 | public class ForNode : BlockNode 4 | { 5 | public ForNode() : base(Classifier.For) 6 | { 7 | } 8 | 9 | public Node Variable { get; set; } 10 | public Node Source { get; set; } 11 | 12 | protected override string ToStr() 13 | { 14 | return $"Source: ({Source}) into Variable: ({Variable})\n{base.ToStr()}"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/FunctionNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hades.Common; 3 | using Hades.Syntax.Expression.Nodes.BlockNodes.Util; 4 | 5 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 6 | { 7 | public class FunctionNode : BlockNode 8 | { 9 | public FunctionNode() : base(Classifier.Function) 10 | { 11 | } 12 | 13 | public bool Override { get; set; } 14 | public bool Fixed { get; set; } 15 | public string Name { get; set; } 16 | public List<(Node Key, Datatype? Value, string SpecificType)> Parameters { get; set; } = new List<(Node Key, Datatype? Value, string SpecificType)>(); 17 | public Node Guard { get; set; } 18 | public AccessModifier AccessModifier { get; set; } 19 | public bool Extension { get; set; } 20 | public (string specificType, Datatype dt) ExtensionType { get; set; } 21 | 22 | protected override string ToStr() 23 | { 24 | var args = ParameterWriter.PrintParameters(Parameters); 25 | 26 | if (!string.IsNullOrEmpty(args)) 27 | { 28 | args = args.Substring(0, args.Length - 1); 29 | args = $" with parameters {args}"; 30 | } 31 | 32 | var accessModifier = AccessModifier.ToString().ToLower(); 33 | var guard = Guard != null ? " with guard (" + Guard + ")" : ""; 34 | var over = Override ? "override " : ""; 35 | var fix = Fixed ? " fixed " : " "; 36 | 37 | var extends = Extension ? (string.IsNullOrEmpty(ExtensionType.specificType) ? $" extends {ExtensionType.dt.ToString().ToLower()}" : $" extends {ExtensionType.dt.ToString().ToLower()}::{ExtensionType.specificType}") : " <"; 38 | 39 | var str = $"{accessModifier}{fix}{over}{Name}{extends}{args}{guard}\n{base.ToStr()}"; 40 | return str; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/GenericBlockNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 2 | { 3 | public class GenericBlockNode : BlockNode 4 | { 5 | public GenericBlockNode() : base(Classifier.Misc) 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/IfNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 5 | { 6 | public class IfNode : Node 7 | { 8 | public IfNode() : base(Classifier.If) 9 | { 10 | } 11 | 12 | public Node Condition { get; set; } 13 | public BlockNode If { get; set; } 14 | public List ElseIfNodes { get; } = new List(); 15 | public BlockNode Else { get; set; } 16 | 17 | protected override string ToStr() 18 | { 19 | var str = ""; 20 | foreach (var child in If.Children) 21 | { 22 | str += string.Join('\n', child.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 23 | } 24 | 25 | if (!string.IsNullOrEmpty(str)) 26 | { 27 | str = str.Substring(0, str.Length - 1); 28 | } 29 | 30 | var elseIfStr = string.Empty; 31 | 32 | foreach (var elseIfNode in ElseIfNodes) 33 | { 34 | elseIfStr += $"\nElse {elseIfNode}"; 35 | } 36 | 37 | var elseStr = string.Empty; 38 | 39 | if (Else != null) 40 | { 41 | foreach (var elseChild in Else.Children) 42 | { 43 | elseStr += string.Join('\n', elseChild.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 44 | } 45 | } 46 | 47 | if (!string.IsNullOrEmpty(elseStr)) 48 | { 49 | elseStr = $"\nElse => \n{elseStr.Substring(0, elseStr.Length - 1)}"; 50 | } 51 | 52 | return $"Condition: ({Condition})\n{str}{elseIfStr}{elseStr}"; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/LambdaNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hades.Common; 3 | using Hades.Syntax.Expression.Nodes.BlockNodes.Util; 4 | 5 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 6 | { 7 | public class LambdaNode : BlockNode 8 | { 9 | public LambdaNode() : base(Classifier.Lambda) 10 | { 11 | } 12 | 13 | public List<(Node Key, Datatype? Value, string SpecificType)> Parameters { get; } = new List<(Node Key, Datatype? Value, string SpecificType)>(); 14 | public bool Complex { get; set; } 15 | 16 | protected override string ToStr() 17 | { 18 | var args = ParameterWriter.PrintParameters(Parameters); 19 | 20 | if (!string.IsNullOrEmpty(args)) 21 | { 22 | args = args.Substring(0, args.Length - 1); 23 | args = $" with parameters {args}"; 24 | } 25 | 26 | var complex = Complex ? "Complex" : "Simple"; 27 | var str = $"{complex} lambda{args}\n{base.ToStr()}"; 28 | return str; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/MatchNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 5 | { 6 | public class MatchNode : Node 7 | { 8 | public MatchNode() : base(Classifier.Match) 9 | { 10 | } 11 | 12 | public Node Match { get; set; } 13 | public bool First { get; set; } 14 | public Dictionary Statements { get; set; } = new Dictionary(); 15 | 16 | protected override string ToStr() 17 | { 18 | var str = Match == null ? "any" : $"({Match})"; 19 | 20 | var functions = Statements.Select(a => $"* ({a.Key})" + " => " + a.Value.ToString().Replace("\n", "\n ")).ToList(); 21 | var fn = string.Empty; 22 | 23 | if (functions.Count != 0) 24 | { 25 | fn = $"\n {string.Join("\n ", functions)}"; 26 | } 27 | 28 | var first = First ? " first" : ""; 29 | return str + first + " to " + fn; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/StructNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 4 | { 5 | public class StructNode : Node 6 | { 7 | public StructNode() : base(Classifier.Struct) 8 | { 9 | } 10 | 11 | public string Name { get; set; } 12 | public AccessModifier AccessModifier { get; set; } 13 | public List PublicVariables { get; } = new List(); 14 | public List PrivateVariables { get; } = new List(); 15 | 16 | protected override string ToStr() 17 | { 18 | var privateVars = string.Join("\n ", PrivateVariables); 19 | 20 | if (privateVars != string.Empty) 21 | { 22 | privateVars = $"\n Private variables:\n {privateVars}"; 23 | } 24 | 25 | var publicVars = string.Join("\n ", PublicVariables); 26 | 27 | if (publicVars != string.Empty) 28 | { 29 | publicVars = $"\n Public variables:\n {publicVars}"; 30 | } 31 | 32 | return $"{Name}{privateVars}{publicVars}"; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/TryCatchElseNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Hades.Common; 4 | 5 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 6 | { 7 | public class TryCatchElseNode : Node 8 | { 9 | public TryCatchElseNode() : base(Classifier.TryCatch) 10 | { 11 | } 12 | 13 | public BlockNode Try { get; set; } 14 | public BlockNode Else { get; set; } 15 | public List Catch { get; } = new List(); 16 | 17 | protected override string ToStr() 18 | { 19 | var tryStr = ""; 20 | foreach (var child in Try.Children) 21 | { 22 | tryStr += string.Join('\n', child.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 23 | } 24 | 25 | if (!string.IsNullOrEmpty(tryStr)) 26 | { 27 | tryStr = $"\n Try:\n{tryStr.Substring(0, tryStr.Length - 1)}"; 28 | } 29 | 30 | var catchString = string.Empty; 31 | 32 | foreach (var catchNode in Catch) 33 | { 34 | var specificType = catchNode.Name; 35 | 36 | if (catchNode.Datatype != Datatype.NONE) 37 | { 38 | specificType = $"{catchNode.Datatype.ToString().ToLower()} {catchNode.Name}"; 39 | } 40 | 41 | if (!string.IsNullOrEmpty(catchNode.SpecificType)) 42 | { 43 | specificType = $"{catchNode.Datatype.ToString().ToLower()}({catchNode.SpecificType}) {catchNode.Name}"; 44 | } 45 | 46 | catchString += $"\n Catch {specificType}\n"; 47 | 48 | foreach (var blockChild in catchNode.Block.Children) 49 | { 50 | catchString += string.Join('\n', blockChild.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 51 | } 52 | 53 | catchString = catchString.TrimEnd('\n'); 54 | } 55 | 56 | var elseStr = ""; 57 | foreach (var child in Else.Children) 58 | { 59 | elseStr += string.Join('\n', child.ToString().Split('\n').Select(a => $" {a}")) + "\n"; 60 | } 61 | 62 | if (!string.IsNullOrEmpty(elseStr)) 63 | { 64 | elseStr = $"\n Else:\n{elseStr.Substring(0, elseStr.Length - 1)}"; 65 | } 66 | 67 | return $"{tryStr}{catchString}{elseStr}"; 68 | } 69 | 70 | public struct CatchBlock 71 | { 72 | public BlockNode Block; 73 | public string SpecificType; 74 | public Datatype Datatype; 75 | public string Name; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/Util/ParameterWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hades.Common; 3 | 4 | namespace Hades.Syntax.Expression.Nodes.BlockNodes.Util 5 | { 6 | public static class ParameterWriter 7 | { 8 | public static string PrintParameters(IEnumerable<(Node Key, Datatype? Value, string SpecificType)> parameters) 9 | { 10 | var args = ""; 11 | 12 | foreach (var parameter in parameters) 13 | { 14 | if (parameter.Value != null) 15 | { 16 | if (parameter.SpecificType != null) 17 | { 18 | args += $"(({parameter.Key}):{parameter.Value.ToString().ToLower()}[{parameter.SpecificType}]),"; 19 | } 20 | else 21 | { 22 | args += $"(({parameter.Key}):{parameter.Value.ToString().ToLower()}),"; 23 | } 24 | } 25 | else 26 | { 27 | args += $"({parameter.Key}),"; 28 | } 29 | } 30 | 31 | return args; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/VarBlockNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 4 | { 5 | public class VarBlockNode : Node 6 | { 7 | public AccessModifier AccessModifier { get; set; } 8 | public List VariableDeclarationNodes { get; } = new List(); 9 | 10 | public VarBlockNode() : base(Classifier.Misc) 11 | { 12 | } 13 | 14 | protected override string ToStr() 15 | { 16 | return ""; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/BlockNodes/WhileNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.BlockNodes 2 | { 3 | public class WhileNode : BlockNode 4 | { 5 | public WhileNode() : base(Classifier.While) 6 | { 7 | } 8 | 9 | public Node Condition { get; set; } 10 | 11 | protected override string ToStr() 12 | { 13 | return $"Condition: ({Condition})\n{base.ToStr()}"; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/CallNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 3 | 4 | namespace Hades.Syntax.Expression.Nodes 5 | { 6 | public class CallNode : Node 7 | { 8 | public CallNode() : base(Classifier.Call) 9 | { 10 | } 11 | 12 | public Node Source { get; set; } 13 | public Node Target { get; set; } 14 | public Dictionary Parameters { get; } = new Dictionary(); 15 | 16 | protected override string ToStr() 17 | { 18 | var str = ""; 19 | 20 | foreach (var keyValuePair in Parameters) 21 | { 22 | if (!string.IsNullOrEmpty(keyValuePair.Value)) 23 | { 24 | str += $"({keyValuePair.Value}="; 25 | } 26 | else 27 | { 28 | str += "("; 29 | } 30 | 31 | str += $"{keyValuePair.Key}),"; 32 | } 33 | 34 | if (!string.IsNullOrEmpty(str)) 35 | { 36 | str = str.Substring(0, str.Length - 1); 37 | str = $" with parameters {str}"; 38 | } 39 | 40 | return $"({Target}) on ({Source}){str}"; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/InlineIf.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class InlineIf : Node 4 | { 5 | public Node Condition; 6 | public Node Falsy; 7 | public Node Truthy; 8 | 9 | public InlineIf() : base(Classifier.InlineIf) 10 | { 11 | } 12 | 13 | protected override string ToStr() 14 | { 15 | return $"(if ({Condition}), ({Truthy}), otherwise ({Falsy}))"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/ArgsNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class ArgsNode : LiteralNode 4 | { 5 | public ArgsNode(string identifier) : base(Classifier.Identifier) 6 | { 7 | Value = identifier; 8 | } 9 | 10 | public override string ToString() 11 | { 12 | return $"Varargs Value: {Value}"; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/BoolLiteralNode.cs: -------------------------------------------------------------------------------- 1 | using Hades.Syntax.Lexeme; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 4 | { 5 | public class BoolLiteralNode : LiteralNode 6 | { 7 | public BoolLiteralNode(Token token) : base(Classifier.BoolLiteral) 8 | { 9 | Value = bool.Parse(token.Value); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/CommandNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class CommandNode : LiteralNode 4 | { 5 | public CommandNode(string command) : base(Classifier.Command) 6 | { 7 | Value = command; 8 | } 9 | 10 | public override string ToString() 11 | { 12 | return $"Command: {Value}"; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/DecLiteralNode.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Hades.Syntax.Lexeme; 3 | 4 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 5 | { 6 | public class DecLiteralNode : LiteralNode 7 | { 8 | public DecLiteralNode(Token token) : base(Classifier.DecLiteral) 9 | { 10 | Value = decimal.Parse(token.Value, CultureInfo.InvariantCulture); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/IdentifierNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class IdentifierNode : LiteralNode 4 | { 5 | public IdentifierNode(string identifier) : base(Classifier.Identifier) 6 | { 7 | Value = identifier; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/IntLiteralNode.cs: -------------------------------------------------------------------------------- 1 | using Hades.Syntax.Lexeme; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 4 | { 5 | public class IntLiteralNode : LiteralNode 6 | { 7 | public IntLiteralNode(Token token) : base(Classifier.IntLiteral) 8 | { 9 | Value = int.Parse(token.Value); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/ListNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 5 | { 6 | public class ListNode : LiteralNode> 7 | { 8 | public ListNode() : base(Classifier.ListLiteral) 9 | { 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return $"Value: {{{string.Join(",", Value.Select(a => $"({a})").ToList())}}}"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/MultiDimensionalArrayNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 4 | { 5 | public class MultiDimensionalArrayNode : LiteralNode> 6 | { 7 | public MultiDimensionalArrayNode() : base(Classifier.MultiDimensionalArrayAccess) 8 | { 9 | Value = new List(); 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return string.Join(",", Value); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/NullValueNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class NullValueNode : LiteralNode 4 | { 5 | public NullValueNode() : base(Classifier.NullLiteral) 6 | { 7 | Value = "NULL"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/OperationNodeNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class OperationNodeNode : LiteralNode 4 | { 5 | public readonly string Representation; 6 | 7 | public OperationNodeNode(Lexeme.Classifier classifier, string val) : base(Classifier.Operation) 8 | { 9 | Value = classifier; 10 | Representation = val; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/PlaceHolderNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 2 | { 3 | public class PlaceHolderNode : LiteralNode 4 | { 5 | public PlaceHolderNode() : base(Classifier.Placeholder) 6 | { 7 | Value = "Placeholder"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/LiteralNodes/StringLiteralNode.cs: -------------------------------------------------------------------------------- 1 | using Hades.Syntax.Lexeme; 2 | 3 | namespace Hades.Syntax.Expression.Nodes.LiteralNodes 4 | { 5 | public class StringLiteralNode : LiteralNode 6 | { 7 | public StringLiteralNode(string value) : base(Classifier.StringLiteral) 8 | { 9 | Value = value; 10 | } 11 | 12 | public StringLiteralNode(Token token) : base(Classifier.StringLiteral) 13 | { 14 | Value = token.Value; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/NoVariableNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class NoVariableNode : LiteralNode 4 | 5 | { 6 | public NoVariableNode() : base(Classifier.Misc) 7 | { 8 | Value = "NONE"; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/NullConditionNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class NullConditionNode : Node 4 | { 5 | public NullConditionNode() : base(Classifier.NullCondition) 6 | { 7 | } 8 | 9 | public Node Condition { get; set; } 10 | public Node Operation { get; set; } 11 | 12 | protected override string ToStr() 13 | { 14 | return $"Return ({Operation}) if ({Condition}) evaluates to null"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/OperationNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 3 | 4 | namespace Hades.Syntax.Expression.Nodes 5 | { 6 | public class OperationNode : Node 7 | { 8 | public OperationNode() : base(Classifier.Operation) 9 | { 10 | } 11 | 12 | public List Operations { get; set; } = new List(); 13 | 14 | protected override string ToStr() 15 | { 16 | var str = ""; 17 | 18 | foreach (var operation in Operations) 19 | { 20 | switch (operation) 21 | { 22 | case OperationNode _: 23 | str += $"[{operation}]"; 24 | break; 25 | case OperationNodeNode operationNode: 26 | str += $" {operationNode.Representation} "; 27 | break; 28 | default: 29 | str += $"({operation})"; 30 | break; 31 | } 32 | } 33 | 34 | return str; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/ParenthesesNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class ParenthesesNode : Node 4 | { 5 | public ParenthesesNode() : base(Classifier.Misc) 6 | { 7 | } 8 | 9 | public Node Node { get; set; } 10 | 11 | protected override string ToStr() 12 | { 13 | return ""; 14 | } 15 | 16 | public override string ToString() 17 | { 18 | return $"[{Node}]"; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/PipelineNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class PipelineNode : Node 4 | { 5 | public Node Destination; 6 | public Node Source; 7 | 8 | public PipelineNode() : base(Classifier.Pipeline) 9 | { 10 | } 11 | 12 | protected override string ToStr() 13 | { 14 | return ""; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/PutNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class PutNode : Node 4 | { 5 | public PutNode() : base(Classifier.Put) 6 | { 7 | } 8 | 9 | public Node Statement { get; set; } 10 | 11 | protected override string ToStr() 12 | { 13 | return $"Statement: ({Statement})"; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/RaiseNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class RaiseNode : Node 4 | { 5 | public RaiseNode() : base(Classifier.Exception) 6 | { 7 | } 8 | 9 | public Node Exception { get; set; } 10 | 11 | protected override string ToStr() 12 | { 13 | return $"Raise exception ({Exception})"; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/SideNode.cs: -------------------------------------------------------------------------------- 1 | using Hades.Syntax.Expression.Nodes.LiteralNodes; 2 | 3 | namespace Hades.Syntax.Expression.Nodes 4 | { 5 | public enum Side 6 | { 7 | LEFT, 8 | RIGHT 9 | } 10 | 11 | public class SideNode : Node 12 | { 13 | private readonly Node BaseNode; 14 | private readonly OperationNodeNode Operation; 15 | private readonly Side Side; 16 | 17 | public SideNode(Node baseNode, OperationNodeNode operation, Side side) : base(side == Side.LEFT ? Classifier.LeftHand : Classifier.RightHand) 18 | { 19 | Side = side; 20 | BaseNode = baseNode; 21 | Operation = operation; 22 | } 23 | 24 | protected override string ToStr() 25 | { 26 | var side = Side == Side.LEFT ? "LeftHand" : "RightHand"; 27 | return $"{side} {Operation.Representation} on ({BaseNode})"; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/ValueCallNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression.Nodes 2 | { 3 | public class ValueCallNode : Node 4 | { 5 | public ValueCallNode() : base(Classifier.ValueCall) 6 | { 7 | } 8 | 9 | public Node Source { get; set; } 10 | public Node Target { get; set; } 11 | 12 | protected override string ToStr() 13 | { 14 | return $"Variable ({Target}) from ({Source})"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/VariableDeclarationNode.cs: -------------------------------------------------------------------------------- 1 | using Hades.Common; 2 | 3 | namespace Hades.Syntax.Expression.Nodes 4 | { 5 | public class VariableDeclarationNode : Node 6 | { 7 | public VariableDeclarationNode() : base(Classifier.VariableDeclaration) 8 | { 9 | } 10 | 11 | public bool Mutable { get; set; } 12 | public string Name { get; set; } 13 | 14 | // ReSharper disable once MemberCanBePrivate.Global 15 | public Datatype? Datatype { get; set; } 16 | public bool Array { get; set; } 17 | public Node ArraySize { get; set; } 18 | public bool InfiniteArray { get; set; } 19 | public Node Assignment { get; set; } 20 | public bool Dynamic { get; set; } 21 | public bool Nullable { get; set; } 22 | public string SpecificType { get; set; } 23 | 24 | protected override string ToStr() 25 | { 26 | var nullable = Nullable ? " nullable" : ""; 27 | var dynamic = Dynamic ? " dynamic" : ""; 28 | var mutable = Mutable ? " mutable" : " imutable"; 29 | var array = Array ? " array" : ""; 30 | var arraySize = ArraySize != null ? " (" + ArraySize + ")" : ""; 31 | var datatype = Datatype != null ? " " + Datatype.ToString().ToLower() : ""; 32 | 33 | if (SpecificType != null) 34 | { 35 | datatype += $"[{SpecificType}]"; 36 | } 37 | 38 | var assignment = Assignment != null ? "(" + Assignment + ")" : ""; 39 | var withAssignment = assignment != "" ? " with assignment " : ""; 40 | return $"Create{nullable}{dynamic}{mutable}{datatype}{array}{arraySize} variable {Name}{withAssignment}{assignment}"; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/Nodes/WithNode.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | 3 | namespace Hades.Syntax.Expression.Nodes 4 | { 5 | public class WithNode : Node 6 | { 7 | public WithNode() : base(Classifier.With) 8 | { 9 | } 10 | 11 | public string Target { get; set; } //package to import 12 | 13 | private string _source { get; set; } 14 | 15 | public string Source 16 | { 17 | set => _source = value; 18 | get => string.IsNullOrEmpty(_source) ? Target : _source; 19 | } //source 20 | 21 | public string NativePackage { get; set; } //source of the native package 22 | 23 | private string _name { get; set; } 24 | 25 | public string Name 26 | { 27 | set => _name = value; 28 | get => string.IsNullOrEmpty(_name) ? Target : _name; 29 | } //the name to be set 30 | 31 | public bool Native { get; set; } //for native imports like std libs 32 | public bool Fixed { get; set; } 33 | 34 | protected override string ToStr() 35 | { 36 | var @fixed = Fixed ? " fixed " : " "; 37 | return Native ? $"Import{@fixed}{Target} from native package {NativePackage}:{Source} as {Name}" : $"Import{@fixed}{Target} from {Source} as {Name}"; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Expression/RootNode.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Expression 2 | { 3 | public class RootNode : BlockNode 4 | { 5 | public RootNode() : base(Classifier.Root) 6 | { 7 | } 8 | 9 | public override string ToString() 10 | { 11 | return $"ROOT\n{base.ToStr()}"; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Hades.Syntax.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Hades.Syntax/Lexeme/Category.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Lexeme 2 | { 3 | public enum Category 4 | { 5 | Unknown, 6 | WhiteSpace, 7 | Comment, 8 | 9 | Literal, 10 | Identifier, 11 | Grouping, 12 | Punctuation, 13 | Operator, 14 | 15 | Invalid, 16 | Other, 17 | Assignment, 18 | LeftHand, 19 | RightHand 20 | } 21 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Lexeme/Classifier.cs: -------------------------------------------------------------------------------- 1 | namespace Hades.Syntax.Lexeme 2 | { 3 | public enum Classifier 4 | { 5 | EndOfFile, 6 | Error, 7 | 8 | #region WhiteSpace 9 | 10 | WhiteSpace, 11 | NewLine, 12 | 13 | #endregion 14 | 15 | #region Comments 16 | 17 | LineComment, 18 | BlockComment, 19 | 20 | #endregion 21 | 22 | #region Literal 23 | 24 | IntLiteral, 25 | MultiLineStringLiteral, 26 | StringLiteral, 27 | DecLiteral, 28 | BoolLiteral, 29 | 30 | #endregion 31 | 32 | #region Identifiers 33 | 34 | Identifier, 35 | Keyword, 36 | 37 | #endregion 38 | 39 | #region Groupings 40 | 41 | LeftBracket, // { 42 | RightBracket, // } 43 | RightBrace, // ] 44 | LeftBrace, // [ 45 | LeftParenthesis, // ( 46 | RightParenthesis, // ) 47 | 48 | #endregion Groupings 49 | 50 | #region Operators 51 | 52 | GreaterThanOrEqual, //>= 53 | GreaterThan, //> 54 | 55 | LessThanOrEqual, //<= 56 | LessThan, //< 57 | 58 | PlusEqual, //+= 59 | PlusPlus, //++ 60 | Plus, //+ 61 | 62 | MinusEqual, // -= 63 | MinusMinus, // -- 64 | Minus, // - 65 | 66 | Assignment, // = 67 | 68 | Not, // ! 69 | NotEqual, // != 70 | 71 | Mul, // * 72 | MulEqual, // *= 73 | 74 | Div, // / 75 | DivEqual, // /= 76 | 77 | BooleanAnd, // && 78 | BooleanOr, // || 79 | 80 | BitwiseAnd, // & 81 | BitwiseOr, // | 82 | 83 | BitwiseAndEqual, // &= 84 | BitwiseOrEqual, // |= 85 | 86 | ModEqual, // %= 87 | Mod, // % 88 | 89 | BitwiseXorEqual, // ^= 90 | BitwiseXor, // ^ 91 | 92 | BitwiseNegate, //~ 93 | BitwiseNegateEqual, //~= 94 | 95 | Equal, //== 96 | 97 | BitShiftLeft, // << 98 | BitShiftRight, // >> 99 | BitShiftLeftEqual, // <<= 100 | BitShiftRightEqual, // >>= 101 | 102 | Question, //? 103 | DoubleQuestion, //?? 104 | NullCondition, //:: 105 | 106 | Pipeline, //|> 107 | Tag, //# 108 | 109 | #endregion 110 | 111 | #region Punctuation 112 | 113 | Comma, //, 114 | Colon, //: 115 | Arrow, // -> 116 | FatArrow, // => 117 | Underscore, //_ 118 | At, //@ 119 | Dot, //. 120 | 121 | #endregion Punctuation 122 | } 123 | } -------------------------------------------------------------------------------- /src/Hades.Syntax/Lexeme/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Hades.Common.Source; 3 | 4 | namespace Hades.Syntax.Lexeme 5 | { 6 | public sealed class Token : IEquatable 7 | { 8 | private readonly Lazy _category; 9 | 10 | public Token(string value) 11 | { 12 | Value = value; 13 | } 14 | 15 | public Token(Classifier kind, string contents, SourceLocation start, SourceLocation end) 16 | { 17 | Kind = kind; 18 | Value = contents; 19 | Span = new Span(start, end); 20 | 21 | _category = new Lazy(GetTokenCategory); 22 | } 23 | 24 | public Category Category => _category.Value; 25 | 26 | public Classifier Kind { get; } 27 | 28 | public Span Span { get; } 29 | 30 | public string Value { get; set; } 31 | 32 | public bool Equals(Token other) 33 | { 34 | if (other == null) 35 | { 36 | return false; 37 | } 38 | 39 | return other.Value == Value && 40 | other.Span == Span && 41 | other.Kind == Kind; 42 | } 43 | 44 | public static bool operator !=(Token left, string right) 45 | { 46 | return left?.Value != right; 47 | } 48 | 49 | public static bool operator !=(string left, Token right) 50 | { 51 | return right?.Value != left; 52 | } 53 | 54 | public static bool operator !=(Token left, Classifier right) 55 | { 56 | return left?.Kind != right; 57 | } 58 | 59 | public static bool operator !=(Classifier left, Token right) 60 | { 61 | return right?.Kind != left; 62 | } 63 | 64 | public static bool operator ==(Token left, string right) 65 | { 66 | return left?.Value == right; 67 | } 68 | 69 | public static bool operator ==(string left, Token right) 70 | { 71 | return right?.Value == left; 72 | } 73 | 74 | public static bool operator ==(Token left, Classifier right) 75 | { 76 | return left?.Kind == right; 77 | } 78 | 79 | public static bool operator ==(Classifier left, Token right) 80 | { 81 | return right?.Kind == left; 82 | } 83 | 84 | public override bool Equals(object obj) 85 | { 86 | if (obj is Token token) 87 | { 88 | return Equals(token); 89 | } 90 | 91 | return base.Equals(obj); 92 | } 93 | 94 | public override int GetHashCode() 95 | { 96 | return Value.GetHashCode() ^ Span.GetHashCode() ^ Kind.GetHashCode(); 97 | } 98 | 99 | public bool IsTrivia() 100 | { 101 | return Category == Category.WhiteSpace || Category == Category.Comment; 102 | } 103 | 104 | private Category GetTokenCategory() 105 | { 106 | switch (Kind) 107 | { 108 | case Classifier.Arrow: 109 | case Classifier.FatArrow: 110 | case Classifier.Colon: 111 | case Classifier.Comma: 112 | case Classifier.Underscore: 113 | case Classifier.At: 114 | case Classifier.Dot: 115 | return Category.Punctuation; 116 | 117 | case Classifier.Equal: 118 | case Classifier.NotEqual: 119 | case Classifier.LessThan: 120 | case Classifier.LessThanOrEqual: 121 | case Classifier.GreaterThan: 122 | case Classifier.GreaterThanOrEqual: 123 | case Classifier.Mod: 124 | case Classifier.Mul: 125 | case Classifier.Plus: 126 | case Classifier.Div: 127 | case Classifier.BooleanOr: 128 | case Classifier.BooleanAnd: 129 | case Classifier.BitwiseXor: 130 | case Classifier.BitwiseOr: 131 | case Classifier.BitwiseAnd: 132 | case Classifier.BitShiftLeft: 133 | case Classifier.BitShiftRight: 134 | case Classifier.BitwiseNegate: 135 | case Classifier.Minus: 136 | case Classifier.Not: 137 | return Category.Operator; 138 | 139 | case Classifier.MinusMinus: 140 | case Classifier.PlusPlus: 141 | return Category.RightHand; 142 | 143 | case Classifier.Assignment: 144 | case Classifier.MulEqual: 145 | case Classifier.MinusEqual: 146 | case Classifier.ModEqual: 147 | case Classifier.PlusEqual: 148 | case Classifier.BitwiseXorEqual: 149 | case Classifier.BitwiseOrEqual: 150 | case Classifier.BitwiseAndEqual: 151 | case Classifier.BitShiftLeftEqual: 152 | case Classifier.BitShiftRightEqual: 153 | case Classifier.BitwiseNegateEqual: 154 | case Classifier.DivEqual: 155 | return Category.Assignment; 156 | 157 | case Classifier.BlockComment: 158 | case Classifier.LineComment: 159 | return Category.Comment; 160 | 161 | case Classifier.NewLine: 162 | case Classifier.WhiteSpace: 163 | return Category.WhiteSpace; 164 | 165 | case Classifier.LeftBrace: 166 | case Classifier.LeftBracket: 167 | case Classifier.LeftParenthesis: 168 | case Classifier.RightBrace: 169 | case Classifier.RightBracket: 170 | case Classifier.RightParenthesis: 171 | return Category.Grouping; 172 | 173 | case Classifier.Identifier: 174 | case Classifier.Keyword: 175 | return Category.Identifier; 176 | 177 | case Classifier.StringLiteral: 178 | case Classifier.DecLiteral: 179 | case Classifier.IntLiteral: 180 | case Classifier.BoolLiteral: 181 | case Classifier.MultiLineStringLiteral: 182 | return Category.Literal; 183 | 184 | case Classifier.Error: 185 | return Category.Invalid; 186 | 187 | case Classifier.Pipeline: 188 | case Classifier.Tag: 189 | case Classifier.Question: 190 | return Category.Other; 191 | 192 | 193 | default: return Category.Unknown; 194 | } 195 | } 196 | 197 | public override string ToString() 198 | { 199 | return $"{Kind} : {Value}"; 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /src/Hades.Testing/Hades.Testing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Hades.Testing/LexerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Hades.Language.Lexer; 4 | using Hades.Syntax.Lexeme; 5 | using NUnit.Framework; 6 | 7 | namespace Hades.Testing 8 | { 9 | [TestFixture] 10 | public class LexerTest 11 | { 12 | private readonly Lexer _lexer = new Lexer(); 13 | 14 | [Test] 15 | public void EnsureParseDec() 16 | { 17 | var results = _lexer.LexFile("1.5").ToList(); 18 | Assert.That(() => results[0].Kind == Classifier.DecLiteral && results[1].Kind == Classifier.EndOfFile); 19 | } 20 | 21 | [Test] 22 | public void EnsureParseInt() 23 | { 24 | var results = _lexer.LexFile("40").ToList(); 25 | Assert.That(() => results[0].Kind == Classifier.IntLiteral && results[1].Kind == Classifier.EndOfFile); 26 | } 27 | 28 | [Test] 29 | public void EnsureParseKeyword() 30 | { 31 | foreach (var keyword in Lexer.Keywords) 32 | { 33 | var results = _lexer.LexFile(keyword).ToList(); 34 | Console.WriteLine(keyword); 35 | Assert.That(() => results[0].Kind == Classifier.Keyword && results[1].Kind == Classifier.EndOfFile); 36 | } 37 | } 38 | 39 | [Test] 40 | public void EnsureParseMultiLineStringLiteral() 41 | { 42 | var results = _lexer.LexFile("\"\"\"Hello \\\"World\\n\\t\\\"\"\"\"").ToList(); 43 | Assert.That(() => results[0].Kind == Classifier.MultiLineStringLiteral && results[1].Kind == Classifier.EndOfFile); 44 | } 45 | 46 | [Test] 47 | public void EnsureParseStringLiteral() 48 | { 49 | var results = _lexer.LexFile("\"Hello \\\"World\\\"\"").ToList(); 50 | Assert.That(() => results[0].Kind == Classifier.StringLiteral && results[1].Kind == Classifier.EndOfFile); 51 | } 52 | 53 | [Test] 54 | public void EnsurePipelines() 55 | { 56 | var results = _lexer.LexFile("list->of({1,2,3,4,5})\n\t|> print").ToList(); 57 | Assert.That(() => results.Count == 22); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Hades.Testing/ParserTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Hades.Language.Lexer; 4 | using Hades.Language.Parser; 5 | using Hades.Syntax.Expression.Nodes; 6 | using NUnit.Framework; 7 | 8 | namespace Hades.Testing 9 | { 10 | [TestFixture] 11 | public class ParserTest 12 | { 13 | private void FailTest(string code, bool fail) 14 | { 15 | var lexer = new Lexer(); 16 | var parser = new Parser(lexer.LexFile(code), false); 17 | 18 | if (fail) 19 | { 20 | Assert.Throws(() => Console.WriteLine(parser.Parse())); 21 | } 22 | else 23 | { 24 | Assert.DoesNotThrow(() => Console.WriteLine(parser.Parse())); 25 | } 26 | } 27 | 28 | [TestCase("(1 + 2) + 3", false)] 29 | [TestCase("(1 + 2) * (3 + (5 - 6))", false)] 30 | [TestCase("(1) * (3 + (5))", false)] 31 | [TestCase("1 + 2 * 3 - 4 / 5", false)] 32 | [TestCase("1 + 2 * 3 - 4 / !5", false)] 33 | [Test] 34 | public void CalculationTest(string code, bool fail) 35 | { 36 | FailTest(code, fail); 37 | } 38 | 39 | [TestCase("exceptions->ArgumentNullException(\"{} is null\"->format(nameof(b)))", false)] 40 | [TestCase("console->print(\"Variable is of type string\")", false)] 41 | [TestCase("console->out(\"Connection open!\")", false)] 42 | [TestCase("square(a)", false)] 43 | [TestCase("root(a)", false)] 44 | [TestCase("square(a,b", true)] 45 | [TestCase("root(a", true)] 46 | [Test] 47 | public void EnsureCall(string code, bool fail) 48 | { 49 | FailTest(code, fail); 50 | } 51 | 52 | [TestCase("var string a = \"Hello\"", false)] 53 | [TestCase("var int a = 12", false)] 54 | [TestCase("var dec a = 1.2", false)] 55 | [TestCase("var bool a = false", false)] 56 | [TestCase("let string a = \"Hello\"", false)] 57 | [TestCase("let int a = 12", false)] 58 | [TestCase("let dec a = 1.2", false)] 59 | [TestCase("let bool a = false", false)] 60 | [TestCase("var a = \"Hello\"", false)] 61 | [TestCase("var a = 12", false)] 62 | [TestCase("var a = 1.2", false)] 63 | [TestCase("var a = false", false)] 64 | [TestCase("let a = \"Hello\"", false)] 65 | [TestCase("let a = 12", false)] 66 | [TestCase("let a = 1.2", false)] 67 | [TestCase("let a = false", false)] 68 | [TestCase("var string a", false)] 69 | [TestCase("var int a", false)] 70 | [TestCase("var dec a", false)] 71 | [TestCase("var bool a", false)] 72 | [TestCase("let string a", false)] 73 | [TestCase("let int a", false)] 74 | [TestCase("let dec a", false)] 75 | [TestCase("let bool a", false)] 76 | [TestCase("var a", false)] 77 | [TestCase("let a", false)] 78 | [TestCase("var string? a = \"Hello\"", false)] 79 | [TestCase("var int? a = 12", false)] 80 | [TestCase("var dec? a = 1.2", false)] 81 | [TestCase("var bool? a = false", false)] 82 | [TestCase("var* a = \"Hello\"", false)] 83 | [TestCase("var* a = 12", false)] 84 | [TestCase("var* a = 1.2", false)] 85 | [TestCase("var* a = false", false)] 86 | [TestCase("var? a = \"Hello\"", false)] 87 | [TestCase("var? a = 12", false)] 88 | [TestCase("var? a = 1.2", false)] 89 | [TestCase("var? a = false", false)] 90 | [TestCase("var string?[*] a", false)] 91 | [TestCase("var int?[*] a", false)] 92 | [TestCase("var dec?[*] a", false)] 93 | [TestCase("var bool?[*] a", false)] 94 | [TestCase("var*[] a", false)] 95 | [TestCase("var int[3,3] matrix = {{1,0,0},{0,1,0},{0,0,1}}", false)] 96 | [TestCase("var int?[2,2,2] 3dArray = {{{1,2},{3,null}},{{null,6},{7,8}}}", false)] 97 | [TestCase("var object::IClient a", false)] 98 | [TestCase("var object::IClient? a", false)] 99 | [TestCase("var object::IClient?[] a", false)] 100 | [TestCase("var object::IClient?[*] a", false)] 101 | [TestCase("let object::IClient? a", true)] 102 | [TestCase("var* object::IClient a", true)] 103 | [TestCase("var* string a = \"Hello\"", true)] 104 | [TestCase("var* int a = 12", true)] 105 | [TestCase("var* dec a = 1.2", true)] 106 | [TestCase("var* bool a = false", true)] 107 | [TestCase("let* string a = \"Hello\"", true)] 108 | [TestCase("let* int a = 12", true)] 109 | [TestCase("let* dec a = 1.2", true)] 110 | [TestCase("let* bool a = false", true)] 111 | [TestCase("var? a", true)] 112 | [TestCase("let? a", true)] 113 | [Test] 114 | public void EnsureVariableDeclaration(string code, bool fail) 115 | { 116 | FailTest(code, fail); 117 | } 118 | 119 | [TestCase("with server", "server", "server", "server", false, null)] 120 | [TestCase("with server as foo", "server", "server", "foo", false, null)] 121 | [TestCase("with console from std:io", "io", "console", "console", true, "std")] 122 | [TestCase("with math as m from std:math", "math", "math", "m", true, "std")] 123 | [TestCase("with console fixed from std:io", "io", "console", "console", true, "std")] 124 | [TestCase("with math fixed as m from std:math", "math", "math", "m", true, "std")] 125 | [Test] 126 | public void EnsureWith(string code, string source, string target, string name, bool native, string nativePackage) 127 | { 128 | var lexer = new Lexer(); 129 | var parser = new Parser(lexer.LexFile(code), false); 130 | var root = parser.Parse(); 131 | var node = root.Children.First(); 132 | Assert.IsTrue(node is WithNode); 133 | var withNode = node as WithNode; 134 | 135 | Assert.AreEqual(withNode.Source, source); 136 | Assert.AreEqual(withNode.Target, target); 137 | Assert.AreEqual(withNode.Name, name); 138 | Assert.AreEqual(withNode.Native, native); 139 | Assert.AreEqual(withNode.NativePackage, nativePackage); 140 | } 141 | 142 | [TestCase("fib(10) |> doStuff |> console->out", false)] 143 | [TestCase("fib(10) |> console->out", false)] 144 | [TestCase("fruits |> map({x => x->toLower()}, ??) |> filter({x => x->startsWith(\"a\")}, ??) |> forEach({x => console->out(x)}, ??)", false)] 145 | [Test] 146 | public void PipelineTes(string code, bool fail) 147 | { 148 | FailTest(code, fail); 149 | } 150 | 151 | [TestCase( 152 | "with console from std:io\n" + 153 | "func myFunction(int a) requires a is 11\n" + 154 | "console->out(\"a is 11\")\n" + 155 | "console->out(\"a is 11\")\n" + 156 | "end", false)] 157 | 158 | [TestCase( 159 | "while(c not 10)\n" + 160 | "console->out(\"c is {}\"->format(c))\n" + 161 | "end", false)] 162 | 163 | [TestCase( 164 | "with console from std:io\n" + 165 | "func myFunction(int a) requires a is 11\n" + 166 | "console->out(\"a is 11\")\n" + 167 | "skip\n" + 168 | "end", true)] 169 | 170 | [TestCase( 171 | "with console from std:io\n" + 172 | "func myFunction(int a) requires a is 11\n" + 173 | "console->out(\"a is 11\")\n" + 174 | "a->b = {x,y=>x+y}(1,2)->toString({x=>x})\n" + 175 | "end", false)] 176 | 177 | [TestCase( 178 | "for(var arg in a)\n" + 179 | "console->out(arg)\n" + 180 | "skip\n" + 181 | "end\n" + 182 | "for(var arg in a)\n" + 183 | "console->out(arg)\n" + 184 | "skip\n" + 185 | "end", false)] 186 | 187 | [TestCase( 188 | "func doStuff(object::IClient a)\n" + 189 | "a->stuff(\"Hello\")\n" + 190 | "end", false)] 191 | 192 | [TestCase( 193 | "func doStuff(object::IClient a, int b)\n" + 194 | "a->stuff(\"Hello\")\n" + 195 | "end", false)] 196 | 197 | [TestCase( 198 | "func doStuff(object::IClient a, c)\n" + 199 | "a->stuff(\"Hello\")\n" + 200 | "end", false)] 201 | 202 | [TestCase( 203 | "func doStuff(object::IClient a, c, int d)\n" + 204 | "a->stuff(\"Hello\")\n" + 205 | "end", false)] 206 | 207 | [TestCase( 208 | "func doStuff(args object::IClient a, c)\n" + 209 | "a->stuff(\"Hello\")\n" + 210 | "end", false)] 211 | 212 | [TestCase( 213 | "with console from std:io\n" + 214 | "if(a < 10)\n" + 215 | "console->out(\"a is smaller than 10\")\n" + 216 | "else if(a is 11)\n" + 217 | "console->out(\"a is 11\")\n" + 218 | "else if(a > 11 and a < 21)\n" + 219 | "console->out(\"a is greater than 11 and smaller than 21\")\n" + 220 | "else\n" + 221 | "console->out(\"a is \" + a)\n" + 222 | "end", false)] 223 | 224 | [TestCase( 225 | "with math as m from std:math\n" + 226 | "func doMath(int a)\n" + 227 | "func root(int b)\n" + 228 | "put m->sqrt(b)\n" + 229 | "end\n" + 230 | "func square(b)\n" + 231 | "put b * b\n" + 232 | "end\n" + 233 | "put square(a) + root(a)\n" + 234 | "end", false)] 235 | 236 | [TestCase( 237 | "srv->get(\"/:path\", {req, res => \n" + 238 | "let path = req->param\n" + 239 | "try\n" + 240 | "if (file->exists(path))\n" + 241 | "let f = file->open(path)\n" + 242 | "res->send(f->read())\n" + 243 | "else\n" + 244 | "raise 404\n" + 245 | "end\n" + 246 | "catch(int status)\n" + 247 | "res->status(status)\n" + 248 | "else\n" + 249 | "res->status(200)\n" + 250 | "end\n" + 251 | "})", false)] 252 | 253 | [TestCase( 254 | "var fruits = list->of({\"Apple\", \"Banana\", \"Mango\", \"Kiwi\", \"Avocado\"})\n" + 255 | "fruits\n" + 256 | "|> map({x => x->toLower()}, ??)\n" + 257 | "|> filter({x => x->startsWith(\"a\")}, ??)\n" + 258 | "|> forEach({x => console->out(x)}, ??)", false)] 259 | 260 | [TestCase( 261 | "let string[] fruits = {\"Apple\", \"Banana\", \"Mango\", \"Kiwi\"}\n" + 262 | "for(var fruit in fruits)\n" + 263 | "console->out(\"{} is very healthy\"->format(fruit))\n" + 264 | "end\n" + 265 | "var a = params->get(0)\n" + 266 | "var b = params->get(1)\n" + 267 | "a :: raise exceptions->ArgumentNullException(\"{} is null\"->format(nameof(a)))\n" + 268 | "b :: raise exceptions->ArgumentNullException(\"{} is null\"->format(nameof(b)))", false)] 269 | 270 | [TestCase( 271 | "try\n" + 272 | "connection->open()\n" + 273 | "console->out(\"Connection open!\")\n" + 274 | "connection->close()\n" + 275 | "catch(object::SqlException e)\n" + 276 | "console->out(\"SqlException was caught!\")\n" + 277 | "catch(e)\n" + 278 | "console->out(\"An unknown exception was caught!\")\n" + 279 | "else\n" + 280 | "console->out(\"No exception thrown!\")\n" + 281 | "end", false)] 282 | 283 | [TestCase( 284 | "struct employee\n"+ 285 | "public let string firstname = \"John\"\n"+ 286 | "public let lastname = \"Doe\"\n"+ 287 | "private var int age = 18\n"+ 288 | "var string[] attributes //When no access modifier is given, the variable will have private access\n"+ 289 | "end\n"+ 290 | "class Person\n"+ 291 | "var int id\n"+ 292 | "@public\n"+ 293 | "var string firstname\n"+ 294 | "var string lastname\n"+ 295 | "end\n"+ 296 | "end\n"+ 297 | "struct Person\n"+ 298 | "@public\n"+ 299 | "var string firstname\n"+ 300 | "var string lastname\n"+ 301 | "end\n"+ 302 | "@private\n"+ 303 | "var int id\n"+ 304 | "end\n"+ 305 | "end", false)] 306 | 307 | [TestCase( 308 | "with console from std:io\n" + 309 | "func myFunction(int a) requires a is 11\n" + 310 | "console->out(\"a is 11\")\n" + 311 | "a->b = {x,y=>x+y}(1,2)->toString({x=>x})\n" + 312 | "end\n" + 313 | "a[0]()->t() |> console\n" + 314 | "struct Person\n" + 315 | "let string firstname\n" + 316 | "var string lastname\n" + 317 | "let birthday\n" + 318 | "var nationality\n" + 319 | "end", false)] 320 | 321 | [TestCase( 322 | "#route(\"/\")\n" + 323 | "func handleMain()\n" + 324 | "put 200\n" + 325 | "end\n" + 326 | "#service\n" + 327 | "#constructor(true)\n" + 328 | "class UserService\n" + 329 | "#injected(UserRepository)\n" + 330 | "let userRepository\n" + 331 | "func getById(id)\n" + 332 | "put userRepository->findById(id)->orElse(null)\n" + 333 | "end\n" + 334 | "end", false)] 335 | 336 | [TestCase( 337 | "let dec[*] points = \n" + 338 | "{\n" + 339 | "{245, 1400},\n" + 340 | "{312, 1600},\n" + 341 | "{279, 1700},\n" + 342 | "{308, 1875},\n" + 343 | "{199, 1100},\n" + 344 | "{219, 1550},\n" + 345 | "{405, 2350},\n" + 346 | "{324, 2450},\n" + 347 | "{319, 1425},\n" + 348 | "{255, 1700}\n" + 349 | "}\n" + 350 | "var weight = 10.0\n" + 351 | "var bias = 100.0\n" + 352 | "var lr = 0.000001\n" + 353 | "for(_ in range(1000000))\n" + 354 | "for(var point in points)\n" + 355 | "var prediction = point[0] * weight + bias\n" + 356 | "var error = point[1] - prediction\n" + 357 | "var gradient = point[0] * error * lr\n" + 358 | "bias += gradient\n" + 359 | "weight += weight * gradient\n" + 360 | "end\n" + 361 | "end", false)] 362 | [Test] 363 | public void ProgramTest(string code, bool fail) 364 | { 365 | FailTest(code, fail); 366 | } 367 | } 368 | } -------------------------------------------------------------------------------- /src/Hades.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Core", "Hades.Core\Hades.Core.csproj", "{54BC2912-B7A1-44B3-B2E9-41A0C48FBADE}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Syntax", "Hades.Syntax\Hades.Syntax.csproj", "{9CBB69AF-EAC6-4556-9495-4F06D295726C}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Common", "Hades.Common\Hades.Common.csproj", "{1F693DED-1D90-47C8-9EC0-EEE047E24C59}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Language", "Hades.Language\Hades.Language.csproj", "{0E884807-1A5C-4AB1-BE54-A00B9B91202E}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Testing", "Hades.Testing\Hades.Testing.csproj", "{BA1980E8-AC47-4934-8160-80410AA99EC0}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Error", "Hades.Error\Hades.Error.csproj", "{AA64808F-6020-4F86-B2C1-F13402FA2F68}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hades.Runtime", "Hades.Runtime\Hades.Runtime.csproj", "{F0148F87-2E59-453A-8618-83A249D13F39}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {54BC2912-B7A1-44B3-B2E9-41A0C48FBADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {54BC2912-B7A1-44B3-B2E9-41A0C48FBADE}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {54BC2912-B7A1-44B3-B2E9-41A0C48FBADE}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {54BC2912-B7A1-44B3-B2E9-41A0C48FBADE}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {9CBB69AF-EAC6-4556-9495-4F06D295726C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {9CBB69AF-EAC6-4556-9495-4F06D295726C}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {9CBB69AF-EAC6-4556-9495-4F06D295726C}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {9CBB69AF-EAC6-4556-9495-4F06D295726C}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {1F693DED-1D90-47C8-9EC0-EEE047E24C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {1F693DED-1D90-47C8-9EC0-EEE047E24C59}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {1F693DED-1D90-47C8-9EC0-EEE047E24C59}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {1F693DED-1D90-47C8-9EC0-EEE047E24C59}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {0E884807-1A5C-4AB1-BE54-A00B9B91202E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {0E884807-1A5C-4AB1-BE54-A00B9B91202E}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {0E884807-1A5C-4AB1-BE54-A00B9B91202E}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {0E884807-1A5C-4AB1-BE54-A00B9B91202E}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {BA1980E8-AC47-4934-8160-80410AA99EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {BA1980E8-AC47-4934-8160-80410AA99EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {BA1980E8-AC47-4934-8160-80410AA99EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {BA1980E8-AC47-4934-8160-80410AA99EC0}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {AA64808F-6020-4F86-B2C1-F13402FA2F68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {AA64808F-6020-4F86-B2C1-F13402FA2F68}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {AA64808F-6020-4F86-B2C1-F13402FA2F68}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {AA64808F-6020-4F86-B2C1-F13402FA2F68}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {F0148F87-2E59-453A-8618-83A249D13F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {F0148F87-2E59-453A-8618-83A249D13F39}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {F0148F87-2E59-453A-8618-83A249D13F39}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {F0148F87-2E59-453A-8618-83A249D13F39}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | EndGlobal 53 | --------------------------------------------------------------------------------