├── .gitattributes ├── .gitignore ├── Gcode.sln ├── Gcodes.Console ├── App.config ├── Gcodes.Console.csproj ├── LoggingSimulator.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── Gcodes.Test ├── EmbeddedFixture.cs ├── FileMapTest.cs ├── Fixtures │ ├── 371373P.gcode │ ├── circle.gcode │ └── simple_mill.gcode ├── Gcodes.Test.csproj ├── LexerTest.cs ├── ParserTest.cs ├── PatternTest.cs ├── Properties │ └── AssemblyInfo.cs ├── Runtime │ ├── EmulatorTest.cs │ └── OperationFactoryTest.cs ├── TokenKindTest.cs └── packages.config ├── Gcodes ├── .gitignore ├── Ast │ ├── Argument.cs │ ├── Code.cs │ ├── Gcode.cs │ ├── GcodeVisitor.cs │ ├── LineNumber.cs │ ├── Mcode.cs │ ├── Ocode.cs │ └── Tcode.cs ├── Exceptions.cs ├── FileMap.cs ├── Gcodes.csproj ├── Interpreter.cs ├── Lexer.cs ├── Location.cs ├── Parser.cs ├── Pattern.cs ├── Properties │ └── AssemblyInfo.cs ├── Runtime │ ├── Emulator.cs │ ├── IOperation.cs │ ├── MachineState.cs │ ├── Noop.cs │ └── OperationFactory.cs ├── SpanInfo.cs ├── Tokens │ ├── Span.cs │ ├── Token.cs │ └── TokenKind.cs ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── intro.md │ └── toc.yml ├── docfx.json ├── index.md └── toc.yml ├── LICENSE.md ├── README.md └── appveyor.yml /.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 | 65 | *.gcode eol=crlf -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /Gcode.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2000 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gcodes", "Gcodes\Gcodes.csproj", "{196C59DA-B880-465D-A1BB-C114067CA838}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gcodes.Test", "Gcodes.Test\Gcodes.Test.csproj", "{8EB51AC3-86B9-462E-AA48-1CC48E3D6129}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gcodes.Console", "Gcodes.Console\Gcodes.Console.csproj", "{BC78987C-B98A-4CD1-A44F-D6CE48A265F0}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {196C59DA-B880-465D-A1BB-C114067CA838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {196C59DA-B880-465D-A1BB-C114067CA838}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {196C59DA-B880-465D-A1BB-C114067CA838}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {196C59DA-B880-465D-A1BB-C114067CA838}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {8EB51AC3-86B9-462E-AA48-1CC48E3D6129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {8EB51AC3-86B9-462E-AA48-1CC48E3D6129}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {8EB51AC3-86B9-462E-AA48-1CC48E3D6129}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {8EB51AC3-86B9-462E-AA48-1CC48E3D6129}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {BC78987C-B98A-4CD1-A44F-D6CE48A265F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BC78987C-B98A-4CD1-A44F-D6CE48A265F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BC78987C-B98A-4CD1-A44F-D6CE48A265F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BC78987C-B98A-4CD1-A44F-D6CE48A265F0}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {DE73C900-280E-4705-8B8C-3A1CC7E4931E} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Gcodes.Console/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Gcodes.Console/Gcodes.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BC78987C-B98A-4CD1-A44F-D6CE48A265F0} 8 | Exe 9 | Gcodes.Console 10 | Gcodes.Console 11 | v4.7.2 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | Gcodes.Console.Program 36 | 37 | 38 | 39 | ..\packages\CommandLineParser.2.2.1\lib\net45\CommandLine.dll 40 | 41 | 42 | ..\packages\Serilog.2.7.1\lib\net46\Serilog.dll 43 | 44 | 45 | ..\packages\Serilog.Exceptions.4.1.0\lib\net45\Serilog.Exceptions.dll 46 | 47 | 48 | ..\packages\Serilog.Sinks.Console.3.1.1\lib\net45\Serilog.Sinks.Console.dll 49 | 50 | 51 | 52 | 53 | ..\packages\System.Console.4.3.1\lib\net46\System.Console.dll 54 | True 55 | 56 | 57 | 58 | ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll 59 | True 60 | 61 | 62 | ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll 63 | True 64 | 65 | 66 | ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll 67 | True 68 | 69 | 70 | ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll 71 | True 72 | 73 | 74 | ..\packages\System.Reflection.TypeExtensions.4.4.0\lib\net461\System.Reflection.TypeExtensions.dll 75 | 76 | 77 | ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll 78 | True 79 | 80 | 81 | ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll 82 | True 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | {196C59DA-B880-465D-A1BB-C114067CA838} 103 | Gcodes 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Gcodes.Console/LoggingSimulator.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Runtime; 2 | using Serilog; 3 | 4 | namespace Gcodes.Console 5 | { 6 | /// 7 | /// An emulator which hooks into the various events provided by an 8 | /// and logs the event arguments. 9 | /// 10 | public class LoggingEmulator : Emulator 11 | { 12 | ILogger logger; 13 | 14 | public LoggingEmulator() 15 | { 16 | logger = Log.ForContext(); 17 | 18 | OperationExecuted += LoggingEmulator_OperationExecuted; 19 | StateChanged += LoggingEmulator_StateChanged; 20 | CommentDetected += LoggingEmulator_CommentDetected; 21 | } 22 | 23 | private void LoggingEmulator_StateChanged(object sender, StateChangeEventArgs e) 24 | { 25 | logger.Debug("State changed to {@State} (t: {Time})", e.NewState, e.Time); 26 | } 27 | 28 | private void LoggingEmulator_OperationExecuted(object sender, OperationExecutedEventArgs e) 29 | { 30 | if (e.Operation is Noop nop && nop.Duration.TotalMilliseconds == 0.0) 31 | { 32 | logger.Debug("Ignoring {$Code}", e.Code); 33 | } 34 | else 35 | { 36 | logger.Debug("Executing {@Code} with {@Operation}", e.Code, e.Operation); 37 | } 38 | } 39 | 40 | private void LoggingEmulator_CommentDetected(object sender, CommentEventArgs e) 41 | { 42 | var info = SpanInfoFor(e.Span); 43 | if (info == null) 44 | { 45 | logger.Debug("Comment: {Comment}", e.Comment); 46 | } 47 | else 48 | { 49 | logger.Debug("Comment: \"{Comment}\" at L{Line},C{Column}", e.Comment, info.Start.Line, info.Start.Column, info); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Gcodes.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using Gcodes.Runtime; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Exceptions; 6 | using System; 7 | using System.IO; 8 | 9 | namespace Gcodes.Console 10 | { 11 | class Program 12 | { 13 | private static OperationFactory operations = new OperationFactory(); 14 | 15 | static int Main(string[] args) 16 | { 17 | var parsedArgs = CommandLine.Parser.Default.ParseArguments(args); 18 | 19 | operations.IgnoreGcode(78); 20 | 21 | return parsedArgs.MapResult(opts => Run(opts), _ => 1); 22 | } 23 | 24 | private static void Initializelogger(Options opts) 25 | { 26 | var minLevel = opts.Verbose ? LogEventLevel.Debug : LogEventLevel.Information; 27 | 28 | Log.Logger = new LoggerConfiguration() 29 | .Enrich.WithExceptionDetails() 30 | .MinimumLevel.Is(minLevel) 31 | .WriteTo.Console() 32 | .CreateLogger(); 33 | } 34 | 35 | private static int Run(Options opts) 36 | { 37 | Initializelogger(opts); 38 | 39 | try 40 | { 41 | Log.Debug("Reading {Filename}", opts.InputFile); 42 | var src = File.ReadAllText(opts.InputFile); 43 | 44 | var vm = new LoggingEmulator 45 | { 46 | Operations = operations 47 | }; 48 | vm.Run(src); 49 | } 50 | catch (Exception ex) 51 | { 52 | Log.Error(ex.Message); 53 | Log.Debug(ex, "An error occurred"); 54 | return 1; 55 | } 56 | 57 | return 0; 58 | } 59 | } 60 | 61 | class Options 62 | { 63 | [Value(0, MetaName = "input file", HelpText = "The gcode file to interpret", Required = true)] 64 | public string InputFile { get; internal set; } 65 | [Option(SetName = "verbose", Default = false, HelpText = "Enable verbose output")] 66 | public bool Verbose { get; internal set; } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Gcodes.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GcodeInterpreter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("GcodeInterpreter")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bc78987c-b98a-4cd1-a44f-d6ce48a265f0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Gcodes.Console/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Gcodes.Test/EmbeddedFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Gcodes.Test 9 | { 10 | class EmbeddedFixture 11 | { 12 | public static string ExtractFile(string filename) 13 | { 14 | var asm = typeof(EmbeddedFixture).Assembly; 15 | filename = asm.GetName().Name + ".Fixtures." + filename; 16 | 17 | var availableFiles = asm.GetManifestResourceNames(); 18 | if (!availableFiles.Contains(filename)) 19 | { 20 | throw new ArgumentException($"Resource \"{filename}\" not found in {string.Join(", ", availableFiles)}"); 21 | } 22 | 23 | using (var stream = asm.GetManifestResourceStream(filename)) 24 | { 25 | using (StreamReader reader = new StreamReader(stream)) 26 | { 27 | return reader.ReadToEnd(); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Gcodes.Test/FileMapTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gcodes.Tokens; 3 | using Xunit; 4 | 5 | namespace Gcodes.Test 6 | { 7 | public class FileMapTest 8 | { 9 | 10 | [Fact] 11 | public void CanInstantiate() 12 | { 13 | var src = "Hello World"; 14 | 15 | var fm = new FileMap(src); 16 | } 17 | 18 | [Theory] 19 | [InlineData(5, 1, 6, 'i')] 20 | // end of the first line 21 | [InlineData(65, 1, 66, '/')] 22 | [InlineData(70, 3, 1, 'G')] 23 | [InlineData(75, 3, 6, '2')] 24 | public void YouCanLookUpLocationDetails(int index, int line, int column, char sanityCheck) 25 | { 26 | var src = EmbeddedFixture.ExtractFile("circle.gcode"); 27 | Assert.Equal(sanityCheck, src[index]); 28 | var map = new FileMap(src); 29 | var shouldBe = new Location(index, line, column); 30 | 31 | var got = map.LocationFor(index); 32 | 33 | Assert.Equal(shouldBe, got); 34 | } 35 | 36 | [Theory] 37 | [InlineData(3, "\nabcdefghijk", 3, 'c')] 38 | [InlineData(3, "abcdefghijk", 4, 'd')] 39 | [InlineData(8, "abc\r\ndefghijk", 4, 'g')] 40 | public void CheckColumnIndices(int index, string src, int shouldBe, char sanityCheck) 41 | { 42 | Assert.Equal(sanityCheck, src[index]); 43 | var map = new FileMap(src); 44 | 45 | var got = map.ColumnNumber(index); 46 | 47 | Assert.Equal(shouldBe, got); 48 | } 49 | 50 | [Theory] 51 | [InlineData(10, "ab\ncdef\nghij\n", 3, 'i')] 52 | [InlineData(10 + 2, "ab\r\ncdef\r\nghij\n", 3, 'i')] 53 | [InlineData(2, "ab\ncdef\nghij\n", 1, '\n')] 54 | [InlineData(8, "ab\r\ncdef\r\nghij\n", 2, '\r')] 55 | [InlineData(9, "ab\r\ncdef\r\nghij\n", 2, '\n')] 56 | [InlineData(0, "ab\ncdef\nghij\n", 1, 'a')] 57 | [InlineData(4, "ab\r\ncdef\r\nghij\n", 2, 'c')] 58 | [InlineData(39, " N1 (* ALPHA V2.0 P80 U 5 FF MV)\r\n N2 G78 M13", 2, 'N')] 59 | [InlineData(46, " N1 (* ALPHA V2.0 P80 U 5 FF MV)\r\n N2 G78 M13", 2, '8')] 60 | public void CheckLineNumbers(int index, string src, int shouldBe, char sanityCheck) 61 | { 62 | Assert.Equal(sanityCheck, src[index]); 63 | var map = new FileMap(src); 64 | 65 | var got = map.LineNumber(index); 66 | 67 | Assert.Equal(shouldBe, got); 68 | } 69 | 70 | [Theory] 71 | [InlineData(1, 5, "ab\r\ncdef\r\nghij\n", "b\r\nc")] 72 | [InlineData(0, 2, "ab\r\ncdef\r\nghij\n", "ab")] 73 | [InlineData(39, 47, " N1 (* ALPHA V2.0 P80 U 5 FF MV)\r\n N2 G78 M13", "N2 G78")] 74 | public void GetSpanInfo(int start, int end, string src, string value) 75 | { 76 | var map = new FileMap(src); 77 | var span = new Span(start, end); 78 | var shouldBe = new SpanInfo(span, map.LocationFor(start), map.LocationFor(end), value); 79 | 80 | var got = map.SpanInfoFor(span); 81 | 82 | Assert.Equal(shouldBe, got); 83 | } 84 | 85 | [Theory] 86 | [InlineData(10, "ab\ncdef\nghij\n", 3)] 87 | [InlineData(10 + 2, "ab\r\ncdef\r\nghij\n", 3)] 88 | [InlineData(2, "ab\ncdef\nghij\n", 1)] 89 | [InlineData(0, "ab\ncdef\nghij\n", 1)] 90 | [InlineData(4, "ab\r\ncdef\r\nghij\n", 2)] 91 | [InlineData(39, " N1 (* ALPHA V2.0 P80 U 5 FF MV)\r\n N2 G78 M13", 2)] 92 | [InlineData(46, " N1 (* ALPHA V2.0 P80 U 5 FF MV)\r\n N2 G78 M13", 2)] 93 | public void WarmTheCacheAndGetLocations(int index, string src, int shouldBe) 94 | { 95 | var map = new FileMap(src); 96 | WarmFileMapCache(map, src.Length); 97 | 98 | var got = map.LineNumber(index); 99 | 100 | Assert.Equal(shouldBe, got); 101 | } 102 | 103 | private void WarmFileMapCache(FileMap map, int length) 104 | { 105 | var rng = new Random(1234); 106 | var currentIndex = 0; 107 | 108 | while (currentIndex < length) 109 | { 110 | map.LocationFor(currentIndex); 111 | currentIndex += rng.Next(1, 20); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Gcodes.Test/Fixtures/371373P.gcode: -------------------------------------------------------------------------------- 1 | N1 (* ALPHA V2.0 P80 U 5 FF MV) 2 | N2 G78 M13 3 | N3 G92 X0 Y0 4 | N4 G91 5 | N5 G22 C1 B0 6 | N6 G4 H5 M10 7 | N7 G4 H5 M11 8 | N8 G22 C2 B0 9 | N9 G21 A12 10 | N10 G22 C3 B0 11 | N11 G20 A6 12 | N12 G22 C4 B0 13 | N13 G22 C5 B0 14 | N14 G4 H5 M12 15 | N15 G4 H5 M13 16 | N16 G20 A6 17 | N17 18 | N18 G98 C1 19 | N19 G4 H2 20 | N20 G1 X3.51 Y-2.98 F10000 21 | N21 G99 22 | N22 23 | N23 G98 C2 24 | N24 G4 H2 25 | N25 G1 X3.74 Y-6.27 F10000 26 | N26 G4 H2 27 | N27 G1 X215.75 F10000 28 | N28 G4 H2 29 | N29 G1 X-215.75 F10000 30 | N30 G4 H2 31 | N31 G1 Y-1201.50 F10000 32 | N32 G4 H2 33 | N33 G1 X210.76 F10000 34 | N34 G4 H2 35 | N35 G4 H2 36 | N36 G4 H2 37 | N37 G1 X1.35 Y300.37 F10000 38 | N38 G1 X1.28 Y300.37 F10000 39 | N39 G1 X1.21 Y300.38 F10000 40 | N40 G1 X1.15 Y300.38 F10000 41 | N41 G4 H2 42 | N42 G1 X3.18 F10000 43 | N43 G4 H2 44 | N44 G1 X20.96 Y-623.48 F10000 45 | N45 G1 X21.53 Y-578.02 F10000 46 | N46 G4 H2 47 | N47 G1 X722.08 F10000 48 | N48 G4 H2 49 | N49 G1 Y1201.50 F10000 50 | N50 G4 H2 51 | N51 G1 X-764.57 F10000 52 | N52 G4 H2 53 | N53 G1 X-4.66 Y5.56 F10000 54 | N54 G4 H2 55 | N55 G1 X-218.47 Y1.93 F10000 56 | N56 G99 57 | N57 58 | N58 G98 C3 59 | N59 G99 60 | N60 61 | N61 G98 C4 62 | N62 G99 63 | N63 64 | N64 G98 C5 65 | N65 G99 66 | N66 67 | N67 (/1 Quality = Maschi._100%) 68 | N68 (/) 69 | N69 (/--------- OFS-H & OFS-HEH -------) 70 | N70 (/2 Blocklength = 1300.00) 71 | N71 (/3 Blockheight = 2500.00) 72 | N72 (/4 Width = 20.00) 73 | N73 (/) 74 | N74 (/5 Piece-nr. parts = 0) 75 | N75 (/6 Piece-nr. rows = 0) 76 | N76 (/7 Piece-nr. tot. = 0) 77 | N77 (/8 Piece-nr. = 0) 78 | N78 (/9 length of margin = 0.00) 79 | N79 (/A height of margin = 0.00) 80 | N80 (/B width of margin = 20.00) 81 | N81 (/C width of the pieces = 980.00) 82 | N82 (/D material weight = 10.0) 83 | N83 (/E height of sheet = 0.00) 84 | N84 (/F length of sheet = 0.00) 85 | N85 (/G Amount of sheets = 0) 86 | N86 (/H sub-prog.number = 0) 87 | N87 (/I advance of sheets= 4000) 88 | N88 (/) 89 | N89 (/-------------OFS-HEV-------------) 90 | N90 (/M Blocklength = 1000.00) 91 | N91 (/N Blockheight = 1000.00) 92 | N92 (/) 93 | N93 (/O Piece-nr. parts = 0) 94 | N94 (/P Piece-nr. rows = 0) 95 | N95 (/Q Piece-nr. tot. = 0) 96 | N96 (/R Piece-nr. = 0) 97 | N97 (/S length of margin = 20.00) 98 | N98 (/T height of margin = 20.00) 99 | N99 ( 20 5) 100 | N100 ( 10 585.20) 101 | N101 ( 11 510.34) 102 | N102 ( 12 0.000) 103 | N103 ( 10 592.96) 104 | N104 ( 11 510.34) 105 | N105 ( 12 692778.937) 106 | N106 ( 10 1385.20) 107 | N107 ( 11 10.00) 108 | N108 ( 12 0.000) 109 | N109 ( 10 1385.20) 110 | N110 ( 11 10.00) 111 | N111 ( 12 0.000) 112 | N112 ( 10 1385.20) 113 | N113 ( 11 10.00) 114 | N114 ( 12 0.000) 115 | N115 ( 13 ) 116 | N116 ( 15 5.00) 117 | N117 ( 16 0.00) 118 | N118 ( 17 5.00) 119 | N119 ( 18 0) 120 | N120 ( 19 5.00) 121 | N121 ( 21 0) 122 | N122 ( 22 0) 123 | N123 ( 23 0.00) 124 | N124 ( 24 0.00) 125 | N125 ( 25 ) 126 | N126 ( 26 ) 127 | N127 ( 27 0.00) 128 | N128 ( 28 0.00) 129 | N129 ( 29 0.00) 130 | N130 ( 30 0) 131 | N131 ( 31 0) 132 | N132 ( 32 0) 133 | N133 ( 33 0) 134 | N134 ( 34 0) 135 | N135 ( 35 0) 136 | N136 ( 36 0.00) 137 | N137 ( 37 0) 138 | N138 ( 38 0) 139 | N139 ( 39 0) 140 | N140 ( 40 0) 141 | N141 ( 41 ) 142 | N142 ( 42 ) 143 | N143 ( 43 0) 144 | N144 ( 44 0) 145 | N145 ( 45 0) 146 | N146 ( 46 0.00) 147 | N147 ( 47 0.00) 148 | N148 ( 48 0.00) 149 | N149 ( 49 0) 150 | N150 ( 50 0.00) 151 | N151 ( 49 0) 152 | N152 ( 50 0.00) 153 | N153 ( 49 0) 154 | N154 ( 50 0.00) 155 | N155 ( 49 0) 156 | N156 ( 50 0.00) 157 | N157 ( 49 0) 158 | N158 ( 50 0.00) 159 | N159 ( 1 MA05) 160 | N160 ( 3 1178.12) 161 | N161 ( 4 510.32) 162 | N162 ( 5 0.00) 163 | N163 ( 6 0) 164 | N164 ( 7 1) 165 | N165 ( 8 2) 166 | N166 ( 1 MA01) 167 | N167 ( 3 585.78) 168 | N168 ( 4 -681.96) 169 | N169 ( 5 0.00) 170 | N170 ( 6 0) 171 | N171 ( 7 1) 172 | N172 ( 8 2) 173 | N173 ( 123 0.00) 174 | N174 ( 124 0.00) 175 | N175 ( 127 0.00) 176 | N176 ( 128 0.00) 177 | N177 ( 130 0) 178 | N178 ( 131 0) 179 | N179 ( 132 0) 180 | N180 ( 133 0) 181 | N181 ( 146 0.00) 182 | N182 ( 147 0.00) 183 | N183 ( 201 2.00) 184 | N184 ( 202 10.00) 185 | N185 ( 203 15.00) 186 | N186 ( 204 20.00) 187 | N187 ( 205 0.03) 188 | N188 ( 206 ) 189 | -------------------------------------------------------------------------------- /Gcodes.Test/Fixtures/circle.gcode: -------------------------------------------------------------------------------- 1 | ; A circle. Taken from http://www.diymachining.com/g-code-example/ 2 | 3 | G17 G20 G90 G94 G54 4 | G0 Z0.25 5 | G0 X-0.5 Y0. 6 | G0 Z0.1 7 | G01 Z0. F5. 8 | G02 X0. Y0.5 I0.5 J0. F2.5 9 | G0 X0.5 Y0. I0. J-0.5 10 | G0 X0. Y-0.5 I-0.5 J0. 11 | G0 X-0.5 Y0. I0. J0.5 12 | G01 Z0.1 F5. 13 | G00 X0. Y0. Z0.25 -------------------------------------------------------------------------------- /Gcodes.Test/Fixtures/simple_mill.gcode: -------------------------------------------------------------------------------- 1 | ; Basic gcode example 2 | ; taken from http://www.helmancnc.com/simple-g-code-example-mill-g-code-programming-for-beginners/ 3 | 4 | O1000 5 | T1 M6 6 | (Linear / Feed - Absolute) 7 | G0 G90 G40 G21 G17 G94 G80 8 | G54 X-75 Y-75 S500 M3 (Position 6) 9 | G43 Z100 H1 10 | G43 Z5 11 | G1 Z-20 F100 12 | G1 X-40 (Position 1) 13 | G1 Y40 M8 (Position 2) 14 | G1 X40 (Position 3) 15 | G1 Y-40 (Position 4) 16 | G1 X-75 (Position 5) 17 | G1 Y-75 (Position 6) 18 | G0 Z100 19 | M30 -------------------------------------------------------------------------------- /Gcodes.Test/Gcodes.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {8EB51AC3-86B9-462E-AA48-1CC48E3D6129} 10 | Library 11 | Properties 12 | Gcodes.Test 13 | Gcodes.Test 14 | v4.7.2 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll 47 | 48 | 49 | ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll 50 | 51 | 52 | ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll 53 | 54 | 55 | ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {196c59da-b880-465d-a1bb-c114067ca838} 81 | Gcodes 82 | 83 | 84 | 85 | 86 | PreserveNewest 87 | 88 | 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | 95 | 96 | 97 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Gcodes.Test/LexerTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace Gcodes.Test 8 | { 9 | public class LexerTest 10 | { 11 | [Fact] 12 | public void CanInstantiate() 13 | { 14 | var lexer = new Lexer("asd"); 15 | } 16 | 17 | [Fact] 18 | public void DetectInvalidCharacters() 19 | { 20 | var shouldBe = new UnrecognisedCharacterException(1, 1, '$'); 21 | var lexer = new Lexer("$Foo"); 22 | 23 | try 24 | { 25 | lexer.Tokenize().ToList(); 26 | } 27 | catch (UnrecognisedCharacterException got) 28 | { 29 | Assert.Equal(shouldBe.Line, got.Line); 30 | Assert.Equal(shouldBe.Column, got.Column); 31 | Assert.Equal(shouldBe.Character, got.Character); 32 | return; 33 | } 34 | 35 | throw new Exception("No exception was thrown"); 36 | } 37 | 38 | [Theory] 39 | [InlineData("G", TokenKind.G)] 40 | [InlineData("O", TokenKind.O)] 41 | [InlineData("g", TokenKind.G)] 42 | [InlineData("N", TokenKind.N)] 43 | [InlineData("M", TokenKind.M)] 44 | [InlineData("T", TokenKind.T)] 45 | [InlineData("X", TokenKind.X)] 46 | [InlineData("Y", TokenKind.Y)] 47 | [InlineData("Z", TokenKind.Z)] 48 | [InlineData("F", TokenKind.F)] 49 | [InlineData("I", TokenKind.I)] 50 | [InlineData("J", TokenKind.J)] 51 | [InlineData("K", TokenKind.K)] 52 | [InlineData("A", TokenKind.A)] 53 | [InlineData("b", TokenKind.B)] 54 | [InlineData("C", TokenKind.C)] 55 | [InlineData("c", TokenKind.C)] 56 | [InlineData("H", TokenKind.H)] 57 | [InlineData("P", TokenKind.P)] 58 | [InlineData("S", TokenKind.S)] 59 | public void RecogniseStandardTokens(string src, TokenKind kind) 60 | { 61 | var lexer = new Lexer(src); 62 | var tok = lexer.Tokenize().First(); 63 | 64 | Assert.Equal(kind, tok.Kind); 65 | } 66 | 67 | [Theory] 68 | [InlineData("12")] 69 | [InlineData("1.23")] 70 | [InlineData(".23")] 71 | [InlineData("0.")] 72 | [InlineData("-1.23")] 73 | [InlineData("-1.")] 74 | public void RecogniseVariousNumbers(string src) 75 | { 76 | var lexer = new Lexer(src); 77 | var tok = lexer.Tokenize().First(); 78 | 79 | Assert.Equal(TokenKind.Number, tok.Kind); 80 | } 81 | 82 | [Fact] 83 | public void SkipComments() 84 | { 85 | var lexer = new Lexer("; this is a comment\nG13"); 86 | 87 | var tok = lexer.Tokenize().First(); 88 | Assert.Equal(TokenKind.G, tok.Kind); 89 | } 90 | 91 | [Fact] 92 | public void BracketsAreCommentsToo() 93 | { 94 | var lexer = new Lexer("( this is a comment)G13"); 95 | 96 | var tok = lexer.Tokenize().First(); 97 | Assert.Equal(TokenKind.G, tok.Kind); 98 | } 99 | 100 | [Fact] 101 | public void CommentsTriggerAnEvent() 102 | { 103 | const string src = "( this is a comment)G13\n;And so is this\n"; 104 | var lexer = new Lexer(src); 105 | var comments = new List(); 106 | lexer.CommentDetected += (s, e) => comments.Add(e); 107 | 108 | _ = lexer.Tokenize().Count(); 109 | 110 | var commentsShouldBe = new List { " this is a comment", "And so is this" }; 111 | Assert.Equal(commentsShouldBe, comments.Select(e => e.Comment).ToList()); 112 | var spansShouldBe = new List { new Span(0, 20), new Span(24, src.Length - 1) }; 113 | Assert.Equal(spansShouldBe, comments.Select(e => e.Span).ToList()); 114 | } 115 | 116 | [Fact] 117 | public void SkipWhitespace() 118 | { 119 | var lexer = new Lexer(" G"); 120 | 121 | var tok = lexer.Tokenize().First(); 122 | Assert.Equal(TokenKind.G, tok.Kind); 123 | } 124 | 125 | [Fact] 126 | public void LexSomeBasicGcodeStuff() 127 | { 128 | var lexer = new Lexer("G10 X50.0 Y100.0"); 129 | var shouldBe = new List 130 | { 131 | new Token(new Span(0, 1), TokenKind.G), 132 | new Token(new Span(1, 3), TokenKind.Number, "10"), 133 | new Token(new Span(4, 5), TokenKind.X), 134 | new Token(new Span(5, 9), TokenKind.Number, "50.0"), 135 | new Token(new Span(10, 11), TokenKind.Y), 136 | new Token(new Span(11, 16), TokenKind.Number, "100.0"), 137 | }; 138 | 139 | var got = lexer.Tokenize().ToList(); 140 | 141 | Assert.Equal(shouldBe, got); 142 | } 143 | 144 | [Theory] 145 | [InlineData("G0 X-0.5 Y0.")] 146 | [InlineData("Y0.")] 147 | public void TokenizeTroublesomeLines(string src) 148 | { 149 | var lexer = new Lexer(src); 150 | var got = lexer.Tokenize().ToList(); 151 | 152 | Assert.NotEmpty(got); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Gcodes.Test/ParserTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using Gcodes.Tokens; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace Gcodes.Test 9 | { 10 | public class ParserTest 11 | { 12 | [Fact] 13 | public void BoringGcode() 14 | { 15 | var src = "G01"; 16 | var parser = new Parser(src); 17 | 18 | var got = parser.ParseGCode(); 19 | 20 | var shouldBe = new Gcode(1, new List(), new Span(0, src.Length)); 21 | Assert.Equal(shouldBe, got); 22 | } 23 | 24 | [Fact] 25 | public void BoringMcode() 26 | { 27 | var src = "N10 M5"; 28 | var parser = new Parser(src); 29 | 30 | var got = parser.ParseMCode(); 31 | 32 | var shouldBe = new Mcode(5, new Span(0, src.Length), 10); 33 | Assert.Equal(shouldBe, got); 34 | } 35 | 36 | [Fact] 37 | public void ProgramNumber() 38 | { 39 | var src = "N10 O500"; 40 | var parser = new Parser(src); 41 | 42 | var got = parser.ParseOCode(); 43 | 44 | var shouldBe = new Ocode(500, new Span(0, src.Length), 10); 45 | Assert.Equal(shouldBe, got); 46 | } 47 | 48 | [Fact] 49 | public void TCode() 50 | { 51 | var src = "N10 T30"; 52 | var parser = new Parser(src); 53 | 54 | var got = parser.ParseTCode(); 55 | 56 | var shouldBe = new Tcode(30, new Span(0, src.Length), 10); 57 | Assert.Equal(shouldBe, got); 58 | } 59 | 60 | [Fact] 61 | public void MoreInterestingGcode() 62 | { 63 | var src = "G555 X-23.4 F1200 Y-0.0 Z+3.1415"; 64 | var parser = new Parser(src); 65 | 66 | var got = parser.ParseGCode(); 67 | 68 | Assert.Equal(555, got.Number); 69 | Assert.Equal(new Span(0, src.Length), got.Span); 70 | Assert.Equal(4, got.Arguments.Count); 71 | 72 | Assert.Equal(-23.4, got.ValueFor(ArgumentKind.X)); 73 | Assert.Equal(1200, got.ValueFor(ArgumentKind.FeedRate)); 74 | Assert.Equal(0, got.ValueFor(ArgumentKind.Y)); 75 | Assert.Equal(3.1415, got.ValueFor(ArgumentKind.Z)); 76 | } 77 | 78 | [Fact] 79 | public void ParseLineNumber() 80 | { 81 | var src = "n10"; 82 | var parser = new Parser(src); 83 | 84 | var got = parser.ParseLineNumber(); 85 | 86 | Assert.Equal(10, got.Number); 87 | Assert.Equal(new Span(0, 3), got.Span); 88 | Assert.True(parser.GetFinished()); 89 | } 90 | 91 | [Theory] 92 | [InlineData("N50.0")] 93 | [InlineData("N-23")] 94 | public void LineNumbersAreIntegers(string src) 95 | { 96 | var parser = new Parser(src); 97 | Assert.Throws(() => parser.ParseLineNumber()); 98 | } 99 | 100 | [Theory] 101 | [InlineData("X50", ArgumentKind.X, 50.0)] 102 | [InlineData("F-30.5", ArgumentKind.FeedRate, -30.5)] 103 | public void ParseAnArgument(string src, ArgumentKind kind, double value) 104 | { 105 | var parser = new Parser(src); 106 | var shouldBe = new Argument(kind, value, new Span(0, src.Length)); 107 | 108 | var got = parser.ParseArgument(); 109 | 110 | Assert.Equal(shouldBe, got); 111 | } 112 | 113 | [Theory] 114 | [InlineData('a')] 115 | [InlineData('b')] 116 | [InlineData('C')] 117 | [InlineData('P')] 118 | [InlineData('X')] 119 | [InlineData('Y')] 120 | [InlineData('Z')] 121 | [InlineData('I')] 122 | [InlineData('J')] 123 | [InlineData('K')] 124 | [InlineData('S')] 125 | [InlineData('H')] 126 | public void CharacterIsRecognisedAsArgumentKind(char c) 127 | { 128 | var parser = new Parser($"{c}50.0"); 129 | 130 | var got = parser.ParseArgument(); 131 | 132 | Assert.NotNull(got); 133 | // use reflection to check there's a corresponding argument kind 134 | var variants = Enum.GetNames(typeof(ArgumentKind)); 135 | var variantNameShouldBe = c.ToString().ToUpper(); 136 | Assert.Contains(variantNameShouldBe, variants); 137 | } 138 | 139 | [Fact] 140 | public void FeedRateIsArgumentKind() 141 | { 142 | var parser = new Parser("F1200"); 143 | 144 | var got = parser.ParseArgument(); 145 | 146 | Assert.NotNull(got); 147 | Assert.Equal(ArgumentKind.FeedRate, got.Kind); 148 | } 149 | 150 | [Fact] 151 | public void AllArgumentKindsCanBeConvertedFromTokens() 152 | { 153 | var variants = Enum.GetNames(typeof(ArgumentKind)) 154 | .Where(v => v != "FeedRate"); // feed rate doesn't follow the naming convention 155 | 156 | foreach (var variant in variants) 157 | { 158 | var tokenKind = (TokenKind) Enum.Parse(typeof(TokenKind), variant); 159 | var asArgumentKind = tokenKind.AsArgumentKind(); 160 | 161 | var convertedTo = Enum.GetName(typeof(ArgumentKind), asArgumentKind); 162 | Assert.Equal(variant, convertedTo); 163 | } 164 | 165 | Assert.Equal(ArgumentKind.FeedRate, TokenKind.F.AsArgumentKind()); 166 | } 167 | 168 | [Theory] 169 | [InlineData("circle.gcode")] 170 | [InlineData("simple_mill.gcode")] 171 | [InlineData("371373P.gcode")] 172 | public void ParseRealGcodes(string filename) 173 | { 174 | var src = EmbeddedFixture.ExtractFile(filename); 175 | var parser = new Parser(src); 176 | 177 | var got = parser.Parse().ToList(); 178 | 179 | Assert.NotEmpty(got); 180 | } 181 | 182 | [Fact] 183 | public void IgnoreDuplicateLineNumbers() 184 | { 185 | var src = "N1 N2 N50 G1"; 186 | var parser = new Parser(src); 187 | 188 | var got = parser.ParseLineNumber(); 189 | 190 | Assert.NotNull(got); 191 | Assert.Equal(50, got.Number); 192 | } 193 | 194 | [Fact] 195 | public void ParserIsDoneWhenOnlyLineNumbersAreLeft() 196 | { 197 | var src = "N1 N2 N3 N4"; 198 | var parser = new Parser(src); 199 | 200 | Assert.True(parser.GetFinished()); 201 | } 202 | 203 | [Fact] 204 | public void BaumerCodeHas64Items() 205 | { 206 | var src = EmbeddedFixture.ExtractFile("371373P.gcode"); 207 | var parser = new Parser(src); 208 | 209 | var numCodes = parser.Parse().Count(); 210 | 211 | // manually counted the number of g/m codes in the baumer file :( 212 | Assert.Equal(64, numCodes); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Gcodes.Test/PatternTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Gcodes.Test 6 | { 7 | public class PatternTest 8 | { 9 | [Fact] 10 | public void CanInstantiate() 11 | { 12 | var pat = new Pattern(@"G", TokenKind.G); 13 | } 14 | 15 | [Fact] 16 | public void MatchASimpleInteger() 17 | { 18 | var src = "123"; 19 | var pat = new Pattern(@"\d+", TokenKind.Number); 20 | 21 | Assert.True(pat.TryMatch(src, 0, out Token tok)); 22 | 23 | Assert.Equal(new Span(0, src.Length), tok.Span); 24 | Assert.Equal(TokenKind.Number, tok.Kind); 25 | Assert.Equal("123", tok.Value); 26 | } 27 | 28 | [Fact] 29 | public void NotMatched() 30 | { 31 | var src = "123"; 32 | var pat = new Pattern(@"\G[a-z]+", TokenKind.Number); 33 | 34 | Assert.False(pat.TryMatch(src, 0, out Token tok)); 35 | Assert.Null(tok); 36 | } 37 | 38 | [Fact] 39 | public void PatternIgnoreCase() 40 | { 41 | var pat = new Pattern(@"\Gasd", TokenKind.Number); 42 | 43 | 44 | Assert.True(pat.TryMatch("asd", 0, out Token tok)); 45 | Assert.True(pat.TryMatch("ASD", 0, out tok)); 46 | Assert.True(pat.TryMatch("AsD", 0, out tok)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Gcodes.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Gcode.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("Gcode.Test")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8eb51ac3-86b9-462e-aa48-1cc48e3d6129")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Gcodes.Test/Runtime/EmulatorTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Runtime; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Gcodes.Test.Runtime 10 | { 11 | public class EmulatorTest 12 | { 13 | [Fact] 14 | public void CanInstantiate() 15 | { 16 | var emulator = new Emulator(); 17 | } 18 | 19 | [Theory(Skip = "Not all operations are implemented")] 20 | [InlineData("circle.gcode")] 21 | [InlineData("simple_mill.gcode")] 22 | [InlineData("371373P.gcode")] 23 | public void EmulateAValidGcodeProgram(string filename) 24 | { 25 | var src = EmbeddedFixture.ExtractFile(filename); 26 | var emulator = new Emulator(); 27 | 28 | emulator.Run(src); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Gcodes.Test/Runtime/OperationFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using Gcodes.Runtime; 3 | using Gcodes.Tokens; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Gcodes.Test.Runtime 12 | { 13 | public class OperationFactoryTest 14 | { 15 | OperationFactory operations = new OperationFactory(); 16 | MachineState initialState = new MachineState(); 17 | 18 | [Fact] 19 | public void GetIgnoredInstruction() 20 | { 21 | const int number = 1; 22 | operations.IgnoreGcode(number); 23 | 24 | var got = operations.GcodeOp(new Gcode(number, new List(), Span.Empty), initialState); 25 | 26 | Assert.IsType(got); 27 | } 28 | 29 | [Fact] 30 | public void RecogniseADwell() 31 | { 32 | var duration = 5; 33 | var code = new Gcode(4, new List { new Argument(ArgumentKind.P, duration * 1000, Span.Empty) }, Span.Empty); 34 | var shouldBe = new Noop(initialState, duration); 35 | 36 | var got = operations.GcodeOp(code, initialState); 37 | 38 | Assert.Equal(shouldBe, got); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Gcodes.Test/TokenKindTest.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using Gcodes.Tokens; 3 | using System; 4 | using Xunit; 5 | 6 | namespace Gcodes.Test 7 | { 8 | public class TokenKindTest 9 | { 10 | [Theory] 11 | [InlineData(TokenKind.X, ArgumentKind.X)] 12 | [InlineData(TokenKind.Y, ArgumentKind.Y)] 13 | [InlineData(TokenKind.Z, ArgumentKind.Z)] 14 | [InlineData(TokenKind.F, ArgumentKind.FeedRate)] 15 | public void ConvertTokenKindsToArgumentKinds(TokenKind src, ArgumentKind shouldBe) 16 | { 17 | var got = src.AsArgumentKind(); 18 | Assert.Equal(shouldBe, got); 19 | } 20 | 21 | [Theory] 22 | [InlineData(TokenKind.Number)] 23 | [InlineData(TokenKind.G)] 24 | [InlineData(TokenKind.M)] 25 | [InlineData(TokenKind.N)] 26 | public void CantConvertInvalidTokenKinds(TokenKind src) 27 | { 28 | Assert.Throws(() => src.AsArgumentKind()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Gcodes.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Gcodes/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ############### 3 | # folder # 4 | ############### 5 | /**/DROP/ 6 | /**/TEMP/ 7 | /**/packages/ 8 | /**/bin/ 9 | /**/obj/ 10 | _site 11 | log.txt 12 | -------------------------------------------------------------------------------- /Gcodes/Ast/Argument.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Gcodes.Ast 6 | { 7 | /// 8 | /// A single argument, containing both an and 9 | /// its corresponding value. 10 | /// 11 | public class Argument : IEquatable 12 | { 13 | public Argument(ArgumentKind kind, double value, Span span) 14 | { 15 | Span = span; 16 | Kind = kind; 17 | Value = value; 18 | } 19 | 20 | /// 21 | /// The argument's location in its source text. 22 | /// 23 | public Span Span { get; } 24 | /// 25 | /// Which kind of argument is this? 26 | /// 27 | public ArgumentKind Kind { get; } 28 | /// 29 | /// The argument's value (e.g. feed rate for a ). 30 | /// 31 | public double Value { get; } 32 | 33 | #region Equals 34 | public override bool Equals(object obj) 35 | { 36 | return Equals(obj as Argument); 37 | } 38 | 39 | public bool Equals(Argument other) 40 | { 41 | return other != null && 42 | EqualityComparer.Default.Equals(Span, other.Span) && 43 | Kind == other.Kind && 44 | Value == other.Value; 45 | } 46 | 47 | public override int GetHashCode() 48 | { 49 | var hashCode = -1030702410; 50 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Span); 51 | hashCode = hashCode * -1521134295 + Kind.GetHashCode(); 52 | hashCode = hashCode * -1521134295 + Value.GetHashCode(); 53 | return hashCode; 54 | } 55 | 56 | public static bool operator ==(Argument argument1, Argument argument2) 57 | { 58 | return EqualityComparer.Default.Equals(argument1, argument2); 59 | } 60 | 61 | public static bool operator !=(Argument argument1, Argument argument2) 62 | { 63 | return !(argument1 == argument2); 64 | } 65 | #endregion 66 | } 67 | 68 | /// 69 | /// The various types of gcode arguments. 70 | /// 71 | public enum ArgumentKind 72 | { 73 | X, 74 | Y, 75 | Z, 76 | FeedRate, 77 | K, 78 | J, 79 | I, 80 | C, 81 | B, 82 | A, 83 | P, 84 | S, 85 | H, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Gcodes/Ast/Code.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Gcodes.Ast 9 | { 10 | /// 11 | /// The base class all AST nodes in a gcode file inherit from. 12 | /// 13 | [Serializable] 14 | public abstract class Code : IEquatable 15 | { 16 | protected Code(Span span, int? line = null) 17 | { 18 | Line = line; 19 | Span = span; 20 | } 21 | 22 | /// 23 | /// The item's line number, if one was supplied. 24 | /// 25 | public int? Line { get; } 26 | /// 27 | /// The item's location within its source text. 28 | /// 29 | public Span Span { get; } 30 | 31 | /// 32 | /// 33 | /// 34 | /// 35 | public abstract void Accept(IGcodeVisitor visitor); 36 | 37 | #region Equals 38 | public override bool Equals(object obj) 39 | { 40 | return Equals(obj as Code); 41 | } 42 | 43 | public bool Equals(Code other) 44 | { 45 | return other != null && 46 | EqualityComparer.Default.Equals(Line, other.Line); 47 | } 48 | 49 | public override int GetHashCode() 50 | { 51 | return -2039293089 + EqualityComparer.Default.GetHashCode(Line); 52 | } 53 | 54 | public static bool operator ==(Code code1, Code code2) 55 | { 56 | return EqualityComparer.Default.Equals(code1, code2); 57 | } 58 | 59 | public static bool operator !=(Code code1, Code code2) 60 | { 61 | return !(code1 == code2); 62 | } 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Gcodes/Ast/Gcode.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Gcodes.Ast 8 | { 9 | /// 10 | /// A generic gcode. 11 | /// 12 | [Serializable] 13 | public class Gcode : Code, IEquatable 14 | { 15 | private List args; 16 | 17 | public Gcode(int number, List args, Span span, int? line = null) : base(span, line) 18 | { 19 | Number = number; 20 | this.args = args; 21 | } 22 | 23 | /// 24 | /// The kind of gcode this is. 25 | /// 26 | public int Number { get; } 27 | /// 28 | /// The full list of arguments attached to this gcode. 29 | /// 30 | public IReadOnlyList Arguments { get => args; } 31 | 32 | /// 33 | /// Get the value for a particular , if the 34 | /// argument was specified in this gcode. 35 | /// 36 | /// 37 | /// 38 | public double? ValueFor(ArgumentKind kind) 39 | { 40 | var found = args.Where(arg => arg.Kind == kind); 41 | 42 | if (found.Any()) 43 | { 44 | return found.First().Value; 45 | } 46 | else 47 | { 48 | return null; 49 | } 50 | } 51 | 52 | public double? ValueFor(params ArgumentKind[] kinds) 53 | { 54 | return kinds.Select(kind => ValueFor(kind)).Where(kind => kind != null).FirstOrDefault(); 55 | } 56 | 57 | public override void Accept(IGcodeVisitor visitor) 58 | { 59 | visitor.Visit(this); 60 | } 61 | 62 | public override string ToString() 63 | { 64 | var sb = new StringBuilder(); 65 | sb.AppendFormat("G{0}", Number); 66 | 67 | foreach (var arg in Arguments) 68 | { 69 | sb.AppendFormat(" {0}{1}", arg.Kind, arg.Value); 70 | } 71 | 72 | return sb.ToString(); 73 | } 74 | 75 | #region Equals 76 | public override bool Equals(object obj) 77 | { 78 | return Equals(obj as Gcode); 79 | } 80 | 81 | public bool Equals(Gcode other) 82 | { 83 | return other != null && 84 | base.Equals(other) && 85 | args.SequenceEqual(other.args) && 86 | Number == other.Number; 87 | } 88 | 89 | public override int GetHashCode() 90 | { 91 | var hashCode = 1590044514; 92 | hashCode = hashCode * -1521134295 + base.GetHashCode(); 93 | hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(args); 94 | hashCode = hashCode * -1521134295 + Number.GetHashCode(); 95 | return hashCode; 96 | } 97 | 98 | public static bool operator ==(Gcode gcode1, Gcode gcode2) 99 | { 100 | return EqualityComparer.Default.Equals(gcode1, gcode2); 101 | } 102 | 103 | public static bool operator !=(Gcode gcode1, Gcode gcode2) 104 | { 105 | return !(gcode1 == gcode2); 106 | } 107 | #endregion 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Gcodes/Ast/GcodeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Gcodes.Ast 8 | { 9 | /// 10 | /// An object which can be used to inspect gcodes at runtime using 11 | /// the visitor pattern. 12 | /// 13 | public interface IGcodeVisitor 14 | { 15 | void Visit(Gcode code); 16 | void Visit(Mcode code); 17 | void Visit(Tcode tcode); 18 | void Visit(Ocode code); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Gcodes/Ast/LineNumber.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Gcodes.Ast 6 | { 7 | internal class LineNumber : IEquatable 8 | { 9 | public Span Span { get; } 10 | public int Number { get; } 11 | 12 | public LineNumber(int number, Span span) 13 | { 14 | Span = span; 15 | Number = number; 16 | } 17 | 18 | public override bool Equals(object obj) 19 | { 20 | return Equals(obj as LineNumber); 21 | } 22 | 23 | public bool Equals(LineNumber other) 24 | { 25 | return other != null && 26 | EqualityComparer.Default.Equals(Span, other.Span) && 27 | Number == other.Number; 28 | } 29 | 30 | public override int GetHashCode() 31 | { 32 | var hashCode = 1293783753; 33 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Span); 34 | hashCode = hashCode * -1521134295 + Number.GetHashCode(); 35 | return hashCode; 36 | } 37 | 38 | public static bool operator ==(LineNumber number1, LineNumber number2) 39 | { 40 | return EqualityComparer.Default.Equals(number1, number2); 41 | } 42 | 43 | public static bool operator !=(LineNumber number1, LineNumber number2) 44 | { 45 | return !(number1 == number2); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Gcodes/Ast/Mcode.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Gcodes.Ast 6 | { 7 | /// 8 | /// A "machine" code, typically used for invoking special machine-specific 9 | /// subroutines or actions. 10 | /// 11 | public class Mcode : Code, IEquatable 12 | { 13 | public Mcode(int number, Span span, int? line = null): base(span, line) 14 | { 15 | Number = number; 16 | } 17 | 18 | public int Number { get; } 19 | 20 | public override void Accept(IGcodeVisitor visitor) 21 | { 22 | visitor.Visit(this); 23 | } 24 | 25 | #region Equals 26 | public override bool Equals(object obj) 27 | { 28 | return Equals(obj as Mcode); 29 | } 30 | 31 | public bool Equals(Mcode other) 32 | { 33 | return other != null && 34 | base.Equals(other) && 35 | Number == other.Number; 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | var hashCode = -2028225194; 41 | hashCode = hashCode * -1521134295 + base.GetHashCode(); 42 | hashCode = hashCode * -1521134295 + Number.GetHashCode(); 43 | return hashCode; 44 | } 45 | 46 | public static bool operator ==(Mcode mcode1, Mcode mcode2) 47 | { 48 | return EqualityComparer.Default.Equals(mcode1, mcode2); 49 | } 50 | 51 | public static bool operator !=(Mcode mcode1, Mcode mcode2) 52 | { 53 | return !(mcode1 == mcode2); 54 | } 55 | #endregion 56 | } 57 | } -------------------------------------------------------------------------------- /Gcodes/Ast/Ocode.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Gcodes.Ast 9 | { 10 | public class Ocode: Code 11 | { 12 | public Ocode(int programNumber, Span span, int? line = null): base(span, line) 13 | { 14 | ProgramNumber = programNumber; 15 | } 16 | 17 | public int ProgramNumber { get; } 18 | 19 | public override void Accept(IGcodeVisitor visitor) 20 | { 21 | visitor.Visit(this); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Gcodes/Ast/Tcode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Gcodes.Tokens; 7 | 8 | namespace Gcodes.Ast 9 | { 10 | /// 11 | /// An tool change instruction. 12 | /// 13 | public class Tcode : Code, IEquatable 14 | { 15 | public Tcode(int number, Span span, int? line = null) : base(span, line) 16 | { 17 | Number = number; 18 | } 19 | 20 | public int Number { get; } 21 | 22 | public override void Accept(IGcodeVisitor visitor) 23 | { 24 | visitor.Visit(this); 25 | } 26 | 27 | #region Equals 28 | public override bool Equals(object obj) 29 | { 30 | return Equals(obj as Tcode); 31 | } 32 | 33 | public bool Equals(Tcode other) 34 | { 35 | return other != null && 36 | base.Equals(other) && 37 | Number == other.Number; 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | var hashCode = -2028225194; 43 | hashCode = hashCode * -1521134295 + base.GetHashCode(); 44 | hashCode = hashCode * -1521134295 + Number.GetHashCode(); 45 | return hashCode; 46 | } 47 | 48 | public static bool operator ==(Tcode tcode1, Tcode tcode2) 49 | { 50 | return EqualityComparer.Default.Equals(tcode1, tcode2); 51 | } 52 | 53 | public static bool operator !=(Tcode tcode1, Tcode tcode2) 54 | { 55 | return !(tcode1 == tcode2); 56 | } 57 | #endregion 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Gcodes/Exceptions.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | 4 | namespace Gcodes 5 | { 6 | 7 | [Serializable] 8 | public class GcodeException : Exception 9 | { 10 | public GcodeException() { } 11 | public GcodeException(string message) : base(message) { } 12 | public GcodeException(string message, Exception inner) : base(message, inner) { } 13 | protected GcodeException( 14 | System.Runtime.Serialization.SerializationInfo info, 15 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 16 | } 17 | 18 | 19 | [Serializable] 20 | public class UnrecognisedCharacterException : GcodeException 21 | { 22 | public int Line { get; } 23 | public int Column { get; } 24 | public char Character { get; } 25 | 26 | public UnrecognisedCharacterException(int line, int column, char character) 27 | : this($"Unrecognised character \"{character}\" at line {line} column {column}") 28 | { 29 | Line = line; 30 | Column = column; 31 | Character = character; 32 | } 33 | public UnrecognisedCharacterException() { } 34 | public UnrecognisedCharacterException(string message) : base(message) { } 35 | public UnrecognisedCharacterException(string message, Exception inner) : base(message, inner) { } 36 | protected UnrecognisedCharacterException( 37 | System.Runtime.Serialization.SerializationInfo info, 38 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 39 | } 40 | 41 | 42 | [Serializable] 43 | public class ParseException : GcodeException 44 | { 45 | public Span Span { get; } = Span.Empty; 46 | public ParseException() { } 47 | public ParseException(string message) : base(message) { } 48 | public ParseException(string message, Span span) : base(message) 49 | { 50 | Span = span; 51 | } 52 | public ParseException(string message, Exception inner) : base(message, inner) { } 53 | protected ParseException( 54 | System.Runtime.Serialization.SerializationInfo info, 55 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 56 | } 57 | 58 | 59 | [Serializable] 60 | public class UnexpectedEOFException : ParseException 61 | { 62 | public TokenKind[] Expected { get; } 63 | 64 | public UnexpectedEOFException(TokenKind[] expected) 65 | : this($"Expected one of [{string.Join(", ", expected)}] but reached the end of input") 66 | { 67 | Expected = expected; 68 | } 69 | public UnexpectedEOFException() { } 70 | public UnexpectedEOFException(string message) : base(message) { } 71 | public UnexpectedEOFException(string message, Exception inner) : base(message, inner) { } 72 | protected UnexpectedEOFException( 73 | System.Runtime.Serialization.SerializationInfo info, 74 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 75 | } 76 | 77 | [Serializable] 78 | public class UnexpectedTokenException : ParseException 79 | { 80 | public TokenKind[] Expected { get; } 81 | public TokenKind Found { get; } 82 | 83 | public UnexpectedTokenException(TokenKind[] expected, TokenKind found, Span span) 84 | : base($"Expected one of [{string.Join(", ", expected)}] but found {found}", span) 85 | { 86 | Expected = expected; 87 | Found = found; 88 | } 89 | 90 | public UnexpectedTokenException() { } 91 | public UnexpectedTokenException(string message) : base(message) { } 92 | public UnexpectedTokenException(string message, Exception inner) : base(message, inner) { } 93 | protected UnexpectedTokenException( 94 | System.Runtime.Serialization.SerializationInfo info, 95 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 96 | } 97 | 98 | 99 | [Serializable] 100 | public class UnknownGcodeException : GcodeException 101 | { 102 | public UnknownGcodeException() { } 103 | public UnknownGcodeException(string message) : base(message) { } 104 | public UnknownGcodeException(string message, Exception inner) : base(message, inner) { } 105 | protected UnknownGcodeException( 106 | System.Runtime.Serialization.SerializationInfo info, 107 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Gcodes/FileMap.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Gcodes 7 | { 8 | /// 9 | /// A map for finding details about a particular location in text. 10 | /// 11 | public class FileMap 12 | { 13 | private SortedDictionary locations = new SortedDictionary(); 14 | private Dictionary spans = new Dictionary(); 15 | private string src; 16 | 17 | 18 | public FileMap(string src) 19 | { 20 | this.src = src ?? throw new ArgumentNullException(nameof(src)); 21 | } 22 | 23 | /// 24 | /// Get information associated with the provided span. 25 | /// 26 | /// 27 | /// 28 | public SpanInfo SpanInfoFor(Span span) 29 | { 30 | if (!spans.TryGetValue(span, out SpanInfo info)) 31 | { 32 | info = spans[span] = CalculateSpanInfo(span); 33 | } 34 | 35 | return info; 36 | } 37 | 38 | private SpanInfo CalculateSpanInfo(Span span) 39 | { 40 | var start = LocationFor(span.Start); 41 | var end = LocationFor(span.End); 42 | var value = src.Substring(span.Start, span.Length); 43 | 44 | return new SpanInfo(span, start, end, value); 45 | } 46 | 47 | public Location LocationFor(int byteIndex) 48 | { 49 | if (!locations.TryGetValue(byteIndex, out Location location)) 50 | { 51 | location = locations[byteIndex] = CalculateLocation(byteIndex); 52 | } 53 | 54 | return location; 55 | } 56 | 57 | private Location CalculateLocation(int byteIndex) 58 | { 59 | var closestLocation = locations.Values.Where(loc => loc.ByteIndex < byteIndex).LastOrDefault(); 60 | 61 | var line = LineNumber(byteIndex, closestLocation); 62 | var column = ColumnNumber(byteIndex); 63 | 64 | return new Location(byteIndex, line, column); 65 | } 66 | 67 | internal int ColumnNumber(int byteIndex) 68 | { 69 | var lastNewline = src.LastIndexOf('\n', byteIndex); 70 | var col = lastNewline < 0 ? byteIndex + 1 : byteIndex - lastNewline; 71 | 72 | return col; 73 | } 74 | 75 | internal int LineNumber(int byteIndex, Location closest = null) 76 | { 77 | var line = NaiveLineNumber(src, byteIndex, closest?.ByteIndex ?? 0); 78 | 79 | if (closest != null) 80 | { 81 | line += closest.Line - 1; 82 | } 83 | 84 | return line; 85 | } 86 | 87 | private int NaiveLineNumber(string src, int byteIndex, int startIndex = 0) 88 | { 89 | var line = 1; 90 | 91 | for (int index = startIndex; index < byteIndex; index++) 92 | { 93 | var c = src[index]; 94 | if (c == '\n') 95 | { 96 | line += 1; 97 | } 98 | } 99 | 100 | return line; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Gcodes/Gcodes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {196C59DA-B880-465D-A1BB-C114067CA838} 8 | Library 9 | Properties 10 | Gcodes 11 | Gcodes 12 | v4.7.2 13 | 512 14 | 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Gcodes/Interpreter.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using Gcodes.Tokens; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Gcodes 8 | { 9 | /// 10 | /// 11 | /// A gcode interpreter which handles the tokenizing and parsing of a gcode 12 | /// program and will then visit each parsed gcode. 13 | /// 14 | /// 15 | /// Callbacks are fired at each step of the process, allowing subclasses 16 | /// to hook into the interpreting process. 17 | /// 18 | /// 19 | public class Interpreter : IGcodeVisitor 20 | { 21 | private bool running = false; 22 | private FileMap map; 23 | 24 | /// 25 | /// Callback fired whenever a comment is encountered during the 26 | /// tokenizing process. 27 | /// 28 | public event EventHandler CommentDetected; 29 | /// 30 | /// Callback fired immediately before the interpreter starts executing 31 | /// gcodes. 32 | /// 33 | /// 34 | public event EventHandler> BeforeRun; 35 | /// 36 | /// Callback fired before the token stream is parsed. 37 | /// 38 | /// 39 | public event EventHandler> BeforeParse; 40 | 41 | /// 42 | /// 43 | /// Tokenize, parse, then execute a gcode program. 44 | /// 45 | /// 46 | /// This will also populate an internal , giving 47 | /// you access to line and span info. 48 | /// 49 | /// 50 | /// 51 | public void Run(string src) 52 | { 53 | map = new FileMap(src); 54 | var lexer = new Lexer(src); 55 | lexer.CommentDetected += OnCommentDetected; 56 | 57 | var tokens = lexer.Tokenize().ToList(); 58 | Run(tokens); 59 | } 60 | 61 | /// 62 | /// Parse a stream of s and execute them. 63 | /// 64 | /// 65 | public void Run(List tokens) 66 | { 67 | OnBeforeParse(tokens); 68 | var parser = new Parser(tokens); 69 | var codes = parser.Parse().ToList(); 70 | Run(codes); 71 | } 72 | 73 | /// 74 | /// Start executing a stream of pre-parsed gcodes. 75 | /// 76 | /// 77 | public void Run(List codes) 78 | { 79 | OnBeforeRun(codes); 80 | running = true; 81 | 82 | try 83 | { 84 | foreach (var code in codes) 85 | { 86 | if (!running) break; 87 | code.Accept(this); 88 | } 89 | } 90 | finally 91 | { 92 | running = false; 93 | } 94 | } 95 | 96 | /// 97 | /// Tell the interpreter to stop executing gcodes. 98 | /// 99 | public void Halt() 100 | { 101 | running = false; 102 | } 103 | 104 | /// 105 | /// If interpreting from source, get the for a 106 | /// particular . 107 | /// 108 | /// 109 | /// 110 | /// Span information, or null if not interpeting from source. 111 | /// 112 | protected SpanInfo SpanInfoFor(Span span) 113 | { 114 | return map?.SpanInfoFor(span); 115 | } 116 | 117 | /// 118 | /// If interpreting from source, get the for a 119 | /// particular byte index into the source text. 120 | /// 121 | /// 122 | /// 123 | /// The corresponding , or null if not 124 | /// interpreting from source. 125 | /// 126 | protected Location LocationFor(int byteIndex) 127 | { 128 | return map?.LocationFor(byteIndex); 129 | } 130 | 131 | public virtual void Visit(Gcode code) { } 132 | public virtual void Visit(Mcode code) { } 133 | public virtual void Visit(Tcode tcode) { } 134 | public virtual void Visit(Ocode code) { } 135 | 136 | private void OnBeforeParse(List tokens) { 137 | BeforeParse?.Invoke(this, tokens); 138 | } 139 | 140 | private void OnBeforeRun(List codes) 141 | { 142 | BeforeRun?.Invoke(this, codes); 143 | } 144 | 145 | private void OnCommentDetected(object sender, CommentEventArgs e) 146 | { 147 | CommentDetected?.Invoke(this, e); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Gcodes/Lexer.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace Gcodes 8 | { 9 | /// 10 | /// A tokenizer for converting a stream of characters into a stream of 11 | /// s. 12 | /// 13 | public class Lexer 14 | { 15 | List patterns; 16 | List skips; 17 | string src; 18 | int pointer; 19 | int lineNumber; 20 | 21 | internal bool Finished => pointer >= src.Length; 22 | 23 | /// 24 | /// Event fired whenever a comment is encountered. 25 | /// 26 | public event EventHandler CommentDetected; 27 | 28 | /// 29 | /// Create a new which will tokenize the provided 30 | /// source text. 31 | /// 32 | /// 33 | public Lexer(string src) 34 | { 35 | skips = new List 36 | { 37 | new Regex(@"\G\s+", RegexOptions.Compiled), 38 | new Regex(@"\G;([^\n\r]*)", RegexOptions.Compiled), 39 | new Regex(@"\G\(([^)\n\r]*)\)", RegexOptions.Compiled), 40 | }; 41 | this.src = src; 42 | pointer = 0; 43 | lineNumber = 0; 44 | 45 | patterns = new List 46 | { 47 | new Pattern(@"G", TokenKind.G), 48 | new Pattern(@"O", TokenKind.O), 49 | new Pattern(@"N", TokenKind.N), 50 | new Pattern(@"M", TokenKind.M), 51 | new Pattern(@"T", TokenKind.T), 52 | new Pattern(@"X", TokenKind.X), 53 | new Pattern(@"Y", TokenKind.Y), 54 | new Pattern(@"Z", TokenKind.Z), 55 | new Pattern(@"F", TokenKind.F), 56 | new Pattern(@"I", TokenKind.I), 57 | new Pattern(@"J", TokenKind.J), 58 | new Pattern(@"K", TokenKind.K), 59 | new Pattern(@"A", TokenKind.A), 60 | new Pattern(@"B", TokenKind.B), 61 | new Pattern(@"C", TokenKind.C), 62 | new Pattern(@"H", TokenKind.H), 63 | new Pattern(@"P", TokenKind.P), 64 | new Pattern(@"S", TokenKind.S), 65 | 66 | new Pattern(@"[-+]?(\d+\.\d+|\.\d+|\d+\.?)", TokenKind.Number), 67 | }; 68 | } 69 | 70 | /// 71 | /// Start tokenizing the input. 72 | /// 73 | /// 74 | public IEnumerable Tokenize() 75 | { 76 | while (!Finished) 77 | { 78 | SkipStuff(); 79 | if (Finished) break; 80 | yield return NextToken(); 81 | } 82 | } 83 | 84 | private void SkipStuff() 85 | { 86 | int currentPass; 87 | 88 | do 89 | { 90 | currentPass = pointer; 91 | 92 | foreach (var skip in skips) 93 | { 94 | var match = skip.Match(src, pointer); 95 | 96 | if (match.Success) 97 | { 98 | OnCommentDetected(match); 99 | pointer += match.Length; 100 | lineNumber += match.Value.Count(c => c == '\n'); 101 | } 102 | } 103 | } while (pointer < src.Length && pointer != currentPass); 104 | } 105 | 106 | private void OnCommentDetected(Match match) 107 | { 108 | for (int i = 1; i < match.Groups.Count; i++) 109 | { 110 | var group = match.Groups[i]; 111 | if (group.Success) 112 | { 113 | var span = new Span(pointer, pointer + match.Length); 114 | CommentDetected?.Invoke(this, new CommentEventArgs(group.Value, span)); 115 | break; 116 | } 117 | } 118 | } 119 | 120 | private Token NextToken() 121 | { 122 | foreach (var pat in patterns) 123 | { 124 | if (pat.TryMatch(src, pointer, out Token tok)) 125 | { 126 | pointer = tok.Span.End; 127 | if (tok.Value != null) 128 | { 129 | lineNumber += tok.Value.Count(c => c == '\n'); 130 | } 131 | return tok; 132 | } 133 | } 134 | 135 | var column = CurrentColumn(); 136 | throw new UnrecognisedCharacterException(lineNumber + 1, column + 1, src[pointer]); 137 | } 138 | 139 | private int CurrentColumn() 140 | { 141 | var lastNewline = src.LastIndexOf('\n', pointer); 142 | return lastNewline < 0 ? pointer : pointer - lastNewline; 143 | } 144 | } 145 | 146 | /// 147 | /// The event arguments passed in when the 148 | /// event is fired. 149 | /// 150 | public class CommentEventArgs : EventArgs 151 | { 152 | internal CommentEventArgs(string comment, Span span) 153 | { 154 | Comment = comment ?? throw new ArgumentNullException(nameof(comment)); 155 | Span = span; 156 | } 157 | 158 | /// 159 | /// The comment's contents. 160 | /// 161 | public string Comment { get; } 162 | /// 163 | /// The location of the comment in the source text. 164 | /// 165 | public Span Span { get; } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Gcodes/Location.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Gcodes 5 | { 6 | public class Location : IEquatable 7 | { 8 | 9 | public Location(int byteIndex, int line, int column) 10 | { 11 | ByteIndex = byteIndex; 12 | Line = line; 13 | Column = column; 14 | } 15 | 16 | public int ByteIndex { get; } 17 | public int Line { get; } 18 | public int Column { get; } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | return Equals(obj as Location); 23 | } 24 | 25 | public bool Equals(Location other) 26 | { 27 | return other != null && 28 | ByteIndex == other.ByteIndex && 29 | Line == other.Line && 30 | Column == other.Column; 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | var hashCode = 1862217691; 36 | hashCode = hashCode * -1521134295 + ByteIndex.GetHashCode(); 37 | hashCode = hashCode * -1521134295 + Line.GetHashCode(); 38 | hashCode = hashCode * -1521134295 + Column.GetHashCode(); 39 | return hashCode; 40 | } 41 | 42 | public static bool operator ==(Location location1, Location location2) 43 | { 44 | return EqualityComparer.Default.Equals(location1, location2); 45 | } 46 | 47 | public static bool operator !=(Location location1, Location location2) 48 | { 49 | return !(location1 == location2); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Gcodes/Parser.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using Gcodes.Tokens; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Gcodes 8 | { 9 | /// 10 | /// A parser for converting a stream of tokens into their corresponding 11 | /// Gcodes and Mcodes. 12 | /// 13 | public class Parser 14 | { 15 | private readonly List tokens; 16 | private int index; 17 | 18 | /// 19 | /// Create a new Parser using the provided list of tokens. 20 | /// 21 | /// 22 | public Parser(List tokens) 23 | { 24 | this.tokens = tokens ?? throw new ArgumentNullException(nameof(tokens)); 25 | index = 0; 26 | } 27 | /// 28 | /// Create a new , automatically invoking the 29 | /// to tokenize the input. 30 | /// 31 | /// 32 | public Parser(string src) : this(new Lexer(src).Tokenize().ToList()) { } 33 | 34 | /// 35 | /// Is this parser finished? 36 | /// 37 | /// 38 | public bool GetFinished() 39 | { 40 | // being finished can mean one of two things. 41 | // either we've run out of input 42 | if (index >= tokens.Count) 43 | return true; 44 | 45 | // or *everything* to the end of the file will be ignored (e.g. line numbers) 46 | // a line number is just a "N" followed by a number, so we just need to make 47 | // sure all odd tokens are "N" and the even ones are numbers 48 | var tokensLeft = tokens.Count - index; 49 | if (tokensLeft % 2 != 0) return false; 50 | 51 | for (int i = index; i < tokens.Count; i += 2) 52 | { 53 | if (tokens[i].Kind != TokenKind.N) 54 | return false; 55 | } 56 | for (int j = index + 1; j < tokens.Count; j += 2) 57 | { 58 | if (tokens[j].Kind != TokenKind.Number) 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | public IEnumerable Parse() 65 | { 66 | while (!GetFinished()) 67 | { 68 | yield return NextItem(); 69 | } 70 | } 71 | 72 | internal Mcode ParseMCode() 73 | { 74 | var start = index; 75 | var line = ParseLineNumber(); 76 | 77 | var m = Chomp(TokenKind.M); 78 | if (m == null) 79 | { 80 | index = start; 81 | return null; 82 | } 83 | 84 | var numberTok = ParseInteger() ?? throw ParseError(TokenKind.Number); ; 85 | 86 | var number = int.Parse(numberTok.Value); 87 | var span = numberTok.Span.Merge(m.Span); 88 | span = line == null ? span : span.Merge(line.Span); 89 | 90 | return new Mcode(number, span, line?.Number); 91 | } 92 | 93 | internal Tcode ParseTCode() 94 | { 95 | var start = index; 96 | var line = ParseLineNumber(); 97 | 98 | var t = Chomp(TokenKind.T); 99 | if (t == null) 100 | { 101 | index = start; 102 | return null; 103 | } 104 | 105 | var numberTok = ParseInteger() ?? throw ParseError(TokenKind.Number); ; 106 | 107 | var number = int.Parse(numberTok.Value); 108 | var span = numberTok.Span.Merge(t.Span); 109 | span = line == null ? span : span.Merge(line.Span); 110 | 111 | return new Tcode(number, span, line?.Number); 112 | } 113 | 114 | /// 115 | /// Parse a single gcode. 116 | /// 117 | /// 118 | /// This roughly matches the following grammar: 119 | /// 120 | /// gcode := [line_number] "G" INTEGER argument* 121 | /// line_number := "N" INTEGER 122 | /// argument := "X" FLOAT 123 | /// | " 124 | /// 125 | /// 126 | /// 127 | internal Gcode ParseGCode() 128 | { 129 | var start = index; 130 | 131 | var line = ParseLineNumber(); 132 | 133 | var g = Chomp(TokenKind.G); 134 | if (g == null) 135 | { 136 | index = start; 137 | return null; 138 | } 139 | 140 | var numberTok = ParseInteger() ?? throw ParseError(TokenKind.Number); ; 141 | var number = int.Parse(numberTok.Value); 142 | var args = ParseArguments(); 143 | 144 | var span = args.Aggregate(g.Span.Merge(numberTok.Span), (acc, elem) => acc.Merge(elem.Span)); 145 | 146 | if (line != null) 147 | { 148 | span = span.Merge(line.Span); 149 | } 150 | 151 | return new Gcode(number, args, span, line?.Number); 152 | } 153 | 154 | internal Argument ParseArgument() 155 | { 156 | var kindTok = Chomp(TokenKind.F, TokenKind.P, TokenKind.S, TokenKind.H, 157 | TokenKind.X, TokenKind.Y, TokenKind.Z, 158 | TokenKind.I, TokenKind.J, TokenKind.K, 159 | TokenKind.A, TokenKind.B, TokenKind.C); 160 | if (kindTok == null) return null; 161 | 162 | var valueTok = Chomp(TokenKind.Number) ?? throw ParseError(TokenKind.Number); 163 | 164 | var span = kindTok.Span.Merge(valueTok.Span); 165 | var value = double.Parse(valueTok.Value, CultureInfo.InvariantCulture); 166 | var kind = kindTok.Kind.AsArgumentKind(); 167 | 168 | return new Argument(kind, value, span); 169 | } 170 | 171 | private Code NextItem() 172 | { 173 | Code got = ParseGCode() ?? ParseMCode() ?? ParseTCode() ?? (Code)ParseOCode(); 174 | 175 | if (got == null) 176 | throw ParseError(TokenKind.G, TokenKind.M, TokenKind.T, TokenKind.O); 177 | 178 | return got; 179 | } 180 | 181 | private List ParseArguments() 182 | { 183 | var args = new List(); 184 | 185 | while (!GetFinished()) 186 | { 187 | var arg = ParseArgument(); 188 | if (arg != null) 189 | { 190 | args.Add(arg); 191 | } 192 | else 193 | { 194 | break; 195 | } 196 | } 197 | 198 | return args; 199 | } 200 | 201 | private Token ParseInteger() 202 | { 203 | var numberTok = Chomp(TokenKind.Number); 204 | 205 | if (numberTok == null) 206 | { 207 | return null; 208 | } 209 | 210 | if (numberTok.Value.Contains('.') || numberTok.Value.Contains('-')) 211 | { 212 | throw new ParseException("The number for a \"G\" code should be a positive integer", numberTok.Span); 213 | } 214 | 215 | return numberTok; 216 | } 217 | 218 | internal Ocode ParseOCode() 219 | { 220 | var start = index; 221 | var line = ParseLineNumber(); 222 | 223 | var O = Chomp(TokenKind.O); 224 | if (O == null) 225 | { 226 | index = start; 227 | return null; 228 | } 229 | 230 | var numberTok = ParseInteger() ?? throw ParseError(TokenKind.Number); ; 231 | 232 | var span = O.Span.Merge(numberTok.Span); 233 | if (line != null) 234 | { 235 | span = span.Merge(line.Span); 236 | } 237 | 238 | return new Ocode(int.Parse(numberTok.Value), span, line?.Number); 239 | } 240 | 241 | /// 242 | /// Parses a line number ("N50"). If there are multiple 243 | /// consecutive line numbers, this skips to the last line number and 244 | /// matches that. 245 | /// 246 | /// 247 | internal LineNumber ParseLineNumber() 248 | { 249 | Token n = null; 250 | Token numberTok = null; 251 | 252 | do 253 | { 254 | n = Chomp(TokenKind.N); 255 | if (n == null) break; 256 | 257 | numberTok = ParseInteger() ?? throw ParseError(TokenKind.Number); 258 | } while (Peek()?.Kind == TokenKind.N); 259 | 260 | if (n == null || numberTok == null) 261 | { 262 | return null; 263 | } 264 | 265 | return new LineNumber(int.Parse(numberTok.Value), n.Span.Merge(numberTok.Span)); 266 | } 267 | 268 | private Exception ParseError(params TokenKind[] expected) 269 | { 270 | var next = Peek(); 271 | 272 | if (next != null) 273 | { 274 | return new UnexpectedTokenException(expected, next.Kind, next.Span); 275 | } 276 | else 277 | { 278 | return new UnexpectedEOFException(expected); 279 | } 280 | } 281 | 282 | private Token Chomp(params TokenKind[] kind) 283 | { 284 | var tok = Peek(); 285 | if (tok != null && kind.Contains(tok.Kind)) 286 | { 287 | index += 1; 288 | return tok; 289 | } 290 | else 291 | { 292 | return null; 293 | } 294 | } 295 | 296 | private Token Peek() 297 | { 298 | if (index < tokens.Count) 299 | { 300 | return tokens[index]; 301 | } 302 | else 303 | { 304 | return null; 305 | } 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /Gcodes/Pattern.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Gcodes 6 | { 7 | internal class Pattern 8 | { 9 | Regex regex; 10 | TokenKind kind; 11 | 12 | public Pattern(string pattern, TokenKind kind) 13 | { 14 | if (!pattern.StartsWith(@"\G")) 15 | pattern = @"\G" + pattern; 16 | 17 | regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); 18 | this.kind = kind; 19 | } 20 | 21 | public bool TryMatch(string src, int startIndex, out Token tok) 22 | { 23 | var match = regex.Match(src, startIndex); 24 | 25 | if (match.Success) 26 | { 27 | var span = new Span(startIndex, startIndex + match.Length); 28 | 29 | if (kind.HasValue()) 30 | { 31 | tok = new Token(span, kind, match.Value); 32 | } 33 | else 34 | { 35 | tok = new Token(span, kind); 36 | } 37 | return true; 38 | } 39 | else 40 | { 41 | tok = null; 42 | return false; 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Gcodes/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Gcode")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("Gcode")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: InternalsVisibleTo("Gcodes.Test")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("196c59da-b880-465d-a1bb-c114067ca838")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.0.0.0")] 37 | [assembly: AssemblyFileVersion("1.0.0.0")] 38 | -------------------------------------------------------------------------------- /Gcodes/Runtime/Emulator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Gcodes.Ast; 4 | 5 | namespace Gcodes.Runtime 6 | { 7 | public class Emulator : Interpreter 8 | { 9 | private MachineState state; 10 | private double time; 11 | public OperationFactory Operations { get; set; } = new OperationFactory(); 12 | public MachineState InitialState { get; set; } 13 | 14 | public event EventHandler StateChanged; 15 | public event EventHandler OperationExecuted; 16 | 17 | public Emulator() 18 | { 19 | BeforeRun += Reset; 20 | } 21 | 22 | private void Reset(object sender, List args) 23 | { 24 | UpdateState(0.0, InitialState); 25 | } 26 | 27 | public MachineState State 28 | { 29 | get => state; 30 | set 31 | { 32 | state = value; 33 | OnStateChange(); 34 | } 35 | } 36 | 37 | public double Time 38 | { 39 | get => time; 40 | set 41 | { 42 | time = value; 43 | OnStateChange(); 44 | } 45 | } 46 | 47 | public double MinimumTimeStep { get; set; } 48 | 49 | public override void Visit(Gcode code) 50 | { 51 | var operation = Operations.GcodeOp(code, State); 52 | 53 | ExecuteOperation(operation); 54 | OnOperationExecuted(operation, code); 55 | } 56 | 57 | private void OnOperationExecuted(IOperation operation, Code code) 58 | { 59 | OperationExecuted?.Invoke(this, new OperationExecutedEventArgs(operation, code)); 60 | } 61 | 62 | private void ExecuteOperation(IOperation operation) 63 | { 64 | var timeStep = Math.Max(operation.Duration.TotalSeconds / 20.0, MinimumTimeStep); 65 | var start = Time; 66 | var end = start + operation.Duration.TotalSeconds; 67 | 68 | var numSteps = operation.Duration.TotalSeconds / timeStep; 69 | 70 | for (int step = 0; step < Math.Floor(numSteps); step++) 71 | { 72 | var deltaTime = step * timeStep; 73 | var newState = operation.NextState(TimeSpan.FromSeconds(deltaTime)); 74 | UpdateState(start + deltaTime, newState); 75 | } 76 | 77 | if (Time != end) 78 | { 79 | UpdateState(end, operation.NextState(operation.Duration)); 80 | } 81 | } 82 | 83 | private void UpdateState(double newTime, MachineState newState) 84 | { 85 | state = newState; 86 | time = newTime; 87 | OnStateChange(); 88 | } 89 | 90 | private void OnStateChange() 91 | { 92 | StateChanged?.Invoke(this, new StateChangeEventArgs(State, Time)); 93 | } 94 | 95 | 96 | public class StateChangeEventArgs : EventArgs 97 | { 98 | public StateChangeEventArgs(MachineState newState, double time) 99 | { 100 | NewState = newState; 101 | Time = time; 102 | } 103 | 104 | public MachineState NewState { get; } 105 | public double Time { get; } 106 | } 107 | 108 | public class OperationExecutedEventArgs : EventArgs 109 | { 110 | public OperationExecutedEventArgs(IOperation operation, Code code) 111 | { 112 | Operation = operation; 113 | Code = code; 114 | } 115 | 116 | public Code Code { get; } 117 | public IOperation Operation { get; } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Gcodes/Runtime/IOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Gcodes.Runtime 4 | { 5 | /// 6 | /// An operation which will give you the machine's state at any arbitrary 7 | /// time between when it starts and ends. 8 | /// 9 | public interface IOperation 10 | { 11 | /// 12 | /// Get the machine's state at some arbitrary time. 13 | /// 14 | /// 15 | /// The amount of time since this operation started. 16 | /// 17 | /// 18 | /// 19 | /// Thrown if you try to get the state after the operation ended (i.e. 20 | /// is greater than ). 21 | /// 22 | MachineState NextState(TimeSpan deltaTime); 23 | 24 | /// 25 | /// How long the operation should take to complete. 26 | /// 27 | TimeSpan Duration { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /Gcodes/Runtime/MachineState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Gcodes.Runtime 8 | { 9 | public struct MachineState 10 | { 11 | public double X; 12 | public double Y; 13 | public double Z; 14 | public double FeedRate; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Gcodes/Runtime/Noop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Gcodes.Runtime 8 | { 9 | public class Noop : IOperation, IEquatable 10 | { 11 | private MachineState state; 12 | 13 | public Noop(MachineState state) : this(state, TimeSpan.FromSeconds(0)) { } 14 | public Noop(MachineState state, double duration) : this(state, TimeSpan.FromSeconds(duration)) { } 15 | public Noop(MachineState state, TimeSpan duration) 16 | { 17 | this.state = state; 18 | Duration = duration; 19 | } 20 | 21 | public TimeSpan Duration { get; set; } 22 | 23 | public override bool Equals(object obj) 24 | { 25 | return Equals(obj as Noop); 26 | } 27 | 28 | public bool Equals(Noop other) 29 | { 30 | return other != null && 31 | EqualityComparer.Default.Equals(state, other.state) && 32 | Duration.Equals(other.Duration); 33 | } 34 | 35 | public override int GetHashCode() 36 | { 37 | var hashCode = -465428333; 38 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(state); 39 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Duration); 40 | return hashCode; 41 | } 42 | 43 | public MachineState NextState(TimeSpan deltaTime) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | 48 | public static bool operator ==(Noop noop1, Noop noop2) 49 | { 50 | return EqualityComparer.Default.Equals(noop1, noop2); 51 | } 52 | 53 | public static bool operator !=(Noop noop1, Noop noop2) 54 | { 55 | return !(noop1 == noop2); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Gcodes/Runtime/OperationFactory.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Gcodes.Runtime 6 | { 7 | /// 8 | /// A factory class for instantiating and configuring an 9 | /// which corresponds to the provided . 10 | /// 11 | public class OperationFactory 12 | { 13 | private HashSet ignoredGcodes = new HashSet(); 14 | 15 | /// 16 | /// Get the corresponding to a particular 17 | /// . 18 | /// 19 | /// 20 | /// 21 | /// 22 | public virtual IOperation GcodeOp(Gcode code, MachineState initialState) 23 | { 24 | Exception ex; 25 | 26 | if (ignoredGcodes.Contains(code.Number)) 27 | { 28 | return new Noop(initialState); 29 | } 30 | 31 | switch (code.Number) 32 | { 33 | case 4: 34 | var ms = code.ValueFor(ArgumentKind.P); 35 | if (ms == null) 36 | { 37 | ex = new ArgumentException("Dwell commands require a P argument"); 38 | ex.Data[nameof(code)] = code; 39 | throw ex; 40 | } 41 | return new Noop(initialState, TimeSpan.FromMilliseconds(ms.Value)); 42 | default: 43 | ex = new UnknownGcodeException($"No applicable operation for G{code.Number}"); 44 | ex.Data[nameof(code)] = code; 45 | throw ex; 46 | } 47 | } 48 | 49 | /// 50 | /// Add one or more gcode instructions to the ignore list. 51 | /// 52 | /// 53 | public void IgnoreGcode(params int[] numbers) 54 | { 55 | foreach (var number in numbers) 56 | { 57 | ignoredGcodes.Add(number); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Gcodes/SpanInfo.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Tokens; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Gcodes 6 | { 7 | public class SpanInfo : IEquatable 8 | { 9 | public SpanInfo(Span span, Location start, Location end, string value) 10 | { 11 | Span = span; 12 | Start = start; 13 | End = end; 14 | Value = value; 15 | } 16 | 17 | public Span Span { get; } 18 | public Location Start { get; } 19 | public Location End { get; } 20 | public string Value { get; } 21 | 22 | public override bool Equals(object obj) 23 | { 24 | return Equals(obj as SpanInfo); 25 | } 26 | 27 | public bool Equals(SpanInfo other) 28 | { 29 | return other != null && 30 | EqualityComparer.Default.Equals(Span, other.Span) && 31 | EqualityComparer.Default.Equals(Start, other.Start) && 32 | EqualityComparer.Default.Equals(End, other.End) && 33 | Value == other.Value; 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | var hashCode = 1648364364; 39 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Span); 40 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Start); 41 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(End); 42 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); 43 | return hashCode; 44 | } 45 | 46 | public static bool operator ==(SpanInfo info1, SpanInfo info2) 47 | { 48 | return EqualityComparer.Default.Equals(info1, info2); 49 | } 50 | 51 | public static bool operator !=(SpanInfo info1, SpanInfo info2) 52 | { 53 | return !(info1 == info2); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Gcodes/Tokens/Span.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Gcodes.Tokens 5 | { 6 | /// 7 | /// A location in the source text as a pair of byte indices. 8 | /// 9 | public struct Span 10 | { 11 | /// 12 | /// The empty span. 13 | /// 14 | public static Span Empty = new Span(0, 0); 15 | 16 | /// 17 | /// The index a segment of text starts at. 18 | /// 19 | public int Start { get; } 20 | /// 21 | /// The index one after the end of the selected text. 22 | /// 23 | public int End { get; } 24 | public int Length { get => End - Start; } 25 | 26 | public Span(int start, int end) 27 | { 28 | Start = start; 29 | End = end; 30 | } 31 | 32 | /// 33 | /// Calculate the line number this starts at. 34 | /// 35 | /// 36 | /// 37 | public int LineNumber(string src) 38 | { 39 | var start = Start; 40 | return src.Where((_, i) => i < start).Where(c => c == '\n').Count() + 1; 41 | } 42 | 43 | /// 44 | /// Determine which column this span starts in. 45 | /// 46 | /// 47 | /// 48 | public int ColumnNumber(string src) 49 | { 50 | var lastNewline = src.LastIndexOf('\n', Start); 51 | return lastNewline < 0 ? Start : Start - lastNewline; 52 | } 53 | 54 | public override string ToString() 55 | { 56 | return $"{Start}-{End}"; 57 | } 58 | 59 | /// 60 | /// Retrieve the encompassing two spans. 61 | /// 62 | /// 63 | /// 64 | public Span Merge(Span other) 65 | { 66 | return new Span(Math.Min(Start, other.Start), Math.Max(End, other.End)); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Gcodes/Tokens/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Gcodes.Tokens 6 | { 7 | /// 8 | /// The smallest atomic unit in a gcode file, containing a token kind, 9 | /// the original string, and its location in the source text (as a 10 | /// ). 11 | /// 12 | public class Token : IEquatable 13 | { 14 | /// 15 | /// Create a new which doesn't have a useful 16 | /// string value (e.g. punctuation). 17 | /// 18 | /// 19 | /// 20 | public Token(Span span, TokenKind kind) : this(span, kind, null) { } 21 | /// 22 | /// Create a new out of its constituent parts. 23 | /// 24 | /// 25 | /// 26 | /// 27 | public Token(Span span, TokenKind kind, string value) 28 | { 29 | Span = span; 30 | Kind = kind; 31 | Value = value; 32 | } 33 | 34 | /// 35 | /// Where the lies in its source text. 36 | /// 37 | public Span Span { get; } 38 | /// 39 | /// Which kind of token is this? 40 | /// 41 | public TokenKind Kind { get; } 42 | /// 43 | /// The token's original string text. May be null if the token 44 | /// kind doesn't care about its source text. 45 | /// 46 | public string Value { get; } 47 | 48 | #region Equals 49 | public override bool Equals(object obj) 50 | { 51 | return Equals(obj as Token); 52 | } 53 | 54 | public bool Equals(Token other) 55 | { 56 | return other != null && 57 | EqualityComparer.Default.Equals(Span, other.Span) && 58 | Kind == other.Kind && 59 | Value == other.Value; 60 | } 61 | 62 | public override int GetHashCode() 63 | { 64 | var hashCode = -1030702410; 65 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Span); 66 | hashCode = hashCode * -1521134295 + Kind.GetHashCode(); 67 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); 68 | return hashCode; 69 | } 70 | 71 | public override string ToString() 72 | { 73 | var sb = new StringBuilder(); 74 | sb.Append(Kind.ToString()); 75 | 76 | if (Kind.HasValue()) 77 | { 78 | sb.AppendFormat("({0})", Value); 79 | } 80 | 81 | sb.AppendFormat(" @ {0}", Span); 82 | 83 | return sb.ToString(); 84 | } 85 | 86 | public static bool operator ==(Token token1, Token token2) 87 | { 88 | return EqualityComparer.Default.Equals(token1, token2); 89 | } 90 | 91 | public static bool operator !=(Token token1, Token token2) 92 | { 93 | return !(token1 == token2); 94 | } 95 | #endregion 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Gcodes/Tokens/TokenKind.cs: -------------------------------------------------------------------------------- 1 | using Gcodes.Ast; 2 | using System; 3 | 4 | namespace Gcodes.Tokens 5 | { 6 | /// 7 | /// The various possible kinds of tokens. 8 | /// 9 | public enum TokenKind 10 | { 11 | G, 12 | M, 13 | X, 14 | Y, 15 | Number, 16 | Z, 17 | F, 18 | N, 19 | I, 20 | J, 21 | A, 22 | B, 23 | C, 24 | H, 25 | P, 26 | K, 27 | O, 28 | T, 29 | S, 30 | } 31 | 32 | /// 33 | /// Extension methods for the enum. 34 | /// 35 | public static class TokenKindExt 36 | { 37 | /// 38 | /// Does this have a meaningful string value? 39 | /// 40 | /// 41 | /// 42 | public static bool HasValue(this TokenKind kind) 43 | { 44 | switch (kind) 45 | { 46 | case TokenKind.Number: 47 | return true; 48 | default: 49 | return false; 50 | } 51 | } 52 | 53 | /// 54 | /// Try to convert the into an 55 | /// , if applicable. 56 | /// 57 | /// 58 | /// 59 | /// 60 | /// Raised if there is no corresponding for 61 | /// this . 62 | /// 63 | public static ArgumentKind AsArgumentKind(this TokenKind kind) 64 | { 65 | switch (kind) 66 | { 67 | case TokenKind.X: 68 | return ArgumentKind.X; 69 | case TokenKind.Y: 70 | return ArgumentKind.Y; 71 | case TokenKind.Z: 72 | return ArgumentKind.Z; 73 | 74 | case TokenKind.A: 75 | return ArgumentKind.A; 76 | case TokenKind.B: 77 | return ArgumentKind.B; 78 | case TokenKind.C: 79 | return ArgumentKind.C; 80 | 81 | case TokenKind.I: 82 | return ArgumentKind.I; 83 | case TokenKind.J: 84 | return ArgumentKind.J; 85 | case TokenKind.K: 86 | return ArgumentKind.K; 87 | 88 | case TokenKind.H: 89 | return ArgumentKind.H; 90 | case TokenKind.P: 91 | return ArgumentKind.P; 92 | case TokenKind.S: 93 | return ArgumentKind.S; 94 | case TokenKind.F: 95 | return ArgumentKind.FeedRate; 96 | default: 97 | throw new ArgumentOutOfRangeException(nameof(kind), "No equivalent argument kind"); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Gcodes/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /Gcodes/api/index.md: -------------------------------------------------------------------------------- 1 | # PLACEHOLDER 2 | TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*! 3 | -------------------------------------------------------------------------------- /Gcodes/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Add your introductions here! 2 | -------------------------------------------------------------------------------- /Gcodes/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: intro.md 3 | -------------------------------------------------------------------------------- /Gcodes/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ 7 | "**.csproj" 8 | ] 9 | } 10 | ], 11 | "dest": "api", 12 | "disableGitFeatures": false 13 | } 14 | ], 15 | "build": { 16 | "content": [ 17 | { 18 | "files": [ 19 | "api/**.yml", 20 | "api/index.md" 21 | ] 22 | }, 23 | { 24 | "files": [ 25 | "articles/**.md", 26 | "articles/**/toc.yml", 27 | "toc.yml", 28 | "*.md" 29 | ] 30 | } 31 | ], 32 | "resource": [ 33 | { 34 | "files": [ 35 | "images/**" 36 | ] 37 | } 38 | ], 39 | "overwrite": [ 40 | { 41 | "files": [ 42 | "apidoc/**.md" 43 | ], 44 | "exclude": [ 45 | "obj/**", 46 | "_site/**" 47 | ] 48 | } 49 | ], 50 | "dest": "_site", 51 | "globalMetadataFiles": [], 52 | "fileMetadataFiles": [], 53 | "template": [ 54 | "default" 55 | ], 56 | "postProcessors": [], 57 | "markdownEngineName": "markdig", 58 | "noLangKeyword": false, 59 | "keepFileLink": false, 60 | "cleanupCacheHistory": false, 61 | "disableGitFeatures": false 62 | } 63 | } -------------------------------------------------------------------------------- /Gcodes/index.md: -------------------------------------------------------------------------------- 1 | # This is the **HOMEPAGE**. 2 | Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. 3 | ## Quick Start Notes: 4 | 1. Add images to the *images* folder if the file is referencing an image. 5 | -------------------------------------------------------------------------------- /Gcodes/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Michael-F-Bryan 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gcodes 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/b4t1cp42205pdqta?svg=true)](https://ci.appveyor.com/project/Michael-F-Bryan/gcodes) 4 | 5 | A basic C# gcode parser and interpreter. 6 | 7 | ## Getting Started 8 | 9 | Once you've downloaded a copy of the repository (e.g. as a git submodule), you 10 | should be able to start using the `gcodes` library by asking Visual Studio to 11 | add a reference to it. 12 | 13 | ### Analysing Gcode Programs 14 | 15 | If you just want to inspect a gcode program without doing any simulation you 16 | just need to run the `Parser` and inspect its output. 17 | 18 | Parsing the input text takes place in two steps: 19 | 20 | - Tokenizing, using the `Lexer` class to convert source text into a stream of 21 | `Token`s 22 | - Parsing, using a `Parser` to convert a stream of `Token`s into a bunch of 23 | `Gcode` objects. 24 | 25 | ```C# 26 | public static void ParseGcodeFile(string filename) { 27 | string src = File.ReadAllText(filename); 28 | 29 | var lexer = new Lexer(src); 30 | List tokens = lexer.Tokenize().ToList(); 31 | 32 | var parser = new Parser(tokens); 33 | List gcodes = parser.Parse().ToList(); 34 | 35 | // do something with the gcodes 36 | } 37 | ``` 38 | 39 | > **Note:** For convenience, `Parser` also has a constructor which accepts a 40 | > `string` and will tokenize everything for you. 41 | 42 | There is no guarantee that every argument is provided to a gcode, therefore 43 | if you want to fetch an argument's value (e.g. `X` or feed rate) you can use 44 | the `ValueFor()` method. This will search the arguments provided to a particular 45 | gcode for the specified `ArgumentKind`, and return its value. 46 | 47 | ### Simulating A CNC Machine -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | cache: 4 | - packages -> **\packages.config 5 | 6 | configuration: Debug 7 | 8 | build: 9 | parallel: true 10 | project: 11 | 12 | before_build: 13 | - nuget restore 14 | 15 | environment: 16 | access_token: "UPDATE_ME" 17 | --------------------------------------------------------------------------------