Skip to content

Commit d455e9b

Browse files
authoredMay 17, 2023
feat: add odin mode (#5169)
* feat: add odin mode * feat(odin): also indent after : for case statements * fix(odin): don't higlight uppercase as constants because of false-positives * test(odin): add indent/outdent tests * fix(odin): wrong highlight rule was removed
1 parent aa6a94a commit d455e9b

File tree

5 files changed

+333
-0
lines changed

5 files changed

+333
-0
lines changed
 

‎demo/kitchen-sink/docs/odin.odin

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import "core:fmt"
4+
5+
main :: proc() {
6+
program := "+ + * 😃 - /"
7+
accumulator := 0
8+
9+
for token in program {
10+
switch token {
11+
case '+': accumulator += 1
12+
case '-': accumulator -= 1
13+
case '*': accumulator *= 2
14+
case '/': accumulator /= 2
15+
case '😃': accumulator *= accumulator
16+
case: // Ignore everything else
17+
}
18+
}
19+
20+
fmt.printf("The program \"%s\" calculates the value %d\n", program, accumulator)
21+
}

‎src/ext/modelist.js

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ var supportedModes = {
154154
Nunjucks: ["nunjucks|nunjs|nj|njk"],
155155
ObjectiveC: ["m|mm"],
156156
OCaml: ["ml|mli"],
157+
Odin: ["odin"],
157158
PartiQL: ["partiql|pql"],
158159
Pascal: ["pas|p"],
159160
Perl: ["pl|pm"],

‎src/mode/odin.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var oop = require("../lib/oop");
2+
var TextMode = require("./text").Mode;
3+
var OdinHighlightRules =
4+
require("./odin_highlight_rules").OdinHighlightRules;
5+
var MatchingBraceOutdent =
6+
require("./matching_brace_outdent").MatchingBraceOutdent;
7+
var CStyleFoldMode = require("./folding/cstyle").FoldMode;
8+
9+
var Mode = function () {
10+
this.HighlightRules = OdinHighlightRules;
11+
this.$outdent = new MatchingBraceOutdent();
12+
this.foldingRules = new CStyleFoldMode();
13+
this.$behaviour = this.$defaultBehaviour;
14+
};
15+
oop.inherits(Mode, TextMode);
16+
17+
(function () {
18+
this.lineCommentStart = "//";
19+
this.blockComment = { start: "/*", end: "*/" };
20+
21+
this.getNextLineIndent = function (state, line, tab) {
22+
var indent = this.$getIndent(line);
23+
24+
var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
25+
var tokens = tokenizedLine.tokens;
26+
27+
if (tokens.length && tokens[tokens.length - 1].type == "comment") {
28+
return indent;
29+
}
30+
31+
if (state == "start") {
32+
var match = line.match(/^.*[\{\(\[:]\s*$/);
33+
if (match) {
34+
indent += tab;
35+
}
36+
}
37+
38+
return indent;
39+
}; //end getNextLineIndent
40+
41+
this.checkOutdent = function (state, line, input) {
42+
return this.$outdent.checkOutdent(line, input);
43+
};
44+
45+
this.autoOutdent = function (state, doc, row) {
46+
this.$outdent.autoOutdent(doc, row);
47+
};
48+
49+
this.$id = "ace/mode/odin";
50+
}).call(Mode.prototype);
51+
52+
exports.Mode = Mode;

‎src/mode/odin_highlight_rules.js

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
var oop = require("../lib/oop");
2+
var DocCommentHighlightRules =
3+
require("./doc_comment_highlight_rules").DocCommentHighlightRules;
4+
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
5+
6+
var OdinHighlightRules = function () {
7+
var keywords =
8+
"using|transmute|cast|distinct|opaque|where|" +
9+
"struct|enum|union|bit_field|bit_set|" +
10+
"if|when|else|do|switch|case|break|fallthrough|" +
11+
"size_of|offset_of|type_info_if|typeid_of|type_of|align_of|" +
12+
"or_return|or_else|inline|no_inline|" +
13+
"import|package|foreign|defer|auto_cast|map|matrix|proc|" +
14+
"for|continue|not_in|in";
15+
16+
const cartesian = (...a) =>
17+
a
18+
.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat())))
19+
.map((parts) => parts.join(""));
20+
21+
var builtinTypes = [
22+
"int",
23+
"uint",
24+
"uintptr",
25+
"typeid",
26+
"rawptr",
27+
"string",
28+
"cstring",
29+
"i8",
30+
"u8",
31+
"any",
32+
"byte",
33+
"rune",
34+
"bool",
35+
"b8",
36+
"b16",
37+
"b32",
38+
"b64",
39+
...cartesian(["i", "u"], ["16", "32", "64", "128"], ["", "le", "be"]),
40+
...cartesian(["f"], ["16", "32", "64"], ["", "le", "be"]),
41+
...cartesian(["complex"], ["32", "64", "128"]),
42+
...cartesian(["quaternion"], ["64", "128", "256"])
43+
].join("|");
44+
45+
var operators = [
46+
"\\*",
47+
"/",
48+
"%",
49+
"%%",
50+
"<<",
51+
">>",
52+
"&",
53+
"&~",
54+
"\\+",
55+
"\\-",
56+
"~",
57+
"\\|",
58+
">",
59+
"<",
60+
"<=",
61+
">=",
62+
"==",
63+
"!="
64+
]
65+
.concat(":")
66+
.map((operator) => operator + "=")
67+
.concat("=", ":=", "::", "->", "\\^", "&", ":")
68+
.join("|");
69+
70+
var builtinFunctions = "new|cap|copy|panic|len|make|delete|append|free";
71+
var builtinConstants = "nil|true|false";
72+
73+
var keywordMapper = this.createKeywordMapper(
74+
{
75+
keyword: keywords,
76+
"constant.language": builtinConstants,
77+
"support.function": builtinFunctions,
78+
"support.type": builtinTypes
79+
},
80+
""
81+
);
82+
83+
var stringEscapeRe =
84+
"\\\\(?:[0-7]{3}|x\\h{2}|u{4}|U\\h{6}|[abfnrtv'\"\\\\])".replace(
85+
/\\h/g,
86+
"[a-fA-F\\d]"
87+
);
88+
89+
this.$rules = {
90+
start: [
91+
{
92+
token: "comment",
93+
regex: /\/\/.*$/
94+
},
95+
DocCommentHighlightRules.getStartRule("doc-start"),
96+
{
97+
token: "comment.start", // multi line comment
98+
regex: "\\/\\*",
99+
next: "comment"
100+
},
101+
{
102+
token: "string", // single line
103+
regex: /"(?:[^"\\]|\\.)*?"/
104+
},
105+
{
106+
token: "string", // raw
107+
regex: "`",
108+
next: "bqstring"
109+
},
110+
{
111+
token: "support.constant",
112+
regex: /#[a-z_]+/
113+
},
114+
{
115+
token: "constant.numeric", // rune
116+
regex:
117+
"'(?:[^\\'\uD800-\uDBFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|" +
118+
stringEscapeRe.replace('"', "") +
119+
")'"
120+
},
121+
{
122+
token: "constant.numeric", // hex
123+
regex: "0[xX][0-9a-fA-F]+\\b"
124+
},
125+
{
126+
token: "constant.numeric", // float
127+
regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
128+
},
129+
{
130+
token: [
131+
"entity.name.function",
132+
"text",
133+
"keyword.operator",
134+
"text",
135+
"keyword"
136+
],
137+
regex: "([a-zA-Z_$][a-zA-Z0-9_$]*)(\\s+)(::)(\\s+)(proc)\\b"
138+
},
139+
{
140+
token: function (val) {
141+
if (val[val.length - 1] == "(") {
142+
return [
143+
{
144+
type: keywordMapper(val.slice(0, -1)) || "support.function",
145+
value: val.slice(0, -1)
146+
},
147+
{
148+
type: "paren.lparen",
149+
value: val.slice(-1)
150+
}
151+
];
152+
}
153+
154+
return keywordMapper(val) || "identifier";
155+
},
156+
regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b\\(?"
157+
},
158+
{
159+
token: "keyword.operator",
160+
regex: operators
161+
},
162+
{
163+
token: "punctuation.operator",
164+
regex: "\\?|\\,|\\;|\\."
165+
},
166+
{
167+
token: "paren.lparen",
168+
regex: "[[({]"
169+
},
170+
{
171+
token: "paren.rparen",
172+
regex: "[\\])}]"
173+
},
174+
{
175+
token: "text",
176+
regex: "\\s+"
177+
}
178+
],
179+
comment: [
180+
{
181+
token: "comment.end",
182+
regex: "\\*\\/",
183+
next: "start"
184+
},
185+
{
186+
defaultToken: "comment"
187+
}
188+
],
189+
bqstring: [
190+
{
191+
token: "string",
192+
regex: "`",
193+
next: "start"
194+
},
195+
{
196+
defaultToken: "string"
197+
}
198+
]
199+
};
200+
201+
this.embedRules(DocCommentHighlightRules, "doc-", [
202+
DocCommentHighlightRules.getEndRule("start")
203+
]);
204+
};
205+
oop.inherits(OdinHighlightRules, TextHighlightRules);
206+
207+
exports.OdinHighlightRules = OdinHighlightRules;

‎src/mode/odin_test.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
if (typeof process !== "undefined") {
2+
require("amd-loader");
3+
}
4+
5+
"use strict";
6+
7+
var EditSession = require("../edit_session").EditSession;
8+
var OdinMode = require("./odin").Mode;
9+
var assert = require("../test/assertions");
10+
11+
module.exports = {
12+
setUp : function() {
13+
this.mode = new OdinMode();
14+
},
15+
16+
"test: indent after opening function": function() {
17+
assert.equal(" ", this.mode.getNextLineIndent("start", "main :: proc() {", " "));
18+
},
19+
20+
"test: indent after opening block": function() {
21+
assert.equal(" ", this.mode.getNextLineIndent("start", "{", " "));
22+
},
23+
24+
"test: indent after opening array": function() {
25+
assert.equal(" ", this.mode.getNextLineIndent("start", "foo := [", " "));
26+
},
27+
28+
"test: indent after opening parentheses": function() {
29+
assert.equal(" ", this.mode.getNextLineIndent("start", "foo := (", " "));
30+
},
31+
32+
"test: indent after case:": function() {
33+
assert.equal(" ", this.mode.getNextLineIndent("start", "case bar:", " "));
34+
},
35+
36+
"test: auto outdent should indent the line with the same indent as the line with the matching opening brace" : function() {
37+
var session = new EditSession([" main :: proc() {", " bla", " }"], this.mode);
38+
this.mode.autoOutdent("start", session, 2);
39+
assert.equal(" }", session.getLine(2));
40+
},
41+
42+
"test: no auto outdent if no matching brace is found" : function() {
43+
var session = new EditSession([" main :: proc()", " bla", " }"], this.mode);
44+
this.mode.autoOutdent("start", session, 2);
45+
assert.equal(" }", session.getLine(2));
46+
}
47+
};
48+
49+
50+
if (typeof module !== "undefined" && module === require.main) {
51+
require("asyncjs").test.testcase(module.exports).exec();
52+
}

0 commit comments

Comments
 (0)
Please sign in to comment.