Merge pull request #13572 from overleaf/mj-bibtex-grammar

[cm6] Add support for bibtex

GitOrigin-RevId: 28bc8e47c53df1612c1e30cf690e893b0bbf500c
This commit is contained in:
Mathias Jakobsen 2023-07-03 12:18:27 +02:00 committed by Copybot
parent e5d6777211
commit 67e7621633
15 changed files with 413 additions and 101 deletions

88
package-lock.json generated
View file

@ -5796,9 +5796,9 @@
"devOptional": true "devOptional": true
}, },
"node_modules/@lezer/common": { "node_modules/@lezer/common": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz",
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==" "integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA=="
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.0.0", "version": "1.0.0",
@ -5810,22 +5810,22 @@
} }
}, },
"node_modules/@lezer/generator": { "node_modules/@lezer/generator": {
"version": "1.1.3", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.3.0.tgz",
"integrity": "sha512-qGF0I2TTJ+VBjjsVX8FGqKJy3laALBnVbD5EbXEu13Sgszl/vjnxjcZ69O8w9IK8/WtVFQLspU4UjCCUNRlWzA==", "integrity": "sha512-7HfulDoOMOkskb97fnwgpC6StwPVSob4ptc0iuOH72rapNQBbp6lVj05y7vc5IM0E9pjFjiLmNQeiBiSbLpCtA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.2",
"@lezer/lr": "^1.0.0" "@lezer/lr": "^1.3.0"
}, },
"bin": { "bin": {
"lezer-generator": "dist/lezer-generator.cjs" "lezer-generator": "dist/lezer-generator.cjs"
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
"version": "1.1.3", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
@ -5849,17 +5849,17 @@
} }
}, },
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.3.3", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz",
"integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==", "integrity": "sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@lezer/markdown": { "node_modules/@lezer/markdown": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.3.tgz",
"integrity": "sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A==", "integrity": "sha512-QEcXFCKf1TBdVhmxL2V9afJTIs4w795DTl2NKnsYZyMOtMsA+5AlEy0biPo/Ojv05ELkk6HIPSDBj0g+ShlkBw==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0" "@lezer/highlight": "^1.0.0"
@ -41266,10 +41266,10 @@
"@contentful/rich-text-html-renderer": "^16.0.2", "@contentful/rich-text-html-renderer": "^16.0.2",
"@contentful/rich-text-types": "^16.0.2", "@contentful/rich-text-types": "^16.0.2",
"@google-cloud/bigquery": "^6.0.1", "@google-cloud/bigquery": "^6.0.1",
"@lezer/common": "^1.0.2", "@lezer/common": "^1.0.3",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.6",
"@lezer/lr": "^1.3.3", "@lezer/lr": "^1.3.7",
"@lezer/markdown": "^1.0.2", "@lezer/markdown": "^1.0.3",
"@node-oauth/oauth2-server": "^4.3.0", "@node-oauth/oauth2-server": "^4.3.0",
"@opentelemetry/api": "^1.0.4", "@opentelemetry/api": "^1.0.4",
"@opentelemetry/auto-instrumentations-web": "^0.27.2", "@opentelemetry/auto-instrumentations-web": "^0.27.2",
@ -41441,7 +41441,7 @@
"devDependencies": { "devDependencies": {
"@babel/register": "^7.21.0", "@babel/register": "^7.21.0",
"@juggle/resize-observer": "^3.3.1", "@juggle/resize-observer": "^3.3.1",
"@lezer/generator": "^1.1.3", "@lezer/generator": "^1.3.0",
"@testing-library/cypress": "^9.0.0", "@testing-library/cypress": "^9.0.0",
"@testing-library/dom": "^9.3.0", "@testing-library/dom": "^9.3.0",
"@testing-library/react": "^12.1.5", "@testing-library/react": "^12.1.5",
@ -46835,9 +46835,9 @@
"devOptional": true "devOptional": true
}, },
"@lezer/common": { "@lezer/common": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz",
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==" "integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA=="
}, },
"@lezer/css": { "@lezer/css": {
"version": "1.0.0", "version": "1.0.0",
@ -46849,19 +46849,19 @@
} }
}, },
"@lezer/generator": { "@lezer/generator": {
"version": "1.1.3", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.3.0.tgz",
"integrity": "sha512-qGF0I2TTJ+VBjjsVX8FGqKJy3laALBnVbD5EbXEu13Sgszl/vjnxjcZ69O8w9IK8/WtVFQLspU4UjCCUNRlWzA==", "integrity": "sha512-7HfulDoOMOkskb97fnwgpC6StwPVSob4ptc0iuOH72rapNQBbp6lVj05y7vc5IM0E9pjFjiLmNQeiBiSbLpCtA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.2",
"@lezer/lr": "^1.0.0" "@lezer/lr": "^1.3.0"
} }
}, },
"@lezer/highlight": { "@lezer/highlight": {
"version": "1.1.3", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==",
"requires": { "requires": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
@ -46885,17 +46885,17 @@
} }
}, },
"@lezer/lr": { "@lezer/lr": {
"version": "1.3.3", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz",
"integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==", "integrity": "sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==",
"requires": { "requires": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"@lezer/markdown": { "@lezer/markdown": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.3.tgz",
"integrity": "sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A==", "integrity": "sha512-QEcXFCKf1TBdVhmxL2V9afJTIs4w795DTl2NKnsYZyMOtMsA+5AlEy0biPo/Ojv05ELkk6HIPSDBj0g+ShlkBw==",
"requires": { "requires": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0" "@lezer/highlight": "^1.0.0"
@ -50249,11 +50249,11 @@
"@contentful/rich-text-types": "^16.0.2", "@contentful/rich-text-types": "^16.0.2",
"@google-cloud/bigquery": "^6.0.1", "@google-cloud/bigquery": "^6.0.1",
"@juggle/resize-observer": "^3.3.1", "@juggle/resize-observer": "^3.3.1",
"@lezer/common": "^1.0.2", "@lezer/common": "^1.0.3",
"@lezer/generator": "^1.1.3", "@lezer/generator": "^1.3.0",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.6",
"@lezer/lr": "^1.3.3", "@lezer/lr": "^1.3.7",
"@lezer/markdown": "^1.0.2", "@lezer/markdown": "^1.0.3",
"@node-oauth/oauth2-server": "^4.3.0", "@node-oauth/oauth2-server": "^4.3.0",
"@opentelemetry/api": "^1.0.4", "@opentelemetry/api": "^1.0.4",
"@opentelemetry/auto-instrumentations-web": "^0.27.2", "@opentelemetry/auto-instrumentations-web": "^0.27.2",

View file

@ -6,3 +6,5 @@ modules/**/frontend/js/vendor
/public/ /public/
frontend/js/features/source-editor/lezer-latex/latex.mjs frontend/js/features/source-editor/lezer-latex/latex.mjs
frontend/js/features/source-editor/lezer-latex/latex.terms.mjs frontend/js/features/source-editor/lezer-latex/latex.terms.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs

View file

@ -94,5 +94,7 @@ frontend/js/features/source-editor/themes/ace/
# Compiled parser files # Compiled parser files
frontend/js/features/source-editor/lezer-latex/latex.mjs frontend/js/features/source-editor/lezer-latex/latex.mjs
frontend/js/features/source-editor/lezer-latex/latex.terms.mjs frontend/js/features/source-editor/lezer-latex/latex.terms.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs
!**/fixtures/**/*.log !**/fixtures/**/*.log

View file

@ -8,3 +8,5 @@ public/minjs
frontend/stylesheets/components/nvd3.less frontend/stylesheets/components/nvd3.less
frontend/js/features/source-editor/lezer-latex/latex.mjs frontend/js/features/source-editor/lezer-latex/latex.mjs
frontend/js/features/source-editor/lezer-latex/latex.terms.mjs frontend/js/features/source-editor/lezer-latex/latex.terms.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs
frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs

View file

@ -0,0 +1,10 @@
import { LRLanguage } from '@codemirror/language'
import { parser } from '../../lezer-bibtex/bibtex.mjs'
import { bibtexEntryCompletions } from './completions/snippets'
export const BibTeXLanguage = LRLanguage.define({
parser,
languageData: {
autocomplete: bibtexEntryCompletions,
},
})

View file

@ -0,0 +1,95 @@
import { CompletionContext, snippet } from '@codemirror/autocomplete'
type Environment = {
name: string
requiredAttributes: string[]
}
const environments: Environment[] = [
{
name: 'article',
requiredAttributes: ['author', 'title', 'journal', 'year'],
},
{
name: 'book',
requiredAttributes: ['author', 'title', 'publisher', 'year'],
},
{
name: 'booklet',
requiredAttributes: ['key', 'title'],
},
{
name: 'conference',
requiredAttributes: ['key', 'title', 'year'],
},
{
name: 'inbook',
requiredAttributes: ['author', 'title', 'publisher', 'year', 'chapter'],
},
{
name: 'incollection',
requiredAttributes: ['author', 'title', 'booktitle', 'publisher', 'year'],
},
{
name: 'inproceedings',
requiredAttributes: ['author', 'title', 'booktitle', 'year'],
},
{
name: 'manual',
requiredAttributes: ['key', 'title'],
},
{
name: 'masterthesis',
requiredAttributes: ['author', 'title', 'school', 'year'],
},
{
name: 'misc',
requiredAttributes: ['key', 'note'],
},
{
name: 'phdthesis',
requiredAttributes: ['author', 'title', 'school', 'year'],
},
{
name: 'proceedings',
requiredAttributes: ['key', 'title', 'year'],
},
{
name: 'techreport',
requiredAttributes: ['author', 'title', 'institution', 'year'],
},
{
name: 'unpublished',
requiredAttributes: ['author', 'title', 'note'],
},
]
const prepareSnippet = (environment: Environment) => {
return `@${
environment.name
}{#{citation-key},${environment.requiredAttributes.map(
attribute => `
${attribute} = #{}`
)}
}`
}
export function bibtexEntryCompletions(context: CompletionContext) {
const word = context.matchBefore(/@\w*/)
if (word?.from === word?.to && !context.explicit) return null
return {
from: word?.from ?? context.pos,
options: [
...environments.map(env => ({
label: `@${env.name}`,
type: 'snippet',
apply: snippet(prepareSnippet(env)),
})),
{
label: '@string',
type: 'snippet',
apply: snippet('@string{#{string-key} = #{}}'),
},
],
}
}

View file

@ -0,0 +1,8 @@
import { LanguageSupport, indentUnit } from '@codemirror/language'
import { BibTeXLanguage } from './bibtex-language'
export const bibtex = () => {
return new LanguageSupport(BibTeXLanguage, [
indentUnit.of(' '), // 4 spaces
])
}

View file

@ -5,7 +5,6 @@ export const languages = [
name: 'latex', name: 'latex',
extensions: [ extensions: [
'tex', 'tex',
'bib',
'sty', 'sty',
'cls', 'cls',
'clo', 'clo',
@ -50,6 +49,13 @@ export const languages = [
return import('./latex').then(m => m.latex()) return import('./latex').then(m => m.latex())
}, },
}), }),
LanguageDescription.of({
name: 'bibtex',
extensions: ['bib'],
load: () => {
return import('./bibtex').then(m => m.bibtex())
},
}),
LanguageDescription.of({ LanguageDescription.of({
name: 'markdown', name: 'markdown',
extensions: ['md', 'markdown'], extensions: ['md', 'markdown'],

View file

@ -0,0 +1,85 @@
@top Bibliography {
(Declaration | StringDeclaration)*
}
@tokens {
whiteSpace { @whitespace+ }
Identifier { $[a-zA-Z:_0-9-]+ }
StringName { $[a-zA-Z:_] $[a-zA-Z:_0-9-]* }
FieldName {$[a-zA-Z-_0-9]+}
LiteralString {
'"' (!["] | "\\" _)* '"'?
}
EntryTypeName { $[a-zA-Z]+ }
Number { @digit+ }
StringKeyword {"@"$[Ss]$[Tt]$[Rr]$[Ii]$[Nn]$[Gg]}
"@" "{" "}" "\"" "," "#" "@string"
Comment { "%" ![\n]* }
}
// FIXME: Technically skipping comments is wrong here. They can only appear
// alone on a line, but I'm not sure how to express that easily in Lezer
@skip {whiteSpace | Comment}
StringDeclaration {
StringKeyword "{"
Field<StringName>*
"}"
}
Declaration {
EntryName { "@" EntryTypeName } "{"
Identifier
fieldEntry {
("," Field<FieldName> )
}*
("," )?
"}"
}
Field<Name> {
Name "=" Expression
}
Expression {
BracedString |
Number |
StringConcatenation
}
@local tokens {
openBracedContents {"{"}
closeBracedContents {"}"}
@else nonClosingBracedContents
}
@skip {}{
bracedStringContents {
(
nonClosingBracedContents |
nestedBracedString {
openBracedContents
bracedStringContents
closeBracedContents
}
)*
}
BracedString {
"{"
bracedStringContents closeBracedContents
}
}
// TODO: Implement this
@precedence { concatenation @left }
StringConcatenation {
StringConcatenation !concatenation "#" StringConcatenation |
LiteralString |
StringName
}
@external propSource highlighting from "./highlight.mjs"

View file

@ -0,0 +1,15 @@
import { styleTags, tags as t } from '@lezer/highlight'
export const highlighting = styleTags({
LiteralString: t.string,
'BracedString/...': t.string,
Number: t.number,
Identifier: t.name,
'EntryName/...': t.keyword,
FieldName: t.attributeName,
Expression: t.attributeValue,
'#': t.operator,
StringKeyword: t.keyword,
StringName: t.variableName,
Comment: t.comment,
})

View file

@ -103,6 +103,21 @@ export const Visual = (args: any, { globals: { theme } }: any) => {
return <SourceEditor /> return <SourceEditor />
} }
export const Bibtex = (args: any, { globals: { theme } }: any) => {
useScope({
editor: {
sharejs_doc: mockDoc(content.bib, changes.bib),
open_doc_name: 'example.bib',
},
settings: {
...settings,
overallTheme: theme === 'default-' ? '' : theme,
},
})
return <SourceEditor />
}
const MAX_DOC_LENGTH = 2 * 1024 * 1024 // window.maxDocLength const MAX_DOC_LENGTH = 2 * 1024 * 1024 // window.maxDocLength
const mockDoc = (content: string, changes: Array<Record<string, any>> = []) => { const mockDoc = (content: string, changes: Array<Record<string, any>> = []) => {
@ -163,6 +178,7 @@ const changes: Record<string, Array<Record<string, any>>> = {
}, },
], ],
md: [], md: [],
bib: [],
} }
const content = { const content = {
@ -290,4 +306,52 @@ We hope you find Overleaf useful, and do take a look at our \\href{https://www.o
This is **bold** This is **bold**
This is _italic_`, This is _italic_`,
bib: `@book{texbook,
author = {Donald E. Knuth},
year = {1986},
title = {The {\\TeX} Book},
publisher = {Addison-Wesley Professional}
}
@book{latex:companion,
author = {Frank Mittelbach and Michel Gossens
and Johannes Braams and David Carlisle
and Chris Rowley},
year = {2004},
title = {The {\\LaTeX} Companion},
publisher = {Addison-Wesley Professional},
edition = {2}
}
@book{latex2e,
author = {Leslie Lamport},
year = {1994},
title = {{\\LaTeX}: a Document Preparation System},
publisher = {Addison Wesley},
address = {Massachusetts},
edition = {2}
}
@article{knuth:1984,
title={Literate Programming},
author={Donald E. Knuth},
journal={The Computer Journal},
volume={27},
number={2},
pages={97--111},
year={1984},
publisher={Oxford University Press}
}
@inproceedings{lesk:1977,
title={Computer Typesetting of Technical Journals on {UNIX}},
author={Michael Lesk and Brian Kernighan},
booktitle={Proceedings of American Federation of
Information Processing Societies: 1977
National Computer Conference},
pages={879--888},
year={1977},
address={Dallas, Texas}
}
`,
} }

View file

@ -82,10 +82,10 @@
"@contentful/rich-text-html-renderer": "^16.0.2", "@contentful/rich-text-html-renderer": "^16.0.2",
"@contentful/rich-text-types": "^16.0.2", "@contentful/rich-text-types": "^16.0.2",
"@google-cloud/bigquery": "^6.0.1", "@google-cloud/bigquery": "^6.0.1",
"@lezer/common": "^1.0.2", "@lezer/common": "^1.0.3",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.6",
"@lezer/lr": "^1.3.3", "@lezer/lr": "^1.3.7",
"@lezer/markdown": "^1.0.2", "@lezer/markdown": "^1.0.3",
"@node-oauth/oauth2-server": "^4.3.0", "@node-oauth/oauth2-server": "^4.3.0",
"@opentelemetry/api": "^1.0.4", "@opentelemetry/api": "^1.0.4",
"@opentelemetry/auto-instrumentations-web": "^0.27.2", "@opentelemetry/auto-instrumentations-web": "^0.27.2",
@ -257,7 +257,7 @@
"devDependencies": { "devDependencies": {
"@babel/register": "^7.21.0", "@babel/register": "^7.21.0",
"@juggle/resize-observer": "^3.3.1", "@juggle/resize-observer": "^3.3.1",
"@lezer/generator": "^1.1.3", "@lezer/generator": "^1.3.0",
"@testing-library/cypress": "^9.0.0", "@testing-library/cypress": "^9.0.0",
"@testing-library/dom": "^9.3.0", "@testing-library/dom": "^9.3.0",
"@testing-library/react": "^12.1.5", "@testing-library/react": "^12.1.5",

View file

@ -2,23 +2,39 @@ const { buildParserFile } = require('@lezer/generator')
const { writeFileSync, readFileSync } = require('fs') const { writeFileSync, readFileSync } = require('fs')
const path = require('path') const path = require('path')
const options = { const grammars = [
grammarPath: path.resolve( {
__dirname, grammarPath: path.resolve(
'../../frontend/js/features/source-editor/lezer-latex/latex.grammar' __dirname,
), '../../frontend/js/features/source-editor/lezer-latex/latex.grammar'
parserOutputPath: path.resolve( ),
__dirname, parserOutputPath: path.resolve(
'../../frontend/js/features/source-editor/lezer-latex/latex.mjs' __dirname,
), '../../frontend/js/features/source-editor/lezer-latex/latex.mjs'
termsOutputPath: path.resolve( ),
__dirname, termsOutputPath: path.resolve(
'../../frontend/js/features/source-editor/lezer-latex/latex.terms.mjs' __dirname,
), '../../frontend/js/features/source-editor/lezer-latex/latex.terms.mjs'
} ),
},
{
grammarPath: path.resolve(
__dirname,
'../../frontend/js/features/source-editor/lezer-bibtex/bibtex.grammar'
),
parserOutputPath: path.resolve(
__dirname,
'../../frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs'
),
termsOutputPath: path.resolve(
__dirname,
'../../frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs'
),
},
]
function compile() { function compile(grammar) {
const { grammarPath, termsOutputPath, parserOutputPath } = options const { grammarPath, termsOutputPath, parserOutputPath } = grammar
const moduleStyle = 'es' const moduleStyle = 'es'
console.info(`Compiling ${grammarPath}`) console.info(`Compiling ${grammarPath}`)
@ -40,11 +56,11 @@ function compile() {
console.info('Done!') console.info('Done!')
} }
module.exports = { compile, options } module.exports = { compile, grammars }
if (require.main === module) { if (require.main === module) {
try { try {
compile() grammars.forEach(compile)
process.exit(0) process.exit(0)
} catch (err) { } catch (err) {
console.error(err) console.error(err)

View file

@ -1,11 +1,13 @@
import { readFileSync } from 'fs' import { readFileSync } from 'fs'
import { logTree } from '../../frontend/js/features/source-editor/lezer-latex/print-tree.mjs' import { logTree } from '../../frontend/js/features/source-editor/lezer-latex/print-tree.mjs'
import { parser } from '../../frontend/js/features/source-editor/lezer-latex/latex.mjs' import { parser as LaTeXParser } from '../../frontend/js/features/source-editor/lezer-latex/latex.mjs'
import { parser as BibTeXParser } from '../../frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs'
// Runs the lezer-latex parser on a supplied file, and prints the resulting // Runs the lezer-latex or lezer-bibtex parser on a supplied file, and prints the resulting
// parse tree to stdout // parse tree to stdout
// //
// show parse tree: lezer-latex-run.js test/frontend/shared/lezer-latex/examples/amsmath.tex // show parse tree: lezer-latex-run.js test/frontend/shared/lezer-latex/examples/amsmath.tex
// lezer-latex-run.js test/frontend/shared/lezer-latex/examples/overleaf.bib
// show error summary: lezer-latex-run.js coverage test/frontend/shared/lezer-latex/examples/amsmath.tex // show error summary: lezer-latex-run.js coverage test/frontend/shared/lezer-latex/examples/amsmath.tex
let files = process.argv.slice(2) let files = process.argv.slice(2)
@ -27,6 +29,7 @@ function reportErrorCounts(output) {
function parseFile(filename) { function parseFile(filename) {
const text = readFileSync(filename).toString() const text = readFileSync(filename).toString()
const t0 = process.hrtime() const t0 = process.hrtime()
const parser = filename.endsWith('.bib') ? BibTeXParser : LaTeXParser
const tree = parser.parse(text) const tree = parser.parse(text)
const dt = process.hrtime(t0) const dt = process.hrtime(t0)
const timeTaken = dt[0] + dt[1] * 1e-9 const timeTaken = dt[0] + dt[1] * 1e-9

View file

@ -4,45 +4,49 @@ const modulePath = path.resolve(__dirname, '../scripts/lezer-latex/generate.js')
try { try {
fs.accessSync(modulePath, fs.constants.W_OK) fs.accessSync(modulePath, fs.constants.W_OK)
const { compile, options } = require(modulePath) const { compile, grammars } = require(modulePath)
const PLUGIN_NAME = 'lezer-grammar-compiler' const PLUGIN_NAME = 'lezer-grammar-compiler'
class LezerGrammarCompilerPlugin { class LezerGrammarCompilerPlugin {
apply(compiler) { apply(compiler) {
compiler.hooks.make.tap(PLUGIN_NAME, compilation => { for (const grammar of grammars) {
// Add the grammar file to the file paths watched by webpack compiler.hooks.make.tap(PLUGIN_NAME, compilation => {
compilation.fileDependencies.add(options.grammarPath) // Add the grammar file to the file paths watched by webpack
}) compilation.fileDependencies.add(grammar.grammarPath)
compiler.hooks.beforeCompile.tapAsync( })
PLUGIN_NAME, compiler.hooks.beforeCompile.tapAsync(
(_compilation, callback) => { PLUGIN_NAME,
// Check timestamps on grammar and parser files, and re-compile if needed. (_compilation, callback) => {
// (Note: the compiled parser file is watched by webpack, and so will trigger // Check timestamps on grammar and parser files, and re-compile if needed.
// a second compilation immediately after. This seems harmless.) // (Note: the compiled parser file is watched by webpack, and so will trigger
if ( // a second compilation immediately after. This seems harmless.)
!fs.existsSync(options.parserOutputPath) || if (
!fs.existsSync(options.termsOutputPath) !fs.existsSync(grammar.parserOutputPath) ||
) { !fs.existsSync(grammar.termsOutputPath)
console.log('Parser does not exist, compiling') ) {
compile() console.log('Parser does not exist, compiling')
return callback() compile(grammar)
} return callback()
fs.stat(options.grammarPath, (err, grammarStat) => {
if (err) {
return callback(err)
} }
fs.stat(options.parserOutputPath, (err, parserStat) => { fs.stat(grammar.grammarPath, (err, grammarStat) => {
if (err) { if (err) {
return callback(err) return callback(err)
} }
callback() fs.stat(grammar.parserOutputPath, (err, parserStat) => {
if (grammarStat.mtime > parserStat.mtime) { if (err) {
console.log('Grammar file newer than parser file, re-compiling') return callback(err)
compile() }
} callback()
if (grammarStat.mtime > parserStat.mtime) {
console.log(
'Grammar file newer than parser file, re-compiling'
)
compile(grammar)
}
})
}) })
}) }
} )
) }
} }
} }
module.exports = { LezerGrammarCompilerPlugin } module.exports = { LezerGrammarCompilerPlugin }