Skip to content

Commit ff79077

Browse files
committed
feat: add MakeFile mode
1 parent 6c40fd9 commit ff79077

15 files changed

Lines changed: 1589 additions & 8 deletions

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v1.1.5
4+
5+
- Added a Makefile mode
6+
37
## v1.1.4
48

59
- Added an enhanced YAML/YML mode with structural parsing, richer scalar highlighting, indentation, and folding.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "acode-additional-langmodes",
3-
"version": "1.1.4",
3+
"version": "1.1.5",
44
"description": "Additional CodeMirror language modes for Acode",
55
"main": "src/main.js",
66
"repository": "",

plugin.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"id": "acode.additional.langmodes",
44
"name": "Additional Language Modes",
55
"main": "main.js",
6-
"version": "1.1.4",
6+
"version": "1.1.5",
77
"repository": "https://github.com/Acode-Foundation/acode-additional-langmodes",
88
"icon": "icon.png",
99
"minVersionCode": 290,
@@ -18,7 +18,9 @@
1818
"zig",
1919
"svelte",
2020
"jsonc",
21-
"yaml"
21+
"yaml",
22+
"makefile",
23+
"make"
2224
],
2325
"price": 0,
2426
"permissions": [],

plugin.zip

19.4 KB
Binary file not shown.

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Adds CodeMirror language support for languages that are not bundled with Acode.
2626
- Svelte (`.svelte`)
2727
- WGSL (`.wgsl`)
2828
- Gleam (`.gleam`)
29+
- Makefile (`.mk`, `.mak`, `.make`, `Makefile`, `makefile`, `GNUmakefile`)
2930

3031
Community modes are registered only when Acode does not already provide a mode
3132
with the same name. Languages already covered by Acode's

scripts/test-parser.mjs

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,286 @@ for (const sample of gleamSamples) {
610610
}
611611

612612
console.log(`Validated ${gleamSamples.length} Gleam parser fixtures.`);
613+
614+
// Test Makefile Parser
615+
const { parser: makefileParser } = await bundledRequire(
616+
"src/languages/makefile/parser.js",
617+
"acode-makefile-parser-test.cjs",
618+
);
619+
620+
const makefileSamples = [
621+
{
622+
name: "rules, assignments, recipes, special targets",
623+
source: `CC := gcc
624+
CFLAGS = -Wall
625+
626+
.PHONY: all clean
627+
628+
all: main.o
629+
$(CC) $(CFLAGS) -o app main.o
630+
631+
clean:
632+
rm -f app main.o
633+
`,
634+
nodes: [
635+
"VariableAssignment",
636+
"TargetLine",
637+
"RecipeLine",
638+
"SpecialTarget",
639+
"NamedVarRef",
640+
"AssignOp",
641+
"RecipeStart",
642+
],
643+
},
644+
{
645+
name: "conditionals, define, else ifeq",
646+
source: `ifeq ($(DEBUG),1)
647+
CFLAGS += -g
648+
else ifeq ($(DEBUG),2)
649+
CFLAGS += -g -O0
650+
else
651+
CFLAGS += -O2
652+
endif
653+
654+
define greet
655+
echo hello
656+
endef
657+
`,
658+
nodes: [
659+
"Conditional",
660+
"EqConditional",
661+
"ElsifClause",
662+
"ElseClause",
663+
"DefineDirective",
664+
"IfeqKw",
665+
"EndifKw",
666+
"DefineKw",
667+
"EndefKw",
668+
],
669+
},
670+
{
671+
name: "includes, functions, automatic vars, vpath",
672+
source: `include config.mk
673+
-include local.mk
674+
vpath %.h include
675+
676+
SOURCES := $(wildcard src/*.c)
677+
OBJS := $(patsubst %.c,%.o,$(SOURCES))
678+
export PATH
679+
override CFLAGS += -std=c11
680+
681+
%.o: %.c
682+
$(CC) -c -o $@ $<
683+
684+
$(info Building $(words $(OBJS)) objects)
685+
`,
686+
nodes: [
687+
"IncludeDirective",
688+
"IncludeKw",
689+
"VpathDirective",
690+
"FunctionCall",
691+
"FunctionName",
692+
"FunctionStatement",
693+
"PatternWildcard",
694+
"ExportDirective",
695+
"OverrideDirective",
696+
"AutomaticVariable",
697+
],
698+
},
699+
{
700+
name: "static pattern rule and line continue",
701+
source: `# build objects
702+
objects = foo.o \\
703+
bar.o
704+
$(objects): %.o: %.c
705+
$(CC) -c $(CFLAGS) $< -o $@
706+
`,
707+
nodes: [
708+
"Comment",
709+
"LineContinue",
710+
"TargetLine",
711+
"PatternWildcard",
712+
"RecipeLine",
713+
"AutomaticVariable",
714+
],
715+
},
716+
{
717+
name: "order-only auto vars, gnu make 4.4 functions, combined directives",
718+
source: `.DEFAULT_GOAL := help
719+
export override CFLAGS += -Wall
720+
override define GREET
721+
@echo Hello
722+
endef
723+
private define LOG
724+
@echo Log
725+
endef
726+
prog: override CFLAGS += -g
727+
728+
app: main.o | lib.a
729+
$(CC) -o $@ $< $| $(|D) $(let x,1,$(x)) $(intcmp 1,2,lt,eq,gt)
730+
`,
731+
nodes: [
732+
"VariableAssignment",
733+
"ExportDirective",
734+
"OverrideDirective",
735+
"PrivateDirective",
736+
"DefineDirective",
737+
"TargetLine",
738+
"OrderOnlySep",
739+
"AutomaticVariable",
740+
"FunctionCall",
741+
"FunctionName",
742+
],
743+
},
744+
{
745+
name: "non-start directive words stay plain identifiers",
746+
source: `vpath %.h include
747+
FOO else ifeq: bar
748+
`,
749+
nodes: [
750+
"VpathDirective",
751+
"TargetLine",
752+
],
753+
},
754+
];
755+
756+
for (const sample of makefileSamples) {
757+
const tree = makefileParser.parse(sample.source);
758+
const names = new Set();
759+
const errors = [];
760+
761+
tree.iterate({
762+
enter(node) {
763+
names.add(node.name);
764+
if (node.type.isError) {
765+
errors.push([node.from, node.to, sample.source.slice(node.from, node.to)]);
766+
}
767+
},
768+
});
769+
770+
assert.equal(
771+
errors.length,
772+
0,
773+
`${sample.name} produced parse errors: ${JSON.stringify(errors)}`,
774+
);
775+
assert.equal(
776+
tree.length,
777+
sample.source.length,
778+
`${sample.name} was not fully parsed`,
779+
);
780+
for (const node of sample.nodes) {
781+
assert(names.has(node), `${sample.name} did not produce ${node}`);
782+
}
783+
}
784+
785+
console.log(`Validated ${makefileSamples.length} Makefile parser fixtures.`);
786+
787+
const makefileBehaviorBundle = path.join(
788+
os.tmpdir(),
789+
"acode-makefile-behavior-test-bundle.mjs",
790+
);
791+
const makefileBehaviorSource = String.raw`
792+
import assert from "node:assert/strict";
793+
import { EditorState } from "@codemirror/state";
794+
import { foldable, getIndentation, indentUnit, syntaxTree } from "@codemirror/language";
795+
import { classHighlighter, highlightTree } from "@lezer/highlight";
796+
import { makefile, makefileLanguage } from "./src/languages/makefile/index.js";
797+
798+
const source = [
799+
"# Makefile fixture",
800+
"include config.mk",
801+
"CC := gcc",
802+
".PHONY: all",
803+
"all: main.o",
804+
"\t$(CC) -o app $@",
805+
"ifeq ($(DEBUG),1)",
806+
" CFLAGS += -g",
807+
"endif",
808+
"$(info hello)",
809+
"",
810+
].join("\n");
811+
812+
const tree = makefileLanguage.parser.parse(source);
813+
const names = new Set();
814+
const errors = [];
815+
tree.iterate({
816+
enter(node) {
817+
names.add(node.name);
818+
if (node.type.isError) errors.push([node.from, node.to]);
819+
},
820+
});
821+
assert.equal(errors.length, 0, "Makefile fixture produced parse errors");
822+
for (const name of [
823+
"Comment",
824+
"IncludeDirective",
825+
"IncludeKw",
826+
"VariableAssignment",
827+
"SpecialTarget",
828+
"TargetLine",
829+
"RecipeLine",
830+
"Conditional",
831+
"FunctionStatement",
832+
"FunctionName",
833+
"AutomaticVariable",
834+
]) {
835+
assert(names.has(name), "Makefile fixture did not produce " + name);
836+
}
837+
838+
const spans = [];
839+
highlightTree(tree, classHighlighter, (from, to, cls) => {
840+
spans.push({ text: source.slice(from, to), cls });
841+
});
842+
843+
function isHighlighted(text, tokenClass) {
844+
return spans.some((span) => span.text === text && span.cls.includes(tokenClass));
845+
}
846+
847+
assert(spans.some((span) => span.cls.includes("tok-comment") && span.text.includes("Makefile fixture")));
848+
assert(isHighlighted("include", "tok-keyword"));
849+
assert(isHighlighted("ifeq", "tok-keyword"));
850+
assert(isHighlighted("endif", "tok-keyword"));
851+
assert(isHighlighted(":=", "tok-operator"));
852+
assert(isHighlighted("$@", "tok-variableName"));
853+
assert(isHighlighted("\t", "tok-meta"));
854+
855+
const indentSource = "ifeq (a,b)\nCFLAGS = 1\nelse\nCFLAGS = 0\nendif\n";
856+
const indentState = EditorState.create({
857+
doc: indentSource,
858+
extensions: [makefile(), indentUnit.of(" ")],
859+
});
860+
syntaxTree(indentState);
861+
assert.equal(getIndentation(indentState, indentState.doc.line(2).from), 2);
862+
assert.equal(getIndentation(indentState, indentState.doc.line(4).from), 2);
863+
assert.equal(getIndentation(indentState, indentState.doc.line(5).from), 0);
864+
assert(foldable(indentState, indentState.doc.line(1).from, indentState.doc.line(1).to));
865+
866+
const defineSource = "define greet\necho hi\nendef\n";
867+
const defineState = EditorState.create({
868+
doc: defineSource,
869+
extensions: [makefile()],
870+
});
871+
syntaxTree(defineState);
872+
assert(foldable(defineState, defineState.doc.line(1).from, defineState.doc.line(1).to));
873+
874+
console.log("Validated Makefile parsing, highlighting, indentation, and folding fixtures.");
875+
`;
876+
877+
await build({
878+
stdin: {
879+
contents: makefileBehaviorSource,
880+
resolveDir: process.cwd(),
881+
sourcefile: "makefile-behavior-test.mjs",
882+
loader: "js",
883+
},
884+
bundle: true,
885+
format: "esm",
886+
platform: "node",
887+
outfile: makefileBehaviorBundle,
888+
logLevel: "silent",
889+
});
890+
891+
try {
892+
await import(pathToFileURL(makefileBehaviorBundle).href + `?t=${Date.now()}`);
893+
} finally {
894+
fs.rmSync(makefileBehaviorBundle, { force: true });
895+
}

0 commit comments

Comments
 (0)