├── .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 | [](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 |
--------------------------------------------------------------------------------