module.exports = grammar({ name: "m3u", rules: { file: ($) => repeat(seq(choice($._blank, $.uri, $.comment, $.tag), $._eol)), _eol: (_) => /\r?\n/, _blank: (_) => /\n/, uri: (_) => /[^#\n][^\n]*/, comment: (_) => choice(/#EX[^T][^\n]*/, /#E[^X][^\n]*/, /#[^E][^\n]*/), tag: ($) => seq( "#", alias(/EXT[^\n:]*/, $.tag_name), optional( seq( ":", choice( // #EXT-X-KEY: $.attribute_list, // #EXT-X-TARGETDURATION: $._value, // #EXT-X-PLAYLIST-TYPE: $.enum, // #EXT-X-PROGRAM-DATE-TIME: $.date_time_msec, // #EXT-X-BYTERANGE:[@] $.range, // #EXTINF:,[] seq( choice($.dec, $.float), choice(/\s+/, optional(seq(/\s+/, $.attributes))), ",", $.title ) ) ) ) ), _dec: (_) => /\d+/, resolution: ($) => seq($._dec, "x", $._dec), float: ($) => choice(seq("-", $._dec), seq(optional("-"), seq($._dec, ".", $._dec))), dec: ($) => $._dec, hex: (_) => seq("0X", /[\dA-F]+/), string: (_) => seq('"', field("content", repeat1(/[^"]|\\"/)), '"'), _value: ($) => choice($.dec, $.hex, $.resolution, $.float, $.string), attribute_list: ($) => seq(repeat(seq($.attribute, ",")), $.attribute), attribute: ($) => seq(alias(/[A-Za-z][A-Za-z\d-]*/, $.attribute_name), "=", $._value), enum: (_) => choice("VOD", "EVENT"), date_time_msec: (_) => /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z?/, range: ($) => seq($._dec, "@", $._dec), attributes: ($) => seq(repeat(seq($.attribute, /\s+/)), $.attribute), title: (_) => repeat1(/[^\n]/), }, });