Skip to content

Commit d1f28a5

Browse files
Introduce the concept of base language
Co-authored-by: Lea Verou <[email protected]>
1 parent 31b467f commit d1f28a5

File tree

3 files changed

+33
-14
lines changed

3 files changed

+33
-14
lines changed

src/core/registry.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { kebabToCamelCase } from '../shared/util';
2-
import { forEach } from '../util/iterables';
2+
import { cloneGrammar } from '../util/extend';
3+
import { forEach, toArray } from '../util/iterables';
34
import { extend } from '../util/language-util';
4-
import type { ComponentProto, Grammar } from '../types';
5+
import type { ComponentProto, Grammar, LanguageProto } from '../types';
56
import type { Prism } from './prism';
67

78
interface Entry {
@@ -60,6 +61,13 @@ export class Registry {
6061
// add aliases
6162
forEach(proto.alias, alias => this.aliasMap.set(alias, id));
6263

64+
if ((proto as LanguageProto).base) {
65+
proto.require = [
66+
(proto as LanguageProto).base as ComponentProto,
67+
...toArray(proto.require),
68+
];
69+
}
70+
6371
// dependencies
6472
forEach(proto.require, register);
6573

@@ -152,11 +160,6 @@ export class Registry {
152160
return entry.evaluatedGrammar;
153161
}
154162

155-
if (typeof grammar === 'object') {
156-
// the grammar is a simple object, so we don't need to evaluate it
157-
return (entry.evaluatedGrammar = grammar);
158-
}
159-
160163
const required = (id: string): Grammar => {
161164
const grammar = this.getLanguage(id);
162165
if (!grammar) {
@@ -165,10 +168,24 @@ export class Registry {
165168
return grammar;
166169
};
167170

168-
return (entry.evaluatedGrammar = grammar({
169-
getLanguage: required,
170-
getOptionalLanguage: id => this.getLanguage(id),
171-
extend: (id, ref) => extend(required(id), id, ref),
172-
}));
171+
const base = (entry?.proto as LanguageProto).base;
172+
// We need this so that any code modifying the base grammar doesn't affect other instances
173+
const baseGrammar = base && cloneGrammar(required(base.id), base.id);
174+
175+
let evaluatedGrammar =
176+
typeof grammar === 'object'
177+
? grammar
178+
: grammar({
179+
base: baseGrammar,
180+
getLanguage: required,
181+
getOptionalLanguage: id => this.getLanguage(id),
182+
extend: (id, ref) => extend(required(id), id, ref),
183+
});
184+
185+
if (base) {
186+
evaluatedGrammar = extend(baseGrammar!, base.id, evaluatedGrammar);
187+
}
188+
189+
return (entry.evaluatedGrammar = evaluatedGrammar);
173190
}
174191
}

src/types.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { Prism } from './core/prism';
21
import type { TokenStream } from './core/classes/token';
2+
import type { Prism } from './core/prism';
33

44
export interface GrammarOptions {
55
readonly getLanguage: (id: string) => Grammar;
6+
readonly base?: Grammar;
67
readonly getOptionalLanguage: (id: string) => Grammar | undefined;
78
readonly extend: (id: string, ref: GrammarTokens) => Grammar;
89
}
@@ -16,6 +17,7 @@ export interface ComponentProtoBase<Id extends string = string> {
1617
export interface LanguageProto<Id extends string = string> extends ComponentProtoBase<Id> {
1718
grammar: Grammar | ((options: GrammarOptions) => Grammar);
1819
plugin?: undefined;
20+
base?: LanguageProto;
1921
}
2022
type PluginType<Name extends string> = unknown;
2123
export interface PluginProto<Id extends string = string> extends ComponentProtoBase<Id> {

src/util/extend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function extend (grammar: Grammar, id: string, reDef: Grammar): Grammar {
3838
return lang;
3939
}
4040

41-
function cloneGrammar (grammar: Grammar, id: string): Grammar {
41+
export function cloneGrammar (grammar: Grammar, id: string): Grammar {
4242
const result: Grammar = {};
4343

4444
const visited = new Map<Grammar, Grammar>();

0 commit comments

Comments
 (0)