Skip to content

Commit

Permalink
Refactor ParserError structure
Browse files Browse the repository at this point in the history
  • Loading branch information
loicknuchel committed Sep 24, 2024
1 parent e5abae3 commit 1ea8677
Show file tree
Hide file tree
Showing 20 changed files with 100 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
function parse(lang, content) {
if (lang === 'aml') return aml.parseAml(content)
if (lang === 'json') return aml.parseJsonDatabase(content)
return {errors: [{name: 'UnsupportedDialect', kind: 'error', message: 'Unsupported source dialect: ' + lang, offset: {start: 0, end: 100}, position: {start: {line: 1, column: 1}, end: {line: 10, column: 10}}}]}
return {errors: [{message: 'Unsupported source dialect: ' + lang, kind: 'UnsupportedDialect', level: 'error', offset: {start: 0, end: 100}, position: {start: {line: 1, column: 1}, end: {line: 10, column: 10}}}]}
}
function format(lang, content) {
if (lang === 'aml') return aml.generateAml(content)
Expand Down
12 changes: 6 additions & 6 deletions cli/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
generateJsonDatabase,
parseJsonDatabase,
ParserError,
ParserErrorLevel,
ParserResult,
TokenEditor,
zodParse
TokenEditor
} from "@azimutt/models";
import {parseAml, generateAml} from "@azimutt/aml";
import {generateAml, parseAml} from "@azimutt/aml";
import {fileRead, fileWrite} from "./utils/file.js";
import {logger} from "./utils/logger.js";

Expand Down Expand Up @@ -41,7 +41,7 @@ export async function convertFile(path: string, opts: Opts): Promise<void> {
function parseDialect(dialect: string, content: string): ParserResult<Database> {
if (dialect === 'aml') return parseAml(content)
if (dialect === 'json') return parseJsonDatabase(content)
return ParserResult.failure([parserError('BadArgument', `Can't parse ${dialect} dialect`)])
return ParserResult.failure([parserError(`Can't parse ${dialect} dialect`, 'BadArgument')])
}

function generateDialect(dialect: string, db: Database): Result<string, string> {
Expand All @@ -64,8 +64,8 @@ function dialectToExtension(dialect: string): string {
return 'txt'
}

function parserError(name: string, message: string): ParserError {
return {name, kind: 'error', message, offset: {start: 0, end: 0}, position: {start: {line: 0, column: 0}, end: {line: 0, column: 0}}}
function parserError(message: string, kind: string): ParserError {
return {message, kind, level: ParserErrorLevel.enum.error, offset: {start: 0, end: 0}, position: {start: {line: 0, column: 0}, end: {line: 0, column: 0}}}
}

function showPosition(pos: TokenEditor): string {
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/Models/ParserError.elm
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
module Models.ParserError exposing (EditorPosition, ParserError, ParserErrorKind(..), TokenOffset, TokenPosition, decode)
module Models.ParserError exposing (EditorPosition, ParserError, ParserErrorLevel(..), TokenOffset, TokenPosition, decode)

import Json.Decode as Decode
import Libs.Json.Decode as Decode


type alias ParserError =
{ name : String
, kind : ParserErrorKind
, message : String
{ message : String
, kind : String
, level : ParserErrorLevel
, offset : TokenOffset
, position : TokenPosition
}


type ParserErrorKind
type ParserErrorLevel
= Error
| Warning
| Info
Expand All @@ -35,20 +35,20 @@ type alias EditorPosition =
decode : Decode.Decoder ParserError
decode =
Decode.map5 ParserError
(Decode.field "name" Decode.string)
(Decode.field "kind" decodeParserErrorKind)
(Decode.field "message" Decode.string)
(Decode.field "kind" Decode.string)
(Decode.field "level" decodeParserErrorLevel)
(Decode.field "offset" decodeTokenOffset)
(Decode.field "position" decodeTokenPosition)


decodeParserErrorKind : Decode.Decoder ParserErrorKind
decodeParserErrorKind =
Decode.string |> Decode.andThen (\v -> v |> parserErrorKindFromString |> Decode.fromMaybe ("'" ++ v ++ "' is not a valid ParserErrorKind"))
decodeParserErrorLevel : Decode.Decoder ParserErrorLevel
decodeParserErrorLevel =
Decode.string |> Decode.andThen (\v -> v |> parserErrorLevelFromString |> Decode.fromMaybe ("'" ++ v ++ "' is not a valid ParserErrorLevel"))


parserErrorKindFromString : String -> Maybe ParserErrorKind
parserErrorKindFromString value =
parserErrorLevelFromString : String -> Maybe ParserErrorLevel
parserErrorLevelFromString value =
case value of
"error" ->
Just Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ update now projectRef msg model =
|> Maybe.map
(\source ->
if source.id /= id then
( model |> mapAmlSidebarM (setErrors [ { name = "EditorError", kind = ParserError.Error, message = "Source has changed", offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )
( model |> mapAmlSidebarM (setErrors [ { message = "Source has changed", kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )

else if String.length (Source.contentStr source) /= length then
( model |> mapAmlSidebarM (setErrors [ { name = "EditorError", kind = ParserError.Error, message = "AML has changed", offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )
( model |> mapAmlSidebarM (setErrors [ { message = "AML has changed", kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )

else
schema |> Maybe.map (\s -> model |> updateSource now source s errors |> setDirty) |> Maybe.withDefault ( model |> mapAmlSidebarM (setErrors errors), Extra.none )
)
|> Maybe.withDefault ( model |> mapAmlSidebarM (setErrors [ { name = "EditorError", kind = ParserError.Error, message = "Source not found", offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )
|> Maybe.withDefault ( model |> mapAmlSidebarM (setErrors [ { message = "Source not found", kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none )

ASourceUpdated id ->
(model.erd |> Maybe.andThen (.sources >> List.findBy .id id))
Expand Down Expand Up @@ -289,7 +289,7 @@ viewSourceEditor : AmlSidebar -> Source -> Html Msg
viewSourceEditor model source =
let
( errors, warnings ) =
( model.errors |> List.filterBy .kind ParserError.Error, model.errors |> List.filterBy .kind ParserError.Warning )
( model.errors |> List.filterBy .level ParserError.Error, model.errors |> List.filterBy .level ParserError.Warning )
in
div [ class "mt-3" ]
[ -- , node "intl-date" [ attribute "lang" "fr-FR", attribute "year" (String.fromInt 2024), attribute "month" (String.fromInt 9) ] []
Expand Down
28 changes: 14 additions & 14 deletions libs/aml/src/aml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ type range \`(subtype = float8, subtype_diff = float8mi)\` # custom type
extra: {}
}
const parsed = parseAmlTest(input)
expect(parsed).toEqual({result: db, errors: [{name: 'Duplicated', kind: 'warning', message: 'Type status already defined at line 2', ...tokenPosition(66, 81, 5, 17, 5, 32)}]})
expect(parsed).toEqual({result: db, errors: [{message: 'Type status already defined at line 2', kind: 'Duplicated', level: 'warning', ...tokenPosition(66, 81, 5, 17, 5, 32)}]})
expect(generateAml(parsed.result || {})).toEqual(input)
})
test('bad schema', () => {
Expand All @@ -180,8 +180,8 @@ type range \`(subtype = float8, subtype_diff = float8mi)\` # custom type
extra: {}
},
errors: [
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> 'bad' <--", ...tokenPosition(2, 4, 1, 3, 1, 5)},
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> 'schema' <--", ...tokenPosition(6, 11, 1, 7, 1, 12)},
{message: "Expecting token of type --> NewLine <-- but found --> 'bad' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(2, 4, 1, 3, 1, 5)},
{message: "Expecting token of type --> NewLine <-- but found --> 'schema' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(6, 11, 1, 7, 1, 12)},
]
})
})
Expand Down Expand Up @@ -250,38 +250,38 @@ type public.status (pending, wip, done)
})
expect(parseAmlTest('posts\n author int -\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], extra: {}},
errors: [{name: 'NoViableAltException', kind: 'error', message: "Expecting: one of these possible Token sequences:\n 1. [Dash]\n 2. [LowerThan]\n 3. [GreaterThan]\nbut found: '\n'", ...tokenPosition(20, 20, 2, 15, 2, 15)}]
errors: [{message: "Expecting: one of these possible Token sequences:\n 1. [Dash]\n 2. [LowerThan]\n 3. [GreaterThan]\nbut found: '\n'", kind: 'NoViableAltException', level: 'error', ...tokenPosition(20, 20, 2, 15, 2, 15)}]
})
expect(parseAmlTest('posts\n author int ->\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], extra: {}},
errors: [{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", ...tokenPosition(21, 21, 2, 16, 2, 16)}]
errors: [{message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(21, 21, 2, 16, 2, 16)}]
})
expect(parseAmlTest('posts\n author int -> users\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], relations: [{src: {entity: 'posts'}, ref: {entity: 'users'}, attrs: [{src: ['author'], ref: ['unknown']}], extra: {line: 2, statement: 1, natural: 'ref', inline: true}}], extra: {}},
})
expect(parseAmlTest('posts\n author int -> users(\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], extra: {}},
errors: [{name: 'EarlyExitException', kind: 'error', message: "Expecting: expecting at least one iteration which starts with one of these possible Token sequences::\n <[WhiteSpace] ,[Identifier]>\nbut found: '\n'", ...tokenPosition(28, 28, 2, 23, 2, 23)}]
errors: [{message: "Expecting: expecting at least one iteration which starts with one of these possible Token sequences::\n <[WhiteSpace] ,[Identifier]>\nbut found: '\n'", kind: 'EarlyExitException', level: 'error', ...tokenPosition(28, 28, 2, 23, 2, 23)}]
})
expect(parseAmlTest('posts\n author int -> users(id\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], relations: [{src: {entity: 'posts'}, ref: {entity: 'users'}, attrs: [{src: ["author"], ref: ["id"]}], extra: {line: 2, statement: 1, inline: true}}], extra: {}},
errors: [{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> RParen <-- but found --> '\n' <--", ...tokenPosition(30, 30, 2, 25, 2, 25)}]
errors: [{message: "Expecting token of type --> RParen <-- but found --> '\n' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(30, 30, 2, 25, 2, 25)}]
})
expect(parseAmlTest('posts\n author int -> users(id)\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], relations: [{src: {entity: 'posts'}, ref: {entity: 'users'}, attrs: [{src: ["author"], ref: ["id"]}], extra: {line: 2, statement: 1, inline: true}}], extra: {}},
})

expect(parseAmlTest('posts\n author int - users(id)\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], relations: [{src: {entity: 'posts'}, ref: {entity: 'users'}, attrs: [{src: ["author"], ref: ["id"]}], extra: {line: 2, statement: 1, inline: true}}], extra: {}},
errors: [{name: 'NoViableAltException', kind: 'error', message: "Expecting: one of these possible Token sequences:\n 1. [Dash]\n 2. [LowerThan]\n 3. [GreaterThan]\nbut found: ' '", ...tokenPosition(20, 20, 2, 15, 2, 15)}]
errors: [{message: "Expecting: one of these possible Token sequences:\n 1. [Dash]\n 2. [LowerThan]\n 3. [GreaterThan]\nbut found: ' '", kind: 'NoViableAltException', level: 'error', ...tokenPosition(20, 20, 2, 15, 2, 15)}]
})
expect(parseAmlTest('posts\n author int users(id)\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}, {name: 'id', extra: {line: 2, statement: 2}}], extra: {}},
// TODO handle error better to not generate a fake entity (id)
errors: [
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> 'users' <--", ...tokenPosition(20, 24, 2, 15, 2, 19)},
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> '(' <--", ...tokenPosition(25, 25, 2, 20, 2, 20)},
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> ')' <--", ...tokenPosition(28, 28, 2, 23, 2, 23)}
{message: "Expecting token of type --> NewLine <-- but found --> 'users' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(20, 24, 2, 15, 2, 19)},
{message: "Expecting token of type --> NewLine <-- but found --> '(' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(25, 25, 2, 20, 2, 20)},
{message: "Expecting token of type --> NewLine <-- but found --> ')' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(28, 28, 2, 23, 2, 23)}
]
})
})
Expand All @@ -291,12 +291,12 @@ type public.status (pending, wip, done)
})
expect(parseAmlTest('posts\n author int f\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}, {name: 'f', extra: {line: 2, statement: 2}}], extra: {}},
errors: [{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> NewLine <-- but found --> 'f' <--", ...tokenPosition(19, 19, 2, 14, 2, 14)}]
errors: [{message: "Expecting token of type --> NewLine <-- but found --> 'f' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(19, 19, 2, 14, 2, 14)}]
})
expect(parseAmlTest('posts\n author int fk\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], extra: {}},
errors: [
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", ...tokenPosition(21, 21, 2, 16, 2, 16)},
{message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(21, 21, 2, 16, 2, 16)},
{...legacy('"fk" is legacy, replace it with "->"'), ...tokenPosition(19, 20, 2, 14, 2, 15)},
]
})
Expand All @@ -308,7 +308,7 @@ type public.status (pending, wip, done)
expect(parseAmlTest('posts\n author int fk users.\n')).toEqual({
result: {entities: [{name: 'posts', attrs: [{name: 'author', type: 'int'}], extra: {line: 1, statement: 1}}], extra: {}},
errors: [
{name: 'MismatchedTokenException', kind: 'error', message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", ...tokenPosition(28, 28, 2, 23, 2, 23)},
{message: "Expecting token of type --> Identifier <-- but found --> '\n' <--", kind: 'MismatchedTokenException', level: 'error', ...tokenPosition(28, 28, 2, 23, 2, 23)},
{...legacy('"fk" is legacy, replace it with "->"'), ...tokenPosition(19, 20, 2, 14, 2, 15)},
{...legacy('"users." is the legacy way, use "users()" instead'), ...tokenPosition(22, 26, 2, 17, 2, 21)},
]
Expand Down
6 changes: 3 additions & 3 deletions libs/aml/src/ast.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {isObject} from "@azimutt/utils";
import {isParserErrorKind, isTokenPosition, ParserErrorKind, TokenPosition} from "@azimutt/models";
import {isParserErrorLevel, isTokenPosition, ParserErrorLevel, TokenPosition} from "@azimutt/models";

export type AmlAst = StatementAst[]
export type StatementAst = NamespaceStatement | EntityStatement | RelationStatement | TypeStatement | EmptyStatement
Expand Down Expand Up @@ -50,7 +50,7 @@ export type DocToken = { token: 'Doc', value: string } & TokenPosition
export type CommentToken = { token: 'Comment', value: string } & TokenPosition

export type TokenInfo = TokenPosition & { issues?: TokenIssue[] }
export type TokenIssue = { name: string, kind: ParserErrorKind, message: string } // TODO: migrate to: { message: string, kind: string, level: ParserErrorLevel }
export type TokenIssue = { message: string, kind: string, level: ParserErrorLevel }

export const isTokenInfo = (value: unknown): value is TokenInfo => isTokenPosition(value) && (!('issues' in value) || ('issues' in value && Array.isArray(value.issues) && value.issues.every(isTokenIssue)))
export const isTokenIssue = (value: unknown): value is TokenIssue => isObject(value) && ('kind' in value && isParserErrorKind(value.kind)) && ('message' in value && typeof value.message === 'string')
export const isTokenIssue = (value: unknown): value is TokenIssue => isObject(value) && ('message' in value && typeof value.message === 'string') && ('kind' in value && typeof value.kind === 'string') && ('level' in value && isParserErrorLevel(value.level))
2 changes: 1 addition & 1 deletion libs/aml/src/docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('docs', () => {
const snippets = path.indexOf('../demos/') !== -1 ? [] : (content.match(amlv1Regex) || []).map((s: string) => s.replace(/^```amlv1\n/, '').replace(/```$/, ''))
snippets.forEach((aml, index) => {
const res = parseAml(aml)
const errors = (res.errors || []).filter(e => e.name !== 'LegacySyntax')
const errors = (res.errors || []).filter(e => e.kind !== 'LegacySyntax')
if (errors.length > 0) {
console.log(`File ${path}, snippet ${index + 1}:\n${aml}`)
expect(errors).toEqual([])
Expand Down
Loading

0 comments on commit 1ea8677

Please sign in to comment.