diff --git a/CHANGES.md b/CHANGES.md
index 794f4f4747..5c8f7c2ff8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -39,6 +39,7 @@ Core Grammars:
- enh(markdown) add entity support [David Schach][] [TaraLei][]
- enh(css) add `justify-items` and `justify-self` attributes [Vasily Polovnyov][]
- enh(css) add `accent-color`, `appearance`, `color-scheme`, `rotate`, `scale` and `translate` attributes [Carl Räfting][]
+- enh(gcode) rewrote language for modern gcode support [Barthélémy Bonhomme][]
New Grammars:
@@ -88,6 +89,7 @@ Themes:
[Rúnar Bjarnason]: https://github.com/runarorama
[Carl Räfting]: https://github.com/carlrafting
[BackupMiles]: https://github.com/BackupMiles
+[Barthélémy Bonhomme]: https://github.com/barthy-koeln
diff --git a/src/languages/gcode.js b/src/languages/gcode.js
index 15bf97af7c..822d997514 100644
--- a/src/languages/gcode.js
+++ b/src/languages/gcode.js
@@ -7,58 +7,116 @@
*/
export default function(hljs) {
- const GCODE_IDENT_RE = '[A-Z_][A-Z0-9_.]*';
- const GCODE_CLOSE_RE = '%';
const GCODE_KEYWORDS = {
- $pattern: GCODE_IDENT_RE,
- keyword: 'IF DO WHILE ENDWHILE CALL ENDIF SUB ENDSUB GOTO REPEAT ENDREPEAT '
- + 'EQ LT GT NE GE LE OR XOR'
- };
- const GCODE_START = {
- className: 'meta',
- begin: '([O])([0-9]+)'
+ $pattern: /[A-Z]+|%/,
+ meta: [
+ // conditions
+ 'THEN',
+ 'ELSE',
+ 'ENDIF',
+ 'IF',
+
+ // controls
+ 'GOTO',
+ 'DO',
+ 'WHILE',
+ 'WH',
+ 'END',
+ 'CALL',
+
+ // scoping
+ 'SUB',
+ 'ENDSUB',
+
+ // comparisons
+ 'EQ',
+ 'NE',
+ 'LT',
+ 'GT',
+ 'LE',
+ 'GE',
+ 'AND',
+ 'OR',
+ 'XOR',
+
+ // start/end of program
+ '%'
+ ],
+ built_in: [
+ 'ATAN',
+ 'ABS',
+ 'ACOS',
+ 'ASIN',
+ 'COS',
+ 'EXP',
+ 'FIX',
+ 'FUP',
+ 'ROUND',
+ 'LN',
+ 'SIN',
+ 'SQRT',
+ 'TAN',
+ 'EXISTS'
+ ]
};
- const NUMBER = hljs.inherit(hljs.C_NUMBER_MODE, { begin: '([-+]?((\\.\\d+)|(\\d+)(\\.\\d*)?))|' + hljs.C_NUMBER_RE });
+
+
+ const NUMBER = /[+-]?((\.\d+)|(\d+)(\.\d*)?)/;
+
const GCODE_CODE = [
- hljs.C_LINE_COMMENT_MODE,
- hljs.C_BLOCK_COMMENT_MODE,
+ // comments
hljs.COMMENT(/\(/, /\)/),
- NUMBER,
- hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null }),
- hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }),
+ hljs.COMMENT(/;/, /$/),
+ hljs.APOS_STRING_MODE,
+ hljs.QUOTE_STRING_MODE,
+ hljs.C_NUMBER_MODE,
+
+ // gcodes
{
- className: 'name',
- begin: '([G])([0-9]+\\.?[0-9]?)'
+ scope: 'title.function',
+ relevance: 10,
+ variants: [
+ // G General functions: G0, G5.1, G5.2, …
+ // M Misc functions: M0, M55.6, M199, …
+ { match: /(?, …
+ { match: /(?/ },
+ // Checksum at end of line: *71, *199, …
+ { match: /\*\s*\d+\s*$/ }
+ ]
},
+
{
- className: 'attr',
- begin: '(VC|VS|#)',
- end: '(\\d+)'
+ scope: 'operator', // N Line number: N1, N2, N1020, …
+ match: /^N\s*\d+/
},
+
{
- className: 'attr',
- begin: '(VZOFX|VZOFY|VZOFZ)'
+ scope: 'variable',
+ relevance: 0,
+ match: /-?#\s*\d+/,
},
+
{
- className: 'built_in',
- begin: '(ATAN|ABS|ACOS|ASIN|SIN|COS|EXP|FIX|FUP|ROUND|LN|TAN)(\\[)',
- contains: [ NUMBER ],
- end: '\\]'
+ scope: 'property', // Physical axes
+ match: new RegExp(`(?O003
-N2 G54 G90 G49 G80
-N3 M6 T1
-N4 M3 S1800
-N5 G0 X-.6 Y2.050
-N6 G43 H1 Z.1
-N7 G1 Z-.3 F50.
-N8 G41 D1 Y1.45
-N9 G1 X0 F20.
-N10 G2 J-1.45
+O003
+N2 G54 G90 G49 G80
+N3 M6 T1
+N4 M3 S1800
+N5 G0 X-.6 Y2.050
+N6 G43 H1 Z.1
+N7 G1 Z-.3 F50.
+N8 G41 D1 Y1.45
+N9 G1 X0 F20.
+N10 G2 J-1.45
-N11 G1 Z-.2 F50.
-N12 Y-.990
-N13 G40
-N14 G0 X-.6 Y1.590
-N15 G0 Z.1
-N16 M5 G49 G28 G91 Z0
-N17 CALL O9456
-N18 #500=0.004
-N19 #503=[#500+#501]
-N20 VC45=0.0006
-VS4=0.0007
-N21 G90 G10 L20 P3 X5.Y4. Z6.567
-N22 G0 X5000
-N23 IF [#1 LT 0.370] GOTO 49
-N24 X-0.678 Y+.990
-N25 G84.3 X-0.1
-N26 #4=#5*COS[45]
-N27 #4=#5*SIN[45]
-N28 VZOFZ=652.9658
+N11 G1 Z-.2 F50.
+N12 Y-.990
+N13 G40
+N14 G0 X-.6 Y1.590
+N15 G0 Z.1
+N16 M5 G49 G28 G91 Z0
+N17 CALL O9456
+N18 #500=0.004
+N19 #503=[#500+#501]
+N20 VC45=0.0006
+VS4=0.0007
+N21 G90 G10 L20 P3 X5.Y4. Z6.567
+N22 G0 X5000
+N23 IF [#1 LT 0.370] GOTO 49
+N24 X-0.678 Y+.990
+N25 G84.3 X-0.1
+N26 #4=#5*COS[45]
+N27 #4=#5*SIN[45]
+N28 VZOFZ=652.9658
%
diff --git a/test/markup/gcode/extended.expect.txt b/test/markup/gcode/extended.expect.txt
new file mode 100644
index 0000000000..1881720832
--- /dev/null
+++ b/test/markup/gcode/extended.expect.txt
@@ -0,0 +1,84 @@
+%
+
+
+
+
+
+
+M500
+M500
+
+O<boring> SUB
+
+#20 = [#1 * SIN[#1]]
+#21 = [-#2 * COS[#2]]
+#22 = [#3 / COS[#3]]
+#23 = [#4 + #4 - #4]
+#24 = [[#5 - #5] * TAN[#5]]
+#25=FIX[#101/2.75]
+#26=ROUND[#104]
+#27=ABS[#104]
+
+
+
+N1 O107 IF [#10 GT 5]
+N2 G0 A0 B0 C0 U0 V0 W0 X0 Y0 Z0 *71
+N3 G100 A100 B0
+N4 G5.2 A10.2 B0
+N5 G100 A.2 B0
+N6 G100 A-.2 B0
+N7 G100 A+.2 B0
+N8 T0
+N9 M100 F1 H2 I3 J4 K5 P6 Q7 R8 S9
+N10 M587 S"MYROUTER" P"ABCxyz;" "123"
+N10 O107 ENDIF
+
+
+
+
+O 108 IF [#10 GT 5]
+N 102 G 0 A 0 B 0 C 0 U 0 V 0 W 0 X 0 Y 0 Z 0 *71
+N 103 G 100 A 100 B 0
+N 104 G 5.2 A 10.2 B 0
+N 105 G 100 A .2 B 0
+N 106 G 100 A -.2 B 0
+N 107 G 100 A +.2 B 0
+N 108 T 0
+N 109 M 100 F 1 H 2 I 3 J 4 K 5 P 6 Q 7 R 8 S 9
+N 110 M 587 S "MYROUTER" P "ABCxyz;" "123"
+O 108 ENDIF
+
+
+
+
+O109IF[#10GT5]
+N202G0A0B0C0U0V0W0X0Y0Z0*71
+N203G100A100B0
+N204G5.2A10.2B0
+N205G100A.2B0
+N206G100A-.2B0
+N207G100A+.2B0
+N208T0
+N209M100F1H2I3J4K5P6Q7R8S9
+N210M587S"MYROUTER"P"ABCxyz;""123"
+0O109ENDIF
+
+O<boring> ENDSUB
+
+O<boring> CALL [1] [2] [3] [4] [5]
+
+
+
+GET_RETRACTION
+TURN_OFF_HEATERS
+MANUAL_STEPPER STEPPER=config_name ENABLE=0 SET_POSITION=11.1 SPEED=22.2 ACCEL=33.3 MOVE=44.4 STOP_ON_ENDSTOP=-1 SYNC=0
+
+SET_SKEW XY=100.0000,100.0000,70.7107
+SKEW_PROFILE SAVE="this name has spaces"
+SAVE_CONFIG
+
+%
diff --git a/test/markup/gcode/extended.txt b/test/markup/gcode/extended.txt
new file mode 100644
index 0000000000..5e19021d7f
--- /dev/null
+++ b/test/markup/gcode/extended.txt
@@ -0,0 +1,84 @@
+%; something important
+
+; another comment
+
+(yet another more different comment)
+(
+ multi
+ line
+ madness
+)
+
+M500; comment after code
+M500 (comment after code)
+
+O SUB
+
+#20 = [#1 * SIN[#1]]
+#21 = [-#2 * COS[#2]]
+#22 = [#3 / COS[#3]]
+#23 = [#4 + #4 - #4]
+#24 = [[#5 - #5] * TAN[#5]]
+#25=FIX[#101/2.75]
+#26=ROUND[#104]
+#27=ABS[#104]
+
+; WITH SANE SPACING
+
+N1 O107 IF [#10 GT 5]
+N2 G0 A0 B0 C0 U0 V0 W0 X0 Y0 Z0 *71
+N3 G100 A100 B0
+N4 G5.2 A10.2 B0
+N5 G100 A.2 B0
+N6 G100 A-.2 B0
+N7 G100 A+.2 B0
+N8 T0
+N9 M100 F1 H2 I3 J4 K5 P6 Q7 R8 S9
+N10 M587 S"MYROUTER" P"ABCxyz;" "123"
+N10 O107 ENDIF
+
+
+; WITH EXCESSIVE SPACING
+
+O 108 IF [#10 GT 5]
+N 102 G 0 A 0 B 0 C 0 U 0 V 0 W 0 X 0 Y 0 Z 0 *71
+N 103 G 100 A 100 B 0
+N 104 G 5.2 A 10.2 B 0
+N 105 G 100 A .2 B 0
+N 106 G 100 A -.2 B 0
+N 107 G 100 A +.2 B 0
+N 108 T 0
+N 109 M 100 F 1 H 2 I 3 J 4 K 5 P 6 Q 7 R 8 S 9
+N 110 M 587 S "MYROUTER" P "ABCxyz;" "123"
+O 108 ENDIF
+
+
+; WITHOUT SPACING
+
+O109IF[#10GT5]
+N202G0A0B0C0U0V0W0X0Y0Z0*71
+N203G100A100B0
+N204G5.2A10.2B0
+N205G100A.2B0
+N206G100A-.2B0
+N207G100A+.2B0
+N208T0
+N209M100F1H2I3J4K5P6Q7R8S9
+N210M587S"MYROUTER"P"ABCxyz;""123"
+0O109ENDIF
+
+O ENDSUB
+
+O CALL [1] [2] [3] [4] [5]
+
+; Words as see in Klipper Firmware GCode
+
+GET_RETRACTION
+TURN_OFF_HEATERS
+MANUAL_STEPPER STEPPER=config_name ENABLE=0 SET_POSITION=11.1 SPEED=22.2 ACCEL=33.3 MOVE=44.4 STOP_ON_ENDSTOP=-1 SYNC=0
+
+SET_SKEW XY=100.0000,100.0000,70.7107
+SKEW_PROFILE SAVE="this name has spaces"
+SAVE_CONFIG
+
+%